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 let aarch64 = target.tuple.arch() == Arch::Aarch64;
991 if aarch64 || rucc_asm::kept(&funcs, names, target) {
992 if opts.debug_info {
993 return Err(vec![unsupported(if aarch64 {
994 "debug information in an object for aarch64"
995 } else {
996 "debug information for a unit with an `asm` template kept as text"
997 })]);
998 }
999 let listing = rucc_asm::print(
1000 &funcs,
1001 &globals,
1002 &aliases,
1003 names,
1004 target,
1005 unwind,
1006 output(opts, target),
1007 )
1008 .map_err(refused)?;
1009 let read = rucc_asm::read(&listing, target.tuple.arch()).map_err(|trouble| {
1010 let what = if aarch64 {
1011 "a unit for aarch64"
1012 } else {
1013 "an `asm` template kept as text"
1014 };
1015 vec![unsupported(&format!(
1016 "{what}, whose listing the assembler stopped at on line {}: {}",
1017 trouble.line, trouble.why
1018 ))]
1019 })?;
1020 let defines = rucc_object::assembled_defines(&read);
1021 let bytes =
1022 rucc_object::assembled(&read, &TargetInfo::new(opts.target)).map_err(wrote)?;
1023 return Ok(Artifact::Object { bytes, defines });
1024 }
1025 let assembled = rucc_asm::assemble(&funcs, names, target, unwind, opts.debug_info)
1026 .map_err(refused)?;
1027 let data = globals.image();
1028 let info = if opts.debug_info {
1032 describe(&assembled, &data, &funcs, origin, opts, target)
1033 .map_err(|why| vec![internal(&why)])?
1034 } else {
1035 rucc_object::Info::default()
1036 };
1037 let text = assembled.text;
1038 let bytes =
1041 rucc_object::write(&text, &data, &aliases, target, output(opts, target), &info)
1042 .map_err(wrote)?;
1043 let defines = rucc_object::defines(&text, &data, &aliases, target).map_err(wrote)?;
1048 Ok(Artifact::Object { bytes, defines })
1049 }
1050 _ => Ok(Artifact::Text(rucc_mir::print(&funcs, names, target.regs))),
1051 }
1052}
1053
1054fn describe(
1073 assembled: &rucc_asm::Assembled,
1074 data: &rucc_object::Data,
1075 machine: &[rucc_mir::Func],
1076 origin: Origin<'_>,
1077 opts: &Options,
1078 target: &TargetInfo,
1079) -> Result<rucc_object::Info, String> {
1080 let rucc_asm::Assembled { text, lines, frames } = assembled;
1081 let rewrite = |path: &str| opts.prefix_map.debug.apply(path).into_owned();
1082 let mut files: Vec<String> = Vec::new();
1087 let mut funcs = Vec::with_capacity(text.funcs.len());
1088 for ((extent, rows), built) in text.funcs.iter().zip(lines).zip(machine) {
1089 let mut out: Vec<rucc_debug::Row> = Vec::with_capacity(rows.len());
1090 for row in rows {
1091 if row.span.is_dummy() {
1092 continue;
1093 }
1094 let Some(at) = origin.map.presumed(row.span.lo) else {
1095 continue;
1096 };
1097 let which = interned(&mut files, rewrite(at.name));
1098 let place = rucc_debug::Row {
1099 at: row.at as u64,
1100 file: which,
1101 line: at.line,
1102 column: at.column,
1103 };
1104 match out.last() {
1114 Some(last) if last.at == place.at => {}
1115 _ => out.push(place),
1116 }
1117 }
1118 if let Some(first) = out.first_mut() {
1125 first.at = 0;
1126 }
1127 let known = origin.meaning.funcs.get(&extent.name);
1133 let decl = known.map(|known| rucc_debug::Place {
1134 file: interned(&mut files, rewrite(&known.file)),
1135 line: known.line,
1136 });
1137 let mut sig = known.and_then(|known| known.sig.clone());
1146 let mut placed: Vec<(u32, i32)> = built.locals.clone();
1147 let mut spots = stretches(extent, rows, built, target);
1148 for &decl in &built.sharing {
1153 if !spots.iter().any(|(at, _)| *at == decl) {
1154 spots.push((decl, Vec::new()));
1155 }
1156 }
1157 if let (Some(sig), Some(known)) = (sig.as_mut(), known) {
1158 for (param, decl) in sig.params.iter_mut().zip(&known.params) {
1159 let Some(decl) = *decl else { continue };
1160 if let Some(which) = placed.iter().position(|&(at, _)| at == decl) {
1161 let at = rucc_debug::Held::Frame(i64::from(placed.remove(which).1));
1162 param.spot = Some(rucc_debug::Spot::Always(at));
1163 continue;
1164 }
1165 let Some(which) = spots.iter().position(|(at, _)| *at == decl) else { continue };
1169 param.spot = Some(rucc_debug::Spot::Over(spots.remove(which).1));
1170 }
1171 }
1172 let mut locals = Vec::with_capacity(placed.len() + spots.len());
1176 let mut wants: Vec<Option<usize>> = Vec::with_capacity(locals.capacity());
1180 for (decl, at) in placed {
1181 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1182 wants.push(named.scope);
1183 locals.push(rucc_debug::Local {
1184 name: named.name.clone(),
1185 ty: named.ty,
1186 decl: Some(rucc_debug::Place {
1187 file: interned(&mut files, rewrite(&named.file)),
1188 line: named.line,
1189 }),
1190 spot: rucc_debug::Spot::Always(rucc_debug::Held::Frame(i64::from(at))),
1191 scope: None,
1192 });
1193 }
1194 spots.sort_by_key(|(decl, _)| *decl);
1198 for (decl, spans) in spots {
1199 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1200 wants.push(named.scope);
1201 locals.push(rucc_debug::Local {
1202 name: named.name.clone(),
1203 ty: named.ty,
1204 decl: Some(rucc_debug::Place {
1205 file: interned(&mut files, rewrite(&named.file)),
1206 line: named.line,
1207 }),
1208 spot: rucc_debug::Spot::Over(spans),
1209 scope: None,
1210 });
1211 }
1212 let (scopes, at) = nests(&wants, &origin.meaning.scopes, extent, rows);
1217 for (local, want) in locals.iter_mut().zip(&wants) {
1218 local.scope = want.and_then(|want| at.get(&want).copied());
1219 }
1220 funcs.push(rucc_debug::Function {
1221 name: extent.name.clone(),
1222 len: extent.len as u64,
1223 rows: out,
1224 decl,
1225 sig,
1226 external: known.is_some_and(|known| known.external),
1227 locals,
1228 scopes,
1229 });
1230 }
1231 let mut globals = Vec::new();
1238 for object in &data.objects {
1239 let Some(held) = origin.meaning.objects.get(&object.name) else { continue };
1240 globals.push(rucc_debug::Global {
1241 name: object.name.clone(),
1242 ty: held.ty,
1243 decl: Some(rucc_debug::Place {
1244 file: interned(&mut files, rewrite(&held.file)),
1245 line: held.line,
1246 }),
1247 external: held.external,
1248 });
1249 }
1250 let unit = rucc_debug::Unit {
1251 name: rewrite(origin.name),
1252 dir: rewrite(opts.working_dir.as_deref().unwrap_or(".")),
1255 producer: format!("rucc {}", crate::VERSION),
1256 files,
1257 types: origin.meaning.types.clone(),
1258 funcs,
1259 globals,
1260 pointer: u8::try_from(target.pointer_width / 8).unwrap_or(8),
1261 frames: opts.unwinds() || frames.is_some(),
1266 };
1267 let mut info = rucc_debug::write(&unit).map_err(|why| why.to_string())?;
1268 info.chunks.extend(frames.clone());
1269 Ok(info)
1270}
1271
1272fn stretches(
1283 extent: &rucc_object::Extent,
1284 rows: &[rucc_asm::Row],
1285 built: &rucc_mir::Func,
1286 target: &TargetInfo,
1287) -> Vec<(u32, Vec<rucc_debug::Span>)> {
1288 let (false, Some(regs)) = (built.kept.is_empty(), target.call_regs) else {
1291 return Vec::new();
1292 };
1293 let ends = ends(extent, rows);
1294 let mut bounds = vec![None; built.inst_count()];
1295 for (which, row) in rows.iter().enumerate() {
1296 let Some(inst) = row.inst else { continue };
1297 bounds[inst.index()] = Some((row.at as u64, ends[which]));
1298 }
1299 let mut spots: Vec<(u32, Vec<rucc_debug::Span>)> = Vec::new();
1300 for kept in &built.kept {
1301 let (Some((from, _)), Some((_, to))) = (bounds[kept.from.index()], bounds[kept.to.index()])
1302 else {
1303 continue;
1304 };
1305 if to <= from {
1306 continue;
1307 }
1308 let held = match kept.at {
1309 rucc_mir::Where::Reg { reg, class } => match regs.dwarf(class, reg) {
1312 Some(number) => rucc_debug::Held::Reg(number),
1313 None => continue,
1314 },
1315 rucc_mir::Where::Frame(at) => rucc_debug::Held::Frame(i64::from(at)),
1316 };
1317 let span = rucc_debug::Span { from, len: to - from, held };
1318 match spots.iter_mut().find(|(decl, _)| *decl == kept.decl) {
1319 Some((_, spans)) => spans.push(span),
1320 None => spots.push((kept.decl, vec![span])),
1321 }
1322 }
1323 for (_, spans) in &mut spots {
1324 *spans = settle(std::mem::take(spans));
1325 }
1326 spots.retain(|(_, spans)| !spans.is_empty());
1327 spots
1328}
1329
1330fn ends(extent: &rucc_object::Extent, rows: &[rucc_asm::Row]) -> Vec<u64> {
1340 let mut out = vec![extent.len as u64; rows.len()];
1341 let mut next = extent.len as u64;
1342 for which in (0..rows.len()).rev() {
1343 let at = rows[which].at as u64;
1344 out[which] = match next > at {
1347 true => next,
1348 false => out.get(which + 1).copied().unwrap_or(extent.len as u64),
1349 };
1350 next = next.min(at);
1351 }
1352 out
1353}
1354
1355fn nests(
1371 wants: &[Option<usize>],
1372 scopes: &[crate::shapes::Scope],
1373 extent: &rucc_object::Extent,
1374 rows: &[rucc_asm::Row],
1375) -> (Vec<rucc_debug::Scope>, HashMap<usize, usize>) {
1376 let mut needed: Vec<usize> = Vec::new();
1377 for &want in wants {
1378 let mut up = want;
1379 while let Some(which) = up {
1380 if needed.contains(&which) {
1381 break;
1382 }
1383 needed.push(which);
1384 up = scopes.get(which).and_then(|scope| scope.parent);
1385 }
1386 }
1387 needed.sort_unstable();
1390 let at: HashMap<usize, usize> =
1391 needed.iter().enumerate().map(|(which, &scope)| (scope, which)).collect();
1392 let ends = ends(extent, rows);
1393 let out = needed
1394 .iter()
1395 .map(|&which| {
1396 let scope = &scopes[which];
1397 rucc_debug::Scope {
1398 parent: scope.parent.and_then(|parent| at.get(&parent).copied()),
1399 over: spread(scope.span, &ends, rows),
1400 }
1401 })
1402 .collect();
1403 (out, at)
1404}
1405
1406fn spread(span: Span, ends: &[u64], rows: &[rucc_asm::Row]) -> Vec<rucc_debug::Reach> {
1414 let mut out: Vec<rucc_debug::Reach> = Vec::new();
1415 for (which, row) in rows.iter().enumerate() {
1416 if row.span.is_dummy() || row.span.lo < span.lo || row.span.hi > span.hi {
1417 continue;
1418 }
1419 let (from, to) = (row.at as u64, ends[which]);
1420 if to <= from {
1421 continue;
1422 }
1423 match out.last_mut() {
1424 Some(last) if last.from + last.len >= from => {
1425 last.len = to.saturating_sub(last.from).max(last.len);
1426 }
1427 _ => out.push(rucc_debug::Reach { from, len: to - from }),
1428 }
1429 }
1430 out
1431}
1432
1433fn settle(mut spans: Vec<rucc_debug::Span>) -> Vec<rucc_debug::Span> {
1450 spans.sort_by_key(|span| (span.from, span.len));
1451 for which in 0..spans.len() {
1452 let (from, end, held) =
1453 (spans[which].from, spans[which].from + spans[which].len, spans[which].held);
1454 let later = spans[which + 1..]
1455 .iter()
1456 .take_while(|later| later.from < end)
1457 .find(|later| later.from > from && later.held != held);
1458 if let Some(later) = later {
1459 spans[which].len = later.from - from;
1460 }
1461 }
1462 let mut edges: Vec<u64> =
1465 spans.iter().flat_map(|span| [span.from, span.from + span.len]).collect();
1466 edges.sort_unstable();
1467 edges.dedup();
1468 let mut out: Vec<rucc_debug::Span> = Vec::new();
1469 let mut first = 0;
1470 for pair in edges.windows(2) {
1471 let (from, to) = (pair[0], pair[1]);
1472 while spans.get(first).is_some_and(|span| span.from + span.len <= from) {
1476 first += 1;
1477 }
1478 let mut held = None;
1479 let mut agreed = true;
1480 for span in &spans[first..] {
1481 if span.from >= to {
1482 break;
1483 }
1484 if span.from > from || span.from + span.len < to {
1485 continue;
1486 }
1487 match held {
1488 None => held = Some(span.held),
1489 Some(seen) => agreed &= seen == span.held,
1490 }
1491 }
1492 let (Some(held), true) = (held, agreed) else { continue };
1493 match out.last_mut() {
1494 Some(last) if last.from + last.len == from && last.held == held => {
1495 last.len += to - from
1496 }
1497 _ => out.push(rucc_debug::Span { from, len: to - from, held }),
1498 }
1499 }
1500 out
1501}
1502
1503fn interned(files: &mut Vec<String>, name: String) -> usize {
1509 match files.iter().position(|have| *have == name) {
1510 Some(which) => which,
1511 None => {
1512 files.push(name);
1513 files.len() - 1
1514 }
1515 }
1516}
1517
1518fn output(opts: &Options, target: &TargetInfo) -> rucc_object::Output {
1530 let mut features = 0;
1531 if target.tuple.arch() == Arch::X86_64 {
1532 if opts.control.branch() {
1533 features |= rucc_object::Property::IBT;
1534 }
1535 if opts.control.ret() {
1536 features |= rucc_object::Property::SHSTK;
1537 }
1538 }
1539 rucc_object::Output {
1540 sections: rucc_object::Sections {
1541 functions: opts.function_sections,
1542 data: opts.data_sections,
1543 },
1544 property: rucc_object::Property { features },
1545 }
1546}
1547
1548fn wrote(why: rucc_object::Error) -> Vec<Diagnostic> {
1554 match why {
1555 rucc_object::Error::Format { .. } => vec![unsupported(&why.to_string())],
1556 rucc_object::Error::Refused { .. } => vec![internal(&why.to_string())],
1557 }
1558}
1559
1560fn refused(why: rucc_asm::Error) -> Vec<Diagnostic> {
1567 match why {
1568 rucc_asm::Error::Thread { .. }
1569 | rucc_asm::Error::IFunc { .. }
1570 | rucc_asm::Error::Frame { .. } => {
1571 vec![unsupported(&why.to_string())]
1572 }
1573 _ => vec![internal(&why.to_string())],
1574 }
1575}
1576
1577fn unsupported(message: &str) -> Diagnostic {
1583 unsupported_at(message, Span::DUMMY)
1584}
1585
1586fn unsupported_at(message: &str, span: Span) -> Diagnostic {
1592 Diagnostic::error(message.to_owned(), span)
1593 .with_code("E0653")
1594 .note("this construct is not lowered yet, see https://github.com/tamnd/rucc/issues", span)
1595}
1596
1597fn invalid(message: &str) -> Diagnostic {
1599 Diagnostic::error(message.to_owned(), Span::DUMMY).with_code("E0661")
1600}
1601
1602fn internal(message: &str) -> Diagnostic {
1604 Diagnostic::error(format!("internal error: {message}"), Span::DUMMY)
1605 .with_code("E0652")
1606 .note("this is a bug in rucc rather than in the program, please report it", Span::DUMMY)
1607}
1608
1609fn failure(message: String) -> Compiled {
1612 Compiled {
1613 artifact: Artifact::Nothing,
1614 messages: vec![format!("rucc: error: {message}")],
1615 errors: 1,
1616 fired: Fired::new(),
1617 pressure: Pressure::new(),
1618 lowerings: Lowerings::new(),
1619 dumps: Vec::new(),
1620 remarks: String::new(),
1621 deps: Vec::new(),
1622 temps: Temps::default(),
1623 }
1624}
1625
1626#[cfg(test)]
1627mod tests {
1628 use rucc_session::{MemoryFileSystem, Std};
1629 use rucc_target::Triple;
1630
1631 use super::*;
1632
1633 fn options() -> Options {
1634 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1635 opts.emit = EmitKind::Tast;
1636 opts
1637 }
1638
1639 fn run(opts: &Options, source: &str) -> Compiled {
1640 let mut fs = MemoryFileSystem::new();
1641 fs.insert("/main.c", source.to_owned().into_bytes());
1642 compile(opts, "/main.c", &fs)
1643 }
1644
1645 fn freestanding() -> Options {
1649 let mut opts = options();
1650 opts.hosted = false;
1651 opts.search.push_system(rucc_session::runtime::DIR);
1652 opts
1653 }
1654
1655 fn shipped(source: &str) -> String {
1657 let result = run(&freestanding(), source);
1658 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1659 result.text().to_owned()
1660 }
1661
1662 fn tast(source: &str) -> String {
1664 let result = run(&options(), source);
1665 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1666 result.text().to_owned()
1667 }
1668
1669 #[test]
1670 fn the_shipped_stdarg_declares_a_list_and_the_four_operators() {
1671 let text = shipped(concat!(
1672 "#include <stdarg.h>\n",
1673 "int sum(int n, ...) {\n",
1674 " va_list ap, copy;\n",
1675 " va_start(ap, n);\n",
1676 " va_copy(copy, ap);\n",
1677 " int total = va_arg(ap, int) + va_arg(copy, int);\n",
1678 " va_end(ap);\n",
1679 " va_end(copy);\n",
1680 " return total;\n",
1681 "}\n",
1682 ));
1683 assert!(text.contains("va-start"), "{text}");
1684 assert!(text.contains("va-copy"), "{text}");
1685 assert!(text.contains("va-arg"), "{text}");
1686 assert!(text.contains("va-end"), "{text}");
1687 }
1688
1689 #[test]
1693 fn stdarg_hands_out_the_type_alone_when_that_is_all_that_was_asked_for() {
1694 let text = shipped(concat!(
1695 "#define __need___va_list\n",
1696 "#include <stdarg.h>\n",
1697 "int vprint(const char *f, __gnuc_va_list ap);\n",
1698 "#ifdef va_start\n",
1699 "#error va_start should not be defined\n",
1700 "#endif\n",
1701 "#ifdef _VA_LIST_DEFINED\n",
1702 "#error va_list should not have been made\n",
1703 "#endif\n",
1704 ));
1705 assert!(text.contains("vprint"), "{text}");
1706 }
1707
1708 #[test]
1711 fn stddef_answers_one_piece_at_a_time_and_the_next_request_still_gets_through() {
1712 let text = shipped(concat!(
1713 "#define __need_size_t\n",
1714 "#include <stddef.h>\n",
1715 "#ifdef offsetof\n",
1716 "#error offsetof should not be defined yet\n",
1717 "#endif\n",
1718 "#define __need_ptrdiff_t\n",
1719 "#include <stddef.h>\n",
1720 "#include <stddef.h>\n",
1721 "size_t a;\n",
1722 "ptrdiff_t b;\n",
1723 "wchar_t c;\n",
1724 "max_align_t d;\n",
1725 "void *e = NULL;\n",
1726 "struct P { int x; long y; };\n",
1727 "size_t f = offsetof(struct P, y);\n",
1728 ));
1729 assert!(text.contains("decl #0 a : unsigned long"), "{text}");
1730 assert!(text.contains("decl #1 b : long"), "{text}");
1731 }
1732
1733 #[test]
1734 fn the_shipped_limits_and_float_are_the_targets_own_answers() {
1735 let text = shipped(concat!(
1736 "#include <limits.h>\n",
1737 "#include <float.h>\n",
1738 "int bits = CHAR_BIT;\n",
1739 "long big = LONG_MAX;\n",
1740 "int low = INT_MIN;\n",
1741 "int radix = FLT_RADIX;\n",
1742 "int digits = DBL_MANT_DIG;\n",
1743 ));
1744 assert!(text.contains("const 8 : int"), "{text}");
1745 assert!(text.contains("const 9223372036854775807 : long"), "{text}");
1746 assert!(text.contains("const 2 : int"), "{text}");
1747 assert!(text.contains("const 53 : int"), "{text}");
1748 }
1749
1750 #[test]
1754 fn the_shipped_stdint_writes_the_whole_set_when_there_is_no_library_to_defer_to() {
1755 let text = shipped(concat!(
1756 "#include <stdint.h>\n",
1757 "int64_t a = INT64_C(1);\n",
1758 "uint_least16_t b;\n",
1759 "intptr_t c;\n",
1760 "uintmax_t d = UINTMAX_MAX;\n",
1761 "int wide = sizeof(int_fast64_t);\n",
1762 ));
1763 assert!(text.contains("decl #0 a : long"), "{text}");
1764 assert!(text.contains("decl #1 b : unsigned short"), "{text}");
1765 assert!(text.contains("decl #2 c : long"), "{text}");
1766 }
1767
1768 #[test]
1779 fn the_shipped_mmintrin_defines_the_mmx_type_and_the_operations_over_it() {
1780 let text = shipped(concat!(
1781 "#include <mmintrin.h>\n",
1782 "__m64 add(__m64 a, __m64 b) { return _mm_add_pi16(a, b); }\n",
1783 "__m64 pack(__m64 a, __m64 b) { return _m_packsswb(a, b); }\n",
1784 "__m64 shift(__m64 a) { return _mm_srai_pi32(a, 3); }\n",
1785 "int low(__m64 a) { return _mm_cvtsi64_si32(a); }\n",
1786 "void done(void) { _mm_empty(); }\n",
1787 ));
1788 assert!(text.contains("add"), "{text}");
1789 assert!(text.contains("pack"), "{text}");
1790 assert!(text.contains("shift"), "{text}");
1791 }
1792
1793 #[test]
1798 fn the_shipped_mm_malloc_asks_for_aligned_memory_and_gives_it_back() {
1799 let text = shipped(concat!(
1800 "#include <mm_malloc.h>\n",
1801 "void *get(void) { return _mm_malloc(64, 16); }\n",
1802 "void put(void *p) { _mm_free(p); }\n",
1803 ));
1804 assert!(text.contains("get"), "{text}");
1805 assert!(text.contains("put"), "{text}");
1806 }
1807
1808 #[test]
1820 fn the_shipped_xmmintrin_defines_the_sse_type_and_the_operations_over_it() {
1821 let text = shipped(concat!(
1822 "#include <xmmintrin.h>\n",
1823 "__m128 add(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1824 "__m128 one(__m128 a, __m128 b) { return _mm_max_ss(a, b); }\n",
1825 "__m128 mask(__m128 a, __m128 b) { return _mm_cmpnle_ps(a, b); }\n",
1826 "__m128 pick(__m128 a, __m128 b) { return _mm_shuffle_ps(a, b, _MM_SHUFFLE(0,1,2,3)); }\n",
1827 "int bits(__m128 a) { return _mm_movemask_ps(a); }\n",
1828 "int near(__m128 a) { return _mm_cvtss_si32(a); }\n",
1829 "__m128 wide(__m64 a) { return _mm_cvtpi16_ps(a); }\n",
1830 "void *room(void) { return _mm_malloc(64, 16); }\n",
1831 "void hint(const float *p) { _mm_prefetch(p, _MM_HINT_T0); _mm_sfence(); }\n",
1832 ));
1833 assert!(text.contains("add"), "{text}");
1834 assert!(text.contains("mask"), "{text}");
1835 assert!(text.contains("pick"), "{text}");
1836 assert!(text.contains("wide"), "{text}");
1837 }
1838
1839 #[test]
1846 fn the_shipped_xmmintrin_leaves_out_the_names_that_need_an_instruction() {
1847 let text = rucc_session::runtime::header("xmmintrin.h").expect("xmmintrin.h is shipped");
1848 for absent in [
1849 "_mm_sqrt_ps",
1850 "_mm_sqrt_ss",
1851 "_mm_rsqrt_ps",
1852 "_mm_rsqrt_ss",
1853 "_mm_getcsr",
1854 "_mm_setcsr",
1855 ] {
1856 let defined = text.contains(&format!("{absent}("));
1857 assert!(!defined, "{absent} is defined and the header says it is not");
1858 assert!(text.contains(absent), "{absent} is absent and unexplained");
1859 }
1860 }
1861
1862 #[test]
1863 fn the_shipped_emmintrin_defines_both_sse2_types_and_the_operations_over_them() {
1864 let text = shipped(concat!(
1865 "#include <emmintrin.h>\n",
1866 "__m128i add(__m128i a, __m128i b) { return _mm_add_epi64(a, b); }\n",
1867 "__m128i wide(__m128i a, __m128i b) { return _mm_mul_epu32(a, b); }\n",
1868 "__m128i pick(__m128i a) { return _mm_shuffle_epi32(a, _MM_SHUFFLE(0,1,2,3)); }\n",
1869 "__m128i up(__m128i a) { return _mm_slli_epi64(a, 13); }\n",
1870 "__m128i down(__m128i a) { return _mm_srli_si128(a, 3); }\n",
1871 "__m128i pack(__m128i a, __m128i b) { return _mm_packus_epi16(a, b); }\n",
1872 "int bits(__m128i a) { return _mm_movemask_epi8(a); }\n",
1873 "__m128d sum(__m128d a, __m128d b) { return _mm_add_sd(a, b); }\n",
1874 "__m128d mask(__m128d a, __m128d b) { return _mm_cmpunord_pd(a, b); }\n",
1875 "__m128i near(__m128d a) { return _mm_cvtpd_epi32(a); }\n",
1876 "__m128d over(__m128 a) { return _mm_cvtps_pd(a); }\n",
1877 "__m128i half(__m64 a) { return _mm_movpi64_epi64(a); }\n",
1878 "__m128i grab(void const *p) { return _mm_loadu_si128(p); }\n",
1879 "void wall(void) { _mm_lfence(); _mm_mfence(); }\n",
1880 ));
1881 assert!(text.contains("wide"), "{text}");
1882 assert!(text.contains("pack"), "{text}");
1883 assert!(text.contains("near"), "{text}");
1884 assert!(text.contains("half"), "{text}");
1885 }
1886
1887 #[test]
1891 fn the_shipped_immintrin_reaches_the_names_the_headers_under_it_define() {
1892 let text = shipped(concat!(
1893 "#include <immintrin.h>\n",
1894 "unsigned long long matching(unsigned char tag, unsigned char const *bucket) {\n",
1895 " __m128i const want = _mm_set1_epi8((char)tag);\n",
1896 " __m128i const chunk = _mm_loadu_si128((__m128i const *)(void const *)bucket);\n",
1897 " __m128i const same = _mm_cmpeq_epi8(chunk, want);\n",
1898 " return (unsigned long long)_mm_movemask_epi8(same);\n",
1899 "}\n",
1900 "__m64 narrow(__m64 a, __m64 b) { return _mm_add_pi32(a, b); }\n",
1901 "__m128 single(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1902 ));
1903 assert!(text.contains("matching"), "{text}");
1904 assert!(text.contains("narrow"), "the MMX header is not reached: {text}");
1905 assert!(text.contains("single"), "the SSE header is not reached: {text}");
1906 }
1907
1908 #[test]
1912 fn the_shipped_x86intrin_reaches_the_fences_windows_headers_ask_it_for() {
1913 let text = shipped(concat!(
1914 "#include <x86intrin.h>\n",
1915 "void barriers(void *p) {\n",
1916 " _mm_lfence();\n",
1917 " _mm_sfence();\n",
1918 " _mm_mfence();\n",
1919 " _mm_pause();\n",
1920 " _mm_clflush(p);\n",
1921 "}\n",
1922 "__m128i wide(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1923 ));
1924 assert!(text.contains("barriers"), "{text}");
1925 assert!(text.contains("wide"), "the SSE2 header is not reached: {text}");
1926 }
1927
1928 #[test]
1932 fn the_umbrella_and_the_header_under_it_can_both_be_included() {
1933 let text = shipped(concat!(
1934 "#include <immintrin.h>\n",
1935 "#include <emmintrin.h>\n",
1936 "#include <immintrin.h>\n",
1937 "#include <x86intrin.h>\n",
1938 "__m128i twice(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1939 ));
1940 assert!(text.contains("twice"), "{text}");
1941 }
1942
1943 #[test]
1947 fn the_shipped_emmintrin_leaves_out_the_two_square_roots() {
1948 let text = rucc_session::runtime::header("emmintrin.h").expect("emmintrin.h is shipped");
1949 for absent in ["_mm_sqrt_pd", "_mm_sqrt_sd"] {
1950 let defined = text.contains(&format!("{absent}("));
1951 assert!(!defined, "{absent} is defined and the header says it is not");
1952 assert!(text.contains(absent), "{absent} is absent and unexplained");
1953 }
1954 }
1955
1956 #[test]
1957 fn the_three_formality_headers_still_have_to_work() {
1958 let text = shipped(concat!(
1959 "#include <stdbool.h>\n",
1960 "#include <stdalign.h>\n",
1961 "#include <iso646.h>\n",
1962 "#include <stdnoreturn.h>\n",
1963 "int t = true and not false;\n",
1964 "_Alignas(16) char buf[16];\n",
1965 "int a = alignof(long);\n",
1966 ));
1967 assert!(text.contains("decl #0 t : int"), "{text}");
1968 assert!(text.contains("const 8 : unsigned long"), "{text}");
1969 }
1970
1971 #[test]
1979 fn every_shipped_header_can_be_included_twice() {
1980 let once: String = rucc_session::runtime::names()
1981 .iter()
1982 .map(|name| format!("#include <{name}>\n"))
1983 .collect();
1984 let twice = once.repeat(2);
1985 assert_eq!(shipped(&format!("{once}int x;\n")), shipped(&format!("{twice}int x;\n")));
1986 }
1987
1988 #[test]
1989 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
1990 let fs = MemoryFileSystem::new();
1991 let result = compile(&options(), "/nope.c", &fs);
1992 assert!(result.failed());
1993 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
1994 assert!(result.text().is_empty());
1995 }
1996
1997 #[test]
1998 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
1999 let text = tast("int x = 1;\n");
2000 let expected = "\
2001decl #0 x : int object external static defined
2002 init
2003 +0
2004 const 1 : int
2005";
2006 assert_eq!(text, expected);
2007 }
2008
2009 #[test]
2010 fn the_macros_are_expanded_before_anything_is_parsed() {
2011 let text = tast("#define N 2\nint a[N];\n");
2015 assert!(text.starts_with("decl #0 a : int[2] object external static tentative"), "{text}");
2016 }
2017
2018 #[test]
2024 fn a_pragma_is_not_a_declaration_and_the_parse_walks_past_the_ones_it_does_not_read() {
2025 let text = tast(concat!(
2026 "#pragma pack(4)\n",
2027 "struct s { int a; };\n",
2028 "#pragma pack()\n",
2029 "int b;\n",
2030 "_Pragma(\"GCC visibility push(default)\") int c;\n",
2031 ));
2032 assert!(text.contains("decl #0 b : int"), "{text}");
2033 assert!(text.contains("decl #1 c : int"), "{text}");
2034 }
2035
2036 #[test]
2044 fn the_layout_attributes_move_the_members_and_the_record_the_way_gcc_lays_them_out() {
2045 tast(concat!(
2046 "struct A { char c; int i; } __attribute__((packed));\n",
2047 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2048 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2049 "struct B { char c; int i; } __attribute__((aligned));\n",
2052 "_Static_assert(sizeof(struct B) == 16 && _Alignof(struct B) == 16, \"B\");\n",
2053 "struct C { char c; int i __attribute__((packed)); };\n",
2054 "_Static_assert(sizeof(struct C) == 5 && _Alignof(struct C) == 1, \"C\");\n",
2055 "_Static_assert(__builtin_offsetof(struct C, i) == 1, \"C.i\");\n",
2056 "struct D { char c; int i; } __attribute__((packed, aligned(4)));\n",
2057 "_Static_assert(sizeof(struct D) == 8 && _Alignof(struct D) == 4, \"D\");\n",
2058 "_Static_assert(__builtin_offsetof(struct D, i) == 1, \"D.i\");\n",
2059 "struct E { char c; _Alignas(8) int i; };\n",
2060 "_Static_assert(sizeof(struct E) == 16 && _Alignof(struct E) == 8, \"E\");\n",
2061 "_Static_assert(__builtin_offsetof(struct E, i) == 8, \"E.i\");\n",
2062 "struct F { char c; int i __attribute__((aligned(8))); };\n",
2063 "_Static_assert(sizeof(struct F) == 16 && _Alignof(struct F) == 8, \"F\");\n",
2064 "struct G { char c; short s; } __attribute__((aligned(2)));\n",
2067 "_Static_assert(sizeof(struct G) == 4 && _Alignof(struct G) == 2, \"G\");\n",
2068 "struct H { char c; int i; } __attribute__((aligned(2)));\n",
2069 "_Static_assert(sizeof(struct H) == 8 && _Alignof(struct H) == 4, \"H\");\n",
2070 "struct I { [[gnu::packed]] char c; int i; };\n",
2073 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2074 "struct J { char c; [[gnu::packed]] int i; };\n",
2075 "_Static_assert(sizeof(struct J) == 5 && _Alignof(struct J) == 1, \"J\");\n",
2076 "struct M { char c; int i : 5; int j : 20; } __attribute__((packed));\n",
2077 "_Static_assert(sizeof(struct M) == 5 && _Alignof(struct M) == 1, \"M\");\n",
2078 "struct N { char c; long long l; } __attribute__((aligned(32)));\n",
2079 "_Static_assert(sizeof(struct N) == 32 && _Alignof(struct N) == 32, \"N\");\n",
2080 "union L { char c; int i; } __attribute__((packed));\n",
2081 "_Static_assert(sizeof(union L) == 4 && _Alignof(union L) == 1, \"L\");\n",
2082 "struct O { char c; int i; } __attribute__((__packed__));\n",
2086 "_Static_assert(sizeof(struct O) == 5 && _Alignof(struct O) == 1, \"O\");\n",
2087 "struct P { char c; int i; } __attribute__((__aligned__(8)));\n",
2088 "_Static_assert(sizeof(struct P) == 8 && _Alignof(struct P) == 8, \"P\");\n",
2089 ));
2090 }
2091
2092 #[test]
2105 fn a_transparent_union_takes_the_member_a_value_fits_and_is_declared_either_way() {
2106 let text = tast(concat!(
2107 "struct one { int x; };\n",
2108 "struct two { long y; };\n",
2109 "typedef union { struct one *a; struct two *b; void *any; }\n",
2110 " __attribute__((__transparent_union__)) arg;\n",
2111 "int takes(arg v);\n",
2112 "int f(struct one *p, struct two *q, char *c) {\n",
2113 " return takes(p) + takes(q) + takes(c) + takes(0);\n",
2114 "}\n",
2115 "int takes(struct one *p);\n",
2117 "int (*as_a_member)(struct one *) = takes;\n",
2118 "int (*as_the_union)(arg) = takes;\n",
2119 ));
2120 assert!(text.contains("compound-literal"), "{text}");
2121 }
2122
2123 #[test]
2129 fn the_attribute_on_the_declarator_of_a_typedef_is_the_one_glibc_writes() {
2130 let text = tast(concat!(
2131 "struct sockaddr { int family; };\n",
2132 "struct sockaddr_in { int family; int addr; };\n",
2133 "typedef union { struct sockaddr *plain; struct sockaddr_in *inet; }\n",
2134 " addr_arg __attribute__((__transparent_union__));\n",
2135 "int bind_to(int fd, addr_arg where);\n",
2136 "int f(struct sockaddr_in *where) { return bind_to(0, where); }\n",
2137 ));
2138 assert!(text.contains("compound-literal"), "{text}");
2139 }
2140
2141 #[test]
2149 fn a_transparent_union_that_cannot_keep_the_promise_is_dropped_with_a_word_about_it() {
2150 let result = run(
2151 &options(),
2152 concat!(
2153 "union wider { int small; double large; } __attribute__((transparent_union));\n",
2154 "struct plain { int x; } __attribute__((transparent_union));\n",
2155 ),
2156 );
2157 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
2158 assert!(!result.failed(), "{:?}", result.messages);
2159 for message in &result.messages {
2160 assert!(message.contains("'transparent_union' attribute ignored"), "{message}");
2161 }
2162 assert!(result.messages[0].contains("first member"), "{:?}", result.messages);
2163 assert!(result.messages[1].contains("only a union"), "{:?}", result.messages);
2164 }
2165
2166 #[test]
2175 fn an_access_to_a_packed_member_says_the_alignment_the_layout_left_it() {
2176 let packed = body(concat!(
2177 "struct P { char c; int v; } __attribute__((packed));\n",
2178 "int f(struct P *p) { return p->v; }\n",
2179 ));
2180 assert!(packed.contains("load.i32 %2, align 1,"), "{packed}");
2181 let plain = body(concat!(
2183 "struct P { char c; int v; };\n",
2184 "int f(struct P *p) { return p->v; }\n",
2185 ));
2186 assert!(plain.contains("load.i32 %2, align 4,"), "{plain}");
2187 }
2188
2189 #[test]
2196 fn what_is_inside_a_packed_member_is_no_more_aligned_than_the_member_is() {
2197 let stepped = body(concat!(
2198 "struct P { char c; int v[4]; } __attribute__((packed));\n",
2199 "int f(struct P *p, int i) { return p->v[i]; }\n",
2200 ));
2201 assert!(stepped.contains(", align 1,"), "{stepped}");
2202 assert!(!stepped.contains(", align 4,"), "{stepped}");
2203 let nested = body(concat!(
2204 "struct Inner { int v; };\n",
2205 "struct P { char c; struct Inner in; } __attribute__((packed));\n",
2206 "int f(struct P *p) { return p->in.v; }\n",
2207 ));
2208 assert!(nested.contains(", align 1,"), "{nested}");
2209 assert!(!nested.contains(", align 4,"), "{nested}");
2210 }
2211
2212 #[test]
2228 fn an_access_through_a_typedef_that_lowered_its_alignment_says_the_one_the_typedef_asked_for() {
2229 let through = body(concat!(
2230 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2231 "unsigned int f(const void *p) { return *(const unalign32 *)p; }\n",
2232 ));
2233 assert!(through.contains("load.i32 %0, align 1,"), "{through}");
2234 let stepped = body(concat!(
2237 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2238 "unsigned int f(unalign32 *p, int i) { return p[i]; }\n",
2239 ));
2240 assert!(stepped.contains(", align 1,"), "{stepped}");
2241 assert!(!stepped.contains(", align 4,"), "{stepped}");
2242 let plain = body(concat!(
2245 "typedef unsigned int word;\n",
2246 "unsigned int f(const void *p) { return *(const word *)p; }\n",
2247 ));
2248 assert!(plain.contains("load.i32 %0, align 4,"), "{plain}");
2249 }
2250
2251 #[test]
2262 fn a_vector_read_through_a_typedef_that_lowered_its_alignment_comes_back_a_piece_at_a_time() {
2263 let prefix = concat!(
2264 "typedef long long v2di __attribute__((__vector_size__(16)));\n",
2265 "typedef long long v2di_u __attribute__((__vector_size__(16), __aligned__(1)));\n",
2266 );
2267 let loaded =
2268 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di_u *)p; }}"));
2269 assert_eq!(loaded.matches("align 1\n").count(), 2, "{loaded}");
2270 assert!(!loaded.contains("align 16"), "{loaded}");
2271 let stored = body(&format!("{prefix}void f(void *p, v2di b) {{ *(v2di_u *)p = b; }}"));
2274 assert!(stored.contains("memcpy %0, %3, size 16, align 1"), "{stored}");
2275 let aligned =
2277 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di *)p; }}"));
2278 assert!(aligned.contains("align 16"), "{aligned}");
2279 }
2280
2281 #[test]
2290 fn the_aligned_attribute_on_a_declaration_raises_what_that_one_object_is_aligned_to() {
2291 tast(concat!(
2292 "int v __attribute__((aligned(64)));\n",
2293 "_Static_assert(__alignof__(v) == 64, \"v\");\n",
2294 "__attribute__((aligned(32))) int w;\n",
2297 "_Static_assert(__alignof__(w) == 32, \"w\");\n",
2298 "[[gnu::aligned(16)]] int x;\n",
2299 "_Static_assert(__alignof__(x) == 16, \"x\");\n",
2300 "int y __attribute__((aligned(2)));\n",
2303 "_Static_assert(__alignof__(y) == 4, \"y\");\n",
2304 "void f(void) { int a __attribute__((aligned(128)));\n",
2306 "_Static_assert(__alignof__(a) == 128, \"a\"); (void)a; }\n",
2307 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2310 "void g(void) __attribute__((aligned(256)));\n",
2313 "void g(void) {}\n",
2314 "_Static_assert(__alignof__(g) == 256, \"g\");\n",
2315 ));
2316 }
2317
2318 #[test]
2322 fn what_a_declaration_asked_to_be_aligned_to_is_what_the_assembler_is_told() {
2323 let text = asm(concat!(
2324 "int v __attribute__((aligned(64)));\n",
2325 "void g(void) __attribute__((aligned(256)));\n",
2326 "void g(void) {}\n",
2327 "void plain(void) {}\n",
2328 ));
2329 assert!(text.contains("\t.p2align\t6\n\t.type\tv, @object\n"), "{text}");
2330 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2331 assert!(text.contains("\t.p2align\t4, 0x90\n\t.globl\tplain\n"), "{text}");
2332 }
2333
2334 #[test]
2340 fn the_alignment_the_command_line_asked_of_every_function_is_a_floor_under_all_of_them() {
2341 let source = concat!(
2342 "void g(void) __attribute__((aligned(256)));\n",
2343 "void g(void) {}\n",
2344 "void small(void) __attribute__((aligned(4)));\n",
2345 "void small(void) {}\n",
2346 "void plain(void) {}\n",
2347 );
2348 let listing = |align: Option<u32>| {
2349 let mut opts = options();
2350 opts.emit = EmitKind::Asm;
2351 opts.align_functions = align;
2352 let result = run(&opts, source);
2353 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
2354 result.text().to_owned()
2355 };
2356
2357 let text = listing(Some(32));
2358 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "the larger one wins: {text}");
2359 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tsmall\n"), "{text}");
2360 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tplain\n"), "{text}");
2361
2362 let text = listing(Some(8));
2365 assert!(text.contains("\t.p2align\t3, 0x90\n\t.globl\tplain\n"), "{text}");
2366 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2367 }
2368
2369 #[test]
2378 fn an_aligned_typedef_says_what_an_object_of_it_is_aligned_to_and_may_lower_it() {
2379 tast(concat!(
2380 "typedef int L __attribute__((aligned(2)));\n",
2381 "_Static_assert(__alignof__(L) == 2, \"L\");\n",
2382 "_Static_assert(_Alignof(L) == 2, \"L alignof\");\n",
2383 "_Static_assert(sizeof(L) == 4, \"L size\");\n",
2385 "struct T { char c; L x; };\n",
2386 "_Static_assert(sizeof(struct T) == 6, \"T\");\n",
2387 "_Static_assert(__builtin_offsetof(struct T, x) == 2, \"T.x\");\n",
2388 "typedef int H __attribute__((aligned(16)));\n",
2390 "_Static_assert(__alignof__(H) == 16, \"H\");\n",
2391 "_Static_assert(sizeof(H) == 4, \"H size\");\n",
2392 "struct U { char c; H x; };\n",
2393 "_Static_assert(sizeof(struct U) == 32, \"U\");\n",
2394 "_Static_assert(__builtin_offsetof(struct U, x) == 16, \"U.x\");\n",
2395 "typedef L M __attribute__((aligned(8)));\n",
2398 "_Static_assert(__alignof__(M) == 8, \"M\");\n",
2399 "typedef L N;\n",
2402 "_Static_assert(__alignof__(N) == 2, \"N\");\n",
2403 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2405 ));
2406 let text = asm(concat!(
2407 "typedef int L __attribute__((aligned(2)));\n",
2408 "typedef int H __attribute__((aligned(16)));\n",
2409 "L low;\n",
2410 "H high;\n",
2411 ));
2412 assert!(text.contains("\t.p2align\t1\n\t.type\tlow, @object\n"), "{text}");
2413 assert!(text.contains("\t.p2align\t4\n\t.type\thigh, @object\n"), "{text}");
2414 }
2415
2416 #[test]
2424 fn the_vector_size_attribute_builds_a_type_of_lanes_and_measures_it_in_bytes() {
2425 tast(concat!(
2426 "typedef int __attribute__((vector_size(16))) v4si;\n",
2427 "_Static_assert(sizeof(v4si) == 16 && _Alignof(v4si) == 16, \"v4si\");\n",
2428 "typedef char __attribute__((vector_size(16))) v16qi;\n",
2429 "_Static_assert(sizeof(v16qi) == 16, \"v16qi\");\n",
2430 "typedef int __attribute__((vector_size(4))) v1si;\n",
2433 "_Static_assert(sizeof(v1si) == 4, \"v1si\");\n",
2434 "typedef float __attribute__((__vector_size__(8))) v2sf;\n",
2436 "_Static_assert(sizeof(v2sf) == 8, \"v2sf\");\n",
2437 "typedef short [[gnu::vector_size(8)]] v4hi;\n",
2438 "_Static_assert(sizeof(v4hi) == 8, \"v4hi\");\n",
2439 "v4si g;\n",
2442 "_Static_assert(sizeof(g[0]) == 4, \"lane\");\n",
2443 "_Static_assert(sizeof(g + g) == 16, \"whole\");\n",
2444 "_Static_assert(sizeof(g + 1) == 16, \"broadcast\");\n",
2447 "_Static_assert(sizeof(v4si[3]) == 48, \"array\");\n",
2449 ));
2450 }
2451
2452 #[test]
2462 fn a_vector_is_written_whole_into_an_array_of_them_and_named_by_a_type_name() {
2463 tast(concat!(
2464 "typedef int __attribute__((vector_size(8))) v2si;\n",
2465 "v2si table[] = { (v2si){ 1, 2 }, (v2si){ 3, 4 } };\n",
2466 "_Static_assert(sizeof(table) == 16, \"two of them and not eight lanes\");\n",
2467 "v2si written = (int __attribute__((vector_size(8)))){ 5, 6 };\n",
2469 "_Static_assert(sizeof((int __attribute__((vector_size(16)))){ 0 }) == 16, \"named\");\n",
2470 "v2si lanes[2] = { 1, 2, 3, 4 };\n",
2473 "_Static_assert(sizeof(lanes) == 16, \"still elided\");\n",
2474 ));
2475 }
2476
2477 #[test]
2485 fn a_lane_is_assignable_and_a_shift_takes_a_count_of_its_own_lane() {
2486 let result = run(
2487 &options(),
2488 concat!(
2489 "typedef int __attribute__((vector_size(16))) v4si;\n",
2490 "typedef unsigned __attribute__((vector_size(16))) v4ui;\n",
2491 "void write(v4si *out, v4ui a, v4si b, int n) {\n",
2492 " v4si v = { 1, 2, 3, 4 };\n",
2493 " v[0] = n;\n",
2494 " v[1] += n;\n",
2495 " v[2]++;\n",
2496 " *&v[3] = n;\n",
2497 " v4ui shifted = a >> b;\n",
2499 " shifted <<= b;\n",
2500 " *out = v + (v4si)shifted + (1 << b);\n",
2503 "}\n",
2504 "void refused(const v4si c) {\n",
2507 " c[0] = 1;\n",
2508 "}\n",
2509 ),
2510 );
2511 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
2512 assert!(result.messages[0].contains("assignment of read-only"), "{:?}", result.messages);
2513 }
2514
2515 #[test]
2527 fn a_record_that_asks_for_the_other_byte_order_swaps_every_scalar_it_holds() {
2528 let read = "int f(struct s *p) { return p->i; }\n";
2529 let big = "struct s { int i; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
2530 assert!(body(&format!("{big}{read}")).contains("bswap"), "{big}");
2531
2532 let armoured =
2533 "struct s { int i; } __attribute__((__scalar_storage_order__(\"big-endian\")));\n";
2534 assert!(body(&format!("{armoured}{read}")).contains("bswap"), "{armoured}");
2535
2536 let front = "struct __attribute__((scalar_storage_order(\"big-endian\"))) s { int i; };\n";
2537 assert!(body(&format!("{front}{read}")).contains("bswap"), "{front}");
2538
2539 let standard = "struct s { int i; } [[gnu::scalar_storage_order(\"big-endian\")]];\n";
2540 assert!(body(&format!("{standard}{read}")).contains("bswap"), "{standard}");
2541
2542 let same =
2543 "struct s { int i; } __attribute__((scalar_storage_order(\"little-endian\")));\n";
2544 assert!(!body(&format!("{same}{read}")).contains("bswap"), "{same}");
2545
2546 let byte = "struct s { char c; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
2548 let source = format!("{byte}int f(struct s *p) {{ return p->c; }}\n");
2549 assert!(!body(&source).contains("bswap"), "{byte}");
2550
2551 tast(concat!(
2552 "struct s { int i; short h; char c; }",
2553 " __attribute__((scalar_storage_order(\"big-endian\")));\n",
2554 "_Static_assert(sizeof(struct s) == 8 && _Alignof(struct s) == 4, \"s\");\n",
2555 "_Static_assert(__builtin_offsetof(struct s, h) == 4, \"s.h\");\n",
2556 "_Static_assert(__builtin_offsetof(struct s, c) == 6, \"s.c\");\n",
2557 ));
2558 }
2559
2560 #[test]
2566 fn a_bit_field_in_one_of_those_records_is_counted_from_the_top_of_its_bytes() {
2567 let members = "short i : 12; char c1 : 1; char c2 : 1; char c3 : 1; char c4 : 1;";
2568 let read = "int f(struct s *p) { return p->i; }\n";
2569 let plain = format!("struct s {{ {members} }};\n{read}");
2570 let reversed = format!(
2571 "struct s {{ {members} }} __attribute__((scalar_storage_order(\"big-endian\")));\n\
2572 {read}"
2573 );
2574 assert!(body(&plain).contains("shl"), "{}", body(&plain));
2575 assert!(!body(&plain).contains("bswap"), "{}", body(&plain));
2576 let built = body(&reversed);
2579 assert!(built.contains("bswap"), "{built}");
2580 assert!(!built.contains("shl"), "{built}");
2581 assert!(built.contains("ashr"), "{built}");
2582 }
2583
2584 #[test]
2591 fn the_address_of_a_scalar_stored_the_other_way_round_is_refused() {
2592 let opts = options();
2593 let record = "struct s { int i; int a[2]; struct in { int n; } w; }\n\
2594 __attribute__((scalar_storage_order(\"big-endian\")));\n";
2595 let taken = format!("{record}int *f(struct s *p) {{ return &p->i; }}\n");
2596 assert_eq!(
2597 run(&opts, &taken).messages,
2598 ["/main.c:3:30: error: cannot take address of scalar with reverse storage order \
2599 [E0712]"]
2600 );
2601 let element = format!("{record}int *f(struct s *p) {{ return &p->a[0]; }}\n");
2602 let messages = run(&opts, &element).messages;
2603 assert!(messages[0].contains("[E0712]"), "{messages:?}");
2604
2605 let whole = format!("{record}int *f(struct s *p) {{ return (int *) &p->w; }}\n");
2606 assert_eq!(run(&opts, &whole).messages, Vec::<String>::new(), "{whole}");
2607 }
2608
2609 #[test]
2613 fn a_storage_order_that_names_neither_end_is_refused_with_the_two_words_that_are_taken() {
2614 let opts = options();
2615 let wrong = "struct s { int i; } __attribute__((scalar_storage_order(\"middle\")));\n";
2616 assert_eq!(
2617 run(&opts, wrong).messages,
2618 ["/main.c:1:36: error: 'scalar_storage_order' argument must be one of \"big-endian\" \
2619 or \"little-endian\" [E0688]"]
2620 );
2621 let bare = "struct s { int i; } __attribute__((scalar_storage_order));\n";
2622 let messages = run(&opts, bare).messages;
2623 assert!(messages[0].contains("[E0688]"), "{messages:?}");
2624 }
2625
2626 #[test]
2636 fn packing_is_what_decides_whether_a_bit_field_may_straddle_its_own_storage() {
2637 assert_eq!(bit_field_byte("struct s { int x : 12; char y : 6; };"), 2);
2639 assert_eq!(
2640 bit_field_byte("struct s { int x : 12; char y : 6; } __attribute__((packed));"),
2641 1
2642 );
2643 assert_eq!(
2644 bit_field_byte("struct s { int x : 12; __attribute__((packed)) char y : 6; };"),
2645 1
2646 );
2647 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { int x : 12; char y : 6; };"), 1);
2648 assert_eq!(bit_field_byte("struct s { char x; int y : 30; };"), 4);
2650 assert_eq!(bit_field_byte("struct s { char x; int y : 30; } __attribute__((packed));"), 1);
2651 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { char x; int y : 30; };"), 1);
2653 assert_eq!(bit_field_byte("#pragma pack(2)\nstruct s { char x; int y : 30; };"), 1);
2654 }
2655
2656 fn bit_field_byte(record: &str) -> u64 {
2658 let source = format!("{record}\nint f(struct s *p) {{ return p->y; }}\n");
2659 let body = body(&source);
2660 let Some((before, _)) = body.split_once("ptr_add") else { return 0 };
2661 let (_, constant) = before.rsplit_once("iconst.i64 ").expect("an offset constant");
2662 constant.lines().next().expect("a line").trim().parse().expect("a byte offset")
2663 }
2664
2665 #[test]
2671 fn an_attribute_among_the_specifiers_is_kept_beside_the_ones_written_in_front() {
2672 tast(concat!(
2673 "struct a { char c; __attribute__((aligned(8))) int i; };\n",
2674 "_Static_assert(sizeof(struct a) == 16 && _Alignof(struct a) == 8, \"a\");\n",
2675 "_Static_assert(__builtin_offsetof(struct a, i) == 8, \"a.i\");\n",
2676 "struct b { char c; __attribute__((packed)) int i; };\n",
2677 "_Static_assert(sizeof(struct b) == 5 && _Alignof(struct b) == 1, \"b\");\n",
2678 "_Static_assert(__builtin_offsetof(struct b, i) == 1, \"b.i\");\n",
2679 "typedef struct { char c; int i; } __attribute__((packed)) c;\n",
2680 "_Static_assert(sizeof(c) == 5 && _Alignof(c) == 1, \"c\");\n",
2681 ));
2682 }
2683
2684 #[test]
2690 fn pragma_pack_caps_every_member_and_is_read_where_the_body_closes() {
2691 tast(concat!(
2692 "#pragma pack(1)\n",
2693 "struct A { char c; int i; };\n",
2694 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2695 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2696 "#pragma pack()\n",
2697 "struct B { char c; int i; };\n",
2698 "_Static_assert(sizeof(struct B) == 8 && _Alignof(struct B) == 4, \"B\");\n",
2699 "#pragma pack(2)\n",
2700 "struct C { char c; int i; double d; };\n",
2701 "_Static_assert(sizeof(struct C) == 14 && _Alignof(struct C) == 2, \"C\");\n",
2702 "_Static_assert(__builtin_offsetof(struct C, d) == 6, \"C.d\");\n",
2703 "struct K { char c; int i __attribute__((aligned(8))); };\n",
2705 "_Static_assert(sizeof(struct K) == 6 && _Alignof(struct K) == 2, \"K\");\n",
2706 "_Static_assert(__builtin_offsetof(struct K, i) == 2, \"K.i\");\n",
2707 "struct J { char c; int i; } __attribute__((aligned(8)));\n",
2709 "_Static_assert(sizeof(struct J) == 8 && _Alignof(struct J) == 8, \"J\");\n",
2710 "#pragma pack()\n",
2711 "#pragma pack(push, 1)\n",
2712 "struct D { char c; short s; };\n",
2713 "_Static_assert(sizeof(struct D) == 3 && _Alignof(struct D) == 1, \"D\");\n",
2714 "#pragma pack(pop)\n",
2715 "struct E { char c; short s; };\n",
2716 "_Static_assert(sizeof(struct E) == 4 && _Alignof(struct E) == 2, \"E\");\n",
2717 "struct H { char c;\n",
2719 "#pragma pack(1)\n",
2720 " int i; };\n",
2721 "_Static_assert(sizeof(struct H) == 5 && _Alignof(struct H) == 1, \"H\");\n",
2722 "#pragma pack(1)\n",
2723 "struct I { char c;\n",
2724 "#pragma pack()\n",
2725 " int i; };\n",
2726 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2727 "#pragma pack()\n",
2728 "#pragma pack(push, 8)\n",
2730 "#pragma pack(push, 1)\n",
2731 "struct P { char c; int i; };\n",
2732 "_Static_assert(sizeof(struct P) == 5 && _Alignof(struct P) == 1, \"P\");\n",
2733 "#pragma pack(pop)\n",
2734 "struct Q { char c; int i; };\n",
2735 "_Static_assert(sizeof(struct Q) == 8 && _Alignof(struct Q) == 4, \"Q\");\n",
2736 "#pragma pack(pop)\n",
2737 "#pragma pack(16)\n",
2739 "struct R { char c; int i; };\n",
2740 "_Static_assert(sizeof(struct R) == 8 && _Alignof(struct R) == 4, \"R\");\n",
2741 "#pragma pack()\n",
2742 "#pragma pack(1)\n",
2743 "struct S { char c; int i : 5; int j : 20; };\n",
2744 "_Static_assert(sizeof(struct S) == 5 && _Alignof(struct S) == 1, \"S\");\n",
2745 "union T { char c; int i; };\n",
2746 "_Static_assert(sizeof(union T) == 4 && _Alignof(union T) == 1, \"T\");\n",
2747 "#pragma pack()\n",
2748 ));
2749 }
2750
2751 #[test]
2755 fn a_pack_line_that_is_not_one_is_reported_in_the_words_gcc_uses() {
2756 let result = run(
2757 &options(),
2758 concat!(
2759 "#pragma pack 4\n",
2760 "#pragma pack(pop)\n",
2761 "#pragma pack(3)\n",
2762 "#pragma pack(1) junk\n",
2763 "#pragma pack(push, 1\n",
2764 "#pragma pack(x)\n",
2765 "#pragma pack(0)\n",
2768 "#pragma pack(push)\n",
2769 "struct s { char c; int i; };\n",
2770 "#pragma pack(pop)\n",
2771 "#pragma pack(pop, foo)\n",
2772 ),
2773 );
2774 let expected = [
2775 "missing `(` after `#pragma pack` - ignored",
2776 "`#pragma pack (pop)` encountered without matching `#pragma pack (push)`",
2777 "alignment must be a small power of two, not 3",
2778 "junk at end of `#pragma pack`",
2779 "malformed `#pragma pack(push[, id][, <n>])` - ignored",
2780 "unknown action `x` for `#pragma pack` - ignored",
2781 "`#pragma pack(pop, foo)` encountered without matching `#pragma pack(push, foo)`",
2782 ];
2783 assert_eq!(result.messages.len(), expected.len(), "{:?}", result.messages);
2784 for (message, want) in result.messages.iter().zip(expected) {
2785 assert!(message.contains(want), "expected {want:?} in {message:?}");
2786 }
2787 }
2788
2789 #[test]
2796 fn a_declaration_behind_an_empty_macro_is_not_eaten_by_the_pragma_above_it() {
2797 let result = run(
2798 &options(),
2799 concat!(
2800 "#pragma pack(push, 1)\n",
2801 "#pragma pack(pop)\n",
2802 "#define API\n",
2803 "API const char version[] = \"3.53.4\";\n",
2804 "const char *get(void) { return version; }\n",
2805 ),
2806 );
2807 assert!(result.messages.is_empty(), "{:?}", result.messages);
2808 }
2809
2810 #[test]
2814 fn the_wide_integer_answers_to_all_three_of_its_names() {
2815 let text = tast("__uint128_t a; __int128_t b; unsigned __int128 c;\n");
2816 assert!(text.contains("decl #0 a : unsigned __int128"), "{text}");
2817 assert!(text.contains("decl #1 b : __int128"), "{text}");
2818 assert!(text.contains("decl #2 c : unsigned __int128"), "{text}");
2819 }
2820
2821 #[test]
2822 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
2823 let text = tast("long f(int a, long b) { return a + b; }\n");
2827 assert!(text.contains("convert arithmetic"), "{text}");
2828 }
2829
2830 #[test]
2831 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
2832 for source in [
2833 "#error stop\n",
2834 "int f(void) { return 1 + ; }\n",
2835 "int f(void) { return undeclared; }\n",
2836 ] {
2837 let result = run(&options(), source);
2838 assert!(result.failed(), "expected this to fail:\n{source}");
2839 assert!(
2840 result.text().is_empty(),
2841 "a file that did not compile wrote a tree:\n{source}"
2842 );
2843 }
2844 }
2845
2846 #[test]
2847 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
2848 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
2852 assert_eq!(result.errors, 1, "{:?}", result.messages);
2853 }
2854
2855 #[test]
2856 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
2857 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
2861 assert_eq!(result.errors, 1, "{:?}", result.messages);
2862 }
2863
2864 #[test]
2865 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
2866 let source = "int f(void) { char c = 300; return c; }\n";
2867 let plain = run(&options(), source);
2868 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
2869 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
2870 assert!(!plain.text().is_empty(), "a warning is not a reason to write nothing");
2871
2872 let mut opts = options();
2873 opts.warnings_are_errors = true;
2874 let strict = run(&opts, source);
2875 assert!(strict.failed());
2876 assert!(strict.text().is_empty(), "and under -Werror it is a reason to write nothing");
2877 for message in &strict.messages {
2878 assert!(!message.contains("warning:"), "{message}");
2879 }
2880 }
2881
2882 #[test]
2883 fn w_drops_the_warning_before_werror_can_promote_it() {
2884 let source = "int f(void) { char c = 300; return c; }\n";
2885 let mut opts = options();
2886 opts.warnings = false;
2887 let quiet = run(&opts, source);
2888 assert_eq!(quiet.messages, Vec::<String>::new());
2889 assert_eq!(quiet.errors, 0);
2890 assert!(!quiet.text().is_empty(), "and the file still compiles");
2891
2892 opts.warnings_are_errors = true;
2895 let both = run(&opts, source);
2896 assert_eq!(both.messages, Vec::<String>::new());
2897 assert!(!both.failed(), "-w -Werror is not an error about a warning nobody saw");
2898 }
2899
2900 #[test]
2901 fn the_dialect_reaches_the_keywords_and_the_checking() {
2902 let source = "typeof(1) x;\n";
2905 let mut opts = options();
2906 opts.std = Std::C23;
2907 opts.gnu_extensions = false;
2908 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
2909
2910 opts.std = Std::C17;
2911 assert!(run(&opts, source).failed());
2912 }
2913
2914 #[test]
2915 fn asking_for_a_kind_that_is_not_written_yet_runs_the_front_end_and_writes_nothing() {
2916 let mut opts = options();
2917 opts.emit = EmitKind::Object;
2918 let result = run(&opts, "int x = 1;\n");
2919 assert!(!result.failed(), "{:?}", result.messages);
2920 assert!(result.text().is_empty());
2921 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
2924 }
2925
2926 fn mir(source: &str) -> String {
2928 let mut opts = options();
2929 opts.emit = EmitKind::MirFinal;
2930 let result = run(&opts, source);
2931 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2932 result.text().to_owned()
2933 }
2934
2935 #[test]
2941 fn a_function_goes_from_c_to_instructions_with_real_registers_in_them() {
2942 let text = mir("int add(int a, int b) { return a + b; }\n");
2943 assert!(text.starts_with("mfunc @add {"), "{text}");
2944 assert!(text.contains("x64.add_rr_32"), "{text}");
2945 assert!(text.contains("x64.ret"), "{text}");
2946 assert!(!text.contains('%'), "{text}");
2949 }
2950
2951 #[test]
2953 fn a_function_with_no_body_produces_no_machine_function() {
2954 let text = mir("int g(int);\nint f(int a) { return g(a); }\n");
2955 assert_eq!(text.matches("mfunc @").count(), 1, "{text}");
2956 assert!(text.contains("mfunc @f {"), "{text}");
2957 assert!(text.contains("x64.call"), "{text}");
2958 }
2959
2960 #[test]
2962 fn every_definition_in_the_file_is_generated_and_they_keep_their_order() {
2963 let text = mir("int a(int x) { return x; }\nint b(int x) { return x; }\n");
2964 let first = text.find("mfunc @a").expect("the first function");
2965 let second = text.find("mfunc @b").expect("the second function");
2966 assert!(first < second, "{text}");
2967 }
2968
2969 #[test]
2971 fn the_target_decides_which_convention_the_generated_code_follows() {
2972 let mut opts = options();
2973 opts.emit = EmitKind::MirFinal;
2974 let linux = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2975 assert!(linux.contains("$rdi"), "{linux}");
2976
2977 opts.target = "x86_64-pc-windows-msvc".parse::<Triple>().unwrap();
2978 let windows = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2979 assert!(windows.contains("$rcx"), "{windows}");
2980 assert!(!windows.contains("$rdi"), "{windows}");
2981 }
2982
2983 #[test]
2991 fn a_tagged_member_with_no_name_is_a_member_on_windows_and_nothing_on_linux() {
2992 let source = concat!(
2993 "struct S { union U { int i; void *p; }; unsigned long tymed; };\n",
2994 "int size(void) { return sizeof(struct S); }\n",
2995 "int f(struct S *s) { s->i = 1; return s->i; }\n",
2996 );
2997
2998 let mut opts = options();
2999 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3000 let windows = run(&opts, source);
3001 assert!(windows.messages.is_empty(), "{:?}", windows.messages);
3002
3003 let linux = run(&options(), source);
3004 assert_eq!(linux.messages.len(), 3, "{:?}", linux.messages);
3005 assert!(linux.messages[0].contains("does not declare anything"), "{:?}", linux.messages);
3006
3007 let mut opts = options();
3010 opts.ms_extensions = Some(true);
3011 let asked = run(&opts, source);
3012 assert!(asked.messages.is_empty(), "{:?}", asked.messages);
3013 }
3014
3015 #[test]
3017 fn a_target_this_has_no_back_end_for_is_reported_rather_than_generated() {
3018 let mut opts = options();
3019 opts.emit = EmitKind::MirFinal;
3020 opts.target = "riscv64-unknown-linux-gnu".parse::<Triple>().unwrap();
3021 let result = run(&opts, "int f(int a) { return a; }\n");
3022 assert!(result.failed());
3023 assert!(result.messages[0].contains("no back end for riscv64"), "{:?}", result.messages);
3024 assert!(result.text().is_empty());
3025 }
3026
3027 #[test]
3030 fn an_aarch64_target_is_written_as_aarch64_assembly() {
3031 let mut opts = options();
3032 opts.emit = EmitKind::Asm;
3033 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3034 let source = "int g(int);\nint f(int a, int b) { return g(a) + b; }\n";
3035 let result = run(&opts, source);
3036 assert!(!result.failed(), "{:?}", result.messages);
3037 let text = result.text();
3038 for line in ["stp x29, x30, [sp, #-16]!", "mov x29, sp", "bl g", "ldp x29, x30, [sp], #16"]
3039 {
3040 assert!(text.contains(line), "{line} is not in\n{text}");
3041 }
3042 assert!(!text.contains('%'), "{text}");
3043 }
3044
3045 #[test]
3048 fn an_aarch64_target_reaches_an_object_file() {
3049 let mut opts = options();
3050 opts.emit = EmitKind::Object;
3051 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3052 let source = concat!(
3053 "int g(int);\n",
3054 "int table[4] = {1, 2, 3, 4};\n",
3055 "int f(int a, int b) { return g(a) + table[b & 3]; }\n",
3056 );
3057 let result = run(&opts, source);
3058 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3059 let bytes = match result.artifact {
3060 Artifact::Object { bytes, defines } => {
3061 assert_eq!(defines, ["f", "table"]);
3062 bytes
3063 }
3064 other => panic!("expected an object, got {other:?}"),
3065 };
3066 assert_eq!(&bytes[..4], b"\x7fELF");
3067 assert_eq!(&bytes[18..20], &183u16.to_le_bytes(), "EM_AARCH64");
3068
3069 opts.debug_info = true;
3070 let result = run(&opts, source);
3071 assert!(result.failed());
3072 assert!(result.messages.iter().any(|m| m.contains("aarch64")), "{:?}", result.messages);
3073 }
3074
3075 #[test]
3078 fn the_aarch64_vector_type_names_are_declared_on_that_target_and_nowhere_else() {
3079 let mut opts = options();
3080 opts.emit = EmitKind::Asm;
3081 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3082 let source = "typedef __Float32x4_t f4;\n__SVFloat32_t sv(__SVFloat32_t, __SVBool_t);\n\
3083 int n = sizeof(f4) + sizeof(__Int8x8_t);\n\
3084 int f(f4 v) { int __Uint8x16_t = 3; return v[1] + __Uint8x16_t; }\n";
3085 let result = run(&opts, source);
3086 assert!(!result.failed(), "{:?}", result.messages);
3087 assert!(result.text().contains(".long\t24"), "{}", result.text());
3088 opts.target = "x86_64-unknown-linux-gnu".parse::<Triple>().unwrap();
3089 let result = run(&opts, "typedef __Float32x4_t f4;\n");
3090 assert!(result.failed());
3091 let result = run(&opts, "int __Float32x4_t = 1;\n");
3092 assert!(!result.failed(), "{:?}", result.messages);
3093 }
3094
3095 #[test]
3098 fn an_aarch64_result_in_memory_is_reached_through_x8() {
3099 let mut opts = options();
3100 opts.emit = EmitKind::Asm;
3101 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3102 let source = "struct big { long a, b, c; };\nstruct big make(long v);\n\
3103 long f(long v) { return make(v).c; }\n\
3104 struct big g(long v) { struct big b = { v, v, v }; return b; }\n";
3105 let result = run(&opts, source);
3106 assert!(!result.failed(), "{:?}", result.messages);
3107 let text = result.text();
3108 assert!(text.contains("x8"), "{text}");
3109 assert!(text.contains("bl make"), "{text}");
3110 }
3111
3112 #[test]
3115 fn an_aarch64_remainder_is_a_division_and_a_multiply_subtract() {
3116 let mut opts = options();
3117 opts.emit = EmitKind::Asm;
3118 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3119 let source = "int s(int a, int b) { return a % b; }\n\
3120 unsigned long u(unsigned long a, unsigned long b) { return a % b; }\n";
3121 let result = run(&opts, source);
3122 assert!(!result.failed(), "{:?}", result.messages);
3123 let text = result.text();
3124 let at = |what: &str| text.find(what).unwrap_or_else(|| panic!("{what} is not in\n{text}"));
3125 assert!(at("sdiv w") < at("msub w"), "{text}");
3126 assert!(at("udiv x") < at("msub x"), "{text}");
3127 }
3128
3129 #[test]
3132 fn an_aarch64_jump_table_is_reached_with_adr() {
3133 let mut opts = options();
3134 opts.emit = EmitKind::Asm;
3135 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3136 let source = "int f(int x) { switch (x) { case 0: return 10; case 1: return 21; \
3137 case 2: return 32; case 3: return 43; case 4: return 54; case 5: return 65; \
3138 case 6: return 76; case 7: return 87; case 8: return 98; case 9: return 9; \
3139 case 10: return 19; case 11: return 29; default: return 0; } }\n";
3140 let result = run(&opts, source);
3141 assert!(!result.failed(), "{:?}", result.messages);
3142 let text = result.text();
3143 let at = |what: &str| text.find(what).unwrap_or_else(|| panic!("{what} is not in\n{text}"));
3144 assert!(at("adr x") < at("ldrsw x"), "{text}");
3145 assert!(at("ldrsw x") < at("br x"), "{text}");
3146 assert!(text.contains("_j0:"), "{text}");
3147 assert!(text.contains(".long"), "{text}");
3148 }
3149
3150 #[test]
3154 fn an_aarch64_va_start_writes_the_five_fields_of_its_list() {
3155 let mut opts = options();
3156 opts.emit = EmitKind::Asm;
3157 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3158 let source = "typedef __builtin_va_list va_list;\n\
3159 int f(int n, ...) { va_list ap; __builtin_va_start(ap, n); \
3160 int x = __builtin_va_arg(ap, int); double d = __builtin_va_arg(ap, double); \
3161 __builtin_va_end(ap); return x + (int)d; }\n";
3162 let result = run(&opts, source);
3163 assert!(!result.failed(), "{:?}", result.messages);
3164 let text = result.text();
3165 assert!(text.contains("#-56"), "{text}");
3166 assert!(text.contains("#-128"), "{text}");
3167 assert!(text.contains("#24]"), "{text}");
3168 assert!(text.contains("#28]"), "{text}");
3169 assert!(text.contains("str q"), "{text}");
3170 }
3171
3172 #[test]
3175 fn an_aarch64_long_double_is_a_quad_in_a_vector_register() {
3176 let mut opts = options();
3177 opts.emit = EmitKind::Asm;
3178 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3179 let source = "void f(long double *p, long double x) { *p = *p + x; }\n";
3180 let result = run(&opts, source);
3181 assert!(!result.failed(), "{:?}", result.messages);
3182 let text = result.text();
3183 assert!(text.contains("ldr q"), "{text}");
3184 assert!(text.contains("str q"), "{text}");
3185 assert!(text.contains("__addtf3"), "{text}");
3186 }
3187
3188 #[test]
3191 fn an_aarch64_thread_local_is_reached_through_tpidr_el0() {
3192 let mut opts = options();
3193 opts.emit = EmitKind::Asm;
3194 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3195 let source = "__thread int n;\nint *f(void) { return &n; }\n\
3196 void *g(void) { return __builtin_thread_pointer(); }\n";
3197 let result = run(&opts, source);
3198 assert!(!result.failed(), "{:?}", result.messages);
3199 let text = result.text();
3200 assert!(text.contains(":gottprel:n"), "{text}");
3201 assert!(text.contains(":gottprel_lo12:n]"), "{text}");
3202 assert_eq!(text.matches("mrs x").count(), 2, "{text}");
3203 assert!(text.contains("tpidr_el0"), "{text}");
3204 }
3205
3206 #[test]
3209 fn a_darwin_thread_local_is_reached_through_its_descriptor() {
3210 let source = "__thread int n = 5;\nint *f(void) { return &n; }\n";
3211 for (triple, wanted) in [
3212 ("aarch64-apple-darwin", &["_n@TLVPPAGE\n", "_n@TLVPPAGEOFF]\n", "\tblr x"][..]),
3213 ("x86_64-apple-darwin", &["_n@TLVP(%rip), %rdi\n", "\tcall\t*%"][..]),
3214 ] {
3215 let mut opts = options();
3216 opts.emit = EmitKind::Asm;
3217 opts.target = triple.parse::<Triple>().unwrap();
3218 let result = run(&opts, source);
3219 assert!(!result.failed(), "{triple}: {:?}", result.messages);
3220 let text = result.text();
3221 for want in wanted {
3222 assert!(text.contains(want), "{triple} wanted {want:?}:\n{text}");
3223 }
3224 assert!(text.contains("\n_n:\n\t.quad\t__tlv_bootstrap\n"), "{text}");
3225 assert!(!text.contains("tpidr_el0") && !text.contains("%fs"), "{text}");
3226 }
3227 }
3228
3229 #[test]
3231 fn the_thread_pointer_is_refused_on_darwin() {
3232 let mut opts = options();
3233 opts.emit = EmitKind::Asm;
3234 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3235 let result = run(&opts, "void *f(void) { return __builtin_thread_pointer(); }\n");
3236 assert!(result.failed());
3237 assert!(result.messages[0].contains("thread pointer"), "{:?}", result.messages);
3238 }
3239
3240 #[test]
3243 fn a_darwin_variadic_definition_saves_nothing_and_walks_the_stack() {
3244 let mut opts = options();
3245 opts.emit = EmitKind::Asm;
3246 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3247 let source = "int f(int n, ...) { __builtin_va_list ap; __builtin_va_start(ap, n);\n\
3248 int r = __builtin_va_arg(ap, int); __builtin_va_end(ap); return r; }\n";
3249 let result = run(&opts, source);
3250 assert!(!result.failed(), "{:?}", result.messages);
3251 let text = result.text();
3252 assert!(!text.contains("str q"), "{text}");
3253 assert!(!text.contains("x7"), "{text}");
3254 }
3255
3256 #[test]
3259 fn a_darwin_call_puts_its_variadic_arguments_in_memory() {
3260 let mut opts = options();
3261 opts.emit = EmitKind::Asm;
3262 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3263 let source = "int printf(const char *, ...);\n\
3264 int g(double x) { return printf(\"%d %f\", 7, x); }\n";
3265 let result = run(&opts, source);
3266 assert!(!result.failed(), "{:?}", result.messages);
3267 let text = result.text();
3268 assert!(text.contains("str d0, [sp, #8]"), "{text}");
3269 }
3270
3271 #[test]
3275 fn a_darwin_listing_is_one_apples_assembler_reads() {
3276 let mut opts = options();
3277 opts.emit = EmitKind::Asm;
3278 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3279 let source = "extern int ext;\n\
3280 int g[4];\n\
3281 int f(int i) { return g[i] + ext; }\n";
3282 let result = run(&opts, source);
3283 assert!(!result.failed(), "{:?}", result.messages);
3284 let text = result.text();
3285 assert!(text.contains(", _g@PAGE\n"), "{text}");
3286 assert!(text.contains(", _g@PAGEOFF\n"), "{text}");
3287 assert!(text.contains(", _ext@GOTPAGE\n"), "{text}");
3288 assert!(text.contains(", _ext@GOTPAGEOFF]\n"), "{text}");
3289 assert!(!text.contains(":lo12:"), "{text}");
3290 assert!(text.contains("\t.globl\t_g\n\t.zerofill\t__DATA,__bss,_g,16,2\n"), "{text}");
3291 }
3292
3293 #[test]
3299 fn a_signed_char_on_aarch64_is_widened_with_its_sign_before_it_is_added_to() {
3300 for target in ["aarch64-linux-gnu", "aarch64-apple-darwin"] {
3301 let mut opts = options();
3302 opts.emit = EmitKind::Asm;
3303 opts.target = target.parse::<Triple>().unwrap();
3304 let source = "int f(signed char *p) { return *p + 1; }\n\
3305 unsigned g(unsigned short *p) { return *p + 1u; }\n";
3306 let result = run(&opts, source);
3307 assert!(!result.failed(), "{:?}", result.messages);
3308 let text = result.text();
3309 let signed = text.contains("\tsxtb w") || text.contains("\tldrsb w");
3310 assert!(signed, "{target}: {text}");
3311 }
3312 }
3313
3314 #[test]
3326 fn a_construct_the_back_end_cannot_reach_yet_is_reported_against_its_function() {
3327 let mut opts = options();
3328 opts.emit = EmitKind::MirFinal;
3329 let source = "void a(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
3330 s; s.x = 1; v[0] = s.x; }\n\
3331 void b(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
3332 s; s.x = 1; v[0] = s.x; }\n";
3333 let result = run(&opts, source);
3334 assert!(result.failed());
3335 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
3336 assert!(result.messages[0].contains("cannot generate code for 'a'"), "{:?}", result);
3337 assert!(result.messages[0].contains("wants more alignment"), "{:?}", result);
3338 assert!(result.messages[1].contains("cannot generate code for 'b'"), "{:?}", result);
3339 assert!(result.text().is_empty());
3340 }
3341
3342 #[test]
3350 fn a_variable_length_array_walks_its_pages_where_every_page_of_the_frame_is_to_be_touched() {
3351 let mut opts = options();
3352 opts.emit = EmitKind::MirFinal;
3353 let source = "void a(int n) { int v[n]; v[0] = 1; }\n";
3354 let plain = run(&opts, source);
3355 assert!(!plain.failed(), "{:?}", plain.messages);
3356 assert!(!plain.text().contains("cmp_set_a_64"), "{}", plain.text());
3357
3358 opts.stack_clash = true;
3359 let result = run(&opts, source);
3360 assert!(!result.failed(), "{:?}", result.messages);
3361 assert!(result.text().contains("cmp_set_a_64"), "{}", result.text());
3362 assert!(result.text().contains("or_mi_8"), "{}", result.text());
3363 }
3364
3365 #[test]
3375 fn a_function_that_keeps_a_frame_pointer_on_windows_reaches_an_object_file() {
3376 let mut opts = options();
3377 opts.emit = EmitKind::Object;
3378 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3379 let source = concat!(
3380 "void use(void *p);\n",
3381 "void array(int n) { int v[n]; v[0] = 1; use(v); }\n",
3382 "void taken(unsigned long n) { use(__builtin_alloca(n)); }\n",
3383 );
3384 let result = run(&opts, source);
3385 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3386 let bytes = match result.artifact {
3387 Artifact::Object { bytes, .. } => bytes,
3388 other => panic!("expected an object, got {other:?}"),
3389 };
3390 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3391
3392 let mut opts = options();
3395 opts.emit = EmitKind::Object;
3396 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3397 }
3398
3399 #[test]
3410 fn the_address_of_a_function_this_file_only_declares_reaches_a_windows_object() {
3411 let source = concat!(
3412 "void other(void *p);\n",
3413 "void takes(void (*f)(void *));\n",
3414 "void (*held)(void *);\n",
3415 "void pass(void) { takes(other); }\n",
3416 "void keep(void) { held = other; }\n",
3417 "void call(void) { other(0); }\n",
3418 );
3419 let mut opts = options();
3420 opts.emit = EmitKind::Object;
3421 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3422 let result = run(&opts, source);
3423 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3424 let bytes = match result.artifact {
3425 Artifact::Object { bytes, .. } => bytes,
3426 other => panic!("expected an object, got {other:?}"),
3427 };
3428 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3429
3430 let mut opts = options();
3433 opts.emit = EmitKind::Object;
3434 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3435 }
3436
3437 #[test]
3451 fn an_opcode_with_no_name_in_the_rule_language_is_named_by_its_own_spelling() {
3452 let mut opts = options();
3453 opts.emit = EmitKind::MirFinal;
3454 let source =
3455 "long double f(int a) {\n __int128 wide = a;\n return (long double) wide;\n}\n";
3456 let result = run(&opts, source);
3457 assert!(result.failed());
3458 assert!(
3459 result.messages[0].contains("no rule lowers a `sext` producing a `i128`"),
3460 "{result:?}"
3461 );
3462 assert!(result.messages[0].contains(":2:"), "the line the widening is on: {result:?}");
3463 assert!(!result.messages[0].contains("this instruction"), "{result:?}");
3464 }
3465
3466 #[test]
3468 fn the_note_on_unfinished_work_points_at_the_issues_rather_than_at_the_plan() {
3469 let mut opts = options();
3470 opts.emit = EmitKind::MirFinal;
3471 let source = "long double f(int a) { __int128 wide = a; return (long double) wide; }\n";
3472 let result = run(&opts, source);
3473 assert!(result.failed());
3474 let note = result.messages.iter().find(|line| line.contains("note:")).expect("a note");
3475 assert!(note.contains("https://github.com/tamnd/rucc/issues"), "{note}");
3476 assert!(!note.contains("spec/17-milestones.md"), "{note}");
3477 }
3478
3479 #[test]
3481 fn the_frame_flags_on_the_command_line_reach_the_generated_frame() {
3482 let source = "int f(int a) { return a; }\n";
3483 assert!(!mir(source).contains("$rbp"), "a leaf needs no frame pointer by default");
3484
3485 let mut opts = options();
3486 opts.emit = EmitKind::MirFinal;
3487 opts.frame_pointer = true;
3488 let kept = run(&opts, source).text().to_owned();
3489 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
3490 }
3491
3492 fn asm(source: &str) -> String {
3494 let mut opts = options();
3495 opts.emit = EmitKind::Asm;
3496 let result = run(&opts, source);
3497 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3498 result.text().to_owned()
3499 }
3500
3501 #[test]
3508 fn a_function_goes_from_c_to_assembly_an_assembler_would_take() {
3509 let text = asm("int add(int a, int b) { return a + b; }\n");
3510 assert!(text.contains("\t.globl\tadd\n"), "{text}");
3511 assert!(text.contains("\t.type\tadd, @function\n"), "{text}");
3512 assert!(text.contains("\nadd:\n"), "{text}");
3513 assert!(text.contains("\taddl\t"), "{text}");
3514 assert!(text.contains("\tret\n"), "{text}");
3515 assert!(text.contains("\t.size\tadd, .-add\n"), "{text}");
3516 assert!(text.contains(".note.GNU-stack"), "{text}");
3519 }
3520
3521 #[test]
3527 fn a_call_through_a_function_pointer_goes_through_the_register_it_is_in() {
3528 let text = asm("int g(int);\nint f(int (*p)(int), int a) { return p(a) + g(a); }\n");
3529 assert!(text.contains("\tcall\t*%"), "{text}");
3530 assert!(text.contains("\tcall\tg\n"), "{text}");
3531 assert!(text.contains("%rdi"), "{text}");
3535 }
3536
3537 #[test]
3541 fn the_address_of_a_global_is_read_from_the_instruction_pointer() {
3542 let text = asm("extern int counter;\nint f(void) { return counter; }\n");
3543 assert!(text.contains("\tmovl\tcounter(%rip), %eax\n"), "{text}");
3544 }
3545
3546 #[test]
3555 fn a_branch_on_a_comparison_jumps_on_the_opposite_of_what_it_compared() {
3556 let arms = "return 1; return 2;";
3557 let signed = [("==", "jne"), ("!=", "je"), ("<", "jge"), ("<=", "jg"), (">", "jle")];
3558 for (operator, jump) in signed.into_iter().chain([(">=", "jl")]) {
3559 let text = asm(&format!("int f(int a, int b) {{ if (a {operator} b) {arms} }}\n"));
3560 assert!(
3561 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
3562 "{operator}: {text}"
3563 );
3564 assert!(!text.contains("\tset"), "{operator}: {text}");
3565 assert!(!text.contains("\ttest"), "{operator}: {text}");
3566 }
3567 let unsigned = [("<", "jae"), ("<=", "ja"), (">", "jbe"), (">=", "jb")];
3568 for (operator, jump) in unsigned {
3569 let source =
3570 format!("int f(unsigned a, unsigned b) {{ if (a {operator} b) {arms} }}\n");
3571 let text = asm(&source);
3572 assert!(
3573 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
3574 "{operator}: {text}"
3575 );
3576 }
3577
3578 let text = asm("int f(int a) { if (a < 7) return 1; return 2; }\n");
3581 assert!(text.contains("\tcmpl\t$7, %edi\n\tjge\t"), "{text}");
3582 }
3583
3584 #[test]
3590 fn a_comparison_whose_answer_the_program_wanted_still_writes_a_byte() {
3591 let text = asm("int f(int a, int b) { return a < b; }\n");
3592 assert!(text.contains("\tsetl\t"), "{text}");
3593 }
3594
3595 fn optimized(source: &str) -> String {
3597 let mut opts = options();
3598 opts.emit = EmitKind::Asm;
3599 opts.opt_level = rucc_session::OptLevel::O2;
3600 let result = run(&opts, source);
3601 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3602 result.text().to_owned()
3603 }
3604
3605 #[test]
3615 fn a_switch_whose_arms_are_a_function_of_the_label_is_a_range_check_and_arithmetic() {
3616 let arms: String =
3617 (0..16).map(|k| format!("case {k}: return {};", k + 1)).collect::<Vec<_>>().join(" ");
3618 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3619 assert!(text.contains("\tcmpl\t$15, %edi\n\tja\t"), "{text}");
3620 assert!(text.contains("\taddl\t$1, %edi"), "{text}");
3621 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
3622 }
3623
3624 #[test]
3632 fn a_dense_switch_whose_arms_are_not_a_line_is_a_load_from_a_table() {
3633 let arms: String = (0..16)
3634 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
3635 .collect::<Vec<_>>()
3636 .join(" ");
3637 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3638 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
3639 assert!(!text.contains("\tjmp\t*"), "{text}");
3640 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
3641 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
3642 let section = text[..text.find("CSWTCH.0:").unwrap_or(0)].rfind("\t.section\t.rodata");
3643 assert!(section.is_some(), "{text}");
3644 assert_eq!(table.matches("\t.long\t").count(), 16, "{text}");
3645 assert!(table.contains("\t.long\t100\n"), "{text}");
3646 }
3647
3648 #[test]
3654 fn a_switch_whose_arms_give_strings_is_a_table_of_how_far_away_they_are() {
3655 let text = optimized(
3656 "const char *f(int k) { switch (k) { case 0: return \"zero\"; \
3657 case 1: return \"one\"; case 2: return \"two\"; case 3: return \"three\"; } \
3658 return \"many\"; }\n",
3659 );
3660 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
3661 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
3662 assert!(!text.contains(".data.rel.ro"), "{text}");
3663 let at = text.find("CSWTCH.0:").expect("the table is in the output");
3664 assert!(text[..at].rfind("\t.section\t.rodata").is_some(), "{text}");
3665 let table = &text[at..];
3666 assert_eq!(table.matches(" - .\n").count(), 4, "{text}");
3667 assert!(table.contains("\t.long\t.Lstr.1+4 - .\n"), "{text}");
3668 }
3669
3670 #[test]
3676 fn a_table_at_os_has_cells_as_narrow_as_its_answers() {
3677 let arms: String = (0..16)
3678 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
3679 .collect::<Vec<_>>()
3680 .join(" ");
3681 let mut opts = options();
3682 opts.emit = EmitKind::Asm;
3683 opts.opt_level = rucc_session::OptLevel::Os;
3684 let result = run(&opts, &format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3685 assert_eq!(result.messages, Vec::<String>::new());
3686 let text = result.text();
3687 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
3688 assert_eq!(table.matches("\t.byte\t").count(), 16, "{text}");
3689 assert!(text.contains("\tmovsbl\t"), "{text}");
3690 }
3691
3692 #[test]
3699 fn a_table_that_covers_its_operand_has_no_range_check() {
3700 let text = optimized(
3701 "int f(unsigned x) { switch (x & 3) { case 0: return 5; case 1: return 9; \
3702 case 2: return 2; case 3: return 7; } return -1; }\n",
3703 );
3704 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
3705 assert!(!text.contains("\tcmp"), "{text}");
3706 assert!(!text.contains("$-1"), "{text}");
3707 }
3708
3709 #[test]
3714 fn a_store_to_a_local_the_loop_just_read_is_a_conditional_move() {
3715 let text = optimized(
3716 "int f(const int *v, int n, int k) { int best[8] = {0}; \
3717 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; \
3718 return best[k & 7]; }\n",
3719 );
3720 assert!(text.contains("\tcmovgl"), "{text}");
3721 assert!(!text.contains("\tset"), "{text}");
3722 assert!(!text.contains("\ttestb"), "{text}");
3723 }
3724
3725 #[test]
3727 fn a_store_to_a_global_the_loop_just_read_keeps_its_branch() {
3728 let text = optimized(
3729 "int best[8]; void f(const int *v, int n) { \
3730 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; }\n",
3731 );
3732 assert!(!text.contains("\tcmov"), "{text}");
3733 }
3734
3735 #[test]
3743 fn a_conversion_from_a_constant_double_is_the_number_it_converts_to() {
3744 let text = optimized("int f(void) { double d = 2.75; return (int) d; }\n");
3745 assert!(text.contains("movl\t$2, %eax"), "{text}");
3746 assert!(!text.contains("cvttsd2si"), "{text}");
3747 }
3748
3749 #[test]
3757 fn a_slot_of_a_read_only_table_is_the_value_the_table_holds() {
3758 let text =
3759 optimized("static const int t[4] = {10, 20, 30, 40};\nint f(void) { return t[2]; }\n");
3760 assert!(text.contains("movl\t$30, %eax"), "{text}");
3761 assert!(!text.contains("t(%rip)"), "{text}");
3762 }
3763
3764 #[test]
3767 fn a_byte_of_a_read_only_string_is_the_byte_the_string_spells() {
3768 let text = optimized("static const char s[] = \"abc\";\nint f(void) { return s[1]; }\n");
3769 assert!(text.contains("movl\t$98, %eax"), "{text}");
3770 }
3771
3772 #[test]
3776 fn a_table_that_is_not_read_only_keeps_its_load() {
3777 let text = optimized(
3778 "static int t[4] = {10, 20, 30, 40};\nvoid g(int x) { t[2] = x; }\nint f(void) { return t[2]; }\n",
3779 );
3780 assert!(!text.contains("movl\t$30, %eax"), "{text}");
3781 }
3782
3783 #[test]
3790 fn a_call_guarded_by_a_condition_a_read_only_object_settles_is_not_emitted() {
3791 let text = optimized(
3792 "void link_error(void);\nconst double one = 1.0;\nint main(void) { if ((int) one != 1) link_error(); return 0; }\n",
3793 );
3794 assert!(!text.contains("call\tlink_error"), "{text}");
3795 }
3796
3797 #[test]
3799 fn a_cast_between_a_pointer_and_an_integer_leaves_the_value_where_it_is() {
3800 let text = asm("long f(void *p) { return (long)p; }\n");
3801 for line in text.lines().filter(|line| line.starts_with('\t') && !line.contains('.')) {
3806 let mnemonic = line.split_whitespace().next().unwrap_or("");
3807 assert!(matches!(mnemonic, "movq" | "ret"), "{line} in\n{text}");
3808 }
3809 }
3810
3811 #[test]
3815 fn an_argument_past_the_last_register_is_read_out_of_the_caller_s_stack() {
3816 let six = "long a, long b, long c, long d, long e, long f";
3817 let text = asm(&format!("long f({six}, long g, long h) {{ return g + h; }}\n"));
3818
3819 assert!(text.contains("\tmovq\t8(%rsp), "), "{text}");
3826 assert!(text.contains("\taddq\t16(%rsp), "), "{text}");
3827
3828 let narrow = asm(&format!("int f({six}, int g) {{ return g; }}\n"));
3832 assert!(narrow.contains("\tmovl\t8(%rsp), "), "{narrow}");
3833 let eight =
3834 "double a, double b, double c, double d, double e, double f, double g, double h";
3835 let float = asm(&format!("double f({eight}, double i) {{ return i; }}\n"));
3836 assert!(float.contains("\tmovsd\t8(%rsp), "), "{float}");
3837 }
3838
3839 #[test]
3842 fn a_call_writes_the_arguments_with_no_register_left_at_the_stack_pointer() {
3843 let six = "1, 2, 3, 4, 5, 6";
3844 let decl = "long g(long, long, long, long, long, long, long, long);\n";
3845 let text = asm(&format!("{decl}long f(void) {{ return g({six}, 7, 8); }}\n"));
3846
3847 assert!(text.contains("\tmovq\t%"), "{text}");
3848 assert!(text.contains(", (%rsp)\n"), "{text}");
3849 assert!(text.contains(", 8(%rsp)\n"), "{text}");
3850 assert!(text.contains("\tsubq\t$"), "{text}");
3852
3853 let narrow = "int g(int, int, int, int, int, int, int);\n";
3855 let text = asm(&format!("{narrow}int f(void) {{ return g({six}, 7); }}\n"));
3856 assert!(text.contains("\tmovl\t%"), "{text}");
3857 assert!(text.contains(", (%rsp)\n"), "{text}");
3858 }
3859
3860 #[test]
3863 fn a_variadic_call_counts_registers_and_not_arguments() {
3864 let nine = "1., 2., 3., 4., 5., 6., 7., 8., 9.";
3865 let decl = "int g(int, ...);\n";
3866 let text = asm(&format!("{decl}int f(void) {{ return g(0, {nine}); }}\n"));
3867
3868 assert!(text.contains("\tmovl\t$8, "), "eight registers, not nine: {text}");
3869 assert!(text.contains("\tmovsd\t%"), "{text}");
3870 assert!(text.contains(", (%rsp)\n"), "{text}");
3871 }
3872
3873 #[test]
3878 fn a_variadic_function_writes_the_argument_registers_it_was_handed_into_its_frame() {
3879 let body =
3880 "__builtin_va_list ap; __builtin_va_start(ap, n); __builtin_va_end(ap); return n;";
3881 let text = asm(&format!("int f(int n, ...) {{ {body} }}\n"));
3882
3883 let stores = |mnemonic: &str| text.matches(&format!("\t{mnemonic}\t%")).count();
3886 assert!(text.contains(", 8(%r"), "the second slot, not the first: {text}");
3887 assert!(!text.contains(", 0(%r"), "{text}");
3888 assert_eq!(stores("movaps"), 8, "every vector register: {text}");
3891 assert_eq!(stores("movsd"), 0, "and the whole of each one: {text}");
3892
3893 assert!(text.contains("\tsubq\t$"), "{text}");
3895 }
3896
3897 #[test]
3900 fn va_start_writes_the_four_fields_the_psabi_describes() {
3901 let start = "__builtin_va_list ap; __builtin_va_start(ap, d);";
3902 let params = "int a, int b, int c, double d";
3903 let text = asm(&format!("int f({params}, ...) {{ {start} return a; }}\n"));
3904
3905 assert!(text.contains(" movl $24, "), "{text}");
3909 assert!(text.contains(" movl $64, "), "{text}");
3910 assert!(text.contains(", 8(%r"), "{text}");
3914 assert!(text.contains(", 16(%r"), "{text}");
3915 let frame: u32 = text
3916 .lines()
3917 .find_map(|line| line.trim().strip_prefix("subq $")?.split(',').next()?.parse().ok())
3918 .expect("a variadic function takes a frame for the save area");
3919 let above = |line: &str| {
3920 let at: u32 = line.trim().strip_prefix("leaq ")?.split('(').next()?.parse().ok()?;
3921 Some(at > frame)
3922 };
3923 assert!(text.lines().filter_map(above).any(|it| it), "{frame}: {text}");
3924 }
3925
3926 #[test]
3929 fn va_arg_branches_on_whether_the_argument_is_still_in_the_save_area() {
3930 let read = "__builtin_va_list ap; __builtin_va_start(ap, n);";
3931 let ints = format!("int f(int n, ...) {{ {read} return __builtin_va_arg(ap, int); }}\n");
3932 let text = asm(&ints);
3933
3934 assert!(text.contains("$40, "), "{text}");
3937 assert!(text.contains(" cmpl "), "{text}");
3938 assert!(text.contains(" ja "), "{text}");
3942
3943 let arg = "__builtin_va_arg(ap, double)";
3944 let text = asm(&format!("double f(int n, ...) {{ {read} return {arg}; }}\n"));
3945 assert!(text.contains("$160, "), "the last vector slot: {text}");
3946 }
3947
3948 #[test]
3951 fn a_structure_assignment_is_a_move_for_each_word_of_it() {
3952 let decl = "struct pair { long a, b; };\n";
3953 let body = "struct pair p = *q; return p.a + p.b;";
3954 let text = asm(&format!("{decl}long f(struct pair *q) {{ {body} }}\n"));
3955
3956 assert!(!text.contains("memcpy"), "nothing calls the library: {text}");
3957 assert!(!text.contains("\tcall"), "{text}");
3958 assert!(text.matches("\tmovq\t").count() >= 4, "two words each way: {text}");
3960 }
3961
3962 #[test]
3965 fn how_wide_a_word_of_a_copy_is_follows_the_alignment() {
3966 let decl = "struct bytes { char a[8]; };\n";
3967 let body = "struct bytes p = *q; return p.a[0];";
3968 let text = asm(&format!("{decl}int f(struct bytes *q) {{ {body} }}\n"));
3969
3970 assert!(text.matches("\tmovb\t").count() >= 16, "a byte at a time: {text}");
3972 }
3973
3974 #[test]
3977 fn the_part_of_an_initialiser_that_names_nothing_is_stored_as_zero() {
3978 let decl = "struct wide { long a, b, c; };\n";
3979 let text = asm(&format!("{decl}long f(void) {{ struct wide w = {{ 7 }}; return w.c; }}\n"));
3980
3981 assert!(!text.contains("memset"), "nothing calls the library: {text}");
3982 assert!(text.contains("\tmovq\t$0, ") || text.contains("\txorl\t"), "the zero: {text}");
3987 }
3988
3989 #[test]
3992 fn a_copy_too_large_to_unroll_calls_the_runtime() {
3993 let decl = "struct huge { char a[4096]; };\n";
3994 let mut opts = options();
3995 opts.emit = EmitKind::Asm;
3996 let source = format!("{decl}void f(struct huge *p, struct huge *q) {{ *p = *q; }}\n");
3997 let result = run(&opts, &source);
3998 assert!(!result.failed(), "{:?}", result.messages);
3999 let text = result.text();
4000 assert!(text.contains("call") && text.contains("memcpy"), "{text}");
4001 assert!(text.contains("4096"), "the size travels: {text}");
4004 }
4005
4006 #[test]
4013 fn a_structure_too_large_to_unroll_is_copied_into_the_argument_area_by_the_runtime() {
4014 let decl = "struct huge { char a[4096]; };\nint take(struct huge);\n";
4015 let text = asm(&format!("{decl}int f(struct huge *p) {{ return take(*p); }}\n"));
4016
4017 let copy = text.find("call\tmemcpy").expect("the copy");
4018 let call = text.find("call\ttake").expect("the call");
4019 assert!(copy < call, "the copy comes first: {text}");
4020 assert!(text.contains("movq\t%rsp, %rdi"), "the destination: {text}");
4025 assert!(text.contains("$4096, %edx"), "the size: {text}");
4026 }
4027
4028 #[test]
4031 fn a_realigned_frame_reads_them_through_the_frame_pointer() {
4032 let six = "long a, long b, long c, long d, long e, long f";
4033 let body = "_Alignas(32) long wide[4]; wide[0] = g; return wide[0];";
4034 let text = asm(&format!("long f({six}, long g) {{ {body} }}\n"));
4035
4036 assert!(text.contains("\tandq\t$-32, %rsp"), "{text}");
4040 assert!(text.contains("\tmovq\t16(%rbp), "), "{text}");
4041 assert!(!text.contains("\tmovq\t16(%rsp), "), "{text}");
4042 }
4043
4044 #[test]
4046 fn the_target_decides_how_the_assembly_is_spelled() {
4047 let mut opts = options();
4048 opts.emit = EmitKind::Asm;
4049 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
4050 let text = run(&opts, "int f(void) { return 0; }\n").text().to_owned();
4051 assert!(text.contains("__TEXT,__text"), "{text}");
4052 assert!(text.contains("\n_f:\n"), "{text}");
4053 assert!(!text.contains(".note.GNU-stack"), "{text}");
4054 }
4055
4056 fn obj(source: &str) -> Vec<u8> {
4058 let mut opts = options();
4059 opts.emit = EmitKind::Object;
4060 let result = run(&opts, source);
4061 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4062 match result.artifact {
4063 Artifact::Object { bytes, .. } => bytes,
4064 other => panic!("expected an object, got {other:?}"),
4065 }
4066 }
4067
4068 #[test]
4074 fn a_function_goes_from_c_to_an_object_a_linker_would_take() {
4075 let bytes = obj("int add(int a, int b) { return a + b; }\n");
4076 assert_eq!(&bytes[..4], b"\x7fELF", "an object file starts by saying it is one");
4077 let text = asm("int add(int a, int b) { return a + b; }\n");
4078 assert!(
4079 text.contains("\taddl\t"),
4080 "and the listing of it is the same instructions:\n{text}"
4081 );
4082 }
4083
4084 #[test]
4086 fn a_variable_goes_from_c_to_the_section_it_belongs_in() {
4087 let text = asm("int counter = 42;\nstatic int hidden;\nconst int fixed = 7;\n");
4088 assert!(text.contains("\t.data\n\t.globl\tcounter\n"), "{text}");
4089 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
4090 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
4091 assert!(text.contains("\t.bss\n\t.p2align\t2\n"), "{text}");
4094 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
4095 assert!(!text.contains(".globl\thidden"), "{text}");
4096 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4099 }
4100
4101 #[test]
4108 fn a_bit_field_initializer_writes_every_byte_of_the_value_and_not_only_the_ones_that_are_set() {
4109 let text = asm("struct s { unsigned f : 20; } x = { 0x12300 };\n");
4110 assert!(text.contains("\t.data\n"), "there is something to write: {text}");
4111 assert!(text.contains("\nx:\n\t.ascii\t\"\\000#\\001\"\n"), "and it is the value: {text}");
4112
4113 let text = asm("struct s { unsigned a : 8; unsigned b : 8; } x = { 0, 3 };\n");
4116 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\003\"\n"), "{text}");
4117
4118 let text = asm("struct s { unsigned long long f : 40; } x = { 0x100000 };\n");
4121 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\000\\020\"\n\t.space\t5\n"), "{text}");
4122
4123 let text = asm("struct s { unsigned f : 20; } x = { 0 };\n");
4125 assert!(text.contains("\t.bss\n"), "an object of zeroes is zeroes: {text}");
4126 assert!(text.contains("\nx:\n\t.space\t4\n"), "{text}");
4127 }
4128
4129 #[test]
4131 fn a_string_literal_is_a_variable_with_a_name_no_program_could_write() {
4132 let text = asm("const char *f(void) { return \"hi\"; }\n");
4133 assert!(text.contains("\t.ascii\t\"hi\\000\"\n"), "{text}");
4134 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4135 let label = text
4136 .lines()
4137 .find(|line| line.starts_with(".Lstr"))
4138 .unwrap_or_else(|| panic!("a label for the literal in\n{text}"));
4139 assert!(!text.contains(&format!(".globl\t{}", label.trim_end_matches(':'))), "{text}");
4140 }
4141
4142 #[test]
4144 fn an_address_in_an_initializer_is_left_to_the_linker() {
4145 let source = "int counter;\nint *p = &counter;\n";
4146 let text = asm(source);
4147 assert!(text.contains("\np:\n\t.quad\tcounter\n"), "{text}");
4148 let bytes = obj(source);
4151 assert!(bytes.windows(8).any(|w| w == b"counter\0"), "the object has to name it");
4152 }
4153
4154 #[test]
4163 fn a_constant_holding_an_address_goes_in_the_section_the_loader_may_write_once() {
4164 let text = asm("static void a(void) {}\nstatic void b(void) {}\n\
4167 struct m { void (*x)(void); void (*y)(void); };\n\
4168 const struct m t = { a, b };\n");
4169 assert!(text.contains("\t.section\t.data.rel.ro.local,\"aw\",@progbits\n"), "{text}");
4170 assert!(text.contains("\nt:\n\t.quad\ta\n\t.quad\tb\n"), "{text}");
4171
4172 let text =
4175 asm("void a(void);\nstruct m { void (*x)(void); };\nconst struct m t = { a };\n");
4176 assert!(text.contains("\t.section\t.data.rel.ro,\"aw\",@progbits\n"), "{text}");
4177
4178 let text = asm("const int fixed = 7;\n");
4180 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4181 }
4182
4183 #[test]
4190 fn a_thread_local_variable_is_storage_a_thread_gets_a_copy_of_and_an_offset_into_it() {
4191 let text = asm("_Thread_local int x = 1;\nint read(void) { return x; }\n");
4192 assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
4195 assert!(text.contains("\t.type\tx, @tls_object\n"), "{text}");
4196 assert!(text.contains("x@GOTTPOFF(%rip)"), "{text}");
4199 assert!(text.contains("%fs:0"), "{text}");
4200 }
4201
4202 #[test]
4208 fn the_address_of_this_thread_s_own_storage_is_read_out_of_the_segment_register() {
4209 let text = asm("void *here(void) { return __builtin_thread_pointer(); }\n");
4210 assert!(text.contains("movq\t%fs:0, "), "{text}");
4211 assert!(!text.contains("GOTTPOFF"), "{text}");
4213 }
4214
4215 #[test]
4226 fn a_prefetch_is_one_of_four_instructions_and_the_locality_is_what_picks() {
4227 for (locality, wanted) in
4228 [(0, "prefetchnta"), (1, "prefetcht2"), (2, "prefetcht1"), (3, "prefetcht0")]
4229 {
4230 let source =
4231 format!("void warm(void *p) {{ __builtin_prefetch(p, 0, {locality}); }}\n");
4232 let text = asm(&source);
4233 assert!(text.contains(&format!("\t{wanted}\t")), "locality {locality}: {text}");
4234 }
4235 let text = asm("void warm(void *p) { __builtin_prefetch(p); }\n");
4237 assert!(text.contains("\tprefetcht0\t"), "{text}");
4238 let text = asm("void warm(void *p) { __builtin_prefetch(p, 1); }\n");
4241 assert!(text.contains("\tprefetcht0\t"), "{text}");
4242 assert!(!text.contains("prefetchw"), "{text}");
4243 }
4244
4245 #[test]
4256 fn a_trap_is_the_instruction_the_machine_has_no_meaning_for() {
4257 let text = asm("void stop(void) { __builtin_trap(); }\n");
4258 assert!(text.contains("\tud2\n"), "{text}");
4259 assert!(!text.contains("\tcall"), "a stop is not a call to anything: {text}");
4260
4261 let text = asm("int stop(int a) { __builtin_trap(); return a + 1; }\n");
4262 assert!(text.contains("\tud2\n"), "{text}");
4263 assert!(text.contains("\taddl\t"), "the block goes on after a stop: {text}");
4264 }
4265
4266 #[test]
4278 fn assume_aligned_is_its_first_argument_and_keeps_the_rest() {
4279 let text = asm("void *aligned(char *p) { return __builtin_assume_aligned(p, 16); }\n");
4280 assert!(!text.contains("assume_aligned"), "{text}");
4281 assert!(!text.contains("\tcall"), "nothing is called for an alignment fact: {text}");
4282
4283 let source = "unsigned long width(void);\n\
4284 void *aligned(char *p) { return __builtin_assume_aligned(p, width()); }\n";
4285 let text = asm(source);
4286 assert!(!text.contains("assume_aligned"), "{text}");
4287 assert!(text.contains("width"), "the argument that is not the answer still runs: {text}");
4288 }
4289
4290 #[test]
4300 fn the_frame_address_is_the_frame_pointer_after_walking_that_many_links() {
4301 let text = asm("void *here(void) { return __builtin_frame_address(0); }\n");
4302 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
4303 assert!(text.contains("movq\t%rbp, %rax"), "{text}");
4304 assert!(!text.contains("\tcall"), "a frame address is not a call to anything: {text}");
4305
4306 let walk = |depth: u32| {
4307 let source = format!("void *up(void) {{ return __builtin_frame_address({depth}); }}\n");
4308 asm(&source).matches("movq\t(%r").count()
4309 };
4310 assert_eq!(walk(1), 1, "one link is one load");
4311 assert_eq!(walk(3), 3, "three links are three loads");
4312 }
4313
4314 #[test]
4324 fn the_return_address_is_one_word_above_the_frame_the_walk_ended_at() {
4325 let text = asm("void *back(void) { return __builtin_return_address(0); }\n");
4326 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
4327 assert!(text.contains("movq\t8(%rbp), %rax"), "{text}");
4328 assert!(!text.contains("\tcall"), "a return address is not a call to anything: {text}");
4329
4330 let text = asm("void *back(void) { return __builtin_return_address(2); }\n");
4331 assert_eq!(text.matches("movq\t(%r").count(), 2, "two links are two loads: {text}");
4332 assert!(text.contains("movq\t8(%r"), "and the answer is above the last of them: {text}");
4333 }
4334
4335 #[test]
4346 fn a_depth_that_is_not_a_small_constant_is_refused() {
4347 let mut opts = options();
4348 opts.emit = EmitKind::Ir;
4349 for source in [
4350 "void *up(int n) { return __builtin_return_address(n); }\n",
4351 "void *up(void) { return __builtin_frame_address(1000); }\n",
4352 ] {
4353 let messages = run(&opts, source).messages;
4354 let named = messages.iter().any(|m| m.contains("E0705"));
4355 assert!(named, "expected a refusal in {messages:?}");
4356 }
4357 }
4358
4359 #[test]
4371 fn an_alloca_takes_the_bytes_off_the_stack_pointer_and_answers_where_they_are() {
4372 let text =
4373 asm("void use(void *p); void f(unsigned long n) { use(__builtin_alloca(n)); }\n");
4374 assert!(text.contains("andq\t$-16"), "the size is rounded up to sixteen: {text}");
4375 assert!(text.contains("subq\t%rdi, %rsp"), "and taken off the stack pointer: {text}");
4376 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
4377
4378 let plain = concat!(
4381 "extern void *alloca(__SIZE_TYPE__);\n",
4382 "void use(void *p);\n",
4383 "void f(unsigned long n) { use(alloca(n)); }\n",
4384 );
4385 let text = asm(plain);
4386 assert!(text.contains("subq\t%rdi, %rsp"), "the plain name is the same bytes: {text}");
4387 assert_eq!(text.matches("\tcall").count(), 1, "and is not a call either: {text}");
4388
4389 let own = concat!(
4392 "static void *alloca(unsigned long n) { return 0; }\n",
4393 "void *f(unsigned long n) { return alloca(n); }\n",
4394 );
4395 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
4396 }
4397
4398 #[test]
4413 fn a_builtin_the_program_never_declared_is_the_builtin_rather_than_the_one_c89_wrote_down() {
4414 let mut opts = options();
4418 opts.permissive = true;
4419 let undeclared = "void use(void *p);
4420void f(unsigned long n) { use(alloca(n)); }
4421";
4422 assert_eq!(
4423 run(&opts, undeclared).messages,
4424 [
4425 "/main.c:2:31: warning: implicit declaration of function 'alloca' [E0521]",
4426 "/main.c:2:31: warning: incompatible implicit declaration of built-in function \
4427 'alloca' [E0713]",
4428 ]
4429 );
4430
4431 opts.emit = EmitKind::Asm;
4432 let text = run(&opts, undeclared).text().to_owned();
4433 assert!(text.contains("subq\t%rdi, %rsp"), "the bytes come off the stack: {text}");
4434 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
4435
4436 let string = "unsigned long f(void) { return strlen(\"abc\"); }\n";
4439 let text = run(&opts, string).text().to_owned();
4440 assert!(text.contains("call\tstrlen"), "strlen is still a call: {text}");
4441
4442 let own = concat!(
4445 "static void *alloca(unsigned long n) { return 0; }\n",
4446 "void *f(unsigned long n) { return alloca(n); }\n",
4447 );
4448 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
4449 }
4450
4451 #[test]
4461 fn the_bytes_an_alloca_took_are_still_there_at_the_end_of_the_block_that_took_them() {
4462 let inner = "{ use(__builtin_alloca(n)); }";
4463 for body in [inner.to_owned(), format!("int a[n]; {inner} use(a);")] {
4464 let source = format!("void use(void *p);\nvoid f(unsigned long n) {{ {body} }}\n");
4465 let text = asm(&source);
4466 for line in text.lines().filter(|line| line.trim_end().ends_with(", %rsp")) {
4470 let taking = line.contains("subq");
4471 let leaving = line.contains("%rbp");
4472 assert!(taking || leaving, "nothing puts the stack back: {line} in {text}");
4473 }
4474 }
4475 }
4476
4477 #[test]
4479 fn the_object_and_the_listing_are_two_spellings_of_one_compilation() {
4480 let source = "int callee(void); int g(void) { return callee(); }\n";
4484 let bytes = obj(source);
4485 assert!(
4486 bytes.windows(7).any(|w| w == b"callee\0"),
4487 "the object has to name the callee for the linker to find it"
4488 );
4489 let text = asm(source);
4490 assert!(text.contains("\tcall\tcallee\n"), "{text}");
4491 }
4492
4493 #[test]
4499 fn compiling_for_an_executable_produces_an_object_and_not_a_dump() {
4500 let mut opts = options();
4501 opts.emit = EmitKind::Executable;
4503 let result = run(&opts, "int main(void) { return 0; }\n");
4504 assert_eq!(result.messages, Vec::<String>::new());
4505 match result.artifact {
4506 Artifact::Object { bytes, .. } => assert_eq!(&bytes[..4], b"\x7fELF"),
4507 other => panic!("expected an object, got {other:?}"),
4508 }
4509 }
4510
4511 #[test]
4513 fn a_platform_with_no_object_writer_is_said_so_rather_than_written_as_elf() {
4514 let mut opts = options();
4515 opts.emit = EmitKind::Object;
4516 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
4517 let result = run(&opts, "int f(void) { return 0; }\n");
4518 assert!(result.failed(), "an object nobody can read is worse than a message");
4519 assert!(
4520 result.messages.iter().any(|m| m.contains("no object writer")),
4521 "{:?}",
4522 result.messages
4523 );
4524 }
4525
4526 fn ir(source: &str) -> String {
4528 let mut opts = options();
4529 opts.emit = EmitKind::Ir;
4530 let result = run(&opts, source);
4531 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4532 result.text().to_owned()
4533 }
4534
4535 fn errors(source: &str) -> Vec<String> {
4537 let mut opts = options();
4538 opts.emit = EmitKind::Ir;
4539 let result = run(&opts, source);
4540 assert!(result.failed(), "expected this to be refused:\n{source}");
4541 result.messages
4542 }
4543
4544 fn body(source: &str) -> String {
4546 let text = ir(source);
4547 let (_, rest) = text.split_once("{\n").expect("a function definition");
4548 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
4549 body.to_owned()
4550 }
4551
4552 #[test]
4560 fn gnu89_inline_is_what_decides_whether_a_bare_inline_definition_reaches_the_module() {
4561 let source = "inline int f(int x) { return x + 1; }\n";
4562 let with = |flag: bool| {
4563 let mut opts = options();
4564 opts.emit = EmitKind::Ir;
4565 opts.gnu89_inline = flag;
4566 let result = run(&opts, source);
4567 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
4568 result.text().to_owned()
4569 };
4570
4571 assert!(!with(false).contains("block0"), "no body: {}", with(false));
4574
4575 assert!(with(true).contains("block0"), "a body: {}", with(true));
4578 }
4579
4580 #[test]
4587 fn an_access_through_a_type_names_the_type_it_went_through() {
4588 let source = "\
4589struct s { int a; float b; };\n\
4590union u { int i; float f; };\n\
4591int scalar(int *p) { return *p; }\n\
4592float member(struct s *p) { p->a = 1; return p->b; }\n\
4593int element(int *a, long i) { return a[i]; }\n\
4594float through_a_union(union u *p) { p->i = 1; return p->f; }\n";
4595 let text = ir(source);
4596 assert!(text.contains(r#"!0 = tbaa "char""#), "the root: {text}");
4597 assert!(text.contains(r#"tbaa "int", parent !0"#), "int under it: {text}");
4598 assert!(text.contains(r#"tbaa "float", parent !0"#), "float under it: {text}");
4599 let named = text.lines().filter(|line| line.contains(", tbaa !")).count();
4602 assert_eq!(named, 6, "six accesses: {text}");
4603 }
4604
4605 #[test]
4612 fn turning_strict_aliasing_off_leaves_the_type_off_every_access() {
4613 let source = "int punned(float *f, int *i) { *i = 1; *f = 2.0f; return *i; }\n";
4614 let mut opts = options();
4615 opts.emit = EmitKind::Ir;
4616 opts.strict_aliasing = false;
4617 let result = run(&opts, source);
4618 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
4619 let text = result.text().to_owned();
4620 assert!(!text.contains("tbaa"), "not even the root: {text}");
4621 }
4622
4623 #[test]
4629 fn instrumenting_functions_calls_the_hooks_around_every_body_but_the_hooks() {
4630 let source = concat!(
4631 "#define NOCHK __attribute__((no_instrument_function))\n",
4632 "void __cyg_profile_func_enter(void *, void *) NOCHK;\n",
4633 "void __cyg_profile_func_exit(void *, void *) NOCHK;\n",
4634 "int calls;\n",
4635 "int pick(int x) { if (x) return 1; return 2; }\n",
4636 "void quiet(void) NOCHK;\n",
4637 "void quiet(void) { calls++; }\n",
4638 "void __cyg_profile_func_enter(void *fn, void *site) { calls++; }\n",
4639 "void __cyg_profile_func_exit(void *fn, void *site) { calls--; }\n",
4640 );
4641 let mut opts = options();
4642 opts.emit = EmitKind::Ir;
4643 opts.instrument_functions = true;
4644 let result = run(&opts, source);
4645 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
4646 let text = result.text().to_owned();
4647 let body = |name: &str| -> String {
4648 let open = format!("func @{name}(");
4649 let start = text.find(&open).unwrap_or_else(|| panic!("no {name}: {text}"));
4650 let rest = &text[start..];
4651 rest[..rest.find("\n}").unwrap_or(rest.len())].to_owned()
4652 };
4653 let pick = body("pick");
4654 assert_eq!(pick.matches("call @__cyg_profile_func_enter(").count(), 1, "{pick}");
4655 assert_eq!(pick.matches("call @__cyg_profile_func_exit(").count(), 2, "{pick}");
4656 assert!(pick.contains("return_address"), "{pick}");
4657 assert!(pick.contains("global_addr @pick"), "{pick}");
4658 for quiet in ["quiet", "__cyg_profile_func_enter", "__cyg_profile_func_exit"] {
4659 assert!(!body(quiet).contains("call "), "{quiet} is left alone: {text}");
4660 }
4661
4662 opts.instrument_functions = false;
4663 let result = run(&opts, source);
4664 assert!(!result.text().contains("call @__cyg_profile"), "off unless asked for");
4665 }
4666
4667 #[test]
4675 fn a_bare_return_from_a_function_that_promised_a_value_gives_back_a_zero() {
4676 let mut opts = options();
4677 opts.emit = EmitKind::Ir;
4678 opts.std = Std::C89;
4679 let compiled = |source: &str| {
4680 let result = run(&opts, source);
4681 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
4682 result.text().to_owned()
4683 };
4684
4685 let text = compiled("int f(int x) { if (x) return; return 3; }\n");
4686 assert!(text.contains("iconst.i32 0\n return"), "zero goes back: {text}");
4687 assert!(!text.contains("unreachable"), "the branch that reached it is kept: {text}");
4688
4689 let text = compiled("double f(int x) { if (x) return; return 1.0; }\n");
4691 assert!(text.contains("fconst.f64 0x0\n return"), "a float zero goes back: {text}");
4692 }
4693
4694 #[test]
4702 fn a_call_to_a_name_nothing_declared_declares_it_as_c89_said_to() {
4703 let mut opts = options();
4704 opts.emit = EmitKind::Ir;
4705 opts.std = Std::C89;
4706 let compiled = |source: &str| {
4707 let result = run(&opts, source);
4708 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
4709 result.text().to_owned()
4710 };
4711
4712 let text = compiled("int f(void) { return g(); }\n");
4714 assert!(text.contains("call @g"), "the call is to the name that was written: {text}");
4715 assert!(text.contains("i32"), "and it gives back an int: {text}");
4716
4717 let text = compiled("int f(char c) { return g(c); }\n");
4720 assert!(text.contains("sext.i32"), "the argument is promoted: {text}");
4721
4722 let mut opts = options();
4725 opts.std = Std::C89;
4726 let said = run(&opts, "int f(void) { return h; }\n").messages.join("\n");
4727 assert!(said.contains("'h' undeclared"), "not a call, so not declared: {said}");
4728 }
4729
4730 #[test]
4740 fn a_name_called_before_it_is_defined_still_gets_its_definition() {
4741 let mut opts = options();
4742 opts.emit = EmitKind::Ir;
4743 opts.std = Std::C89;
4744 let text = run(&opts, "int f(void) { return dummy(); }\ndummy () { return 7; }\n")
4745 .text()
4746 .to_owned();
4747 assert!(text.contains("func @f()"), "the caller is there: {text}");
4748 assert!(text.contains("func @dummy"), "and so is what it calls: {text}");
4749 assert!(text.contains("iconst.i32 7"), "with the body it was given: {text}");
4750 }
4751
4752 #[test]
4760 fn an_old_style_parameter_is_converted_from_what_the_call_promoted_it_to() {
4761 let mut opts = options();
4762 opts.emit = EmitKind::Ir;
4763 opts.std = Std::C89;
4764 let compiled = |source: &str| run(&opts, source).text().to_owned();
4765
4766 let text = compiled("f (c) unsigned char c; { return c; }\n");
4767 assert!(text.contains("func @f(i32"), "an int arrives: {text}");
4768 assert!(text.contains("trunc.i8"), "and is cut down to what was declared: {text}");
4769 assert!(text.contains("zext.i32"), "then read back unsigned: {text}");
4770
4771 let text = compiled("f (s) short s; { return s; }\n");
4773 assert!(text.contains("trunc.i16"), "cut down: {text}");
4774 assert!(text.contains("sext.i32"), "and read back signed: {text}");
4775
4776 let text = compiled("f (x) float x; { return x * 2; }\n");
4779 assert!(text.contains("func @f(f64"), "a double arrives: {text}");
4780 assert!(text.contains("fptrunc.f32"), "and is narrowed to the float: {text}");
4781
4782 let text = compiled("int f(unsigned char c) { return c; }\n");
4785 assert!(text.contains("func @f(i8)"), "the declared type arrives: {text}");
4786 assert!(!text.contains("trunc"), "so there is nothing to cut down: {text}");
4787 }
4788
4789 #[test]
4798 fn the_rules_gcc_promoted_are_decided_by_the_dialect_and_by_fpermissive() {
4799 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
4801 let cases = [
4802 ("static counted;\n", ["", "error", "warning", "error"]),
4803 ("int f(void) { return g(); }\n", ["", "error", "warning", "error"]),
4804 ("int f(x) { return x; }\n", ["", "error", "warning", "error"]),
4805 ("int *p;\nvoid h(void) { p = 1; }\n", ["warning", "error", "warning", "error"]),
4806 (
4807 "char *q;\nint *r;\nvoid k(void) { r = q; }\n",
4808 ["warning", "error", "warning", "error"],
4809 ),
4810 ("int f(void) { return; }\n", ["", "error", "warning", "error"]),
4811 ("void g(void) { return 1; }\n", ["warning", "error", "warning", "error"]),
4812 ];
4813
4814 for (source, wanted) in cases {
4815 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
4816 let mut opts = options();
4817 opts.std = std;
4818 opts.permissive = permissive;
4819 let said = run(&opts, source).messages.join("\n");
4820 let severity = if said.contains(": error: ") {
4821 "error"
4822 } else if said.contains(": warning: ") {
4823 "warning"
4824 } else {
4825 ""
4826 };
4827 let how = if permissive { " -fpermissive" } else { "" };
4828 assert_eq!(
4829 severity,
4830 wanted,
4831 "under -std={}{how}, {source} was answered with `{said}`",
4832 std.as_str()
4833 );
4834 if wanted.is_empty() {
4835 assert!(said.is_empty(), "nothing to say, but said `{said}`");
4836 }
4837 }
4838 }
4839 }
4840
4841 #[test]
4850 fn the_three_variadic_builtins_answer_a_bad_list_the_way_a_call_answers_a_bad_argument() {
4851 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
4852 let cases = [
4853 (
4854 "int f(int n, ...) { char *p; return __builtin_va_arg(p, int); }\n",
4855 "first argument to 'va_arg' not of type 'va_list'",
4856 ["error", "error", "error", "error"],
4857 ),
4858 (
4859 "void f(int n, ...) { char *p; __builtin_va_start(p, n); }\n",
4860 "passing argument 1 of '__builtin_va_start' from incompatible pointer type",
4861 ["warning", "error", "warning", "error"],
4862 ),
4863 (
4864 "void f(int n, ...) { int x; __builtin_va_end(x); }\n",
4865 "passing argument 1 of '__builtin_va_end' makes pointer from integer without a \
4866 cast",
4867 ["warning", "error", "warning", "error"],
4868 ),
4869 (
4870 "void f(int n, ...) { __builtin_va_list a; char *p; __builtin_va_copy(a, p); }\n",
4871 "passing argument 2 of '__builtin_va_copy' from incompatible pointer type",
4872 ["warning", "error", "warning", "error"],
4873 ),
4874 ];
4875
4876 for (source, message, wanted) in cases {
4877 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
4878 let mut opts = options();
4879 opts.std = std;
4880 opts.permissive = permissive;
4881 let said = run(&opts, source).messages.join("\n");
4882 let how = if permissive { " -fpermissive" } else { "" };
4883 assert!(
4884 said.contains(&format!(": {wanted}: {message}")),
4885 "under -std={}{how}, {source} was answered with `{said}`",
4886 std.as_str()
4887 );
4888 }
4889 }
4890 }
4891
4892 fn safe_ir(tier: rucc_session::Safety, source: &str) -> String {
4894 let mut opts = options();
4895 opts.emit = EmitKind::Ir;
4896 opts.safety = tier;
4897 let result = run(&opts, source);
4898 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4899 result.text().to_owned()
4900 }
4901
4902 const READS_THROUGH_A_POINTER: &str = "int read(int *p) { return p[1]; }\n";
4903
4904 fn padded_ir(padding: Padding, source: &str) -> String {
4906 let mut opts = options();
4907 opts.emit = EmitKind::Ir;
4908 opts.safety = rucc_session::Safety::Detect;
4909 opts.padding = padding;
4910 let result = run(&opts, source);
4911 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4912 result.text().to_owned()
4913 }
4914
4915 const FILLS_A_RECORD_A_MEMBER_AT_A_TIME: &str = "struct padded { char tag; int value; };\n\
4916 void fill(struct padded *p) { p->tag = 1; p->value = 2; }\n";
4917
4918 #[test]
4919 fn a_record_filled_a_member_at_a_time_comes_out_whole_when_padding_does_not_participate() {
4920 let text = padded_ir(Padding::Ignored, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
4924 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
4925 }
4926
4927 #[test]
4928 fn a_store_says_only_what_it_wrote_when_padding_does_participate() {
4929 let text = padded_ir(Padding::Tracked, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
4932 assert!(!text.contains("owns"), "{text}");
4933 }
4934
4935 #[test]
4936 fn a_member_of_a_union_owns_nothing_after_it() {
4937 let text = padded_ir(
4941 Padding::Ignored,
4942 "union u { char tag; long wide; };\nvoid fill(union u *p) { p->tag = 1; }\n",
4943 );
4944 assert!(!text.contains("owns"), "{text}");
4945 }
4946
4947 #[test]
4948 fn an_inner_records_trailing_padding_reaches_the_outer_records() {
4949 let text = padded_ir(
4954 Padding::Ignored,
4955 "struct inner { char c; };\n\
4956 struct outer { struct inner in; int x; };\n\
4957 void fill(struct outer *p) { p->in.c = 1; p->x = 2; }\n",
4958 );
4959 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
4960 }
4961
4962 #[test]
4963 fn a_build_that_did_not_ask_for_the_monitor_is_compiled_the_way_it_always_was() {
4964 let text = ir(READS_THROUGH_A_POINTER);
4968 assert!(!text.contains("check_"), "{text}");
4969 assert!(!text.contains("cap_of"), "{text}");
4970 }
4971
4972 #[test]
4973 fn asking_for_a_tier_puts_the_checks_in_before_the_optimizer_sees_them() {
4974 let text = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4975 assert!(text.contains("cap_of"), "{text}");
4976 assert!(text.contains("check_bounds"), "{text}");
4977 assert!(text.contains("check_live"), "{text}");
4978 assert!(text.contains("check_deriv"), "{text}");
4980 assert!(text.contains("check_type"), "{text}");
4982 }
4983
4984 #[test]
4985 fn the_three_tiers_that_are_not_off_all_check_the_same_accesses_so_far() {
4986 let detect = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4990 for tier in [rucc_session::Safety::Enforce, rucc_session::Safety::Kernel] {
4991 assert_eq!(safe_ir(tier, READS_THROUGH_A_POINTER), detect, "{tier}");
4992 }
4993 }
4994
4995 fn summary(tier: rucc_session::Safety, source: &str) -> String {
4997 let mut opts = options();
4998 opts.emit = EmitKind::SafetySummary;
4999 opts.safety = tier;
5000 let result = run(&opts, source);
5001 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5002 result.text().to_owned()
5003 }
5004
5005 #[test]
5006 fn the_summary_counts_the_checks_that_went_in_and_the_ones_still_standing() {
5007 let text = summary(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5008 assert!(text.contains("\"tier\": \"detect\""), "{text}");
5009 assert!(
5011 text.contains("\"bounds\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"),
5012 "{text}"
5013 );
5014 assert!(
5015 text.contains(
5016 "\"derivation\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"
5017 ),
5018 "{text}"
5019 );
5020 }
5021
5022 #[test]
5023 fn a_build_without_the_monitor_summarises_as_a_build_with_no_checks_in_it() {
5024 let text = summary(rucc_session::Safety::Off, READS_THROUGH_A_POINTER);
5028 assert!(text.contains("\"tier\": \"off\""), "{text}");
5029 assert!(
5030 text.contains("\"bounds\": { \"emitted\": 0, \"remaining\": 0, \"discharged\": 0 }"),
5031 "{text}"
5032 );
5033 }
5034
5035 #[test]
5036 fn a_call_the_boundary_models_is_counted_apart_from_one_it_does_not() {
5037 let text = summary(
5038 rucc_session::Safety::Detect,
5039 "void *memcpy(void *, const void *, unsigned long);\n\
5040 int puts(const char *);\n\
5041 void f(char *d, char *s) { memcpy(d, s, 4); puts(d); }\n",
5042 );
5043 assert!(text.contains("\"interposed\": 1"), "{text}");
5044 assert!(text.contains("\"puts\""), "{text}");
5045 assert!(!text.contains("__rucc_wrap_memcpy\""), "{text}");
5049 }
5050
5051 #[test]
5052 fn an_address_taken_of_a_library_function_is_counted_the_way_a_call_to_one_is() {
5053 let text = summary(
5058 rucc_session::Safety::Detect,
5059 "void *memcpy(void *, const void *, unsigned long);\n\
5060 int puts(const char *);\n\
5061 void *table[2] = { (void *)memcpy, (void *)puts };\n\
5062 void *f(int i) { return table[i]; }\n",
5063 );
5064 assert!(text.contains("\"interposed\": 1"), "{text}");
5065 assert!(text.contains("\"puts\""), "{text}");
5066 assert!(!text.contains("\"memcpy\""), "{text}");
5067 }
5068
5069 #[test]
5070 fn the_two_directions_a_pointer_crosses_the_boundary_are_counted_apart() {
5071 let text = summary(
5075 rucc_session::Safety::Detect,
5076 "void *notes_open(void);\n\
5077 char *f(char *p) { char *q = notes_open(); return q ? q : p; }\n",
5078 );
5079 assert!(text.contains("\"crossings\": { \"entered\": 1, \"returned\": 1 }"), "{text}");
5080 assert!(text.contains("\"notes_open\""), "{text}");
5081 }
5082
5083 #[test]
5084 fn a_static_function_nobody_takes_the_address_of_is_not_a_crossing() {
5085 let text = summary(
5088 rucc_session::Safety::Detect,
5089 "static int len(const char *p) { return p ? 1 : 0; }\n\
5090 int f(void) { return len(\"x\"); }\n",
5091 );
5092 assert!(text.contains("\"crossings\": { \"entered\": 0, \"returned\": 0 }"), "{text}");
5093 }
5094
5095 fn granules(source: &str) -> String {
5097 let mut opts = options();
5098 opts.emit = EmitKind::TypeGranules;
5099 let result = run(&opts, source);
5100 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5101 result.text().to_owned()
5102 }
5103
5104 #[test]
5105 fn the_granule_report_names_every_record_and_both_keyings() {
5106 let text = granules(
5107 "struct hot { char *p; int a; int b; };\n\
5108 int f(struct hot *h) { return h->a; }\n",
5109 );
5110 assert!(text.contains("struct hot"), "{text}");
5111 assert!(text.contains("every type distinct"), "{text}");
5114 assert!(text.contains("every pointer one type"), "{text}");
5115 assert!(text.contains("budget"), "{text}");
5116 }
5117
5118 #[test]
5119 fn a_record_nothing_uses_is_still_measured() {
5120 let text = granules("struct unused { long a; double b; };\nint f(void) { return 0; }\n");
5123 assert!(text.contains("struct unused"), "{text}");
5124 }
5125
5126 #[test]
5127 fn the_granule_report_stops_before_anything_is_lowered() {
5128 let text = granules(
5132 "struct wide { long double d; };\n\
5133 long double f(long double x) { return x * x; }\n",
5134 );
5135 assert!(text.contains("struct wide"), "{text}");
5136 }
5137
5138 #[test]
5139 fn a_witness_reaches_the_assembler_as_a_call_to_the_runtime() {
5140 let text = safe_asm(rucc_session::Safety::Detect, "char *f(char *p) { return p; }\n");
5143 assert!(text.contains("\tcall\t__rucc_cap_witness\n"), "{text}");
5144 }
5145
5146 #[test]
5147 fn a_pointer_turned_into_an_integer_is_on_the_trust_set() {
5148 let text = summary(
5149 rucc_session::Safety::Detect,
5150 "unsigned long f(int *p) { return (unsigned long) p; }\n",
5151 );
5152 assert!(text.contains("\"exposed\": 1"), "{text}");
5153 }
5154
5155 fn safe_asm(tier: rucc_session::Safety, source: &str) -> String {
5157 let mut opts = options();
5158 opts.emit = EmitKind::Asm;
5159 opts.safety = tier;
5160 let result = run(&opts, source);
5161 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5162 result.text().to_owned()
5163 }
5164
5165 #[test]
5166 fn a_check_reaches_the_assembler_as_a_call_to_the_runtime() {
5167 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5168 assert!(text.contains("\tcall\t__rucc_check_bounds\n"), "{text}");
5169 assert!(text.contains("\tcall\t__rucc_check_live\n"), "{text}");
5170 assert!(text.contains("\tcall\t__rucc_check_deriv\n"), "{text}");
5171 assert!(text.contains("\tcall\t__rucc_check_typed_init\n"), "{text}");
5174 }
5175
5176 #[test]
5177 fn every_check_that_reached_the_assembler_has_a_row_describing_it() {
5178 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5184 let section = format!("\t.section\t{},", rucc_safety::SECTION);
5185 assert_eq!(text.matches(§ion).count(), 4, "{text}");
5186 for index in 0..4 {
5187 let name = format!("__rucc_safety_desc_{index}");
5188 assert!(text.contains(&format!("{name}:\n")), "{text}");
5191 assert!(text.contains(&format!("{name}(%rip)")), "{text}");
5192 }
5193 assert!(!text.contains("__rucc_safety_desc_4"), "{text}");
5194 }
5195
5196 #[test]
5204 fn builtin_constant_p_is_folded_where_it_is_written_rather_than_called() {
5205 let text = ir(concat!(
5206 "int g;\n",
5207 "int a = __builtin_constant_p(1);\n",
5208 "int b = __builtin_constant_p(g);\n",
5209 "int c = __builtin_constant_p(\"abc\");\n",
5210 "int d = __builtin_constant_p(&g);\n",
5211 "int e = __builtin_constant_p(1.5);\n",
5212 "int h = __builtin_choose_expr(__builtin_constant_p(3), 11, 22);\n",
5213 ));
5214 assert!(text.contains("global @a : i32 = 1,"), "{text}");
5215 assert!(text.contains("global @b : i32 = 0,"), "{text}");
5216 assert!(text.contains("global @c : i32 = 1,"), "{text}");
5217 assert!(text.contains("global @d : i32 = 0,"), "{text}");
5218 assert!(text.contains("global @e : i32 = 1,"), "{text}");
5219 assert!(text.contains("global @h : i32 = 11,"), "{text}");
5220 assert!(!text.contains("__builtin_constant_p"), "it is not a call to anything:\n{text}");
5221
5222 let text = body("int f(void) { int i = 0; __builtin_constant_p(i++); return i; }\n");
5226 assert_eq!(text, "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 0\n return %0\n");
5227 }
5228
5229 #[test]
5238 fn a_call_to_a_library_builtin_reaches_the_library_function() {
5239 let text = body("void f(void) { __builtin_abort(); }\n");
5240 assert_eq!(text, "block0:\n call @abort() : ()\n return\n");
5241
5242 let text = ir("int f(const char *s) { return __builtin_puts(s) + __builtin_strlen(s); }\n");
5245 assert!(text.contains("call @puts(%0) : (ptr) -> i32"), "{text}");
5246 assert!(text.contains("call @strlen(%0) : (ptr) -> i64"), "{text}");
5247 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
5248 }
5249
5250 #[test]
5263 fn a_chk_builtin_reaches_the_checking_function_and_keeps_the_size() {
5264 let text = ir(concat!(
5265 "char d[8];\n",
5266 "void f(const char *s, unsigned long n) {\n",
5267 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
5268 " __builtin___strcpy_chk(d, s, __builtin_object_size(d, 1));\n",
5269 " __builtin___memset_chk(d, 0, n, 8);\n",
5270 "}\n",
5271 ));
5272 assert!(text.contains("call @__memcpy_chk("), "{text}");
5273 assert!(text.contains("call @__strcpy_chk("), "{text}");
5274 assert!(text.contains("call @__memset_chk("), "{text}");
5275 assert!(text.contains("iconst.i64 8"), "the object size reaches the call: {text}");
5276 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
5277 }
5278
5279 #[test]
5287 fn a_checking_call_whose_size_says_nothing_is_known_is_the_plain_library_call() {
5288 let text = ir(concat!(
5289 "extern char *p;\n",
5290 "char d[8];\n",
5291 "void f(const char *s, unsigned long n) {\n",
5292 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
5293 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
5294 " __builtin___strcpy_chk(p, s, __builtin_object_size(p, 0));\n",
5295 " __builtin___stpncpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
5296 " __builtin___sprintf_chk(p, 1, __builtin_object_size(p, 0), s);\n",
5297 "}\n",
5298 ));
5299
5300 assert!(
5302 text.contains("call @__memcpy_chk(%2, %0, %1, %3) : (ptr, ptr, i64, i64)"),
5303 "{text}"
5304 );
5305
5306 assert!(text.contains("call @memcpy(%6, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
5309 assert!(text.contains("call @strcpy(%10, %0) : (ptr, ptr) -> ptr"), "{text}");
5310 assert!(text.contains("call @stpncpy(%14, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
5311
5312 assert!(text.contains("call @__sprintf_chk("), "{text}");
5315
5316 let asm = asm(concat!(
5319 "void f(char *p, const char *s, unsigned long n) {\n",
5320 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
5321 "}\n",
5322 ));
5323 assert!(asm.contains("call\tmemcpy"), "{asm}");
5324 assert!(!asm.contains("$-1"), "the size that went away leaves no instruction:\n{asm}");
5325 }
5326
5327 #[test]
5335 fn the_v_spellings_of_the_chk_family_take_the_list_a_va_list_parameter_holds() {
5336 let text = ir(concat!(
5337 "char d[64];\n",
5338 "int f(const char *fmt, ...) {\n",
5339 " __builtin_va_list ap;\n",
5340 " __builtin_va_start(ap, fmt);\n",
5341 " int n = __builtin___vsprintf_chk(d, 1, __builtin_object_size(d, 0), fmt, ap);\n",
5342 " __builtin_va_end(ap);\n",
5343 " return n;\n",
5344 "}\n",
5345 ));
5346 assert!(text.contains("call @__vsprintf_chk("), "{text}");
5347 assert!(text.contains("iconst.i64 64"), "the object size reaches the call: {text}");
5348 }
5349
5350 #[test]
5361 fn the_absolute_value_family_is_the_magnitude_and_not_a_call() {
5362 let text = body(concat!(
5363 "long long llabs(long long);\n",
5364 "long long f(long long x) { return llabs(x); }\n",
5365 ));
5366 assert!(text.contains("%1 = iconst.i64 63"), "{text}");
5367 assert!(text.contains("%2 = ashr %0, %1"), "{text}");
5368 assert!(text.contains("%3 = xor %0, %2"), "{text}");
5369 assert!(text.contains("%4 = sub %3, %2"), "{text}");
5370 assert!(!text.contains("call"), "the call does not happen:\n{text}");
5371
5372 let text = body("int abs(int);\nint f(int x) { return abs(x); }\n");
5375 assert!(text.contains("iconst.i32 31"), "{text}");
5376 let text = body("long labs(long);\nlong f(long x) { return labs(x); }\n");
5377 assert!(text.contains("iconst.i64 63"), "{text}");
5378
5379 let text = body("long long f(long long x) { return __builtin_llabs(x); }\n");
5382 assert!(!text.contains("call"), "{text}");
5383
5384 let text = ir(concat!(
5386 "long long llabs(long long b);\n",
5387 "long long g(long long x) { return llabs(x); }\n",
5388 "long long llabs(long long b) { return 7; }\n",
5389 ));
5390 assert!(!text.contains("call @llabs"), "{text}");
5391 }
5392
5393 #[test]
5400 fn a_byte_swap_is_arithmetic_and_not_a_call() {
5401 let text = body("unsigned f(unsigned x) { return __builtin_bswap32(x); }\n");
5402 assert_eq!(text, "block0(%0: i32):\n %1 = bswap %0\n return %1\n");
5403
5404 let text = body("unsigned f(unsigned char c) { return __builtin_bswap32(c); }\n");
5407 assert!(text.contains("zext.i32 %0"), "widened first: {text}");
5408 assert!(text.contains("bswap %1"), "and swapped at four bytes: {text}");
5409 }
5410
5411 #[test]
5417 fn the_byte_swaps_reverse_at_the_width_their_name_says() {
5418 for (name, ty, width) in [
5419 ("__builtin_bswap16", "unsigned short", "i16"),
5420 ("__builtin_bswap32", "unsigned", "i32"),
5421 ("__builtin_bswap64", "unsigned long long", "i64"),
5422 ] {
5423 let source = format!("{ty} f({ty} x) {{ return {name}(x); }}\n");
5424 let text = body(&source);
5425 assert_eq!(
5426 text,
5427 format!("block0(%0: {width}):\n %1 = bswap %0\n return %1\n"),
5428 "{name}"
5429 );
5430 }
5431 }
5432
5433 #[test]
5440 fn the_bit_counts_are_instructions_and_not_calls() {
5441 let text = body("int f(unsigned x) { return __builtin_clz(x); }\n");
5442 assert_eq!(text, "block0(%0: i32):\n %1 = ctlz %0\n return %1\n");
5443
5444 let text = body("int f(unsigned x) { return __builtin_ctz(x); }\n");
5445 assert_eq!(text, "block0(%0: i32):\n %1 = cttz %0\n return %1\n");
5446
5447 let text = body("int f(unsigned x) { return __builtin_popcount(x); }\n");
5448 assert_eq!(text, "block0(%0: i32):\n %1 = ctpop %0\n return %1\n");
5449 }
5450
5451 #[test]
5460 fn the_bit_counts_ask_about_the_width_their_name_says() {
5461 let text = body("int f(unsigned long long x) { return __builtin_clzll(x); }\n");
5462 assert!(text.starts_with("block0(%0: i64):"), "counted at eight bytes: {text}");
5463 assert!(text.contains("%1 = ctlz %0"), "{text}");
5464 assert!(text.contains("trunc.i32 %1"), "and answered in an int: {text}");
5465
5466 let text = body("int f(unsigned long long x) { return __builtin_clz(x); }\n");
5469 assert!(text.contains("trunc.i32 %0"), "narrowed to what was asked about: {text}");
5470 assert!(text.contains("ctlz %1"), "and counted there: {text}");
5471
5472 let text = body("int f(unsigned long x) { return __builtin_popcountl(x); }\n");
5473 assert!(text.contains("%1 = ctpop %0"), "{text}");
5474 assert!(!text.contains("call"), "{text}");
5475 }
5476
5477 #[test]
5482 fn a_parity_is_the_low_bit_of_the_set_bit_count() {
5483 let text = body("int f(unsigned x) { return __builtin_parity(x); }\n");
5484 assert!(text.contains("%1 = ctpop %0"), "{text}");
5485 assert!(text.contains("iconst.i32 1"), "{text}");
5486 assert!(text.contains("and %1, %2"), "the low bit of it: {text}");
5487 }
5488
5489 #[test]
5495 fn the_first_set_bit_is_one_based_and_zero_for_a_zero() {
5496 let text = body("int f(int x) { return __builtin_ffs(x); }\n");
5497 assert!(text.contains("%1 = cttz %0"), "{text}");
5498 assert!(text.contains("%4 = add %1, %2"), "one more than the count: {text}");
5499 assert!(text.contains("%5 = icmp ne %0, %3"), "whether there was a bit at all: {text}");
5500 assert!(text.contains("%7 = sub %3, %6"), "spread to a mask: {text}");
5501 assert!(text.contains("%8 = and %4, %7"), "and kept only then: {text}");
5502 assert!(!text.contains("br_if"), "no branch: {text}");
5503 }
5504
5505 #[test]
5515 fn the_redundant_sign_bit_count_is_instructions_and_not_a_call() {
5516 let text = body("int f(int x) { return __builtin_clrsb(x); }\n");
5517 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
5518 assert!(text.contains("%2 = ashr %0, %1"), "the sign over every bit: {text}");
5519 assert!(text.contains("%3 = xor %0, %2"), "folded onto it: {text}");
5520 assert!(text.contains("%5 = shl %3, %4"), "one less than the count: {text}");
5521 assert!(text.contains("%6 = or %5, %4"), "with something to count at zero: {text}");
5522 assert!(text.contains("%7 = ctlz %6"), "{text}");
5523 assert!(!text.contains("call"), "{text}");
5524 assert!(!text.contains("br_if"), "no branch: {text}");
5525 }
5526
5527 #[test]
5533 fn the_unsigned_absolute_value_family_answers_in_the_unsigned_type() {
5534 let text = body("unsigned f(int x) { return __builtin_uabs(x); }\n");
5535 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
5536 assert!(text.contains("%4 = sub %3, %2"), "{text}");
5537 assert!(!text.contains("call"), "nothing declares uabs, so a call would not link: {text}");
5538
5539 let text = body("unsigned long long f(long long x) { return __builtin_ullabs(x); }\n");
5540 assert!(text.contains("iconst.i64 63"), "at the width the name says: {text}");
5541
5542 let text = body("int f(int x) { return __builtin_uabs(x) > 2147483647u; }\n");
5545 assert!(text.contains("icmp ugt"), "compared unsigned: {text}");
5546 }
5547
5548 #[test]
5556 fn the_widest_absolute_value_is_whichever_type_the_target_makes_intmax_t() {
5557 let text = body("long f(long x) { return __builtin_imaxabs(x); }\n");
5558 assert!(text.contains("iconst.i64 63"), "{text}");
5559 assert!(text.contains("%4 = sub %3, %2"), "{text}");
5560 assert!(!text.contains("call"), "{text}");
5561
5562 let text = body("unsigned long f(long x) { return __builtin_umaxabs(x); }\n");
5563 assert!(text.contains("iconst.i64 63"), "{text}");
5564 assert!(!text.contains("call"), "{text}");
5565 }
5566
5567 #[test]
5575 fn an_overflow_predicate_writes_nothing_and_answers_the_bit_the_check_would() {
5576 let text =
5577 body("int f(int a, int b) { return __builtin_add_overflow_p(a, b, (int) 0); }\n");
5578 assert!(text.contains("%2, %3 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
5579 assert!(!text.contains("store"), "nothing is written: {text}");
5580 assert!(!text.contains("call"), "{text}");
5581
5582 let text =
5585 body("int f(int a, int b) { return __builtin_mul_overflow_p(a, b, (long long) 0); }\n");
5586 assert!(text.contains("smul_overflow.(i64, i1)"), "{text}");
5587 assert!(!text.contains("store"), "{text}");
5588
5589 let text = body(concat!(
5592 "int g(void);\n",
5593 "int f(int a, int b) { return __builtin_sub_overflow_p(a, b, g()); }\n",
5594 ));
5595 assert!(!text.contains("call @g"), "the third argument is not evaluated: {text}");
5596 }
5597
5598 #[test]
5608 fn an_overflow_check_is_arithmetic_and_not_a_call() {
5609 let text =
5610 body("int f(int a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
5611 assert!(text.contains("%3, %4 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
5612 assert!(text.contains("store %3 -> %2"), "{text}");
5613 assert!(!text.contains("call"), "{text}");
5614
5615 let text =
5616 body("int f(int a, int b, int *r) { return __builtin_sub_overflow(a, b, r); }\n");
5617 assert!(text.contains("ssub_overflow.(i32, i1) %0, %1"), "{text}");
5618
5619 let text =
5620 body("int f(int a, int b, int *r) { return __builtin_mul_overflow(a, b, r); }\n");
5621 assert!(text.contains("smul_overflow.(i32, i1) %0, %1"), "{text}");
5622
5623 let text = body(
5626 "int f(unsigned a, unsigned b, unsigned *r) { return __builtin_add_overflow(a, b, r); }\n",
5627 );
5628 assert!(text.contains("uadd_overflow.(i32, i1) %0, %1"), "{text}");
5629 }
5630
5631 #[test]
5639 fn an_overflow_check_is_done_at_a_type_that_holds_every_operand() {
5640 let text = body(
5641 "int f(unsigned a, int b, long long *r) { return __builtin_add_overflow(a, b, r); }\n",
5642 );
5643 assert!(text.contains("%3 = zext.i64 %0"), "the unsigned operand keeps its value: {text}");
5644 assert!(text.contains("%4 = sext.i64 %1"), "and so does the signed one: {text}");
5645 assert!(text.contains("sadd_overflow.(i64, i1) %3, %4"), "{text}");
5646
5647 let text = body(
5650 "int f(long long a, long long b, long long *r) { return __builtin_mul_overflow(a, b, r); }\n",
5651 );
5652 assert!(text.contains("smul_overflow.(i64, i1) %0, %1"), "{text}");
5653 assert!(!text.contains("sext."), "{text}");
5654 assert!(!text.contains("zext.i64"), "{text}");
5656 }
5657
5658 #[test]
5666 fn an_overflow_check_writes_the_wrapped_answer_whether_or_not_it_fit() {
5667 let text =
5668 body("int f(int a, int b, char *r) { return __builtin_sub_overflow(a, b, r); }\n");
5669 assert!(text.contains("%3, %4 = ssub_overflow.(i32, i1) %0, %1"), "{text}");
5670 assert!(text.contains("%5 = trunc.i8 %3"), "narrowed to where it goes: {text}");
5671 assert!(text.contains("%6 = sext.i32 %5"), "and back: {text}");
5672 assert!(text.contains("%7 = icmp ne %6, %3"), "which is whether it fit: {text}");
5673 assert!(text.contains("store %5 -> %2"), "the narrowed value is stored either way: {text}");
5674 assert!(text.contains("%8 = or %4, %7"), "and either bit is an overflow: {text}");
5675 }
5676
5677 #[test]
5684 fn a_call_needing_more_than_the_widest_type_still_compiles() {
5685 for name in ["add", "sub", "mul"] {
5686 let source = format!(
5687 "int f(unsigned __int128 a, long long b, __int128 *r) {{\n \
5688 return __builtin_{name}_overflow(a, b, r);\n}}\n"
5689 );
5690 let mut opts = options();
5691 opts.emit = EmitKind::MirFinal;
5692 assert!(!run(&opts, &source).failed(), "{name} was refused or stopped the back end");
5693 }
5694 }
5695
5696 #[test]
5699 fn an_overflow_check_over_something_that_is_not_an_integer_says_so() {
5700 let messages =
5701 errors("int f(double a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
5702 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
5703
5704 let messages =
5705 errors("int f(int a, int b, double *r) { return __builtin_add_overflow(a, b, r); }\n");
5706 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
5707 }
5708
5709 #[test]
5720 fn an_ordered_access_is_ordered_in_the_ir() {
5721 let text = body("int f(int *p) { return __atomic_load_n(p, 0); }\n");
5722 assert!(text.contains("atomic_load.i32 %0, align 4, relaxed"), "{text}");
5723
5724 let text = body("long f(long *p) { return __atomic_load_n(p, 2); }\n");
5725 assert!(text.contains("atomic_load.i64 %0, align 8, acquire"), "{text}");
5726
5727 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
5728 assert!(text.contains("atomic_store %1 -> %0, align 4, release"), "{text}");
5729
5730 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
5731 assert!(text.contains("atomic_store %1 -> %0, align 4, seq_cst"), "{text}");
5732
5733 let text = body("void f(char *p, int v) { __atomic_store_n(p, v, 0); }\n");
5736 assert!(text.contains("trunc.i8 %1"), "{text}");
5737 assert!(text.contains("atomic_store %2 -> %0, align 1, relaxed"), "{text}");
5738 }
5739
5740 #[test]
5749 fn an_ordered_access_is_the_plain_instruction_on_this_machine() {
5750 let text = asm("int f(int *p) { return __atomic_load_n(p, 5); }\n");
5751 assert!(text.contains("movl\t(%rdi), %eax"), "{text}");
5752 assert!(!text.contains("mfence"), "a load needs no barrier here: {text}");
5753
5754 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
5755 assert!(text.contains("movl\t%esi, (%rdi)"), "{text}");
5756 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
5757
5758 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
5759 let (before, after) = text.split_once("mfence").expect("a barrier: {text}");
5760 assert!(before.contains("movl\t%esi, (%rdi)"), "the store comes first: {text}");
5761 assert!(!after.contains("movl"), "and nothing else is between them: {text}");
5762 }
5763
5764 #[test]
5774 fn a_barrier_is_one_instruction_at_the_strongest_ordering_and_none_below_it() {
5775 assert!(asm("void f(void) { __atomic_thread_fence(5); }\n").contains("mfence"));
5776 assert!(asm("void f(void) { __sync_synchronize(); }\n").contains("mfence"));
5777
5778 for weaker in ["1", "2", "3", "4"] {
5779 let source = format!("void f(void) {{ __atomic_thread_fence({weaker}); }}\n");
5780 assert!(!asm(&source).contains("mfence"), "{weaker} costs nothing here");
5781 }
5782 }
5783
5784 #[test]
5794 fn the_three_x86_fences_are_the_barrier_the_strongest_ordering_gives() {
5795 for name in ["__builtin_ia32_sfence", "__builtin_ia32_lfence", "__builtin_ia32_mfence"] {
5796 let source = format!("void f(void) {{ {name}(); }}\n");
5797 assert!(asm(&source).contains("mfence"), "{name} is a barrier");
5798 let text = body(&source);
5799 assert!(text.contains("fence seq_cst"), "{name}: {text}");
5800 }
5801
5802 let result = run(&options(), "void f(void) { __builtin_ia32_sfence(1); }\n");
5803 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
5804 assert!(result.messages[0].contains("too many arguments"), "{:?}", result.messages);
5805 }
5806
5807 #[test]
5813 fn a_compare_and_exchange_is_one_instruction_answering_two_things() {
5814 let text =
5817 body("int f(int *p, int e, int d) { return __sync_val_compare_and_swap(p, e, d); }\n");
5818 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
5819 assert!(text.contains("return %3"), "the value it found: {text}");
5820
5821 let text =
5822 body("int f(int *p, int e, int d) { return __sync_bool_compare_and_swap(p, e, d); }\n");
5823 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
5824 assert!(text.contains("zext.i32 %4"), "whether it happened: {text}");
5825
5826 let text = body(
5829 "int f(int *p, int *e, int d) { return __atomic_compare_exchange_n(p, e, d, 0, 4, 2); }\n",
5830 );
5831 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
5832 assert!(text.contains("%4, %5 = cmpxchg.(i32, i1) %0, %3, %2, align 4, acq_rel"), "{text}");
5833 assert!(text.contains("br_if %5, block2, block1"), "{text}");
5834 assert!(text.contains("store %4 -> %1, align 4"), "{text}");
5835
5836 let text = body(
5839 "int f(int *p, int *e, int *d) { return __atomic_compare_exchange(p, e, d, 0, 5, 5); }\n",
5840 );
5841 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
5842 assert!(text.contains("%4 = load.i32 %2, align 4"), "{text}");
5843 assert!(text.contains("%5, %6 = cmpxchg.(i32, i1) %0, %3, %4, align 4, seq_cst"), "{text}");
5844 }
5845
5846 #[test]
5853 fn a_compare_and_exchange_is_a_locked_instruction_at_the_width_of_the_object() {
5854 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
5855 for (ty, suffix, reg) in widths {
5856 let source = format!(
5857 "int f({ty} *p, {ty} e, {ty} d) {{ return __sync_bool_compare_and_swap(p, e, d); }}\n"
5858 );
5859 let text = asm(&source);
5860 assert!(text.contains("\tlock\n"), "{ty}: {text}");
5861 assert!(text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
5862 assert!(text.contains("sete\t"), "{ty}: {text}");
5863 }
5864 let source =
5865 "int f(long *p, long e, long d) { return __sync_bool_compare_and_swap(p, e, d); }\n";
5866 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
5867
5868 for order in ["0", "2", "3", "4", "5"] {
5872 let call = format!("__atomic_compare_exchange_n(p, e, d, 0, {order}, 0)");
5873 let source = format!("int f(int *p, int *e, int d) {{ return {call}; }}\n");
5874 let text = asm(&source);
5875 assert!(text.contains("cmpxchgl\t"), "{order}: {text}");
5876 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
5877 }
5878 }
5879
5880 #[test]
5892 fn a_read_modify_write_is_one_instruction_and_the_arithmetic_a_name_asks_for() {
5893 let text = body("int f(int *p, int v) { return __atomic_fetch_add(p, v, 5); }\n");
5894 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
5895 assert!(text.contains("return %2"), "the value that was there: {text}");
5896
5897 let text = body("int f(int *p, int v) { return __atomic_add_fetch(p, v, 5); }\n");
5898 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
5899 assert!(text.contains("%3 = add %2, %1"), "and the value afterwards: {text}");
5900
5901 let text = body("int f(int *p, int v) { return __atomic_sub_fetch(p, v, 5); }\n");
5902 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
5903 assert!(text.contains("%3 = sub %2, %1"), "{text}");
5904
5905 let text = body("int f(int *p, int v) { return __sync_fetch_and_sub(p, v); }\n");
5907 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
5908
5909 let text = body("int f(int *p, int v) { return __atomic_exchange_n(p, v, 5); }\n");
5912 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, seq_cst"), "{text}");
5913
5914 let text = body("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
5915 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, acquire"), "{text}");
5916
5917 let text = body("void f(int *p) { __sync_lock_release(p); }\n");
5920 assert!(text.contains("release"), "{text}");
5921 assert!(text.contains("%1 = iconst.i32 0"), "{text}");
5922
5923 let text = body("void f(int *p, int guard) { __sync_lock_release(p, guard); }\n");
5927 assert!(text.contains("%2 = iconst.i32 0"), "{text}");
5928 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
5929
5930 let text = body("int f(int *p, int v) { return __atomic_fetch_and(p, v, 5); }\n");
5933 assert!(text.contains("%2 = atomic_rmw.i32 and %0, %1, align 4, seq_cst"), "{text}");
5934
5935 let text = body("int f(int *p, int v) { return __sync_or_and_fetch(p, v); }\n");
5936 assert!(text.contains("%2 = atomic_rmw.i32 or %0, %1, align 4, seq_cst"), "{text}");
5937 assert!(text.contains("%3 = or %2, %1"), "and the value afterwards: {text}");
5938
5939 let text = body("int f(int *p, int v) { return __atomic_nand_fetch(p, v, 5); }\n");
5942 assert!(text.contains("%2 = atomic_rmw.i32 nand %0, %1, align 4, seq_cst"), "{text}");
5943 assert!(text.contains("%3 = and %2, %1"), "{text}");
5944 assert!(text.contains("%4 = iconst.i32 -1"), "{text}");
5945 assert!(text.contains("%5 = xor %3, %4"), "{text}");
5946 }
5947
5948 #[test]
5959 fn a_bitwise_read_modify_write_is_a_loop_around_the_compare_and_exchange() {
5960 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
5961 for (ty, suffix, reg) in widths {
5962 for (name, call, insn) in [
5963 ("and", "__atomic_fetch_and(p, v, 5)", "and"),
5964 ("or", "__sync_fetch_and_or(p, v)", "or"),
5965 ("xor", "__atomic_xor_fetch(p, v, 5)", "xor"),
5966 ] {
5967 let source = format!("{ty} f({ty} *p, {ty} v) {{ return {call}; }}\n");
5968 let text = asm(&source);
5969 assert!(text.contains("\tlock\n"), "{ty} {name}: {text}");
5970 assert!(
5971 text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")),
5972 "{ty} {name}: {text}"
5973 );
5974 assert!(text.contains(&format!("{insn}{suffix}\t")), "{ty} {name}: {text}");
5975 assert!(!text.contains("\txadd"), "{ty} {name} is not an add: {text}");
5977 assert!(!text.contains("\txchg"), "{ty} {name} is not an exchange: {text}");
5978 }
5979 }
5980 let source = "long f(long *p, long v) { return __atomic_fetch_or(p, v, 5); }\n";
5981 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
5982
5983 let text = asm("int f(int *p, int v) { return __sync_fetch_and_nand(p, v); }\n");
5987 assert!(text.contains("cmpxchgl\t"), "{text}");
5988 assert!(text.contains("andl\t"), "{text}");
5989 assert!(text.contains("notl\t"), "{text}");
5990 }
5991
5992 #[test]
6001 fn an_access_through_a_second_pointer_is_the_same_access_and_one_more() {
6002 let text = body("void f(int *p, int *r) { __atomic_load(p, r, 5); }\n");
6003 assert!(text.contains("%2 = atomic_load.i32 %0, align 4, seq_cst"), "{text}");
6004 assert!(text.contains("store %2 -> %1, align 4"), "and out through the place: {text}");
6005
6006 let text = body("void f(int *p, int *v) { __atomic_store(p, v, 3); }\n");
6007 assert!(text.contains("%2 = load.i32 %1, align 4"), "in through the place: {text}");
6008 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
6009
6010 let text = body("void f(int *p, int *v, int *r) { __atomic_exchange(p, v, r, 5); }\n");
6013 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
6014 assert!(text.contains("%4 = atomic_rmw.i32 xchg %0, %3, align 4, seq_cst"), "{text}");
6015 assert!(text.contains("store %4 -> %2, align 4"), "{text}");
6016 }
6017
6018 #[test]
6029 fn a_flag_is_an_exchange_of_one_byte_and_a_store_of_a_zero_over_the_same_byte() {
6030 for pointer in ["char", "int", "void"] {
6031 let source = format!("int f({pointer} *p) {{ return __atomic_test_and_set(p, 5); }}\n");
6032 let text = body(&source);
6033 assert!(text.contains("%1 = iconst.i8 1"), "{pointer}: {text}");
6034 assert!(
6035 text.contains("%2 = atomic_rmw.i8 xchg %0, %1, align 1, seq_cst"),
6036 "{pointer}: {text}"
6037 );
6038 assert!(text.contains("%4 = icmp ne %2, %3"), "{pointer}: {text}");
6039
6040 let source = format!("void f({pointer} *p) {{ __atomic_clear(p, 3); }}\n");
6041 let text = body(&source);
6042 assert!(text.contains("atomic_store %2 -> %0, align 1, release"), "{pointer}: {text}");
6043 }
6044
6045 let text = asm("int f(int *p) { return __atomic_test_and_set(p, 5); }\n");
6048 assert!(text.contains("xchgb\t%al, (%rdi)"), "{text}");
6049 assert!(text.contains("setne\t"), "{text}");
6050 }
6051
6052 #[test]
6060 fn a_read_modify_write_is_an_exchange_or_a_locked_add_at_the_width_of_the_object() {
6061 let widths = [("char", "b", "%sil"), ("short", "w", "%si"), ("int", "l", "%esi")];
6062 for (ty, suffix, reg) in widths {
6063 let source =
6064 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_fetch_add(p, v, 5); }}\n");
6065 let text = asm(&source);
6066 assert!(text.contains("\tlock\n"), "{ty}: {text}");
6067 assert!(text.contains(&format!("xadd{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
6068
6069 let source =
6070 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_exchange_n(p, v, 5); }}\n");
6071 let text = asm(&source);
6072 assert!(text.contains(&format!("xchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
6073 assert!(!text.contains("\tlock\n"), "an exchange is locked already: {ty}: {text}");
6074 }
6075 let source = "long f(long *p, long v) { return __atomic_fetch_add(p, v, 5); }\n";
6076 assert!(asm(source).contains("xaddq\t%rsi, (%rdi)"), "{}", asm(source));
6077
6078 let source = "int f(int *p, int v) { return __atomic_fetch_sub(p, v, 5); }\n";
6081 let text = asm(source);
6082 assert!(text.contains("negl\t"), "{text}");
6083 assert!(text.contains("xaddl\t"), "{text}");
6084
6085 for order in ["0", "2", "3", "4", "5"] {
6088 let source =
6089 format!("int f(int *p, int v) {{ return __atomic_fetch_add(p, v, {order}); }}\n");
6090 let text = asm(&source);
6091 assert!(text.contains("xaddl\t"), "{order}: {text}");
6092 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
6093 }
6094
6095 let text = asm("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
6099 assert!(text.contains("xchgl\t%esi, (%rdi)"), "{text}");
6100 let text = asm("void f(int *p) { __sync_lock_release(p); }\n");
6107 assert!(text.contains("xorl\t%eax, %eax"), "{text}");
6108 assert!(text.contains("movl\t%eax, (%rdi)"), "{text}");
6109 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
6110 }
6111
6112 #[test]
6124 fn the_lock_free_questions_are_answered_as_constants() {
6125 for size in ["1", "2", "4", "8"] {
6126 let source =
6127 format!("int f(void) {{ return __atomic_always_lock_free({size}, 0); }}\n");
6128 let text = asm(&source);
6129 assert!(text.contains("movb\t$1, %al"), "{size} bytes is lock free: {text}");
6130 assert!(!text.contains("call"), "and is not a call: {text}");
6131 }
6132 for size in ["3", "16", "sizeof(long double)"] {
6133 let source = format!("int f(void) {{ return __atomic_is_lock_free({size}, 0); }}\n");
6134 let text = asm(&source);
6135 assert!(text.contains("movb\t$0, %al"), "{size} bytes is not: {text}");
6136 assert!(!text.contains("call"), "and is not a call either: {text}");
6137 }
6138
6139 let text = asm("int f(int n) { return __atomic_is_lock_free(n, 0); }\n");
6143 assert!(text.contains("movb\t$0, %al"), "a size nobody knows is not lock free: {text}");
6144 let text = asm("int f(int *p) { return __atomic_always_lock_free(8, p); }\n");
6145 assert!(text.contains("movb\t$0, %al"), "eight bytes at four is not: {text}");
6146 let text = asm("int f(long *p) { return __atomic_always_lock_free(8, p); }\n");
6147 assert!(text.contains("movb\t$1, %al"), "and at eight it is: {text}");
6148 }
6149
6150 #[test]
6162 fn a_memory_order_an_operation_cannot_carry_is_read_as_the_strongest() {
6163 let mut opts = options();
6164 opts.emit = EmitKind::Ir;
6165
6166 let acquire_store = run(&opts, "void f(int *p, int v) { __atomic_store_n(p, v, 2); }\n");
6167 assert!(acquire_store.text().contains("seq_cst"), "{:?}", acquire_store.text());
6168 assert!(acquire_store.messages[0].contains("[W0333]"), "{:?}", acquire_store.messages);
6169
6170 let nonsense = run(&opts, "int f(int *p) { return __atomic_load_n(p, 99); }\n");
6171 assert!(nonsense.text().contains("seq_cst"), "{:?}", nonsense.text());
6172 assert!(nonsense.messages[0].contains("[W0333]"), "{:?}", nonsense.messages);
6173
6174 let computed = run(&opts, "int f(int *p, int n) { return __atomic_load_n(p, n); }\n");
6175 assert!(computed.text().contains("seq_cst"), "{:?}", computed.text());
6176 assert_eq!(computed.messages, Vec::<String>::new(), "a computed order is not a mistake");
6177 }
6178
6179 #[test]
6191 fn a_conversion_between_a_float_and_the_widest_unsigned_integer_is_written_without_a_branch() {
6192 let text = asm("double f(unsigned long long x) { return (double)x; }\n");
6193 assert!(text.contains("cvtsi2sdq"), "the signed conversion is what runs: {text}");
6194 assert!(text.contains("shrq"), "with the value halved first: {text}");
6195 assert!(text.contains("addsd"), "and doubled after: {text}");
6196 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
6197
6198 let text = asm("unsigned long long f(double d) { return (unsigned long long)d; }\n");
6199 assert!(text.contains("cvttsd2siq"), "the signed conversion is what runs: {text}");
6200 assert!(text.contains("subsd"), "with half the range taken off first: {text}");
6201 assert!(text.contains("shlq\t$63"), "and the top bit put back: {text}");
6202 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
6203 }
6204
6205 #[test]
6216 fn a_plain_name_the_program_took_is_the_programs_own_function() {
6217 let taken = concat!(
6218 "static long long llabs(long long b) { return 7; }\n",
6219 "long long f(long long x) { return llabs(x); }\n",
6220 );
6221 assert!(ir(taken).contains("call @llabs"), "a static definition is the program's own");
6222
6223 let retyped = concat!("int llabs(int b);\n", "int f(int x) { return llabs(x); }\n",);
6224 assert!(ir(retyped).contains("call @llabs"), "another type is another function");
6225
6226 let plain = concat!(
6227 "long long llabs(long long b);\n",
6228 "long long f(long long x) { return llabs(x); }\n",
6229 );
6230 let mut opts = options();
6231 opts.emit = EmitKind::Ir;
6232 assert!(!run(&opts, plain).text().contains("call @llabs"), "the library's by default");
6233
6234 opts.builtins = false;
6235 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin");
6236
6237 opts.builtins = true;
6238 opts.no_builtin = vec!["llabs".to_owned()];
6239 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin-llabs");
6240 let one = "long labs(long b);\nlong f(long x) { return labs(x); }\n";
6241 assert!(!run(&opts, one).text().contains("call @labs"), "one name and not the family");
6242
6243 opts.no_builtin = Vec::new();
6246 opts.builtins = false;
6247 let prefixed = "long long f(long long x) { return __builtin_llabs(x); }\n";
6248 assert!(!run(&opts, prefixed).text().contains("call @llabs"), "the prefix is a promise");
6249 }
6250
6251 #[test]
6264 fn the_hint_builtins_are_their_first_argument_and_the_hint_leaves_no_trace() {
6265 let text = ir(concat!(
6266 "long a = __builtin_expect(7, 1);\n",
6267 "long b = __builtin_expect_with_probability(9, 1, 0.9);\n",
6268 "unsigned long c = sizeof(__builtin_expect((char)1, 1));\n",
6269 ));
6270 assert!(text.contains("global @a : i64 = 7,"), "{text}");
6271 assert!(text.contains("global @b : i64 = 9,"), "{text}");
6272 assert!(text.contains("global @c : i64 = 8,"), "{text}");
6273 assert!(!text.contains("__builtin_expect"), "it is not a call to anything:\n{text}");
6274
6275 let text = body("long f(char c) { return __builtin_expect(c, 1); }\n");
6278 assert!(text.contains("sext"), "{text}");
6279
6280 let one = "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 1\n %2 = sext.i64 %1\n return %0\n";
6284 assert_eq!(body("int f(void) { int i = 0; __builtin_expect(1, i++); return i; }\n"), one);
6285 let source = "int g(void) { int i = 0; __builtin_expect_with_probability(1, i++, 0.5); return i; }\n";
6286 assert_eq!(body(source), one);
6287
6288 let kept = body("int f(int n) { int i = 0; __builtin_expect(n, i++); return i; }\n");
6293 assert!(kept.contains("add.nsw"), "the hint still runs: {kept}");
6294 assert!(kept.ends_with("return %3\n"), "and the answer is what it left behind: {kept}");
6295 let both = "int g(int n) { int i = 0; __builtin_expect_with_probability(n, i++, 0.5); return i; }\n";
6296 assert!(body(both).contains("add.nsw"), "and so does the one with three arguments");
6297 }
6298
6299 #[test]
6311 fn a_promise_that_control_does_not_arrive_writes_no_instruction() {
6312 let promised = "int f(int x) { if (x) return 1; __builtin_unreachable(); }\n";
6313 let text = ir(promised);
6314 assert!(text.contains(" unreachable_hint\n"), "{text}");
6315 assert!(!text.contains("call"), "it is not a call to anything:\n{text}");
6316
6317 let after = body("int g(int x) { __builtin_unreachable(); return x; }\n");
6321 assert!(after.contains("return"), "{after}");
6322
6323 let text = asm(promised);
6326 let mine = text.split_once("\nf:\n").expect("a definition").1;
6327 let mine = mine.split_once("\t.size").expect("a definition").0;
6328 let plain = asm("int f(int x) { if (x) return 1; }\n");
6329 let plain = plain.split_once("\nf:\n").expect("a definition").1;
6330 let plain = plain.split_once("\t.size").expect("a definition").0;
6331 assert_eq!(mine, plain);
6332 let last = mine.lines().rfind(|line| !line.trim_start().starts_with('.'));
6335 assert_eq!(last.map(str::trim), Some("ret"), "{mine}");
6336 assert!(!mine.contains("ud2"), "{mine}");
6337 }
6338
6339 #[test]
6346 fn a_library_builtin_is_diagnosed_under_the_name_the_program_wrote() {
6347 let mut opts = options();
6348 opts.emit = EmitKind::Ir;
6349 let messages = run(&opts, "void f(void) { __builtin_abort(1); }\n").messages;
6350 assert!(
6351 messages.iter().any(|m| m.contains("__builtin_abort")),
6352 "expected the written name in {messages:?}"
6353 );
6354 }
6355
6356 #[test]
6364 fn a_builtin_nothing_lowers_is_refused_by_name() {
6365 let mut opts = options();
6366 opts.emit = EmitKind::Ir;
6367 let builtin = "__atomic_signal_fence";
6368 let source = format!("int counter;\nint f(void) {{ return ({builtin}(5), 0); }}\n");
6369 let messages = run(&opts, &source).messages;
6370 let named = messages.iter().any(|m| m.contains(builtin) && m.contains("E0686"));
6371 assert!(named, "expected {builtin} to be refused by name in {messages:?}");
6372 }
6373
6374 #[test]
6383 fn what_is_refused_is_the_call_and_not_the_name() {
6384 let text = ir(concat!(
6385 "void __atomic_signal_fence(int order) { (void)order; }\n",
6386 "void f(void) { __atomic_signal_fence(5); }\n",
6387 ));
6388 assert!(text.contains("call @__atomic_signal_fence"), "{text}");
6389 }
6390
6391 #[test]
6400 fn the_object_size_of_an_address_is_what_the_layout_leaves_in_front_of_it() {
6401 let text = ir(concat!(
6402 "struct S { char a[8]; int n; char b[12]; };\n",
6403 "char g[32];\n",
6404 "struct S gs;\n",
6405 "unsigned long whole = __builtin_object_size(g, 0);\n",
6406 "unsigned long moved = __builtin_object_size(g + 4, 0);\n",
6407 "unsigned long back = __builtin_object_size(g + 30 - 2, 0);\n",
6408 "unsigned long outer = __builtin_object_size(gs.a, 0);\n",
6409 "unsigned long inner = __builtin_object_size(gs.a, 1);\n",
6410 "unsigned long scalar = __builtin_object_size(&gs.n, 1);\n",
6411 "unsigned long after = __builtin_object_size(&gs.n, 0);\n",
6412 "unsigned long into = __builtin_object_size(&gs.b[2], 1);\n",
6413 "unsigned long text = __builtin_object_size(\"hello\", 0);\n",
6414 "unsigned long dyn = __builtin_dynamic_object_size(gs.b, 1);\n",
6415 ));
6416 for (name, size) in [
6417 ("whole", 32),
6418 ("moved", 28),
6419 ("back", 4),
6420 ("outer", 24),
6421 ("inner", 8),
6422 ("scalar", 4),
6423 ("after", 16),
6424 ("into", 10),
6425 ("text", 6),
6426 ("dyn", 12),
6427 ] {
6428 let said = format!("global @{name} : i64 = {size},");
6429 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
6430 }
6431 }
6432
6433 #[test]
6441 fn the_object_behind_an_address_can_be_one_with_automatic_storage() {
6442 let text = body(concat!(
6443 "struct S { char a[8]; int n; char b[12]; };\n",
6444 "unsigned long f(void) {\n",
6445 " char loc[20];\n",
6446 " struct S ls;\n",
6447 " return __builtin_object_size(loc + 3, 0) + __builtin_object_size(ls.b + 2, 1);\n",
6448 "}\n",
6449 ));
6450 assert!(text.contains("iconst.i64 17"), "twenty bytes with three used: {text}");
6451 assert!(text.contains("iconst.i64 10"), "twelve bytes with two used: {text}");
6452 }
6453
6454 #[test]
6464 fn an_address_with_no_object_in_sight_answers_at_the_end_of_the_range_its_kind_asks_for() {
6465 let text = ir(concat!(
6466 "struct T { int n; char f[]; };\n",
6467 "extern char *p;\n",
6468 "extern struct T *t;\n",
6469 "unsigned long largest = __builtin_object_size(p, 0);\n",
6470 "unsigned long nearest = __builtin_object_size(p, 1);\n",
6471 "unsigned long least = __builtin_object_size(p, 2);\n",
6472 "unsigned long tight = __builtin_object_size(p, 3);\n",
6473 "unsigned long flex = __builtin_object_size(t->f, 1);\n",
6474 "int says = __builtin_object_size(p, 0) == (unsigned long)-1;\n",
6475 ));
6476 for name in ["largest", "nearest", "flex"] {
6477 let said = format!("global @{name} : i64 = -1,");
6481 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
6482 }
6483 for name in ["least", "tight"] {
6484 let said = format!("global @{name} : i64 = 0,");
6485 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
6486 }
6487 assert!(text.contains("global @says : i32 = 1,"), "{text}");
6488 }
6489
6490 #[test]
6497 fn the_address_an_object_size_is_asked_about_is_not_evaluated() {
6498 let text = body(concat!(
6499 "extern char *side(void);\n",
6500 "unsigned long f(void) { return __builtin_object_size(side(), 0); }\n",
6501 ));
6502 assert!(!text.contains("call"), "nothing is called: {text}");
6503 }
6504
6505 #[test]
6510 fn a_kind_that_is_not_one_of_the_four_is_refused() {
6511 for source in [
6512 "extern char *p;\nextern int k;\nunsigned long f(void) ".to_owned()
6513 + "{ return __builtin_object_size(p, k); }\n",
6514 "extern char *p;\nunsigned long f(void) { return __builtin_object_size(p, 4); }\n"
6515 .to_owned(),
6516 "extern char *p;\nunsigned long f(void) ".to_owned()
6517 + "{ return __builtin_dynamic_object_size(p, -1); }\n",
6518 ] {
6519 let messages = errors(&source);
6520 let named = messages.iter().any(|m| m.contains("E0709") && m.contains("0 to 3"));
6521 assert!(named, "expected a complaint about the kind in {messages:?}");
6522 }
6523 }
6524
6525 #[test]
6531 fn the_pair_that_saves_a_place_lowers_to_the_two_markers() {
6532 let text = ir(concat!(
6533 "void *buf[5];\n",
6534 "int f(void) {\n",
6535 " if (__builtin_setjmp(buf)) return 2;\n",
6536 " return 1;\n",
6537 "}\n",
6538 "void g(void) { __builtin_longjmp(buf, 1); }\n",
6539 ));
6540 assert!(text.contains("= setjmp_marker.i32 %0\n"), "the save answers a value: {text}");
6541 assert!(text.contains(" longjmp_marker %0\n"), "the restore answers nothing: {text}");
6542 assert!(!text.contains("call @"), "neither of them is a call: {text}");
6543 }
6544
6545 #[test]
6553 fn a_local_of_a_function_that_saves_a_place_gets_a_slot() {
6554 let text = ir(concat!(
6555 "void *buf[5];\n",
6556 "int f(int x) { int a = 0; if (__builtin_setjmp(buf)) return a; a = 1; return x; }\n",
6557 "int g(int x) { int a = 0; if (x) return a; a = 1; return x; }\n",
6558 ));
6559 let (saves, plain) = text.split_once("func @g").expect("both functions");
6560 assert_eq!(saves.matches("= alloca").count(), 2, "the parameter and the local: {text}");
6561 assert!(saves.contains("store %9 -> %2"), "the local is written through: {text}");
6562 assert!(!plain.contains("alloca"), "nothing in the plain one needs a slot: {text}");
6563 }
6564
6565 #[test]
6574 fn the_save_writes_four_words_and_carries_on_in_a_new_block() {
6575 let text =
6576 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
6577 let body = text.split_once("\nf:\n").expect("the function").1;
6578 assert!(body.contains("\tmovq\t%rsp, %rbp\n"), "a frame pointer whatever: {text}");
6579 assert!(body.contains("\tsubq\t$8, %rsp\n"), "no red zone: {text}");
6580 assert!(body.contains("\tmovq\t%rbp, (%rax)\n"), "the frame pointer: {text}");
6581 assert!(body.contains("\tmovq\t%rsp, 16(%rax)\n"), "the stack pointer: {text}");
6582 assert!(body.contains("\tleaq\t.Lf_1(%rip), %rcx\n"), "where to come back to: {text}");
6583 assert!(body.contains("\tmovq\t%rcx, 8(%rax)\n"), "and that goes in the buffer: {text}");
6584 let back = body.split_once(".Lf_1:\n").expect("the block control comes back to").1;
6585 assert!(back.starts_with("\tmovq\t(%rsp), %rax\n"), "the answer is read back: {text}");
6586 }
6587
6588 #[test]
6596 fn a_save_destroys_every_register_the_allocator_hands_out() {
6597 let text =
6598 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
6599 for reg in ["%rbx", "%r12", "%r13", "%r14", "%r15"] {
6600 assert!(text.contains(&format!("\tpushq\t{reg}\n")), "{reg} is saved: {text}");
6601 assert!(text.contains(&format!("\tpopq\t{reg}\n")), "{reg} is restored: {text}");
6602 }
6603 }
6604
6605 #[test]
6612 fn the_restore_puts_the_frame_back_before_it_jumps() {
6613 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
6614 let mut opts = options();
6615 opts.emit = EmitKind::Asm;
6616 opts.opt_level = level;
6617 let source = "void *buf[5];\nvoid g(void) { __builtin_longjmp(buf, 1); }\n";
6618 let result = run(&opts, source);
6619 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
6620 let text = result.text().to_owned();
6621 let jump = text.find("\tjmp\t*%").unwrap_or_else(|| panic!("an indirect jump: {text}"));
6622 let stack = text.find(", %rsp\n").unwrap_or_else(|| panic!("the stack back: {text}"));
6623 let frame = text.find(", %rbp\n").unwrap_or_else(|| panic!("the frame back: {text}"));
6624 assert!(stack < jump, "the stack goes back first at {level:?}: {text}");
6625 assert!(frame < jump, "and so does the frame at {level:?}: {text}");
6626 }
6627 }
6628
6629 #[test]
6635 fn a_longjmp_whose_second_argument_is_not_one_is_turned_down() {
6636 for source in [
6637 "void *buf[5];\nvoid f(void) { __builtin_longjmp(buf, 0); }\n",
6638 "void *buf[5];\nextern int v;\nvoid f(void) { __builtin_longjmp(buf, v); }\n",
6639 ] {
6640 let messages = errors(source);
6641 let named = messages.iter().any(|m| m.contains("E0710"));
6642 assert!(named, "expected a complaint about the value in {messages:?}");
6643 }
6644 }
6645
6646 #[test]
6651 fn a_static_function_nothing_refers_to_is_not_emitted() {
6652 let text = ir("static int dropped(void) { return 1; }\n\
6653 static int kept(void) { return 2; }\n\
6654 int main(void) { return kept(); }\n");
6655 assert!(text.contains("func @kept"), "{text}");
6656 assert!(!text.contains("dropped"), "{text}");
6657 }
6658
6659 #[test]
6665 fn two_static_functions_that_only_call_each_other_are_both_dropped() {
6666 let text = ir("static int ping(void);\n\
6667 static int pong(void) { return ping(); }\n\
6668 static int ping(void) { return pong(); }\n\
6669 int main(void) { return 0; }\n");
6670 assert!(!text.contains("ping"), "{text}");
6671 assert!(!text.contains("pong"), "{text}");
6672 }
6673
6674 #[test]
6680 fn naming_a_static_function_anywhere_keeps_it() {
6681 let text = ir("static int by_address(void) { return 1; }\n\
6682 static int in_an_image(void) { return 2; }\n\
6683 static int deeper(void) { return 3; }\n\
6684 static int reaches_deeper(void) { return deeper(); }\n\
6685 static int (*table[1])(void) = {in_an_image};\n\
6686 int main(void) {\n\
6687 int (*p)(void) = by_address;\n\
6688 return p() + table[0]() + reaches_deeper();\n\
6689 }\n");
6690 for kept in ["by_address", "in_an_image", "deeper", "reaches_deeper"] {
6691 assert!(text.contains(&format!("func @{kept}")), "expected {kept} in:\n{text}");
6692 }
6693 }
6694
6695 #[test]
6701 fn an_attribute_keeps_a_static_function_nothing_refers_to() {
6702 for attribute in ["used", "retain", "constructor", "destructor", "__used__"] {
6703 let source = format!(
6704 "__attribute__(({attribute})) static int kept(void) {{ return 1; }}\n\
6705 int main(void) {{ return 0; }}\n"
6706 );
6707 let text = ir(&source);
6708 assert!(text.contains("func @kept"), "for {attribute}:\n{text}");
6709 }
6710 }
6711
6712 #[test]
6715 fn a_function_anything_could_call_is_emitted_without_being_called() {
6716 let text =
6717 ir("int nobody_here_calls_it(void) { return 1; }\nint main(void) { return 0; }\n");
6718 assert!(text.contains("func @nobody_here_calls_it"), "{text}");
6719 }
6720
6721 #[test]
6728 fn a_classification_c_has_an_operator_for_is_that_operator() {
6729 for (builtin, operator) in [
6730 ("__builtin_isgreater", "binary >"),
6731 ("__builtin_isgreaterequal", "binary >="),
6732 ("__builtin_isless", "binary <"),
6733 ("__builtin_islessequal", "binary <="),
6734 ] {
6735 let source = format!("int f(double x, double y) {{ return {builtin}(x, y); }}\n");
6736 let text = tast(&source);
6737 assert!(text.contains(&format!("{operator} : int")), "for {builtin}:\n{text}");
6738 }
6739 }
6740
6741 #[test]
6750 fn the_classification_builtins_are_comparisons_and_not_calls() {
6751 let text = body("int f(double x, double y) { return __builtin_isunordered(x, y); }\n");
6752 assert_eq!(
6753 text,
6754 "block0(%0: f64, %1: f64):\n %2 = fcmp uno %0, %1\n %3 = zext.i32 \
6755 %2\n return %3\n"
6756 );
6757
6758 let text = body("int f(double x, double y) { return __builtin_islessgreater(x, y); }\n");
6760 assert!(text.contains("fcmp one %0, %1"), "{text}");
6761
6762 let text = body("int f(double x) { return __builtin_isnan(x); }\n");
6763 assert!(text.contains("fcmp uno %0, %0"), "{text}");
6764
6765 let text = body("int f(double x) { return __builtin_isinf(x); }\n");
6766 assert!(text.contains("fconst.f64 0x7ff0000000000000"), "{text}");
6767 assert!(text.contains("fconst.f64 0xfff0000000000000"), "{text}");
6768 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
6769 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
6770 assert!(text.contains("%5 = or %3, %4"), "{text}");
6771
6772 let text = body("int f(double x) { return __builtin_isfinite(x); }\n");
6775 assert!(text.contains("%3 = fcmp olt %2, %0"), "{text}");
6776 assert!(text.contains("%4 = fcmp olt %0, %1"), "{text}");
6777 assert!(text.contains("%5 = and %3, %4"), "{text}");
6778
6779 let text = body("int f(double x) { return __builtin_signbit(x); }\n");
6780 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
6781 assert!(text.contains("icmp slt %1, %2"), "{text}");
6782
6783 let text = body("int f(long double x) { return __builtin_signbitl(x); }\n");
6787 assert!(text.contains("load.i16"), "{text}");
6788 assert!(text.contains("icmp slt"), "{text}");
6789 assert!(!text.contains("i80"), "{text}");
6790
6791 let text = body("double g(void);\nint f(void) { return __builtin_isnan(g()); }\n");
6794 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
6795 }
6796
6797 #[test]
6804 fn a_classification_spelling_that_names_a_width_converts_before_it_asks() {
6805 let text = ir(concat!(
6806 "int a = __builtin_isinff(1e300);\n",
6807 "int b = __builtin_isinf(1e300);\n",
6808 "int c = __builtin_isnan(0.0);\n",
6812 "int d = __builtin_signbit(-0.0);\n",
6813 "int e = __builtin_islessgreater(1.0, 2.0);\n",
6814 ));
6815 assert!(text.contains("global @a : i32 = 1,"), "{text}");
6816 assert!(text.contains("global @b : i32 = 0,"), "{text}");
6817 assert!(text.contains("global @c : i32 = 0,"), "{text}");
6818 assert!(text.contains("global @d : i32 = 1,"), "{text}");
6819 assert!(text.contains("global @e : i32 = 1,"), "{text}");
6820 }
6821
6822 #[test]
6824 fn a_classification_builtin_refuses_an_argument_that_is_not_floating_point() {
6825 let mut opts = options();
6826 opts.emit = EmitKind::Ir;
6827 let source = concat!(
6828 "int a(int x) { return __builtin_isnan(x); }\n",
6829 "int b(int x, int y) { return __builtin_isunordered(x, y); }\n",
6830 "int c(double x) { return __builtin_isnan(x, x); }\n",
6831 );
6832 let messages = run(&opts, source).messages;
6833 assert_eq!(
6834 messages,
6835 [
6836 "/main.c:1:23: error: non-floating-point argument in call to function \
6837 '__builtin_isnan' [E0685]",
6838 "/main.c:2:30: error: non-floating-point arguments in call to function \
6839 '__builtin_isunordered' [E0685]",
6840 "/main.c:3:26: error: too many arguments to function '__builtin_isnan' [E0511]",
6841 ]
6842 );
6843 }
6844
6845 #[test]
6854 fn the_last_three_classification_builtins_are_comparisons_and_not_calls() {
6855 let text = body("int f(double x) { return __builtin_isnormal(x); }\n");
6856 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
6860 assert!(text.contains("%2 = iconst.i64 9223372036854775807"), "{text}");
6861 assert!(text.contains("%3 = and %1, %2"), "{text}");
6862 assert!(text.contains("%4 = iconst.i64 4503599627370496"), "{text}");
6863 assert!(text.contains("%5 = iconst.i64 9218868437227405312"), "{text}");
6864 assert!(text.contains("%6 = icmp uge %3, %4"), "{text}");
6865 assert!(text.contains("%7 = icmp ult %3, %5"), "{text}");
6866 assert!(text.contains("%8 = and %6, %7"), "{text}");
6867
6868 let text = body("int f(long double x) { return __builtin_isnormal(x); }\n");
6873 assert!(text.contains("fconst.f80 0x18000000000000000"), "{text}");
6874 assert!(text.contains("fconst.f80 0x7fff8000000000000000"), "{text}");
6875 assert!(text.contains("fcmp oge"), "{text}");
6876 assert!(text.contains("fcmp olt"), "{text}");
6877
6878 let text = body("int f(double x) { return __builtin_isinf_sign(x); }\n");
6879 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
6880 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
6881 assert!(text.contains("%7 = sub %5, %6"), "{text}");
6882
6883 let text = body("int f(double x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n");
6884 assert!(text.contains("fcmp uno %0, %0"), "{text}");
6885 assert!(text.contains("fcmp oeq %0, %6"), "{text}");
6886 assert_eq!(text.matches(" = zext.i32 ").count(), 4, "{text}");
6890 assert_eq!(text.matches(" = xor ").count(), 4, "{text}");
6891 assert!(!text.contains("call"), "{text}");
6892
6893 let text = body(concat!(
6896 "double g(void);\n",
6897 "int f(void) { return __builtin_fpclassify(0, 1, 2, 3, 4, g()); }\n",
6898 ));
6899 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
6900 }
6901
6902 #[test]
6909 fn the_last_three_classification_builtins_fold_where_their_operand_is_a_constant() {
6910 let text = ir(concat!(
6911 "int a = __builtin_isnormal(1.0);\n",
6912 "int b = __builtin_isnormal(0.0);\n",
6913 "int c = __builtin_isnormal(1.0 / 0.0);\n",
6914 "int d = __builtin_isinf_sign(-1.0 / 0.0);\n",
6915 "int e = __builtin_isinf_sign(1.0);\n",
6916 "int g = __builtin_fpclassify(0, 1, 2, 3, 4, 0.0);\n",
6917 "int h = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0);\n",
6918 "int i = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0 / 0.0);\n",
6919 ));
6920 assert!(text.contains("global @a : i32 = 1,"), "{text}");
6921 assert!(text.contains("global @b : i32 = 0,"), "{text}");
6922 assert!(text.contains("global @c : i32 = 0,"), "{text}");
6923 assert!(text.contains("global @d : i32 = -1,"), "{text}");
6924 assert!(text.contains("global @e : i32 = 0,"), "{text}");
6925 assert!(text.contains("global @g : i32 = 4,"), "{text}");
6926 assert!(text.contains("global @h : i32 = 2,"), "{text}");
6927 assert!(text.contains("global @i : i32 = 1,"), "{text}");
6928 }
6929
6930 #[test]
6936 fn fpclassify_refuses_an_answer_that_is_not_an_integer_constant() {
6937 let mut opts = options();
6938 opts.emit = EmitKind::Ir;
6939 let source = concat!(
6940 "int a(double x, int n) { return __builtin_fpclassify(0, 1, n, 3, 4, x); }\n",
6941 "int b(double x) { return __builtin_fpclassify(0, 1, 2, 3, x); }\n",
6942 "int c(int x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n",
6943 );
6944 let messages = run(&opts, source).messages;
6945 assert_eq!(
6946 messages,
6947 [
6948 "/main.c:1:60: error: non-const integer argument 3 in call to function \
6949 '__builtin_fpclassify' [E0687]",
6950 "/main.c:2:26: error: too few arguments to function '__builtin_fpclassify' \
6951 [E0511]",
6952 "/main.c:3:23: error: non-floating-point argument in call to function \
6953 '__builtin_fpclassify' [E0685]",
6954 ]
6955 );
6956 }
6957
6958 #[test]
6966 fn a_builtin_whose_answer_is_a_constant_is_one_and_not_a_call() {
6967 let text = ir(concat!(
6968 "double a = __builtin_inf();\n",
6969 "float b = __builtin_huge_valf();\n",
6970 "long double c = __builtin_infl();\n",
6971 "double d = __builtin_huge_val();\n",
6972 ));
6973 assert!(text.contains("global @a : f64 = 0x7ff0000000000000,"), "{text}");
6974 assert!(text.contains("global @b : f32 = 0x7f800000,"), "{text}");
6975 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
6976 assert!(text.contains("global @d : f64 = 0x7ff0000000000000,"), "{text}");
6977 assert!(!text.contains("call"), "{text}");
6978 }
6979
6980 #[test]
6989 fn a_nan_is_written_with_the_payload_the_program_asked_for() {
6990 let text = ir(concat!(
6991 "double a = __builtin_nan(\"\");\n",
6992 "double b = __builtin_nan(\"0x1\");\n",
6993 "double c = __builtin_nan(\"010\");\n",
6995 "double d = __builtin_nans(\"\");\n",
6996 "double e = __builtin_nans(\"0x1\");\n",
6997 "float f = __builtin_nanf(\"0x1\");\n",
6998 "float g = __builtin_nansf(\"\");\n",
6999 "long double h = __builtin_nansl(\"\");\n",
7000 ));
7001 assert!(text.contains("global @a : f64 = 0x7ff8000000000000,"), "{text}");
7002 assert!(text.contains("global @b : f64 = 0x7ff8000000000001,"), "{text}");
7003 assert!(text.contains("global @c : f64 = 0x7ff8000000000008,"), "{text}");
7004 assert!(text.contains("global @d : f64 = 0x7ff4000000000000,"), "{text}");
7005 assert!(text.contains("global @e : f64 = 0x7ff0000000000001,"), "{text}");
7006 assert!(text.contains("global @f : f32 = 0x7fc00001,"), "{text}");
7007 assert!(text.contains("global @g : f32 = 0x7fa00000,"), "{text}");
7008 assert!(text.contains("f80 0x7fffa000000000000000"), "{text}");
7009
7010 let text = ir(concat!(
7013 "double f(const char *p) { return __builtin_nan(p); }\n",
7014 "double g(void) { return __builtin_nans(\"1x\"); }\n",
7015 ));
7016 assert_eq!(text.matches("call @nan(").count(), 1, "{text}");
7017 assert_eq!(text.matches("call @nans(").count(), 1, "{text}");
7018 }
7019
7020 #[test]
7028 fn the_length_and_the_order_of_a_string_literal_are_known_here() {
7029 let text = ir(concat!(
7030 "unsigned long a = __builtin_strlen(\"hello\");\n",
7031 "unsigned long b = __builtin_strlen(\"a\\0bc\");\n",
7032 "int c = __builtin_strcmp(\"X\", \"X\\376\") < 0;\n",
7033 "int d = __builtin_strcmp(\"abc\", \"abc\");\n",
7034 "int e = __builtin_strcmp(\"abc\", \"ab\") > 0;\n",
7035 ));
7036 assert!(text.contains("global @a : i64 = 5,"), "{text}");
7037 assert!(text.contains("global @b : i64 = 1,"), "{text}");
7038 assert!(text.contains("global @c : i32 = 1,"), "{text}");
7039 assert!(text.contains("global @d : i32 = 0,"), "{text}");
7040 assert!(text.contains("global @e : i32 = 1,"), "{text}");
7041 assert!(!text.contains("call"), "{text}");
7042
7043 let text = ir("unsigned long f(const char *p) { return __builtin_strlen(p); }\n");
7045 assert!(text.contains("call @strlen("), "{text}");
7046 }
7047
7048 #[test]
7055 fn a_sign_builtin_is_a_mask_over_the_bits_and_not_a_call() {
7056 let text = body("double f(double x) { return __builtin_fabs(x); }\n");
7057 assert!(text.contains("bitcast.i64 %0"), "{text}");
7058 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
7059 assert!(text.contains("and %1, %2"), "{text}");
7060 assert!(text.contains("bitcast.f64 %3"), "{text}");
7061 assert!(!text.contains("call"), "{text}");
7062
7063 let text = body("double f(double x, double y) { return __builtin_copysign(x, y); }\n");
7064 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
7065 assert!(text.contains("%8 = or %4, %7"), "{text}");
7066 assert!(!text.contains("call"), "{text}");
7067
7068 let text = body("long double f(long double x) { return __builtin_fabsl(x); }\n");
7071 assert!(text.contains("iconst.i16 32767"), "{text}");
7072 assert!(text.contains("load.f80"), "{text}");
7073 assert!(!text.contains("call"), "{text}");
7074
7075 let text = body("double f(float x) { return __builtin_fabs(x); }\n");
7078 assert!(text.contains("fpext.f64 %0"), "{text}");
7079 assert!(text.contains("bitcast.i64 %1"), "{text}");
7080 }
7081
7082 #[test]
7089 fn a_shuffle_picks_each_lane_by_the_low_bits_of_the_mask() {
7090 let text = body(concat!(
7091 "typedef int v2 __attribute__((vector_size(8)));\n",
7092 "void f(v2 *v, v2 m) { *v = __builtin_shuffle(*v, m); }\n",
7093 ));
7094 assert!(text.contains("memcpy"), "{text}");
7095 assert_eq!(text.matches("iconst.i32 1\n").count(), 2, "{text}");
7096 assert_eq!(text.matches(" = and ").count(), 2, "{text}");
7097 assert!(!text.contains("call"), "{text}");
7098
7099 let text = body(concat!(
7102 "typedef char v4 __attribute__((vector_size(4)));\n",
7103 "v4 f(v4 a, v4 b, v4 m) { return __builtin_shuffle(a, b, m); }\n",
7104 ));
7105 assert_eq!(text.matches("iconst.i32 7\n").count(), 4, "{text}");
7106 assert!(text.contains("zext.i32"), "{text}");
7107 assert!(!text.contains("call"), "{text}");
7108 }
7109
7110 #[test]
7112 fn a_shuffle_refuses_what_gcc_refuses() {
7113 let mut opts = options();
7114 opts.emit = EmitKind::Ir;
7115 let source = concat!(
7116 "typedef int v4 __attribute__((vector_size(16)));\n",
7117 "typedef float f4 __attribute__((vector_size(16)));\n",
7118 "typedef short s8 __attribute__((vector_size(16)));\n",
7119 "typedef long long l4 __attribute__((vector_size(32)));\n",
7120 "void a(v4 x, f4 m) { __builtin_shuffle(x, m); }\n",
7121 "void b(int x, v4 m) { __builtin_shuffle(x, m); }\n",
7122 "void c(v4 x, f4 y, v4 m) { __builtin_shuffle(x, y, m); }\n",
7123 "void d(v4 x, s8 m) { __builtin_shuffle(x, m); }\n",
7124 "void e(f4 x, l4 m) { __builtin_shuffle(x, m); }\n",
7125 "void g(v4 x) { __builtin_shuffle(x); }\n",
7126 );
7127 let messages = run(&opts, source).messages;
7128 let wanted = [
7129 "last argument must be an integer vector [E0715]",
7130 "arguments must be vectors [E0715]",
7131 "argument vectors must be of the same type [E0715]",
7132 "number of elements of the argument vector(s) and the mask vector should be the same \
7133 [E0715]",
7134 "argument vector(s) inner type must have the same size as inner type of the mask \
7135 [E0715]",
7136 "too few arguments to function '__builtin_shuffle' [E0511]",
7137 ];
7138 assert_eq!(messages.len(), wanted.len(), "{messages:?}");
7139 for (message, wanted) in messages.iter().zip(wanted) {
7140 assert!(message.ends_with(wanted), "{message}");
7141 }
7142 }
7143
7144 #[test]
7153 fn the_plain_math_names_are_the_same_mask_and_not_a_call() {
7154 let text =
7155 body(concat!("double fabs(double x);\n", "double f(double x) { return fabs(x); }\n",));
7156 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
7157 assert!(!text.contains("call"), "{text}");
7158
7159 let text =
7160 body(concat!("float fabsf(float x);\n", "float f(float x) { return fabsf(x); }\n",));
7161 assert!(text.contains("bitcast.i32 %0"), "{text}");
7162 assert!(!text.contains("call"), "{text}");
7163
7164 let text = body(concat!(
7165 "double copysign(double x, double y);\n",
7166 "double f(double x, double y) { return copysign(x, y); }\n",
7167 ));
7168 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
7169 assert!(!text.contains("call"), "{text}");
7170
7171 let text = body(concat!(
7172 "float copysignf(float x, float y);\n",
7173 "float f(float x, float y) { return copysignf(x, y); }\n",
7174 ));
7175 assert!(!text.contains("call"), "{text}");
7176
7177 let text = ir(concat!(
7181 "long double fabsl(long double x);\n",
7182 "long double f(long double x) { return fabsl(x); }\n",
7183 ));
7184 assert!(text.contains("call @fabsl"), "{text}");
7185 }
7186
7187 #[test]
7195 fn a_plain_math_name_the_program_took_is_the_programs_own_function() {
7196 let taken = concat!(
7197 "static double fabs(double b) { return 7; }\n",
7198 "double f(double x) { return fabs(x); }\n",
7199 );
7200 assert!(ir(taken).contains("call @fabs"), "a static definition is the program's own");
7201
7202 let retyped = concat!("int fabs(int b);\n", "int f(int x) { return fabs(x); }\n");
7203 assert!(ir(retyped).contains("call @fabs"), "another type is another function");
7204
7205 let plain = concat!("double fabs(double b);\n", "double f(double x) { return fabs(x); }\n");
7206 let mut opts = options();
7207 opts.emit = EmitKind::Ir;
7208 assert!(!run(&opts, plain).text().contains("call @fabs"), "the library's by default");
7209
7210 opts.builtins = false;
7211 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin");
7212
7213 opts.builtins = true;
7214 opts.no_builtin = vec!["fabs".to_owned()];
7215 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin-fabs");
7216 let one = concat!(
7217 "double copysign(double a, double b);\n",
7218 "double f(double x) { return copysign(x, 1.0); }\n",
7219 );
7220 assert!(!run(&opts, one).text().contains("call @copysign"), "one name and not the family");
7221
7222 opts.no_builtin = Vec::new();
7224 opts.builtins = false;
7225 let prefixed = "double f(double x) { return __builtin_fabs(x); }\n";
7226 assert!(!run(&opts, prefixed).text().contains("call @fabs"), "the prefix is not a library");
7227 }
7228
7229 #[test]
7238 fn the_sign_builtins_answer_a_zero_and_a_nan_the_way_the_bits_say() {
7239 let text = ir(concat!(
7240 "double a = __builtin_fabs(-3.5);\n",
7241 "double b = __builtin_copysign(1.0, -0.0);\n",
7242 "double c = __builtin_copysign(0.0, -2.0);\n",
7243 "double d = __builtin_copysign(-__builtin_nan(\"\"), 1.0);\n",
7245 "double e = __builtin_fabs(-__builtin_nan(\"0x1\"));\n",
7246 "float g = __builtin_copysignf(-0.0f, 2.0f);\n",
7247 "long double h = __builtin_copysignl(1.0L, -1.0L);\n",
7248 "long double i = __builtin_fabsl(-__builtin_infl());\n",
7249 ));
7250 assert!(text.contains("global @a : f64 = 0x400c000000000000,"), "{text}");
7251 assert!(text.contains("global @b : f64 = 0xbff0000000000000,"), "{text}");
7252 assert!(text.contains("global @c : f64 = 0x8000000000000000,"), "{text}");
7253 assert!(text.contains("global @d : f64 = 0x7ff8000000000000,"), "{text}");
7254 assert!(text.contains("global @e : f64 = 0x7ff8000000000001,"), "{text}");
7255 assert!(text.contains("global @g : f32 = 0x0,"), "{text}");
7256 assert!(text.contains("f80 0xbfff8000000000000000"), "{text}");
7257 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
7258 }
7259
7260 #[test]
7267 fn the_sign_of_a_long_double_is_in_the_word_at_the_top_of_it() {
7268 for source in [
7269 "int f(long double x) { return __builtin_signbit(x); }\n",
7270 "long double f(long double x) { return __builtin_fabsl(x); }\n",
7271 "long double f(long double x, long double y) { return __builtin_copysignl(x, y); }\n",
7272 "int f(long double x) { return __builtin_isnormal(x); }\n",
7273 ] {
7274 let text = body(source);
7275 assert!(!text.contains("i80"), "{text}");
7276 assert!(text.contains("i16"), "{text}");
7277 }
7278 }
7279
7280 #[test]
7288 fn the_complex_builtins_are_the_halves_of_the_value_and_not_a_call() {
7289 let text = body("double f(_Complex double z) { return __builtin_creal(z); }\n");
7290 assert!(!text.contains("call"), "{text}");
7291 let text = body("double f(_Complex double z) { return __builtin_cimag(z); }\n");
7292 assert!(!text.contains("call"), "{text}");
7293
7294 let text = body("_Complex double f(_Complex double z) { return __builtin_conj(z); }\n");
7297 assert_eq!(text.matches("fneg").count(), 1, "{text}");
7298 assert!(!text.contains("call"), "{text}");
7299 let negated = body("_Complex double f(_Complex double z) { return -z; }\n");
7300 assert_eq!(negated.matches("fneg").count(), 2, "{negated}");
7301
7302 let written = body("_Complex double f(_Complex double z) { return ~z; }\n");
7305 assert_eq!(written, text, "the name and the operator are the same thing");
7306
7307 let text = body(concat!(
7309 "double creal(_Complex double z);\n",
7310 "double f(_Complex double z) { return creal(z); }\n",
7311 ));
7312 assert!(!text.contains("call"), "{text}");
7313 let text = body(concat!(
7314 "_Complex float conjf(_Complex float z);\n",
7315 "_Complex float f(_Complex float z) { return conjf(z); }\n",
7316 ));
7317 assert_eq!(text.matches("fneg").count(), 1, "{text}");
7318 assert!(!text.contains("call"), "{text}");
7319
7320 let taken = concat!(
7323 "static double creal(_Complex double z) { return 7; }\n",
7324 "double f(_Complex double z) { return creal(z); }\n",
7325 );
7326 assert!(ir(taken).contains("call @creal"), "a static definition is the program's own");
7327 let retyped = concat!("int cimag(int z);\n", "int f(int z) { return cimag(z); }\n");
7328 assert!(ir(retyped).contains("call @cimag"), "another type is another function");
7329 let plain = concat!(
7330 "double cimag(_Complex double z);\n",
7331 "double f(_Complex double z) { return cimag(z); }\n",
7332 );
7333 let mut opts = options();
7334 opts.emit = EmitKind::Ir;
7335 opts.builtins = false;
7336 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin");
7337 opts.builtins = true;
7338 opts.no_builtin = vec!["cimag".to_owned()];
7339 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin-cimag");
7340
7341 let text = ir(concat!(
7343 "double a = __builtin_creal(1.5 + 2.5i);\n",
7344 "double b = __builtin_cimag(1.5 + 2.5i);\n",
7345 "_Complex double c = __builtin_conj(1.5 + 2.5i);\n",
7346 ));
7347 assert!(text.contains("global @a : f64 = 0x3ff8000000000000,"), "{text}");
7348 assert!(text.contains("global @b : f64 = 0x4004000000000000,"), "{text}");
7349 assert!(
7350 text.contains("{ f64 0x3ff8000000000000, f64 0xc004000000000000 }"),
7351 "the conjugate of a constant is the constant with the second half negated: {text}"
7352 );
7353 assert!(!text.contains("call"), "{text}");
7354 }
7355
7356 #[test]
7364 fn a_math_library_builtin_of_a_constant_is_the_answer_and_not_a_call() {
7365 let text = ir(concat!(
7366 "double a = __builtin_ceil(1.5);\n",
7367 "double b = __builtin_floor(1.5);\n",
7368 "double c = __builtin_trunc(-1.5);\n",
7369 "double d = __builtin_round(2.5);\n",
7372 "double e = __builtin_ceil(-0.5);\n",
7374 "double f = __builtin_fmax(1.0, 2.0);\n",
7375 "double g = __builtin_fmin(1.0, 2.0);\n",
7376 "float h = __builtin_ceilf(1.25f);\n",
7377 "double ceil(double x);\n",
7380 "double i = ceil(2.25);\n",
7381 ));
7382 assert!(text.contains("global @a : f64 = 0x4000000000000000,"), "{text}");
7383 assert!(text.contains("global @b : f64 = 0x3ff0000000000000,"), "{text}");
7384 assert!(text.contains("global @c : f64 = 0xbff0000000000000,"), "{text}");
7385 assert!(text.contains("global @d : f64 = 0x4008000000000000,"), "{text}");
7386 assert!(text.contains("global @e : f64 = 0x8000000000000000,"), "{text}");
7387 assert!(text.contains("global @f : f64 = 0x4000000000000000,"), "{text}");
7388 assert!(text.contains("global @g : f64 = 0x3ff0000000000000,"), "{text}");
7389 assert!(text.contains("global @h : f32 = 0x40000000,"), "{text}");
7390 assert!(text.contains("global @i : f64 = 0x4008000000000000,"), "{text}");
7391 assert!(!text.contains("call"), "{text}");
7392 }
7393
7394 #[test]
7402 fn a_math_library_builtin_of_anything_else_is_a_call_to_the_library() {
7403 let text = ir(concat!(
7404 "double f(double x) { return __builtin_ceil(x); }\n",
7405 "float g(float x) { return __builtin_floorf(x); }\n",
7406 "double h(double x, double y) { return __builtin_fmax(x, y); }\n",
7407 ));
7408 assert!(text.contains("call @ceil("), "{text}");
7409 assert!(text.contains("call @floorf("), "{text}");
7410 assert!(text.contains("call @fmax("), "{text}");
7411
7412 let text = ir(concat!(
7416 "double f(void) { return __builtin_rint(2.5); }\n",
7417 "double g(void) { return __builtin_nearbyint(2.5); }\n",
7418 ));
7419 assert!(text.contains("call @rint("), "{text}");
7420 assert!(text.contains("call @nearbyint("), "{text}");
7421
7422 let text = ir("double f(void) { return __builtin_fmin(__builtin_nan(\"\"), 1.0); }\n");
7425 assert!(text.contains("call @fmin("), "{text}");
7426
7427 let plain = concat!("double ceil(double x);\n", "double f(void) { return ceil(2.25); }\n");
7430 let mut opts = options();
7431 opts.emit = EmitKind::Ir;
7432 opts.no_builtin = vec!["ceil".to_owned()];
7433 assert!(run(&opts, plain).text().contains("call @ceil("), "-fno-builtin-ceil");
7434 }
7435
7436 #[test]
7443 fn a_constexpr_object_is_a_constant_wherever_one_is_required() {
7444 let text = ir(concat!(
7445 "constexpr int side = 4;\n",
7446 "constexpr int wider = side + 1;\n",
7447 "constexpr double half = 1.5;\n",
7448 "struct point { int x; int y; };\n",
7449 "constexpr struct point origin = { 5, 6 };\n",
7450 "int square[side * side];\n",
7451 "int rectangle[wider];\n",
7452 "int rounded[(int)half * 2];\n",
7453 "int across[origin.y];\n",
7454 "enum named { four = side };\n",
7455 "int e = four;\n",
7456 ));
7457 assert!(text.contains("global @square : bytes 64 ="), "{text}");
7458 assert!(text.contains("global @rectangle : bytes 20 ="), "{text}");
7459 assert!(text.contains("global @rounded : bytes 8 ="), "{text}");
7460 assert!(text.contains("global @across : bytes 24 ="), "{text}");
7461 assert!(text.contains("global @e : i32 = 4,"), "{text}");
7462
7463 let mut opts = options();
7466 opts.emit = EmitKind::Ir;
7467 let konst = "const int n = 1;\nint a[n];\n";
7468 let message = "/main.c:2:5: error: variably modified 'a' at file scope [E0538]";
7469 assert_eq!(run(&opts, konst).messages, [message]);
7470
7471 let subscript = "constexpr int t[3] = { 1, 2, 3 };\nint a[t[1]];\n";
7473 assert_eq!(run(&opts, subscript).messages, [message]);
7474
7475 let address = "constexpr int c = 3;\nint *p = &c;\n";
7477 let warning = "/main.c:2:6: warning: initialization discards 'const' qualifier from \
7478 pointer target type [E0514]";
7479 assert_eq!(run(&opts, address).messages, [warning]);
7480 }
7481
7482 #[test]
7491 fn a_member_whose_size_was_refused_is_not_a_flexible_array_member() {
7492 let mut opts = options();
7493 opts.emit = EmitKind::Ir;
7494
7495 let alone = "int k;\nextern struct D { int a[k]; } ed;\n";
7496 let message = "/main.c:2:23: error: variably modified 'a' at file scope [E0538]";
7497 assert_eq!(run(&opts, alone).messages, [message]);
7498
7499 let first = "int k;\nextern struct E { int a[k]; int b; } ee;\n";
7501 assert_eq!(run(&opts, first).messages, [message]);
7502
7503 let negative = "struct F { int a[-1]; };\n";
7506 let refused = "/main.c:1:18: error: size of array 'a' is negative [E0536]";
7507 assert_eq!(run(&opts, negative).messages, [refused]);
7508
7509 let flexible = "struct G { int a[]; };\n";
7512 let named = "/main.c:1:16: error: flexible array member in a struct with no named \
7513 members [E0554]";
7514 assert_eq!(run(&opts, flexible).messages, [named]);
7515 }
7516
7517 #[test]
7532 fn a_pointer_to_an_array_gains_a_qualifier_the_same_way_a_pointer_to_anything_else_does() {
7533 let mut opts = options();
7534 opts.emit = EmitKind::Ir;
7535 let prefix = "typedef unsigned int B[4];\nstruct H { B category[2]; };\n";
7536
7537 let adding = format!("{prefix}const B *f(struct H *h) {{ return &h->category[0]; }}\n");
7539 assert_eq!(run(&opts, &adding).messages, [] as [String; 0]);
7540
7541 let plain = concat!(
7544 "const unsigned int (*f(unsigned int (*p)[4]))[4] { return p; }\n",
7545 "const unsigned int (*g(unsigned int (*p)[2][3]))[2][3] { return p; }\n",
7546 );
7547 assert_eq!(run(&opts, plain).messages, [] as [String; 0]);
7548
7549 let dropping = format!("{prefix}B *f(const B *p) {{ return p; }}\n");
7552 let warning = "/main.c:3:27: warning: return discards 'const' qualifier from pointer target type \
7553 [E0514]";
7554 assert_eq!(run(&opts, &dropping).messages, [warning]);
7555
7556 let wrong = "const unsigned int (*f(unsigned short (*p)[4]))[4] { return p; }\n";
7559 let error = "/main.c:1:61: error: returning 'unsigned short (*)[4]' from a function with \
7560 incompatible return type 'const unsigned int (*)[4]' [E0512]";
7561 assert_eq!(run(&opts, wrong).messages, [error]);
7562 }
7563
7564 #[test]
7573 fn an_old_style_definition_takes_its_types_from_the_declarations_under_its_list() {
7574 let mut opts = options();
7577 opts.std = Std::C17;
7578 let source = concat!(
7579 "int add(a, b)\n",
7580 "int a;\n",
7581 "int b;\n",
7582 "{ return a + b; }\n",
7583 "int promoted(c)\n",
7584 "char c;\n",
7585 "{ return c; }\n",
7586 "int narrow(char);\n",
7587 "int narrow(c)\n",
7588 "char c;\n",
7589 "{ return c; }\n",
7590 "int first(a)\n",
7591 "int a[4];\n",
7592 "{ return a[0]; }\n",
7593 );
7594 let result = run(&opts, source);
7595 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
7596 let text = result.text();
7597 assert!(text.contains("add : int(int, int) function external defined"), "{text}");
7598 assert!(text.contains("promoted : int(int) function external defined"), "{text}");
7599 assert!(text.contains("c : char object automatic defined"), "{text}");
7601 assert!(text.contains("narrow : int(char) function external defined"), "{text}");
7602 assert!(text.contains("first : int(int *) function external defined"), "{text}");
7604 }
7605
7606 #[test]
7613 fn the_two_halves_of_an_old_style_parameter_list_have_to_agree() {
7614 let mut opts = options();
7615 opts.std = Std::C17;
7616 for (source, message) in [
7617 ("int f(a, a)\nint a;\n{ return a; }\n", "1:10: error: multiple parameters named 'a'"),
7618 (
7619 "int f(a)\nint a;\nint b;\n{ return a; }\n",
7620 "3:5: error: declaration for parameter 'b' but no such parameter",
7621 ),
7622 ("int f(a)\nint a;\nint a;\n{ return a; }\n", "3:5: error: redefinition of parameter"),
7623 ("int f(a)\nint a = 1;\n{ return a; }\n", "2:5: error: parameter 'a' is initialized"),
7624 (
7625 "int f(a)\nstatic int a;\n{ return a; }\n",
7626 "2:12: error: storage class specified for parameter 'a'",
7627 ),
7628 (
7629 "int f(char);\nint f(a)\nshort a;\n{ return a; }\n",
7630 "2:7: error: argument 'a' doesn't match prototype",
7631 ),
7632 ] {
7633 let result = run(&opts, source);
7634 assert!(result.failed(), "expected this to fail:\n{source}");
7635 assert!(result.messages[0].contains(message), "{:?}", result.messages);
7636 }
7637
7638 let implicit = "int f(a, b)\nint a;\n{ return a + b; }\n";
7641 let mut older = options();
7642 older.std = Std::C89;
7643 assert!(!run(&older, implicit).failed(), "{:?}", run(&older, implicit).messages);
7644 let result = run(&opts, implicit);
7645 assert!(
7646 result.messages[0].contains("1:10: error: type of 'b' defaults to 'int'"),
7647 "{:?}",
7648 result.messages
7649 );
7650
7651 let mut newer = options();
7655 newer.std = Std::C23;
7656 let plain = "int f(a)\nint a;\n{ return a; }\n";
7657 let result = run(&newer, plain);
7658 assert!(!result.failed(), "{:?}", result.messages);
7659 assert_eq!(
7660 result.messages,
7661 ["/main.c:1:5: warning: old-style function definition [E0412]"]
7662 );
7663 assert!(run(&opts, plain).messages.is_empty(), "and nothing to say in the dialects before");
7664 }
7665
7666 #[test]
7673 fn the_obsolete_designators_are_taken_and_are_pedantic_warnings() {
7674 let array = "int a[8] = { [3] 7 };\n";
7675 let member = "struct s { int x; } v = { x: 7 };\n";
7676 for source in [array, member] {
7677 let result = run(&options(), source);
7678 assert!(!result.failed(), "{:?}", result.messages);
7679 assert!(result.messages.is_empty(), "nothing to say: {:?}", result.messages);
7680 }
7681
7682 let mut asked = options();
7683 asked.pedantic = true;
7684 assert_eq!(
7685 run(&asked, array).messages,
7686 ["/main.c:1:18: warning: obsolete designator, write `[i] =` instead [E0415]"]
7687 );
7688 assert_eq!(
7689 run(&asked, member).messages,
7690 ["/main.c:1:27: warning: obsolete designator, write `.field =` instead [E0413]"]
7691 );
7692 }
7693
7694 #[test]
7701 fn a_type_is_refused_when_it_passes_the_largest_object_and_not_before() {
7702 let text = ir(concat!(
7703 "struct huge_struct { short buf[(1L << 62) - 256]; int a, b, c, d; };\n",
7704 "struct brim { char buf[9223372036854775807L]; };\n",
7705 "struct bitty { char buf[9223372036854775800L]; int x : 1; };\n",
7706 "unsigned long h = sizeof(struct huge_struct);\n",
7707 "unsigned long b = sizeof(struct brim);\n",
7708 "unsigned long y = sizeof(struct bitty);\n",
7709 ));
7710 assert!(text.contains("global @h : i64 = 9223372036854775312,"), "{text}");
7711 assert!(text.contains("global @b : i64 = 9223372036854775807,"), "{text}");
7712 assert!(text.contains("global @y : i64 = 9223372036854775804,"), "{text}");
7713
7714 let mut opts = options();
7715 opts.emit = EmitKind::Ir;
7716 let over = "struct over { char buf[9223372036854775800L]; char x[8]; };\n";
7717 let message = "/main.c:1:1: error: type 'struct over' is too large [E0560]";
7718 assert_eq!(run(&opts, over).messages, [message]);
7719 let array = "struct wide { short buf[1L << 62]; };\n";
7720 let message = "/main.c:1:25: error: size of array 'buf' exceeds \
7721 maximum object size '9223372036854775807' [E0537]";
7722 assert_eq!(run(&opts, array).messages[0], message);
7723 }
7724
7725 fn compile_bytes(source: &[u8]) -> Compiled {
7730 let mut opts = options();
7731 opts.emit = EmitKind::Ir;
7732 let mut fs = MemoryFileSystem::new();
7733 fs.insert("/main.c", source.to_vec());
7734 compile(&opts, "/main.c", &fs)
7735 }
7736
7737 #[test]
7744 fn a_byte_that_is_not_a_character_is_kept_in_a_literal_and_refused_outside_one() {
7745 let mut source = b"char s[] = \"a".to_vec();
7746 source.push(0xff);
7747 source.extend_from_slice(b"b\";\nchar c = '");
7748 source.push(0xff);
7749 source.extend_from_slice(b"';\n");
7750 let result = compile_bytes(&source);
7751 assert_eq!(result.messages, Vec::<String>::new(), "a raw byte in a literal is that byte");
7752 assert!(result.text().contains(r#"bytes "a\ffb\00""#), "{}", result.text());
7753 assert!(result.text().contains("global @c : i8 = -1,"), "{}", result.text());
7755
7756 let mut stray = b"int a".to_vec();
7757 stray.push(0xff);
7758 stray.extend_from_slice(b" = 1;\n");
7759 let result = compile_bytes(&stray);
7760 assert!(
7761 result.messages.iter().any(|m| m.contains("source is not valid UTF-8 here")),
7762 "{:?}",
7763 result.messages
7764 );
7765 }
7766
7767 #[test]
7768 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
7769 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
7770 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
7771 let expected = "\
7772func @add(i32, i32) -> i32, linkage(external) {
7773block0(%0: i32, %1: i32):
7774 %2 = add.nsw %0, %1
7775 return %2
7776}
7777";
7778 assert!(text.contains(expected), "{text}");
7779 }
7780
7781 #[test]
7782 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
7783 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
7784 assert!(!text.contains("alloca"), "{text}");
7785 assert!(!text.contains("load"), "{text}");
7786 assert!(!text.contains("store"), "{text}");
7787 }
7788
7789 #[test]
7790 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
7791 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
7792 let expected = "\
7793block0:
7794 %0 = alloca, size 4, align 4
7795 %1 = iconst.i32 1
7796 store %1 -> %0, align 4, tbaa !1
7797 %2 = call @g(%0) : (ptr) -> i32
7798 return %2
7799";
7800 assert_eq!(text, expected);
7801 }
7802
7803 #[test]
7804 fn a_loop_carries_what_it_changes_as_block_parameters() {
7805 let text = body(
7808 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
7809 return total;\n}\n",
7810 );
7811 assert!(!text.contains("alloca"), "{text}");
7812 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
7813 assert!(text.contains("jump block1("), "{text}");
7814 }
7815
7816 #[test]
7817 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
7818 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
7819 assert!(text.contains("icmp slt %0, %1"), "{text}");
7820 assert!(!text.contains("zext"), "{text}");
7821 }
7822
7823 #[test]
7824 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
7825 let text = body("int f(int a, int b) { return a && b; }\n");
7826 let expected = "\
7827block0(%0: i32, %1: i32):
7828 %2 = iconst.i32 0
7829 %3 = icmp ne %0, %2
7830 %4 = iconst.i1 0
7831 br_if %3, block1, block2(%4)
7832
7833block1:
7834 %5 = iconst.i32 0
7835 %6 = icmp ne %1, %5
7836 jump block2(%6)
7837
7838block2(%7: i1):
7839 %8 = zext.i32 %7
7840 return %8
7841";
7842 assert_eq!(text, expected);
7843 }
7844
7845 #[test]
7846 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
7847 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
7848 assert!(!text.contains("block3"), "{text}");
7851 assert!(!text.contains("iconst.i32 3"), "{text}");
7852 }
7853
7854 #[test]
7855 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
7856 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
7857 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
7858 assert!(body("int f(void) { }\n").contains("unreachable"));
7859 }
7860
7861 #[test]
7862 fn a_structure_is_copied_rather_than_held_in_a_value() {
7863 let text = body(
7864 "struct point { int x, y; };\n\
7865 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
7866 );
7867 assert!(text.contains("memcpy"), "{text}");
7868 }
7869
7870 #[test]
7871 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
7872 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
7873 assert!(text.contains("memset"), "{text}");
7874 }
7875
7876 #[test]
7877 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
7878 let text = body(
7879 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
7880 default: r = 4; } return r; }\n",
7881 );
7882 let expected = "\
7883block0(%0: i32):
7884 %1 = iconst.i32 0
7885 switch %0, block1, [1 => block2, 2 => block3(%1)]
7886
7887block1:
7888 %2 = iconst.i32 4
7889 jump block4(%2)
7890
7891block2:
7892 %3 = iconst.i32 1
7893 jump block3(%3)
7894
7895block3(%4: i32):
7896 %5 = iconst.i32 2
7897 %6 = add.nsw %4, %5
7898 jump block4(%6)
7899
7900block4(%7: i32):
7901 return %7
7902";
7903 assert_eq!(text, expected);
7904 }
7905
7906 #[test]
7907 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
7908 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
7911 assert!(text.contains("%2 = sub %0, %1"), "{text}");
7912 assert!(text.contains("icmp ule"), "{text}");
7913 assert!(!text.contains("switch"), "{text}");
7914 }
7915
7916 #[test]
7917 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
7918 let text = body(
7919 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
7920 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
7921 );
7922 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
7925 assert!(text.contains("block5:\n jump block7("), "{text}");
7926 assert!(text.contains("block6:\n jump block8("), "{text}");
7927 }
7928
7929 #[test]
7930 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
7931 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
7932 }
7933
7934 #[test]
7935 fn a_label_a_loop_is_only_entered_through_builds_the_loop_around_it() {
7936 let text = body(
7941 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
7942 return n; }\n",
7943 );
7944 assert!(text.contains("switch %0, block1(%1), [1 => block2, 2 => block3(%1)]"), "{text}");
7947 assert!(text.contains("block3(%3: i32):\n %4 = iconst.i32 1"), "{text}");
7948 assert!(text.contains("block4:\n jump block3("), "{text}");
7949 }
7950
7951 #[test]
7952 fn a_goto_into_a_loop_body_enters_it_without_the_test() {
7953 let text = body("int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n");
7956 assert!(text.starts_with("block0(%0: i32, %1: i32):\n jump block1(%1)"), "{text}");
7957 assert!(text.contains("block1(%2: i32):\n %3 = iconst.i32 1"), "{text}");
7958 assert!(text.contains("br_if %6, block2, block3"), "{text}");
7959 }
7960
7961 #[test]
7962 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
7963 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
7964 assert!(!text.contains("alloca"), "{text}");
7968 assert!(text.contains("block2(%4: i32):\n return %4"), "{text}");
7969 assert_eq!(text.matches("jump block2(").count(), 2, "{text}");
7970 }
7971
7972 #[test]
7973 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
7974 let text =
7975 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
7976 assert!(!text.contains("alloca"), "{text}");
7977 assert!(text.contains("block1(%2: i32):"), "{text}");
7978 assert!(text.contains("jump block1(%5)"), "{text}");
7979 }
7980
7981 #[test]
7982 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
7983 assert_eq!(
7986 body("int f(int x) { return x; spare: return 0; }\n"),
7987 "block0(%0: i32):\n return %0\n"
7988 );
7989 }
7990
7991 #[test]
7992 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
7993 let text = body(
7994 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
7995 );
7996 assert_eq!(
7999 text,
8000 "\
8001block0(%0: ptr):
8002 %1 = load.i8 %0, align 1
8003 %2 = iconst.i8 3
8004 %3 = ashr %1, %2
8005 %4 = sext.i32 %3
8006 return %4
8007"
8008 );
8009 }
8010
8011 #[test]
8012 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
8013 let text =
8017 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
8018 assert_eq!(
8019 text,
8020 "\
8021block0(%0: ptr, %1: i32):
8022 %2 = iconst.i32 16777215
8023 %3 = and %1, %2
8024 %4 = trunc.i16 %3
8025 store %4 -> %0, align 2
8026 %5 = iconst.i32 16
8027 %6 = lshr %3, %5
8028 %7 = trunc.i8 %6
8029 %8 = iconst.i64 2
8030 %9 = ptr_add %0, %8
8031 store %7 -> %9, align 1
8032 return
8033"
8034 );
8035 }
8036
8037 #[test]
8038 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
8039 let text =
8040 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
8041 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
8044 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
8045 }
8046
8047 #[test]
8048 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
8049 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
8052 assert_eq!(text.matches("ashr").count(), 0, "{text}");
8053 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
8054 }
8055
8056 #[test]
8057 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
8058 let text = body(
8062 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
8063 );
8064 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
8065 }
8066
8067 #[test]
8068 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
8069 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
8072 assert!(
8073 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
8074 "{text}"
8075 );
8076 }
8077
8078 #[test]
8079 fn an_initialized_flexible_array_member_makes_the_object_larger_than_its_type() {
8080 let text = ir(concat!(
8085 "struct a { int i; int j[]; } x = { 1, { 2, 0, 2, 3 } };\n",
8086 "struct b { char c; char p[]; } y = { 'o', \"wx\" };\n",
8087 "struct c { char c; char p[]; } z = { '9', { 'e', 'b' } };\n",
8088 "char s[2] = \"hi\";\n",
8089 ));
8090 assert!(
8091 text.contains("global @x : bytes 20 = { i32 1, i32 2, i32 0, i32 2, i32 3 }"),
8092 "{text}"
8093 );
8094 assert!(text.contains("global @y : bytes 4 = { i8 111, bytes \"wx\\00\" }"), "{text}");
8095 assert!(text.contains("global @z : bytes 3 = { i8 57, i8 101, i8 98 }"), "{text}");
8096 assert!(text.contains("global @s : bytes 2 = { bytes \"hi\" }"), "{text}");
8099 }
8100
8101 #[test]
8102 fn a_definition_takes_a_parameter_it_left_unnamed() {
8103 let text = ir("int f(int a, int) { return a; }\n");
8107 assert!(text.contains("func @f(i32, i32) -> i32"), "{text}");
8108 assert!(text.contains("block0(%0: i32, %1: i32):"), "{text}");
8109
8110 let text = ir("int g(int, int n) { return n; }\n");
8113 assert!(text.contains("block0(%0: i32, %1: i32):\n return %1\n"), "{text}");
8114 }
8115
8116 #[test]
8117 fn an_assignment_of_a_structure_is_the_object_it_wrote() {
8118 let text = body(concat!(
8123 "struct s { int f; int g; };\n",
8124 "void h(struct s *a, struct s *c, struct s *d, struct s *e)\n",
8125 "{ *d = *e = a[0] = *c; }\n",
8126 ));
8127 assert_eq!(text.matches("memcpy").count(), 3, "{text}");
8128 assert!(text.contains("memcpy %8, %1, size 8, align 4\n"), "{text}");
8129 assert!(text.contains("memcpy %3, %8, size 8, align 4\n"), "{text}");
8130 assert!(text.contains("memcpy %2, %3, size 8, align 4\n"), "{text}");
8131 }
8132
8133 #[test]
8134 fn a_string_literal_stops_at_the_end_of_the_array_it_is_filling() {
8135 let mut opts = options();
8140 opts.emit = EmitKind::Ir;
8141 let result = run(
8142 &opts,
8143 concat!(
8144 "const char a[2][3] = { \"1234\", \"xyz\" };\n",
8145 "static const char b[3][5] = { \"12345\", \"678\", \"9\" };\n",
8146 "union u { struct { char x[4]; char y[4]; }; struct { char z[8]; }; };\n",
8147 "const union u c = { { \"1234\", \"567\" } };\n",
8148 ),
8149 );
8150 let text = result.text();
8151 assert_eq!(
8152 result.messages,
8153 ["/main.c:1:24: warning: initializer-string for array of 'const char' is too long \
8154 (5 chars into 3 available) [E0637]"]
8155 );
8156 assert!(text.contains("global @a : bytes 6 = { bytes \"123\", bytes \"xyz\" }"), "{text}");
8157 assert!(
8158 text.contains(
8159 "global @b : bytes 15 = { bytes \"12345\", bytes \"678\\00\", zero 1, \
8160 bytes \"9\\00\", zero 3 }"
8161 ),
8162 "{text}"
8163 );
8164 assert!(
8167 text.contains("global @c : bytes 8 = { bytes \"1234\", bytes \"567\\00\" }"),
8168 "{text}"
8169 );
8170 }
8171
8172 #[test]
8173 fn a_cast_of_a_record_to_its_own_type_is_the_object_that_was_cast() {
8174 let text = body(concat!(
8178 "struct s { int a, b; };\nstruct v { struct s s; int t; };\n",
8179 "void g(struct v *);\n",
8180 "void f(struct s *p) { struct v w = { (struct s)*p, 5 }; g(&w); }\n",
8181 ));
8182 assert_eq!(text.matches("memcpy").count(), 1, "{text}");
8183 }
8184
8185 #[test]
8186 fn a_compound_literal_read_in_a_static_initializer_lays_its_bytes_into_the_image() {
8187 let text = ir(concat!(
8192 "struct s { int x; };\n",
8193 "struct t { struct s s; int o; } a = { (struct s){ 2 }, 3 };\n",
8194 "int n = (int){ 7 };\n",
8195 "struct u { struct s p; struct s q; } b = { (struct s){ 1 }, (struct s){ } };\n",
8196 ));
8197 assert!(text.contains("global @a : bytes 8 = { i32 2, i32 3 }"), "{text}");
8198 assert!(text.contains("global @n : i32 = 7,"), "{text}");
8199 assert!(text.contains("global @b : bytes 8 = { i32 1, zero 4 }"), "{text}");
8202 }
8203
8204 #[test]
8205 fn the_address_of_a_compound_literal_asks_for_the_object_it_points_at() {
8206 let text = ir("struct s { int x; };\nstruct s *q = &(struct s){ 9 };\n");
8210 assert!(text.contains("global @.Lanon.0 : i32 = 9, align 4, linkage(internal)"), "{text}");
8211 assert!(text.contains("global @q : bytes 8 = { addr.8 @.Lanon.0 }"), "{text}");
8212 }
8213
8214 #[test]
8215 fn an_object_of_no_size_at_all_has_an_image_with_nothing_in_it() {
8216 let text = ir("unsigned char foo[1][0];\n");
8220 assert!(text.contains("global @foo : bytes 0 = {}, align 1"), "{text}");
8221 }
8222
8223 #[test]
8224 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
8225 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
8228 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
8229 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
8230 }
8231
8232 #[test]
8233 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
8234 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
8238 assert!(
8239 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
8240 "{text}"
8241 );
8242 }
8243
8244 #[test]
8245 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
8246 let text = body(
8251 "\
8252struct s { int a, b; };
8253struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
8254",
8255 );
8256 assert!(text.contains("block3(%7: ptr)"), "{text}");
8258 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
8259 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
8260 }
8261
8262 #[test]
8270 fn the_left_side_of_a_conditional_with_no_middle_is_evaluated_once() {
8271 let text = body("int f(int i) { return ++i ?: 10; }\n");
8272 assert!(text.contains("jump block3(%2)"), "the arm is the value that was tested: {text}");
8273 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
8274
8275 let text = body("long f(int i) { return ++i ?: 10L; }\n");
8278 assert!(text.contains("%5 = sext.i64 %2"), "the arm widens what was tested: {text}");
8279 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
8280
8281 let text = body("int g(void);\nint f(void) { return g() ?: 10; }\n");
8283 assert_eq!(text.matches("call @g").count(), 1, "called once: {text}");
8284
8285 let text = body("int f(int i) { return ++i ? ++i : 10; }\n");
8288 assert_eq!(text.matches("add.nsw").count(), 2, "incremented twice: {text}");
8289 }
8290
8291 #[test]
8292 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
8293 let text = ir("\
8297struct pair { int a, b; };
8298struct pair make(int a, int b);
8299struct pair twice(struct pair p) { return make(p.a, p.b); }
8300");
8301 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
8302 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
8303 }
8304
8305 #[test]
8306 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
8307 let text = ir("\
8311struct big { double v[8]; };
8312struct big grow(struct big b);
8313struct big twice(struct big b) { return grow(grow(b)); }
8314");
8315 assert!(
8316 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
8317 "{text}"
8318 );
8319 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
8320 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
8323 }
8324
8325 #[test]
8326 fn a_structure_passed_to_a_variadic_function_says_so_at_the_call() {
8327 let text = ir("\
8332struct big { double v[8]; };
8333struct pair { int a, b; };
8334int p(const char *, ...);
8335int f(struct big b, struct pair q) { return p(\"\", 1, b, q); }
8336");
8337 assert!(
8338 text.contains("call @p(%4, %5, %2 byval(64, align 8), %6) : (ptr, ...) -> i32"),
8339 "{text}"
8340 );
8341 }
8342
8343 #[test]
8344 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
8345 let body = body(
8348 "\
8349struct pair { int a, b; };
8350struct pair make(int a, int b);
8351int second(void) { return make(1, 2).b; }
8352",
8353 );
8354 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
8355 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
8356 }
8357
8358 #[test]
8359 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
8360 let source = "\
8364struct hfa { float x, y, z; };
8365int take(struct hfa h);
8366int give(struct hfa h) { return take(h); }
8367";
8368 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
8369 let mut opts = options();
8370 opts.emit = EmitKind::Ir;
8371 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
8372 let result = run(&opts, source);
8373 assert_eq!(result.messages, Vec::<String>::new());
8374 assert!(result.text().contains("func @take(f32, f32, f32) -> i32"), "{}", result.text());
8375 }
8376
8377 #[test]
8378 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
8379 let source = "\
8382int use(int *);
8383void f(int n) {
8384 {
8385 int a[n];
8386 use(a);
8387 }
8388 use(0);
8389}
8390";
8391 let body = body(source);
8392 assert!(body.contains("mul.nsw"), "{body}");
8393 assert!(body.contains("stacksave"), "{body}");
8394 assert!(body.contains("alloca %"), "{body}");
8395 assert!(body.contains("stackrestore"), "{body}");
8396 }
8397
8398 #[test]
8399 fn a_goto_out_of_the_scope_of_one_gives_its_stack_back_on_the_way() {
8400 let source = "\
8405int use(int *);
8406int f(int n) {
8407 {
8408 int a[n];
8409 if (use(a)) goto out;
8410 use(0);
8411 }
8412out:
8413 return 0;
8414}
8415";
8416 let body = body(source);
8417 assert_eq!(body.matches("stackrestore").count(), 2, "{body}");
8419 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
8420 assert!(after.starts_with(" %4\n jump block"), "{body}");
8421 }
8422
8423 #[test]
8424 fn a_goto_to_a_label_the_array_is_still_alive_at_leaves_the_stack_alone() {
8425 let source = "\
8429int use(int *);
8430int f(int n) {
8431 int a[n];
8432again:
8433 if (use(a)) goto again;
8434 return 0;
8435}
8436";
8437 let body = body(source);
8438 assert!(body.contains("stacksave"), "{body}");
8439 assert!(!body.contains("stackrestore"), "{body}");
8440 }
8441
8442 #[test]
8443 fn a_goto_back_to_a_label_in_front_of_one_gives_it_back_every_time_round() {
8444 let source = "\
8449int use(int *);
8450int f(int n) {
8451again:
8452 {
8453 int a[n];
8454 if (use(a)) goto again;
8455 }
8456 return 0;
8457}
8458";
8459 let body = body(source);
8460 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
8461 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
8462 assert!(after.starts_with(" %4\n jump block1\n"), "{body}");
8463 }
8464
8465 #[test]
8466 fn the_head_of_a_for_loop_is_a_scope_that_closes_where_the_loop_is_left() {
8467 let source = "\
8473int f(void);
8474void t(void) {
8475 int count = 10;
8476 for (; count--;) {
8477 int b[f()];
8478 int i;
8479 for (i = 0; i < f(); i++) {
8480 b[i] = count;
8481 }
8482 }
8483}
8484";
8485 let body = body(source);
8486 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
8490 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
8491 let next = after.split("\n\n").next().expect("the block the restore is in");
8494 assert!(next.contains("jump block1("), "{body}");
8495 }
8496
8497 #[test]
8498 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
8499 let source = "\
8502unsigned long f(int n) {
8503 int a[n];
8504 n = 0;
8505 return sizeof a;
8506}
8507";
8508 let body = body(source);
8509 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
8511 }
8512
8513 #[test]
8514 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
8515 let source = "\
8518int use(int);
8519int f(int x) {
8520 return ({
8521 int t = use(x);
8522 t * t;
8523 });
8524}
8525";
8526 let expected = "\
8527block0(%0: i32):
8528 %1 = call @use(%0) : (i32) -> i32
8529 %2 = mul.nsw %1, %1
8530 return %2
8531";
8532 assert_eq!(body(source), expected);
8533 }
8534
8535 #[test]
8536 fn a_comma_whose_value_is_an_object_names_the_object_the_right_side_named() {
8537 let source = "\
8541struct pair { int a, b; };
8542void bail(void);
8543int f(struct pair p) {
8544 return (bail(), p).b;
8545}
8546";
8547 let expected = "\
8548block0(%0: i64):
8549 %1 = alloca, size 8, align 4
8550 store %0 -> %1, align 4
8551 call @bail() : ()
8552 %2 = iconst.i64 4
8553 %3 = ptr_add %1, %2
8554 %4 = load.i32 %3, align 4, tbaa !1
8555 return %4
8556";
8557 assert_eq!(body(source), expected);
8558 }
8559
8560 #[test]
8561 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
8562 let source = "int f(int x) { return ({ return x; 0; }); }\n";
8566 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
8567 }
8568
8569 #[test]
8570 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
8571 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
8575 let expected = "\
8576block0(%0: ptr):
8577 %1 = va_arg.f64 %0
8578 %2 = va_arg.f64 %0
8579 %3 = fadd %1, %2
8580 return %3
8581";
8582 assert_eq!(body(source), expected);
8583 }
8584
8585 #[test]
8586 fn one_that_reads_a_structure_answers_where_the_object_is() {
8587 let source = "\
8601struct s { int a; long b; };
8602long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.b; }
8603";
8604 let expected = "\
8605block0(%0: ptr):
8606 %1 = alloca, size 16, align 16
8607 %2 = va_object %0, size 16, align 8, in(int 8 at 0, int 8 at 8)
8608 memcpy %1, %2, size 16, align 8
8609 %3 = iconst.i64 8
8610 %4 = ptr_add %1, %3
8611 %5 = load.i64 %4, align 8, tbaa !1
8612 return %5
8613";
8614 assert_eq!(body(source), expected);
8615 }
8616
8617 #[test]
8621 fn the_classification_says_which_registers_the_object_arrived_in() {
8622 let source = "\
8623struct s { double a; double b; };
8624double f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a; }
8625";
8626 assert!(
8627 body(source)
8628 .contains("va_object %0, size 16, align 8, in(float f64 at 0, float f64 at 8)"),
8629 "{}",
8630 body(source)
8631 );
8632
8633 let big = "\
8634struct s { long a[4]; };
8635long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a[0]; }
8636";
8637 assert!(body(big).contains("va_object %0, size 32, align 8\n"), "{}", body(big));
8638 }
8639
8640 #[test]
8641 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
8642 let source = "\
8646int f(int c) {
8647 void *p = c ? &&one : &&two;
8648 goto *p;
8649one:
8650 return 1;
8651two:
8652 return 2;
8653}
8654";
8655 let expected = "\
8656block0(%0: i32):
8657 %1 = iconst.i32 0
8658 %2 = icmp ne %0, %1
8659 br_if %2, block1, block2
8660
8661block1:
8662 %3 = block_addr block3
8663 jump block4(%3)
8664
8665block2:
8666 %4 = block_addr block5
8667 jump block4(%4)
8668
8669block3:
8670 %5 = iconst.i32 1
8671 return %5
8672
8673block4(%6: ptr):
8674 indirect_br %6, block3, block5
8675
8676block5:
8677 %7 = iconst.i32 2
8678 return %7
8679";
8680 assert_eq!(body(source), expected);
8681 }
8682
8683 fn dispatch(labels: usize) -> String {
8686 let mask = labels - 1;
8687 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
8688 for index in 0..labels {
8689 source.push_str(&format!(" &&a{index},"));
8690 }
8691 source.push_str(" };\n\tint w = n, x = n + 1, y = n + 2, z = n + 3;\n");
8692 source.push_str(&format!("\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
8693 for index in 0..labels {
8694 let step = match index % 4 {
8695 0 => "w += x;",
8696 1 => "x += y;",
8697 2 => "y += z;",
8698 _ => "z += w;",
8699 };
8700 source.push_str(&format!("a{index}:\n\t{step}\n"));
8701 source.push_str("\tif (--n <= 0) return w + x + y + z;\n");
8702 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
8703 }
8704 source.push_str("}\n");
8705 source
8706 }
8707
8708 fn in_front_of_the_jump(text: &str) -> usize {
8710 let (before, _) = text.split_once("\tjmp\t*%").expect("a jump through a register");
8711 before.lines().rev().take_while(|line| line.starts_with("\tmov")).count()
8712 }
8713
8714 #[test]
8724 fn a_jump_through_a_register_writes_what_it_carries_and_not_the_whole_table() {
8725 let small = in_front_of_the_jump(&asm(&dispatch(4)));
8726 let large = in_front_of_the_jump(&asm(&dispatch(32)));
8727 assert_eq!(small, large, "eight times the labels and the same values in hand");
8728 assert!(large <= 8, "the values the loop keeps, and not a set of them per label: {large}");
8729 }
8730
8731 fn crowded(labels: usize) -> String {
8734 const VALUES: usize = 24;
8735 let mask = labels - 1;
8736 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
8737 for index in 0..labels {
8738 source.push_str(&format!(" &&a{index},"));
8739 }
8740 source.push_str(" };\n\t");
8741 for value in 0..VALUES {
8742 source.push_str(&format!("int v{value} = n + {value}; "));
8743 }
8744 let sum: Vec<String> = (0..VALUES).map(|value| format!("v{value}")).collect();
8745 source.push_str(&format!("\n\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
8746 for index in 0..labels {
8747 let (to, from) = (index % VALUES, (index + 1) % VALUES);
8748 source.push_str(&format!("a{index}:\n\tv{to} += v{from};\n"));
8749 source.push_str(&format!("\tif (--n <= 0) return {};\n", sum.join(" + ")));
8750 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
8751 }
8752 source.push_str("}\n");
8753 source
8754 }
8755
8756 fn the_frame(text: &str) -> u64 {
8758 text.lines()
8759 .find_map(|line| {
8760 let (size, _) = line.strip_prefix("\tsubq\t$")?.split_once(", %rsp")?;
8761 size.parse().ok()
8762 })
8763 .expect("a function that opens a frame")
8764 }
8765
8766 #[test]
8775 fn a_frame_holds_what_is_wanted_at_once_and_not_a_slot_for_every_label() {
8776 let small = the_frame(&asm(&crowded(16)));
8777 let large = the_frame(&asm(&crowded(64)));
8778 assert_eq!(small, large, "four times the labels and the same values: {small}, {large}");
8779 }
8780
8781 #[test]
8790 fn a_template_that_names_its_own_registers_gets_the_ones_it_named() {
8791 let source = "void save(void *nlr) {
8792 __asm volatile (
8793 \"movq %%rsp, 32(%%rdi) \\n\"
8794 \"movq %%rbx, 40(%%rdi) \\n\"
8795 \"movq %%r12, 48(%%rdi) \\n\"
8796 : : \"D\" (nlr) : \"memory\");
8797}
8798";
8799 let text = asm(source);
8800 assert!(text.contains("\tmovq\t%rsp, 32(%rdi)\n"), "{text}");
8801 assert!(text.contains("\tmovq\t%rbx, 40(%rdi)\n"), "{text}");
8802 assert!(text.contains("\tmovq\t%r12, 48(%rdi)\n"), "{text}");
8803 }
8804
8805 #[test]
8806 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
8807 let source = "void **next(void);
8810void f(void) { goto *next(); }
8811";
8812 let expected = "\
8813block0:
8814 %0 = call @next() : () -> ptr
8815 unreachable
8816";
8817 assert_eq!(body(source), expected);
8818 }
8819
8820 #[test]
8821 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
8822 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
8825 let expected = "\
8826block0:
8827 inline_asm.volatile \"mfence\", \"\", \"memory\"()
8828 return
8829";
8830 assert_eq!(body(source), expected);
8831 }
8832
8833 #[test]
8834 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
8835 let source = "\
8838int f(int x, int y) {
8839 int r;
8840 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
8841 return r + y;
8842}
8843";
8844 let expected = "\
8845block0(%0: i32, %1: i32):
8846 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
8847 %4 = add.nsw %2, %3
8848 return %4
8849";
8850 assert_eq!(body(source), expected);
8851 }
8852
8853 #[test]
8854 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
8855 let source = "\
8860struct pair { int a, b; };
8861int f(int x) {
8862 int slot = x;
8863 struct pair p = { x, x };
8864 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
8865 return slot + p.a;
8866}
8867";
8868 let text = body(source);
8869 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
8870 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
8871 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
8872 }
8873
8874 #[test]
8875 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
8876 let source = "\
8881int f(int x) {
8882 int r = 7;
8883 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
8884 return r;
8885away:
8886 return r;
8887}
8888";
8889 let expected = "\
8890block0(%0: i32):
8891 %1 = iconst.i32 7
8892 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
8893
8894block1:
8895 return %2
8896
8897block2:
8898 return %1
8899";
8900 assert_eq!(body(source), expected);
8901 }
8902
8903 #[test]
8904 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
8905 let mut opts = options();
8909 opts.emit = EmitKind::Ir;
8910 for (source, expected) in [
8911 (
8912 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
8913 "output operand constraint lacks '='",
8914 ),
8915 (
8916 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
8917 "lvalue required in 'asm' statement",
8918 ),
8919 (
8920 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
8921 "read-only variable 'g' used as 'asm' output",
8922 ),
8923 (
8924 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
8925 "input operand constraint contains '='",
8926 ),
8927 (
8928 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
8929 "memory input 0 is not directly addressable",
8930 ),
8931 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
8932 (
8933 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
8934 "duplicate asm operand name 'a'",
8935 ),
8936 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
8937 ] {
8938 let result = run(&opts, source);
8939 assert!(result.failed(), "expected this to be reported:\n{source}");
8940 assert!(
8941 result.messages.iter().any(|m| m.contains(expected)),
8942 "{expected}\n{:?}",
8943 result.messages
8944 );
8945 }
8946 }
8947
8948 #[test]
8953 fn an_asm_at_file_scope_that_is_directives_becomes_the_objects_it_defines() {
8954 let text = ir(concat!(
8955 "__asm__(\n",
8956 " \".section .rodata\\n\"\n",
8957 " \".globl first\\n\"\n",
8958 " \".balign 8\\n\"\n",
8959 " \"first:\\n\"\n",
8960 " \".long 1\\n\"\n",
8961 " \".long 2\\n\"\n",
8962 " \".globl last\\n\"\n",
8963 " \"last:\\n\"\n",
8964 " \".quad last - first\\n\");\n",
8965 "extern const int first[];\n",
8966 "extern const long last;\n",
8967 ));
8968 assert!(text.contains("global @first : bytes 8 = { i32 1, i32 2 }, align 8"), "{text}");
8969 assert!(text.contains("global @last : i64 = 8"), "{text}");
8970 }
8971
8972 #[test]
8976 fn a_name_an_asm_at_file_scope_defined_is_not_undone_by_a_declaration_of_it() {
8977 let text = ir(concat!(
8978 "__asm__(\".data\\n.globl counter\\ncounter:\\n.long 7\\n\");\n",
8979 "extern int counter;\n",
8980 "int read(void) { return counter; }\n",
8981 ));
8982 assert!(text.contains("global @counter : i32 = 7"), "{text}");
8983 }
8984
8985 #[test]
8990 fn bytes_under_no_label_at_file_scope_are_a_global_in_front_of_the_label() {
8991 let text = ir(concat!(
8992 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n662:\\n",
8993 ".pushsection .data.ignore\\n.byte 7\\n.popsection\\n.byte 662b - 661b\\n\");\n",
8994 "extern unsigned char stuff[];\n",
8995 "int read(void) { return stuff[0]; }\n",
8996 ));
8997 let under = text.find("global @.Lasm.0 : i8 = 41").expect(&text);
8998 let named = text.find("global @stuff : i8 = 42").expect(&text);
8999 assert!(under < named, "the bytes under no label come first: {text}");
9000 assert!(text.contains("global @.Lasm.1 : i8 = 7, align 1, linkage(internal), section"));
9001 let after = text.find("global @.Lasm.2 : i8 = 1").expect(&text);
9006 assert!(named < after, "{text}");
9007 }
9008
9009 #[test]
9014 fn a_distance_from_here_at_file_scope_is_a_hole_naming_the_global_it_measures_to() {
9015 let text = ir(concat!(
9016 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n",
9017 ".pushsection .data.ignore\\n.long 661b - .\\n.popsection\\n\");\n",
9018 "extern unsigned char stuff[];\n",
9019 "int read(void) { return stuff[0]; }\n",
9020 ));
9021 assert!(text.contains("global @.Lasm.1 : bytes 4 = { away.4 @stuff }"), "{text}");
9025 }
9026
9027 #[test]
9032 fn a_set_at_file_scope_is_a_second_name_for_what_it_names() {
9033 let text = ir(concat!(
9034 "void base(void) {}\n",
9035 "__asm__(\".weak one\\n.set one, base\");\n",
9036 "__asm__(\".globl two\\n.set two, base\");\n",
9037 "__asm__(\".set three, base\");\n",
9038 "void three(void) {}\n",
9039 ));
9040 assert!(text.contains("alias @one = @base, linkage(weak)"), "{text}");
9041 assert!(text.contains("alias @two = @base"), "{text}");
9042 assert!(!text.contains("alias @three"), "a definition of the name wins: {text}");
9043 assert!(text.contains("func @three"), "{text}");
9044 }
9045
9046 #[test]
9051 fn a_set_of_a_name_this_file_does_not_define_says_so() {
9052 let messages = errors("__asm__(\".set here, elsewhere\");\n");
9053 assert!(
9054 messages
9055 .iter()
9056 .any(|m| m.contains("'here' is aliased to undefined symbol 'elsewhere'")
9057 && m.contains("E0697")),
9058 "{messages:?}"
9059 );
9060 }
9061
9062 #[test]
9065 fn an_incbin_at_file_scope_is_the_bytes_of_the_file_it_names() {
9066 let mut opts = options();
9067 opts.emit = EmitKind::Ir;
9068 let mut fs = MemoryFileSystem::new();
9069 fs.insert(
9070 "/main.c",
9071 b"__asm__(\".data\\n.globl blob\\nblob:\\n.incbin \\\"seed\\\"\\n\");\n".to_vec(),
9072 );
9073 fs.insert("seed", b"hi".to_vec());
9074 let result = compile(&opts, "/main.c", &fs);
9075 assert_eq!(result.messages, Vec::<String>::new());
9076 let text = result.text();
9077 assert!(text.contains("global @blob : bytes 2 = { bytes \"hi\" }"), "{text}");
9078 }
9079
9080 #[test]
9083 fn an_incbin_naming_a_file_that_is_not_there_says_which_file() {
9084 let messages = errors("__asm__(\".data\\nb:\\n.incbin \\\"nowhere\\\"\\n\");\n");
9085 assert!(
9086 messages
9087 .iter()
9088 .any(|m| m.contains("cannot open 'nowhere' for reading") && m.contains("E0702")),
9089 "{messages:?}"
9090 );
9091 }
9092
9093 #[test]
9096 fn an_instruction_in_an_asm_at_file_scope_is_refused_rather_than_ignored() {
9097 for source in [
9098 "__asm__(\".text\\n.globl f\\nf:\\n ret\\n\");\n",
9099 "__asm__(\".data\\n.set alias, 4\\n\");\n",
9100 ] {
9101 let messages = errors(source);
9102 assert!(
9103 messages
9104 .iter()
9105 .any(|m| m.contains("not supported yet")
9106 && m.contains("in an `asm` at file scope")),
9107 "{source}\n{messages:?}"
9108 );
9109 }
9110 }
9111
9112 #[test]
9122 fn a_naked_function_is_its_own_prologue_and_its_own_ending() {
9123 let text = asm(concat!(
9124 "unsigned nlr_push_tail(void *nlr);\n",
9125 "__attribute__((naked)) unsigned nlr_push(void *nlr) {\n",
9126 " __asm volatile(\n",
9127 " \"movq (%rsp), %rax\\n\"\n",
9128 " \"movq %rax, 16(%rdi)\\n\"\n",
9129 " \"movq %rbx, 40(%rdi)\\n\"\n",
9130 " \"jmp nlr_push_tail\\n\");\n",
9131 "}\n",
9132 ));
9133 assert!(text.contains("\tmovq\t(%rsp), %rax\n"), "{text}");
9134 assert!(text.contains("\tjmp\tnlr_push_tail\n"), "{text}");
9135 assert!(text.contains("\tud2\n"), "{text}");
9136 assert!(!text.contains("\tpushq\t"), "nothing is saved in front of it: {text}");
9137 assert!(!text.contains("\tret\n"), "the jump is where it ends: {text}");
9138 }
9139
9140 #[test]
9143 fn what_a_function_without_a_prologue_cannot_be_given_is_refused() {
9144 let mut opts = options();
9145 opts.emit = EmitKind::Asm;
9146 for (source, why) in [
9147 (
9148 "__attribute__((naked)) void f(void) { volatile long a[8]; a[0] = 1; }\n",
9149 "bytes of frame",
9150 ),
9151 (
9152 "__attribute__((naked)) void f(int n) { char a[n]; __asm(\"nop\" ::\"r\"(a)); }\n",
9153 "has no prologue to point a frame pointer at it with",
9154 ),
9155 ("void elsewhere(void); void f(void) { __asm(\"jmp elsewhere\"); }\n", "jumps out of"),
9156 ] {
9157 let result = run(&opts, source);
9158 assert!(result.failed(), "expected this to be refused:\n{source}");
9159 assert!(
9160 result.messages.iter().any(|message| message.contains(why)),
9161 "{:?}",
9162 result.messages
9163 );
9164 }
9165 }
9166
9167 #[test]
9168 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
9169 let mut opts = options();
9170 opts.emit = EmitKind::Ir;
9171 for source in [
9172 "int f(int n) { void *p = &&out; if (n) goto *p; { int a[n]; out: return 1; } }\n",
9173 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
9174 ] {
9175 let result = run(&opts, source);
9176 assert!(result.failed(), "expected this to be reported:\n{source}");
9177 assert!(
9178 result.messages.iter().any(|m| m.contains("not supported yet")),
9179 "{:?}",
9180 result.messages
9181 );
9182 }
9183 }
9184
9185 fn round_trip(source: &str) -> (String, String) {
9187 let printed = ir(source);
9188 let mut opts = options();
9189 opts.emit = EmitKind::Ir;
9190 let mut fs = MemoryFileSystem::new();
9191 fs.insert("/main.ir", printed.clone().into_bytes());
9192 let result = compile_ir(&opts, "/main.ir", &fs);
9193 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
9194 (printed, result.text().to_owned())
9195 }
9196
9197 #[test]
9198 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
9199 let (printed, again) = round_trip(
9203 "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",
9204 );
9205 assert_eq!(printed, again);
9206 }
9207
9208 #[test]
9209 fn ir_that_is_not_ir_says_which_line_stopped_it() {
9210 let mut opts = options();
9211 opts.emit = EmitKind::Ir;
9212 let mut fs = MemoryFileSystem::new();
9213 let text = "\
9214; ModuleID = 'a.c'
9215; format 0
9216target triple = \"x86_64-unknown-linux-gnu\"
9217target datalayout = \"e-p:64:64-i64:64-S128\"
9218
9219func @f(), linkage(external) {
9220block0:
9221 frobnicate
9222}
9223";
9224 fs.insert("/main.ir", text.as_bytes().to_vec());
9225 let result = compile_ir(&opts, "/main.ir", &fs);
9226 assert!(result.failed());
9227 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
9228 }
9229
9230 #[test]
9231 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
9232 let mut opts = options();
9235 opts.emit = EmitKind::Ir;
9236 let mut fs = MemoryFileSystem::new();
9237 let text = "\
9238; ModuleID = 'a.c'
9239; format 0
9240target triple = \"x86_64-unknown-linux-gnu\"
9241target datalayout = \"e-p:64:64-i64:64-S128\"
9242
9243func @f(), linkage(external) {
9244block0:
9245 %0 = iconst.i32 1
9246 return %0
9247}
9248";
9249 fs.insert("/main.ir", text.as_bytes().to_vec());
9250 let result = compile_ir(&opts, "/main.ir", &fs);
9251 assert!(result.failed());
9252 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
9253 }
9254
9255 #[test]
9256 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
9257 let mut fs = MemoryFileSystem::new();
9259 fs.insert("/main.ir", Vec::new());
9260 let result = compile_ir(&options(), "/main.ir", &fs);
9261 assert!(result.failed());
9262 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
9263 }
9264
9265 #[test]
9266 fn the_printed_ir_reads_back_as_the_same_module() {
9267 let text = ir("\
9270struct point { int x, y; };
9271static const char greeting[] = \"hi\";
9272int table[4] = { 1, 2, 3 };
9273int puts(const char *);
9274double half(double x) { return x / 2.0; }
9275int f(int n) {
9276 int total = 0;
9277 for (int i = 0; i < n; i++) {
9278 if (i == 3) continue;
9279 total += table[i];
9280 }
9281 switch (n) {
9282 case 0: total = 1;
9283 case 1: total++; break;
9284 default: total = -total;
9285 }
9286 struct point p = { total, 1 };
9287 int *q = &p.y;
9288 puts(greeting);
9289 return p.x + *q;
9290}
9291int dispatch(int c) {
9292 void *p = c ? &&one : &&two;
9293 goto *p;
9294one:
9295 return 1;
9296two:
9297 return 2;
9298}
9299int assembly(int x, int *p) {
9300 int r;
9301 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
9302 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
9303 return r;
9304away:
9305 return 0;
9306}
9307");
9308 let mut names = Interner::new();
9309 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
9310 assert_eq!(rucc_ir::print(&module, &names), text);
9311 }
9312
9313 #[test]
9314 fn what_save_temps_keeps_is_the_text_that_was_compiled_and_the_assembly_that_was_assembled() {
9315 let mut opts = options();
9319 opts.emit = EmitKind::Object;
9320 opts.save_temps = rucc_session::SaveTemps::Object;
9321 let result = run(&opts, "#define N 2\nint a[N];\n");
9322 assert_eq!(result.messages, Vec::<String>::new());
9323 let text = result.temps.preprocessed.expect("the preprocessed text");
9324 assert!(text.contains("int a[2];"), "{text}");
9325 assert!(text.starts_with("# 1 \"/main.c\""), "{text}");
9326 let asm = result.temps.assembly.expect("the assembly");
9327 assert!(asm.contains("a:"), "{asm}");
9328 assert!(matches!(result.artifact, Artifact::Object { .. }), "{:?}", result.artifact);
9329 }
9330
9331 #[test]
9332 fn nothing_is_kept_unless_the_flag_asked_for_it() {
9333 let mut opts = options();
9336 opts.emit = EmitKind::Object;
9337 assert_eq!(run(&opts, "int a;\n").temps, Temps::default());
9338 }
9339
9340 #[test]
9341 fn a_compilation_that_stops_before_the_back_end_keeps_the_text_and_no_assembly() {
9342 let mut opts = options();
9345 opts.emit = EmitKind::Ir;
9346 opts.save_temps = rucc_session::SaveTemps::Cwd;
9347 let result = run(&opts, "int a;\n");
9348 assert!(result.temps.preprocessed.is_some());
9349 assert_eq!(result.temps.assembly, None);
9350 }
9351
9352 fn span(from: u64, len: u64, held: rucc_debug::Held) -> rucc_debug::Span {
9354 rucc_debug::Span { from, len, held }
9355 }
9356
9357 #[test]
9358 fn two_stretches_that_meet_and_agree_come_out_as_one() {
9359 let one = span(0, 4, rucc_debug::Held::Reg(3));
9360 let two = span(4, 4, rucc_debug::Held::Reg(3));
9361 assert_eq!(settle(vec![two, one]), vec![span(0, 8, rucc_debug::Held::Reg(3))]);
9362 }
9363
9364 #[test]
9365 fn a_stretch_another_starts_inside_and_disagrees_with_ends_where_the_other_starts() {
9366 let one = span(0, 8, rucc_debug::Held::Reg(3));
9367 let two = span(4, 8, rucc_debug::Held::Reg(4));
9368 let settled = settle(vec![one, two]);
9371 assert_eq!(
9372 settled,
9373 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 8, rucc_debug::Held::Reg(4))]
9374 );
9375 }
9376
9377 #[test]
9378 fn a_stretch_cut_by_one_that_ends_first_does_not_come_back_after_it() {
9379 let one = span(0, 16, rucc_debug::Held::Reg(3));
9382 let two = span(4, 4, rucc_debug::Held::Reg(4));
9383 assert_eq!(
9384 settle(vec![one, two]),
9385 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 4, rucc_debug::Held::Reg(4))]
9386 );
9387 }
9388
9389 #[test]
9390 fn a_stretch_inside_another_that_agrees_with_it_cuts_nothing() {
9391 let one = span(0, 16, rucc_debug::Held::Reg(3));
9392 let two = span(4, 4, rucc_debug::Held::Reg(3));
9393 assert_eq!(settle(vec![one, two]), vec![span(0, 16, rucc_debug::Held::Reg(3))]);
9394 }
9395
9396 #[test]
9397 fn a_stretch_two_others_disagree_over_the_whole_of_says_nothing_at_all() {
9398 let one = span(0, 8, rucc_debug::Held::Reg(3));
9399 let two = span(0, 8, rucc_debug::Held::Frame(-16));
9400 assert_eq!(settle(vec![one, two]), Vec::new());
9401 }
9402
9403 #[test]
9404 fn stretches_with_a_gap_between_them_keep_the_gap() {
9405 let one = span(0, 4, rucc_debug::Held::Reg(3));
9406 let two = span(16, 4, rucc_debug::Held::Reg(3));
9407 assert_eq!(settle(vec![one, two]), vec![one, two]);
9408 }
9409
9410 fn extent(len: usize) -> rucc_object::Extent {
9412 rucc_object::Extent {
9413 name: "f".to_owned(),
9414 start: 0,
9415 len,
9416 align: 1,
9417 binding: rucc_object::Binding::Global,
9418 visibility: rucc_object::Visibility::Default,
9419 patch: None,
9420 }
9421 }
9422
9423 fn row(at: usize, lo: u32, hi: u32) -> rucc_asm::Row {
9425 let span = Span::new(lo, hi);
9426 rucc_asm::Row { at, span, inst: None }
9427 }
9428
9429 #[test]
9430 fn a_row_ends_where_the_next_address_begins() {
9431 let rows = [row(0, 0, 1), row(4, 1, 2), row(10, 2, 3)];
9432 assert_eq!(ends(&extent(16), &rows), vec![4, 10, 16]);
9433 }
9434
9435 #[test]
9436 fn rows_sharing_an_address_all_end_where_the_next_address_begins() {
9437 let rows = [row(0, 0, 1), row(4, 1, 2), row(4, 2, 3), row(4, 3, 4)];
9440 assert_eq!(ends(&extent(12), &rows), vec![4, 12, 12, 12]);
9441 }
9442
9443 #[test]
9444 fn the_rows_of_a_scope_that_are_next_to_each_other_come_out_as_one_stretch() {
9445 let rows = [row(0, 0, 4), row(4, 10, 14), row(8, 14, 18), row(12, 40, 44)];
9446 let ends = ends(&extent(16), &rows);
9447 let scope = Span::new(8, 20);
9448 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 8 }]);
9449 }
9450
9451 #[test]
9452 fn a_scope_the_back_end_split_in_two_comes_out_as_two_stretches() {
9453 let rows = [row(0, 10, 14), row(4, 40, 44), row(8, 14, 18)];
9454 let ends = ends(&extent(12), &rows);
9455 let scope = Span::new(8, 20);
9456 let over = spread(scope, &ends, &rows);
9457 assert_eq!(
9458 over,
9459 vec![rucc_debug::Reach { from: 0, len: 4 }, rucc_debug::Reach { from: 8, len: 4 }]
9460 );
9461 }
9462
9463 #[test]
9464 fn a_row_with_no_source_of_its_own_belongs_to_no_scope() {
9465 let rows = [rucc_asm::Row { at: 0, span: Span::DUMMY, inst: None }, row(4, 10, 14)];
9467 let ends = ends(&extent(8), &rows);
9468 let scope = Span::new(0, 20);
9469 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 4 }]);
9470 }
9471
9472 fn scope(parent: Option<usize>, lo: u32, hi: u32) -> crate::shapes::Scope {
9474 let span = Span::new(lo, hi);
9475 crate::shapes::Scope { parent, span }
9476 }
9477
9478 #[test]
9479 fn a_function_gets_the_scopes_its_own_locals_are_in_and_nothing_else() {
9480 let scopes = [scope(None, 0, 10), scope(None, 20, 30), scope(Some(1), 22, 26)];
9482 let rows = [row(0, 22, 24), row(4, 26, 28)];
9483 let (out, at) = nests(&[Some(2)], &scopes, &extent(8), &rows);
9484 assert_eq!(at.get(&1), Some(&0));
9487 assert_eq!(at.get(&2), Some(&1));
9488 assert_eq!(at.get(&0), None);
9489 assert_eq!(out.len(), 2);
9490 assert_eq!(out[0].parent, None);
9491 assert_eq!(out[1].parent, Some(0));
9492 assert_eq!(out[0].over, vec![rucc_debug::Reach { from: 0, len: 8 }]);
9493 assert_eq!(out[1].over, vec![rucc_debug::Reach { from: 0, len: 4 }]);
9494 }
9495
9496 #[test]
9497 fn a_local_written_straight_into_the_body_pulls_no_scope_in() {
9498 let scopes = [scope(None, 20, 30)];
9499 let rows = [row(0, 22, 24)];
9500 let (out, at) = nests(&[None], &scopes, &extent(4), &rows);
9501 assert_eq!(out, Vec::new());
9502 assert!(at.is_empty());
9503 }
9504
9505 #[test]
9506 fn a_scope_whose_code_all_went_away_is_still_one_of_the_functions_scopes() {
9507 let scopes = [scope(None, 20, 30)];
9511 let rows = [row(0, 40, 44)];
9512 let (out, at) = nests(&[Some(0)], &scopes, &extent(4), &rows);
9513 assert_eq!(at.get(&0), Some(&0));
9514 assert_eq!(out.len(), 1);
9515 assert_eq!(out[0].over, Vec::new());
9516 }
9517}