1use std::collections::HashMap;
14use std::path::Path;
15
16use rucc_base::{Interner, Symbol};
17use rucc_codegen::coverage::Fired;
18use rucc_codegen::elsewhere::Elsewhere;
19use rucc_codegen::lowering::Lowerings;
20use rucc_codegen::pipeline::{self, Machine, Recording};
21use rucc_codegen::pressure::Pressure;
22use rucc_cost::Goal;
23use rucc_diag::{Diagnostic, Severity, SourceMap, Span};
24use rucc_ir::{FpContract, Pic as IrPic, Visibility as IrVisibility};
25use rucc_lex::{Convert, Keywords, PpToken, convert};
26use rucc_lower::Protector as LowerProtector;
27use rucc_sema::{Checker, Context as CheckContext};
28use rucc_session::{
29 Contract, EmitKind, FileSystem, Options, Padding, Pic, Protector, Session, Visibility,
30};
31use rucc_target::TargetInfo;
32use rucc_tuple::{Arch, ObjectFormat};
33
34use crate::preprocess::render;
35
36#[derive(Debug, Clone, PartialEq, Eq, Default)]
43pub enum Artifact {
44 #[default]
47 Nothing,
48 Text(String),
50 Object {
57 bytes: Vec<u8>,
59 defines: Vec<String>,
63 },
64}
65
66impl Artifact {
67 #[must_use]
69 pub fn bytes(&self) -> &[u8] {
70 match self {
71 Artifact::Nothing => &[],
72 Artifact::Text(text) => text.as_bytes(),
73 Artifact::Object { bytes, .. } => bytes,
74 }
75 }
76}
77
78#[derive(Debug, Clone, PartialEq, Eq)]
80pub struct Compiled {
81 pub artifact: Artifact,
83 pub messages: Vec<String>,
85 pub errors: u32,
87 pub fired: Fired,
93 pub pressure: Pressure,
98 pub lowerings: Lowerings,
103 pub dumps: Vec<rucc_opt::Dump>,
109 pub remarks: String,
115 pub deps: Vec<rucc_pp::Dependency>,
120 pub temps: Temps,
127}
128
129#[derive(Debug, Clone, PartialEq, Eq, Default)]
136pub struct Temps {
137 pub preprocessed: Option<String>,
139 pub assembly: Option<String>,
141}
142
143impl Compiled {
144 #[must_use]
146 pub fn failed(&self) -> bool {
147 self.errors > 0
148 }
149
150 #[must_use]
155 pub fn text(&self) -> &str {
156 match &self.artifact {
157 Artifact::Text(text) => text,
158 _ => "",
159 }
160 }
161}
162
163#[must_use]
176pub fn compile(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
177 let mut sess = Session::new(opts.clone());
178 let keywords = Keywords::new(&mut sess.interner, opts.std, opts.gnu_extensions);
182 let mut diagnostics: Vec<Diagnostic> = Vec::new();
183 let mut fired = Fired::new();
185 let mut pressure = Pressure::new();
187 let mut lowerings = Lowerings::asked(opts.lowering_dump.is_some());
188 let mut dumps = Vec::new();
190 let mut remarks = String::new();
191 let mut temps = Temps::default();
193
194 let bytes = match fs.read(Path::new(name)) {
195 Ok(bytes) => bytes,
196 Err(e) => return failure(format!("{name}: {e}")),
197 };
198 let Ok(file) = sess.sources.add_shared(crate::phase::source_name(name), bytes, None) else {
199 return failure(format!("{name}: the source map has no room left for this file"));
200 };
201
202 let mut pp = rucc_pp::Preprocessor::with_prefix_map(opts.prefix_map.macros.clone());
206 let predef = rucc_pp::Predef::for_options(opts);
207 let expanded: Vec<PpToken> = {
208 let mut tokens = Vec::new();
209 {
214 let mut cx =
215 rucc_pp::Context::new(&mut sess.interner, &mut sess.sources, fs, &opts.search);
216 cx.lex = rucc_lex::Options::for_dialect(opts.std, opts.gnu_extensions);
217 cx.pedantic = opts.pedantic;
218 if pp.predefine(&sess.target, &predef, &mut cx).is_err() {
219 return failure(format!(
220 "{name}: the source map has no room for the built in macros"
221 ));
222 }
223 if pp.preinclude(&opts.preincludes, &mut tokens, &mut cx).is_err() {
224 return failure(format!("{name}: the source map has no room for the command line"));
225 }
226 tokens.append(&mut pp.run(file, &mut cx));
227 }
228 if opts.save_temps.wanted() {
229 temps.preprocessed = Some(rucc_pp::print(
230 file,
231 &tokens,
232 pp.line_directives(),
233 &sess.sources,
234 &sess.interner,
235 rucc_pp::PrintOptions { line_markers: opts.line_markers },
236 ));
237 }
238 tokens.iter().map(|token| token.to_pp()).collect()
239 };
240 diagnostics.extend(pp.take_diagnostics());
241 let deps = pp.dependencies().to_vec();
244
245 let cx = Convert {
248 keywords: &keywords,
249 interner: &sess.interner,
250 target: &sess.target,
251 std: opts.std,
252 gnu: opts.gnu_extensions,
253 pedantic: opts.pedantic,
254 };
255 let (tokens, complaints) = convert(&expanded, &cx);
256 diagnostics.extend(complaints);
257
258 let type_names: Vec<Symbol> =
260 sess.target.type_names().iter().filter_map(|&(name, _)| sess.interner.find(name)).collect();
261 let parsed = rucc_parse::parse(
262 &tokens,
263 rucc_parse::Context {
264 interner: &sess.interner,
265 std: opts.std,
266 gnu: opts.gnu_extensions,
267 pedantic: opts.pedantic,
268 error_limit: opts.error_limit as usize,
269 type_names: &type_names,
270 },
271 );
272 let parse_failed = parsed.diagnostics.iter().any(|d| d.severity.is_fatal());
273 diagnostics.extend(parsed.diagnostics);
274
275 let mut artifact = Artifact::Nothing;
276 let mut instrumented = Instrumented::default();
279 if !parse_failed {
280 let mut checker = Checker::new(
281 &parsed.ast,
282 CheckContext {
283 names: &sess.interner,
284 target: &sess.target,
285 std: opts.std,
286 gnu: opts.gnu_extensions,
287 pedantic: opts.pedantic,
288 permissive: opts.permissive,
289 gnu89_inline: opts.gnu89_inline,
290 error_limit: opts.error_limit as usize,
291 builtins: opts.builtins && opts.hosted,
294 no_builtin: &opts.no_builtin,
295 short_enums: opts.short_enums,
296 ms_extensions: sess.ms_extensions(),
297 trapping_math: opts.trapping_math,
298 },
299 );
300 checker.check_unit();
301 let checked = checker.finish();
302 if !checked.failed() {
303 match opts.emit {
304 EmitKind::Tast => {
305 artifact = Artifact::Text(rucc_sema::print(
306 &checked.tast,
307 &checked.types,
308 &sess.interner,
309 ));
310 }
311 EmitKind::TypeGranules => {
315 artifact = Artifact::Text(rucc_types::granule_report(
316 &checked.types,
317 &sess.interner,
318 &sess.target,
319 ));
320 }
321 EmitKind::Ir
322 | EmitKind::MirFinal
323 | EmitKind::Asm
324 | EmitKind::Object
325 | EmitKind::Archive
326 | EmitKind::Executable
327 | EmitKind::SafetySummary => {
328 let mut read = |named: &str| {
333 fs.read(Path::new(named))
334 .map(|bytes| bytes.as_slice().to_vec())
335 .map_err(|why| why.to_string())
336 };
337 let meaning = if opts.debug_info {
343 crate::shapes::collect(
344 &checked.tast,
345 &checked.types,
346 &sess.target,
347 &sess.interner,
348 &sess.sources,
349 )
350 } else {
351 crate::shapes::Meaning::default()
352 };
353 let mut lowered = rucc_lower::lower(
354 crate::phase::source_name(name),
355 rucc_lower::Context {
356 tast: &checked.tast,
357 types: &checked.types,
358 target: &sess.target,
359 names: &mut sess.interner,
360 visibility: match opts.visibility {
361 Visibility::Default => IrVisibility::Default,
362 Visibility::Hidden => IrVisibility::Hidden,
363 Visibility::Protected => IrVisibility::Protected,
364 },
365 protector: match opts.protector {
366 Protector::None => LowerProtector::None,
367 Protector::Buffers => LowerProtector::Buffers,
368 Protector::Strong => LowerProtector::Strong,
369 Protector::All => LowerProtector::All,
370 },
371 wrapping: rucc_lower::Wrapping {
372 signed: opts.wrapping.signed,
373 pointer: opts.wrapping.pointer,
374 trap: opts.wrapping.trap,
375 },
376 aliasing: opts.strict_aliasing,
377 padding: opts.padding == Padding::Ignored,
378 contract: match opts.fp_contract {
379 Contract::Off => FpContract::Off,
380 Contract::On => FpContract::On,
381 Contract::Fast => FpContract::Fast,
382 },
383 align: opts.align_functions,
384 instrument: opts.instrument_functions,
385 read: &mut read,
386 },
387 );
388 let failed = lowered.diagnostics.iter().any(|d| d.severity.is_fatal());
392 if !failed {
393 if let Err(errors) = rucc_ir::verify(&lowered.module, &sess.interner) {
398 for error in errors {
399 diagnostics.push(internal(&format!("invalid IR, {error}")));
400 }
401 } else if let Err(complaints) =
402 instrument(&mut lowered.module, &mut sess.interner, opts)
403 .map(|done| instrumented = done)
404 {
405 diagnostics.extend(complaints);
406 } else if let Err(complaints) = optimize(
407 &mut lowered.module,
408 &mut sess.interner,
409 &sess.target,
410 opts,
411 name,
412 &mut dumps,
413 &mut remarks,
414 ) {
415 diagnostics.extend(complaints);
416 } else if opts.emit == EmitKind::SafetySummary {
417 artifact = Artifact::Text(
422 rucc_safety::summarize(
423 &lowered.module,
424 &sess.interner,
425 name,
426 opts.safety.as_str(),
427 instrumented.checks,
428 instrumented.interposed,
429 instrumented.crossings,
430 )
431 .render(),
432 );
433 } else if opts.emit == EmitKind::Ir {
434 artifact =
439 Artifact::Text(rucc_ir::print(&lowered.module, &sess.interner));
440 } else {
441 match generate(
444 &mut lowered.module,
445 &mut sess.interner,
446 &sess.target,
447 opts,
448 &mut Recording {
449 fired: &mut fired,
450 pressure: &mut pressure,
451 lowerings: &mut lowerings,
452 },
453 &mut temps.assembly,
454 Origin { map: &sess.sources, name, meaning: &meaning },
455 ) {
456 Ok(made) => artifact = made,
457 Err(complaints) => diagnostics.extend(complaints),
458 }
459 }
460 }
461 diagnostics.extend(lowered.diagnostics);
462 }
463 _ => {}
464 }
465 }
466 diagnostics.extend(checked.diagnostics);
467 }
468
469 let mut messages = Vec::with_capacity(diagnostics.len());
470 let mut errors = 0;
471 for diag in &diagnostics {
472 if rucc_diag::dropped(diag, &sess.sources, opts.warnings, opts.system_header_warnings) {
478 continue;
479 }
480 if diag.severity.is_fatal()
481 || (diag.severity == Severity::Warning && opts.warnings_are_errors)
482 {
483 errors += 1;
484 }
485 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
486 }
487 if errors > 0 {
488 artifact = Artifact::Nothing;
490 }
491 Compiled { artifact, messages, errors, fired, pressure, lowerings, dumps, remarks, deps, temps }
494}
495
496#[must_use]
506pub fn compile_ir(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
507 let mut sess = Session::new(opts.clone());
508 if opts.emit != EmitKind::Ir {
509 return failure(format!(
510 "{name}: an input of IR can only be emitted as IR, and `--emit={}` asks for what \
511 the C in front of it became",
512 opts.emit.as_str()
513 ));
514 }
515 let bytes = match fs.read(Path::new(name)) {
516 Ok(bytes) => bytes,
517 Err(e) => return failure(format!("{name}: {e}")),
518 };
519 let Ok(text) = std::str::from_utf8(bytes.as_slice()) else {
520 return failure(format!("{name}: this is not text, so it is not IR"));
521 };
522
523 let module = match rucc_ir::parse(text, &mut sess.interner) {
524 Ok(module) => module,
525 Err(error) => {
526 return failure(format!("{name}:{}: {}", error.line, error.message));
527 }
528 };
529 let mut diagnostics: Vec<Diagnostic> = Vec::new();
530 if let Err(errors) = rucc_ir::verify(&module, &sess.interner) {
531 for error in errors {
532 diagnostics.push(invalid(&format!("invalid IR, {error}")));
533 }
534 }
535 let mut messages = Vec::with_capacity(diagnostics.len());
536 for diag in &diagnostics {
537 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
538 }
539 let errors = u32::try_from(messages.len()).unwrap_or(u32::MAX);
540 let artifact = if errors > 0 {
541 Artifact::Nothing
542 } else {
543 Artifact::Text(rucc_ir::print(&module, &sess.interner))
544 };
545 Compiled {
547 artifact,
548 messages,
549 errors,
550 fired: Fired::new(),
551 pressure: Pressure::new(),
552 lowerings: Lowerings::new(),
553 dumps: Vec::new(),
554 remarks: String::new(),
555 deps: Vec::new(),
556 temps: Temps::default(),
557 }
558}
559
560fn instrument(
583 module: &mut rucc_ir::Module,
584 names: &mut Interner,
585 opts: &Options,
586) -> Result<Instrumented, Vec<Diagnostic>> {
587 if !opts.safety.instruments() {
588 return Ok(Instrumented::default());
589 }
590 let mut checks = rucc_safety::run(module, opts.subobject, opts.promise, opts.races);
591 checks.freed = rucc_safety::ending::checks(module, names);
599 let interposed = rucc_safety::redirect(module, names);
604 let crossings = rucc_safety::witness(module, names);
607 match rucc_ir::verify(module, names) {
608 Ok(()) => Ok(Instrumented { checks, interposed, crossings }),
609 Err(errors) => Err(errors
610 .iter()
611 .map(|e| internal(&format!("invalid IR after check insertion, {e}")))
612 .collect()),
613 }
614}
615
616#[derive(Clone, Copy, Debug, Default)]
622struct Instrumented {
623 checks: rucc_safety::Counts,
625 interposed: usize,
627 crossings: rucc_safety::Sites,
629}
630
631fn optimize(
643 module: &mut rucc_ir::Module,
644 names: &mut Interner,
645 target: &TargetInfo,
646 opts: &Options,
647 file: &str,
648 dumps: &mut Vec<rucc_opt::Dump>,
649 remarks: &mut String,
650) -> Result<(), Vec<Diagnostic>> {
651 let mut settings = rucc_opt::Options::for_level(opts.opt_level);
652 settings.interposition = match opts.interposition {
658 true => replaceable(target, opts),
659 false => IrPic::Executable,
660 };
661 settings.toggles.clone_from(&opts.passes);
662 settings.builtins = opts.builtins && opts.hosted;
667 settings.no_builtin.clone_from(&opts.no_builtin);
668 settings.fuel = opts.pass_fuel.iter().cloned().collect();
669 settings.global_fuel = opts.pass_fuel_global;
670 settings.verify |= opts.verify_each;
671 for (on, spec) in &opts.pass_gates {
672 if let Err(why) = settings.gates.add(*on, spec) {
675 return Err(vec![internal(&why)]);
676 }
677 }
678 for spec in &opts.dump_ir {
679 if let Err(why) = settings.dumps.add(spec) {
682 return Err(vec![internal(&why)]);
683 }
684 }
685 let mut wants = rucc_opt::Wants::none();
686 for spec in &opts.opt_info {
687 if let Err(why) = wants.add(spec) {
690 return Err(vec![internal(&why)]);
691 }
692 }
693 let report = rucc_opt::run(module, names, &settings);
694 remarks.push_str(&rucc_opt::optinfo::render(file, &report, names, wants));
695 dumps.extend(report.dumps);
696 match report.broke.is_empty() {
697 true => Ok(()),
698 false => Err(report.broke.iter().map(|why| internal(why)).collect()),
699 }
700}
701
702fn replaceable(target: &TargetInfo, opts: &Options) -> IrPic {
736 match (target.tuple.os().object_format(), opts.pic) {
737 (Some(ObjectFormat::Elf), Pic::Library) => IrPic::Library,
738 _ => IrPic::Executable,
739 }
740}
741
742#[derive(Clone, Copy)]
749struct Origin<'a> {
750 map: &'a SourceMap,
752 name: &'a str,
754 meaning: &'a crate::shapes::Meaning,
756}
757
758fn generate(
759 module: &mut rucc_ir::Module,
760 names: &mut Interner,
761 target: &TargetInfo,
762 opts: &Options,
763 recording: &mut Recording<'_>,
764 assembly: &mut Option<String>,
765 origin: Origin<'_>,
766) -> Result<Artifact, Vec<Diagnostic>> {
767 let Some(machine) = Machine::for_target(target) else {
768 return Err(vec![unsupported(&format!(
769 "there is no back end for {} in this compiler yet, so there is nothing to generate",
770 target.tuple
771 ))]);
772 };
773 if opts.protector != Protector::None && machine.conv.guard.is_none() {
778 return Err(vec![unsupported(&format!(
779 "{} is not supported for {} yet, because the stack protector on that target is not \
780 the one this compiler writes",
781 opts.protector, target.tuple
782 ))]);
783 }
784 if opts.control.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
790 return Err(vec![unsupported(&format!(
791 "-fcf-protection={} is not supported for {} yet, because what says a file was built \
792 for it there is not the note this compiler writes",
793 opts.control, target.tuple
794 ))]);
795 }
796 let profile = match machine.conv.trace {
802 Some(trace) => opts.profile.then(|| opts.hook.early(trace.fentry)),
803 None if opts.profile => {
804 return Err(vec![unsupported(&format!(
805 "-pg is not supported for {} yet, because the profiler's hook on that target is \
806 not the one this compiler calls",
807 target.tuple
808 ))]);
809 }
810 None => None,
811 };
812 if opts.patchable.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
817 return Err(vec![unsupported(&format!(
818 "-fpatchable-function-entry= is not supported for {} yet, because what records where \
819 the room is there is not the section this compiler writes",
820 target.tuple
821 ))]);
822 }
823 let flags = pipeline::Flags {
824 frame_pointer: opts.frame_pointer,
825 red_zone: opts.red_zone,
826 stack_clash: opts.stack_clash,
827 landing: opts.control.branch(),
828 profile: match profile {
829 None => pipeline::Profile::No,
830 Some(true) => pipeline::Profile::Early,
831 Some(false) => pipeline::Profile::Late,
832 },
833 patch: pipeline::Room { after: opts.patchable.after(), before: opts.patchable.before },
834 reorder: opts.reorder_blocks.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
841 reuse: opts.stack_reuse.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
846 schedule: opts.schedule_insns.unwrap_or_else(|| opts.opt_level.schedules()),
851 align_loops: opts.align_loops.unwrap_or(false),
857 accurate: opts.cycle_accurate_model,
859 verify: opts.verify_each,
862 goal: Goal::for_size(opts.opt_level.is_size()),
867 };
868
869 if opts.safety.instruments() {
878 rucc_opt::heap::annotate(module, names);
888 rucc_safety::handover::arrange(module);
895 rucc_safety::lower(module, names);
896 if let Err(errors) = rucc_ir::verify(module, names) {
897 return Err(errors
898 .iter()
899 .map(|e| internal(&format!("invalid IR after check lowering, {e}")))
900 .collect());
901 }
902 }
903
904 let elsewhere = Elsewhere::of(module, replaceable(target, opts), target.object_format);
913
914 let mut funcs = Vec::new();
915 let mut complaints = Vec::new();
916 for id in module.funcs() {
917 if module[id].is_declaration() {
918 continue;
919 }
920 match pipeline::compile_recording(
921 &mut module[id],
922 names,
923 &machine,
924 &elsewhere,
925 flags,
926 recording,
927 ) {
928 Ok(func) => funcs.push(func),
929 Err(why) => {
930 let name = names.resolve(module[id].name).to_owned();
931 let span = why.inst().map_or(Span::DUMMY, |inst| module[id].span(inst));
934 let said = format!("cannot generate code for '{name}': {why}");
935 complaints.push(unsupported_at(&said, span));
936 }
937 }
938 }
939 if !complaints.is_empty() {
940 return Err(complaints);
941 }
942 let (globals, aliases) = match opts.emit {
948 EmitKind::Asm | EmitKind::Object | EmitKind::Archive | EmitKind::Executable => (
949 rucc_asm::globals(module, names, target.object_format).map_err(refused)?,
950 rucc_asm::aliases(module, names).map_err(refused)?,
951 ),
952 _ => (rucc_asm::Globals::default(), Vec::new()),
953 };
954 let unwind = opts.unwinds();
958 match opts.emit {
959 EmitKind::Asm => {
960 rucc_asm::print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
961 .map(Artifact::Text)
962 .map_err(refused)
963 }
964 EmitKind::Object | EmitKind::Archive | EmitKind::Executable => {
968 if opts.save_temps.wanted() {
969 let listing = rucc_asm::print(
970 &funcs,
971 &globals,
972 &aliases,
973 names,
974 target,
975 unwind,
976 output(opts, target),
977 );
978 *assembly = Some(listing.map_err(refused)?);
979 }
980 if rucc_asm::kept(&funcs, names, target) {
987 if opts.debug_info {
988 return Err(vec![unsupported(
989 "debug information for a unit with an `asm` template kept as text",
990 )]);
991 }
992 let listing = rucc_asm::print(
993 &funcs,
994 &globals,
995 &aliases,
996 names,
997 target,
998 unwind,
999 output(opts, target),
1000 )
1001 .map_err(refused)?;
1002 let read = rucc_asm::read(&listing).map_err(|trouble| {
1003 vec![unsupported(&format!(
1004 "an `asm` template kept as text, whose listing the assembler stopped at on \
1005 line {}: {}",
1006 trouble.line, trouble.why
1007 ))]
1008 })?;
1009 let defines = rucc_object::assembled_defines(&read);
1010 let bytes =
1011 rucc_object::assembled(&read, &TargetInfo::new(opts.target)).map_err(wrote)?;
1012 return Ok(Artifact::Object { bytes, defines });
1013 }
1014 let assembled = rucc_asm::assemble(&funcs, names, target, unwind, opts.debug_info)
1015 .map_err(refused)?;
1016 let data = globals.image();
1017 let info = if opts.debug_info {
1021 describe(&assembled, &data, &funcs, origin, opts, target)
1022 .map_err(|why| vec![internal(&why)])?
1023 } else {
1024 rucc_object::Info::default()
1025 };
1026 let text = assembled.text;
1027 let bytes =
1030 rucc_object::write(&text, &data, &aliases, target, output(opts, target), &info)
1031 .map_err(wrote)?;
1032 let defines = rucc_object::defines(&text, &data, &aliases, target).map_err(wrote)?;
1037 Ok(Artifact::Object { bytes, defines })
1038 }
1039 _ => Ok(Artifact::Text(rucc_mir::print(&funcs, names, target.regs))),
1040 }
1041}
1042
1043fn describe(
1062 assembled: &rucc_asm::Assembled,
1063 data: &rucc_object::Data,
1064 machine: &[rucc_mir::Func],
1065 origin: Origin<'_>,
1066 opts: &Options,
1067 target: &TargetInfo,
1068) -> Result<rucc_object::Info, String> {
1069 let rucc_asm::Assembled { text, lines, frames } = assembled;
1070 let rewrite = |path: &str| opts.prefix_map.debug.apply(path).into_owned();
1071 let mut files: Vec<String> = Vec::new();
1076 let mut funcs = Vec::with_capacity(text.funcs.len());
1077 for ((extent, rows), built) in text.funcs.iter().zip(lines).zip(machine) {
1078 let mut out: Vec<rucc_debug::Row> = Vec::with_capacity(rows.len());
1079 for row in rows {
1080 if row.span.is_dummy() {
1081 continue;
1082 }
1083 let Some(at) = origin.map.presumed(row.span.lo) else {
1084 continue;
1085 };
1086 let which = interned(&mut files, rewrite(at.name));
1087 let place = rucc_debug::Row {
1088 at: row.at as u64,
1089 file: which,
1090 line: at.line,
1091 column: at.column,
1092 };
1093 match out.last() {
1103 Some(last) if last.at == place.at => {}
1104 _ => out.push(place),
1105 }
1106 }
1107 if let Some(first) = out.first_mut() {
1114 first.at = 0;
1115 }
1116 let known = origin.meaning.funcs.get(&extent.name);
1122 let decl = known.map(|known| rucc_debug::Place {
1123 file: interned(&mut files, rewrite(&known.file)),
1124 line: known.line,
1125 });
1126 let mut sig = known.and_then(|known| known.sig.clone());
1135 let mut placed: Vec<(u32, i32)> = built.locals.clone();
1136 let mut spots = stretches(extent, rows, built, target);
1137 for &decl in &built.sharing {
1142 if !spots.iter().any(|(at, _)| *at == decl) {
1143 spots.push((decl, Vec::new()));
1144 }
1145 }
1146 if let (Some(sig), Some(known)) = (sig.as_mut(), known) {
1147 for (param, decl) in sig.params.iter_mut().zip(&known.params) {
1148 let Some(decl) = *decl else { continue };
1149 if let Some(which) = placed.iter().position(|&(at, _)| at == decl) {
1150 let at = rucc_debug::Held::Frame(i64::from(placed.remove(which).1));
1151 param.spot = Some(rucc_debug::Spot::Always(at));
1152 continue;
1153 }
1154 let Some(which) = spots.iter().position(|(at, _)| *at == decl) else { continue };
1158 param.spot = Some(rucc_debug::Spot::Over(spots.remove(which).1));
1159 }
1160 }
1161 let mut locals = Vec::with_capacity(placed.len() + spots.len());
1165 let mut wants: Vec<Option<usize>> = Vec::with_capacity(locals.capacity());
1169 for (decl, at) in placed {
1170 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1171 wants.push(named.scope);
1172 locals.push(rucc_debug::Local {
1173 name: named.name.clone(),
1174 ty: named.ty,
1175 decl: Some(rucc_debug::Place {
1176 file: interned(&mut files, rewrite(&named.file)),
1177 line: named.line,
1178 }),
1179 spot: rucc_debug::Spot::Always(rucc_debug::Held::Frame(i64::from(at))),
1180 scope: None,
1181 });
1182 }
1183 spots.sort_by_key(|(decl, _)| *decl);
1187 for (decl, spans) in spots {
1188 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1189 wants.push(named.scope);
1190 locals.push(rucc_debug::Local {
1191 name: named.name.clone(),
1192 ty: named.ty,
1193 decl: Some(rucc_debug::Place {
1194 file: interned(&mut files, rewrite(&named.file)),
1195 line: named.line,
1196 }),
1197 spot: rucc_debug::Spot::Over(spans),
1198 scope: None,
1199 });
1200 }
1201 let (scopes, at) = nests(&wants, &origin.meaning.scopes, extent, rows);
1206 for (local, want) in locals.iter_mut().zip(&wants) {
1207 local.scope = want.and_then(|want| at.get(&want).copied());
1208 }
1209 funcs.push(rucc_debug::Function {
1210 name: extent.name.clone(),
1211 len: extent.len as u64,
1212 rows: out,
1213 decl,
1214 sig,
1215 external: known.is_some_and(|known| known.external),
1216 locals,
1217 scopes,
1218 });
1219 }
1220 let mut globals = Vec::new();
1227 for object in &data.objects {
1228 let Some(held) = origin.meaning.objects.get(&object.name) else { continue };
1229 globals.push(rucc_debug::Global {
1230 name: object.name.clone(),
1231 ty: held.ty,
1232 decl: Some(rucc_debug::Place {
1233 file: interned(&mut files, rewrite(&held.file)),
1234 line: held.line,
1235 }),
1236 external: held.external,
1237 });
1238 }
1239 let unit = rucc_debug::Unit {
1240 name: rewrite(origin.name),
1241 dir: rewrite(opts.working_dir.as_deref().unwrap_or(".")),
1244 producer: format!("rucc {}", crate::VERSION),
1245 files,
1246 types: origin.meaning.types.clone(),
1247 funcs,
1248 globals,
1249 pointer: u8::try_from(target.pointer_width / 8).unwrap_or(8),
1250 frames: opts.unwinds() || frames.is_some(),
1255 };
1256 let mut info = rucc_debug::write(&unit).map_err(|why| why.to_string())?;
1257 info.chunks.extend(frames.clone());
1258 Ok(info)
1259}
1260
1261fn stretches(
1272 extent: &rucc_object::Extent,
1273 rows: &[rucc_asm::Row],
1274 built: &rucc_mir::Func,
1275 target: &TargetInfo,
1276) -> Vec<(u32, Vec<rucc_debug::Span>)> {
1277 let (false, Some(regs)) = (built.kept.is_empty(), target.call_regs) else {
1280 return Vec::new();
1281 };
1282 let ends = ends(extent, rows);
1283 let mut bounds = vec![None; built.inst_count()];
1284 for (which, row) in rows.iter().enumerate() {
1285 let Some(inst) = row.inst else { continue };
1286 bounds[inst.index()] = Some((row.at as u64, ends[which]));
1287 }
1288 let mut spots: Vec<(u32, Vec<rucc_debug::Span>)> = Vec::new();
1289 for kept in &built.kept {
1290 let (Some((from, _)), Some((_, to))) = (bounds[kept.from.index()], bounds[kept.to.index()])
1291 else {
1292 continue;
1293 };
1294 if to <= from {
1295 continue;
1296 }
1297 let held = match kept.at {
1298 rucc_mir::Where::Reg { reg, class } => match regs.dwarf(class, reg) {
1301 Some(number) => rucc_debug::Held::Reg(number),
1302 None => continue,
1303 },
1304 rucc_mir::Where::Frame(at) => rucc_debug::Held::Frame(i64::from(at)),
1305 };
1306 let span = rucc_debug::Span { from, len: to - from, held };
1307 match spots.iter_mut().find(|(decl, _)| *decl == kept.decl) {
1308 Some((_, spans)) => spans.push(span),
1309 None => spots.push((kept.decl, vec![span])),
1310 }
1311 }
1312 for (_, spans) in &mut spots {
1313 *spans = settle(std::mem::take(spans));
1314 }
1315 spots.retain(|(_, spans)| !spans.is_empty());
1316 spots
1317}
1318
1319fn ends(extent: &rucc_object::Extent, rows: &[rucc_asm::Row]) -> Vec<u64> {
1329 let mut out = vec![extent.len as u64; rows.len()];
1330 let mut next = extent.len as u64;
1331 for which in (0..rows.len()).rev() {
1332 let at = rows[which].at as u64;
1333 out[which] = match next > at {
1336 true => next,
1337 false => out.get(which + 1).copied().unwrap_or(extent.len as u64),
1338 };
1339 next = next.min(at);
1340 }
1341 out
1342}
1343
1344fn nests(
1360 wants: &[Option<usize>],
1361 scopes: &[crate::shapes::Scope],
1362 extent: &rucc_object::Extent,
1363 rows: &[rucc_asm::Row],
1364) -> (Vec<rucc_debug::Scope>, HashMap<usize, usize>) {
1365 let mut needed: Vec<usize> = Vec::new();
1366 for &want in wants {
1367 let mut up = want;
1368 while let Some(which) = up {
1369 if needed.contains(&which) {
1370 break;
1371 }
1372 needed.push(which);
1373 up = scopes.get(which).and_then(|scope| scope.parent);
1374 }
1375 }
1376 needed.sort_unstable();
1379 let at: HashMap<usize, usize> =
1380 needed.iter().enumerate().map(|(which, &scope)| (scope, which)).collect();
1381 let ends = ends(extent, rows);
1382 let out = needed
1383 .iter()
1384 .map(|&which| {
1385 let scope = &scopes[which];
1386 rucc_debug::Scope {
1387 parent: scope.parent.and_then(|parent| at.get(&parent).copied()),
1388 over: spread(scope.span, &ends, rows),
1389 }
1390 })
1391 .collect();
1392 (out, at)
1393}
1394
1395fn spread(span: Span, ends: &[u64], rows: &[rucc_asm::Row]) -> Vec<rucc_debug::Reach> {
1403 let mut out: Vec<rucc_debug::Reach> = Vec::new();
1404 for (which, row) in rows.iter().enumerate() {
1405 if row.span.is_dummy() || row.span.lo < span.lo || row.span.hi > span.hi {
1406 continue;
1407 }
1408 let (from, to) = (row.at as u64, ends[which]);
1409 if to <= from {
1410 continue;
1411 }
1412 match out.last_mut() {
1413 Some(last) if last.from + last.len >= from => {
1414 last.len = to.saturating_sub(last.from).max(last.len);
1415 }
1416 _ => out.push(rucc_debug::Reach { from, len: to - from }),
1417 }
1418 }
1419 out
1420}
1421
1422fn settle(mut spans: Vec<rucc_debug::Span>) -> Vec<rucc_debug::Span> {
1439 spans.sort_by_key(|span| (span.from, span.len));
1440 for which in 0..spans.len() {
1441 let (from, end, held) =
1442 (spans[which].from, spans[which].from + spans[which].len, spans[which].held);
1443 let later = spans[which + 1..]
1444 .iter()
1445 .take_while(|later| later.from < end)
1446 .find(|later| later.from > from && later.held != held);
1447 if let Some(later) = later {
1448 spans[which].len = later.from - from;
1449 }
1450 }
1451 let mut edges: Vec<u64> =
1454 spans.iter().flat_map(|span| [span.from, span.from + span.len]).collect();
1455 edges.sort_unstable();
1456 edges.dedup();
1457 let mut out: Vec<rucc_debug::Span> = Vec::new();
1458 let mut first = 0;
1459 for pair in edges.windows(2) {
1460 let (from, to) = (pair[0], pair[1]);
1461 while spans.get(first).is_some_and(|span| span.from + span.len <= from) {
1465 first += 1;
1466 }
1467 let mut held = None;
1468 let mut agreed = true;
1469 for span in &spans[first..] {
1470 if span.from >= to {
1471 break;
1472 }
1473 if span.from > from || span.from + span.len < to {
1474 continue;
1475 }
1476 match held {
1477 None => held = Some(span.held),
1478 Some(seen) => agreed &= seen == span.held,
1479 }
1480 }
1481 let (Some(held), true) = (held, agreed) else { continue };
1482 match out.last_mut() {
1483 Some(last) if last.from + last.len == from && last.held == held => {
1484 last.len += to - from
1485 }
1486 _ => out.push(rucc_debug::Span { from, len: to - from, held }),
1487 }
1488 }
1489 out
1490}
1491
1492fn interned(files: &mut Vec<String>, name: String) -> usize {
1498 match files.iter().position(|have| *have == name) {
1499 Some(which) => which,
1500 None => {
1501 files.push(name);
1502 files.len() - 1
1503 }
1504 }
1505}
1506
1507fn output(opts: &Options, target: &TargetInfo) -> rucc_object::Output {
1519 let mut features = 0;
1520 if target.tuple.arch() == Arch::X86_64 {
1521 if opts.control.branch() {
1522 features |= rucc_object::Property::IBT;
1523 }
1524 if opts.control.ret() {
1525 features |= rucc_object::Property::SHSTK;
1526 }
1527 }
1528 rucc_object::Output {
1529 sections: rucc_object::Sections {
1530 functions: opts.function_sections,
1531 data: opts.data_sections,
1532 },
1533 property: rucc_object::Property { features },
1534 }
1535}
1536
1537fn wrote(why: rucc_object::Error) -> Vec<Diagnostic> {
1543 match why {
1544 rucc_object::Error::Format { .. } => vec![unsupported(&why.to_string())],
1545 rucc_object::Error::Refused { .. } => vec![internal(&why.to_string())],
1546 }
1547}
1548
1549fn refused(why: rucc_asm::Error) -> Vec<Diagnostic> {
1556 match why {
1557 rucc_asm::Error::Thread { .. }
1558 | rucc_asm::Error::IFunc { .. }
1559 | rucc_asm::Error::Frame { .. } => {
1560 vec![unsupported(&why.to_string())]
1561 }
1562 _ => vec![internal(&why.to_string())],
1563 }
1564}
1565
1566fn unsupported(message: &str) -> Diagnostic {
1572 unsupported_at(message, Span::DUMMY)
1573}
1574
1575fn unsupported_at(message: &str, span: Span) -> Diagnostic {
1581 Diagnostic::error(message.to_owned(), span)
1582 .with_code("E0653")
1583 .note("this construct is not lowered yet, see https://github.com/tamnd/rucc/issues", span)
1584}
1585
1586fn invalid(message: &str) -> Diagnostic {
1588 Diagnostic::error(message.to_owned(), Span::DUMMY).with_code("E0661")
1589}
1590
1591fn internal(message: &str) -> Diagnostic {
1593 Diagnostic::error(format!("internal error: {message}"), Span::DUMMY)
1594 .with_code("E0652")
1595 .note("this is a bug in rucc rather than in the program, please report it", Span::DUMMY)
1596}
1597
1598fn failure(message: String) -> Compiled {
1601 Compiled {
1602 artifact: Artifact::Nothing,
1603 messages: vec![format!("rucc: error: {message}")],
1604 errors: 1,
1605 fired: Fired::new(),
1606 pressure: Pressure::new(),
1607 lowerings: Lowerings::new(),
1608 dumps: Vec::new(),
1609 remarks: String::new(),
1610 deps: Vec::new(),
1611 temps: Temps::default(),
1612 }
1613}
1614
1615#[cfg(test)]
1616mod tests {
1617 use rucc_session::{MemoryFileSystem, Std};
1618 use rucc_target::Triple;
1619
1620 use super::*;
1621
1622 fn options() -> Options {
1623 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1624 opts.emit = EmitKind::Tast;
1625 opts
1626 }
1627
1628 fn run(opts: &Options, source: &str) -> Compiled {
1629 let mut fs = MemoryFileSystem::new();
1630 fs.insert("/main.c", source.to_owned().into_bytes());
1631 compile(opts, "/main.c", &fs)
1632 }
1633
1634 fn freestanding() -> Options {
1638 let mut opts = options();
1639 opts.hosted = false;
1640 opts.search.push_system(rucc_session::runtime::DIR);
1641 opts
1642 }
1643
1644 fn shipped(source: &str) -> String {
1646 let result = run(&freestanding(), source);
1647 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1648 result.text().to_owned()
1649 }
1650
1651 fn tast(source: &str) -> String {
1653 let result = run(&options(), source);
1654 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1655 result.text().to_owned()
1656 }
1657
1658 #[test]
1659 fn the_shipped_stdarg_declares_a_list_and_the_four_operators() {
1660 let text = shipped(concat!(
1661 "#include <stdarg.h>\n",
1662 "int sum(int n, ...) {\n",
1663 " va_list ap, copy;\n",
1664 " va_start(ap, n);\n",
1665 " va_copy(copy, ap);\n",
1666 " int total = va_arg(ap, int) + va_arg(copy, int);\n",
1667 " va_end(ap);\n",
1668 " va_end(copy);\n",
1669 " return total;\n",
1670 "}\n",
1671 ));
1672 assert!(text.contains("va-start"), "{text}");
1673 assert!(text.contains("va-copy"), "{text}");
1674 assert!(text.contains("va-arg"), "{text}");
1675 assert!(text.contains("va-end"), "{text}");
1676 }
1677
1678 #[test]
1682 fn stdarg_hands_out_the_type_alone_when_that_is_all_that_was_asked_for() {
1683 let text = shipped(concat!(
1684 "#define __need___va_list\n",
1685 "#include <stdarg.h>\n",
1686 "int vprint(const char *f, __gnuc_va_list ap);\n",
1687 "#ifdef va_start\n",
1688 "#error va_start should not be defined\n",
1689 "#endif\n",
1690 "#ifdef _VA_LIST_DEFINED\n",
1691 "#error va_list should not have been made\n",
1692 "#endif\n",
1693 ));
1694 assert!(text.contains("vprint"), "{text}");
1695 }
1696
1697 #[test]
1700 fn stddef_answers_one_piece_at_a_time_and_the_next_request_still_gets_through() {
1701 let text = shipped(concat!(
1702 "#define __need_size_t\n",
1703 "#include <stddef.h>\n",
1704 "#ifdef offsetof\n",
1705 "#error offsetof should not be defined yet\n",
1706 "#endif\n",
1707 "#define __need_ptrdiff_t\n",
1708 "#include <stddef.h>\n",
1709 "#include <stddef.h>\n",
1710 "size_t a;\n",
1711 "ptrdiff_t b;\n",
1712 "wchar_t c;\n",
1713 "max_align_t d;\n",
1714 "void *e = NULL;\n",
1715 "struct P { int x; long y; };\n",
1716 "size_t f = offsetof(struct P, y);\n",
1717 ));
1718 assert!(text.contains("decl #0 a : unsigned long"), "{text}");
1719 assert!(text.contains("decl #1 b : long"), "{text}");
1720 }
1721
1722 #[test]
1723 fn the_shipped_limits_and_float_are_the_targets_own_answers() {
1724 let text = shipped(concat!(
1725 "#include <limits.h>\n",
1726 "#include <float.h>\n",
1727 "int bits = CHAR_BIT;\n",
1728 "long big = LONG_MAX;\n",
1729 "int low = INT_MIN;\n",
1730 "int radix = FLT_RADIX;\n",
1731 "int digits = DBL_MANT_DIG;\n",
1732 ));
1733 assert!(text.contains("const 8 : int"), "{text}");
1734 assert!(text.contains("const 9223372036854775807 : long"), "{text}");
1735 assert!(text.contains("const 2 : int"), "{text}");
1736 assert!(text.contains("const 53 : int"), "{text}");
1737 }
1738
1739 #[test]
1743 fn the_shipped_stdint_writes_the_whole_set_when_there_is_no_library_to_defer_to() {
1744 let text = shipped(concat!(
1745 "#include <stdint.h>\n",
1746 "int64_t a = INT64_C(1);\n",
1747 "uint_least16_t b;\n",
1748 "intptr_t c;\n",
1749 "uintmax_t d = UINTMAX_MAX;\n",
1750 "int wide = sizeof(int_fast64_t);\n",
1751 ));
1752 assert!(text.contains("decl #0 a : long"), "{text}");
1753 assert!(text.contains("decl #1 b : unsigned short"), "{text}");
1754 assert!(text.contains("decl #2 c : long"), "{text}");
1755 }
1756
1757 #[test]
1768 fn the_shipped_mmintrin_defines_the_mmx_type_and_the_operations_over_it() {
1769 let text = shipped(concat!(
1770 "#include <mmintrin.h>\n",
1771 "__m64 add(__m64 a, __m64 b) { return _mm_add_pi16(a, b); }\n",
1772 "__m64 pack(__m64 a, __m64 b) { return _m_packsswb(a, b); }\n",
1773 "__m64 shift(__m64 a) { return _mm_srai_pi32(a, 3); }\n",
1774 "int low(__m64 a) { return _mm_cvtsi64_si32(a); }\n",
1775 "void done(void) { _mm_empty(); }\n",
1776 ));
1777 assert!(text.contains("add"), "{text}");
1778 assert!(text.contains("pack"), "{text}");
1779 assert!(text.contains("shift"), "{text}");
1780 }
1781
1782 #[test]
1787 fn the_shipped_mm_malloc_asks_for_aligned_memory_and_gives_it_back() {
1788 let text = shipped(concat!(
1789 "#include <mm_malloc.h>\n",
1790 "void *get(void) { return _mm_malloc(64, 16); }\n",
1791 "void put(void *p) { _mm_free(p); }\n",
1792 ));
1793 assert!(text.contains("get"), "{text}");
1794 assert!(text.contains("put"), "{text}");
1795 }
1796
1797 #[test]
1809 fn the_shipped_xmmintrin_defines_the_sse_type_and_the_operations_over_it() {
1810 let text = shipped(concat!(
1811 "#include <xmmintrin.h>\n",
1812 "__m128 add(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1813 "__m128 one(__m128 a, __m128 b) { return _mm_max_ss(a, b); }\n",
1814 "__m128 mask(__m128 a, __m128 b) { return _mm_cmpnle_ps(a, b); }\n",
1815 "__m128 pick(__m128 a, __m128 b) { return _mm_shuffle_ps(a, b, _MM_SHUFFLE(0,1,2,3)); }\n",
1816 "int bits(__m128 a) { return _mm_movemask_ps(a); }\n",
1817 "int near(__m128 a) { return _mm_cvtss_si32(a); }\n",
1818 "__m128 wide(__m64 a) { return _mm_cvtpi16_ps(a); }\n",
1819 "void *room(void) { return _mm_malloc(64, 16); }\n",
1820 "void hint(const float *p) { _mm_prefetch(p, _MM_HINT_T0); _mm_sfence(); }\n",
1821 ));
1822 assert!(text.contains("add"), "{text}");
1823 assert!(text.contains("mask"), "{text}");
1824 assert!(text.contains("pick"), "{text}");
1825 assert!(text.contains("wide"), "{text}");
1826 }
1827
1828 #[test]
1835 fn the_shipped_xmmintrin_leaves_out_the_names_that_need_an_instruction() {
1836 let text = rucc_session::runtime::header("xmmintrin.h").expect("xmmintrin.h is shipped");
1837 for absent in [
1838 "_mm_sqrt_ps",
1839 "_mm_sqrt_ss",
1840 "_mm_rsqrt_ps",
1841 "_mm_rsqrt_ss",
1842 "_mm_getcsr",
1843 "_mm_setcsr",
1844 ] {
1845 let defined = text.contains(&format!("{absent}("));
1846 assert!(!defined, "{absent} is defined and the header says it is not");
1847 assert!(text.contains(absent), "{absent} is absent and unexplained");
1848 }
1849 }
1850
1851 #[test]
1852 fn the_shipped_emmintrin_defines_both_sse2_types_and_the_operations_over_them() {
1853 let text = shipped(concat!(
1854 "#include <emmintrin.h>\n",
1855 "__m128i add(__m128i a, __m128i b) { return _mm_add_epi64(a, b); }\n",
1856 "__m128i wide(__m128i a, __m128i b) { return _mm_mul_epu32(a, b); }\n",
1857 "__m128i pick(__m128i a) { return _mm_shuffle_epi32(a, _MM_SHUFFLE(0,1,2,3)); }\n",
1858 "__m128i up(__m128i a) { return _mm_slli_epi64(a, 13); }\n",
1859 "__m128i down(__m128i a) { return _mm_srli_si128(a, 3); }\n",
1860 "__m128i pack(__m128i a, __m128i b) { return _mm_packus_epi16(a, b); }\n",
1861 "int bits(__m128i a) { return _mm_movemask_epi8(a); }\n",
1862 "__m128d sum(__m128d a, __m128d b) { return _mm_add_sd(a, b); }\n",
1863 "__m128d mask(__m128d a, __m128d b) { return _mm_cmpunord_pd(a, b); }\n",
1864 "__m128i near(__m128d a) { return _mm_cvtpd_epi32(a); }\n",
1865 "__m128d over(__m128 a) { return _mm_cvtps_pd(a); }\n",
1866 "__m128i half(__m64 a) { return _mm_movpi64_epi64(a); }\n",
1867 "__m128i grab(void const *p) { return _mm_loadu_si128(p); }\n",
1868 "void wall(void) { _mm_lfence(); _mm_mfence(); }\n",
1869 ));
1870 assert!(text.contains("wide"), "{text}");
1871 assert!(text.contains("pack"), "{text}");
1872 assert!(text.contains("near"), "{text}");
1873 assert!(text.contains("half"), "{text}");
1874 }
1875
1876 #[test]
1880 fn the_shipped_immintrin_reaches_the_names_the_headers_under_it_define() {
1881 let text = shipped(concat!(
1882 "#include <immintrin.h>\n",
1883 "unsigned long long matching(unsigned char tag, unsigned char const *bucket) {\n",
1884 " __m128i const want = _mm_set1_epi8((char)tag);\n",
1885 " __m128i const chunk = _mm_loadu_si128((__m128i const *)(void const *)bucket);\n",
1886 " __m128i const same = _mm_cmpeq_epi8(chunk, want);\n",
1887 " return (unsigned long long)_mm_movemask_epi8(same);\n",
1888 "}\n",
1889 "__m64 narrow(__m64 a, __m64 b) { return _mm_add_pi32(a, b); }\n",
1890 "__m128 single(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1891 ));
1892 assert!(text.contains("matching"), "{text}");
1893 assert!(text.contains("narrow"), "the MMX header is not reached: {text}");
1894 assert!(text.contains("single"), "the SSE header is not reached: {text}");
1895 }
1896
1897 #[test]
1901 fn the_shipped_x86intrin_reaches_the_fences_windows_headers_ask_it_for() {
1902 let text = shipped(concat!(
1903 "#include <x86intrin.h>\n",
1904 "void barriers(void *p) {\n",
1905 " _mm_lfence();\n",
1906 " _mm_sfence();\n",
1907 " _mm_mfence();\n",
1908 " _mm_pause();\n",
1909 " _mm_clflush(p);\n",
1910 "}\n",
1911 "__m128i wide(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1912 ));
1913 assert!(text.contains("barriers"), "{text}");
1914 assert!(text.contains("wide"), "the SSE2 header is not reached: {text}");
1915 }
1916
1917 #[test]
1921 fn the_umbrella_and_the_header_under_it_can_both_be_included() {
1922 let text = shipped(concat!(
1923 "#include <immintrin.h>\n",
1924 "#include <emmintrin.h>\n",
1925 "#include <immintrin.h>\n",
1926 "#include <x86intrin.h>\n",
1927 "__m128i twice(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1928 ));
1929 assert!(text.contains("twice"), "{text}");
1930 }
1931
1932 #[test]
1936 fn the_shipped_emmintrin_leaves_out_the_two_square_roots() {
1937 let text = rucc_session::runtime::header("emmintrin.h").expect("emmintrin.h is shipped");
1938 for absent in ["_mm_sqrt_pd", "_mm_sqrt_sd"] {
1939 let defined = text.contains(&format!("{absent}("));
1940 assert!(!defined, "{absent} is defined and the header says it is not");
1941 assert!(text.contains(absent), "{absent} is absent and unexplained");
1942 }
1943 }
1944
1945 #[test]
1946 fn the_three_formality_headers_still_have_to_work() {
1947 let text = shipped(concat!(
1948 "#include <stdbool.h>\n",
1949 "#include <stdalign.h>\n",
1950 "#include <iso646.h>\n",
1951 "#include <stdnoreturn.h>\n",
1952 "int t = true and not false;\n",
1953 "_Alignas(16) char buf[16];\n",
1954 "int a = alignof(long);\n",
1955 ));
1956 assert!(text.contains("decl #0 t : int"), "{text}");
1957 assert!(text.contains("const 8 : unsigned long"), "{text}");
1958 }
1959
1960 #[test]
1968 fn every_shipped_header_can_be_included_twice() {
1969 let once: String = rucc_session::runtime::names()
1970 .iter()
1971 .map(|name| format!("#include <{name}>\n"))
1972 .collect();
1973 let twice = once.repeat(2);
1974 assert_eq!(shipped(&format!("{once}int x;\n")), shipped(&format!("{twice}int x;\n")));
1975 }
1976
1977 #[test]
1978 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
1979 let fs = MemoryFileSystem::new();
1980 let result = compile(&options(), "/nope.c", &fs);
1981 assert!(result.failed());
1982 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
1983 assert!(result.text().is_empty());
1984 }
1985
1986 #[test]
1987 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
1988 let text = tast("int x = 1;\n");
1989 let expected = "\
1990decl #0 x : int object external static defined
1991 init
1992 +0
1993 const 1 : int
1994";
1995 assert_eq!(text, expected);
1996 }
1997
1998 #[test]
1999 fn the_macros_are_expanded_before_anything_is_parsed() {
2000 let text = tast("#define N 2\nint a[N];\n");
2004 assert!(text.starts_with("decl #0 a : int[2] object external static tentative"), "{text}");
2005 }
2006
2007 #[test]
2013 fn a_pragma_is_not_a_declaration_and_the_parse_walks_past_the_ones_it_does_not_read() {
2014 let text = tast(concat!(
2015 "#pragma pack(4)\n",
2016 "struct s { int a; };\n",
2017 "#pragma pack()\n",
2018 "int b;\n",
2019 "_Pragma(\"GCC visibility push(default)\") int c;\n",
2020 ));
2021 assert!(text.contains("decl #0 b : int"), "{text}");
2022 assert!(text.contains("decl #1 c : int"), "{text}");
2023 }
2024
2025 #[test]
2033 fn the_layout_attributes_move_the_members_and_the_record_the_way_gcc_lays_them_out() {
2034 tast(concat!(
2035 "struct A { char c; int i; } __attribute__((packed));\n",
2036 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2037 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2038 "struct B { char c; int i; } __attribute__((aligned));\n",
2041 "_Static_assert(sizeof(struct B) == 16 && _Alignof(struct B) == 16, \"B\");\n",
2042 "struct C { char c; int i __attribute__((packed)); };\n",
2043 "_Static_assert(sizeof(struct C) == 5 && _Alignof(struct C) == 1, \"C\");\n",
2044 "_Static_assert(__builtin_offsetof(struct C, i) == 1, \"C.i\");\n",
2045 "struct D { char c; int i; } __attribute__((packed, aligned(4)));\n",
2046 "_Static_assert(sizeof(struct D) == 8 && _Alignof(struct D) == 4, \"D\");\n",
2047 "_Static_assert(__builtin_offsetof(struct D, i) == 1, \"D.i\");\n",
2048 "struct E { char c; _Alignas(8) int i; };\n",
2049 "_Static_assert(sizeof(struct E) == 16 && _Alignof(struct E) == 8, \"E\");\n",
2050 "_Static_assert(__builtin_offsetof(struct E, i) == 8, \"E.i\");\n",
2051 "struct F { char c; int i __attribute__((aligned(8))); };\n",
2052 "_Static_assert(sizeof(struct F) == 16 && _Alignof(struct F) == 8, \"F\");\n",
2053 "struct G { char c; short s; } __attribute__((aligned(2)));\n",
2056 "_Static_assert(sizeof(struct G) == 4 && _Alignof(struct G) == 2, \"G\");\n",
2057 "struct H { char c; int i; } __attribute__((aligned(2)));\n",
2058 "_Static_assert(sizeof(struct H) == 8 && _Alignof(struct H) == 4, \"H\");\n",
2059 "struct I { [[gnu::packed]] char c; int i; };\n",
2062 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2063 "struct J { char c; [[gnu::packed]] int i; };\n",
2064 "_Static_assert(sizeof(struct J) == 5 && _Alignof(struct J) == 1, \"J\");\n",
2065 "struct M { char c; int i : 5; int j : 20; } __attribute__((packed));\n",
2066 "_Static_assert(sizeof(struct M) == 5 && _Alignof(struct M) == 1, \"M\");\n",
2067 "struct N { char c; long long l; } __attribute__((aligned(32)));\n",
2068 "_Static_assert(sizeof(struct N) == 32 && _Alignof(struct N) == 32, \"N\");\n",
2069 "union L { char c; int i; } __attribute__((packed));\n",
2070 "_Static_assert(sizeof(union L) == 4 && _Alignof(union L) == 1, \"L\");\n",
2071 "struct O { char c; int i; } __attribute__((__packed__));\n",
2075 "_Static_assert(sizeof(struct O) == 5 && _Alignof(struct O) == 1, \"O\");\n",
2076 "struct P { char c; int i; } __attribute__((__aligned__(8)));\n",
2077 "_Static_assert(sizeof(struct P) == 8 && _Alignof(struct P) == 8, \"P\");\n",
2078 ));
2079 }
2080
2081 #[test]
2094 fn a_transparent_union_takes_the_member_a_value_fits_and_is_declared_either_way() {
2095 let text = tast(concat!(
2096 "struct one { int x; };\n",
2097 "struct two { long y; };\n",
2098 "typedef union { struct one *a; struct two *b; void *any; }\n",
2099 " __attribute__((__transparent_union__)) arg;\n",
2100 "int takes(arg v);\n",
2101 "int f(struct one *p, struct two *q, char *c) {\n",
2102 " return takes(p) + takes(q) + takes(c) + takes(0);\n",
2103 "}\n",
2104 "int takes(struct one *p);\n",
2106 "int (*as_a_member)(struct one *) = takes;\n",
2107 "int (*as_the_union)(arg) = takes;\n",
2108 ));
2109 assert!(text.contains("compound-literal"), "{text}");
2110 }
2111
2112 #[test]
2118 fn the_attribute_on_the_declarator_of_a_typedef_is_the_one_glibc_writes() {
2119 let text = tast(concat!(
2120 "struct sockaddr { int family; };\n",
2121 "struct sockaddr_in { int family; int addr; };\n",
2122 "typedef union { struct sockaddr *plain; struct sockaddr_in *inet; }\n",
2123 " addr_arg __attribute__((__transparent_union__));\n",
2124 "int bind_to(int fd, addr_arg where);\n",
2125 "int f(struct sockaddr_in *where) { return bind_to(0, where); }\n",
2126 ));
2127 assert!(text.contains("compound-literal"), "{text}");
2128 }
2129
2130 #[test]
2138 fn a_transparent_union_that_cannot_keep_the_promise_is_dropped_with_a_word_about_it() {
2139 let result = run(
2140 &options(),
2141 concat!(
2142 "union wider { int small; double large; } __attribute__((transparent_union));\n",
2143 "struct plain { int x; } __attribute__((transparent_union));\n",
2144 ),
2145 );
2146 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
2147 assert!(!result.failed(), "{:?}", result.messages);
2148 for message in &result.messages {
2149 assert!(message.contains("'transparent_union' attribute ignored"), "{message}");
2150 }
2151 assert!(result.messages[0].contains("first member"), "{:?}", result.messages);
2152 assert!(result.messages[1].contains("only a union"), "{:?}", result.messages);
2153 }
2154
2155 #[test]
2164 fn an_access_to_a_packed_member_says_the_alignment_the_layout_left_it() {
2165 let packed = body(concat!(
2166 "struct P { char c; int v; } __attribute__((packed));\n",
2167 "int f(struct P *p) { return p->v; }\n",
2168 ));
2169 assert!(packed.contains("load.i32 %2, align 1,"), "{packed}");
2170 let plain = body(concat!(
2172 "struct P { char c; int v; };\n",
2173 "int f(struct P *p) { return p->v; }\n",
2174 ));
2175 assert!(plain.contains("load.i32 %2, align 4,"), "{plain}");
2176 }
2177
2178 #[test]
2185 fn what_is_inside_a_packed_member_is_no_more_aligned_than_the_member_is() {
2186 let stepped = body(concat!(
2187 "struct P { char c; int v[4]; } __attribute__((packed));\n",
2188 "int f(struct P *p, int i) { return p->v[i]; }\n",
2189 ));
2190 assert!(stepped.contains(", align 1,"), "{stepped}");
2191 assert!(!stepped.contains(", align 4,"), "{stepped}");
2192 let nested = body(concat!(
2193 "struct Inner { int v; };\n",
2194 "struct P { char c; struct Inner in; } __attribute__((packed));\n",
2195 "int f(struct P *p) { return p->in.v; }\n",
2196 ));
2197 assert!(nested.contains(", align 1,"), "{nested}");
2198 assert!(!nested.contains(", align 4,"), "{nested}");
2199 }
2200
2201 #[test]
2217 fn an_access_through_a_typedef_that_lowered_its_alignment_says_the_one_the_typedef_asked_for() {
2218 let through = body(concat!(
2219 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2220 "unsigned int f(const void *p) { return *(const unalign32 *)p; }\n",
2221 ));
2222 assert!(through.contains("load.i32 %0, align 1,"), "{through}");
2223 let stepped = body(concat!(
2226 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2227 "unsigned int f(unalign32 *p, int i) { return p[i]; }\n",
2228 ));
2229 assert!(stepped.contains(", align 1,"), "{stepped}");
2230 assert!(!stepped.contains(", align 4,"), "{stepped}");
2231 let plain = body(concat!(
2234 "typedef unsigned int word;\n",
2235 "unsigned int f(const void *p) { return *(const word *)p; }\n",
2236 ));
2237 assert!(plain.contains("load.i32 %0, align 4,"), "{plain}");
2238 }
2239
2240 #[test]
2251 fn a_vector_read_through_a_typedef_that_lowered_its_alignment_comes_back_a_piece_at_a_time() {
2252 let prefix = concat!(
2253 "typedef long long v2di __attribute__((__vector_size__(16)));\n",
2254 "typedef long long v2di_u __attribute__((__vector_size__(16), __aligned__(1)));\n",
2255 );
2256 let loaded =
2257 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di_u *)p; }}"));
2258 assert_eq!(loaded.matches("align 1\n").count(), 2, "{loaded}");
2259 assert!(!loaded.contains("align 16"), "{loaded}");
2260 let stored = body(&format!("{prefix}void f(void *p, v2di b) {{ *(v2di_u *)p = b; }}"));
2263 assert!(stored.contains("memcpy %0, %3, size 16, align 1"), "{stored}");
2264 let aligned =
2266 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di *)p; }}"));
2267 assert!(aligned.contains("align 16"), "{aligned}");
2268 }
2269
2270 #[test]
2279 fn the_aligned_attribute_on_a_declaration_raises_what_that_one_object_is_aligned_to() {
2280 tast(concat!(
2281 "int v __attribute__((aligned(64)));\n",
2282 "_Static_assert(__alignof__(v) == 64, \"v\");\n",
2283 "__attribute__((aligned(32))) int w;\n",
2286 "_Static_assert(__alignof__(w) == 32, \"w\");\n",
2287 "[[gnu::aligned(16)]] int x;\n",
2288 "_Static_assert(__alignof__(x) == 16, \"x\");\n",
2289 "int y __attribute__((aligned(2)));\n",
2292 "_Static_assert(__alignof__(y) == 4, \"y\");\n",
2293 "void f(void) { int a __attribute__((aligned(128)));\n",
2295 "_Static_assert(__alignof__(a) == 128, \"a\"); (void)a; }\n",
2296 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2299 "void g(void) __attribute__((aligned(256)));\n",
2302 "void g(void) {}\n",
2303 "_Static_assert(__alignof__(g) == 256, \"g\");\n",
2304 ));
2305 }
2306
2307 #[test]
2311 fn what_a_declaration_asked_to_be_aligned_to_is_what_the_assembler_is_told() {
2312 let text = asm(concat!(
2313 "int v __attribute__((aligned(64)));\n",
2314 "void g(void) __attribute__((aligned(256)));\n",
2315 "void g(void) {}\n",
2316 "void plain(void) {}\n",
2317 ));
2318 assert!(text.contains("\t.p2align\t6\n\t.type\tv, @object\n"), "{text}");
2319 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2320 assert!(text.contains("\t.p2align\t4, 0x90\n\t.globl\tplain\n"), "{text}");
2321 }
2322
2323 #[test]
2329 fn the_alignment_the_command_line_asked_of_every_function_is_a_floor_under_all_of_them() {
2330 let source = concat!(
2331 "void g(void) __attribute__((aligned(256)));\n",
2332 "void g(void) {}\n",
2333 "void small(void) __attribute__((aligned(4)));\n",
2334 "void small(void) {}\n",
2335 "void plain(void) {}\n",
2336 );
2337 let listing = |align: Option<u32>| {
2338 let mut opts = options();
2339 opts.emit = EmitKind::Asm;
2340 opts.align_functions = align;
2341 let result = run(&opts, source);
2342 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
2343 result.text().to_owned()
2344 };
2345
2346 let text = listing(Some(32));
2347 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "the larger one wins: {text}");
2348 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tsmall\n"), "{text}");
2349 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tplain\n"), "{text}");
2350
2351 let text = listing(Some(8));
2354 assert!(text.contains("\t.p2align\t3, 0x90\n\t.globl\tplain\n"), "{text}");
2355 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2356 }
2357
2358 #[test]
2367 fn an_aligned_typedef_says_what_an_object_of_it_is_aligned_to_and_may_lower_it() {
2368 tast(concat!(
2369 "typedef int L __attribute__((aligned(2)));\n",
2370 "_Static_assert(__alignof__(L) == 2, \"L\");\n",
2371 "_Static_assert(_Alignof(L) == 2, \"L alignof\");\n",
2372 "_Static_assert(sizeof(L) == 4, \"L size\");\n",
2374 "struct T { char c; L x; };\n",
2375 "_Static_assert(sizeof(struct T) == 6, \"T\");\n",
2376 "_Static_assert(__builtin_offsetof(struct T, x) == 2, \"T.x\");\n",
2377 "typedef int H __attribute__((aligned(16)));\n",
2379 "_Static_assert(__alignof__(H) == 16, \"H\");\n",
2380 "_Static_assert(sizeof(H) == 4, \"H size\");\n",
2381 "struct U { char c; H x; };\n",
2382 "_Static_assert(sizeof(struct U) == 32, \"U\");\n",
2383 "_Static_assert(__builtin_offsetof(struct U, x) == 16, \"U.x\");\n",
2384 "typedef L M __attribute__((aligned(8)));\n",
2387 "_Static_assert(__alignof__(M) == 8, \"M\");\n",
2388 "typedef L N;\n",
2391 "_Static_assert(__alignof__(N) == 2, \"N\");\n",
2392 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2394 ));
2395 let text = asm(concat!(
2396 "typedef int L __attribute__((aligned(2)));\n",
2397 "typedef int H __attribute__((aligned(16)));\n",
2398 "L low;\n",
2399 "H high;\n",
2400 ));
2401 assert!(text.contains("\t.p2align\t1\n\t.type\tlow, @object\n"), "{text}");
2402 assert!(text.contains("\t.p2align\t4\n\t.type\thigh, @object\n"), "{text}");
2403 }
2404
2405 #[test]
2413 fn the_vector_size_attribute_builds_a_type_of_lanes_and_measures_it_in_bytes() {
2414 tast(concat!(
2415 "typedef int __attribute__((vector_size(16))) v4si;\n",
2416 "_Static_assert(sizeof(v4si) == 16 && _Alignof(v4si) == 16, \"v4si\");\n",
2417 "typedef char __attribute__((vector_size(16))) v16qi;\n",
2418 "_Static_assert(sizeof(v16qi) == 16, \"v16qi\");\n",
2419 "typedef int __attribute__((vector_size(4))) v1si;\n",
2422 "_Static_assert(sizeof(v1si) == 4, \"v1si\");\n",
2423 "typedef float __attribute__((__vector_size__(8))) v2sf;\n",
2425 "_Static_assert(sizeof(v2sf) == 8, \"v2sf\");\n",
2426 "typedef short [[gnu::vector_size(8)]] v4hi;\n",
2427 "_Static_assert(sizeof(v4hi) == 8, \"v4hi\");\n",
2428 "v4si g;\n",
2431 "_Static_assert(sizeof(g[0]) == 4, \"lane\");\n",
2432 "_Static_assert(sizeof(g + g) == 16, \"whole\");\n",
2433 "_Static_assert(sizeof(g + 1) == 16, \"broadcast\");\n",
2436 "_Static_assert(sizeof(v4si[3]) == 48, \"array\");\n",
2438 ));
2439 }
2440
2441 #[test]
2451 fn a_vector_is_written_whole_into_an_array_of_them_and_named_by_a_type_name() {
2452 tast(concat!(
2453 "typedef int __attribute__((vector_size(8))) v2si;\n",
2454 "v2si table[] = { (v2si){ 1, 2 }, (v2si){ 3, 4 } };\n",
2455 "_Static_assert(sizeof(table) == 16, \"two of them and not eight lanes\");\n",
2456 "v2si written = (int __attribute__((vector_size(8)))){ 5, 6 };\n",
2458 "_Static_assert(sizeof((int __attribute__((vector_size(16)))){ 0 }) == 16, \"named\");\n",
2459 "v2si lanes[2] = { 1, 2, 3, 4 };\n",
2462 "_Static_assert(sizeof(lanes) == 16, \"still elided\");\n",
2463 ));
2464 }
2465
2466 #[test]
2474 fn a_lane_is_assignable_and_a_shift_takes_a_count_of_its_own_lane() {
2475 let result = run(
2476 &options(),
2477 concat!(
2478 "typedef int __attribute__((vector_size(16))) v4si;\n",
2479 "typedef unsigned __attribute__((vector_size(16))) v4ui;\n",
2480 "void write(v4si *out, v4ui a, v4si b, int n) {\n",
2481 " v4si v = { 1, 2, 3, 4 };\n",
2482 " v[0] = n;\n",
2483 " v[1] += n;\n",
2484 " v[2]++;\n",
2485 " *&v[3] = n;\n",
2486 " v4ui shifted = a >> b;\n",
2488 " shifted <<= b;\n",
2489 " *out = v + (v4si)shifted + (1 << b);\n",
2492 "}\n",
2493 "void refused(const v4si c) {\n",
2496 " c[0] = 1;\n",
2497 "}\n",
2498 ),
2499 );
2500 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
2501 assert!(result.messages[0].contains("assignment of read-only"), "{:?}", result.messages);
2502 }
2503
2504 #[test]
2516 fn a_record_that_asks_for_the_other_byte_order_swaps_every_scalar_it_holds() {
2517 let read = "int f(struct s *p) { return p->i; }\n";
2518 let big = "struct s { int i; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
2519 assert!(body(&format!("{big}{read}")).contains("bswap"), "{big}");
2520
2521 let armoured =
2522 "struct s { int i; } __attribute__((__scalar_storage_order__(\"big-endian\")));\n";
2523 assert!(body(&format!("{armoured}{read}")).contains("bswap"), "{armoured}");
2524
2525 let front = "struct __attribute__((scalar_storage_order(\"big-endian\"))) s { int i; };\n";
2526 assert!(body(&format!("{front}{read}")).contains("bswap"), "{front}");
2527
2528 let standard = "struct s { int i; } [[gnu::scalar_storage_order(\"big-endian\")]];\n";
2529 assert!(body(&format!("{standard}{read}")).contains("bswap"), "{standard}");
2530
2531 let same =
2532 "struct s { int i; } __attribute__((scalar_storage_order(\"little-endian\")));\n";
2533 assert!(!body(&format!("{same}{read}")).contains("bswap"), "{same}");
2534
2535 let byte = "struct s { char c; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
2537 let source = format!("{byte}int f(struct s *p) {{ return p->c; }}\n");
2538 assert!(!body(&source).contains("bswap"), "{byte}");
2539
2540 tast(concat!(
2541 "struct s { int i; short h; char c; }",
2542 " __attribute__((scalar_storage_order(\"big-endian\")));\n",
2543 "_Static_assert(sizeof(struct s) == 8 && _Alignof(struct s) == 4, \"s\");\n",
2544 "_Static_assert(__builtin_offsetof(struct s, h) == 4, \"s.h\");\n",
2545 "_Static_assert(__builtin_offsetof(struct s, c) == 6, \"s.c\");\n",
2546 ));
2547 }
2548
2549 #[test]
2555 fn a_bit_field_in_one_of_those_records_is_counted_from_the_top_of_its_bytes() {
2556 let members = "short i : 12; char c1 : 1; char c2 : 1; char c3 : 1; char c4 : 1;";
2557 let read = "int f(struct s *p) { return p->i; }\n";
2558 let plain = format!("struct s {{ {members} }};\n{read}");
2559 let reversed = format!(
2560 "struct s {{ {members} }} __attribute__((scalar_storage_order(\"big-endian\")));\n\
2561 {read}"
2562 );
2563 assert!(body(&plain).contains("shl"), "{}", body(&plain));
2564 assert!(!body(&plain).contains("bswap"), "{}", body(&plain));
2565 let built = body(&reversed);
2568 assert!(built.contains("bswap"), "{built}");
2569 assert!(!built.contains("shl"), "{built}");
2570 assert!(built.contains("ashr"), "{built}");
2571 }
2572
2573 #[test]
2580 fn the_address_of_a_scalar_stored_the_other_way_round_is_refused() {
2581 let opts = options();
2582 let record = "struct s { int i; int a[2]; struct in { int n; } w; }\n\
2583 __attribute__((scalar_storage_order(\"big-endian\")));\n";
2584 let taken = format!("{record}int *f(struct s *p) {{ return &p->i; }}\n");
2585 assert_eq!(
2586 run(&opts, &taken).messages,
2587 ["/main.c:3:30: error: cannot take address of scalar with reverse storage order \
2588 [E0712]"]
2589 );
2590 let element = format!("{record}int *f(struct s *p) {{ return &p->a[0]; }}\n");
2591 let messages = run(&opts, &element).messages;
2592 assert!(messages[0].contains("[E0712]"), "{messages:?}");
2593
2594 let whole = format!("{record}int *f(struct s *p) {{ return (int *) &p->w; }}\n");
2595 assert_eq!(run(&opts, &whole).messages, Vec::<String>::new(), "{whole}");
2596 }
2597
2598 #[test]
2602 fn a_storage_order_that_names_neither_end_is_refused_with_the_two_words_that_are_taken() {
2603 let opts = options();
2604 let wrong = "struct s { int i; } __attribute__((scalar_storage_order(\"middle\")));\n";
2605 assert_eq!(
2606 run(&opts, wrong).messages,
2607 ["/main.c:1:36: error: 'scalar_storage_order' argument must be one of \"big-endian\" \
2608 or \"little-endian\" [E0688]"]
2609 );
2610 let bare = "struct s { int i; } __attribute__((scalar_storage_order));\n";
2611 let messages = run(&opts, bare).messages;
2612 assert!(messages[0].contains("[E0688]"), "{messages:?}");
2613 }
2614
2615 #[test]
2625 fn packing_is_what_decides_whether_a_bit_field_may_straddle_its_own_storage() {
2626 assert_eq!(bit_field_byte("struct s { int x : 12; char y : 6; };"), 2);
2628 assert_eq!(
2629 bit_field_byte("struct s { int x : 12; char y : 6; } __attribute__((packed));"),
2630 1
2631 );
2632 assert_eq!(
2633 bit_field_byte("struct s { int x : 12; __attribute__((packed)) char y : 6; };"),
2634 1
2635 );
2636 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { int x : 12; char y : 6; };"), 1);
2637 assert_eq!(bit_field_byte("struct s { char x; int y : 30; };"), 4);
2639 assert_eq!(bit_field_byte("struct s { char x; int y : 30; } __attribute__((packed));"), 1);
2640 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { char x; int y : 30; };"), 1);
2642 assert_eq!(bit_field_byte("#pragma pack(2)\nstruct s { char x; int y : 30; };"), 1);
2643 }
2644
2645 fn bit_field_byte(record: &str) -> u64 {
2647 let source = format!("{record}\nint f(struct s *p) {{ return p->y; }}\n");
2648 let body = body(&source);
2649 let Some((before, _)) = body.split_once("ptr_add") else { return 0 };
2650 let (_, constant) = before.rsplit_once("iconst.i64 ").expect("an offset constant");
2651 constant.lines().next().expect("a line").trim().parse().expect("a byte offset")
2652 }
2653
2654 #[test]
2660 fn an_attribute_among_the_specifiers_is_kept_beside_the_ones_written_in_front() {
2661 tast(concat!(
2662 "struct a { char c; __attribute__((aligned(8))) int i; };\n",
2663 "_Static_assert(sizeof(struct a) == 16 && _Alignof(struct a) == 8, \"a\");\n",
2664 "_Static_assert(__builtin_offsetof(struct a, i) == 8, \"a.i\");\n",
2665 "struct b { char c; __attribute__((packed)) int i; };\n",
2666 "_Static_assert(sizeof(struct b) == 5 && _Alignof(struct b) == 1, \"b\");\n",
2667 "_Static_assert(__builtin_offsetof(struct b, i) == 1, \"b.i\");\n",
2668 "typedef struct { char c; int i; } __attribute__((packed)) c;\n",
2669 "_Static_assert(sizeof(c) == 5 && _Alignof(c) == 1, \"c\");\n",
2670 ));
2671 }
2672
2673 #[test]
2679 fn pragma_pack_caps_every_member_and_is_read_where_the_body_closes() {
2680 tast(concat!(
2681 "#pragma pack(1)\n",
2682 "struct A { char c; int i; };\n",
2683 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2684 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2685 "#pragma pack()\n",
2686 "struct B { char c; int i; };\n",
2687 "_Static_assert(sizeof(struct B) == 8 && _Alignof(struct B) == 4, \"B\");\n",
2688 "#pragma pack(2)\n",
2689 "struct C { char c; int i; double d; };\n",
2690 "_Static_assert(sizeof(struct C) == 14 && _Alignof(struct C) == 2, \"C\");\n",
2691 "_Static_assert(__builtin_offsetof(struct C, d) == 6, \"C.d\");\n",
2692 "struct K { char c; int i __attribute__((aligned(8))); };\n",
2694 "_Static_assert(sizeof(struct K) == 6 && _Alignof(struct K) == 2, \"K\");\n",
2695 "_Static_assert(__builtin_offsetof(struct K, i) == 2, \"K.i\");\n",
2696 "struct J { char c; int i; } __attribute__((aligned(8)));\n",
2698 "_Static_assert(sizeof(struct J) == 8 && _Alignof(struct J) == 8, \"J\");\n",
2699 "#pragma pack()\n",
2700 "#pragma pack(push, 1)\n",
2701 "struct D { char c; short s; };\n",
2702 "_Static_assert(sizeof(struct D) == 3 && _Alignof(struct D) == 1, \"D\");\n",
2703 "#pragma pack(pop)\n",
2704 "struct E { char c; short s; };\n",
2705 "_Static_assert(sizeof(struct E) == 4 && _Alignof(struct E) == 2, \"E\");\n",
2706 "struct H { char c;\n",
2708 "#pragma pack(1)\n",
2709 " int i; };\n",
2710 "_Static_assert(sizeof(struct H) == 5 && _Alignof(struct H) == 1, \"H\");\n",
2711 "#pragma pack(1)\n",
2712 "struct I { char c;\n",
2713 "#pragma pack()\n",
2714 " int i; };\n",
2715 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2716 "#pragma pack()\n",
2717 "#pragma pack(push, 8)\n",
2719 "#pragma pack(push, 1)\n",
2720 "struct P { char c; int i; };\n",
2721 "_Static_assert(sizeof(struct P) == 5 && _Alignof(struct P) == 1, \"P\");\n",
2722 "#pragma pack(pop)\n",
2723 "struct Q { char c; int i; };\n",
2724 "_Static_assert(sizeof(struct Q) == 8 && _Alignof(struct Q) == 4, \"Q\");\n",
2725 "#pragma pack(pop)\n",
2726 "#pragma pack(16)\n",
2728 "struct R { char c; int i; };\n",
2729 "_Static_assert(sizeof(struct R) == 8 && _Alignof(struct R) == 4, \"R\");\n",
2730 "#pragma pack()\n",
2731 "#pragma pack(1)\n",
2732 "struct S { char c; int i : 5; int j : 20; };\n",
2733 "_Static_assert(sizeof(struct S) == 5 && _Alignof(struct S) == 1, \"S\");\n",
2734 "union T { char c; int i; };\n",
2735 "_Static_assert(sizeof(union T) == 4 && _Alignof(union T) == 1, \"T\");\n",
2736 "#pragma pack()\n",
2737 ));
2738 }
2739
2740 #[test]
2744 fn a_pack_line_that_is_not_one_is_reported_in_the_words_gcc_uses() {
2745 let result = run(
2746 &options(),
2747 concat!(
2748 "#pragma pack 4\n",
2749 "#pragma pack(pop)\n",
2750 "#pragma pack(3)\n",
2751 "#pragma pack(1) junk\n",
2752 "#pragma pack(push, 1\n",
2753 "#pragma pack(x)\n",
2754 "#pragma pack(0)\n",
2757 "#pragma pack(push)\n",
2758 "struct s { char c; int i; };\n",
2759 "#pragma pack(pop)\n",
2760 "#pragma pack(pop, foo)\n",
2761 ),
2762 );
2763 let expected = [
2764 "missing `(` after `#pragma pack` - ignored",
2765 "`#pragma pack (pop)` encountered without matching `#pragma pack (push)`",
2766 "alignment must be a small power of two, not 3",
2767 "junk at end of `#pragma pack`",
2768 "malformed `#pragma pack(push[, id][, <n>])` - ignored",
2769 "unknown action `x` for `#pragma pack` - ignored",
2770 "`#pragma pack(pop, foo)` encountered without matching `#pragma pack(push, foo)`",
2771 ];
2772 assert_eq!(result.messages.len(), expected.len(), "{:?}", result.messages);
2773 for (message, want) in result.messages.iter().zip(expected) {
2774 assert!(message.contains(want), "expected {want:?} in {message:?}");
2775 }
2776 }
2777
2778 #[test]
2785 fn a_declaration_behind_an_empty_macro_is_not_eaten_by_the_pragma_above_it() {
2786 let result = run(
2787 &options(),
2788 concat!(
2789 "#pragma pack(push, 1)\n",
2790 "#pragma pack(pop)\n",
2791 "#define API\n",
2792 "API const char version[] = \"3.53.4\";\n",
2793 "const char *get(void) { return version; }\n",
2794 ),
2795 );
2796 assert!(result.messages.is_empty(), "{:?}", result.messages);
2797 }
2798
2799 #[test]
2803 fn the_wide_integer_answers_to_all_three_of_its_names() {
2804 let text = tast("__uint128_t a; __int128_t b; unsigned __int128 c;\n");
2805 assert!(text.contains("decl #0 a : unsigned __int128"), "{text}");
2806 assert!(text.contains("decl #1 b : __int128"), "{text}");
2807 assert!(text.contains("decl #2 c : unsigned __int128"), "{text}");
2808 }
2809
2810 #[test]
2811 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
2812 let text = tast("long f(int a, long b) { return a + b; }\n");
2816 assert!(text.contains("convert arithmetic"), "{text}");
2817 }
2818
2819 #[test]
2820 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
2821 for source in [
2822 "#error stop\n",
2823 "int f(void) { return 1 + ; }\n",
2824 "int f(void) { return undeclared; }\n",
2825 ] {
2826 let result = run(&options(), source);
2827 assert!(result.failed(), "expected this to fail:\n{source}");
2828 assert!(
2829 result.text().is_empty(),
2830 "a file that did not compile wrote a tree:\n{source}"
2831 );
2832 }
2833 }
2834
2835 #[test]
2836 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
2837 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
2841 assert_eq!(result.errors, 1, "{:?}", result.messages);
2842 }
2843
2844 #[test]
2845 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
2846 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
2850 assert_eq!(result.errors, 1, "{:?}", result.messages);
2851 }
2852
2853 #[test]
2854 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
2855 let source = "int f(void) { char c = 300; return c; }\n";
2856 let plain = run(&options(), source);
2857 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
2858 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
2859 assert!(!plain.text().is_empty(), "a warning is not a reason to write nothing");
2860
2861 let mut opts = options();
2862 opts.warnings_are_errors = true;
2863 let strict = run(&opts, source);
2864 assert!(strict.failed());
2865 assert!(strict.text().is_empty(), "and under -Werror it is a reason to write nothing");
2866 for message in &strict.messages {
2867 assert!(!message.contains("warning:"), "{message}");
2868 }
2869 }
2870
2871 #[test]
2872 fn w_drops_the_warning_before_werror_can_promote_it() {
2873 let source = "int f(void) { char c = 300; return c; }\n";
2874 let mut opts = options();
2875 opts.warnings = false;
2876 let quiet = run(&opts, source);
2877 assert_eq!(quiet.messages, Vec::<String>::new());
2878 assert_eq!(quiet.errors, 0);
2879 assert!(!quiet.text().is_empty(), "and the file still compiles");
2880
2881 opts.warnings_are_errors = true;
2884 let both = run(&opts, source);
2885 assert_eq!(both.messages, Vec::<String>::new());
2886 assert!(!both.failed(), "-w -Werror is not an error about a warning nobody saw");
2887 }
2888
2889 #[test]
2890 fn the_dialect_reaches_the_keywords_and_the_checking() {
2891 let source = "typeof(1) x;\n";
2894 let mut opts = options();
2895 opts.std = Std::C23;
2896 opts.gnu_extensions = false;
2897 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
2898
2899 opts.std = Std::C17;
2900 assert!(run(&opts, source).failed());
2901 }
2902
2903 #[test]
2904 fn asking_for_a_kind_that_is_not_written_yet_runs_the_front_end_and_writes_nothing() {
2905 let mut opts = options();
2906 opts.emit = EmitKind::Object;
2907 let result = run(&opts, "int x = 1;\n");
2908 assert!(!result.failed(), "{:?}", result.messages);
2909 assert!(result.text().is_empty());
2910 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
2913 }
2914
2915 fn mir(source: &str) -> String {
2917 let mut opts = options();
2918 opts.emit = EmitKind::MirFinal;
2919 let result = run(&opts, source);
2920 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2921 result.text().to_owned()
2922 }
2923
2924 #[test]
2930 fn a_function_goes_from_c_to_instructions_with_real_registers_in_them() {
2931 let text = mir("int add(int a, int b) { return a + b; }\n");
2932 assert!(text.starts_with("mfunc @add {"), "{text}");
2933 assert!(text.contains("x64.add_rr_32"), "{text}");
2934 assert!(text.contains("x64.ret"), "{text}");
2935 assert!(!text.contains('%'), "{text}");
2938 }
2939
2940 #[test]
2942 fn a_function_with_no_body_produces_no_machine_function() {
2943 let text = mir("int g(int);\nint f(int a) { return g(a); }\n");
2944 assert_eq!(text.matches("mfunc @").count(), 1, "{text}");
2945 assert!(text.contains("mfunc @f {"), "{text}");
2946 assert!(text.contains("x64.call"), "{text}");
2947 }
2948
2949 #[test]
2951 fn every_definition_in_the_file_is_generated_and_they_keep_their_order() {
2952 let text = mir("int a(int x) { return x; }\nint b(int x) { return x; }\n");
2953 let first = text.find("mfunc @a").expect("the first function");
2954 let second = text.find("mfunc @b").expect("the second function");
2955 assert!(first < second, "{text}");
2956 }
2957
2958 #[test]
2960 fn the_target_decides_which_convention_the_generated_code_follows() {
2961 let mut opts = options();
2962 opts.emit = EmitKind::MirFinal;
2963 let linux = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2964 assert!(linux.contains("$rdi"), "{linux}");
2965
2966 opts.target = "x86_64-pc-windows-msvc".parse::<Triple>().unwrap();
2967 let windows = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2968 assert!(windows.contains("$rcx"), "{windows}");
2969 assert!(!windows.contains("$rdi"), "{windows}");
2970 }
2971
2972 #[test]
2980 fn a_tagged_member_with_no_name_is_a_member_on_windows_and_nothing_on_linux() {
2981 let source = concat!(
2982 "struct S { union U { int i; void *p; }; unsigned long tymed; };\n",
2983 "int size(void) { return sizeof(struct S); }\n",
2984 "int f(struct S *s) { s->i = 1; return s->i; }\n",
2985 );
2986
2987 let mut opts = options();
2988 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
2989 let windows = run(&opts, source);
2990 assert!(windows.messages.is_empty(), "{:?}", windows.messages);
2991
2992 let linux = run(&options(), source);
2993 assert_eq!(linux.messages.len(), 3, "{:?}", linux.messages);
2994 assert!(linux.messages[0].contains("does not declare anything"), "{:?}", linux.messages);
2995
2996 let mut opts = options();
2999 opts.ms_extensions = Some(true);
3000 let asked = run(&opts, source);
3001 assert!(asked.messages.is_empty(), "{:?}", asked.messages);
3002 }
3003
3004 #[test]
3006 fn a_target_this_has_no_back_end_for_is_reported_rather_than_generated() {
3007 let mut opts = options();
3008 opts.emit = EmitKind::MirFinal;
3009 opts.target = "riscv64-unknown-linux-gnu".parse::<Triple>().unwrap();
3010 let result = run(&opts, "int f(int a) { return a; }\n");
3011 assert!(result.failed());
3012 assert!(result.messages[0].contains("no back end for riscv64"), "{:?}", result.messages);
3013 assert!(result.text().is_empty());
3014 }
3015
3016 #[test]
3019 fn an_aarch64_target_is_written_as_aarch64_assembly() {
3020 let mut opts = options();
3021 opts.emit = EmitKind::Asm;
3022 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3023 let source = "int g(int);\nint f(int a, int b) { return g(a) + b; }\n";
3024 let result = run(&opts, source);
3025 assert!(!result.failed(), "{:?}", result.messages);
3026 let text = result.text();
3027 for line in ["stp x29, x30, [sp, #-16]!", "mov x29, sp", "bl g", "ldp x29, x30, [sp], #16"]
3028 {
3029 assert!(text.contains(line), "{line} is not in\n{text}");
3030 }
3031 assert!(!text.contains('%'), "{text}");
3032 }
3033
3034 #[test]
3037 fn the_aarch64_vector_type_names_are_declared_on_that_target_and_nowhere_else() {
3038 let mut opts = options();
3039 opts.emit = EmitKind::Asm;
3040 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3041 let source = "typedef __Float32x4_t f4;\n__SVFloat32_t sv(__SVFloat32_t, __SVBool_t);\n\
3042 int n = sizeof(f4) + sizeof(__Int8x8_t);\n\
3043 int f(f4 v) { int __Uint8x16_t = 3; return v[1] + __Uint8x16_t; }\n";
3044 let result = run(&opts, source);
3045 assert!(!result.failed(), "{:?}", result.messages);
3046 assert!(result.text().contains(".long\t24"), "{}", result.text());
3047 opts.target = "x86_64-unknown-linux-gnu".parse::<Triple>().unwrap();
3048 let result = run(&opts, "typedef __Float32x4_t f4;\n");
3049 assert!(result.failed());
3050 let result = run(&opts, "int __Float32x4_t = 1;\n");
3051 assert!(!result.failed(), "{:?}", result.messages);
3052 }
3053
3054 #[test]
3057 fn an_aarch64_result_in_memory_is_reached_through_x8() {
3058 let mut opts = options();
3059 opts.emit = EmitKind::Asm;
3060 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3061 let source = "struct big { long a, b, c; };\nstruct big make(long v);\n\
3062 long f(long v) { return make(v).c; }\n\
3063 struct big g(long v) { struct big b = { v, v, v }; return b; }\n";
3064 let result = run(&opts, source);
3065 assert!(!result.failed(), "{:?}", result.messages);
3066 let text = result.text();
3067 assert!(text.contains("x8"), "{text}");
3068 assert!(text.contains("bl make"), "{text}");
3069 }
3070
3071 #[test]
3074 fn an_aarch64_remainder_is_a_division_and_a_multiply_subtract() {
3075 let mut opts = options();
3076 opts.emit = EmitKind::Asm;
3077 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3078 let source = "int s(int a, int b) { return a % b; }\n\
3079 unsigned long u(unsigned long a, unsigned long b) { return a % b; }\n";
3080 let result = run(&opts, source);
3081 assert!(!result.failed(), "{:?}", result.messages);
3082 let text = result.text();
3083 let at = |what: &str| text.find(what).unwrap_or_else(|| panic!("{what} is not in\n{text}"));
3084 assert!(at("sdiv w") < at("msub w"), "{text}");
3085 assert!(at("udiv x") < at("msub x"), "{text}");
3086 }
3087
3088 #[test]
3091 fn an_aarch64_jump_table_is_reached_with_adr() {
3092 let mut opts = options();
3093 opts.emit = EmitKind::Asm;
3094 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3095 let source = "int f(int x) { switch (x) { case 0: return 10; case 1: return 21; \
3096 case 2: return 32; case 3: return 43; case 4: return 54; case 5: return 65; \
3097 case 6: return 76; case 7: return 87; case 8: return 98; case 9: return 9; \
3098 case 10: return 19; case 11: return 29; default: return 0; } }\n";
3099 let result = run(&opts, source);
3100 assert!(!result.failed(), "{:?}", result.messages);
3101 let text = result.text();
3102 let at = |what: &str| text.find(what).unwrap_or_else(|| panic!("{what} is not in\n{text}"));
3103 assert!(at("adr x") < at("ldrsw x"), "{text}");
3104 assert!(at("ldrsw x") < at("br x"), "{text}");
3105 assert!(text.contains("_j0:"), "{text}");
3106 assert!(text.contains(".long"), "{text}");
3107 }
3108
3109 #[test]
3113 fn an_aarch64_va_start_writes_the_five_fields_of_its_list() {
3114 let mut opts = options();
3115 opts.emit = EmitKind::Asm;
3116 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3117 let source = "typedef __builtin_va_list va_list;\n\
3118 int f(int n, ...) { va_list ap; __builtin_va_start(ap, n); \
3119 int x = __builtin_va_arg(ap, int); double d = __builtin_va_arg(ap, double); \
3120 __builtin_va_end(ap); return x + (int)d; }\n";
3121 let result = run(&opts, source);
3122 assert!(!result.failed(), "{:?}", result.messages);
3123 let text = result.text();
3124 assert!(text.contains("#-56"), "{text}");
3125 assert!(text.contains("#-128"), "{text}");
3126 assert!(text.contains("#24]"), "{text}");
3127 assert!(text.contains("#28]"), "{text}");
3128 assert!(text.contains("str q"), "{text}");
3129 }
3130
3131 #[test]
3134 fn an_aarch64_long_double_is_a_quad_in_a_vector_register() {
3135 let mut opts = options();
3136 opts.emit = EmitKind::Asm;
3137 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3138 let source = "void f(long double *p, long double x) { *p = *p + x; }\n";
3139 let result = run(&opts, source);
3140 assert!(!result.failed(), "{:?}", result.messages);
3141 let text = result.text();
3142 assert!(text.contains("ldr q"), "{text}");
3143 assert!(text.contains("str q"), "{text}");
3144 assert!(text.contains("__addtf3"), "{text}");
3145 }
3146
3147 #[test]
3150 fn an_aarch64_thread_local_is_reached_through_tpidr_el0() {
3151 let mut opts = options();
3152 opts.emit = EmitKind::Asm;
3153 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3154 let source = "__thread int n;\nint *f(void) { return &n; }\n\
3155 void *g(void) { return __builtin_thread_pointer(); }\n";
3156 let result = run(&opts, source);
3157 assert!(!result.failed(), "{:?}", result.messages);
3158 let text = result.text();
3159 assert!(text.contains(":gottprel:n"), "{text}");
3160 assert!(text.contains(":gottprel_lo12:n]"), "{text}");
3161 assert_eq!(text.matches("mrs x").count(), 2, "{text}");
3162 assert!(text.contains("tpidr_el0"), "{text}");
3163 }
3164
3165 #[test]
3168 fn a_darwin_thread_local_is_reached_through_its_descriptor() {
3169 let source = "__thread int n = 5;\nint *f(void) { return &n; }\n";
3170 for (triple, wanted) in [
3171 ("aarch64-apple-darwin", &["_n@TLVPPAGE\n", "_n@TLVPPAGEOFF]\n", "\tblr x"][..]),
3172 ("x86_64-apple-darwin", &["_n@TLVP(%rip), %rdi\n", "\tcall\t*%"][..]),
3173 ] {
3174 let mut opts = options();
3175 opts.emit = EmitKind::Asm;
3176 opts.target = triple.parse::<Triple>().unwrap();
3177 let result = run(&opts, source);
3178 assert!(!result.failed(), "{triple}: {:?}", result.messages);
3179 let text = result.text();
3180 for want in wanted {
3181 assert!(text.contains(want), "{triple} wanted {want:?}:\n{text}");
3182 }
3183 assert!(text.contains("\n_n:\n\t.quad\t__tlv_bootstrap\n"), "{text}");
3184 assert!(!text.contains("tpidr_el0") && !text.contains("%fs"), "{text}");
3185 }
3186 }
3187
3188 #[test]
3190 fn the_thread_pointer_is_refused_on_darwin() {
3191 let mut opts = options();
3192 opts.emit = EmitKind::Asm;
3193 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3194 let result = run(&opts, "void *f(void) { return __builtin_thread_pointer(); }\n");
3195 assert!(result.failed());
3196 assert!(result.messages[0].contains("thread pointer"), "{:?}", result.messages);
3197 }
3198
3199 #[test]
3202 fn a_darwin_variadic_definition_saves_nothing_and_walks_the_stack() {
3203 let mut opts = options();
3204 opts.emit = EmitKind::Asm;
3205 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3206 let source = "int f(int n, ...) { __builtin_va_list ap; __builtin_va_start(ap, n);\n\
3207 int r = __builtin_va_arg(ap, int); __builtin_va_end(ap); return r; }\n";
3208 let result = run(&opts, source);
3209 assert!(!result.failed(), "{:?}", result.messages);
3210 let text = result.text();
3211 assert!(!text.contains("str q"), "{text}");
3212 assert!(!text.contains("x7"), "{text}");
3213 }
3214
3215 #[test]
3218 fn a_darwin_call_puts_its_variadic_arguments_in_memory() {
3219 let mut opts = options();
3220 opts.emit = EmitKind::Asm;
3221 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3222 let source = "int printf(const char *, ...);\n\
3223 int g(double x) { return printf(\"%d %f\", 7, x); }\n";
3224 let result = run(&opts, source);
3225 assert!(!result.failed(), "{:?}", result.messages);
3226 let text = result.text();
3227 assert!(text.contains("str d0, [sp, #8]"), "{text}");
3228 }
3229
3230 #[test]
3234 fn a_darwin_listing_is_one_apples_assembler_reads() {
3235 let mut opts = options();
3236 opts.emit = EmitKind::Asm;
3237 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3238 let source = "extern int ext;\n\
3239 int g[4];\n\
3240 int f(int i) { return g[i] + ext; }\n";
3241 let result = run(&opts, source);
3242 assert!(!result.failed(), "{:?}", result.messages);
3243 let text = result.text();
3244 assert!(text.contains(", _g@PAGE\n"), "{text}");
3245 assert!(text.contains(", _g@PAGEOFF\n"), "{text}");
3246 assert!(text.contains(", _ext@GOTPAGE\n"), "{text}");
3247 assert!(text.contains(", _ext@GOTPAGEOFF]\n"), "{text}");
3248 assert!(!text.contains(":lo12:"), "{text}");
3249 assert!(text.contains("\t.globl\t_g\n\t.zerofill\t__DATA,__bss,_g,16,2\n"), "{text}");
3250 }
3251
3252 #[test]
3258 fn a_signed_char_on_aarch64_is_widened_with_its_sign_before_it_is_added_to() {
3259 for target in ["aarch64-linux-gnu", "aarch64-apple-darwin"] {
3260 let mut opts = options();
3261 opts.emit = EmitKind::Asm;
3262 opts.target = target.parse::<Triple>().unwrap();
3263 let source = "int f(signed char *p) { return *p + 1; }\n\
3264 unsigned g(unsigned short *p) { return *p + 1u; }\n";
3265 let result = run(&opts, source);
3266 assert!(!result.failed(), "{:?}", result.messages);
3267 let text = result.text();
3268 let signed = text.contains("\tsxtb w") || text.contains("\tldrsb w");
3269 assert!(signed, "{target}: {text}");
3270 }
3271 }
3272
3273 #[test]
3285 fn a_construct_the_back_end_cannot_reach_yet_is_reported_against_its_function() {
3286 let mut opts = options();
3287 opts.emit = EmitKind::MirFinal;
3288 let source = "void a(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
3289 s; s.x = 1; v[0] = s.x; }\n\
3290 void b(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
3291 s; s.x = 1; v[0] = s.x; }\n";
3292 let result = run(&opts, source);
3293 assert!(result.failed());
3294 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
3295 assert!(result.messages[0].contains("cannot generate code for 'a'"), "{:?}", result);
3296 assert!(result.messages[0].contains("wants more alignment"), "{:?}", result);
3297 assert!(result.messages[1].contains("cannot generate code for 'b'"), "{:?}", result);
3298 assert!(result.text().is_empty());
3299 }
3300
3301 #[test]
3309 fn a_variable_length_array_walks_its_pages_where_every_page_of_the_frame_is_to_be_touched() {
3310 let mut opts = options();
3311 opts.emit = EmitKind::MirFinal;
3312 let source = "void a(int n) { int v[n]; v[0] = 1; }\n";
3313 let plain = run(&opts, source);
3314 assert!(!plain.failed(), "{:?}", plain.messages);
3315 assert!(!plain.text().contains("cmp_set_a_64"), "{}", plain.text());
3316
3317 opts.stack_clash = true;
3318 let result = run(&opts, source);
3319 assert!(!result.failed(), "{:?}", result.messages);
3320 assert!(result.text().contains("cmp_set_a_64"), "{}", result.text());
3321 assert!(result.text().contains("or_mi_8"), "{}", result.text());
3322 }
3323
3324 #[test]
3334 fn a_function_that_keeps_a_frame_pointer_on_windows_reaches_an_object_file() {
3335 let mut opts = options();
3336 opts.emit = EmitKind::Object;
3337 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3338 let source = concat!(
3339 "void use(void *p);\n",
3340 "void array(int n) { int v[n]; v[0] = 1; use(v); }\n",
3341 "void taken(unsigned long n) { use(__builtin_alloca(n)); }\n",
3342 );
3343 let result = run(&opts, source);
3344 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3345 let bytes = match result.artifact {
3346 Artifact::Object { bytes, .. } => bytes,
3347 other => panic!("expected an object, got {other:?}"),
3348 };
3349 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3350
3351 let mut opts = options();
3354 opts.emit = EmitKind::Object;
3355 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3356 }
3357
3358 #[test]
3369 fn the_address_of_a_function_this_file_only_declares_reaches_a_windows_object() {
3370 let source = concat!(
3371 "void other(void *p);\n",
3372 "void takes(void (*f)(void *));\n",
3373 "void (*held)(void *);\n",
3374 "void pass(void) { takes(other); }\n",
3375 "void keep(void) { held = other; }\n",
3376 "void call(void) { other(0); }\n",
3377 );
3378 let mut opts = options();
3379 opts.emit = EmitKind::Object;
3380 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3381 let result = run(&opts, source);
3382 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3383 let bytes = match result.artifact {
3384 Artifact::Object { bytes, .. } => bytes,
3385 other => panic!("expected an object, got {other:?}"),
3386 };
3387 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3388
3389 let mut opts = options();
3392 opts.emit = EmitKind::Object;
3393 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3394 }
3395
3396 #[test]
3410 fn an_opcode_with_no_name_in_the_rule_language_is_named_by_its_own_spelling() {
3411 let mut opts = options();
3412 opts.emit = EmitKind::MirFinal;
3413 let source =
3414 "long double f(int a) {\n __int128 wide = a;\n return (long double) wide;\n}\n";
3415 let result = run(&opts, source);
3416 assert!(result.failed());
3417 assert!(
3418 result.messages[0].contains("no rule lowers a `sext` producing a `i128`"),
3419 "{result:?}"
3420 );
3421 assert!(result.messages[0].contains(":2:"), "the line the widening is on: {result:?}");
3422 assert!(!result.messages[0].contains("this instruction"), "{result:?}");
3423 }
3424
3425 #[test]
3427 fn the_note_on_unfinished_work_points_at_the_issues_rather_than_at_the_plan() {
3428 let mut opts = options();
3429 opts.emit = EmitKind::MirFinal;
3430 let source = "long double f(int a) { __int128 wide = a; return (long double) wide; }\n";
3431 let result = run(&opts, source);
3432 assert!(result.failed());
3433 let note = result.messages.iter().find(|line| line.contains("note:")).expect("a note");
3434 assert!(note.contains("https://github.com/tamnd/rucc/issues"), "{note}");
3435 assert!(!note.contains("spec/17-milestones.md"), "{note}");
3436 }
3437
3438 #[test]
3440 fn the_frame_flags_on_the_command_line_reach_the_generated_frame() {
3441 let source = "int f(int a) { return a; }\n";
3442 assert!(!mir(source).contains("$rbp"), "a leaf needs no frame pointer by default");
3443
3444 let mut opts = options();
3445 opts.emit = EmitKind::MirFinal;
3446 opts.frame_pointer = true;
3447 let kept = run(&opts, source).text().to_owned();
3448 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
3449 }
3450
3451 fn asm(source: &str) -> String {
3453 let mut opts = options();
3454 opts.emit = EmitKind::Asm;
3455 let result = run(&opts, source);
3456 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3457 result.text().to_owned()
3458 }
3459
3460 #[test]
3467 fn a_function_goes_from_c_to_assembly_an_assembler_would_take() {
3468 let text = asm("int add(int a, int b) { return a + b; }\n");
3469 assert!(text.contains("\t.globl\tadd\n"), "{text}");
3470 assert!(text.contains("\t.type\tadd, @function\n"), "{text}");
3471 assert!(text.contains("\nadd:\n"), "{text}");
3472 assert!(text.contains("\taddl\t"), "{text}");
3473 assert!(text.contains("\tret\n"), "{text}");
3474 assert!(text.contains("\t.size\tadd, .-add\n"), "{text}");
3475 assert!(text.contains(".note.GNU-stack"), "{text}");
3478 }
3479
3480 #[test]
3486 fn a_call_through_a_function_pointer_goes_through_the_register_it_is_in() {
3487 let text = asm("int g(int);\nint f(int (*p)(int), int a) { return p(a) + g(a); }\n");
3488 assert!(text.contains("\tcall\t*%"), "{text}");
3489 assert!(text.contains("\tcall\tg\n"), "{text}");
3490 assert!(text.contains("%rdi"), "{text}");
3494 }
3495
3496 #[test]
3500 fn the_address_of_a_global_is_read_from_the_instruction_pointer() {
3501 let text = asm("extern int counter;\nint f(void) { return counter; }\n");
3502 assert!(text.contains("\tmovl\tcounter(%rip), %eax\n"), "{text}");
3503 }
3504
3505 #[test]
3514 fn a_branch_on_a_comparison_jumps_on_the_opposite_of_what_it_compared() {
3515 let arms = "return 1; return 2;";
3516 let signed = [("==", "jne"), ("!=", "je"), ("<", "jge"), ("<=", "jg"), (">", "jle")];
3517 for (operator, jump) in signed.into_iter().chain([(">=", "jl")]) {
3518 let text = asm(&format!("int f(int a, int b) {{ if (a {operator} b) {arms} }}\n"));
3519 assert!(
3520 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
3521 "{operator}: {text}"
3522 );
3523 assert!(!text.contains("\tset"), "{operator}: {text}");
3524 assert!(!text.contains("\ttest"), "{operator}: {text}");
3525 }
3526 let unsigned = [("<", "jae"), ("<=", "ja"), (">", "jbe"), (">=", "jb")];
3527 for (operator, jump) in unsigned {
3528 let source =
3529 format!("int f(unsigned a, unsigned b) {{ if (a {operator} b) {arms} }}\n");
3530 let text = asm(&source);
3531 assert!(
3532 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
3533 "{operator}: {text}"
3534 );
3535 }
3536
3537 let text = asm("int f(int a) { if (a < 7) return 1; return 2; }\n");
3540 assert!(text.contains("\tcmpl\t$7, %edi\n\tjge\t"), "{text}");
3541 }
3542
3543 #[test]
3549 fn a_comparison_whose_answer_the_program_wanted_still_writes_a_byte() {
3550 let text = asm("int f(int a, int b) { return a < b; }\n");
3551 assert!(text.contains("\tsetl\t"), "{text}");
3552 }
3553
3554 fn optimized(source: &str) -> String {
3556 let mut opts = options();
3557 opts.emit = EmitKind::Asm;
3558 opts.opt_level = rucc_session::OptLevel::O2;
3559 let result = run(&opts, source);
3560 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3561 result.text().to_owned()
3562 }
3563
3564 #[test]
3574 fn a_switch_whose_arms_are_a_function_of_the_label_is_a_range_check_and_arithmetic() {
3575 let arms: String =
3576 (0..16).map(|k| format!("case {k}: return {};", k + 1)).collect::<Vec<_>>().join(" ");
3577 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3578 assert!(text.contains("\tcmpl\t$15, %edi\n\tja\t"), "{text}");
3579 assert!(text.contains("\taddl\t$1, %edi"), "{text}");
3580 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
3581 }
3582
3583 #[test]
3591 fn a_dense_switch_whose_arms_are_not_a_line_is_a_load_from_a_table() {
3592 let arms: String = (0..16)
3593 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
3594 .collect::<Vec<_>>()
3595 .join(" ");
3596 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3597 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
3598 assert!(!text.contains("\tjmp\t*"), "{text}");
3599 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
3600 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
3601 let section = text[..text.find("CSWTCH.0:").unwrap_or(0)].rfind("\t.section\t.rodata");
3602 assert!(section.is_some(), "{text}");
3603 assert_eq!(table.matches("\t.long\t").count(), 16, "{text}");
3604 assert!(table.contains("\t.long\t100\n"), "{text}");
3605 }
3606
3607 #[test]
3613 fn a_table_at_os_has_cells_as_narrow_as_its_answers() {
3614 let arms: String = (0..16)
3615 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
3616 .collect::<Vec<_>>()
3617 .join(" ");
3618 let mut opts = options();
3619 opts.emit = EmitKind::Asm;
3620 opts.opt_level = rucc_session::OptLevel::Os;
3621 let result = run(&opts, &format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3622 assert_eq!(result.messages, Vec::<String>::new());
3623 let text = result.text();
3624 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
3625 assert_eq!(table.matches("\t.byte\t").count(), 16, "{text}");
3626 assert!(text.contains("\tmovsbl\t"), "{text}");
3627 }
3628
3629 #[test]
3636 fn a_table_that_covers_its_operand_has_no_range_check() {
3637 let text = optimized(
3638 "int f(unsigned x) { switch (x & 3) { case 0: return 5; case 1: return 9; \
3639 case 2: return 2; case 3: return 7; } return -1; }\n",
3640 );
3641 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
3642 assert!(!text.contains("\tcmp"), "{text}");
3643 assert!(!text.contains("$-1"), "{text}");
3644 }
3645
3646 #[test]
3651 fn a_store_to_a_local_the_loop_just_read_is_a_conditional_move() {
3652 let text = optimized(
3653 "int f(const int *v, int n, int k) { int best[8] = {0}; \
3654 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; \
3655 return best[k & 7]; }\n",
3656 );
3657 assert!(text.contains("\tcmovgl"), "{text}");
3658 assert!(!text.contains("\tset"), "{text}");
3659 assert!(!text.contains("\ttestb"), "{text}");
3660 }
3661
3662 #[test]
3664 fn a_store_to_a_global_the_loop_just_read_keeps_its_branch() {
3665 let text = optimized(
3666 "int best[8]; void f(const int *v, int n) { \
3667 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; }\n",
3668 );
3669 assert!(!text.contains("\tcmov"), "{text}");
3670 }
3671
3672 #[test]
3680 fn a_conversion_from_a_constant_double_is_the_number_it_converts_to() {
3681 let text = optimized("int f(void) { double d = 2.75; return (int) d; }\n");
3682 assert!(text.contains("movl\t$2, %eax"), "{text}");
3683 assert!(!text.contains("cvttsd2si"), "{text}");
3684 }
3685
3686 #[test]
3694 fn a_slot_of_a_read_only_table_is_the_value_the_table_holds() {
3695 let text =
3696 optimized("static const int t[4] = {10, 20, 30, 40};\nint f(void) { return t[2]; }\n");
3697 assert!(text.contains("movl\t$30, %eax"), "{text}");
3698 assert!(!text.contains("t(%rip)"), "{text}");
3699 }
3700
3701 #[test]
3704 fn a_byte_of_a_read_only_string_is_the_byte_the_string_spells() {
3705 let text = optimized("static const char s[] = \"abc\";\nint f(void) { return s[1]; }\n");
3706 assert!(text.contains("movl\t$98, %eax"), "{text}");
3707 }
3708
3709 #[test]
3713 fn a_table_that_is_not_read_only_keeps_its_load() {
3714 let text = optimized(
3715 "static int t[4] = {10, 20, 30, 40};\nvoid g(int x) { t[2] = x; }\nint f(void) { return t[2]; }\n",
3716 );
3717 assert!(!text.contains("movl\t$30, %eax"), "{text}");
3718 }
3719
3720 #[test]
3727 fn a_call_guarded_by_a_condition_a_read_only_object_settles_is_not_emitted() {
3728 let text = optimized(
3729 "void link_error(void);\nconst double one = 1.0;\nint main(void) { if ((int) one != 1) link_error(); return 0; }\n",
3730 );
3731 assert!(!text.contains("call\tlink_error"), "{text}");
3732 }
3733
3734 #[test]
3736 fn a_cast_between_a_pointer_and_an_integer_leaves_the_value_where_it_is() {
3737 let text = asm("long f(void *p) { return (long)p; }\n");
3738 for line in text.lines().filter(|line| line.starts_with('\t') && !line.contains('.')) {
3743 let mnemonic = line.split_whitespace().next().unwrap_or("");
3744 assert!(matches!(mnemonic, "movq" | "ret"), "{line} in\n{text}");
3745 }
3746 }
3747
3748 #[test]
3752 fn an_argument_past_the_last_register_is_read_out_of_the_caller_s_stack() {
3753 let six = "long a, long b, long c, long d, long e, long f";
3754 let text = asm(&format!("long f({six}, long g, long h) {{ return g + h; }}\n"));
3755
3756 assert!(text.contains("\tmovq\t8(%rsp), "), "{text}");
3763 assert!(text.contains("\taddq\t16(%rsp), "), "{text}");
3764
3765 let narrow = asm(&format!("int f({six}, int g) {{ return g; }}\n"));
3769 assert!(narrow.contains("\tmovl\t8(%rsp), "), "{narrow}");
3770 let eight =
3771 "double a, double b, double c, double d, double e, double f, double g, double h";
3772 let float = asm(&format!("double f({eight}, double i) {{ return i; }}\n"));
3773 assert!(float.contains("\tmovsd\t8(%rsp), "), "{float}");
3774 }
3775
3776 #[test]
3779 fn a_call_writes_the_arguments_with_no_register_left_at_the_stack_pointer() {
3780 let six = "1, 2, 3, 4, 5, 6";
3781 let decl = "long g(long, long, long, long, long, long, long, long);\n";
3782 let text = asm(&format!("{decl}long f(void) {{ return g({six}, 7, 8); }}\n"));
3783
3784 assert!(text.contains("\tmovq\t%"), "{text}");
3785 assert!(text.contains(", (%rsp)\n"), "{text}");
3786 assert!(text.contains(", 8(%rsp)\n"), "{text}");
3787 assert!(text.contains("\tsubq\t$"), "{text}");
3789
3790 let narrow = "int g(int, int, int, int, int, int, int);\n";
3792 let text = asm(&format!("{narrow}int f(void) {{ return g({six}, 7); }}\n"));
3793 assert!(text.contains("\tmovl\t%"), "{text}");
3794 assert!(text.contains(", (%rsp)\n"), "{text}");
3795 }
3796
3797 #[test]
3800 fn a_variadic_call_counts_registers_and_not_arguments() {
3801 let nine = "1., 2., 3., 4., 5., 6., 7., 8., 9.";
3802 let decl = "int g(int, ...);\n";
3803 let text = asm(&format!("{decl}int f(void) {{ return g(0, {nine}); }}\n"));
3804
3805 assert!(text.contains("\tmovl\t$8, "), "eight registers, not nine: {text}");
3806 assert!(text.contains("\tmovsd\t%"), "{text}");
3807 assert!(text.contains(", (%rsp)\n"), "{text}");
3808 }
3809
3810 #[test]
3815 fn a_variadic_function_writes_the_argument_registers_it_was_handed_into_its_frame() {
3816 let body =
3817 "__builtin_va_list ap; __builtin_va_start(ap, n); __builtin_va_end(ap); return n;";
3818 let text = asm(&format!("int f(int n, ...) {{ {body} }}\n"));
3819
3820 let stores = |mnemonic: &str| text.matches(&format!("\t{mnemonic}\t%")).count();
3823 assert!(text.contains(", 8(%r"), "the second slot, not the first: {text}");
3824 assert!(!text.contains(", 0(%r"), "{text}");
3825 assert_eq!(stores("movaps"), 8, "every vector register: {text}");
3828 assert_eq!(stores("movsd"), 0, "and the whole of each one: {text}");
3829
3830 assert!(text.contains("\tsubq\t$"), "{text}");
3832 }
3833
3834 #[test]
3837 fn va_start_writes_the_four_fields_the_psabi_describes() {
3838 let start = "__builtin_va_list ap; __builtin_va_start(ap, d);";
3839 let params = "int a, int b, int c, double d";
3840 let text = asm(&format!("int f({params}, ...) {{ {start} return a; }}\n"));
3841
3842 assert!(text.contains(" movl $24, "), "{text}");
3846 assert!(text.contains(" movl $64, "), "{text}");
3847 assert!(text.contains(", 8(%r"), "{text}");
3851 assert!(text.contains(", 16(%r"), "{text}");
3852 let frame: u32 = text
3853 .lines()
3854 .find_map(|line| line.trim().strip_prefix("subq $")?.split(',').next()?.parse().ok())
3855 .expect("a variadic function takes a frame for the save area");
3856 let above = |line: &str| {
3857 let at: u32 = line.trim().strip_prefix("leaq ")?.split('(').next()?.parse().ok()?;
3858 Some(at > frame)
3859 };
3860 assert!(text.lines().filter_map(above).any(|it| it), "{frame}: {text}");
3861 }
3862
3863 #[test]
3866 fn va_arg_branches_on_whether_the_argument_is_still_in_the_save_area() {
3867 let read = "__builtin_va_list ap; __builtin_va_start(ap, n);";
3868 let ints = format!("int f(int n, ...) {{ {read} return __builtin_va_arg(ap, int); }}\n");
3869 let text = asm(&ints);
3870
3871 assert!(text.contains("$40, "), "{text}");
3874 assert!(text.contains(" cmpl "), "{text}");
3875 assert!(text.contains(" ja "), "{text}");
3879
3880 let arg = "__builtin_va_arg(ap, double)";
3881 let text = asm(&format!("double f(int n, ...) {{ {read} return {arg}; }}\n"));
3882 assert!(text.contains("$160, "), "the last vector slot: {text}");
3883 }
3884
3885 #[test]
3888 fn a_structure_assignment_is_a_move_for_each_word_of_it() {
3889 let decl = "struct pair { long a, b; };\n";
3890 let body = "struct pair p = *q; return p.a + p.b;";
3891 let text = asm(&format!("{decl}long f(struct pair *q) {{ {body} }}\n"));
3892
3893 assert!(!text.contains("memcpy"), "nothing calls the library: {text}");
3894 assert!(!text.contains("\tcall"), "{text}");
3895 assert!(text.matches("\tmovq\t").count() >= 4, "two words each way: {text}");
3897 }
3898
3899 #[test]
3902 fn how_wide_a_word_of_a_copy_is_follows_the_alignment() {
3903 let decl = "struct bytes { char a[8]; };\n";
3904 let body = "struct bytes p = *q; return p.a[0];";
3905 let text = asm(&format!("{decl}int f(struct bytes *q) {{ {body} }}\n"));
3906
3907 assert!(text.matches("\tmovb\t").count() >= 16, "a byte at a time: {text}");
3909 }
3910
3911 #[test]
3914 fn the_part_of_an_initialiser_that_names_nothing_is_stored_as_zero() {
3915 let decl = "struct wide { long a, b, c; };\n";
3916 let text = asm(&format!("{decl}long f(void) {{ struct wide w = {{ 7 }}; return w.c; }}\n"));
3917
3918 assert!(!text.contains("memset"), "nothing calls the library: {text}");
3919 assert!(text.contains("\tmovq\t$0, ") || text.contains("\txorl\t"), "the zero: {text}");
3924 }
3925
3926 #[test]
3929 fn a_copy_too_large_to_unroll_calls_the_runtime() {
3930 let decl = "struct huge { char a[4096]; };\n";
3931 let mut opts = options();
3932 opts.emit = EmitKind::Asm;
3933 let source = format!("{decl}void f(struct huge *p, struct huge *q) {{ *p = *q; }}\n");
3934 let result = run(&opts, &source);
3935 assert!(!result.failed(), "{:?}", result.messages);
3936 let text = result.text();
3937 assert!(text.contains("call") && text.contains("memcpy"), "{text}");
3938 assert!(text.contains("4096"), "the size travels: {text}");
3941 }
3942
3943 #[test]
3950 fn a_structure_too_large_to_unroll_is_copied_into_the_argument_area_by_the_runtime() {
3951 let decl = "struct huge { char a[4096]; };\nint take(struct huge);\n";
3952 let text = asm(&format!("{decl}int f(struct huge *p) {{ return take(*p); }}\n"));
3953
3954 let copy = text.find("call\tmemcpy").expect("the copy");
3955 let call = text.find("call\ttake").expect("the call");
3956 assert!(copy < call, "the copy comes first: {text}");
3957 assert!(text.contains("movq\t%rsp, %rdi"), "the destination: {text}");
3962 assert!(text.contains("$4096, %edx"), "the size: {text}");
3963 }
3964
3965 #[test]
3968 fn a_realigned_frame_reads_them_through_the_frame_pointer() {
3969 let six = "long a, long b, long c, long d, long e, long f";
3970 let body = "_Alignas(32) long wide[4]; wide[0] = g; return wide[0];";
3971 let text = asm(&format!("long f({six}, long g) {{ {body} }}\n"));
3972
3973 assert!(text.contains("\tandq\t$-32, %rsp"), "{text}");
3977 assert!(text.contains("\tmovq\t16(%rbp), "), "{text}");
3978 assert!(!text.contains("\tmovq\t16(%rsp), "), "{text}");
3979 }
3980
3981 #[test]
3983 fn the_target_decides_how_the_assembly_is_spelled() {
3984 let mut opts = options();
3985 opts.emit = EmitKind::Asm;
3986 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
3987 let text = run(&opts, "int f(void) { return 0; }\n").text().to_owned();
3988 assert!(text.contains("__TEXT,__text"), "{text}");
3989 assert!(text.contains("\n_f:\n"), "{text}");
3990 assert!(!text.contains(".note.GNU-stack"), "{text}");
3991 }
3992
3993 fn obj(source: &str) -> Vec<u8> {
3995 let mut opts = options();
3996 opts.emit = EmitKind::Object;
3997 let result = run(&opts, source);
3998 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3999 match result.artifact {
4000 Artifact::Object { bytes, .. } => bytes,
4001 other => panic!("expected an object, got {other:?}"),
4002 }
4003 }
4004
4005 #[test]
4011 fn a_function_goes_from_c_to_an_object_a_linker_would_take() {
4012 let bytes = obj("int add(int a, int b) { return a + b; }\n");
4013 assert_eq!(&bytes[..4], b"\x7fELF", "an object file starts by saying it is one");
4014 let text = asm("int add(int a, int b) { return a + b; }\n");
4015 assert!(
4016 text.contains("\taddl\t"),
4017 "and the listing of it is the same instructions:\n{text}"
4018 );
4019 }
4020
4021 #[test]
4023 fn a_variable_goes_from_c_to_the_section_it_belongs_in() {
4024 let text = asm("int counter = 42;\nstatic int hidden;\nconst int fixed = 7;\n");
4025 assert!(text.contains("\t.data\n\t.globl\tcounter\n"), "{text}");
4026 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
4027 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
4028 assert!(text.contains("\t.bss\n\t.p2align\t2\n"), "{text}");
4031 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
4032 assert!(!text.contains(".globl\thidden"), "{text}");
4033 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4036 }
4037
4038 #[test]
4045 fn a_bit_field_initializer_writes_every_byte_of_the_value_and_not_only_the_ones_that_are_set() {
4046 let text = asm("struct s { unsigned f : 20; } x = { 0x12300 };\n");
4047 assert!(text.contains("\t.data\n"), "there is something to write: {text}");
4048 assert!(text.contains("\nx:\n\t.ascii\t\"\\000#\\001\"\n"), "and it is the value: {text}");
4049
4050 let text = asm("struct s { unsigned a : 8; unsigned b : 8; } x = { 0, 3 };\n");
4053 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\003\"\n"), "{text}");
4054
4055 let text = asm("struct s { unsigned long long f : 40; } x = { 0x100000 };\n");
4058 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\000\\020\"\n\t.space\t5\n"), "{text}");
4059
4060 let text = asm("struct s { unsigned f : 20; } x = { 0 };\n");
4062 assert!(text.contains("\t.bss\n"), "an object of zeroes is zeroes: {text}");
4063 assert!(text.contains("\nx:\n\t.space\t4\n"), "{text}");
4064 }
4065
4066 #[test]
4068 fn a_string_literal_is_a_variable_with_a_name_no_program_could_write() {
4069 let text = asm("const char *f(void) { return \"hi\"; }\n");
4070 assert!(text.contains("\t.ascii\t\"hi\\000\"\n"), "{text}");
4071 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4072 let label = text
4073 .lines()
4074 .find(|line| line.starts_with(".Lstr"))
4075 .unwrap_or_else(|| panic!("a label for the literal in\n{text}"));
4076 assert!(!text.contains(&format!(".globl\t{}", label.trim_end_matches(':'))), "{text}");
4077 }
4078
4079 #[test]
4081 fn an_address_in_an_initializer_is_left_to_the_linker() {
4082 let source = "int counter;\nint *p = &counter;\n";
4083 let text = asm(source);
4084 assert!(text.contains("\np:\n\t.quad\tcounter\n"), "{text}");
4085 let bytes = obj(source);
4088 assert!(bytes.windows(8).any(|w| w == b"counter\0"), "the object has to name it");
4089 }
4090
4091 #[test]
4100 fn a_constant_holding_an_address_goes_in_the_section_the_loader_may_write_once() {
4101 let text = asm("static void a(void) {}\nstatic void b(void) {}\n\
4104 struct m { void (*x)(void); void (*y)(void); };\n\
4105 const struct m t = { a, b };\n");
4106 assert!(text.contains("\t.section\t.data.rel.ro.local,\"aw\",@progbits\n"), "{text}");
4107 assert!(text.contains("\nt:\n\t.quad\ta\n\t.quad\tb\n"), "{text}");
4108
4109 let text =
4112 asm("void a(void);\nstruct m { void (*x)(void); };\nconst struct m t = { a };\n");
4113 assert!(text.contains("\t.section\t.data.rel.ro,\"aw\",@progbits\n"), "{text}");
4114
4115 let text = asm("const int fixed = 7;\n");
4117 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4118 }
4119
4120 #[test]
4127 fn a_thread_local_variable_is_storage_a_thread_gets_a_copy_of_and_an_offset_into_it() {
4128 let text = asm("_Thread_local int x = 1;\nint read(void) { return x; }\n");
4129 assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
4132 assert!(text.contains("\t.type\tx, @tls_object\n"), "{text}");
4133 assert!(text.contains("x@GOTTPOFF(%rip)"), "{text}");
4136 assert!(text.contains("%fs:0"), "{text}");
4137 }
4138
4139 #[test]
4145 fn the_address_of_this_thread_s_own_storage_is_read_out_of_the_segment_register() {
4146 let text = asm("void *here(void) { return __builtin_thread_pointer(); }\n");
4147 assert!(text.contains("movq\t%fs:0, "), "{text}");
4148 assert!(!text.contains("GOTTPOFF"), "{text}");
4150 }
4151
4152 #[test]
4163 fn a_prefetch_is_one_of_four_instructions_and_the_locality_is_what_picks() {
4164 for (locality, wanted) in
4165 [(0, "prefetchnta"), (1, "prefetcht2"), (2, "prefetcht1"), (3, "prefetcht0")]
4166 {
4167 let source =
4168 format!("void warm(void *p) {{ __builtin_prefetch(p, 0, {locality}); }}\n");
4169 let text = asm(&source);
4170 assert!(text.contains(&format!("\t{wanted}\t")), "locality {locality}: {text}");
4171 }
4172 let text = asm("void warm(void *p) { __builtin_prefetch(p); }\n");
4174 assert!(text.contains("\tprefetcht0\t"), "{text}");
4175 let text = asm("void warm(void *p) { __builtin_prefetch(p, 1); }\n");
4178 assert!(text.contains("\tprefetcht0\t"), "{text}");
4179 assert!(!text.contains("prefetchw"), "{text}");
4180 }
4181
4182 #[test]
4193 fn a_trap_is_the_instruction_the_machine_has_no_meaning_for() {
4194 let text = asm("void stop(void) { __builtin_trap(); }\n");
4195 assert!(text.contains("\tud2\n"), "{text}");
4196 assert!(!text.contains("\tcall"), "a stop is not a call to anything: {text}");
4197
4198 let text = asm("int stop(int a) { __builtin_trap(); return a + 1; }\n");
4199 assert!(text.contains("\tud2\n"), "{text}");
4200 assert!(text.contains("\taddl\t"), "the block goes on after a stop: {text}");
4201 }
4202
4203 #[test]
4215 fn assume_aligned_is_its_first_argument_and_keeps_the_rest() {
4216 let text = asm("void *aligned(char *p) { return __builtin_assume_aligned(p, 16); }\n");
4217 assert!(!text.contains("assume_aligned"), "{text}");
4218 assert!(!text.contains("\tcall"), "nothing is called for an alignment fact: {text}");
4219
4220 let source = "unsigned long width(void);\n\
4221 void *aligned(char *p) { return __builtin_assume_aligned(p, width()); }\n";
4222 let text = asm(source);
4223 assert!(!text.contains("assume_aligned"), "{text}");
4224 assert!(text.contains("width"), "the argument that is not the answer still runs: {text}");
4225 }
4226
4227 #[test]
4237 fn the_frame_address_is_the_frame_pointer_after_walking_that_many_links() {
4238 let text = asm("void *here(void) { return __builtin_frame_address(0); }\n");
4239 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
4240 assert!(text.contains("movq\t%rbp, %rax"), "{text}");
4241 assert!(!text.contains("\tcall"), "a frame address is not a call to anything: {text}");
4242
4243 let walk = |depth: u32| {
4244 let source = format!("void *up(void) {{ return __builtin_frame_address({depth}); }}\n");
4245 asm(&source).matches("movq\t(%r").count()
4246 };
4247 assert_eq!(walk(1), 1, "one link is one load");
4248 assert_eq!(walk(3), 3, "three links are three loads");
4249 }
4250
4251 #[test]
4261 fn the_return_address_is_one_word_above_the_frame_the_walk_ended_at() {
4262 let text = asm("void *back(void) { return __builtin_return_address(0); }\n");
4263 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
4264 assert!(text.contains("movq\t8(%rbp), %rax"), "{text}");
4265 assert!(!text.contains("\tcall"), "a return address is not a call to anything: {text}");
4266
4267 let text = asm("void *back(void) { return __builtin_return_address(2); }\n");
4268 assert_eq!(text.matches("movq\t(%r").count(), 2, "two links are two loads: {text}");
4269 assert!(text.contains("movq\t8(%r"), "and the answer is above the last of them: {text}");
4270 }
4271
4272 #[test]
4283 fn a_depth_that_is_not_a_small_constant_is_refused() {
4284 let mut opts = options();
4285 opts.emit = EmitKind::Ir;
4286 for source in [
4287 "void *up(int n) { return __builtin_return_address(n); }\n",
4288 "void *up(void) { return __builtin_frame_address(1000); }\n",
4289 ] {
4290 let messages = run(&opts, source).messages;
4291 let named = messages.iter().any(|m| m.contains("E0705"));
4292 assert!(named, "expected a refusal in {messages:?}");
4293 }
4294 }
4295
4296 #[test]
4308 fn an_alloca_takes_the_bytes_off_the_stack_pointer_and_answers_where_they_are() {
4309 let text =
4310 asm("void use(void *p); void f(unsigned long n) { use(__builtin_alloca(n)); }\n");
4311 assert!(text.contains("andq\t$-16"), "the size is rounded up to sixteen: {text}");
4312 assert!(text.contains("subq\t%rdi, %rsp"), "and taken off the stack pointer: {text}");
4313 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
4314
4315 let plain = concat!(
4318 "extern void *alloca(__SIZE_TYPE__);\n",
4319 "void use(void *p);\n",
4320 "void f(unsigned long n) { use(alloca(n)); }\n",
4321 );
4322 let text = asm(plain);
4323 assert!(text.contains("subq\t%rdi, %rsp"), "the plain name is the same bytes: {text}");
4324 assert_eq!(text.matches("\tcall").count(), 1, "and is not a call either: {text}");
4325
4326 let own = concat!(
4329 "static void *alloca(unsigned long n) { return 0; }\n",
4330 "void *f(unsigned long n) { return alloca(n); }\n",
4331 );
4332 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
4333 }
4334
4335 #[test]
4350 fn a_builtin_the_program_never_declared_is_the_builtin_rather_than_the_one_c89_wrote_down() {
4351 let mut opts = options();
4355 opts.permissive = true;
4356 let undeclared = "void use(void *p);
4357void f(unsigned long n) { use(alloca(n)); }
4358";
4359 assert_eq!(
4360 run(&opts, undeclared).messages,
4361 [
4362 "/main.c:2:31: warning: implicit declaration of function 'alloca' [E0521]",
4363 "/main.c:2:31: warning: incompatible implicit declaration of built-in function \
4364 'alloca' [E0713]",
4365 ]
4366 );
4367
4368 opts.emit = EmitKind::Asm;
4369 let text = run(&opts, undeclared).text().to_owned();
4370 assert!(text.contains("subq\t%rdi, %rsp"), "the bytes come off the stack: {text}");
4371 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
4372
4373 let string = "unsigned long f(void) { return strlen(\"abc\"); }\n";
4376 let text = run(&opts, string).text().to_owned();
4377 assert!(text.contains("call\tstrlen"), "strlen is still a call: {text}");
4378
4379 let own = concat!(
4382 "static void *alloca(unsigned long n) { return 0; }\n",
4383 "void *f(unsigned long n) { return alloca(n); }\n",
4384 );
4385 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
4386 }
4387
4388 #[test]
4398 fn the_bytes_an_alloca_took_are_still_there_at_the_end_of_the_block_that_took_them() {
4399 let inner = "{ use(__builtin_alloca(n)); }";
4400 for body in [inner.to_owned(), format!("int a[n]; {inner} use(a);")] {
4401 let source = format!("void use(void *p);\nvoid f(unsigned long n) {{ {body} }}\n");
4402 let text = asm(&source);
4403 for line in text.lines().filter(|line| line.trim_end().ends_with(", %rsp")) {
4407 let taking = line.contains("subq");
4408 let leaving = line.contains("%rbp");
4409 assert!(taking || leaving, "nothing puts the stack back: {line} in {text}");
4410 }
4411 }
4412 }
4413
4414 #[test]
4416 fn the_object_and_the_listing_are_two_spellings_of_one_compilation() {
4417 let source = "int callee(void); int g(void) { return callee(); }\n";
4421 let bytes = obj(source);
4422 assert!(
4423 bytes.windows(7).any(|w| w == b"callee\0"),
4424 "the object has to name the callee for the linker to find it"
4425 );
4426 let text = asm(source);
4427 assert!(text.contains("\tcall\tcallee\n"), "{text}");
4428 }
4429
4430 #[test]
4436 fn compiling_for_an_executable_produces_an_object_and_not_a_dump() {
4437 let mut opts = options();
4438 opts.emit = EmitKind::Executable;
4440 let result = run(&opts, "int main(void) { return 0; }\n");
4441 assert_eq!(result.messages, Vec::<String>::new());
4442 match result.artifact {
4443 Artifact::Object { bytes, .. } => assert_eq!(&bytes[..4], b"\x7fELF"),
4444 other => panic!("expected an object, got {other:?}"),
4445 }
4446 }
4447
4448 #[test]
4450 fn a_platform_with_no_object_writer_is_said_so_rather_than_written_as_elf() {
4451 let mut opts = options();
4452 opts.emit = EmitKind::Object;
4453 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
4454 let result = run(&opts, "int f(void) { return 0; }\n");
4455 assert!(result.failed(), "an object nobody can read is worse than a message");
4456 assert!(
4457 result.messages.iter().any(|m| m.contains("no object writer")),
4458 "{:?}",
4459 result.messages
4460 );
4461 }
4462
4463 fn ir(source: &str) -> String {
4465 let mut opts = options();
4466 opts.emit = EmitKind::Ir;
4467 let result = run(&opts, source);
4468 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4469 result.text().to_owned()
4470 }
4471
4472 fn errors(source: &str) -> Vec<String> {
4474 let mut opts = options();
4475 opts.emit = EmitKind::Ir;
4476 let result = run(&opts, source);
4477 assert!(result.failed(), "expected this to be refused:\n{source}");
4478 result.messages
4479 }
4480
4481 fn body(source: &str) -> String {
4483 let text = ir(source);
4484 let (_, rest) = text.split_once("{\n").expect("a function definition");
4485 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
4486 body.to_owned()
4487 }
4488
4489 #[test]
4497 fn gnu89_inline_is_what_decides_whether_a_bare_inline_definition_reaches_the_module() {
4498 let source = "inline int f(int x) { return x + 1; }\n";
4499 let with = |flag: bool| {
4500 let mut opts = options();
4501 opts.emit = EmitKind::Ir;
4502 opts.gnu89_inline = flag;
4503 let result = run(&opts, source);
4504 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
4505 result.text().to_owned()
4506 };
4507
4508 assert!(!with(false).contains("block0"), "no body: {}", with(false));
4511
4512 assert!(with(true).contains("block0"), "a body: {}", with(true));
4515 }
4516
4517 #[test]
4524 fn an_access_through_a_type_names_the_type_it_went_through() {
4525 let source = "\
4526struct s { int a; float b; };\n\
4527union u { int i; float f; };\n\
4528int scalar(int *p) { return *p; }\n\
4529float member(struct s *p) { p->a = 1; return p->b; }\n\
4530int element(int *a, long i) { return a[i]; }\n\
4531float through_a_union(union u *p) { p->i = 1; return p->f; }\n";
4532 let text = ir(source);
4533 assert!(text.contains(r#"!0 = tbaa "char""#), "the root: {text}");
4534 assert!(text.contains(r#"tbaa "int", parent !0"#), "int under it: {text}");
4535 assert!(text.contains(r#"tbaa "float", parent !0"#), "float under it: {text}");
4536 let named = text.lines().filter(|line| line.contains(", tbaa !")).count();
4539 assert_eq!(named, 6, "six accesses: {text}");
4540 }
4541
4542 #[test]
4549 fn turning_strict_aliasing_off_leaves_the_type_off_every_access() {
4550 let source = "int punned(float *f, int *i) { *i = 1; *f = 2.0f; return *i; }\n";
4551 let mut opts = options();
4552 opts.emit = EmitKind::Ir;
4553 opts.strict_aliasing = false;
4554 let result = run(&opts, source);
4555 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
4556 let text = result.text().to_owned();
4557 assert!(!text.contains("tbaa"), "not even the root: {text}");
4558 }
4559
4560 #[test]
4566 fn instrumenting_functions_calls_the_hooks_around_every_body_but_the_hooks() {
4567 let source = concat!(
4568 "#define NOCHK __attribute__((no_instrument_function))\n",
4569 "void __cyg_profile_func_enter(void *, void *) NOCHK;\n",
4570 "void __cyg_profile_func_exit(void *, void *) NOCHK;\n",
4571 "int calls;\n",
4572 "int pick(int x) { if (x) return 1; return 2; }\n",
4573 "void quiet(void) NOCHK;\n",
4574 "void quiet(void) { calls++; }\n",
4575 "void __cyg_profile_func_enter(void *fn, void *site) { calls++; }\n",
4576 "void __cyg_profile_func_exit(void *fn, void *site) { calls--; }\n",
4577 );
4578 let mut opts = options();
4579 opts.emit = EmitKind::Ir;
4580 opts.instrument_functions = true;
4581 let result = run(&opts, source);
4582 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
4583 let text = result.text().to_owned();
4584 let body = |name: &str| -> String {
4585 let open = format!("func @{name}(");
4586 let start = text.find(&open).unwrap_or_else(|| panic!("no {name}: {text}"));
4587 let rest = &text[start..];
4588 rest[..rest.find("\n}").unwrap_or(rest.len())].to_owned()
4589 };
4590 let pick = body("pick");
4591 assert_eq!(pick.matches("call @__cyg_profile_func_enter(").count(), 1, "{pick}");
4592 assert_eq!(pick.matches("call @__cyg_profile_func_exit(").count(), 2, "{pick}");
4593 assert!(pick.contains("return_address"), "{pick}");
4594 assert!(pick.contains("global_addr @pick"), "{pick}");
4595 for quiet in ["quiet", "__cyg_profile_func_enter", "__cyg_profile_func_exit"] {
4596 assert!(!body(quiet).contains("call "), "{quiet} is left alone: {text}");
4597 }
4598
4599 opts.instrument_functions = false;
4600 let result = run(&opts, source);
4601 assert!(!result.text().contains("call @__cyg_profile"), "off unless asked for");
4602 }
4603
4604 #[test]
4612 fn a_bare_return_from_a_function_that_promised_a_value_gives_back_a_zero() {
4613 let mut opts = options();
4614 opts.emit = EmitKind::Ir;
4615 opts.std = Std::C89;
4616 let compiled = |source: &str| {
4617 let result = run(&opts, source);
4618 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
4619 result.text().to_owned()
4620 };
4621
4622 let text = compiled("int f(int x) { if (x) return; return 3; }\n");
4623 assert!(text.contains("iconst.i32 0\n return"), "zero goes back: {text}");
4624 assert!(!text.contains("unreachable"), "the branch that reached it is kept: {text}");
4625
4626 let text = compiled("double f(int x) { if (x) return; return 1.0; }\n");
4628 assert!(text.contains("fconst.f64 0x0\n return"), "a float zero goes back: {text}");
4629 }
4630
4631 #[test]
4639 fn a_call_to_a_name_nothing_declared_declares_it_as_c89_said_to() {
4640 let mut opts = options();
4641 opts.emit = EmitKind::Ir;
4642 opts.std = Std::C89;
4643 let compiled = |source: &str| {
4644 let result = run(&opts, source);
4645 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
4646 result.text().to_owned()
4647 };
4648
4649 let text = compiled("int f(void) { return g(); }\n");
4651 assert!(text.contains("call @g"), "the call is to the name that was written: {text}");
4652 assert!(text.contains("i32"), "and it gives back an int: {text}");
4653
4654 let text = compiled("int f(char c) { return g(c); }\n");
4657 assert!(text.contains("sext.i32"), "the argument is promoted: {text}");
4658
4659 let mut opts = options();
4662 opts.std = Std::C89;
4663 let said = run(&opts, "int f(void) { return h; }\n").messages.join("\n");
4664 assert!(said.contains("'h' undeclared"), "not a call, so not declared: {said}");
4665 }
4666
4667 #[test]
4677 fn a_name_called_before_it_is_defined_still_gets_its_definition() {
4678 let mut opts = options();
4679 opts.emit = EmitKind::Ir;
4680 opts.std = Std::C89;
4681 let text = run(&opts, "int f(void) { return dummy(); }\ndummy () { return 7; }\n")
4682 .text()
4683 .to_owned();
4684 assert!(text.contains("func @f()"), "the caller is there: {text}");
4685 assert!(text.contains("func @dummy"), "and so is what it calls: {text}");
4686 assert!(text.contains("iconst.i32 7"), "with the body it was given: {text}");
4687 }
4688
4689 #[test]
4697 fn an_old_style_parameter_is_converted_from_what_the_call_promoted_it_to() {
4698 let mut opts = options();
4699 opts.emit = EmitKind::Ir;
4700 opts.std = Std::C89;
4701 let compiled = |source: &str| run(&opts, source).text().to_owned();
4702
4703 let text = compiled("f (c) unsigned char c; { return c; }\n");
4704 assert!(text.contains("func @f(i32"), "an int arrives: {text}");
4705 assert!(text.contains("trunc.i8"), "and is cut down to what was declared: {text}");
4706 assert!(text.contains("zext.i32"), "then read back unsigned: {text}");
4707
4708 let text = compiled("f (s) short s; { return s; }\n");
4710 assert!(text.contains("trunc.i16"), "cut down: {text}");
4711 assert!(text.contains("sext.i32"), "and read back signed: {text}");
4712
4713 let text = compiled("f (x) float x; { return x * 2; }\n");
4716 assert!(text.contains("func @f(f64"), "a double arrives: {text}");
4717 assert!(text.contains("fptrunc.f32"), "and is narrowed to the float: {text}");
4718
4719 let text = compiled("int f(unsigned char c) { return c; }\n");
4722 assert!(text.contains("func @f(i8)"), "the declared type arrives: {text}");
4723 assert!(!text.contains("trunc"), "so there is nothing to cut down: {text}");
4724 }
4725
4726 #[test]
4735 fn the_rules_gcc_promoted_are_decided_by_the_dialect_and_by_fpermissive() {
4736 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
4738 let cases = [
4739 ("static counted;\n", ["", "error", "warning", "error"]),
4740 ("int f(void) { return g(); }\n", ["", "error", "warning", "error"]),
4741 ("int f(x) { return x; }\n", ["", "error", "warning", "error"]),
4742 ("int *p;\nvoid h(void) { p = 1; }\n", ["warning", "error", "warning", "error"]),
4743 (
4744 "char *q;\nint *r;\nvoid k(void) { r = q; }\n",
4745 ["warning", "error", "warning", "error"],
4746 ),
4747 ("int f(void) { return; }\n", ["", "error", "warning", "error"]),
4748 ("void g(void) { return 1; }\n", ["warning", "error", "warning", "error"]),
4749 ];
4750
4751 for (source, wanted) in cases {
4752 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
4753 let mut opts = options();
4754 opts.std = std;
4755 opts.permissive = permissive;
4756 let said = run(&opts, source).messages.join("\n");
4757 let severity = if said.contains(": error: ") {
4758 "error"
4759 } else if said.contains(": warning: ") {
4760 "warning"
4761 } else {
4762 ""
4763 };
4764 let how = if permissive { " -fpermissive" } else { "" };
4765 assert_eq!(
4766 severity,
4767 wanted,
4768 "under -std={}{how}, {source} was answered with `{said}`",
4769 std.as_str()
4770 );
4771 if wanted.is_empty() {
4772 assert!(said.is_empty(), "nothing to say, but said `{said}`");
4773 }
4774 }
4775 }
4776 }
4777
4778 #[test]
4787 fn the_three_variadic_builtins_answer_a_bad_list_the_way_a_call_answers_a_bad_argument() {
4788 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
4789 let cases = [
4790 (
4791 "int f(int n, ...) { char *p; return __builtin_va_arg(p, int); }\n",
4792 "first argument to 'va_arg' not of type 'va_list'",
4793 ["error", "error", "error", "error"],
4794 ),
4795 (
4796 "void f(int n, ...) { char *p; __builtin_va_start(p, n); }\n",
4797 "passing argument 1 of '__builtin_va_start' from incompatible pointer type",
4798 ["warning", "error", "warning", "error"],
4799 ),
4800 (
4801 "void f(int n, ...) { int x; __builtin_va_end(x); }\n",
4802 "passing argument 1 of '__builtin_va_end' makes pointer from integer without a \
4803 cast",
4804 ["warning", "error", "warning", "error"],
4805 ),
4806 (
4807 "void f(int n, ...) { __builtin_va_list a; char *p; __builtin_va_copy(a, p); }\n",
4808 "passing argument 2 of '__builtin_va_copy' from incompatible pointer type",
4809 ["warning", "error", "warning", "error"],
4810 ),
4811 ];
4812
4813 for (source, message, wanted) in cases {
4814 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
4815 let mut opts = options();
4816 opts.std = std;
4817 opts.permissive = permissive;
4818 let said = run(&opts, source).messages.join("\n");
4819 let how = if permissive { " -fpermissive" } else { "" };
4820 assert!(
4821 said.contains(&format!(": {wanted}: {message}")),
4822 "under -std={}{how}, {source} was answered with `{said}`",
4823 std.as_str()
4824 );
4825 }
4826 }
4827 }
4828
4829 fn safe_ir(tier: rucc_session::Safety, source: &str) -> String {
4831 let mut opts = options();
4832 opts.emit = EmitKind::Ir;
4833 opts.safety = tier;
4834 let result = run(&opts, source);
4835 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4836 result.text().to_owned()
4837 }
4838
4839 const READS_THROUGH_A_POINTER: &str = "int read(int *p) { return p[1]; }\n";
4840
4841 fn padded_ir(padding: Padding, source: &str) -> String {
4843 let mut opts = options();
4844 opts.emit = EmitKind::Ir;
4845 opts.safety = rucc_session::Safety::Detect;
4846 opts.padding = padding;
4847 let result = run(&opts, source);
4848 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4849 result.text().to_owned()
4850 }
4851
4852 const FILLS_A_RECORD_A_MEMBER_AT_A_TIME: &str = "struct padded { char tag; int value; };\n\
4853 void fill(struct padded *p) { p->tag = 1; p->value = 2; }\n";
4854
4855 #[test]
4856 fn a_record_filled_a_member_at_a_time_comes_out_whole_when_padding_does_not_participate() {
4857 let text = padded_ir(Padding::Ignored, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
4861 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
4862 }
4863
4864 #[test]
4865 fn a_store_says_only_what_it_wrote_when_padding_does_participate() {
4866 let text = padded_ir(Padding::Tracked, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
4869 assert!(!text.contains("owns"), "{text}");
4870 }
4871
4872 #[test]
4873 fn a_member_of_a_union_owns_nothing_after_it() {
4874 let text = padded_ir(
4878 Padding::Ignored,
4879 "union u { char tag; long wide; };\nvoid fill(union u *p) { p->tag = 1; }\n",
4880 );
4881 assert!(!text.contains("owns"), "{text}");
4882 }
4883
4884 #[test]
4885 fn an_inner_records_trailing_padding_reaches_the_outer_records() {
4886 let text = padded_ir(
4891 Padding::Ignored,
4892 "struct inner { char c; };\n\
4893 struct outer { struct inner in; int x; };\n\
4894 void fill(struct outer *p) { p->in.c = 1; p->x = 2; }\n",
4895 );
4896 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
4897 }
4898
4899 #[test]
4900 fn a_build_that_did_not_ask_for_the_monitor_is_compiled_the_way_it_always_was() {
4901 let text = ir(READS_THROUGH_A_POINTER);
4905 assert!(!text.contains("check_"), "{text}");
4906 assert!(!text.contains("cap_of"), "{text}");
4907 }
4908
4909 #[test]
4910 fn asking_for_a_tier_puts_the_checks_in_before_the_optimizer_sees_them() {
4911 let text = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4912 assert!(text.contains("cap_of"), "{text}");
4913 assert!(text.contains("check_bounds"), "{text}");
4914 assert!(text.contains("check_live"), "{text}");
4915 assert!(text.contains("check_deriv"), "{text}");
4917 assert!(text.contains("check_type"), "{text}");
4919 }
4920
4921 #[test]
4922 fn the_three_tiers_that_are_not_off_all_check_the_same_accesses_so_far() {
4923 let detect = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4927 for tier in [rucc_session::Safety::Enforce, rucc_session::Safety::Kernel] {
4928 assert_eq!(safe_ir(tier, READS_THROUGH_A_POINTER), detect, "{tier}");
4929 }
4930 }
4931
4932 fn summary(tier: rucc_session::Safety, source: &str) -> String {
4934 let mut opts = options();
4935 opts.emit = EmitKind::SafetySummary;
4936 opts.safety = tier;
4937 let result = run(&opts, source);
4938 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4939 result.text().to_owned()
4940 }
4941
4942 #[test]
4943 fn the_summary_counts_the_checks_that_went_in_and_the_ones_still_standing() {
4944 let text = summary(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4945 assert!(text.contains("\"tier\": \"detect\""), "{text}");
4946 assert!(
4948 text.contains("\"bounds\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"),
4949 "{text}"
4950 );
4951 assert!(
4952 text.contains(
4953 "\"derivation\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"
4954 ),
4955 "{text}"
4956 );
4957 }
4958
4959 #[test]
4960 fn a_build_without_the_monitor_summarises_as_a_build_with_no_checks_in_it() {
4961 let text = summary(rucc_session::Safety::Off, READS_THROUGH_A_POINTER);
4965 assert!(text.contains("\"tier\": \"off\""), "{text}");
4966 assert!(
4967 text.contains("\"bounds\": { \"emitted\": 0, \"remaining\": 0, \"discharged\": 0 }"),
4968 "{text}"
4969 );
4970 }
4971
4972 #[test]
4973 fn a_call_the_boundary_models_is_counted_apart_from_one_it_does_not() {
4974 let text = summary(
4975 rucc_session::Safety::Detect,
4976 "void *memcpy(void *, const void *, unsigned long);\n\
4977 int puts(const char *);\n\
4978 void f(char *d, char *s) { memcpy(d, s, 4); puts(d); }\n",
4979 );
4980 assert!(text.contains("\"interposed\": 1"), "{text}");
4981 assert!(text.contains("\"puts\""), "{text}");
4982 assert!(!text.contains("__rucc_wrap_memcpy\""), "{text}");
4986 }
4987
4988 #[test]
4989 fn an_address_taken_of_a_library_function_is_counted_the_way_a_call_to_one_is() {
4990 let text = summary(
4995 rucc_session::Safety::Detect,
4996 "void *memcpy(void *, const void *, unsigned long);\n\
4997 int puts(const char *);\n\
4998 void *table[2] = { (void *)memcpy, (void *)puts };\n\
4999 void *f(int i) { return table[i]; }\n",
5000 );
5001 assert!(text.contains("\"interposed\": 1"), "{text}");
5002 assert!(text.contains("\"puts\""), "{text}");
5003 assert!(!text.contains("\"memcpy\""), "{text}");
5004 }
5005
5006 #[test]
5007 fn the_two_directions_a_pointer_crosses_the_boundary_are_counted_apart() {
5008 let text = summary(
5012 rucc_session::Safety::Detect,
5013 "void *notes_open(void);\n\
5014 char *f(char *p) { char *q = notes_open(); return q ? q : p; }\n",
5015 );
5016 assert!(text.contains("\"crossings\": { \"entered\": 1, \"returned\": 1 }"), "{text}");
5017 assert!(text.contains("\"notes_open\""), "{text}");
5018 }
5019
5020 #[test]
5021 fn a_static_function_nobody_takes_the_address_of_is_not_a_crossing() {
5022 let text = summary(
5025 rucc_session::Safety::Detect,
5026 "static int len(const char *p) { return p ? 1 : 0; }\n\
5027 int f(void) { return len(\"x\"); }\n",
5028 );
5029 assert!(text.contains("\"crossings\": { \"entered\": 0, \"returned\": 0 }"), "{text}");
5030 }
5031
5032 fn granules(source: &str) -> String {
5034 let mut opts = options();
5035 opts.emit = EmitKind::TypeGranules;
5036 let result = run(&opts, source);
5037 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5038 result.text().to_owned()
5039 }
5040
5041 #[test]
5042 fn the_granule_report_names_every_record_and_both_keyings() {
5043 let text = granules(
5044 "struct hot { char *p; int a; int b; };\n\
5045 int f(struct hot *h) { return h->a; }\n",
5046 );
5047 assert!(text.contains("struct hot"), "{text}");
5048 assert!(text.contains("every type distinct"), "{text}");
5051 assert!(text.contains("every pointer one type"), "{text}");
5052 assert!(text.contains("budget"), "{text}");
5053 }
5054
5055 #[test]
5056 fn a_record_nothing_uses_is_still_measured() {
5057 let text = granules("struct unused { long a; double b; };\nint f(void) { return 0; }\n");
5060 assert!(text.contains("struct unused"), "{text}");
5061 }
5062
5063 #[test]
5064 fn the_granule_report_stops_before_anything_is_lowered() {
5065 let text = granules(
5069 "struct wide { long double d; };\n\
5070 long double f(long double x) { return x * x; }\n",
5071 );
5072 assert!(text.contains("struct wide"), "{text}");
5073 }
5074
5075 #[test]
5076 fn a_witness_reaches_the_assembler_as_a_call_to_the_runtime() {
5077 let text = safe_asm(rucc_session::Safety::Detect, "char *f(char *p) { return p; }\n");
5080 assert!(text.contains("\tcall\t__rucc_cap_witness\n"), "{text}");
5081 }
5082
5083 #[test]
5084 fn a_pointer_turned_into_an_integer_is_on_the_trust_set() {
5085 let text = summary(
5086 rucc_session::Safety::Detect,
5087 "unsigned long f(int *p) { return (unsigned long) p; }\n",
5088 );
5089 assert!(text.contains("\"exposed\": 1"), "{text}");
5090 }
5091
5092 fn safe_asm(tier: rucc_session::Safety, source: &str) -> String {
5094 let mut opts = options();
5095 opts.emit = EmitKind::Asm;
5096 opts.safety = tier;
5097 let result = run(&opts, source);
5098 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5099 result.text().to_owned()
5100 }
5101
5102 #[test]
5103 fn a_check_reaches_the_assembler_as_a_call_to_the_runtime() {
5104 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5105 assert!(text.contains("\tcall\t__rucc_check_bounds\n"), "{text}");
5106 assert!(text.contains("\tcall\t__rucc_check_live\n"), "{text}");
5107 assert!(text.contains("\tcall\t__rucc_check_deriv\n"), "{text}");
5108 assert!(text.contains("\tcall\t__rucc_check_typed_init\n"), "{text}");
5111 }
5112
5113 #[test]
5114 fn every_check_that_reached_the_assembler_has_a_row_describing_it() {
5115 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5121 let section = format!("\t.section\t{},", rucc_safety::SECTION);
5122 assert_eq!(text.matches(§ion).count(), 4, "{text}");
5123 for index in 0..4 {
5124 let name = format!("__rucc_safety_desc_{index}");
5125 assert!(text.contains(&format!("{name}:\n")), "{text}");
5128 assert!(text.contains(&format!("{name}(%rip)")), "{text}");
5129 }
5130 assert!(!text.contains("__rucc_safety_desc_4"), "{text}");
5131 }
5132
5133 #[test]
5141 fn builtin_constant_p_is_folded_where_it_is_written_rather_than_called() {
5142 let text = ir(concat!(
5143 "int g;\n",
5144 "int a = __builtin_constant_p(1);\n",
5145 "int b = __builtin_constant_p(g);\n",
5146 "int c = __builtin_constant_p(\"abc\");\n",
5147 "int d = __builtin_constant_p(&g);\n",
5148 "int e = __builtin_constant_p(1.5);\n",
5149 "int h = __builtin_choose_expr(__builtin_constant_p(3), 11, 22);\n",
5150 ));
5151 assert!(text.contains("global @a : i32 = 1,"), "{text}");
5152 assert!(text.contains("global @b : i32 = 0,"), "{text}");
5153 assert!(text.contains("global @c : i32 = 1,"), "{text}");
5154 assert!(text.contains("global @d : i32 = 0,"), "{text}");
5155 assert!(text.contains("global @e : i32 = 1,"), "{text}");
5156 assert!(text.contains("global @h : i32 = 11,"), "{text}");
5157 assert!(!text.contains("__builtin_constant_p"), "it is not a call to anything:\n{text}");
5158
5159 let text = body("int f(void) { int i = 0; __builtin_constant_p(i++); return i; }\n");
5163 assert_eq!(text, "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 0\n return %0\n");
5164 }
5165
5166 #[test]
5175 fn a_call_to_a_library_builtin_reaches_the_library_function() {
5176 let text = body("void f(void) { __builtin_abort(); }\n");
5177 assert_eq!(text, "block0:\n call @abort() : ()\n return\n");
5178
5179 let text = ir("int f(const char *s) { return __builtin_puts(s) + __builtin_strlen(s); }\n");
5182 assert!(text.contains("call @puts(%0) : (ptr) -> i32"), "{text}");
5183 assert!(text.contains("call @strlen(%0) : (ptr) -> i64"), "{text}");
5184 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
5185 }
5186
5187 #[test]
5200 fn a_chk_builtin_reaches_the_checking_function_and_keeps_the_size() {
5201 let text = ir(concat!(
5202 "char d[8];\n",
5203 "void f(const char *s, unsigned long n) {\n",
5204 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
5205 " __builtin___strcpy_chk(d, s, __builtin_object_size(d, 1));\n",
5206 " __builtin___memset_chk(d, 0, n, 8);\n",
5207 "}\n",
5208 ));
5209 assert!(text.contains("call @__memcpy_chk("), "{text}");
5210 assert!(text.contains("call @__strcpy_chk("), "{text}");
5211 assert!(text.contains("call @__memset_chk("), "{text}");
5212 assert!(text.contains("iconst.i64 8"), "the object size reaches the call: {text}");
5213 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
5214 }
5215
5216 #[test]
5224 fn a_checking_call_whose_size_says_nothing_is_known_is_the_plain_library_call() {
5225 let text = ir(concat!(
5226 "extern char *p;\n",
5227 "char d[8];\n",
5228 "void f(const char *s, unsigned long n) {\n",
5229 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
5230 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
5231 " __builtin___strcpy_chk(p, s, __builtin_object_size(p, 0));\n",
5232 " __builtin___stpncpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
5233 " __builtin___sprintf_chk(p, 1, __builtin_object_size(p, 0), s);\n",
5234 "}\n",
5235 ));
5236
5237 assert!(
5239 text.contains("call @__memcpy_chk(%2, %0, %1, %3) : (ptr, ptr, i64, i64)"),
5240 "{text}"
5241 );
5242
5243 assert!(text.contains("call @memcpy(%6, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
5246 assert!(text.contains("call @strcpy(%10, %0) : (ptr, ptr) -> ptr"), "{text}");
5247 assert!(text.contains("call @stpncpy(%14, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
5248
5249 assert!(text.contains("call @__sprintf_chk("), "{text}");
5252
5253 let asm = asm(concat!(
5256 "void f(char *p, const char *s, unsigned long n) {\n",
5257 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
5258 "}\n",
5259 ));
5260 assert!(asm.contains("call\tmemcpy"), "{asm}");
5261 assert!(!asm.contains("$-1"), "the size that went away leaves no instruction:\n{asm}");
5262 }
5263
5264 #[test]
5272 fn the_v_spellings_of_the_chk_family_take_the_list_a_va_list_parameter_holds() {
5273 let text = ir(concat!(
5274 "char d[64];\n",
5275 "int f(const char *fmt, ...) {\n",
5276 " __builtin_va_list ap;\n",
5277 " __builtin_va_start(ap, fmt);\n",
5278 " int n = __builtin___vsprintf_chk(d, 1, __builtin_object_size(d, 0), fmt, ap);\n",
5279 " __builtin_va_end(ap);\n",
5280 " return n;\n",
5281 "}\n",
5282 ));
5283 assert!(text.contains("call @__vsprintf_chk("), "{text}");
5284 assert!(text.contains("iconst.i64 64"), "the object size reaches the call: {text}");
5285 }
5286
5287 #[test]
5298 fn the_absolute_value_family_is_the_magnitude_and_not_a_call() {
5299 let text = body(concat!(
5300 "long long llabs(long long);\n",
5301 "long long f(long long x) { return llabs(x); }\n",
5302 ));
5303 assert!(text.contains("%1 = iconst.i64 63"), "{text}");
5304 assert!(text.contains("%2 = ashr %0, %1"), "{text}");
5305 assert!(text.contains("%3 = xor %0, %2"), "{text}");
5306 assert!(text.contains("%4 = sub %3, %2"), "{text}");
5307 assert!(!text.contains("call"), "the call does not happen:\n{text}");
5308
5309 let text = body("int abs(int);\nint f(int x) { return abs(x); }\n");
5312 assert!(text.contains("iconst.i32 31"), "{text}");
5313 let text = body("long labs(long);\nlong f(long x) { return labs(x); }\n");
5314 assert!(text.contains("iconst.i64 63"), "{text}");
5315
5316 let text = body("long long f(long long x) { return __builtin_llabs(x); }\n");
5319 assert!(!text.contains("call"), "{text}");
5320
5321 let text = ir(concat!(
5323 "long long llabs(long long b);\n",
5324 "long long g(long long x) { return llabs(x); }\n",
5325 "long long llabs(long long b) { return 7; }\n",
5326 ));
5327 assert!(!text.contains("call @llabs"), "{text}");
5328 }
5329
5330 #[test]
5337 fn a_byte_swap_is_arithmetic_and_not_a_call() {
5338 let text = body("unsigned f(unsigned x) { return __builtin_bswap32(x); }\n");
5339 assert_eq!(text, "block0(%0: i32):\n %1 = bswap %0\n return %1\n");
5340
5341 let text = body("unsigned f(unsigned char c) { return __builtin_bswap32(c); }\n");
5344 assert!(text.contains("zext.i32 %0"), "widened first: {text}");
5345 assert!(text.contains("bswap %1"), "and swapped at four bytes: {text}");
5346 }
5347
5348 #[test]
5354 fn the_byte_swaps_reverse_at_the_width_their_name_says() {
5355 for (name, ty, width) in [
5356 ("__builtin_bswap16", "unsigned short", "i16"),
5357 ("__builtin_bswap32", "unsigned", "i32"),
5358 ("__builtin_bswap64", "unsigned long long", "i64"),
5359 ] {
5360 let source = format!("{ty} f({ty} x) {{ return {name}(x); }}\n");
5361 let text = body(&source);
5362 assert_eq!(
5363 text,
5364 format!("block0(%0: {width}):\n %1 = bswap %0\n return %1\n"),
5365 "{name}"
5366 );
5367 }
5368 }
5369
5370 #[test]
5377 fn the_bit_counts_are_instructions_and_not_calls() {
5378 let text = body("int f(unsigned x) { return __builtin_clz(x); }\n");
5379 assert_eq!(text, "block0(%0: i32):\n %1 = ctlz %0\n return %1\n");
5380
5381 let text = body("int f(unsigned x) { return __builtin_ctz(x); }\n");
5382 assert_eq!(text, "block0(%0: i32):\n %1 = cttz %0\n return %1\n");
5383
5384 let text = body("int f(unsigned x) { return __builtin_popcount(x); }\n");
5385 assert_eq!(text, "block0(%0: i32):\n %1 = ctpop %0\n return %1\n");
5386 }
5387
5388 #[test]
5397 fn the_bit_counts_ask_about_the_width_their_name_says() {
5398 let text = body("int f(unsigned long long x) { return __builtin_clzll(x); }\n");
5399 assert!(text.starts_with("block0(%0: i64):"), "counted at eight bytes: {text}");
5400 assert!(text.contains("%1 = ctlz %0"), "{text}");
5401 assert!(text.contains("trunc.i32 %1"), "and answered in an int: {text}");
5402
5403 let text = body("int f(unsigned long long x) { return __builtin_clz(x); }\n");
5406 assert!(text.contains("trunc.i32 %0"), "narrowed to what was asked about: {text}");
5407 assert!(text.contains("ctlz %1"), "and counted there: {text}");
5408
5409 let text = body("int f(unsigned long x) { return __builtin_popcountl(x); }\n");
5410 assert!(text.contains("%1 = ctpop %0"), "{text}");
5411 assert!(!text.contains("call"), "{text}");
5412 }
5413
5414 #[test]
5419 fn a_parity_is_the_low_bit_of_the_set_bit_count() {
5420 let text = body("int f(unsigned x) { return __builtin_parity(x); }\n");
5421 assert!(text.contains("%1 = ctpop %0"), "{text}");
5422 assert!(text.contains("iconst.i32 1"), "{text}");
5423 assert!(text.contains("and %1, %2"), "the low bit of it: {text}");
5424 }
5425
5426 #[test]
5432 fn the_first_set_bit_is_one_based_and_zero_for_a_zero() {
5433 let text = body("int f(int x) { return __builtin_ffs(x); }\n");
5434 assert!(text.contains("%1 = cttz %0"), "{text}");
5435 assert!(text.contains("%4 = add %1, %2"), "one more than the count: {text}");
5436 assert!(text.contains("%5 = icmp ne %0, %3"), "whether there was a bit at all: {text}");
5437 assert!(text.contains("%7 = sub %3, %6"), "spread to a mask: {text}");
5438 assert!(text.contains("%8 = and %4, %7"), "and kept only then: {text}");
5439 assert!(!text.contains("br_if"), "no branch: {text}");
5440 }
5441
5442 #[test]
5452 fn the_redundant_sign_bit_count_is_instructions_and_not_a_call() {
5453 let text = body("int f(int x) { return __builtin_clrsb(x); }\n");
5454 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
5455 assert!(text.contains("%2 = ashr %0, %1"), "the sign over every bit: {text}");
5456 assert!(text.contains("%3 = xor %0, %2"), "folded onto it: {text}");
5457 assert!(text.contains("%5 = shl %3, %4"), "one less than the count: {text}");
5458 assert!(text.contains("%6 = or %5, %4"), "with something to count at zero: {text}");
5459 assert!(text.contains("%7 = ctlz %6"), "{text}");
5460 assert!(!text.contains("call"), "{text}");
5461 assert!(!text.contains("br_if"), "no branch: {text}");
5462 }
5463
5464 #[test]
5470 fn the_unsigned_absolute_value_family_answers_in_the_unsigned_type() {
5471 let text = body("unsigned f(int x) { return __builtin_uabs(x); }\n");
5472 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
5473 assert!(text.contains("%4 = sub %3, %2"), "{text}");
5474 assert!(!text.contains("call"), "nothing declares uabs, so a call would not link: {text}");
5475
5476 let text = body("unsigned long long f(long long x) { return __builtin_ullabs(x); }\n");
5477 assert!(text.contains("iconst.i64 63"), "at the width the name says: {text}");
5478
5479 let text = body("int f(int x) { return __builtin_uabs(x) > 2147483647u; }\n");
5482 assert!(text.contains("icmp ugt"), "compared unsigned: {text}");
5483 }
5484
5485 #[test]
5493 fn the_widest_absolute_value_is_whichever_type_the_target_makes_intmax_t() {
5494 let text = body("long f(long x) { return __builtin_imaxabs(x); }\n");
5495 assert!(text.contains("iconst.i64 63"), "{text}");
5496 assert!(text.contains("%4 = sub %3, %2"), "{text}");
5497 assert!(!text.contains("call"), "{text}");
5498
5499 let text = body("unsigned long f(long x) { return __builtin_umaxabs(x); }\n");
5500 assert!(text.contains("iconst.i64 63"), "{text}");
5501 assert!(!text.contains("call"), "{text}");
5502 }
5503
5504 #[test]
5512 fn an_overflow_predicate_writes_nothing_and_answers_the_bit_the_check_would() {
5513 let text =
5514 body("int f(int a, int b) { return __builtin_add_overflow_p(a, b, (int) 0); }\n");
5515 assert!(text.contains("%2, %3 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
5516 assert!(!text.contains("store"), "nothing is written: {text}");
5517 assert!(!text.contains("call"), "{text}");
5518
5519 let text =
5522 body("int f(int a, int b) { return __builtin_mul_overflow_p(a, b, (long long) 0); }\n");
5523 assert!(text.contains("smul_overflow.(i64, i1)"), "{text}");
5524 assert!(!text.contains("store"), "{text}");
5525
5526 let text = body(concat!(
5529 "int g(void);\n",
5530 "int f(int a, int b) { return __builtin_sub_overflow_p(a, b, g()); }\n",
5531 ));
5532 assert!(!text.contains("call @g"), "the third argument is not evaluated: {text}");
5533 }
5534
5535 #[test]
5545 fn an_overflow_check_is_arithmetic_and_not_a_call() {
5546 let text =
5547 body("int f(int a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
5548 assert!(text.contains("%3, %4 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
5549 assert!(text.contains("store %3 -> %2"), "{text}");
5550 assert!(!text.contains("call"), "{text}");
5551
5552 let text =
5553 body("int f(int a, int b, int *r) { return __builtin_sub_overflow(a, b, r); }\n");
5554 assert!(text.contains("ssub_overflow.(i32, i1) %0, %1"), "{text}");
5555
5556 let text =
5557 body("int f(int a, int b, int *r) { return __builtin_mul_overflow(a, b, r); }\n");
5558 assert!(text.contains("smul_overflow.(i32, i1) %0, %1"), "{text}");
5559
5560 let text = body(
5563 "int f(unsigned a, unsigned b, unsigned *r) { return __builtin_add_overflow(a, b, r); }\n",
5564 );
5565 assert!(text.contains("uadd_overflow.(i32, i1) %0, %1"), "{text}");
5566 }
5567
5568 #[test]
5576 fn an_overflow_check_is_done_at_a_type_that_holds_every_operand() {
5577 let text = body(
5578 "int f(unsigned a, int b, long long *r) { return __builtin_add_overflow(a, b, r); }\n",
5579 );
5580 assert!(text.contains("%3 = zext.i64 %0"), "the unsigned operand keeps its value: {text}");
5581 assert!(text.contains("%4 = sext.i64 %1"), "and so does the signed one: {text}");
5582 assert!(text.contains("sadd_overflow.(i64, i1) %3, %4"), "{text}");
5583
5584 let text = body(
5587 "int f(long long a, long long b, long long *r) { return __builtin_mul_overflow(a, b, r); }\n",
5588 );
5589 assert!(text.contains("smul_overflow.(i64, i1) %0, %1"), "{text}");
5590 assert!(!text.contains("sext."), "{text}");
5591 assert!(!text.contains("zext.i64"), "{text}");
5593 }
5594
5595 #[test]
5603 fn an_overflow_check_writes_the_wrapped_answer_whether_or_not_it_fit() {
5604 let text =
5605 body("int f(int a, int b, char *r) { return __builtin_sub_overflow(a, b, r); }\n");
5606 assert!(text.contains("%3, %4 = ssub_overflow.(i32, i1) %0, %1"), "{text}");
5607 assert!(text.contains("%5 = trunc.i8 %3"), "narrowed to where it goes: {text}");
5608 assert!(text.contains("%6 = sext.i32 %5"), "and back: {text}");
5609 assert!(text.contains("%7 = icmp ne %6, %3"), "which is whether it fit: {text}");
5610 assert!(text.contains("store %5 -> %2"), "the narrowed value is stored either way: {text}");
5611 assert!(text.contains("%8 = or %4, %7"), "and either bit is an overflow: {text}");
5612 }
5613
5614 #[test]
5621 fn a_call_needing_more_than_the_widest_type_still_compiles() {
5622 for name in ["add", "sub", "mul"] {
5623 let source = format!(
5624 "int f(unsigned __int128 a, long long b, __int128 *r) {{\n \
5625 return __builtin_{name}_overflow(a, b, r);\n}}\n"
5626 );
5627 let mut opts = options();
5628 opts.emit = EmitKind::MirFinal;
5629 assert!(!run(&opts, &source).failed(), "{name} was refused or stopped the back end");
5630 }
5631 }
5632
5633 #[test]
5636 fn an_overflow_check_over_something_that_is_not_an_integer_says_so() {
5637 let messages =
5638 errors("int f(double a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
5639 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
5640
5641 let messages =
5642 errors("int f(int a, int b, double *r) { return __builtin_add_overflow(a, b, r); }\n");
5643 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
5644 }
5645
5646 #[test]
5657 fn an_ordered_access_is_ordered_in_the_ir() {
5658 let text = body("int f(int *p) { return __atomic_load_n(p, 0); }\n");
5659 assert!(text.contains("atomic_load.i32 %0, align 4, relaxed"), "{text}");
5660
5661 let text = body("long f(long *p) { return __atomic_load_n(p, 2); }\n");
5662 assert!(text.contains("atomic_load.i64 %0, align 8, acquire"), "{text}");
5663
5664 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
5665 assert!(text.contains("atomic_store %1 -> %0, align 4, release"), "{text}");
5666
5667 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
5668 assert!(text.contains("atomic_store %1 -> %0, align 4, seq_cst"), "{text}");
5669
5670 let text = body("void f(char *p, int v) { __atomic_store_n(p, v, 0); }\n");
5673 assert!(text.contains("trunc.i8 %1"), "{text}");
5674 assert!(text.contains("atomic_store %2 -> %0, align 1, relaxed"), "{text}");
5675 }
5676
5677 #[test]
5686 fn an_ordered_access_is_the_plain_instruction_on_this_machine() {
5687 let text = asm("int f(int *p) { return __atomic_load_n(p, 5); }\n");
5688 assert!(text.contains("movl\t(%rdi), %eax"), "{text}");
5689 assert!(!text.contains("mfence"), "a load needs no barrier here: {text}");
5690
5691 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
5692 assert!(text.contains("movl\t%esi, (%rdi)"), "{text}");
5693 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
5694
5695 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
5696 let (before, after) = text.split_once("mfence").expect("a barrier: {text}");
5697 assert!(before.contains("movl\t%esi, (%rdi)"), "the store comes first: {text}");
5698 assert!(!after.contains("movl"), "and nothing else is between them: {text}");
5699 }
5700
5701 #[test]
5711 fn a_barrier_is_one_instruction_at_the_strongest_ordering_and_none_below_it() {
5712 assert!(asm("void f(void) { __atomic_thread_fence(5); }\n").contains("mfence"));
5713 assert!(asm("void f(void) { __sync_synchronize(); }\n").contains("mfence"));
5714
5715 for weaker in ["1", "2", "3", "4"] {
5716 let source = format!("void f(void) {{ __atomic_thread_fence({weaker}); }}\n");
5717 assert!(!asm(&source).contains("mfence"), "{weaker} costs nothing here");
5718 }
5719 }
5720
5721 #[test]
5731 fn the_three_x86_fences_are_the_barrier_the_strongest_ordering_gives() {
5732 for name in ["__builtin_ia32_sfence", "__builtin_ia32_lfence", "__builtin_ia32_mfence"] {
5733 let source = format!("void f(void) {{ {name}(); }}\n");
5734 assert!(asm(&source).contains("mfence"), "{name} is a barrier");
5735 let text = body(&source);
5736 assert!(text.contains("fence seq_cst"), "{name}: {text}");
5737 }
5738
5739 let result = run(&options(), "void f(void) { __builtin_ia32_sfence(1); }\n");
5740 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
5741 assert!(result.messages[0].contains("too many arguments"), "{:?}", result.messages);
5742 }
5743
5744 #[test]
5750 fn a_compare_and_exchange_is_one_instruction_answering_two_things() {
5751 let text =
5754 body("int f(int *p, int e, int d) { return __sync_val_compare_and_swap(p, e, d); }\n");
5755 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
5756 assert!(text.contains("return %3"), "the value it found: {text}");
5757
5758 let text =
5759 body("int f(int *p, int e, int d) { return __sync_bool_compare_and_swap(p, e, d); }\n");
5760 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
5761 assert!(text.contains("zext.i32 %4"), "whether it happened: {text}");
5762
5763 let text = body(
5766 "int f(int *p, int *e, int d) { return __atomic_compare_exchange_n(p, e, d, 0, 4, 2); }\n",
5767 );
5768 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
5769 assert!(text.contains("%4, %5 = cmpxchg.(i32, i1) %0, %3, %2, align 4, acq_rel"), "{text}");
5770 assert!(text.contains("br_if %5, block2, block1"), "{text}");
5771 assert!(text.contains("store %4 -> %1, align 4"), "{text}");
5772
5773 let text = body(
5776 "int f(int *p, int *e, int *d) { return __atomic_compare_exchange(p, e, d, 0, 5, 5); }\n",
5777 );
5778 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
5779 assert!(text.contains("%4 = load.i32 %2, align 4"), "{text}");
5780 assert!(text.contains("%5, %6 = cmpxchg.(i32, i1) %0, %3, %4, align 4, seq_cst"), "{text}");
5781 }
5782
5783 #[test]
5790 fn a_compare_and_exchange_is_a_locked_instruction_at_the_width_of_the_object() {
5791 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
5792 for (ty, suffix, reg) in widths {
5793 let source = format!(
5794 "int f({ty} *p, {ty} e, {ty} d) {{ return __sync_bool_compare_and_swap(p, e, d); }}\n"
5795 );
5796 let text = asm(&source);
5797 assert!(text.contains("\tlock\n"), "{ty}: {text}");
5798 assert!(text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
5799 assert!(text.contains("sete\t"), "{ty}: {text}");
5800 }
5801 let source =
5802 "int f(long *p, long e, long d) { return __sync_bool_compare_and_swap(p, e, d); }\n";
5803 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
5804
5805 for order in ["0", "2", "3", "4", "5"] {
5809 let call = format!("__atomic_compare_exchange_n(p, e, d, 0, {order}, 0)");
5810 let source = format!("int f(int *p, int *e, int d) {{ return {call}; }}\n");
5811 let text = asm(&source);
5812 assert!(text.contains("cmpxchgl\t"), "{order}: {text}");
5813 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
5814 }
5815 }
5816
5817 #[test]
5829 fn a_read_modify_write_is_one_instruction_and_the_arithmetic_a_name_asks_for() {
5830 let text = body("int f(int *p, int v) { return __atomic_fetch_add(p, v, 5); }\n");
5831 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
5832 assert!(text.contains("return %2"), "the value that was there: {text}");
5833
5834 let text = body("int f(int *p, int v) { return __atomic_add_fetch(p, v, 5); }\n");
5835 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
5836 assert!(text.contains("%3 = add %2, %1"), "and the value afterwards: {text}");
5837
5838 let text = body("int f(int *p, int v) { return __atomic_sub_fetch(p, v, 5); }\n");
5839 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
5840 assert!(text.contains("%3 = sub %2, %1"), "{text}");
5841
5842 let text = body("int f(int *p, int v) { return __sync_fetch_and_sub(p, v); }\n");
5844 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
5845
5846 let text = body("int f(int *p, int v) { return __atomic_exchange_n(p, v, 5); }\n");
5849 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, seq_cst"), "{text}");
5850
5851 let text = body("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
5852 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, acquire"), "{text}");
5853
5854 let text = body("void f(int *p) { __sync_lock_release(p); }\n");
5857 assert!(text.contains("release"), "{text}");
5858 assert!(text.contains("%1 = iconst.i32 0"), "{text}");
5859
5860 let text = body("void f(int *p, int guard) { __sync_lock_release(p, guard); }\n");
5864 assert!(text.contains("%2 = iconst.i32 0"), "{text}");
5865 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
5866
5867 let text = body("int f(int *p, int v) { return __atomic_fetch_and(p, v, 5); }\n");
5870 assert!(text.contains("%2 = atomic_rmw.i32 and %0, %1, align 4, seq_cst"), "{text}");
5871
5872 let text = body("int f(int *p, int v) { return __sync_or_and_fetch(p, v); }\n");
5873 assert!(text.contains("%2 = atomic_rmw.i32 or %0, %1, align 4, seq_cst"), "{text}");
5874 assert!(text.contains("%3 = or %2, %1"), "and the value afterwards: {text}");
5875
5876 let text = body("int f(int *p, int v) { return __atomic_nand_fetch(p, v, 5); }\n");
5879 assert!(text.contains("%2 = atomic_rmw.i32 nand %0, %1, align 4, seq_cst"), "{text}");
5880 assert!(text.contains("%3 = and %2, %1"), "{text}");
5881 assert!(text.contains("%4 = iconst.i32 -1"), "{text}");
5882 assert!(text.contains("%5 = xor %3, %4"), "{text}");
5883 }
5884
5885 #[test]
5896 fn a_bitwise_read_modify_write_is_a_loop_around_the_compare_and_exchange() {
5897 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
5898 for (ty, suffix, reg) in widths {
5899 for (name, call, insn) in [
5900 ("and", "__atomic_fetch_and(p, v, 5)", "and"),
5901 ("or", "__sync_fetch_and_or(p, v)", "or"),
5902 ("xor", "__atomic_xor_fetch(p, v, 5)", "xor"),
5903 ] {
5904 let source = format!("{ty} f({ty} *p, {ty} v) {{ return {call}; }}\n");
5905 let text = asm(&source);
5906 assert!(text.contains("\tlock\n"), "{ty} {name}: {text}");
5907 assert!(
5908 text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")),
5909 "{ty} {name}: {text}"
5910 );
5911 assert!(text.contains(&format!("{insn}{suffix}\t")), "{ty} {name}: {text}");
5912 assert!(!text.contains("\txadd"), "{ty} {name} is not an add: {text}");
5914 assert!(!text.contains("\txchg"), "{ty} {name} is not an exchange: {text}");
5915 }
5916 }
5917 let source = "long f(long *p, long v) { return __atomic_fetch_or(p, v, 5); }\n";
5918 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
5919
5920 let text = asm("int f(int *p, int v) { return __sync_fetch_and_nand(p, v); }\n");
5924 assert!(text.contains("cmpxchgl\t"), "{text}");
5925 assert!(text.contains("andl\t"), "{text}");
5926 assert!(text.contains("notl\t"), "{text}");
5927 }
5928
5929 #[test]
5938 fn an_access_through_a_second_pointer_is_the_same_access_and_one_more() {
5939 let text = body("void f(int *p, int *r) { __atomic_load(p, r, 5); }\n");
5940 assert!(text.contains("%2 = atomic_load.i32 %0, align 4, seq_cst"), "{text}");
5941 assert!(text.contains("store %2 -> %1, align 4"), "and out through the place: {text}");
5942
5943 let text = body("void f(int *p, int *v) { __atomic_store(p, v, 3); }\n");
5944 assert!(text.contains("%2 = load.i32 %1, align 4"), "in through the place: {text}");
5945 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
5946
5947 let text = body("void f(int *p, int *v, int *r) { __atomic_exchange(p, v, r, 5); }\n");
5950 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
5951 assert!(text.contains("%4 = atomic_rmw.i32 xchg %0, %3, align 4, seq_cst"), "{text}");
5952 assert!(text.contains("store %4 -> %2, align 4"), "{text}");
5953 }
5954
5955 #[test]
5966 fn a_flag_is_an_exchange_of_one_byte_and_a_store_of_a_zero_over_the_same_byte() {
5967 for pointer in ["char", "int", "void"] {
5968 let source = format!("int f({pointer} *p) {{ return __atomic_test_and_set(p, 5); }}\n");
5969 let text = body(&source);
5970 assert!(text.contains("%1 = iconst.i8 1"), "{pointer}: {text}");
5971 assert!(
5972 text.contains("%2 = atomic_rmw.i8 xchg %0, %1, align 1, seq_cst"),
5973 "{pointer}: {text}"
5974 );
5975 assert!(text.contains("%4 = icmp ne %2, %3"), "{pointer}: {text}");
5976
5977 let source = format!("void f({pointer} *p) {{ __atomic_clear(p, 3); }}\n");
5978 let text = body(&source);
5979 assert!(text.contains("atomic_store %2 -> %0, align 1, release"), "{pointer}: {text}");
5980 }
5981
5982 let text = asm("int f(int *p) { return __atomic_test_and_set(p, 5); }\n");
5985 assert!(text.contains("xchgb\t%al, (%rdi)"), "{text}");
5986 assert!(text.contains("setne\t"), "{text}");
5987 }
5988
5989 #[test]
5997 fn a_read_modify_write_is_an_exchange_or_a_locked_add_at_the_width_of_the_object() {
5998 let widths = [("char", "b", "%sil"), ("short", "w", "%si"), ("int", "l", "%esi")];
5999 for (ty, suffix, reg) in widths {
6000 let source =
6001 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_fetch_add(p, v, 5); }}\n");
6002 let text = asm(&source);
6003 assert!(text.contains("\tlock\n"), "{ty}: {text}");
6004 assert!(text.contains(&format!("xadd{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
6005
6006 let source =
6007 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_exchange_n(p, v, 5); }}\n");
6008 let text = asm(&source);
6009 assert!(text.contains(&format!("xchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
6010 assert!(!text.contains("\tlock\n"), "an exchange is locked already: {ty}: {text}");
6011 }
6012 let source = "long f(long *p, long v) { return __atomic_fetch_add(p, v, 5); }\n";
6013 assert!(asm(source).contains("xaddq\t%rsi, (%rdi)"), "{}", asm(source));
6014
6015 let source = "int f(int *p, int v) { return __atomic_fetch_sub(p, v, 5); }\n";
6018 let text = asm(source);
6019 assert!(text.contains("negl\t"), "{text}");
6020 assert!(text.contains("xaddl\t"), "{text}");
6021
6022 for order in ["0", "2", "3", "4", "5"] {
6025 let source =
6026 format!("int f(int *p, int v) {{ return __atomic_fetch_add(p, v, {order}); }}\n");
6027 let text = asm(&source);
6028 assert!(text.contains("xaddl\t"), "{order}: {text}");
6029 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
6030 }
6031
6032 let text = asm("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
6036 assert!(text.contains("xchgl\t%esi, (%rdi)"), "{text}");
6037 let text = asm("void f(int *p) { __sync_lock_release(p); }\n");
6044 assert!(text.contains("xorl\t%eax, %eax"), "{text}");
6045 assert!(text.contains("movl\t%eax, (%rdi)"), "{text}");
6046 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
6047 }
6048
6049 #[test]
6061 fn the_lock_free_questions_are_answered_as_constants() {
6062 for size in ["1", "2", "4", "8"] {
6063 let source =
6064 format!("int f(void) {{ return __atomic_always_lock_free({size}, 0); }}\n");
6065 let text = asm(&source);
6066 assert!(text.contains("movb\t$1, %al"), "{size} bytes is lock free: {text}");
6067 assert!(!text.contains("call"), "and is not a call: {text}");
6068 }
6069 for size in ["3", "16", "sizeof(long double)"] {
6070 let source = format!("int f(void) {{ return __atomic_is_lock_free({size}, 0); }}\n");
6071 let text = asm(&source);
6072 assert!(text.contains("movb\t$0, %al"), "{size} bytes is not: {text}");
6073 assert!(!text.contains("call"), "and is not a call either: {text}");
6074 }
6075
6076 let text = asm("int f(int n) { return __atomic_is_lock_free(n, 0); }\n");
6080 assert!(text.contains("movb\t$0, %al"), "a size nobody knows is not lock free: {text}");
6081 let text = asm("int f(int *p) { return __atomic_always_lock_free(8, p); }\n");
6082 assert!(text.contains("movb\t$0, %al"), "eight bytes at four is not: {text}");
6083 let text = asm("int f(long *p) { return __atomic_always_lock_free(8, p); }\n");
6084 assert!(text.contains("movb\t$1, %al"), "and at eight it is: {text}");
6085 }
6086
6087 #[test]
6099 fn a_memory_order_an_operation_cannot_carry_is_read_as_the_strongest() {
6100 let mut opts = options();
6101 opts.emit = EmitKind::Ir;
6102
6103 let acquire_store = run(&opts, "void f(int *p, int v) { __atomic_store_n(p, v, 2); }\n");
6104 assert!(acquire_store.text().contains("seq_cst"), "{:?}", acquire_store.text());
6105 assert!(acquire_store.messages[0].contains("[W0333]"), "{:?}", acquire_store.messages);
6106
6107 let nonsense = run(&opts, "int f(int *p) { return __atomic_load_n(p, 99); }\n");
6108 assert!(nonsense.text().contains("seq_cst"), "{:?}", nonsense.text());
6109 assert!(nonsense.messages[0].contains("[W0333]"), "{:?}", nonsense.messages);
6110
6111 let computed = run(&opts, "int f(int *p, int n) { return __atomic_load_n(p, n); }\n");
6112 assert!(computed.text().contains("seq_cst"), "{:?}", computed.text());
6113 assert_eq!(computed.messages, Vec::<String>::new(), "a computed order is not a mistake");
6114 }
6115
6116 #[test]
6128 fn a_conversion_between_a_float_and_the_widest_unsigned_integer_is_written_without_a_branch() {
6129 let text = asm("double f(unsigned long long x) { return (double)x; }\n");
6130 assert!(text.contains("cvtsi2sdq"), "the signed conversion is what runs: {text}");
6131 assert!(text.contains("shrq"), "with the value halved first: {text}");
6132 assert!(text.contains("addsd"), "and doubled after: {text}");
6133 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
6134
6135 let text = asm("unsigned long long f(double d) { return (unsigned long long)d; }\n");
6136 assert!(text.contains("cvttsd2siq"), "the signed conversion is what runs: {text}");
6137 assert!(text.contains("subsd"), "with half the range taken off first: {text}");
6138 assert!(text.contains("shlq\t$63"), "and the top bit put back: {text}");
6139 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
6140 }
6141
6142 #[test]
6153 fn a_plain_name_the_program_took_is_the_programs_own_function() {
6154 let taken = concat!(
6155 "static long long llabs(long long b) { return 7; }\n",
6156 "long long f(long long x) { return llabs(x); }\n",
6157 );
6158 assert!(ir(taken).contains("call @llabs"), "a static definition is the program's own");
6159
6160 let retyped = concat!("int llabs(int b);\n", "int f(int x) { return llabs(x); }\n",);
6161 assert!(ir(retyped).contains("call @llabs"), "another type is another function");
6162
6163 let plain = concat!(
6164 "long long llabs(long long b);\n",
6165 "long long f(long long x) { return llabs(x); }\n",
6166 );
6167 let mut opts = options();
6168 opts.emit = EmitKind::Ir;
6169 assert!(!run(&opts, plain).text().contains("call @llabs"), "the library's by default");
6170
6171 opts.builtins = false;
6172 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin");
6173
6174 opts.builtins = true;
6175 opts.no_builtin = vec!["llabs".to_owned()];
6176 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin-llabs");
6177 let one = "long labs(long b);\nlong f(long x) { return labs(x); }\n";
6178 assert!(!run(&opts, one).text().contains("call @labs"), "one name and not the family");
6179
6180 opts.no_builtin = Vec::new();
6183 opts.builtins = false;
6184 let prefixed = "long long f(long long x) { return __builtin_llabs(x); }\n";
6185 assert!(!run(&opts, prefixed).text().contains("call @llabs"), "the prefix is a promise");
6186 }
6187
6188 #[test]
6201 fn the_hint_builtins_are_their_first_argument_and_the_hint_leaves_no_trace() {
6202 let text = ir(concat!(
6203 "long a = __builtin_expect(7, 1);\n",
6204 "long b = __builtin_expect_with_probability(9, 1, 0.9);\n",
6205 "unsigned long c = sizeof(__builtin_expect((char)1, 1));\n",
6206 ));
6207 assert!(text.contains("global @a : i64 = 7,"), "{text}");
6208 assert!(text.contains("global @b : i64 = 9,"), "{text}");
6209 assert!(text.contains("global @c : i64 = 8,"), "{text}");
6210 assert!(!text.contains("__builtin_expect"), "it is not a call to anything:\n{text}");
6211
6212 let text = body("long f(char c) { return __builtin_expect(c, 1); }\n");
6215 assert!(text.contains("sext"), "{text}");
6216
6217 let one = "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 1\n %2 = sext.i64 %1\n return %0\n";
6221 assert_eq!(body("int f(void) { int i = 0; __builtin_expect(1, i++); return i; }\n"), one);
6222 let source = "int g(void) { int i = 0; __builtin_expect_with_probability(1, i++, 0.5); return i; }\n";
6223 assert_eq!(body(source), one);
6224
6225 let kept = body("int f(int n) { int i = 0; __builtin_expect(n, i++); return i; }\n");
6230 assert!(kept.contains("add.nsw"), "the hint still runs: {kept}");
6231 assert!(kept.ends_with("return %3\n"), "and the answer is what it left behind: {kept}");
6232 let both = "int g(int n) { int i = 0; __builtin_expect_with_probability(n, i++, 0.5); return i; }\n";
6233 assert!(body(both).contains("add.nsw"), "and so does the one with three arguments");
6234 }
6235
6236 #[test]
6248 fn a_promise_that_control_does_not_arrive_writes_no_instruction() {
6249 let promised = "int f(int x) { if (x) return 1; __builtin_unreachable(); }\n";
6250 let text = ir(promised);
6251 assert!(text.contains(" unreachable_hint\n"), "{text}");
6252 assert!(!text.contains("call"), "it is not a call to anything:\n{text}");
6253
6254 let after = body("int g(int x) { __builtin_unreachable(); return x; }\n");
6258 assert!(after.contains("return"), "{after}");
6259
6260 let text = asm(promised);
6263 let mine = text.split_once("\nf:\n").expect("a definition").1;
6264 let mine = mine.split_once("\t.size").expect("a definition").0;
6265 let plain = asm("int f(int x) { if (x) return 1; }\n");
6266 let plain = plain.split_once("\nf:\n").expect("a definition").1;
6267 let plain = plain.split_once("\t.size").expect("a definition").0;
6268 assert_eq!(mine, plain);
6269 let last = mine.lines().rfind(|line| !line.trim_start().starts_with('.'));
6272 assert_eq!(last.map(str::trim), Some("ret"), "{mine}");
6273 assert!(!mine.contains("ud2"), "{mine}");
6274 }
6275
6276 #[test]
6283 fn a_library_builtin_is_diagnosed_under_the_name_the_program_wrote() {
6284 let mut opts = options();
6285 opts.emit = EmitKind::Ir;
6286 let messages = run(&opts, "void f(void) { __builtin_abort(1); }\n").messages;
6287 assert!(
6288 messages.iter().any(|m| m.contains("__builtin_abort")),
6289 "expected the written name in {messages:?}"
6290 );
6291 }
6292
6293 #[test]
6301 fn a_builtin_nothing_lowers_is_refused_by_name() {
6302 let mut opts = options();
6303 opts.emit = EmitKind::Ir;
6304 let builtin = "__atomic_signal_fence";
6305 let source = format!("int counter;\nint f(void) {{ return ({builtin}(5), 0); }}\n");
6306 let messages = run(&opts, &source).messages;
6307 let named = messages.iter().any(|m| m.contains(builtin) && m.contains("E0686"));
6308 assert!(named, "expected {builtin} to be refused by name in {messages:?}");
6309 }
6310
6311 #[test]
6320 fn what_is_refused_is_the_call_and_not_the_name() {
6321 let text = ir(concat!(
6322 "void __atomic_signal_fence(int order) { (void)order; }\n",
6323 "void f(void) { __atomic_signal_fence(5); }\n",
6324 ));
6325 assert!(text.contains("call @__atomic_signal_fence"), "{text}");
6326 }
6327
6328 #[test]
6337 fn the_object_size_of_an_address_is_what_the_layout_leaves_in_front_of_it() {
6338 let text = ir(concat!(
6339 "struct S { char a[8]; int n; char b[12]; };\n",
6340 "char g[32];\n",
6341 "struct S gs;\n",
6342 "unsigned long whole = __builtin_object_size(g, 0);\n",
6343 "unsigned long moved = __builtin_object_size(g + 4, 0);\n",
6344 "unsigned long back = __builtin_object_size(g + 30 - 2, 0);\n",
6345 "unsigned long outer = __builtin_object_size(gs.a, 0);\n",
6346 "unsigned long inner = __builtin_object_size(gs.a, 1);\n",
6347 "unsigned long scalar = __builtin_object_size(&gs.n, 1);\n",
6348 "unsigned long after = __builtin_object_size(&gs.n, 0);\n",
6349 "unsigned long into = __builtin_object_size(&gs.b[2], 1);\n",
6350 "unsigned long text = __builtin_object_size(\"hello\", 0);\n",
6351 "unsigned long dyn = __builtin_dynamic_object_size(gs.b, 1);\n",
6352 ));
6353 for (name, size) in [
6354 ("whole", 32),
6355 ("moved", 28),
6356 ("back", 4),
6357 ("outer", 24),
6358 ("inner", 8),
6359 ("scalar", 4),
6360 ("after", 16),
6361 ("into", 10),
6362 ("text", 6),
6363 ("dyn", 12),
6364 ] {
6365 let said = format!("global @{name} : i64 = {size},");
6366 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
6367 }
6368 }
6369
6370 #[test]
6378 fn the_object_behind_an_address_can_be_one_with_automatic_storage() {
6379 let text = body(concat!(
6380 "struct S { char a[8]; int n; char b[12]; };\n",
6381 "unsigned long f(void) {\n",
6382 " char loc[20];\n",
6383 " struct S ls;\n",
6384 " return __builtin_object_size(loc + 3, 0) + __builtin_object_size(ls.b + 2, 1);\n",
6385 "}\n",
6386 ));
6387 assert!(text.contains("iconst.i64 17"), "twenty bytes with three used: {text}");
6388 assert!(text.contains("iconst.i64 10"), "twelve bytes with two used: {text}");
6389 }
6390
6391 #[test]
6401 fn an_address_with_no_object_in_sight_answers_at_the_end_of_the_range_its_kind_asks_for() {
6402 let text = ir(concat!(
6403 "struct T { int n; char f[]; };\n",
6404 "extern char *p;\n",
6405 "extern struct T *t;\n",
6406 "unsigned long largest = __builtin_object_size(p, 0);\n",
6407 "unsigned long nearest = __builtin_object_size(p, 1);\n",
6408 "unsigned long least = __builtin_object_size(p, 2);\n",
6409 "unsigned long tight = __builtin_object_size(p, 3);\n",
6410 "unsigned long flex = __builtin_object_size(t->f, 1);\n",
6411 "int says = __builtin_object_size(p, 0) == (unsigned long)-1;\n",
6412 ));
6413 for name in ["largest", "nearest", "flex"] {
6414 let said = format!("global @{name} : i64 = -1,");
6418 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
6419 }
6420 for name in ["least", "tight"] {
6421 let said = format!("global @{name} : i64 = 0,");
6422 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
6423 }
6424 assert!(text.contains("global @says : i32 = 1,"), "{text}");
6425 }
6426
6427 #[test]
6434 fn the_address_an_object_size_is_asked_about_is_not_evaluated() {
6435 let text = body(concat!(
6436 "extern char *side(void);\n",
6437 "unsigned long f(void) { return __builtin_object_size(side(), 0); }\n",
6438 ));
6439 assert!(!text.contains("call"), "nothing is called: {text}");
6440 }
6441
6442 #[test]
6447 fn a_kind_that_is_not_one_of_the_four_is_refused() {
6448 for source in [
6449 "extern char *p;\nextern int k;\nunsigned long f(void) ".to_owned()
6450 + "{ return __builtin_object_size(p, k); }\n",
6451 "extern char *p;\nunsigned long f(void) { return __builtin_object_size(p, 4); }\n"
6452 .to_owned(),
6453 "extern char *p;\nunsigned long f(void) ".to_owned()
6454 + "{ return __builtin_dynamic_object_size(p, -1); }\n",
6455 ] {
6456 let messages = errors(&source);
6457 let named = messages.iter().any(|m| m.contains("E0709") && m.contains("0 to 3"));
6458 assert!(named, "expected a complaint about the kind in {messages:?}");
6459 }
6460 }
6461
6462 #[test]
6468 fn the_pair_that_saves_a_place_lowers_to_the_two_markers() {
6469 let text = ir(concat!(
6470 "void *buf[5];\n",
6471 "int f(void) {\n",
6472 " if (__builtin_setjmp(buf)) return 2;\n",
6473 " return 1;\n",
6474 "}\n",
6475 "void g(void) { __builtin_longjmp(buf, 1); }\n",
6476 ));
6477 assert!(text.contains("= setjmp_marker.i32 %0\n"), "the save answers a value: {text}");
6478 assert!(text.contains(" longjmp_marker %0\n"), "the restore answers nothing: {text}");
6479 assert!(!text.contains("call @"), "neither of them is a call: {text}");
6480 }
6481
6482 #[test]
6490 fn a_local_of_a_function_that_saves_a_place_gets_a_slot() {
6491 let text = ir(concat!(
6492 "void *buf[5];\n",
6493 "int f(int x) { int a = 0; if (__builtin_setjmp(buf)) return a; a = 1; return x; }\n",
6494 "int g(int x) { int a = 0; if (x) return a; a = 1; return x; }\n",
6495 ));
6496 let (saves, plain) = text.split_once("func @g").expect("both functions");
6497 assert_eq!(saves.matches("= alloca").count(), 2, "the parameter and the local: {text}");
6498 assert!(saves.contains("store %9 -> %2"), "the local is written through: {text}");
6499 assert!(!plain.contains("alloca"), "nothing in the plain one needs a slot: {text}");
6500 }
6501
6502 #[test]
6511 fn the_save_writes_four_words_and_carries_on_in_a_new_block() {
6512 let text =
6513 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
6514 let body = text.split_once("\nf:\n").expect("the function").1;
6515 assert!(body.contains("\tmovq\t%rsp, %rbp\n"), "a frame pointer whatever: {text}");
6516 assert!(body.contains("\tsubq\t$8, %rsp\n"), "no red zone: {text}");
6517 assert!(body.contains("\tmovq\t%rbp, (%rax)\n"), "the frame pointer: {text}");
6518 assert!(body.contains("\tmovq\t%rsp, 16(%rax)\n"), "the stack pointer: {text}");
6519 assert!(body.contains("\tleaq\t.Lf_1(%rip), %rcx\n"), "where to come back to: {text}");
6520 assert!(body.contains("\tmovq\t%rcx, 8(%rax)\n"), "and that goes in the buffer: {text}");
6521 let back = body.split_once(".Lf_1:\n").expect("the block control comes back to").1;
6522 assert!(back.starts_with("\tmovq\t(%rsp), %rax\n"), "the answer is read back: {text}");
6523 }
6524
6525 #[test]
6533 fn a_save_destroys_every_register_the_allocator_hands_out() {
6534 let text =
6535 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
6536 for reg in ["%rbx", "%r12", "%r13", "%r14", "%r15"] {
6537 assert!(text.contains(&format!("\tpushq\t{reg}\n")), "{reg} is saved: {text}");
6538 assert!(text.contains(&format!("\tpopq\t{reg}\n")), "{reg} is restored: {text}");
6539 }
6540 }
6541
6542 #[test]
6549 fn the_restore_puts_the_frame_back_before_it_jumps() {
6550 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
6551 let mut opts = options();
6552 opts.emit = EmitKind::Asm;
6553 opts.opt_level = level;
6554 let source = "void *buf[5];\nvoid g(void) { __builtin_longjmp(buf, 1); }\n";
6555 let result = run(&opts, source);
6556 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
6557 let text = result.text().to_owned();
6558 let jump = text.find("\tjmp\t*%").unwrap_or_else(|| panic!("an indirect jump: {text}"));
6559 let stack = text.find(", %rsp\n").unwrap_or_else(|| panic!("the stack back: {text}"));
6560 let frame = text.find(", %rbp\n").unwrap_or_else(|| panic!("the frame back: {text}"));
6561 assert!(stack < jump, "the stack goes back first at {level:?}: {text}");
6562 assert!(frame < jump, "and so does the frame at {level:?}: {text}");
6563 }
6564 }
6565
6566 #[test]
6572 fn a_longjmp_whose_second_argument_is_not_one_is_turned_down() {
6573 for source in [
6574 "void *buf[5];\nvoid f(void) { __builtin_longjmp(buf, 0); }\n",
6575 "void *buf[5];\nextern int v;\nvoid f(void) { __builtin_longjmp(buf, v); }\n",
6576 ] {
6577 let messages = errors(source);
6578 let named = messages.iter().any(|m| m.contains("E0710"));
6579 assert!(named, "expected a complaint about the value in {messages:?}");
6580 }
6581 }
6582
6583 #[test]
6588 fn a_static_function_nothing_refers_to_is_not_emitted() {
6589 let text = ir("static int dropped(void) { return 1; }\n\
6590 static int kept(void) { return 2; }\n\
6591 int main(void) { return kept(); }\n");
6592 assert!(text.contains("func @kept"), "{text}");
6593 assert!(!text.contains("dropped"), "{text}");
6594 }
6595
6596 #[test]
6602 fn two_static_functions_that_only_call_each_other_are_both_dropped() {
6603 let text = ir("static int ping(void);\n\
6604 static int pong(void) { return ping(); }\n\
6605 static int ping(void) { return pong(); }\n\
6606 int main(void) { return 0; }\n");
6607 assert!(!text.contains("ping"), "{text}");
6608 assert!(!text.contains("pong"), "{text}");
6609 }
6610
6611 #[test]
6617 fn naming_a_static_function_anywhere_keeps_it() {
6618 let text = ir("static int by_address(void) { return 1; }\n\
6619 static int in_an_image(void) { return 2; }\n\
6620 static int deeper(void) { return 3; }\n\
6621 static int reaches_deeper(void) { return deeper(); }\n\
6622 static int (*table[1])(void) = {in_an_image};\n\
6623 int main(void) {\n\
6624 int (*p)(void) = by_address;\n\
6625 return p() + table[0]() + reaches_deeper();\n\
6626 }\n");
6627 for kept in ["by_address", "in_an_image", "deeper", "reaches_deeper"] {
6628 assert!(text.contains(&format!("func @{kept}")), "expected {kept} in:\n{text}");
6629 }
6630 }
6631
6632 #[test]
6638 fn an_attribute_keeps_a_static_function_nothing_refers_to() {
6639 for attribute in ["used", "retain", "constructor", "destructor", "__used__"] {
6640 let source = format!(
6641 "__attribute__(({attribute})) static int kept(void) {{ return 1; }}\n\
6642 int main(void) {{ return 0; }}\n"
6643 );
6644 let text = ir(&source);
6645 assert!(text.contains("func @kept"), "for {attribute}:\n{text}");
6646 }
6647 }
6648
6649 #[test]
6652 fn a_function_anything_could_call_is_emitted_without_being_called() {
6653 let text =
6654 ir("int nobody_here_calls_it(void) { return 1; }\nint main(void) { return 0; }\n");
6655 assert!(text.contains("func @nobody_here_calls_it"), "{text}");
6656 }
6657
6658 #[test]
6665 fn a_classification_c_has_an_operator_for_is_that_operator() {
6666 for (builtin, operator) in [
6667 ("__builtin_isgreater", "binary >"),
6668 ("__builtin_isgreaterequal", "binary >="),
6669 ("__builtin_isless", "binary <"),
6670 ("__builtin_islessequal", "binary <="),
6671 ] {
6672 let source = format!("int f(double x, double y) {{ return {builtin}(x, y); }}\n");
6673 let text = tast(&source);
6674 assert!(text.contains(&format!("{operator} : int")), "for {builtin}:\n{text}");
6675 }
6676 }
6677
6678 #[test]
6687 fn the_classification_builtins_are_comparisons_and_not_calls() {
6688 let text = body("int f(double x, double y) { return __builtin_isunordered(x, y); }\n");
6689 assert_eq!(
6690 text,
6691 "block0(%0: f64, %1: f64):\n %2 = fcmp uno %0, %1\n %3 = zext.i32 \
6692 %2\n return %3\n"
6693 );
6694
6695 let text = body("int f(double x, double y) { return __builtin_islessgreater(x, y); }\n");
6697 assert!(text.contains("fcmp one %0, %1"), "{text}");
6698
6699 let text = body("int f(double x) { return __builtin_isnan(x); }\n");
6700 assert!(text.contains("fcmp uno %0, %0"), "{text}");
6701
6702 let text = body("int f(double x) { return __builtin_isinf(x); }\n");
6703 assert!(text.contains("fconst.f64 0x7ff0000000000000"), "{text}");
6704 assert!(text.contains("fconst.f64 0xfff0000000000000"), "{text}");
6705 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
6706 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
6707 assert!(text.contains("%5 = or %3, %4"), "{text}");
6708
6709 let text = body("int f(double x) { return __builtin_isfinite(x); }\n");
6712 assert!(text.contains("%3 = fcmp olt %2, %0"), "{text}");
6713 assert!(text.contains("%4 = fcmp olt %0, %1"), "{text}");
6714 assert!(text.contains("%5 = and %3, %4"), "{text}");
6715
6716 let text = body("int f(double x) { return __builtin_signbit(x); }\n");
6717 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
6718 assert!(text.contains("icmp slt %1, %2"), "{text}");
6719
6720 let text = body("int f(long double x) { return __builtin_signbitl(x); }\n");
6724 assert!(text.contains("load.i16"), "{text}");
6725 assert!(text.contains("icmp slt"), "{text}");
6726 assert!(!text.contains("i80"), "{text}");
6727
6728 let text = body("double g(void);\nint f(void) { return __builtin_isnan(g()); }\n");
6731 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
6732 }
6733
6734 #[test]
6741 fn a_classification_spelling_that_names_a_width_converts_before_it_asks() {
6742 let text = ir(concat!(
6743 "int a = __builtin_isinff(1e300);\n",
6744 "int b = __builtin_isinf(1e300);\n",
6745 "int c = __builtin_isnan(0.0);\n",
6749 "int d = __builtin_signbit(-0.0);\n",
6750 "int e = __builtin_islessgreater(1.0, 2.0);\n",
6751 ));
6752 assert!(text.contains("global @a : i32 = 1,"), "{text}");
6753 assert!(text.contains("global @b : i32 = 0,"), "{text}");
6754 assert!(text.contains("global @c : i32 = 0,"), "{text}");
6755 assert!(text.contains("global @d : i32 = 1,"), "{text}");
6756 assert!(text.contains("global @e : i32 = 1,"), "{text}");
6757 }
6758
6759 #[test]
6761 fn a_classification_builtin_refuses_an_argument_that_is_not_floating_point() {
6762 let mut opts = options();
6763 opts.emit = EmitKind::Ir;
6764 let source = concat!(
6765 "int a(int x) { return __builtin_isnan(x); }\n",
6766 "int b(int x, int y) { return __builtin_isunordered(x, y); }\n",
6767 "int c(double x) { return __builtin_isnan(x, x); }\n",
6768 );
6769 let messages = run(&opts, source).messages;
6770 assert_eq!(
6771 messages,
6772 [
6773 "/main.c:1:23: error: non-floating-point argument in call to function \
6774 '__builtin_isnan' [E0685]",
6775 "/main.c:2:30: error: non-floating-point arguments in call to function \
6776 '__builtin_isunordered' [E0685]",
6777 "/main.c:3:26: error: too many arguments to function '__builtin_isnan' [E0511]",
6778 ]
6779 );
6780 }
6781
6782 #[test]
6791 fn the_last_three_classification_builtins_are_comparisons_and_not_calls() {
6792 let text = body("int f(double x) { return __builtin_isnormal(x); }\n");
6793 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
6797 assert!(text.contains("%2 = iconst.i64 9223372036854775807"), "{text}");
6798 assert!(text.contains("%3 = and %1, %2"), "{text}");
6799 assert!(text.contains("%4 = iconst.i64 4503599627370496"), "{text}");
6800 assert!(text.contains("%5 = iconst.i64 9218868437227405312"), "{text}");
6801 assert!(text.contains("%6 = icmp uge %3, %4"), "{text}");
6802 assert!(text.contains("%7 = icmp ult %3, %5"), "{text}");
6803 assert!(text.contains("%8 = and %6, %7"), "{text}");
6804
6805 let text = body("int f(long double x) { return __builtin_isnormal(x); }\n");
6810 assert!(text.contains("fconst.f80 0x18000000000000000"), "{text}");
6811 assert!(text.contains("fconst.f80 0x7fff8000000000000000"), "{text}");
6812 assert!(text.contains("fcmp oge"), "{text}");
6813 assert!(text.contains("fcmp olt"), "{text}");
6814
6815 let text = body("int f(double x) { return __builtin_isinf_sign(x); }\n");
6816 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
6817 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
6818 assert!(text.contains("%7 = sub %5, %6"), "{text}");
6819
6820 let text = body("int f(double x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n");
6821 assert!(text.contains("fcmp uno %0, %0"), "{text}");
6822 assert!(text.contains("fcmp oeq %0, %6"), "{text}");
6823 assert_eq!(text.matches(" = zext.i32 ").count(), 4, "{text}");
6827 assert_eq!(text.matches(" = xor ").count(), 4, "{text}");
6828 assert!(!text.contains("call"), "{text}");
6829
6830 let text = body(concat!(
6833 "double g(void);\n",
6834 "int f(void) { return __builtin_fpclassify(0, 1, 2, 3, 4, g()); }\n",
6835 ));
6836 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
6837 }
6838
6839 #[test]
6846 fn the_last_three_classification_builtins_fold_where_their_operand_is_a_constant() {
6847 let text = ir(concat!(
6848 "int a = __builtin_isnormal(1.0);\n",
6849 "int b = __builtin_isnormal(0.0);\n",
6850 "int c = __builtin_isnormal(1.0 / 0.0);\n",
6851 "int d = __builtin_isinf_sign(-1.0 / 0.0);\n",
6852 "int e = __builtin_isinf_sign(1.0);\n",
6853 "int g = __builtin_fpclassify(0, 1, 2, 3, 4, 0.0);\n",
6854 "int h = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0);\n",
6855 "int i = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0 / 0.0);\n",
6856 ));
6857 assert!(text.contains("global @a : i32 = 1,"), "{text}");
6858 assert!(text.contains("global @b : i32 = 0,"), "{text}");
6859 assert!(text.contains("global @c : i32 = 0,"), "{text}");
6860 assert!(text.contains("global @d : i32 = -1,"), "{text}");
6861 assert!(text.contains("global @e : i32 = 0,"), "{text}");
6862 assert!(text.contains("global @g : i32 = 4,"), "{text}");
6863 assert!(text.contains("global @h : i32 = 2,"), "{text}");
6864 assert!(text.contains("global @i : i32 = 1,"), "{text}");
6865 }
6866
6867 #[test]
6873 fn fpclassify_refuses_an_answer_that_is_not_an_integer_constant() {
6874 let mut opts = options();
6875 opts.emit = EmitKind::Ir;
6876 let source = concat!(
6877 "int a(double x, int n) { return __builtin_fpclassify(0, 1, n, 3, 4, x); }\n",
6878 "int b(double x) { return __builtin_fpclassify(0, 1, 2, 3, x); }\n",
6879 "int c(int x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n",
6880 );
6881 let messages = run(&opts, source).messages;
6882 assert_eq!(
6883 messages,
6884 [
6885 "/main.c:1:60: error: non-const integer argument 3 in call to function \
6886 '__builtin_fpclassify' [E0687]",
6887 "/main.c:2:26: error: too few arguments to function '__builtin_fpclassify' \
6888 [E0511]",
6889 "/main.c:3:23: error: non-floating-point argument in call to function \
6890 '__builtin_fpclassify' [E0685]",
6891 ]
6892 );
6893 }
6894
6895 #[test]
6903 fn a_builtin_whose_answer_is_a_constant_is_one_and_not_a_call() {
6904 let text = ir(concat!(
6905 "double a = __builtin_inf();\n",
6906 "float b = __builtin_huge_valf();\n",
6907 "long double c = __builtin_infl();\n",
6908 "double d = __builtin_huge_val();\n",
6909 ));
6910 assert!(text.contains("global @a : f64 = 0x7ff0000000000000,"), "{text}");
6911 assert!(text.contains("global @b : f32 = 0x7f800000,"), "{text}");
6912 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
6913 assert!(text.contains("global @d : f64 = 0x7ff0000000000000,"), "{text}");
6914 assert!(!text.contains("call"), "{text}");
6915 }
6916
6917 #[test]
6926 fn a_nan_is_written_with_the_payload_the_program_asked_for() {
6927 let text = ir(concat!(
6928 "double a = __builtin_nan(\"\");\n",
6929 "double b = __builtin_nan(\"0x1\");\n",
6930 "double c = __builtin_nan(\"010\");\n",
6932 "double d = __builtin_nans(\"\");\n",
6933 "double e = __builtin_nans(\"0x1\");\n",
6934 "float f = __builtin_nanf(\"0x1\");\n",
6935 "float g = __builtin_nansf(\"\");\n",
6936 "long double h = __builtin_nansl(\"\");\n",
6937 ));
6938 assert!(text.contains("global @a : f64 = 0x7ff8000000000000,"), "{text}");
6939 assert!(text.contains("global @b : f64 = 0x7ff8000000000001,"), "{text}");
6940 assert!(text.contains("global @c : f64 = 0x7ff8000000000008,"), "{text}");
6941 assert!(text.contains("global @d : f64 = 0x7ff4000000000000,"), "{text}");
6942 assert!(text.contains("global @e : f64 = 0x7ff0000000000001,"), "{text}");
6943 assert!(text.contains("global @f : f32 = 0x7fc00001,"), "{text}");
6944 assert!(text.contains("global @g : f32 = 0x7fa00000,"), "{text}");
6945 assert!(text.contains("f80 0x7fffa000000000000000"), "{text}");
6946
6947 let text = ir(concat!(
6950 "double f(const char *p) { return __builtin_nan(p); }\n",
6951 "double g(void) { return __builtin_nans(\"1x\"); }\n",
6952 ));
6953 assert_eq!(text.matches("call @nan(").count(), 1, "{text}");
6954 assert_eq!(text.matches("call @nans(").count(), 1, "{text}");
6955 }
6956
6957 #[test]
6965 fn the_length_and_the_order_of_a_string_literal_are_known_here() {
6966 let text = ir(concat!(
6967 "unsigned long a = __builtin_strlen(\"hello\");\n",
6968 "unsigned long b = __builtin_strlen(\"a\\0bc\");\n",
6969 "int c = __builtin_strcmp(\"X\", \"X\\376\") < 0;\n",
6970 "int d = __builtin_strcmp(\"abc\", \"abc\");\n",
6971 "int e = __builtin_strcmp(\"abc\", \"ab\") > 0;\n",
6972 ));
6973 assert!(text.contains("global @a : i64 = 5,"), "{text}");
6974 assert!(text.contains("global @b : i64 = 1,"), "{text}");
6975 assert!(text.contains("global @c : i32 = 1,"), "{text}");
6976 assert!(text.contains("global @d : i32 = 0,"), "{text}");
6977 assert!(text.contains("global @e : i32 = 1,"), "{text}");
6978 assert!(!text.contains("call"), "{text}");
6979
6980 let text = ir("unsigned long f(const char *p) { return __builtin_strlen(p); }\n");
6982 assert!(text.contains("call @strlen("), "{text}");
6983 }
6984
6985 #[test]
6992 fn a_sign_builtin_is_a_mask_over_the_bits_and_not_a_call() {
6993 let text = body("double f(double x) { return __builtin_fabs(x); }\n");
6994 assert!(text.contains("bitcast.i64 %0"), "{text}");
6995 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
6996 assert!(text.contains("and %1, %2"), "{text}");
6997 assert!(text.contains("bitcast.f64 %3"), "{text}");
6998 assert!(!text.contains("call"), "{text}");
6999
7000 let text = body("double f(double x, double y) { return __builtin_copysign(x, y); }\n");
7001 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
7002 assert!(text.contains("%8 = or %4, %7"), "{text}");
7003 assert!(!text.contains("call"), "{text}");
7004
7005 let text = body("long double f(long double x) { return __builtin_fabsl(x); }\n");
7008 assert!(text.contains("iconst.i16 32767"), "{text}");
7009 assert!(text.contains("load.f80"), "{text}");
7010 assert!(!text.contains("call"), "{text}");
7011
7012 let text = body("double f(float x) { return __builtin_fabs(x); }\n");
7015 assert!(text.contains("fpext.f64 %0"), "{text}");
7016 assert!(text.contains("bitcast.i64 %1"), "{text}");
7017 }
7018
7019 #[test]
7026 fn a_shuffle_picks_each_lane_by_the_low_bits_of_the_mask() {
7027 let text = body(concat!(
7028 "typedef int v2 __attribute__((vector_size(8)));\n",
7029 "void f(v2 *v, v2 m) { *v = __builtin_shuffle(*v, m); }\n",
7030 ));
7031 assert!(text.contains("memcpy"), "{text}");
7032 assert_eq!(text.matches("iconst.i32 1\n").count(), 2, "{text}");
7033 assert_eq!(text.matches(" = and ").count(), 2, "{text}");
7034 assert!(!text.contains("call"), "{text}");
7035
7036 let text = body(concat!(
7039 "typedef char v4 __attribute__((vector_size(4)));\n",
7040 "v4 f(v4 a, v4 b, v4 m) { return __builtin_shuffle(a, b, m); }\n",
7041 ));
7042 assert_eq!(text.matches("iconst.i32 7\n").count(), 4, "{text}");
7043 assert!(text.contains("zext.i32"), "{text}");
7044 assert!(!text.contains("call"), "{text}");
7045 }
7046
7047 #[test]
7049 fn a_shuffle_refuses_what_gcc_refuses() {
7050 let mut opts = options();
7051 opts.emit = EmitKind::Ir;
7052 let source = concat!(
7053 "typedef int v4 __attribute__((vector_size(16)));\n",
7054 "typedef float f4 __attribute__((vector_size(16)));\n",
7055 "typedef short s8 __attribute__((vector_size(16)));\n",
7056 "typedef long long l4 __attribute__((vector_size(32)));\n",
7057 "void a(v4 x, f4 m) { __builtin_shuffle(x, m); }\n",
7058 "void b(int x, v4 m) { __builtin_shuffle(x, m); }\n",
7059 "void c(v4 x, f4 y, v4 m) { __builtin_shuffle(x, y, m); }\n",
7060 "void d(v4 x, s8 m) { __builtin_shuffle(x, m); }\n",
7061 "void e(f4 x, l4 m) { __builtin_shuffle(x, m); }\n",
7062 "void g(v4 x) { __builtin_shuffle(x); }\n",
7063 );
7064 let messages = run(&opts, source).messages;
7065 let wanted = [
7066 "last argument must be an integer vector [E0715]",
7067 "arguments must be vectors [E0715]",
7068 "argument vectors must be of the same type [E0715]",
7069 "number of elements of the argument vector(s) and the mask vector should be the same \
7070 [E0715]",
7071 "argument vector(s) inner type must have the same size as inner type of the mask \
7072 [E0715]",
7073 "too few arguments to function '__builtin_shuffle' [E0511]",
7074 ];
7075 assert_eq!(messages.len(), wanted.len(), "{messages:?}");
7076 for (message, wanted) in messages.iter().zip(wanted) {
7077 assert!(message.ends_with(wanted), "{message}");
7078 }
7079 }
7080
7081 #[test]
7090 fn the_plain_math_names_are_the_same_mask_and_not_a_call() {
7091 let text =
7092 body(concat!("double fabs(double x);\n", "double f(double x) { return fabs(x); }\n",));
7093 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
7094 assert!(!text.contains("call"), "{text}");
7095
7096 let text =
7097 body(concat!("float fabsf(float x);\n", "float f(float x) { return fabsf(x); }\n",));
7098 assert!(text.contains("bitcast.i32 %0"), "{text}");
7099 assert!(!text.contains("call"), "{text}");
7100
7101 let text = body(concat!(
7102 "double copysign(double x, double y);\n",
7103 "double f(double x, double y) { return copysign(x, y); }\n",
7104 ));
7105 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
7106 assert!(!text.contains("call"), "{text}");
7107
7108 let text = body(concat!(
7109 "float copysignf(float x, float y);\n",
7110 "float f(float x, float y) { return copysignf(x, y); }\n",
7111 ));
7112 assert!(!text.contains("call"), "{text}");
7113
7114 let text = ir(concat!(
7118 "long double fabsl(long double x);\n",
7119 "long double f(long double x) { return fabsl(x); }\n",
7120 ));
7121 assert!(text.contains("call @fabsl"), "{text}");
7122 }
7123
7124 #[test]
7132 fn a_plain_math_name_the_program_took_is_the_programs_own_function() {
7133 let taken = concat!(
7134 "static double fabs(double b) { return 7; }\n",
7135 "double f(double x) { return fabs(x); }\n",
7136 );
7137 assert!(ir(taken).contains("call @fabs"), "a static definition is the program's own");
7138
7139 let retyped = concat!("int fabs(int b);\n", "int f(int x) { return fabs(x); }\n");
7140 assert!(ir(retyped).contains("call @fabs"), "another type is another function");
7141
7142 let plain = concat!("double fabs(double b);\n", "double f(double x) { return fabs(x); }\n");
7143 let mut opts = options();
7144 opts.emit = EmitKind::Ir;
7145 assert!(!run(&opts, plain).text().contains("call @fabs"), "the library's by default");
7146
7147 opts.builtins = false;
7148 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin");
7149
7150 opts.builtins = true;
7151 opts.no_builtin = vec!["fabs".to_owned()];
7152 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin-fabs");
7153 let one = concat!(
7154 "double copysign(double a, double b);\n",
7155 "double f(double x) { return copysign(x, 1.0); }\n",
7156 );
7157 assert!(!run(&opts, one).text().contains("call @copysign"), "one name and not the family");
7158
7159 opts.no_builtin = Vec::new();
7161 opts.builtins = false;
7162 let prefixed = "double f(double x) { return __builtin_fabs(x); }\n";
7163 assert!(!run(&opts, prefixed).text().contains("call @fabs"), "the prefix is not a library");
7164 }
7165
7166 #[test]
7175 fn the_sign_builtins_answer_a_zero_and_a_nan_the_way_the_bits_say() {
7176 let text = ir(concat!(
7177 "double a = __builtin_fabs(-3.5);\n",
7178 "double b = __builtin_copysign(1.0, -0.0);\n",
7179 "double c = __builtin_copysign(0.0, -2.0);\n",
7180 "double d = __builtin_copysign(-__builtin_nan(\"\"), 1.0);\n",
7182 "double e = __builtin_fabs(-__builtin_nan(\"0x1\"));\n",
7183 "float g = __builtin_copysignf(-0.0f, 2.0f);\n",
7184 "long double h = __builtin_copysignl(1.0L, -1.0L);\n",
7185 "long double i = __builtin_fabsl(-__builtin_infl());\n",
7186 ));
7187 assert!(text.contains("global @a : f64 = 0x400c000000000000,"), "{text}");
7188 assert!(text.contains("global @b : f64 = 0xbff0000000000000,"), "{text}");
7189 assert!(text.contains("global @c : f64 = 0x8000000000000000,"), "{text}");
7190 assert!(text.contains("global @d : f64 = 0x7ff8000000000000,"), "{text}");
7191 assert!(text.contains("global @e : f64 = 0x7ff8000000000001,"), "{text}");
7192 assert!(text.contains("global @g : f32 = 0x0,"), "{text}");
7193 assert!(text.contains("f80 0xbfff8000000000000000"), "{text}");
7194 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
7195 }
7196
7197 #[test]
7204 fn the_sign_of_a_long_double_is_in_the_word_at_the_top_of_it() {
7205 for source in [
7206 "int f(long double x) { return __builtin_signbit(x); }\n",
7207 "long double f(long double x) { return __builtin_fabsl(x); }\n",
7208 "long double f(long double x, long double y) { return __builtin_copysignl(x, y); }\n",
7209 "int f(long double x) { return __builtin_isnormal(x); }\n",
7210 ] {
7211 let text = body(source);
7212 assert!(!text.contains("i80"), "{text}");
7213 assert!(text.contains("i16"), "{text}");
7214 }
7215 }
7216
7217 #[test]
7225 fn the_complex_builtins_are_the_halves_of_the_value_and_not_a_call() {
7226 let text = body("double f(_Complex double z) { return __builtin_creal(z); }\n");
7227 assert!(!text.contains("call"), "{text}");
7228 let text = body("double f(_Complex double z) { return __builtin_cimag(z); }\n");
7229 assert!(!text.contains("call"), "{text}");
7230
7231 let text = body("_Complex double f(_Complex double z) { return __builtin_conj(z); }\n");
7234 assert_eq!(text.matches("fneg").count(), 1, "{text}");
7235 assert!(!text.contains("call"), "{text}");
7236 let negated = body("_Complex double f(_Complex double z) { return -z; }\n");
7237 assert_eq!(negated.matches("fneg").count(), 2, "{negated}");
7238
7239 let written = body("_Complex double f(_Complex double z) { return ~z; }\n");
7242 assert_eq!(written, text, "the name and the operator are the same thing");
7243
7244 let text = body(concat!(
7246 "double creal(_Complex double z);\n",
7247 "double f(_Complex double z) { return creal(z); }\n",
7248 ));
7249 assert!(!text.contains("call"), "{text}");
7250 let text = body(concat!(
7251 "_Complex float conjf(_Complex float z);\n",
7252 "_Complex float f(_Complex float z) { return conjf(z); }\n",
7253 ));
7254 assert_eq!(text.matches("fneg").count(), 1, "{text}");
7255 assert!(!text.contains("call"), "{text}");
7256
7257 let taken = concat!(
7260 "static double creal(_Complex double z) { return 7; }\n",
7261 "double f(_Complex double z) { return creal(z); }\n",
7262 );
7263 assert!(ir(taken).contains("call @creal"), "a static definition is the program's own");
7264 let retyped = concat!("int cimag(int z);\n", "int f(int z) { return cimag(z); }\n");
7265 assert!(ir(retyped).contains("call @cimag"), "another type is another function");
7266 let plain = concat!(
7267 "double cimag(_Complex double z);\n",
7268 "double f(_Complex double z) { return cimag(z); }\n",
7269 );
7270 let mut opts = options();
7271 opts.emit = EmitKind::Ir;
7272 opts.builtins = false;
7273 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin");
7274 opts.builtins = true;
7275 opts.no_builtin = vec!["cimag".to_owned()];
7276 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin-cimag");
7277
7278 let text = ir(concat!(
7280 "double a = __builtin_creal(1.5 + 2.5i);\n",
7281 "double b = __builtin_cimag(1.5 + 2.5i);\n",
7282 "_Complex double c = __builtin_conj(1.5 + 2.5i);\n",
7283 ));
7284 assert!(text.contains("global @a : f64 = 0x3ff8000000000000,"), "{text}");
7285 assert!(text.contains("global @b : f64 = 0x4004000000000000,"), "{text}");
7286 assert!(
7287 text.contains("{ f64 0x3ff8000000000000, f64 0xc004000000000000 }"),
7288 "the conjugate of a constant is the constant with the second half negated: {text}"
7289 );
7290 assert!(!text.contains("call"), "{text}");
7291 }
7292
7293 #[test]
7301 fn a_math_library_builtin_of_a_constant_is_the_answer_and_not_a_call() {
7302 let text = ir(concat!(
7303 "double a = __builtin_ceil(1.5);\n",
7304 "double b = __builtin_floor(1.5);\n",
7305 "double c = __builtin_trunc(-1.5);\n",
7306 "double d = __builtin_round(2.5);\n",
7309 "double e = __builtin_ceil(-0.5);\n",
7311 "double f = __builtin_fmax(1.0, 2.0);\n",
7312 "double g = __builtin_fmin(1.0, 2.0);\n",
7313 "float h = __builtin_ceilf(1.25f);\n",
7314 "double ceil(double x);\n",
7317 "double i = ceil(2.25);\n",
7318 ));
7319 assert!(text.contains("global @a : f64 = 0x4000000000000000,"), "{text}");
7320 assert!(text.contains("global @b : f64 = 0x3ff0000000000000,"), "{text}");
7321 assert!(text.contains("global @c : f64 = 0xbff0000000000000,"), "{text}");
7322 assert!(text.contains("global @d : f64 = 0x4008000000000000,"), "{text}");
7323 assert!(text.contains("global @e : f64 = 0x8000000000000000,"), "{text}");
7324 assert!(text.contains("global @f : f64 = 0x4000000000000000,"), "{text}");
7325 assert!(text.contains("global @g : f64 = 0x3ff0000000000000,"), "{text}");
7326 assert!(text.contains("global @h : f32 = 0x40000000,"), "{text}");
7327 assert!(text.contains("global @i : f64 = 0x4008000000000000,"), "{text}");
7328 assert!(!text.contains("call"), "{text}");
7329 }
7330
7331 #[test]
7339 fn a_math_library_builtin_of_anything_else_is_a_call_to_the_library() {
7340 let text = ir(concat!(
7341 "double f(double x) { return __builtin_ceil(x); }\n",
7342 "float g(float x) { return __builtin_floorf(x); }\n",
7343 "double h(double x, double y) { return __builtin_fmax(x, y); }\n",
7344 ));
7345 assert!(text.contains("call @ceil("), "{text}");
7346 assert!(text.contains("call @floorf("), "{text}");
7347 assert!(text.contains("call @fmax("), "{text}");
7348
7349 let text = ir(concat!(
7353 "double f(void) { return __builtin_rint(2.5); }\n",
7354 "double g(void) { return __builtin_nearbyint(2.5); }\n",
7355 ));
7356 assert!(text.contains("call @rint("), "{text}");
7357 assert!(text.contains("call @nearbyint("), "{text}");
7358
7359 let text = ir("double f(void) { return __builtin_fmin(__builtin_nan(\"\"), 1.0); }\n");
7362 assert!(text.contains("call @fmin("), "{text}");
7363
7364 let plain = concat!("double ceil(double x);\n", "double f(void) { return ceil(2.25); }\n");
7367 let mut opts = options();
7368 opts.emit = EmitKind::Ir;
7369 opts.no_builtin = vec!["ceil".to_owned()];
7370 assert!(run(&opts, plain).text().contains("call @ceil("), "-fno-builtin-ceil");
7371 }
7372
7373 #[test]
7380 fn a_constexpr_object_is_a_constant_wherever_one_is_required() {
7381 let text = ir(concat!(
7382 "constexpr int side = 4;\n",
7383 "constexpr int wider = side + 1;\n",
7384 "constexpr double half = 1.5;\n",
7385 "struct point { int x; int y; };\n",
7386 "constexpr struct point origin = { 5, 6 };\n",
7387 "int square[side * side];\n",
7388 "int rectangle[wider];\n",
7389 "int rounded[(int)half * 2];\n",
7390 "int across[origin.y];\n",
7391 "enum named { four = side };\n",
7392 "int e = four;\n",
7393 ));
7394 assert!(text.contains("global @square : bytes 64 ="), "{text}");
7395 assert!(text.contains("global @rectangle : bytes 20 ="), "{text}");
7396 assert!(text.contains("global @rounded : bytes 8 ="), "{text}");
7397 assert!(text.contains("global @across : bytes 24 ="), "{text}");
7398 assert!(text.contains("global @e : i32 = 4,"), "{text}");
7399
7400 let mut opts = options();
7403 opts.emit = EmitKind::Ir;
7404 let konst = "const int n = 1;\nint a[n];\n";
7405 let message = "/main.c:2:5: error: variably modified 'a' at file scope [E0538]";
7406 assert_eq!(run(&opts, konst).messages, [message]);
7407
7408 let subscript = "constexpr int t[3] = { 1, 2, 3 };\nint a[t[1]];\n";
7410 assert_eq!(run(&opts, subscript).messages, [message]);
7411
7412 let address = "constexpr int c = 3;\nint *p = &c;\n";
7414 let warning = "/main.c:2:6: warning: initialization discards 'const' qualifier from \
7415 pointer target type [E0514]";
7416 assert_eq!(run(&opts, address).messages, [warning]);
7417 }
7418
7419 #[test]
7428 fn a_member_whose_size_was_refused_is_not_a_flexible_array_member() {
7429 let mut opts = options();
7430 opts.emit = EmitKind::Ir;
7431
7432 let alone = "int k;\nextern struct D { int a[k]; } ed;\n";
7433 let message = "/main.c:2:23: error: variably modified 'a' at file scope [E0538]";
7434 assert_eq!(run(&opts, alone).messages, [message]);
7435
7436 let first = "int k;\nextern struct E { int a[k]; int b; } ee;\n";
7438 assert_eq!(run(&opts, first).messages, [message]);
7439
7440 let negative = "struct F { int a[-1]; };\n";
7443 let refused = "/main.c:1:18: error: size of array 'a' is negative [E0536]";
7444 assert_eq!(run(&opts, negative).messages, [refused]);
7445
7446 let flexible = "struct G { int a[]; };\n";
7449 let named = "/main.c:1:16: error: flexible array member in a struct with no named \
7450 members [E0554]";
7451 assert_eq!(run(&opts, flexible).messages, [named]);
7452 }
7453
7454 #[test]
7469 fn a_pointer_to_an_array_gains_a_qualifier_the_same_way_a_pointer_to_anything_else_does() {
7470 let mut opts = options();
7471 opts.emit = EmitKind::Ir;
7472 let prefix = "typedef unsigned int B[4];\nstruct H { B category[2]; };\n";
7473
7474 let adding = format!("{prefix}const B *f(struct H *h) {{ return &h->category[0]; }}\n");
7476 assert_eq!(run(&opts, &adding).messages, [] as [String; 0]);
7477
7478 let plain = concat!(
7481 "const unsigned int (*f(unsigned int (*p)[4]))[4] { return p; }\n",
7482 "const unsigned int (*g(unsigned int (*p)[2][3]))[2][3] { return p; }\n",
7483 );
7484 assert_eq!(run(&opts, plain).messages, [] as [String; 0]);
7485
7486 let dropping = format!("{prefix}B *f(const B *p) {{ return p; }}\n");
7489 let warning = "/main.c:3:27: warning: return discards 'const' qualifier from pointer target type \
7490 [E0514]";
7491 assert_eq!(run(&opts, &dropping).messages, [warning]);
7492
7493 let wrong = "const unsigned int (*f(unsigned short (*p)[4]))[4] { return p; }\n";
7496 let error = "/main.c:1:61: error: returning 'unsigned short (*)[4]' from a function with \
7497 incompatible return type 'const unsigned int (*)[4]' [E0512]";
7498 assert_eq!(run(&opts, wrong).messages, [error]);
7499 }
7500
7501 #[test]
7510 fn an_old_style_definition_takes_its_types_from_the_declarations_under_its_list() {
7511 let mut opts = options();
7514 opts.std = Std::C17;
7515 let source = concat!(
7516 "int add(a, b)\n",
7517 "int a;\n",
7518 "int b;\n",
7519 "{ return a + b; }\n",
7520 "int promoted(c)\n",
7521 "char c;\n",
7522 "{ return c; }\n",
7523 "int narrow(char);\n",
7524 "int narrow(c)\n",
7525 "char c;\n",
7526 "{ return c; }\n",
7527 "int first(a)\n",
7528 "int a[4];\n",
7529 "{ return a[0]; }\n",
7530 );
7531 let result = run(&opts, source);
7532 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
7533 let text = result.text();
7534 assert!(text.contains("add : int(int, int) function external defined"), "{text}");
7535 assert!(text.contains("promoted : int(int) function external defined"), "{text}");
7536 assert!(text.contains("c : char object automatic defined"), "{text}");
7538 assert!(text.contains("narrow : int(char) function external defined"), "{text}");
7539 assert!(text.contains("first : int(int *) function external defined"), "{text}");
7541 }
7542
7543 #[test]
7550 fn the_two_halves_of_an_old_style_parameter_list_have_to_agree() {
7551 let mut opts = options();
7552 opts.std = Std::C17;
7553 for (source, message) in [
7554 ("int f(a, a)\nint a;\n{ return a; }\n", "1:10: error: multiple parameters named 'a'"),
7555 (
7556 "int f(a)\nint a;\nint b;\n{ return a; }\n",
7557 "3:5: error: declaration for parameter 'b' but no such parameter",
7558 ),
7559 ("int f(a)\nint a;\nint a;\n{ return a; }\n", "3:5: error: redefinition of parameter"),
7560 ("int f(a)\nint a = 1;\n{ return a; }\n", "2:5: error: parameter 'a' is initialized"),
7561 (
7562 "int f(a)\nstatic int a;\n{ return a; }\n",
7563 "2:12: error: storage class specified for parameter 'a'",
7564 ),
7565 (
7566 "int f(char);\nint f(a)\nshort a;\n{ return a; }\n",
7567 "2:7: error: argument 'a' doesn't match prototype",
7568 ),
7569 ] {
7570 let result = run(&opts, source);
7571 assert!(result.failed(), "expected this to fail:\n{source}");
7572 assert!(result.messages[0].contains(message), "{:?}", result.messages);
7573 }
7574
7575 let implicit = "int f(a, b)\nint a;\n{ return a + b; }\n";
7578 let mut older = options();
7579 older.std = Std::C89;
7580 assert!(!run(&older, implicit).failed(), "{:?}", run(&older, implicit).messages);
7581 let result = run(&opts, implicit);
7582 assert!(
7583 result.messages[0].contains("1:10: error: type of 'b' defaults to 'int'"),
7584 "{:?}",
7585 result.messages
7586 );
7587
7588 let mut newer = options();
7592 newer.std = Std::C23;
7593 let plain = "int f(a)\nint a;\n{ return a; }\n";
7594 let result = run(&newer, plain);
7595 assert!(!result.failed(), "{:?}", result.messages);
7596 assert_eq!(
7597 result.messages,
7598 ["/main.c:1:5: warning: old-style function definition [E0412]"]
7599 );
7600 assert!(run(&opts, plain).messages.is_empty(), "and nothing to say in the dialects before");
7601 }
7602
7603 #[test]
7610 fn the_obsolete_designators_are_taken_and_are_pedantic_warnings() {
7611 let array = "int a[8] = { [3] 7 };\n";
7612 let member = "struct s { int x; } v = { x: 7 };\n";
7613 for source in [array, member] {
7614 let result = run(&options(), source);
7615 assert!(!result.failed(), "{:?}", result.messages);
7616 assert!(result.messages.is_empty(), "nothing to say: {:?}", result.messages);
7617 }
7618
7619 let mut asked = options();
7620 asked.pedantic = true;
7621 assert_eq!(
7622 run(&asked, array).messages,
7623 ["/main.c:1:18: warning: obsolete designator, write `[i] =` instead [E0415]"]
7624 );
7625 assert_eq!(
7626 run(&asked, member).messages,
7627 ["/main.c:1:27: warning: obsolete designator, write `.field =` instead [E0413]"]
7628 );
7629 }
7630
7631 #[test]
7638 fn a_type_is_refused_when_it_passes_the_largest_object_and_not_before() {
7639 let text = ir(concat!(
7640 "struct huge_struct { short buf[(1L << 62) - 256]; int a, b, c, d; };\n",
7641 "struct brim { char buf[9223372036854775807L]; };\n",
7642 "struct bitty { char buf[9223372036854775800L]; int x : 1; };\n",
7643 "unsigned long h = sizeof(struct huge_struct);\n",
7644 "unsigned long b = sizeof(struct brim);\n",
7645 "unsigned long y = sizeof(struct bitty);\n",
7646 ));
7647 assert!(text.contains("global @h : i64 = 9223372036854775312,"), "{text}");
7648 assert!(text.contains("global @b : i64 = 9223372036854775807,"), "{text}");
7649 assert!(text.contains("global @y : i64 = 9223372036854775804,"), "{text}");
7650
7651 let mut opts = options();
7652 opts.emit = EmitKind::Ir;
7653 let over = "struct over { char buf[9223372036854775800L]; char x[8]; };\n";
7654 let message = "/main.c:1:1: error: type 'struct over' is too large [E0560]";
7655 assert_eq!(run(&opts, over).messages, [message]);
7656 let array = "struct wide { short buf[1L << 62]; };\n";
7657 let message = "/main.c:1:25: error: size of array 'buf' exceeds \
7658 maximum object size '9223372036854775807' [E0537]";
7659 assert_eq!(run(&opts, array).messages[0], message);
7660 }
7661
7662 fn compile_bytes(source: &[u8]) -> Compiled {
7667 let mut opts = options();
7668 opts.emit = EmitKind::Ir;
7669 let mut fs = MemoryFileSystem::new();
7670 fs.insert("/main.c", source.to_vec());
7671 compile(&opts, "/main.c", &fs)
7672 }
7673
7674 #[test]
7681 fn a_byte_that_is_not_a_character_is_kept_in_a_literal_and_refused_outside_one() {
7682 let mut source = b"char s[] = \"a".to_vec();
7683 source.push(0xff);
7684 source.extend_from_slice(b"b\";\nchar c = '");
7685 source.push(0xff);
7686 source.extend_from_slice(b"';\n");
7687 let result = compile_bytes(&source);
7688 assert_eq!(result.messages, Vec::<String>::new(), "a raw byte in a literal is that byte");
7689 assert!(result.text().contains(r#"bytes "a\ffb\00""#), "{}", result.text());
7690 assert!(result.text().contains("global @c : i8 = -1,"), "{}", result.text());
7692
7693 let mut stray = b"int a".to_vec();
7694 stray.push(0xff);
7695 stray.extend_from_slice(b" = 1;\n");
7696 let result = compile_bytes(&stray);
7697 assert!(
7698 result.messages.iter().any(|m| m.contains("source is not valid UTF-8 here")),
7699 "{:?}",
7700 result.messages
7701 );
7702 }
7703
7704 #[test]
7705 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
7706 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
7707 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
7708 let expected = "\
7709func @add(i32, i32) -> i32, linkage(external) {
7710block0(%0: i32, %1: i32):
7711 %2 = add.nsw %0, %1
7712 return %2
7713}
7714";
7715 assert!(text.contains(expected), "{text}");
7716 }
7717
7718 #[test]
7719 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
7720 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
7721 assert!(!text.contains("alloca"), "{text}");
7722 assert!(!text.contains("load"), "{text}");
7723 assert!(!text.contains("store"), "{text}");
7724 }
7725
7726 #[test]
7727 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
7728 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
7729 let expected = "\
7730block0:
7731 %0 = alloca, size 4, align 4
7732 %1 = iconst.i32 1
7733 store %1 -> %0, align 4, tbaa !1
7734 %2 = call @g(%0) : (ptr) -> i32
7735 return %2
7736";
7737 assert_eq!(text, expected);
7738 }
7739
7740 #[test]
7741 fn a_loop_carries_what_it_changes_as_block_parameters() {
7742 let text = body(
7745 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
7746 return total;\n}\n",
7747 );
7748 assert!(!text.contains("alloca"), "{text}");
7749 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
7750 assert!(text.contains("jump block1("), "{text}");
7751 }
7752
7753 #[test]
7754 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
7755 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
7756 assert!(text.contains("icmp slt %0, %1"), "{text}");
7757 assert!(!text.contains("zext"), "{text}");
7758 }
7759
7760 #[test]
7761 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
7762 let text = body("int f(int a, int b) { return a && b; }\n");
7763 let expected = "\
7764block0(%0: i32, %1: i32):
7765 %2 = iconst.i32 0
7766 %3 = icmp ne %0, %2
7767 %4 = iconst.i1 0
7768 br_if %3, block1, block2(%4)
7769
7770block1:
7771 %5 = iconst.i32 0
7772 %6 = icmp ne %1, %5
7773 jump block2(%6)
7774
7775block2(%7: i1):
7776 %8 = zext.i32 %7
7777 return %8
7778";
7779 assert_eq!(text, expected);
7780 }
7781
7782 #[test]
7783 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
7784 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
7785 assert!(!text.contains("block3"), "{text}");
7788 assert!(!text.contains("iconst.i32 3"), "{text}");
7789 }
7790
7791 #[test]
7792 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
7793 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
7794 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
7795 assert!(body("int f(void) { }\n").contains("unreachable"));
7796 }
7797
7798 #[test]
7799 fn a_structure_is_copied_rather_than_held_in_a_value() {
7800 let text = body(
7801 "struct point { int x, y; };\n\
7802 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
7803 );
7804 assert!(text.contains("memcpy"), "{text}");
7805 }
7806
7807 #[test]
7808 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
7809 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
7810 assert!(text.contains("memset"), "{text}");
7811 }
7812
7813 #[test]
7814 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
7815 let text = body(
7816 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
7817 default: r = 4; } return r; }\n",
7818 );
7819 let expected = "\
7820block0(%0: i32):
7821 %1 = iconst.i32 0
7822 switch %0, block1, [1 => block2, 2 => block3(%1)]
7823
7824block1:
7825 %2 = iconst.i32 4
7826 jump block4(%2)
7827
7828block2:
7829 %3 = iconst.i32 1
7830 jump block3(%3)
7831
7832block3(%4: i32):
7833 %5 = iconst.i32 2
7834 %6 = add.nsw %4, %5
7835 jump block4(%6)
7836
7837block4(%7: i32):
7838 return %7
7839";
7840 assert_eq!(text, expected);
7841 }
7842
7843 #[test]
7844 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
7845 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
7848 assert!(text.contains("%2 = sub %0, %1"), "{text}");
7849 assert!(text.contains("icmp ule"), "{text}");
7850 assert!(!text.contains("switch"), "{text}");
7851 }
7852
7853 #[test]
7854 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
7855 let text = body(
7856 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
7857 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
7858 );
7859 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
7862 assert!(text.contains("block5:\n jump block7("), "{text}");
7863 assert!(text.contains("block6:\n jump block8("), "{text}");
7864 }
7865
7866 #[test]
7867 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
7868 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
7869 }
7870
7871 #[test]
7872 fn a_label_a_loop_is_only_entered_through_builds_the_loop_around_it() {
7873 let text = body(
7878 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
7879 return n; }\n",
7880 );
7881 assert!(text.contains("switch %0, block1(%1), [1 => block2, 2 => block3(%1)]"), "{text}");
7884 assert!(text.contains("block3(%3: i32):\n %4 = iconst.i32 1"), "{text}");
7885 assert!(text.contains("block4:\n jump block3("), "{text}");
7886 }
7887
7888 #[test]
7889 fn a_goto_into_a_loop_body_enters_it_without_the_test() {
7890 let text = body("int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n");
7893 assert!(text.starts_with("block0(%0: i32, %1: i32):\n jump block1(%1)"), "{text}");
7894 assert!(text.contains("block1(%2: i32):\n %3 = iconst.i32 1"), "{text}");
7895 assert!(text.contains("br_if %6, block2, block3"), "{text}");
7896 }
7897
7898 #[test]
7899 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
7900 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
7901 assert!(!text.contains("alloca"), "{text}");
7905 assert!(text.contains("block2(%4: i32):\n return %4"), "{text}");
7906 assert_eq!(text.matches("jump block2(").count(), 2, "{text}");
7907 }
7908
7909 #[test]
7910 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
7911 let text =
7912 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
7913 assert!(!text.contains("alloca"), "{text}");
7914 assert!(text.contains("block1(%2: i32):"), "{text}");
7915 assert!(text.contains("jump block1(%5)"), "{text}");
7916 }
7917
7918 #[test]
7919 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
7920 assert_eq!(
7923 body("int f(int x) { return x; spare: return 0; }\n"),
7924 "block0(%0: i32):\n return %0\n"
7925 );
7926 }
7927
7928 #[test]
7929 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
7930 let text = body(
7931 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
7932 );
7933 assert_eq!(
7936 text,
7937 "\
7938block0(%0: ptr):
7939 %1 = load.i8 %0, align 1
7940 %2 = iconst.i8 3
7941 %3 = ashr %1, %2
7942 %4 = sext.i32 %3
7943 return %4
7944"
7945 );
7946 }
7947
7948 #[test]
7949 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
7950 let text =
7954 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
7955 assert_eq!(
7956 text,
7957 "\
7958block0(%0: ptr, %1: i32):
7959 %2 = iconst.i32 16777215
7960 %3 = and %1, %2
7961 %4 = trunc.i16 %3
7962 store %4 -> %0, align 2
7963 %5 = iconst.i32 16
7964 %6 = lshr %3, %5
7965 %7 = trunc.i8 %6
7966 %8 = iconst.i64 2
7967 %9 = ptr_add %0, %8
7968 store %7 -> %9, align 1
7969 return
7970"
7971 );
7972 }
7973
7974 #[test]
7975 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
7976 let text =
7977 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
7978 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
7981 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
7982 }
7983
7984 #[test]
7985 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
7986 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
7989 assert_eq!(text.matches("ashr").count(), 0, "{text}");
7990 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
7991 }
7992
7993 #[test]
7994 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
7995 let text = body(
7999 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
8000 );
8001 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
8002 }
8003
8004 #[test]
8005 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
8006 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
8009 assert!(
8010 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
8011 "{text}"
8012 );
8013 }
8014
8015 #[test]
8016 fn an_initialized_flexible_array_member_makes_the_object_larger_than_its_type() {
8017 let text = ir(concat!(
8022 "struct a { int i; int j[]; } x = { 1, { 2, 0, 2, 3 } };\n",
8023 "struct b { char c; char p[]; } y = { 'o', \"wx\" };\n",
8024 "struct c { char c; char p[]; } z = { '9', { 'e', 'b' } };\n",
8025 "char s[2] = \"hi\";\n",
8026 ));
8027 assert!(
8028 text.contains("global @x : bytes 20 = { i32 1, i32 2, i32 0, i32 2, i32 3 }"),
8029 "{text}"
8030 );
8031 assert!(text.contains("global @y : bytes 4 = { i8 111, bytes \"wx\\00\" }"), "{text}");
8032 assert!(text.contains("global @z : bytes 3 = { i8 57, i8 101, i8 98 }"), "{text}");
8033 assert!(text.contains("global @s : bytes 2 = { bytes \"hi\" }"), "{text}");
8036 }
8037
8038 #[test]
8039 fn a_definition_takes_a_parameter_it_left_unnamed() {
8040 let text = ir("int f(int a, int) { return a; }\n");
8044 assert!(text.contains("func @f(i32, i32) -> i32"), "{text}");
8045 assert!(text.contains("block0(%0: i32, %1: i32):"), "{text}");
8046
8047 let text = ir("int g(int, int n) { return n; }\n");
8050 assert!(text.contains("block0(%0: i32, %1: i32):\n return %1\n"), "{text}");
8051 }
8052
8053 #[test]
8054 fn an_assignment_of_a_structure_is_the_object_it_wrote() {
8055 let text = body(concat!(
8060 "struct s { int f; int g; };\n",
8061 "void h(struct s *a, struct s *c, struct s *d, struct s *e)\n",
8062 "{ *d = *e = a[0] = *c; }\n",
8063 ));
8064 assert_eq!(text.matches("memcpy").count(), 3, "{text}");
8065 assert!(text.contains("memcpy %8, %1, size 8, align 4\n"), "{text}");
8066 assert!(text.contains("memcpy %3, %8, size 8, align 4\n"), "{text}");
8067 assert!(text.contains("memcpy %2, %3, size 8, align 4\n"), "{text}");
8068 }
8069
8070 #[test]
8071 fn a_string_literal_stops_at_the_end_of_the_array_it_is_filling() {
8072 let mut opts = options();
8077 opts.emit = EmitKind::Ir;
8078 let result = run(
8079 &opts,
8080 concat!(
8081 "const char a[2][3] = { \"1234\", \"xyz\" };\n",
8082 "static const char b[3][5] = { \"12345\", \"678\", \"9\" };\n",
8083 "union u { struct { char x[4]; char y[4]; }; struct { char z[8]; }; };\n",
8084 "const union u c = { { \"1234\", \"567\" } };\n",
8085 ),
8086 );
8087 let text = result.text();
8088 assert_eq!(
8089 result.messages,
8090 ["/main.c:1:24: warning: initializer-string for array of 'const char' is too long \
8091 (5 chars into 3 available) [E0637]"]
8092 );
8093 assert!(text.contains("global @a : bytes 6 = { bytes \"123\", bytes \"xyz\" }"), "{text}");
8094 assert!(
8095 text.contains(
8096 "global @b : bytes 15 = { bytes \"12345\", bytes \"678\\00\", zero 1, \
8097 bytes \"9\\00\", zero 3 }"
8098 ),
8099 "{text}"
8100 );
8101 assert!(
8104 text.contains("global @c : bytes 8 = { bytes \"1234\", bytes \"567\\00\" }"),
8105 "{text}"
8106 );
8107 }
8108
8109 #[test]
8110 fn a_cast_of_a_record_to_its_own_type_is_the_object_that_was_cast() {
8111 let text = body(concat!(
8115 "struct s { int a, b; };\nstruct v { struct s s; int t; };\n",
8116 "void g(struct v *);\n",
8117 "void f(struct s *p) { struct v w = { (struct s)*p, 5 }; g(&w); }\n",
8118 ));
8119 assert_eq!(text.matches("memcpy").count(), 1, "{text}");
8120 }
8121
8122 #[test]
8123 fn a_compound_literal_read_in_a_static_initializer_lays_its_bytes_into_the_image() {
8124 let text = ir(concat!(
8129 "struct s { int x; };\n",
8130 "struct t { struct s s; int o; } a = { (struct s){ 2 }, 3 };\n",
8131 "int n = (int){ 7 };\n",
8132 "struct u { struct s p; struct s q; } b = { (struct s){ 1 }, (struct s){ } };\n",
8133 ));
8134 assert!(text.contains("global @a : bytes 8 = { i32 2, i32 3 }"), "{text}");
8135 assert!(text.contains("global @n : i32 = 7,"), "{text}");
8136 assert!(text.contains("global @b : bytes 8 = { i32 1, zero 4 }"), "{text}");
8139 }
8140
8141 #[test]
8142 fn the_address_of_a_compound_literal_asks_for_the_object_it_points_at() {
8143 let text = ir("struct s { int x; };\nstruct s *q = &(struct s){ 9 };\n");
8147 assert!(text.contains("global @.Lanon.0 : i32 = 9, align 4, linkage(internal)"), "{text}");
8148 assert!(text.contains("global @q : bytes 8 = { addr.8 @.Lanon.0 }"), "{text}");
8149 }
8150
8151 #[test]
8152 fn an_object_of_no_size_at_all_has_an_image_with_nothing_in_it() {
8153 let text = ir("unsigned char foo[1][0];\n");
8157 assert!(text.contains("global @foo : bytes 0 = {}, align 1"), "{text}");
8158 }
8159
8160 #[test]
8161 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
8162 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
8165 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
8166 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
8167 }
8168
8169 #[test]
8170 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
8171 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
8175 assert!(
8176 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
8177 "{text}"
8178 );
8179 }
8180
8181 #[test]
8182 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
8183 let text = body(
8188 "\
8189struct s { int a, b; };
8190struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
8191",
8192 );
8193 assert!(text.contains("block3(%7: ptr)"), "{text}");
8195 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
8196 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
8197 }
8198
8199 #[test]
8207 fn the_left_side_of_a_conditional_with_no_middle_is_evaluated_once() {
8208 let text = body("int f(int i) { return ++i ?: 10; }\n");
8209 assert!(text.contains("jump block3(%2)"), "the arm is the value that was tested: {text}");
8210 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
8211
8212 let text = body("long f(int i) { return ++i ?: 10L; }\n");
8215 assert!(text.contains("%5 = sext.i64 %2"), "the arm widens what was tested: {text}");
8216 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
8217
8218 let text = body("int g(void);\nint f(void) { return g() ?: 10; }\n");
8220 assert_eq!(text.matches("call @g").count(), 1, "called once: {text}");
8221
8222 let text = body("int f(int i) { return ++i ? ++i : 10; }\n");
8225 assert_eq!(text.matches("add.nsw").count(), 2, "incremented twice: {text}");
8226 }
8227
8228 #[test]
8229 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
8230 let text = ir("\
8234struct pair { int a, b; };
8235struct pair make(int a, int b);
8236struct pair twice(struct pair p) { return make(p.a, p.b); }
8237");
8238 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
8239 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
8240 }
8241
8242 #[test]
8243 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
8244 let text = ir("\
8248struct big { double v[8]; };
8249struct big grow(struct big b);
8250struct big twice(struct big b) { return grow(grow(b)); }
8251");
8252 assert!(
8253 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
8254 "{text}"
8255 );
8256 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
8257 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
8260 }
8261
8262 #[test]
8263 fn a_structure_passed_to_a_variadic_function_says_so_at_the_call() {
8264 let text = ir("\
8269struct big { double v[8]; };
8270struct pair { int a, b; };
8271int p(const char *, ...);
8272int f(struct big b, struct pair q) { return p(\"\", 1, b, q); }
8273");
8274 assert!(
8275 text.contains("call @p(%4, %5, %2 byval(64, align 8), %6) : (ptr, ...) -> i32"),
8276 "{text}"
8277 );
8278 }
8279
8280 #[test]
8281 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
8282 let body = body(
8285 "\
8286struct pair { int a, b; };
8287struct pair make(int a, int b);
8288int second(void) { return make(1, 2).b; }
8289",
8290 );
8291 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
8292 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
8293 }
8294
8295 #[test]
8296 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
8297 let source = "\
8301struct hfa { float x, y, z; };
8302int take(struct hfa h);
8303int give(struct hfa h) { return take(h); }
8304";
8305 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
8306 let mut opts = options();
8307 opts.emit = EmitKind::Ir;
8308 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
8309 let result = run(&opts, source);
8310 assert_eq!(result.messages, Vec::<String>::new());
8311 assert!(result.text().contains("func @take(f32, f32, f32) -> i32"), "{}", result.text());
8312 }
8313
8314 #[test]
8315 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
8316 let source = "\
8319int use(int *);
8320void f(int n) {
8321 {
8322 int a[n];
8323 use(a);
8324 }
8325 use(0);
8326}
8327";
8328 let body = body(source);
8329 assert!(body.contains("mul.nsw"), "{body}");
8330 assert!(body.contains("stacksave"), "{body}");
8331 assert!(body.contains("alloca %"), "{body}");
8332 assert!(body.contains("stackrestore"), "{body}");
8333 }
8334
8335 #[test]
8336 fn a_goto_out_of_the_scope_of_one_gives_its_stack_back_on_the_way() {
8337 let source = "\
8342int use(int *);
8343int f(int n) {
8344 {
8345 int a[n];
8346 if (use(a)) goto out;
8347 use(0);
8348 }
8349out:
8350 return 0;
8351}
8352";
8353 let body = body(source);
8354 assert_eq!(body.matches("stackrestore").count(), 2, "{body}");
8356 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
8357 assert!(after.starts_with(" %4\n jump block"), "{body}");
8358 }
8359
8360 #[test]
8361 fn a_goto_to_a_label_the_array_is_still_alive_at_leaves_the_stack_alone() {
8362 let source = "\
8366int use(int *);
8367int f(int n) {
8368 int a[n];
8369again:
8370 if (use(a)) goto again;
8371 return 0;
8372}
8373";
8374 let body = body(source);
8375 assert!(body.contains("stacksave"), "{body}");
8376 assert!(!body.contains("stackrestore"), "{body}");
8377 }
8378
8379 #[test]
8380 fn a_goto_back_to_a_label_in_front_of_one_gives_it_back_every_time_round() {
8381 let source = "\
8386int use(int *);
8387int f(int n) {
8388again:
8389 {
8390 int a[n];
8391 if (use(a)) goto again;
8392 }
8393 return 0;
8394}
8395";
8396 let body = body(source);
8397 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
8398 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
8399 assert!(after.starts_with(" %4\n jump block1\n"), "{body}");
8400 }
8401
8402 #[test]
8403 fn the_head_of_a_for_loop_is_a_scope_that_closes_where_the_loop_is_left() {
8404 let source = "\
8410int f(void);
8411void t(void) {
8412 int count = 10;
8413 for (; count--;) {
8414 int b[f()];
8415 int i;
8416 for (i = 0; i < f(); i++) {
8417 b[i] = count;
8418 }
8419 }
8420}
8421";
8422 let body = body(source);
8423 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
8427 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
8428 let next = after.split("\n\n").next().expect("the block the restore is in");
8431 assert!(next.contains("jump block1("), "{body}");
8432 }
8433
8434 #[test]
8435 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
8436 let source = "\
8439unsigned long f(int n) {
8440 int a[n];
8441 n = 0;
8442 return sizeof a;
8443}
8444";
8445 let body = body(source);
8446 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
8448 }
8449
8450 #[test]
8451 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
8452 let source = "\
8455int use(int);
8456int f(int x) {
8457 return ({
8458 int t = use(x);
8459 t * t;
8460 });
8461}
8462";
8463 let expected = "\
8464block0(%0: i32):
8465 %1 = call @use(%0) : (i32) -> i32
8466 %2 = mul.nsw %1, %1
8467 return %2
8468";
8469 assert_eq!(body(source), expected);
8470 }
8471
8472 #[test]
8473 fn a_comma_whose_value_is_an_object_names_the_object_the_right_side_named() {
8474 let source = "\
8478struct pair { int a, b; };
8479void bail(void);
8480int f(struct pair p) {
8481 return (bail(), p).b;
8482}
8483";
8484 let expected = "\
8485block0(%0: i64):
8486 %1 = alloca, size 8, align 4
8487 store %0 -> %1, align 4
8488 call @bail() : ()
8489 %2 = iconst.i64 4
8490 %3 = ptr_add %1, %2
8491 %4 = load.i32 %3, align 4, tbaa !1
8492 return %4
8493";
8494 assert_eq!(body(source), expected);
8495 }
8496
8497 #[test]
8498 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
8499 let source = "int f(int x) { return ({ return x; 0; }); }\n";
8503 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
8504 }
8505
8506 #[test]
8507 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
8508 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
8512 let expected = "\
8513block0(%0: ptr):
8514 %1 = va_arg.f64 %0
8515 %2 = va_arg.f64 %0
8516 %3 = fadd %1, %2
8517 return %3
8518";
8519 assert_eq!(body(source), expected);
8520 }
8521
8522 #[test]
8523 fn one_that_reads_a_structure_answers_where_the_object_is() {
8524 let source = "\
8538struct s { int a; long b; };
8539long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.b; }
8540";
8541 let expected = "\
8542block0(%0: ptr):
8543 %1 = alloca, size 16, align 16
8544 %2 = va_object %0, size 16, align 8, in(int 8 at 0, int 8 at 8)
8545 memcpy %1, %2, size 16, align 8
8546 %3 = iconst.i64 8
8547 %4 = ptr_add %1, %3
8548 %5 = load.i64 %4, align 8, tbaa !1
8549 return %5
8550";
8551 assert_eq!(body(source), expected);
8552 }
8553
8554 #[test]
8558 fn the_classification_says_which_registers_the_object_arrived_in() {
8559 let source = "\
8560struct s { double a; double b; };
8561double f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a; }
8562";
8563 assert!(
8564 body(source)
8565 .contains("va_object %0, size 16, align 8, in(float f64 at 0, float f64 at 8)"),
8566 "{}",
8567 body(source)
8568 );
8569
8570 let big = "\
8571struct s { long a[4]; };
8572long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a[0]; }
8573";
8574 assert!(body(big).contains("va_object %0, size 32, align 8\n"), "{}", body(big));
8575 }
8576
8577 #[test]
8578 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
8579 let source = "\
8583int f(int c) {
8584 void *p = c ? &&one : &&two;
8585 goto *p;
8586one:
8587 return 1;
8588two:
8589 return 2;
8590}
8591";
8592 let expected = "\
8593block0(%0: i32):
8594 %1 = iconst.i32 0
8595 %2 = icmp ne %0, %1
8596 br_if %2, block1, block2
8597
8598block1:
8599 %3 = block_addr block3
8600 jump block4(%3)
8601
8602block2:
8603 %4 = block_addr block5
8604 jump block4(%4)
8605
8606block3:
8607 %5 = iconst.i32 1
8608 return %5
8609
8610block4(%6: ptr):
8611 indirect_br %6, block3, block5
8612
8613block5:
8614 %7 = iconst.i32 2
8615 return %7
8616";
8617 assert_eq!(body(source), expected);
8618 }
8619
8620 fn dispatch(labels: usize) -> String {
8623 let mask = labels - 1;
8624 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
8625 for index in 0..labels {
8626 source.push_str(&format!(" &&a{index},"));
8627 }
8628 source.push_str(" };\n\tint w = n, x = n + 1, y = n + 2, z = n + 3;\n");
8629 source.push_str(&format!("\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
8630 for index in 0..labels {
8631 let step = match index % 4 {
8632 0 => "w += x;",
8633 1 => "x += y;",
8634 2 => "y += z;",
8635 _ => "z += w;",
8636 };
8637 source.push_str(&format!("a{index}:\n\t{step}\n"));
8638 source.push_str("\tif (--n <= 0) return w + x + y + z;\n");
8639 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
8640 }
8641 source.push_str("}\n");
8642 source
8643 }
8644
8645 fn in_front_of_the_jump(text: &str) -> usize {
8647 let (before, _) = text.split_once("\tjmp\t*%").expect("a jump through a register");
8648 before.lines().rev().take_while(|line| line.starts_with("\tmov")).count()
8649 }
8650
8651 #[test]
8661 fn a_jump_through_a_register_writes_what_it_carries_and_not_the_whole_table() {
8662 let small = in_front_of_the_jump(&asm(&dispatch(4)));
8663 let large = in_front_of_the_jump(&asm(&dispatch(32)));
8664 assert_eq!(small, large, "eight times the labels and the same values in hand");
8665 assert!(large <= 8, "the values the loop keeps, and not a set of them per label: {large}");
8666 }
8667
8668 fn crowded(labels: usize) -> String {
8671 const VALUES: usize = 24;
8672 let mask = labels - 1;
8673 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
8674 for index in 0..labels {
8675 source.push_str(&format!(" &&a{index},"));
8676 }
8677 source.push_str(" };\n\t");
8678 for value in 0..VALUES {
8679 source.push_str(&format!("int v{value} = n + {value}; "));
8680 }
8681 let sum: Vec<String> = (0..VALUES).map(|value| format!("v{value}")).collect();
8682 source.push_str(&format!("\n\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
8683 for index in 0..labels {
8684 let (to, from) = (index % VALUES, (index + 1) % VALUES);
8685 source.push_str(&format!("a{index}:\n\tv{to} += v{from};\n"));
8686 source.push_str(&format!("\tif (--n <= 0) return {};\n", sum.join(" + ")));
8687 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
8688 }
8689 source.push_str("}\n");
8690 source
8691 }
8692
8693 fn the_frame(text: &str) -> u64 {
8695 text.lines()
8696 .find_map(|line| {
8697 let (size, _) = line.strip_prefix("\tsubq\t$")?.split_once(", %rsp")?;
8698 size.parse().ok()
8699 })
8700 .expect("a function that opens a frame")
8701 }
8702
8703 #[test]
8712 fn a_frame_holds_what_is_wanted_at_once_and_not_a_slot_for_every_label() {
8713 let small = the_frame(&asm(&crowded(16)));
8714 let large = the_frame(&asm(&crowded(64)));
8715 assert_eq!(small, large, "four times the labels and the same values: {small}, {large}");
8716 }
8717
8718 #[test]
8727 fn a_template_that_names_its_own_registers_gets_the_ones_it_named() {
8728 let source = "void save(void *nlr) {
8729 __asm volatile (
8730 \"movq %%rsp, 32(%%rdi) \\n\"
8731 \"movq %%rbx, 40(%%rdi) \\n\"
8732 \"movq %%r12, 48(%%rdi) \\n\"
8733 : : \"D\" (nlr) : \"memory\");
8734}
8735";
8736 let text = asm(source);
8737 assert!(text.contains("\tmovq\t%rsp, 32(%rdi)\n"), "{text}");
8738 assert!(text.contains("\tmovq\t%rbx, 40(%rdi)\n"), "{text}");
8739 assert!(text.contains("\tmovq\t%r12, 48(%rdi)\n"), "{text}");
8740 }
8741
8742 #[test]
8743 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
8744 let source = "void **next(void);
8747void f(void) { goto *next(); }
8748";
8749 let expected = "\
8750block0:
8751 %0 = call @next() : () -> ptr
8752 unreachable
8753";
8754 assert_eq!(body(source), expected);
8755 }
8756
8757 #[test]
8758 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
8759 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
8762 let expected = "\
8763block0:
8764 inline_asm.volatile \"mfence\", \"\", \"memory\"()
8765 return
8766";
8767 assert_eq!(body(source), expected);
8768 }
8769
8770 #[test]
8771 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
8772 let source = "\
8775int f(int x, int y) {
8776 int r;
8777 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
8778 return r + y;
8779}
8780";
8781 let expected = "\
8782block0(%0: i32, %1: i32):
8783 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
8784 %4 = add.nsw %2, %3
8785 return %4
8786";
8787 assert_eq!(body(source), expected);
8788 }
8789
8790 #[test]
8791 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
8792 let source = "\
8797struct pair { int a, b; };
8798int f(int x) {
8799 int slot = x;
8800 struct pair p = { x, x };
8801 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
8802 return slot + p.a;
8803}
8804";
8805 let text = body(source);
8806 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
8807 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
8808 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
8809 }
8810
8811 #[test]
8812 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
8813 let source = "\
8818int f(int x) {
8819 int r = 7;
8820 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
8821 return r;
8822away:
8823 return r;
8824}
8825";
8826 let expected = "\
8827block0(%0: i32):
8828 %1 = iconst.i32 7
8829 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
8830
8831block1:
8832 return %2
8833
8834block2:
8835 return %1
8836";
8837 assert_eq!(body(source), expected);
8838 }
8839
8840 #[test]
8841 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
8842 let mut opts = options();
8846 opts.emit = EmitKind::Ir;
8847 for (source, expected) in [
8848 (
8849 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
8850 "output operand constraint lacks '='",
8851 ),
8852 (
8853 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
8854 "lvalue required in 'asm' statement",
8855 ),
8856 (
8857 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
8858 "read-only variable 'g' used as 'asm' output",
8859 ),
8860 (
8861 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
8862 "input operand constraint contains '='",
8863 ),
8864 (
8865 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
8866 "memory input 0 is not directly addressable",
8867 ),
8868 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
8869 (
8870 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
8871 "duplicate asm operand name 'a'",
8872 ),
8873 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
8874 ] {
8875 let result = run(&opts, source);
8876 assert!(result.failed(), "expected this to be reported:\n{source}");
8877 assert!(
8878 result.messages.iter().any(|m| m.contains(expected)),
8879 "{expected}\n{:?}",
8880 result.messages
8881 );
8882 }
8883 }
8884
8885 #[test]
8890 fn an_asm_at_file_scope_that_is_directives_becomes_the_objects_it_defines() {
8891 let text = ir(concat!(
8892 "__asm__(\n",
8893 " \".section .rodata\\n\"\n",
8894 " \".globl first\\n\"\n",
8895 " \".balign 8\\n\"\n",
8896 " \"first:\\n\"\n",
8897 " \".long 1\\n\"\n",
8898 " \".long 2\\n\"\n",
8899 " \".globl last\\n\"\n",
8900 " \"last:\\n\"\n",
8901 " \".quad last - first\\n\");\n",
8902 "extern const int first[];\n",
8903 "extern const long last;\n",
8904 ));
8905 assert!(text.contains("global @first : bytes 8 = { i32 1, i32 2 }, align 8"), "{text}");
8906 assert!(text.contains("global @last : i64 = 8"), "{text}");
8907 }
8908
8909 #[test]
8913 fn a_name_an_asm_at_file_scope_defined_is_not_undone_by_a_declaration_of_it() {
8914 let text = ir(concat!(
8915 "__asm__(\".data\\n.globl counter\\ncounter:\\n.long 7\\n\");\n",
8916 "extern int counter;\n",
8917 "int read(void) { return counter; }\n",
8918 ));
8919 assert!(text.contains("global @counter : i32 = 7"), "{text}");
8920 }
8921
8922 #[test]
8927 fn bytes_under_no_label_at_file_scope_are_a_global_in_front_of_the_label() {
8928 let text = ir(concat!(
8929 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n662:\\n",
8930 ".pushsection .data.ignore\\n.byte 7\\n.popsection\\n.byte 662b - 661b\\n\");\n",
8931 "extern unsigned char stuff[];\n",
8932 "int read(void) { return stuff[0]; }\n",
8933 ));
8934 let under = text.find("global @.Lasm.0 : i8 = 41").expect(&text);
8935 let named = text.find("global @stuff : i8 = 42").expect(&text);
8936 assert!(under < named, "the bytes under no label come first: {text}");
8937 assert!(text.contains("global @.Lasm.1 : i8 = 7, align 1, linkage(internal), section"));
8938 let after = text.find("global @.Lasm.2 : i8 = 1").expect(&text);
8943 assert!(named < after, "{text}");
8944 }
8945
8946 #[test]
8951 fn a_distance_from_here_at_file_scope_is_a_hole_naming_the_global_it_measures_to() {
8952 let text = ir(concat!(
8953 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n",
8954 ".pushsection .data.ignore\\n.long 661b - .\\n.popsection\\n\");\n",
8955 "extern unsigned char stuff[];\n",
8956 "int read(void) { return stuff[0]; }\n",
8957 ));
8958 assert!(text.contains("global @.Lasm.1 : bytes 4 = { away.4 @stuff }"), "{text}");
8962 }
8963
8964 #[test]
8969 fn a_set_at_file_scope_is_a_second_name_for_what_it_names() {
8970 let text = ir(concat!(
8971 "void base(void) {}\n",
8972 "__asm__(\".weak one\\n.set one, base\");\n",
8973 "__asm__(\".globl two\\n.set two, base\");\n",
8974 "__asm__(\".set three, base\");\n",
8975 "void three(void) {}\n",
8976 ));
8977 assert!(text.contains("alias @one = @base, linkage(weak)"), "{text}");
8978 assert!(text.contains("alias @two = @base"), "{text}");
8979 assert!(!text.contains("alias @three"), "a definition of the name wins: {text}");
8980 assert!(text.contains("func @three"), "{text}");
8981 }
8982
8983 #[test]
8988 fn a_set_of_a_name_this_file_does_not_define_says_so() {
8989 let messages = errors("__asm__(\".set here, elsewhere\");\n");
8990 assert!(
8991 messages
8992 .iter()
8993 .any(|m| m.contains("'here' is aliased to undefined symbol 'elsewhere'")
8994 && m.contains("E0697")),
8995 "{messages:?}"
8996 );
8997 }
8998
8999 #[test]
9002 fn an_incbin_at_file_scope_is_the_bytes_of_the_file_it_names() {
9003 let mut opts = options();
9004 opts.emit = EmitKind::Ir;
9005 let mut fs = MemoryFileSystem::new();
9006 fs.insert(
9007 "/main.c",
9008 b"__asm__(\".data\\n.globl blob\\nblob:\\n.incbin \\\"seed\\\"\\n\");\n".to_vec(),
9009 );
9010 fs.insert("seed", b"hi".to_vec());
9011 let result = compile(&opts, "/main.c", &fs);
9012 assert_eq!(result.messages, Vec::<String>::new());
9013 let text = result.text();
9014 assert!(text.contains("global @blob : bytes 2 = { bytes \"hi\" }"), "{text}");
9015 }
9016
9017 #[test]
9020 fn an_incbin_naming_a_file_that_is_not_there_says_which_file() {
9021 let messages = errors("__asm__(\".data\\nb:\\n.incbin \\\"nowhere\\\"\\n\");\n");
9022 assert!(
9023 messages
9024 .iter()
9025 .any(|m| m.contains("cannot open 'nowhere' for reading") && m.contains("E0702")),
9026 "{messages:?}"
9027 );
9028 }
9029
9030 #[test]
9033 fn an_instruction_in_an_asm_at_file_scope_is_refused_rather_than_ignored() {
9034 for source in [
9035 "__asm__(\".text\\n.globl f\\nf:\\n ret\\n\");\n",
9036 "__asm__(\".data\\n.set alias, 4\\n\");\n",
9037 ] {
9038 let messages = errors(source);
9039 assert!(
9040 messages
9041 .iter()
9042 .any(|m| m.contains("not supported yet")
9043 && m.contains("in an `asm` at file scope")),
9044 "{source}\n{messages:?}"
9045 );
9046 }
9047 }
9048
9049 #[test]
9059 fn a_naked_function_is_its_own_prologue_and_its_own_ending() {
9060 let text = asm(concat!(
9061 "unsigned nlr_push_tail(void *nlr);\n",
9062 "__attribute__((naked)) unsigned nlr_push(void *nlr) {\n",
9063 " __asm volatile(\n",
9064 " \"movq (%rsp), %rax\\n\"\n",
9065 " \"movq %rax, 16(%rdi)\\n\"\n",
9066 " \"movq %rbx, 40(%rdi)\\n\"\n",
9067 " \"jmp nlr_push_tail\\n\");\n",
9068 "}\n",
9069 ));
9070 assert!(text.contains("\tmovq\t(%rsp), %rax\n"), "{text}");
9071 assert!(text.contains("\tjmp\tnlr_push_tail\n"), "{text}");
9072 assert!(text.contains("\tud2\n"), "{text}");
9073 assert!(!text.contains("\tpushq\t"), "nothing is saved in front of it: {text}");
9074 assert!(!text.contains("\tret\n"), "the jump is where it ends: {text}");
9075 }
9076
9077 #[test]
9080 fn what_a_function_without_a_prologue_cannot_be_given_is_refused() {
9081 let mut opts = options();
9082 opts.emit = EmitKind::Asm;
9083 for (source, why) in [
9084 (
9085 "__attribute__((naked)) void f(void) { volatile long a[8]; a[0] = 1; }\n",
9086 "bytes of frame",
9087 ),
9088 (
9089 "__attribute__((naked)) void f(int n) { char a[n]; __asm(\"nop\" ::\"r\"(a)); }\n",
9090 "has no prologue to point a frame pointer at it with",
9091 ),
9092 ("void elsewhere(void); void f(void) { __asm(\"jmp elsewhere\"); }\n", "jumps out of"),
9093 ] {
9094 let result = run(&opts, source);
9095 assert!(result.failed(), "expected this to be refused:\n{source}");
9096 assert!(
9097 result.messages.iter().any(|message| message.contains(why)),
9098 "{:?}",
9099 result.messages
9100 );
9101 }
9102 }
9103
9104 #[test]
9105 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
9106 let mut opts = options();
9107 opts.emit = EmitKind::Ir;
9108 for source in [
9109 "int f(int n) { void *p = &&out; if (n) goto *p; { int a[n]; out: return 1; } }\n",
9110 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
9111 ] {
9112 let result = run(&opts, source);
9113 assert!(result.failed(), "expected this to be reported:\n{source}");
9114 assert!(
9115 result.messages.iter().any(|m| m.contains("not supported yet")),
9116 "{:?}",
9117 result.messages
9118 );
9119 }
9120 }
9121
9122 fn round_trip(source: &str) -> (String, String) {
9124 let printed = ir(source);
9125 let mut opts = options();
9126 opts.emit = EmitKind::Ir;
9127 let mut fs = MemoryFileSystem::new();
9128 fs.insert("/main.ir", printed.clone().into_bytes());
9129 let result = compile_ir(&opts, "/main.ir", &fs);
9130 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
9131 (printed, result.text().to_owned())
9132 }
9133
9134 #[test]
9135 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
9136 let (printed, again) = round_trip(
9140 "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",
9141 );
9142 assert_eq!(printed, again);
9143 }
9144
9145 #[test]
9146 fn ir_that_is_not_ir_says_which_line_stopped_it() {
9147 let mut opts = options();
9148 opts.emit = EmitKind::Ir;
9149 let mut fs = MemoryFileSystem::new();
9150 let text = "\
9151; ModuleID = 'a.c'
9152; format 0
9153target triple = \"x86_64-unknown-linux-gnu\"
9154target datalayout = \"e-p:64:64-i64:64-S128\"
9155
9156func @f(), linkage(external) {
9157block0:
9158 frobnicate
9159}
9160";
9161 fs.insert("/main.ir", text.as_bytes().to_vec());
9162 let result = compile_ir(&opts, "/main.ir", &fs);
9163 assert!(result.failed());
9164 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
9165 }
9166
9167 #[test]
9168 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
9169 let mut opts = options();
9172 opts.emit = EmitKind::Ir;
9173 let mut fs = MemoryFileSystem::new();
9174 let text = "\
9175; ModuleID = 'a.c'
9176; format 0
9177target triple = \"x86_64-unknown-linux-gnu\"
9178target datalayout = \"e-p:64:64-i64:64-S128\"
9179
9180func @f(), linkage(external) {
9181block0:
9182 %0 = iconst.i32 1
9183 return %0
9184}
9185";
9186 fs.insert("/main.ir", text.as_bytes().to_vec());
9187 let result = compile_ir(&opts, "/main.ir", &fs);
9188 assert!(result.failed());
9189 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
9190 }
9191
9192 #[test]
9193 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
9194 let mut fs = MemoryFileSystem::new();
9196 fs.insert("/main.ir", Vec::new());
9197 let result = compile_ir(&options(), "/main.ir", &fs);
9198 assert!(result.failed());
9199 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
9200 }
9201
9202 #[test]
9203 fn the_printed_ir_reads_back_as_the_same_module() {
9204 let text = ir("\
9207struct point { int x, y; };
9208static const char greeting[] = \"hi\";
9209int table[4] = { 1, 2, 3 };
9210int puts(const char *);
9211double half(double x) { return x / 2.0; }
9212int f(int n) {
9213 int total = 0;
9214 for (int i = 0; i < n; i++) {
9215 if (i == 3) continue;
9216 total += table[i];
9217 }
9218 switch (n) {
9219 case 0: total = 1;
9220 case 1: total++; break;
9221 default: total = -total;
9222 }
9223 struct point p = { total, 1 };
9224 int *q = &p.y;
9225 puts(greeting);
9226 return p.x + *q;
9227}
9228int dispatch(int c) {
9229 void *p = c ? &&one : &&two;
9230 goto *p;
9231one:
9232 return 1;
9233two:
9234 return 2;
9235}
9236int assembly(int x, int *p) {
9237 int r;
9238 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
9239 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
9240 return r;
9241away:
9242 return 0;
9243}
9244");
9245 let mut names = Interner::new();
9246 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
9247 assert_eq!(rucc_ir::print(&module, &names), text);
9248 }
9249
9250 #[test]
9251 fn what_save_temps_keeps_is_the_text_that_was_compiled_and_the_assembly_that_was_assembled() {
9252 let mut opts = options();
9256 opts.emit = EmitKind::Object;
9257 opts.save_temps = rucc_session::SaveTemps::Object;
9258 let result = run(&opts, "#define N 2\nint a[N];\n");
9259 assert_eq!(result.messages, Vec::<String>::new());
9260 let text = result.temps.preprocessed.expect("the preprocessed text");
9261 assert!(text.contains("int a[2];"), "{text}");
9262 assert!(text.starts_with("# 1 \"/main.c\""), "{text}");
9263 let asm = result.temps.assembly.expect("the assembly");
9264 assert!(asm.contains("a:"), "{asm}");
9265 assert!(matches!(result.artifact, Artifact::Object { .. }), "{:?}", result.artifact);
9266 }
9267
9268 #[test]
9269 fn nothing_is_kept_unless_the_flag_asked_for_it() {
9270 let mut opts = options();
9273 opts.emit = EmitKind::Object;
9274 assert_eq!(run(&opts, "int a;\n").temps, Temps::default());
9275 }
9276
9277 #[test]
9278 fn a_compilation_that_stops_before_the_back_end_keeps_the_text_and_no_assembly() {
9279 let mut opts = options();
9282 opts.emit = EmitKind::Ir;
9283 opts.save_temps = rucc_session::SaveTemps::Cwd;
9284 let result = run(&opts, "int a;\n");
9285 assert!(result.temps.preprocessed.is_some());
9286 assert_eq!(result.temps.assembly, None);
9287 }
9288
9289 fn span(from: u64, len: u64, held: rucc_debug::Held) -> rucc_debug::Span {
9291 rucc_debug::Span { from, len, held }
9292 }
9293
9294 #[test]
9295 fn two_stretches_that_meet_and_agree_come_out_as_one() {
9296 let one = span(0, 4, rucc_debug::Held::Reg(3));
9297 let two = span(4, 4, rucc_debug::Held::Reg(3));
9298 assert_eq!(settle(vec![two, one]), vec![span(0, 8, rucc_debug::Held::Reg(3))]);
9299 }
9300
9301 #[test]
9302 fn a_stretch_another_starts_inside_and_disagrees_with_ends_where_the_other_starts() {
9303 let one = span(0, 8, rucc_debug::Held::Reg(3));
9304 let two = span(4, 8, rucc_debug::Held::Reg(4));
9305 let settled = settle(vec![one, two]);
9308 assert_eq!(
9309 settled,
9310 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 8, rucc_debug::Held::Reg(4))]
9311 );
9312 }
9313
9314 #[test]
9315 fn a_stretch_cut_by_one_that_ends_first_does_not_come_back_after_it() {
9316 let one = span(0, 16, rucc_debug::Held::Reg(3));
9319 let two = span(4, 4, rucc_debug::Held::Reg(4));
9320 assert_eq!(
9321 settle(vec![one, two]),
9322 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 4, rucc_debug::Held::Reg(4))]
9323 );
9324 }
9325
9326 #[test]
9327 fn a_stretch_inside_another_that_agrees_with_it_cuts_nothing() {
9328 let one = span(0, 16, rucc_debug::Held::Reg(3));
9329 let two = span(4, 4, rucc_debug::Held::Reg(3));
9330 assert_eq!(settle(vec![one, two]), vec![span(0, 16, rucc_debug::Held::Reg(3))]);
9331 }
9332
9333 #[test]
9334 fn a_stretch_two_others_disagree_over_the_whole_of_says_nothing_at_all() {
9335 let one = span(0, 8, rucc_debug::Held::Reg(3));
9336 let two = span(0, 8, rucc_debug::Held::Frame(-16));
9337 assert_eq!(settle(vec![one, two]), Vec::new());
9338 }
9339
9340 #[test]
9341 fn stretches_with_a_gap_between_them_keep_the_gap() {
9342 let one = span(0, 4, rucc_debug::Held::Reg(3));
9343 let two = span(16, 4, rucc_debug::Held::Reg(3));
9344 assert_eq!(settle(vec![one, two]), vec![one, two]);
9345 }
9346
9347 fn extent(len: usize) -> rucc_object::Extent {
9349 rucc_object::Extent {
9350 name: "f".to_owned(),
9351 start: 0,
9352 len,
9353 align: 1,
9354 binding: rucc_object::Binding::Global,
9355 visibility: rucc_object::Visibility::Default,
9356 patch: None,
9357 }
9358 }
9359
9360 fn row(at: usize, lo: u32, hi: u32) -> rucc_asm::Row {
9362 let span = Span::new(lo, hi);
9363 rucc_asm::Row { at, span, inst: None }
9364 }
9365
9366 #[test]
9367 fn a_row_ends_where_the_next_address_begins() {
9368 let rows = [row(0, 0, 1), row(4, 1, 2), row(10, 2, 3)];
9369 assert_eq!(ends(&extent(16), &rows), vec![4, 10, 16]);
9370 }
9371
9372 #[test]
9373 fn rows_sharing_an_address_all_end_where_the_next_address_begins() {
9374 let rows = [row(0, 0, 1), row(4, 1, 2), row(4, 2, 3), row(4, 3, 4)];
9377 assert_eq!(ends(&extent(12), &rows), vec![4, 12, 12, 12]);
9378 }
9379
9380 #[test]
9381 fn the_rows_of_a_scope_that_are_next_to_each_other_come_out_as_one_stretch() {
9382 let rows = [row(0, 0, 4), row(4, 10, 14), row(8, 14, 18), row(12, 40, 44)];
9383 let ends = ends(&extent(16), &rows);
9384 let scope = Span::new(8, 20);
9385 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 8 }]);
9386 }
9387
9388 #[test]
9389 fn a_scope_the_back_end_split_in_two_comes_out_as_two_stretches() {
9390 let rows = [row(0, 10, 14), row(4, 40, 44), row(8, 14, 18)];
9391 let ends = ends(&extent(12), &rows);
9392 let scope = Span::new(8, 20);
9393 let over = spread(scope, &ends, &rows);
9394 assert_eq!(
9395 over,
9396 vec![rucc_debug::Reach { from: 0, len: 4 }, rucc_debug::Reach { from: 8, len: 4 }]
9397 );
9398 }
9399
9400 #[test]
9401 fn a_row_with_no_source_of_its_own_belongs_to_no_scope() {
9402 let rows = [rucc_asm::Row { at: 0, span: Span::DUMMY, inst: None }, row(4, 10, 14)];
9404 let ends = ends(&extent(8), &rows);
9405 let scope = Span::new(0, 20);
9406 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 4 }]);
9407 }
9408
9409 fn scope(parent: Option<usize>, lo: u32, hi: u32) -> crate::shapes::Scope {
9411 let span = Span::new(lo, hi);
9412 crate::shapes::Scope { parent, span }
9413 }
9414
9415 #[test]
9416 fn a_function_gets_the_scopes_its_own_locals_are_in_and_nothing_else() {
9417 let scopes = [scope(None, 0, 10), scope(None, 20, 30), scope(Some(1), 22, 26)];
9419 let rows = [row(0, 22, 24), row(4, 26, 28)];
9420 let (out, at) = nests(&[Some(2)], &scopes, &extent(8), &rows);
9421 assert_eq!(at.get(&1), Some(&0));
9424 assert_eq!(at.get(&2), Some(&1));
9425 assert_eq!(at.get(&0), None);
9426 assert_eq!(out.len(), 2);
9427 assert_eq!(out[0].parent, None);
9428 assert_eq!(out[1].parent, Some(0));
9429 assert_eq!(out[0].over, vec![rucc_debug::Reach { from: 0, len: 8 }]);
9430 assert_eq!(out[1].over, vec![rucc_debug::Reach { from: 0, len: 4 }]);
9431 }
9432
9433 #[test]
9434 fn a_local_written_straight_into_the_body_pulls_no_scope_in() {
9435 let scopes = [scope(None, 20, 30)];
9436 let rows = [row(0, 22, 24)];
9437 let (out, at) = nests(&[None], &scopes, &extent(4), &rows);
9438 assert_eq!(out, Vec::new());
9439 assert!(at.is_empty());
9440 }
9441
9442 #[test]
9443 fn a_scope_whose_code_all_went_away_is_still_one_of_the_functions_scopes() {
9444 let scopes = [scope(None, 20, 30)];
9448 let rows = [row(0, 40, 44)];
9449 let (out, at) = nests(&[Some(0)], &scopes, &extent(4), &rows);
9450 assert_eq!(at.get(&0), Some(&0));
9451 assert_eq!(out.len(), 1);
9452 assert_eq!(out[0].over, Vec::new());
9453 }
9454}