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]
7114 fn the_arguments_a_function_was_called_with_are_saved_on_the_way_in() {
7115 let text =
7116 mir("void *f(int a, double b) { (void)a; (void)b; return __builtin_apply_args(); }\n");
7117 assert!(text.matches("x64.mov_mr_64").count() >= 7, "{text}");
7119 assert!(text.matches("x64.movaps_mr").count() >= 8, "{text}");
7120 for reg in ["$rdi", "$rsi", "$rdx", "$rcx", "$r8", "$r9", "$xmm0", "$xmm7"] {
7121 assert!(text.contains(reg), "{reg} is not saved in\n{text}");
7122 }
7123
7124 let text = mir("int f(int a) { return a; }\n");
7126 assert!(!text.contains("movaps_mr"), "{text}");
7127 }
7128
7129 #[test]
7133 fn a_call_built_from_saved_arguments_loads_every_argument_register() {
7134 let text = mir(concat!(
7135 "void *g(void *args, void (*h)()) {\n",
7136 " return __builtin_apply(h, args, 64);\n",
7137 "}\n",
7138 ));
7139 assert!(text.matches("x64.mov_rm_64").count() >= 7, "{text}");
7140 assert!(text.matches("x64.movaps_rm").count() >= 8, "{text}");
7141 assert!(text.contains("call"), "{text}");
7142 assert!(text.matches("x64.movaps_mr").count() >= 2, "{text}");
7144
7145 let mut opts = options();
7147 opts.emit = EmitKind::Ir;
7148 let result = run(
7149 &opts,
7150 "void *g(void *a, void (*h)(), int n) { return __builtin_apply(h, a, n); }\n",
7151 );
7152 assert!(result.failed(), "{:?}", result.messages);
7153 assert!(
7154 result
7155 .messages
7156 .iter()
7157 .any(|m| m.contains("the size given to '__builtin_apply' is a constant")),
7158 "{:?}",
7159 result.messages
7160 );
7161 }
7162
7163 #[test]
7165 fn a_shuffle_refuses_what_gcc_refuses() {
7166 let mut opts = options();
7167 opts.emit = EmitKind::Ir;
7168 let source = concat!(
7169 "typedef int v4 __attribute__((vector_size(16)));\n",
7170 "typedef float f4 __attribute__((vector_size(16)));\n",
7171 "typedef short s8 __attribute__((vector_size(16)));\n",
7172 "typedef long long l4 __attribute__((vector_size(32)));\n",
7173 "void a(v4 x, f4 m) { __builtin_shuffle(x, m); }\n",
7174 "void b(int x, v4 m) { __builtin_shuffle(x, m); }\n",
7175 "void c(v4 x, f4 y, v4 m) { __builtin_shuffle(x, y, m); }\n",
7176 "void d(v4 x, s8 m) { __builtin_shuffle(x, m); }\n",
7177 "void e(f4 x, l4 m) { __builtin_shuffle(x, m); }\n",
7178 "void g(v4 x) { __builtin_shuffle(x); }\n",
7179 );
7180 let messages = run(&opts, source).messages;
7181 let wanted = [
7182 "last argument must be an integer vector [E0715]",
7183 "arguments must be vectors [E0715]",
7184 "argument vectors must be of the same type [E0715]",
7185 "number of elements of the argument vector(s) and the mask vector should be the same \
7186 [E0715]",
7187 "argument vector(s) inner type must have the same size as inner type of the mask \
7188 [E0715]",
7189 "too few arguments to function '__builtin_shuffle' [E0511]",
7190 ];
7191 assert_eq!(messages.len(), wanted.len(), "{messages:?}");
7192 for (message, wanted) in messages.iter().zip(wanted) {
7193 assert!(message.ends_with(wanted), "{message}");
7194 }
7195 }
7196
7197 #[test]
7206 fn the_plain_math_names_are_the_same_mask_and_not_a_call() {
7207 let text =
7208 body(concat!("double fabs(double x);\n", "double f(double x) { return fabs(x); }\n",));
7209 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
7210 assert!(!text.contains("call"), "{text}");
7211
7212 let text =
7213 body(concat!("float fabsf(float x);\n", "float f(float x) { return fabsf(x); }\n",));
7214 assert!(text.contains("bitcast.i32 %0"), "{text}");
7215 assert!(!text.contains("call"), "{text}");
7216
7217 let text = body(concat!(
7218 "double copysign(double x, double y);\n",
7219 "double f(double x, double y) { return copysign(x, y); }\n",
7220 ));
7221 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
7222 assert!(!text.contains("call"), "{text}");
7223
7224 let text = body(concat!(
7225 "float copysignf(float x, float y);\n",
7226 "float f(float x, float y) { return copysignf(x, y); }\n",
7227 ));
7228 assert!(!text.contains("call"), "{text}");
7229
7230 let text = ir(concat!(
7234 "long double fabsl(long double x);\n",
7235 "long double f(long double x) { return fabsl(x); }\n",
7236 ));
7237 assert!(text.contains("call @fabsl"), "{text}");
7238 }
7239
7240 #[test]
7248 fn a_plain_math_name_the_program_took_is_the_programs_own_function() {
7249 let taken = concat!(
7250 "static double fabs(double b) { return 7; }\n",
7251 "double f(double x) { return fabs(x); }\n",
7252 );
7253 assert!(ir(taken).contains("call @fabs"), "a static definition is the program's own");
7254
7255 let retyped = concat!("int fabs(int b);\n", "int f(int x) { return fabs(x); }\n");
7256 assert!(ir(retyped).contains("call @fabs"), "another type is another function");
7257
7258 let plain = concat!("double fabs(double b);\n", "double f(double x) { return fabs(x); }\n");
7259 let mut opts = options();
7260 opts.emit = EmitKind::Ir;
7261 assert!(!run(&opts, plain).text().contains("call @fabs"), "the library's by default");
7262
7263 opts.builtins = false;
7264 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin");
7265
7266 opts.builtins = true;
7267 opts.no_builtin = vec!["fabs".to_owned()];
7268 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin-fabs");
7269 let one = concat!(
7270 "double copysign(double a, double b);\n",
7271 "double f(double x) { return copysign(x, 1.0); }\n",
7272 );
7273 assert!(!run(&opts, one).text().contains("call @copysign"), "one name and not the family");
7274
7275 opts.no_builtin = Vec::new();
7277 opts.builtins = false;
7278 let prefixed = "double f(double x) { return __builtin_fabs(x); }\n";
7279 assert!(!run(&opts, prefixed).text().contains("call @fabs"), "the prefix is not a library");
7280 }
7281
7282 #[test]
7291 fn the_sign_builtins_answer_a_zero_and_a_nan_the_way_the_bits_say() {
7292 let text = ir(concat!(
7293 "double a = __builtin_fabs(-3.5);\n",
7294 "double b = __builtin_copysign(1.0, -0.0);\n",
7295 "double c = __builtin_copysign(0.0, -2.0);\n",
7296 "double d = __builtin_copysign(-__builtin_nan(\"\"), 1.0);\n",
7298 "double e = __builtin_fabs(-__builtin_nan(\"0x1\"));\n",
7299 "float g = __builtin_copysignf(-0.0f, 2.0f);\n",
7300 "long double h = __builtin_copysignl(1.0L, -1.0L);\n",
7301 "long double i = __builtin_fabsl(-__builtin_infl());\n",
7302 ));
7303 assert!(text.contains("global @a : f64 = 0x400c000000000000,"), "{text}");
7304 assert!(text.contains("global @b : f64 = 0xbff0000000000000,"), "{text}");
7305 assert!(text.contains("global @c : f64 = 0x8000000000000000,"), "{text}");
7306 assert!(text.contains("global @d : f64 = 0x7ff8000000000000,"), "{text}");
7307 assert!(text.contains("global @e : f64 = 0x7ff8000000000001,"), "{text}");
7308 assert!(text.contains("global @g : f32 = 0x0,"), "{text}");
7309 assert!(text.contains("f80 0xbfff8000000000000000"), "{text}");
7310 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
7311 }
7312
7313 #[test]
7320 fn the_sign_of_a_long_double_is_in_the_word_at_the_top_of_it() {
7321 for source in [
7322 "int f(long double x) { return __builtin_signbit(x); }\n",
7323 "long double f(long double x) { return __builtin_fabsl(x); }\n",
7324 "long double f(long double x, long double y) { return __builtin_copysignl(x, y); }\n",
7325 "int f(long double x) { return __builtin_isnormal(x); }\n",
7326 ] {
7327 let text = body(source);
7328 assert!(!text.contains("i80"), "{text}");
7329 assert!(text.contains("i16"), "{text}");
7330 }
7331 }
7332
7333 #[test]
7341 fn the_complex_builtins_are_the_halves_of_the_value_and_not_a_call() {
7342 let text = body("double f(_Complex double z) { return __builtin_creal(z); }\n");
7343 assert!(!text.contains("call"), "{text}");
7344 let text = body("double f(_Complex double z) { return __builtin_cimag(z); }\n");
7345 assert!(!text.contains("call"), "{text}");
7346
7347 let text = body("_Complex double f(_Complex double z) { return __builtin_conj(z); }\n");
7350 assert_eq!(text.matches("fneg").count(), 1, "{text}");
7351 assert!(!text.contains("call"), "{text}");
7352 let negated = body("_Complex double f(_Complex double z) { return -z; }\n");
7353 assert_eq!(negated.matches("fneg").count(), 2, "{negated}");
7354
7355 let written = body("_Complex double f(_Complex double z) { return ~z; }\n");
7358 assert_eq!(written, text, "the name and the operator are the same thing");
7359
7360 let text = body(concat!(
7362 "double creal(_Complex double z);\n",
7363 "double f(_Complex double z) { return creal(z); }\n",
7364 ));
7365 assert!(!text.contains("call"), "{text}");
7366 let text = body(concat!(
7367 "_Complex float conjf(_Complex float z);\n",
7368 "_Complex float f(_Complex float z) { return conjf(z); }\n",
7369 ));
7370 assert_eq!(text.matches("fneg").count(), 1, "{text}");
7371 assert!(!text.contains("call"), "{text}");
7372
7373 let taken = concat!(
7376 "static double creal(_Complex double z) { return 7; }\n",
7377 "double f(_Complex double z) { return creal(z); }\n",
7378 );
7379 assert!(ir(taken).contains("call @creal"), "a static definition is the program's own");
7380 let retyped = concat!("int cimag(int z);\n", "int f(int z) { return cimag(z); }\n");
7381 assert!(ir(retyped).contains("call @cimag"), "another type is another function");
7382 let plain = concat!(
7383 "double cimag(_Complex double z);\n",
7384 "double f(_Complex double z) { return cimag(z); }\n",
7385 );
7386 let mut opts = options();
7387 opts.emit = EmitKind::Ir;
7388 opts.builtins = false;
7389 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin");
7390 opts.builtins = true;
7391 opts.no_builtin = vec!["cimag".to_owned()];
7392 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin-cimag");
7393
7394 let text = ir(concat!(
7396 "double a = __builtin_creal(1.5 + 2.5i);\n",
7397 "double b = __builtin_cimag(1.5 + 2.5i);\n",
7398 "_Complex double c = __builtin_conj(1.5 + 2.5i);\n",
7399 ));
7400 assert!(text.contains("global @a : f64 = 0x3ff8000000000000,"), "{text}");
7401 assert!(text.contains("global @b : f64 = 0x4004000000000000,"), "{text}");
7402 assert!(
7403 text.contains("{ f64 0x3ff8000000000000, f64 0xc004000000000000 }"),
7404 "the conjugate of a constant is the constant with the second half negated: {text}"
7405 );
7406 assert!(!text.contains("call"), "{text}");
7407 }
7408
7409 #[test]
7417 fn a_math_library_builtin_of_a_constant_is_the_answer_and_not_a_call() {
7418 let text = ir(concat!(
7419 "double a = __builtin_ceil(1.5);\n",
7420 "double b = __builtin_floor(1.5);\n",
7421 "double c = __builtin_trunc(-1.5);\n",
7422 "double d = __builtin_round(2.5);\n",
7425 "double e = __builtin_ceil(-0.5);\n",
7427 "double f = __builtin_fmax(1.0, 2.0);\n",
7428 "double g = __builtin_fmin(1.0, 2.0);\n",
7429 "float h = __builtin_ceilf(1.25f);\n",
7430 "double ceil(double x);\n",
7433 "double i = ceil(2.25);\n",
7434 ));
7435 assert!(text.contains("global @a : f64 = 0x4000000000000000,"), "{text}");
7436 assert!(text.contains("global @b : f64 = 0x3ff0000000000000,"), "{text}");
7437 assert!(text.contains("global @c : f64 = 0xbff0000000000000,"), "{text}");
7438 assert!(text.contains("global @d : f64 = 0x4008000000000000,"), "{text}");
7439 assert!(text.contains("global @e : f64 = 0x8000000000000000,"), "{text}");
7440 assert!(text.contains("global @f : f64 = 0x4000000000000000,"), "{text}");
7441 assert!(text.contains("global @g : f64 = 0x3ff0000000000000,"), "{text}");
7442 assert!(text.contains("global @h : f32 = 0x40000000,"), "{text}");
7443 assert!(text.contains("global @i : f64 = 0x4008000000000000,"), "{text}");
7444 assert!(!text.contains("call"), "{text}");
7445 }
7446
7447 #[test]
7455 fn a_math_library_builtin_of_anything_else_is_a_call_to_the_library() {
7456 let text = ir(concat!(
7457 "double f(double x) { return __builtin_ceil(x); }\n",
7458 "float g(float x) { return __builtin_floorf(x); }\n",
7459 "double h(double x, double y) { return __builtin_fmax(x, y); }\n",
7460 ));
7461 assert!(text.contains("call @ceil("), "{text}");
7462 assert!(text.contains("call @floorf("), "{text}");
7463 assert!(text.contains("call @fmax("), "{text}");
7464
7465 let text = ir(concat!(
7469 "double f(void) { return __builtin_rint(2.5); }\n",
7470 "double g(void) { return __builtin_nearbyint(2.5); }\n",
7471 ));
7472 assert!(text.contains("call @rint("), "{text}");
7473 assert!(text.contains("call @nearbyint("), "{text}");
7474
7475 let text = ir("double f(void) { return __builtin_fmin(__builtin_nan(\"\"), 1.0); }\n");
7478 assert!(text.contains("call @fmin("), "{text}");
7479
7480 let plain = concat!("double ceil(double x);\n", "double f(void) { return ceil(2.25); }\n");
7483 let mut opts = options();
7484 opts.emit = EmitKind::Ir;
7485 opts.no_builtin = vec!["ceil".to_owned()];
7486 assert!(run(&opts, plain).text().contains("call @ceil("), "-fno-builtin-ceil");
7487 }
7488
7489 #[test]
7496 fn a_constexpr_object_is_a_constant_wherever_one_is_required() {
7497 let text = ir(concat!(
7498 "constexpr int side = 4;\n",
7499 "constexpr int wider = side + 1;\n",
7500 "constexpr double half = 1.5;\n",
7501 "struct point { int x; int y; };\n",
7502 "constexpr struct point origin = { 5, 6 };\n",
7503 "int square[side * side];\n",
7504 "int rectangle[wider];\n",
7505 "int rounded[(int)half * 2];\n",
7506 "int across[origin.y];\n",
7507 "enum named { four = side };\n",
7508 "int e = four;\n",
7509 ));
7510 assert!(text.contains("global @square : bytes 64 ="), "{text}");
7511 assert!(text.contains("global @rectangle : bytes 20 ="), "{text}");
7512 assert!(text.contains("global @rounded : bytes 8 ="), "{text}");
7513 assert!(text.contains("global @across : bytes 24 ="), "{text}");
7514 assert!(text.contains("global @e : i32 = 4,"), "{text}");
7515
7516 let mut opts = options();
7519 opts.emit = EmitKind::Ir;
7520 let konst = "const int n = 1;\nint a[n];\n";
7521 let message = "/main.c:2:5: error: variably modified 'a' at file scope [E0538]";
7522 assert_eq!(run(&opts, konst).messages, [message]);
7523
7524 let subscript = "constexpr int t[3] = { 1, 2, 3 };\nint a[t[1]];\n";
7526 assert_eq!(run(&opts, subscript).messages, [message]);
7527
7528 let address = "constexpr int c = 3;\nint *p = &c;\n";
7530 let warning = "/main.c:2:6: warning: initialization discards 'const' qualifier from \
7531 pointer target type [E0514]";
7532 assert_eq!(run(&opts, address).messages, [warning]);
7533 }
7534
7535 #[test]
7544 fn a_member_whose_size_was_refused_is_not_a_flexible_array_member() {
7545 let mut opts = options();
7546 opts.emit = EmitKind::Ir;
7547
7548 let alone = "int k;\nextern struct D { int a[k]; } ed;\n";
7549 let message = "/main.c:2:23: error: variably modified 'a' at file scope [E0538]";
7550 assert_eq!(run(&opts, alone).messages, [message]);
7551
7552 let first = "int k;\nextern struct E { int a[k]; int b; } ee;\n";
7554 assert_eq!(run(&opts, first).messages, [message]);
7555
7556 let negative = "struct F { int a[-1]; };\n";
7559 let refused = "/main.c:1:18: error: size of array 'a' is negative [E0536]";
7560 assert_eq!(run(&opts, negative).messages, [refused]);
7561
7562 let flexible = "struct G { int a[]; };\n";
7565 let named = "/main.c:1:16: error: flexible array member in a struct with no named \
7566 members [E0554]";
7567 assert_eq!(run(&opts, flexible).messages, [named]);
7568 }
7569
7570 #[test]
7585 fn a_pointer_to_an_array_gains_a_qualifier_the_same_way_a_pointer_to_anything_else_does() {
7586 let mut opts = options();
7587 opts.emit = EmitKind::Ir;
7588 let prefix = "typedef unsigned int B[4];\nstruct H { B category[2]; };\n";
7589
7590 let adding = format!("{prefix}const B *f(struct H *h) {{ return &h->category[0]; }}\n");
7592 assert_eq!(run(&opts, &adding).messages, [] as [String; 0]);
7593
7594 let plain = concat!(
7597 "const unsigned int (*f(unsigned int (*p)[4]))[4] { return p; }\n",
7598 "const unsigned int (*g(unsigned int (*p)[2][3]))[2][3] { return p; }\n",
7599 );
7600 assert_eq!(run(&opts, plain).messages, [] as [String; 0]);
7601
7602 let dropping = format!("{prefix}B *f(const B *p) {{ return p; }}\n");
7605 let warning = "/main.c:3:27: warning: return discards 'const' qualifier from pointer target type \
7606 [E0514]";
7607 assert_eq!(run(&opts, &dropping).messages, [warning]);
7608
7609 let wrong = "const unsigned int (*f(unsigned short (*p)[4]))[4] { return p; }\n";
7612 let error = "/main.c:1:61: error: returning 'unsigned short (*)[4]' from a function with \
7613 incompatible return type 'const unsigned int (*)[4]' [E0512]";
7614 assert_eq!(run(&opts, wrong).messages, [error]);
7615 }
7616
7617 #[test]
7626 fn an_old_style_definition_takes_its_types_from_the_declarations_under_its_list() {
7627 let mut opts = options();
7630 opts.std = Std::C17;
7631 let source = concat!(
7632 "int add(a, b)\n",
7633 "int a;\n",
7634 "int b;\n",
7635 "{ return a + b; }\n",
7636 "int promoted(c)\n",
7637 "char c;\n",
7638 "{ return c; }\n",
7639 "int narrow(char);\n",
7640 "int narrow(c)\n",
7641 "char c;\n",
7642 "{ return c; }\n",
7643 "int first(a)\n",
7644 "int a[4];\n",
7645 "{ return a[0]; }\n",
7646 );
7647 let result = run(&opts, source);
7648 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
7649 let text = result.text();
7650 assert!(text.contains("add : int(int, int) function external defined"), "{text}");
7651 assert!(text.contains("promoted : int(int) function external defined"), "{text}");
7652 assert!(text.contains("c : char object automatic defined"), "{text}");
7654 assert!(text.contains("narrow : int(char) function external defined"), "{text}");
7655 assert!(text.contains("first : int(int *) function external defined"), "{text}");
7657 }
7658
7659 #[test]
7666 fn the_two_halves_of_an_old_style_parameter_list_have_to_agree() {
7667 let mut opts = options();
7668 opts.std = Std::C17;
7669 for (source, message) in [
7670 ("int f(a, a)\nint a;\n{ return a; }\n", "1:10: error: multiple parameters named 'a'"),
7671 (
7672 "int f(a)\nint a;\nint b;\n{ return a; }\n",
7673 "3:5: error: declaration for parameter 'b' but no such parameter",
7674 ),
7675 ("int f(a)\nint a;\nint a;\n{ return a; }\n", "3:5: error: redefinition of parameter"),
7676 ("int f(a)\nint a = 1;\n{ return a; }\n", "2:5: error: parameter 'a' is initialized"),
7677 (
7678 "int f(a)\nstatic int a;\n{ return a; }\n",
7679 "2:12: error: storage class specified for parameter 'a'",
7680 ),
7681 (
7682 "int f(char);\nint f(a)\nshort a;\n{ return a; }\n",
7683 "2:7: error: argument 'a' doesn't match prototype",
7684 ),
7685 ] {
7686 let result = run(&opts, source);
7687 assert!(result.failed(), "expected this to fail:\n{source}");
7688 assert!(result.messages[0].contains(message), "{:?}", result.messages);
7689 }
7690
7691 let implicit = "int f(a, b)\nint a;\n{ return a + b; }\n";
7694 let mut older = options();
7695 older.std = Std::C89;
7696 assert!(!run(&older, implicit).failed(), "{:?}", run(&older, implicit).messages);
7697 let result = run(&opts, implicit);
7698 assert!(
7699 result.messages[0].contains("1:10: error: type of 'b' defaults to 'int'"),
7700 "{:?}",
7701 result.messages
7702 );
7703
7704 let mut newer = options();
7708 newer.std = Std::C23;
7709 let plain = "int f(a)\nint a;\n{ return a; }\n";
7710 let result = run(&newer, plain);
7711 assert!(!result.failed(), "{:?}", result.messages);
7712 assert_eq!(
7713 result.messages,
7714 ["/main.c:1:5: warning: old-style function definition [E0412]"]
7715 );
7716 assert!(run(&opts, plain).messages.is_empty(), "and nothing to say in the dialects before");
7717 }
7718
7719 #[test]
7726 fn the_obsolete_designators_are_taken_and_are_pedantic_warnings() {
7727 let array = "int a[8] = { [3] 7 };\n";
7728 let member = "struct s { int x; } v = { x: 7 };\n";
7729 for source in [array, member] {
7730 let result = run(&options(), source);
7731 assert!(!result.failed(), "{:?}", result.messages);
7732 assert!(result.messages.is_empty(), "nothing to say: {:?}", result.messages);
7733 }
7734
7735 let mut asked = options();
7736 asked.pedantic = true;
7737 assert_eq!(
7738 run(&asked, array).messages,
7739 ["/main.c:1:18: warning: obsolete designator, write `[i] =` instead [E0415]"]
7740 );
7741 assert_eq!(
7742 run(&asked, member).messages,
7743 ["/main.c:1:27: warning: obsolete designator, write `.field =` instead [E0413]"]
7744 );
7745 }
7746
7747 #[test]
7754 fn a_type_is_refused_when_it_passes_the_largest_object_and_not_before() {
7755 let text = ir(concat!(
7756 "struct huge_struct { short buf[(1L << 62) - 256]; int a, b, c, d; };\n",
7757 "struct brim { char buf[9223372036854775807L]; };\n",
7758 "struct bitty { char buf[9223372036854775800L]; int x : 1; };\n",
7759 "unsigned long h = sizeof(struct huge_struct);\n",
7760 "unsigned long b = sizeof(struct brim);\n",
7761 "unsigned long y = sizeof(struct bitty);\n",
7762 ));
7763 assert!(text.contains("global @h : i64 = 9223372036854775312,"), "{text}");
7764 assert!(text.contains("global @b : i64 = 9223372036854775807,"), "{text}");
7765 assert!(text.contains("global @y : i64 = 9223372036854775804,"), "{text}");
7766
7767 let mut opts = options();
7768 opts.emit = EmitKind::Ir;
7769 let over = "struct over { char buf[9223372036854775800L]; char x[8]; };\n";
7770 let message = "/main.c:1:1: error: type 'struct over' is too large [E0560]";
7771 assert_eq!(run(&opts, over).messages, [message]);
7772 let array = "struct wide { short buf[1L << 62]; };\n";
7773 let message = "/main.c:1:25: error: size of array 'buf' exceeds \
7774 maximum object size '9223372036854775807' [E0537]";
7775 assert_eq!(run(&opts, array).messages[0], message);
7776 }
7777
7778 fn compile_bytes(source: &[u8]) -> Compiled {
7783 let mut opts = options();
7784 opts.emit = EmitKind::Ir;
7785 let mut fs = MemoryFileSystem::new();
7786 fs.insert("/main.c", source.to_vec());
7787 compile(&opts, "/main.c", &fs)
7788 }
7789
7790 #[test]
7797 fn a_byte_that_is_not_a_character_is_kept_in_a_literal_and_refused_outside_one() {
7798 let mut source = b"char s[] = \"a".to_vec();
7799 source.push(0xff);
7800 source.extend_from_slice(b"b\";\nchar c = '");
7801 source.push(0xff);
7802 source.extend_from_slice(b"';\n");
7803 let result = compile_bytes(&source);
7804 assert_eq!(result.messages, Vec::<String>::new(), "a raw byte in a literal is that byte");
7805 assert!(result.text().contains(r#"bytes "a\ffb\00""#), "{}", result.text());
7806 assert!(result.text().contains("global @c : i8 = -1,"), "{}", result.text());
7808
7809 let mut stray = b"int a".to_vec();
7810 stray.push(0xff);
7811 stray.extend_from_slice(b" = 1;\n");
7812 let result = compile_bytes(&stray);
7813 assert!(
7814 result.messages.iter().any(|m| m.contains("source is not valid UTF-8 here")),
7815 "{:?}",
7816 result.messages
7817 );
7818 }
7819
7820 #[test]
7821 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
7822 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
7823 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
7824 let expected = "\
7825func @add(i32, i32) -> i32, linkage(external) {
7826block0(%0: i32, %1: i32):
7827 %2 = add.nsw %0, %1
7828 return %2
7829}
7830";
7831 assert!(text.contains(expected), "{text}");
7832 }
7833
7834 #[test]
7835 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
7836 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
7837 assert!(!text.contains("alloca"), "{text}");
7838 assert!(!text.contains("load"), "{text}");
7839 assert!(!text.contains("store"), "{text}");
7840 }
7841
7842 #[test]
7843 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
7844 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
7845 let expected = "\
7846block0:
7847 %0 = alloca, size 4, align 4
7848 %1 = iconst.i32 1
7849 store %1 -> %0, align 4, tbaa !1
7850 %2 = call @g(%0) : (ptr) -> i32
7851 return %2
7852";
7853 assert_eq!(text, expected);
7854 }
7855
7856 #[test]
7857 fn a_loop_carries_what_it_changes_as_block_parameters() {
7858 let text = body(
7861 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
7862 return total;\n}\n",
7863 );
7864 assert!(!text.contains("alloca"), "{text}");
7865 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
7866 assert!(text.contains("jump block1("), "{text}");
7867 }
7868
7869 #[test]
7870 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
7871 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
7872 assert!(text.contains("icmp slt %0, %1"), "{text}");
7873 assert!(!text.contains("zext"), "{text}");
7874 }
7875
7876 #[test]
7877 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
7878 let text = body("int f(int a, int b) { return a && b; }\n");
7879 let expected = "\
7880block0(%0: i32, %1: i32):
7881 %2 = iconst.i32 0
7882 %3 = icmp ne %0, %2
7883 %4 = iconst.i1 0
7884 br_if %3, block1, block2(%4)
7885
7886block1:
7887 %5 = iconst.i32 0
7888 %6 = icmp ne %1, %5
7889 jump block2(%6)
7890
7891block2(%7: i1):
7892 %8 = zext.i32 %7
7893 return %8
7894";
7895 assert_eq!(text, expected);
7896 }
7897
7898 #[test]
7899 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
7900 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
7901 assert!(!text.contains("block3"), "{text}");
7904 assert!(!text.contains("iconst.i32 3"), "{text}");
7905 }
7906
7907 #[test]
7908 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
7909 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
7910 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
7911 assert!(body("int f(void) { }\n").contains("unreachable"));
7912 }
7913
7914 #[test]
7915 fn a_structure_is_copied_rather_than_held_in_a_value() {
7916 let text = body(
7917 "struct point { int x, y; };\n\
7918 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
7919 );
7920 assert!(text.contains("memcpy"), "{text}");
7921 }
7922
7923 #[test]
7924 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
7925 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
7926 assert!(text.contains("memset"), "{text}");
7927 }
7928
7929 #[test]
7930 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
7931 let text = body(
7932 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
7933 default: r = 4; } return r; }\n",
7934 );
7935 let expected = "\
7936block0(%0: i32):
7937 %1 = iconst.i32 0
7938 switch %0, block1, [1 => block2, 2 => block3(%1)]
7939
7940block1:
7941 %2 = iconst.i32 4
7942 jump block4(%2)
7943
7944block2:
7945 %3 = iconst.i32 1
7946 jump block3(%3)
7947
7948block3(%4: i32):
7949 %5 = iconst.i32 2
7950 %6 = add.nsw %4, %5
7951 jump block4(%6)
7952
7953block4(%7: i32):
7954 return %7
7955";
7956 assert_eq!(text, expected);
7957 }
7958
7959 #[test]
7960 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
7961 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
7964 assert!(text.contains("%2 = sub %0, %1"), "{text}");
7965 assert!(text.contains("icmp ule"), "{text}");
7966 assert!(!text.contains("switch"), "{text}");
7967 }
7968
7969 #[test]
7970 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
7971 let text = body(
7972 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
7973 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
7974 );
7975 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
7978 assert!(text.contains("block5:\n jump block7("), "{text}");
7979 assert!(text.contains("block6:\n jump block8("), "{text}");
7980 }
7981
7982 #[test]
7983 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
7984 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
7985 }
7986
7987 #[test]
7988 fn a_label_a_loop_is_only_entered_through_builds_the_loop_around_it() {
7989 let text = body(
7994 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
7995 return n; }\n",
7996 );
7997 assert!(text.contains("switch %0, block1(%1), [1 => block2, 2 => block3(%1)]"), "{text}");
8000 assert!(text.contains("block3(%3: i32):\n %4 = iconst.i32 1"), "{text}");
8001 assert!(text.contains("block4:\n jump block3("), "{text}");
8002 }
8003
8004 #[test]
8005 fn a_goto_into_a_loop_body_enters_it_without_the_test() {
8006 let text = body("int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n");
8009 assert!(text.starts_with("block0(%0: i32, %1: i32):\n jump block1(%1)"), "{text}");
8010 assert!(text.contains("block1(%2: i32):\n %3 = iconst.i32 1"), "{text}");
8011 assert!(text.contains("br_if %6, block2, block3"), "{text}");
8012 }
8013
8014 #[test]
8015 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
8016 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
8017 assert!(!text.contains("alloca"), "{text}");
8021 assert!(text.contains("block2(%4: i32):\n return %4"), "{text}");
8022 assert_eq!(text.matches("jump block2(").count(), 2, "{text}");
8023 }
8024
8025 #[test]
8026 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
8027 let text =
8028 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
8029 assert!(!text.contains("alloca"), "{text}");
8030 assert!(text.contains("block1(%2: i32):"), "{text}");
8031 assert!(text.contains("jump block1(%5)"), "{text}");
8032 }
8033
8034 #[test]
8035 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
8036 assert_eq!(
8039 body("int f(int x) { return x; spare: return 0; }\n"),
8040 "block0(%0: i32):\n return %0\n"
8041 );
8042 }
8043
8044 #[test]
8045 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
8046 let text = body(
8047 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
8048 );
8049 assert_eq!(
8052 text,
8053 "\
8054block0(%0: ptr):
8055 %1 = load.i8 %0, align 1
8056 %2 = iconst.i8 3
8057 %3 = ashr %1, %2
8058 %4 = sext.i32 %3
8059 return %4
8060"
8061 );
8062 }
8063
8064 #[test]
8065 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
8066 let text =
8070 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
8071 assert_eq!(
8072 text,
8073 "\
8074block0(%0: ptr, %1: i32):
8075 %2 = iconst.i32 16777215
8076 %3 = and %1, %2
8077 %4 = trunc.i16 %3
8078 store %4 -> %0, align 2
8079 %5 = iconst.i32 16
8080 %6 = lshr %3, %5
8081 %7 = trunc.i8 %6
8082 %8 = iconst.i64 2
8083 %9 = ptr_add %0, %8
8084 store %7 -> %9, align 1
8085 return
8086"
8087 );
8088 }
8089
8090 #[test]
8091 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
8092 let text =
8093 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
8094 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
8097 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
8098 }
8099
8100 #[test]
8101 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
8102 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
8105 assert_eq!(text.matches("ashr").count(), 0, "{text}");
8106 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
8107 }
8108
8109 #[test]
8110 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
8111 let text = body(
8115 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
8116 );
8117 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
8118 }
8119
8120 #[test]
8121 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
8122 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
8125 assert!(
8126 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
8127 "{text}"
8128 );
8129 }
8130
8131 #[test]
8132 fn an_initialized_flexible_array_member_makes_the_object_larger_than_its_type() {
8133 let text = ir(concat!(
8138 "struct a { int i; int j[]; } x = { 1, { 2, 0, 2, 3 } };\n",
8139 "struct b { char c; char p[]; } y = { 'o', \"wx\" };\n",
8140 "struct c { char c; char p[]; } z = { '9', { 'e', 'b' } };\n",
8141 "char s[2] = \"hi\";\n",
8142 ));
8143 assert!(
8144 text.contains("global @x : bytes 20 = { i32 1, i32 2, i32 0, i32 2, i32 3 }"),
8145 "{text}"
8146 );
8147 assert!(text.contains("global @y : bytes 4 = { i8 111, bytes \"wx\\00\" }"), "{text}");
8148 assert!(text.contains("global @z : bytes 3 = { i8 57, i8 101, i8 98 }"), "{text}");
8149 assert!(text.contains("global @s : bytes 2 = { bytes \"hi\" }"), "{text}");
8152 }
8153
8154 #[test]
8155 fn a_definition_takes_a_parameter_it_left_unnamed() {
8156 let text = ir("int f(int a, int) { return a; }\n");
8160 assert!(text.contains("func @f(i32, i32) -> i32"), "{text}");
8161 assert!(text.contains("block0(%0: i32, %1: i32):"), "{text}");
8162
8163 let text = ir("int g(int, int n) { return n; }\n");
8166 assert!(text.contains("block0(%0: i32, %1: i32):\n return %1\n"), "{text}");
8167 }
8168
8169 #[test]
8170 fn an_assignment_of_a_structure_is_the_object_it_wrote() {
8171 let text = body(concat!(
8176 "struct s { int f; int g; };\n",
8177 "void h(struct s *a, struct s *c, struct s *d, struct s *e)\n",
8178 "{ *d = *e = a[0] = *c; }\n",
8179 ));
8180 assert_eq!(text.matches("memcpy").count(), 3, "{text}");
8181 assert!(text.contains("memcpy %8, %1, size 8, align 4\n"), "{text}");
8182 assert!(text.contains("memcpy %3, %8, size 8, align 4\n"), "{text}");
8183 assert!(text.contains("memcpy %2, %3, size 8, align 4\n"), "{text}");
8184 }
8185
8186 #[test]
8187 fn a_string_literal_stops_at_the_end_of_the_array_it_is_filling() {
8188 let mut opts = options();
8193 opts.emit = EmitKind::Ir;
8194 let result = run(
8195 &opts,
8196 concat!(
8197 "const char a[2][3] = { \"1234\", \"xyz\" };\n",
8198 "static const char b[3][5] = { \"12345\", \"678\", \"9\" };\n",
8199 "union u { struct { char x[4]; char y[4]; }; struct { char z[8]; }; };\n",
8200 "const union u c = { { \"1234\", \"567\" } };\n",
8201 ),
8202 );
8203 let text = result.text();
8204 assert_eq!(
8205 result.messages,
8206 ["/main.c:1:24: warning: initializer-string for array of 'const char' is too long \
8207 (5 chars into 3 available) [E0637]"]
8208 );
8209 assert!(text.contains("global @a : bytes 6 = { bytes \"123\", bytes \"xyz\" }"), "{text}");
8210 assert!(
8211 text.contains(
8212 "global @b : bytes 15 = { bytes \"12345\", bytes \"678\\00\", zero 1, \
8213 bytes \"9\\00\", zero 3 }"
8214 ),
8215 "{text}"
8216 );
8217 assert!(
8220 text.contains("global @c : bytes 8 = { bytes \"1234\", bytes \"567\\00\" }"),
8221 "{text}"
8222 );
8223 }
8224
8225 #[test]
8226 fn a_cast_of_a_record_to_its_own_type_is_the_object_that_was_cast() {
8227 let text = body(concat!(
8231 "struct s { int a, b; };\nstruct v { struct s s; int t; };\n",
8232 "void g(struct v *);\n",
8233 "void f(struct s *p) { struct v w = { (struct s)*p, 5 }; g(&w); }\n",
8234 ));
8235 assert_eq!(text.matches("memcpy").count(), 1, "{text}");
8236 }
8237
8238 #[test]
8239 fn a_compound_literal_read_in_a_static_initializer_lays_its_bytes_into_the_image() {
8240 let text = ir(concat!(
8245 "struct s { int x; };\n",
8246 "struct t { struct s s; int o; } a = { (struct s){ 2 }, 3 };\n",
8247 "int n = (int){ 7 };\n",
8248 "struct u { struct s p; struct s q; } b = { (struct s){ 1 }, (struct s){ } };\n",
8249 ));
8250 assert!(text.contains("global @a : bytes 8 = { i32 2, i32 3 }"), "{text}");
8251 assert!(text.contains("global @n : i32 = 7,"), "{text}");
8252 assert!(text.contains("global @b : bytes 8 = { i32 1, zero 4 }"), "{text}");
8255 }
8256
8257 #[test]
8258 fn the_address_of_a_compound_literal_asks_for_the_object_it_points_at() {
8259 let text = ir("struct s { int x; };\nstruct s *q = &(struct s){ 9 };\n");
8263 assert!(text.contains("global @.Lanon.0 : i32 = 9, align 4, linkage(internal)"), "{text}");
8264 assert!(text.contains("global @q : bytes 8 = { addr.8 @.Lanon.0 }"), "{text}");
8265 }
8266
8267 #[test]
8268 fn an_object_of_no_size_at_all_has_an_image_with_nothing_in_it() {
8269 let text = ir("unsigned char foo[1][0];\n");
8273 assert!(text.contains("global @foo : bytes 0 = {}, align 1"), "{text}");
8274 }
8275
8276 #[test]
8277 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
8278 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
8281 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
8282 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
8283 }
8284
8285 #[test]
8286 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
8287 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
8291 assert!(
8292 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
8293 "{text}"
8294 );
8295 }
8296
8297 #[test]
8298 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
8299 let text = body(
8304 "\
8305struct s { int a, b; };
8306struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
8307",
8308 );
8309 assert!(text.contains("block3(%7: ptr)"), "{text}");
8311 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
8312 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
8313 }
8314
8315 #[test]
8323 fn the_left_side_of_a_conditional_with_no_middle_is_evaluated_once() {
8324 let text = body("int f(int i) { return ++i ?: 10; }\n");
8325 assert!(text.contains("jump block3(%2)"), "the arm is the value that was tested: {text}");
8326 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
8327
8328 let text = body("long f(int i) { return ++i ?: 10L; }\n");
8331 assert!(text.contains("%5 = sext.i64 %2"), "the arm widens what was tested: {text}");
8332 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
8333
8334 let text = body("int g(void);\nint f(void) { return g() ?: 10; }\n");
8336 assert_eq!(text.matches("call @g").count(), 1, "called once: {text}");
8337
8338 let text = body("int f(int i) { return ++i ? ++i : 10; }\n");
8341 assert_eq!(text.matches("add.nsw").count(), 2, "incremented twice: {text}");
8342 }
8343
8344 #[test]
8345 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
8346 let text = ir("\
8350struct pair { int a, b; };
8351struct pair make(int a, int b);
8352struct pair twice(struct pair p) { return make(p.a, p.b); }
8353");
8354 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
8355 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
8356 }
8357
8358 #[test]
8359 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
8360 let text = ir("\
8364struct big { double v[8]; };
8365struct big grow(struct big b);
8366struct big twice(struct big b) { return grow(grow(b)); }
8367");
8368 assert!(
8369 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
8370 "{text}"
8371 );
8372 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
8373 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
8376 }
8377
8378 #[test]
8379 fn a_structure_passed_to_a_variadic_function_says_so_at_the_call() {
8380 let text = ir("\
8385struct big { double v[8]; };
8386struct pair { int a, b; };
8387int p(const char *, ...);
8388int f(struct big b, struct pair q) { return p(\"\", 1, b, q); }
8389");
8390 assert!(
8391 text.contains("call @p(%4, %5, %2 byval(64, align 8), %6) : (ptr, ...) -> i32"),
8392 "{text}"
8393 );
8394 }
8395
8396 #[test]
8397 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
8398 let body = body(
8401 "\
8402struct pair { int a, b; };
8403struct pair make(int a, int b);
8404int second(void) { return make(1, 2).b; }
8405",
8406 );
8407 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
8408 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
8409 }
8410
8411 #[test]
8412 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
8413 let source = "\
8417struct hfa { float x, y, z; };
8418int take(struct hfa h);
8419int give(struct hfa h) { return take(h); }
8420";
8421 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
8422 let mut opts = options();
8423 opts.emit = EmitKind::Ir;
8424 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
8425 let result = run(&opts, source);
8426 assert_eq!(result.messages, Vec::<String>::new());
8427 assert!(result.text().contains("func @take(f32, f32, f32) -> i32"), "{}", result.text());
8428 }
8429
8430 #[test]
8431 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
8432 let source = "\
8435int use(int *);
8436void f(int n) {
8437 {
8438 int a[n];
8439 use(a);
8440 }
8441 use(0);
8442}
8443";
8444 let body = body(source);
8445 assert!(body.contains("mul.nsw"), "{body}");
8446 assert!(body.contains("stacksave"), "{body}");
8447 assert!(body.contains("alloca %"), "{body}");
8448 assert!(body.contains("stackrestore"), "{body}");
8449 }
8450
8451 #[test]
8452 fn a_goto_out_of_the_scope_of_one_gives_its_stack_back_on_the_way() {
8453 let source = "\
8458int use(int *);
8459int f(int n) {
8460 {
8461 int a[n];
8462 if (use(a)) goto out;
8463 use(0);
8464 }
8465out:
8466 return 0;
8467}
8468";
8469 let body = body(source);
8470 assert_eq!(body.matches("stackrestore").count(), 2, "{body}");
8472 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
8473 assert!(after.starts_with(" %4\n jump block"), "{body}");
8474 }
8475
8476 #[test]
8477 fn a_goto_to_a_label_the_array_is_still_alive_at_leaves_the_stack_alone() {
8478 let source = "\
8482int use(int *);
8483int f(int n) {
8484 int a[n];
8485again:
8486 if (use(a)) goto again;
8487 return 0;
8488}
8489";
8490 let body = body(source);
8491 assert!(body.contains("stacksave"), "{body}");
8492 assert!(!body.contains("stackrestore"), "{body}");
8493 }
8494
8495 #[test]
8496 fn a_goto_back_to_a_label_in_front_of_one_gives_it_back_every_time_round() {
8497 let source = "\
8502int use(int *);
8503int f(int n) {
8504again:
8505 {
8506 int a[n];
8507 if (use(a)) goto again;
8508 }
8509 return 0;
8510}
8511";
8512 let body = body(source);
8513 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
8514 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
8515 assert!(after.starts_with(" %4\n jump block1\n"), "{body}");
8516 }
8517
8518 #[test]
8519 fn the_head_of_a_for_loop_is_a_scope_that_closes_where_the_loop_is_left() {
8520 let source = "\
8526int f(void);
8527void t(void) {
8528 int count = 10;
8529 for (; count--;) {
8530 int b[f()];
8531 int i;
8532 for (i = 0; i < f(); i++) {
8533 b[i] = count;
8534 }
8535 }
8536}
8537";
8538 let body = body(source);
8539 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
8543 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
8544 let next = after.split("\n\n").next().expect("the block the restore is in");
8547 assert!(next.contains("jump block1("), "{body}");
8548 }
8549
8550 #[test]
8551 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
8552 let source = "\
8555unsigned long f(int n) {
8556 int a[n];
8557 n = 0;
8558 return sizeof a;
8559}
8560";
8561 let body = body(source);
8562 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
8564 }
8565
8566 #[test]
8567 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
8568 let source = "\
8571int use(int);
8572int f(int x) {
8573 return ({
8574 int t = use(x);
8575 t * t;
8576 });
8577}
8578";
8579 let expected = "\
8580block0(%0: i32):
8581 %1 = call @use(%0) : (i32) -> i32
8582 %2 = mul.nsw %1, %1
8583 return %2
8584";
8585 assert_eq!(body(source), expected);
8586 }
8587
8588 #[test]
8589 fn a_comma_whose_value_is_an_object_names_the_object_the_right_side_named() {
8590 let source = "\
8594struct pair { int a, b; };
8595void bail(void);
8596int f(struct pair p) {
8597 return (bail(), p).b;
8598}
8599";
8600 let expected = "\
8601block0(%0: i64):
8602 %1 = alloca, size 8, align 4
8603 store %0 -> %1, align 4
8604 call @bail() : ()
8605 %2 = iconst.i64 4
8606 %3 = ptr_add %1, %2
8607 %4 = load.i32 %3, align 4, tbaa !1
8608 return %4
8609";
8610 assert_eq!(body(source), expected);
8611 }
8612
8613 #[test]
8614 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
8615 let source = "int f(int x) { return ({ return x; 0; }); }\n";
8619 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
8620 }
8621
8622 #[test]
8623 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
8624 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
8628 let expected = "\
8629block0(%0: ptr):
8630 %1 = va_arg.f64 %0
8631 %2 = va_arg.f64 %0
8632 %3 = fadd %1, %2
8633 return %3
8634";
8635 assert_eq!(body(source), expected);
8636 }
8637
8638 #[test]
8639 fn one_that_reads_a_structure_answers_where_the_object_is() {
8640 let source = "\
8654struct s { int a; long b; };
8655long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.b; }
8656";
8657 let expected = "\
8658block0(%0: ptr):
8659 %1 = alloca, size 16, align 16
8660 %2 = va_object %0, size 16, align 8, in(int 8 at 0, int 8 at 8)
8661 memcpy %1, %2, size 16, align 8
8662 %3 = iconst.i64 8
8663 %4 = ptr_add %1, %3
8664 %5 = load.i64 %4, align 8, tbaa !1
8665 return %5
8666";
8667 assert_eq!(body(source), expected);
8668 }
8669
8670 #[test]
8674 fn the_classification_says_which_registers_the_object_arrived_in() {
8675 let source = "\
8676struct s { double a; double b; };
8677double f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a; }
8678";
8679 assert!(
8680 body(source)
8681 .contains("va_object %0, size 16, align 8, in(float f64 at 0, float f64 at 8)"),
8682 "{}",
8683 body(source)
8684 );
8685
8686 let big = "\
8687struct s { long a[4]; };
8688long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a[0]; }
8689";
8690 assert!(body(big).contains("va_object %0, size 32, align 8\n"), "{}", body(big));
8691 }
8692
8693 #[test]
8694 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
8695 let source = "\
8699int f(int c) {
8700 void *p = c ? &&one : &&two;
8701 goto *p;
8702one:
8703 return 1;
8704two:
8705 return 2;
8706}
8707";
8708 let expected = "\
8709block0(%0: i32):
8710 %1 = iconst.i32 0
8711 %2 = icmp ne %0, %1
8712 br_if %2, block1, block2
8713
8714block1:
8715 %3 = block_addr block3
8716 jump block4(%3)
8717
8718block2:
8719 %4 = block_addr block5
8720 jump block4(%4)
8721
8722block3:
8723 %5 = iconst.i32 1
8724 return %5
8725
8726block4(%6: ptr):
8727 indirect_br %6, block3, block5
8728
8729block5:
8730 %7 = iconst.i32 2
8731 return %7
8732";
8733 assert_eq!(body(source), expected);
8734 }
8735
8736 fn dispatch(labels: usize) -> String {
8739 let mask = labels - 1;
8740 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
8741 for index in 0..labels {
8742 source.push_str(&format!(" &&a{index},"));
8743 }
8744 source.push_str(" };\n\tint w = n, x = n + 1, y = n + 2, z = n + 3;\n");
8745 source.push_str(&format!("\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
8746 for index in 0..labels {
8747 let step = match index % 4 {
8748 0 => "w += x;",
8749 1 => "x += y;",
8750 2 => "y += z;",
8751 _ => "z += w;",
8752 };
8753 source.push_str(&format!("a{index}:\n\t{step}\n"));
8754 source.push_str("\tif (--n <= 0) return w + x + y + z;\n");
8755 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
8756 }
8757 source.push_str("}\n");
8758 source
8759 }
8760
8761 fn in_front_of_the_jump(text: &str) -> usize {
8763 let (before, _) = text.split_once("\tjmp\t*%").expect("a jump through a register");
8764 before.lines().rev().take_while(|line| line.starts_with("\tmov")).count()
8765 }
8766
8767 #[test]
8777 fn a_jump_through_a_register_writes_what_it_carries_and_not_the_whole_table() {
8778 let small = in_front_of_the_jump(&asm(&dispatch(4)));
8779 let large = in_front_of_the_jump(&asm(&dispatch(32)));
8780 assert_eq!(small, large, "eight times the labels and the same values in hand");
8781 assert!(large <= 8, "the values the loop keeps, and not a set of them per label: {large}");
8782 }
8783
8784 fn crowded(labels: usize) -> String {
8787 const VALUES: usize = 24;
8788 let mask = labels - 1;
8789 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
8790 for index in 0..labels {
8791 source.push_str(&format!(" &&a{index},"));
8792 }
8793 source.push_str(" };\n\t");
8794 for value in 0..VALUES {
8795 source.push_str(&format!("int v{value} = n + {value}; "));
8796 }
8797 let sum: Vec<String> = (0..VALUES).map(|value| format!("v{value}")).collect();
8798 source.push_str(&format!("\n\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
8799 for index in 0..labels {
8800 let (to, from) = (index % VALUES, (index + 1) % VALUES);
8801 source.push_str(&format!("a{index}:\n\tv{to} += v{from};\n"));
8802 source.push_str(&format!("\tif (--n <= 0) return {};\n", sum.join(" + ")));
8803 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
8804 }
8805 source.push_str("}\n");
8806 source
8807 }
8808
8809 fn the_frame(text: &str) -> u64 {
8811 text.lines()
8812 .find_map(|line| {
8813 let (size, _) = line.strip_prefix("\tsubq\t$")?.split_once(", %rsp")?;
8814 size.parse().ok()
8815 })
8816 .expect("a function that opens a frame")
8817 }
8818
8819 #[test]
8828 fn a_frame_holds_what_is_wanted_at_once_and_not_a_slot_for_every_label() {
8829 let small = the_frame(&asm(&crowded(16)));
8830 let large = the_frame(&asm(&crowded(64)));
8831 assert_eq!(small, large, "four times the labels and the same values: {small}, {large}");
8832 }
8833
8834 #[test]
8843 fn a_template_that_names_its_own_registers_gets_the_ones_it_named() {
8844 let source = "void save(void *nlr) {
8845 __asm volatile (
8846 \"movq %%rsp, 32(%%rdi) \\n\"
8847 \"movq %%rbx, 40(%%rdi) \\n\"
8848 \"movq %%r12, 48(%%rdi) \\n\"
8849 : : \"D\" (nlr) : \"memory\");
8850}
8851";
8852 let text = asm(source);
8853 assert!(text.contains("\tmovq\t%rsp, 32(%rdi)\n"), "{text}");
8854 assert!(text.contains("\tmovq\t%rbx, 40(%rdi)\n"), "{text}");
8855 assert!(text.contains("\tmovq\t%r12, 48(%rdi)\n"), "{text}");
8856 }
8857
8858 #[test]
8859 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
8860 let source = "void **next(void);
8863void f(void) { goto *next(); }
8864";
8865 let expected = "\
8866block0:
8867 %0 = call @next() : () -> ptr
8868 unreachable
8869";
8870 assert_eq!(body(source), expected);
8871 }
8872
8873 #[test]
8874 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
8875 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
8878 let expected = "\
8879block0:
8880 inline_asm.volatile \"mfence\", \"\", \"memory\"()
8881 return
8882";
8883 assert_eq!(body(source), expected);
8884 }
8885
8886 #[test]
8887 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
8888 let source = "\
8891int f(int x, int y) {
8892 int r;
8893 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
8894 return r + y;
8895}
8896";
8897 let expected = "\
8898block0(%0: i32, %1: i32):
8899 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
8900 %4 = add.nsw %2, %3
8901 return %4
8902";
8903 assert_eq!(body(source), expected);
8904 }
8905
8906 #[test]
8907 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
8908 let source = "\
8913struct pair { int a, b; };
8914int f(int x) {
8915 int slot = x;
8916 struct pair p = { x, x };
8917 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
8918 return slot + p.a;
8919}
8920";
8921 let text = body(source);
8922 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
8923 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
8924 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
8925 }
8926
8927 #[test]
8928 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
8929 let source = "\
8934int f(int x) {
8935 int r = 7;
8936 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
8937 return r;
8938away:
8939 return r;
8940}
8941";
8942 let expected = "\
8943block0(%0: i32):
8944 %1 = iconst.i32 7
8945 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
8946
8947block1:
8948 return %2
8949
8950block2:
8951 return %1
8952";
8953 assert_eq!(body(source), expected);
8954 }
8955
8956 #[test]
8957 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
8958 let mut opts = options();
8962 opts.emit = EmitKind::Ir;
8963 for (source, expected) in [
8964 (
8965 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
8966 "output operand constraint lacks '='",
8967 ),
8968 (
8969 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
8970 "lvalue required in 'asm' statement",
8971 ),
8972 (
8973 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
8974 "read-only variable 'g' used as 'asm' output",
8975 ),
8976 (
8977 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
8978 "input operand constraint contains '='",
8979 ),
8980 (
8981 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
8982 "memory input 0 is not directly addressable",
8983 ),
8984 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
8985 (
8986 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
8987 "duplicate asm operand name 'a'",
8988 ),
8989 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
8990 ] {
8991 let result = run(&opts, source);
8992 assert!(result.failed(), "expected this to be reported:\n{source}");
8993 assert!(
8994 result.messages.iter().any(|m| m.contains(expected)),
8995 "{expected}\n{:?}",
8996 result.messages
8997 );
8998 }
8999 }
9000
9001 #[test]
9006 fn an_asm_at_file_scope_that_is_directives_becomes_the_objects_it_defines() {
9007 let text = ir(concat!(
9008 "__asm__(\n",
9009 " \".section .rodata\\n\"\n",
9010 " \".globl first\\n\"\n",
9011 " \".balign 8\\n\"\n",
9012 " \"first:\\n\"\n",
9013 " \".long 1\\n\"\n",
9014 " \".long 2\\n\"\n",
9015 " \".globl last\\n\"\n",
9016 " \"last:\\n\"\n",
9017 " \".quad last - first\\n\");\n",
9018 "extern const int first[];\n",
9019 "extern const long last;\n",
9020 ));
9021 assert!(text.contains("global @first : bytes 8 = { i32 1, i32 2 }, align 8"), "{text}");
9022 assert!(text.contains("global @last : i64 = 8"), "{text}");
9023 }
9024
9025 #[test]
9029 fn a_name_an_asm_at_file_scope_defined_is_not_undone_by_a_declaration_of_it() {
9030 let text = ir(concat!(
9031 "__asm__(\".data\\n.globl counter\\ncounter:\\n.long 7\\n\");\n",
9032 "extern int counter;\n",
9033 "int read(void) { return counter; }\n",
9034 ));
9035 assert!(text.contains("global @counter : i32 = 7"), "{text}");
9036 }
9037
9038 #[test]
9043 fn bytes_under_no_label_at_file_scope_are_a_global_in_front_of_the_label() {
9044 let text = ir(concat!(
9045 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n662:\\n",
9046 ".pushsection .data.ignore\\n.byte 7\\n.popsection\\n.byte 662b - 661b\\n\");\n",
9047 "extern unsigned char stuff[];\n",
9048 "int read(void) { return stuff[0]; }\n",
9049 ));
9050 let under = text.find("global @.Lasm.0 : i8 = 41").expect(&text);
9051 let named = text.find("global @stuff : i8 = 42").expect(&text);
9052 assert!(under < named, "the bytes under no label come first: {text}");
9053 assert!(text.contains("global @.Lasm.1 : i8 = 7, align 1, linkage(internal), section"));
9054 let after = text.find("global @.Lasm.2 : i8 = 1").expect(&text);
9059 assert!(named < after, "{text}");
9060 }
9061
9062 #[test]
9067 fn a_distance_from_here_at_file_scope_is_a_hole_naming_the_global_it_measures_to() {
9068 let text = ir(concat!(
9069 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n",
9070 ".pushsection .data.ignore\\n.long 661b - .\\n.popsection\\n\");\n",
9071 "extern unsigned char stuff[];\n",
9072 "int read(void) { return stuff[0]; }\n",
9073 ));
9074 assert!(text.contains("global @.Lasm.1 : bytes 4 = { away.4 @stuff }"), "{text}");
9078 }
9079
9080 #[test]
9085 fn a_set_at_file_scope_is_a_second_name_for_what_it_names() {
9086 let text = ir(concat!(
9087 "void base(void) {}\n",
9088 "__asm__(\".weak one\\n.set one, base\");\n",
9089 "__asm__(\".globl two\\n.set two, base\");\n",
9090 "__asm__(\".set three, base\");\n",
9091 "void three(void) {}\n",
9092 ));
9093 assert!(text.contains("alias @one = @base, linkage(weak)"), "{text}");
9094 assert!(text.contains("alias @two = @base"), "{text}");
9095 assert!(!text.contains("alias @three"), "a definition of the name wins: {text}");
9096 assert!(text.contains("func @three"), "{text}");
9097 }
9098
9099 #[test]
9104 fn a_set_of_a_name_this_file_does_not_define_says_so() {
9105 let messages = errors("__asm__(\".set here, elsewhere\");\n");
9106 assert!(
9107 messages
9108 .iter()
9109 .any(|m| m.contains("'here' is aliased to undefined symbol 'elsewhere'")
9110 && m.contains("E0697")),
9111 "{messages:?}"
9112 );
9113 }
9114
9115 #[test]
9118 fn an_incbin_at_file_scope_is_the_bytes_of_the_file_it_names() {
9119 let mut opts = options();
9120 opts.emit = EmitKind::Ir;
9121 let mut fs = MemoryFileSystem::new();
9122 fs.insert(
9123 "/main.c",
9124 b"__asm__(\".data\\n.globl blob\\nblob:\\n.incbin \\\"seed\\\"\\n\");\n".to_vec(),
9125 );
9126 fs.insert("seed", b"hi".to_vec());
9127 let result = compile(&opts, "/main.c", &fs);
9128 assert_eq!(result.messages, Vec::<String>::new());
9129 let text = result.text();
9130 assert!(text.contains("global @blob : bytes 2 = { bytes \"hi\" }"), "{text}");
9131 }
9132
9133 #[test]
9136 fn an_incbin_naming_a_file_that_is_not_there_says_which_file() {
9137 let messages = errors("__asm__(\".data\\nb:\\n.incbin \\\"nowhere\\\"\\n\");\n");
9138 assert!(
9139 messages
9140 .iter()
9141 .any(|m| m.contains("cannot open 'nowhere' for reading") && m.contains("E0702")),
9142 "{messages:?}"
9143 );
9144 }
9145
9146 #[test]
9149 fn an_instruction_in_an_asm_at_file_scope_is_refused_rather_than_ignored() {
9150 for source in [
9151 "__asm__(\".text\\n.globl f\\nf:\\n ret\\n\");\n",
9152 "__asm__(\".data\\n.set alias, 4\\n\");\n",
9153 ] {
9154 let messages = errors(source);
9155 assert!(
9156 messages
9157 .iter()
9158 .any(|m| m.contains("not supported yet")
9159 && m.contains("in an `asm` at file scope")),
9160 "{source}\n{messages:?}"
9161 );
9162 }
9163 }
9164
9165 #[test]
9175 fn a_naked_function_is_its_own_prologue_and_its_own_ending() {
9176 let text = asm(concat!(
9177 "unsigned nlr_push_tail(void *nlr);\n",
9178 "__attribute__((naked)) unsigned nlr_push(void *nlr) {\n",
9179 " __asm volatile(\n",
9180 " \"movq (%rsp), %rax\\n\"\n",
9181 " \"movq %rax, 16(%rdi)\\n\"\n",
9182 " \"movq %rbx, 40(%rdi)\\n\"\n",
9183 " \"jmp nlr_push_tail\\n\");\n",
9184 "}\n",
9185 ));
9186 assert!(text.contains("\tmovq\t(%rsp), %rax\n"), "{text}");
9187 assert!(text.contains("\tjmp\tnlr_push_tail\n"), "{text}");
9188 assert!(text.contains("\tud2\n"), "{text}");
9189 assert!(!text.contains("\tpushq\t"), "nothing is saved in front of it: {text}");
9190 assert!(!text.contains("\tret\n"), "the jump is where it ends: {text}");
9191 }
9192
9193 #[test]
9196 fn what_a_function_without_a_prologue_cannot_be_given_is_refused() {
9197 let mut opts = options();
9198 opts.emit = EmitKind::Asm;
9199 for (source, why) in [
9200 (
9201 "__attribute__((naked)) void f(void) { volatile long a[8]; a[0] = 1; }\n",
9202 "bytes of frame",
9203 ),
9204 (
9205 "__attribute__((naked)) void f(int n) { char a[n]; __asm(\"nop\" ::\"r\"(a)); }\n",
9206 "has no prologue to point a frame pointer at it with",
9207 ),
9208 ("void elsewhere(void); void f(void) { __asm(\"jmp elsewhere\"); }\n", "jumps out of"),
9209 ] {
9210 let result = run(&opts, source);
9211 assert!(result.failed(), "expected this to be refused:\n{source}");
9212 assert!(
9213 result.messages.iter().any(|message| message.contains(why)),
9214 "{:?}",
9215 result.messages
9216 );
9217 }
9218 }
9219
9220 #[test]
9221 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
9222 let mut opts = options();
9223 opts.emit = EmitKind::Ir;
9224 for source in [
9225 "int f(int n) { void *p = &&out; if (n) goto *p; { int a[n]; out: return 1; } }\n",
9226 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
9227 ] {
9228 let result = run(&opts, source);
9229 assert!(result.failed(), "expected this to be reported:\n{source}");
9230 assert!(
9231 result.messages.iter().any(|m| m.contains("not supported yet")),
9232 "{:?}",
9233 result.messages
9234 );
9235 }
9236 }
9237
9238 fn round_trip(source: &str) -> (String, String) {
9240 let printed = ir(source);
9241 let mut opts = options();
9242 opts.emit = EmitKind::Ir;
9243 let mut fs = MemoryFileSystem::new();
9244 fs.insert("/main.ir", printed.clone().into_bytes());
9245 let result = compile_ir(&opts, "/main.ir", &fs);
9246 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
9247 (printed, result.text().to_owned())
9248 }
9249
9250 #[test]
9251 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
9252 let (printed, again) = round_trip(
9256 "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",
9257 );
9258 assert_eq!(printed, again);
9259 }
9260
9261 #[test]
9262 fn ir_that_is_not_ir_says_which_line_stopped_it() {
9263 let mut opts = options();
9264 opts.emit = EmitKind::Ir;
9265 let mut fs = MemoryFileSystem::new();
9266 let text = "\
9267; ModuleID = 'a.c'
9268; format 0
9269target triple = \"x86_64-unknown-linux-gnu\"
9270target datalayout = \"e-p:64:64-i64:64-S128\"
9271
9272func @f(), linkage(external) {
9273block0:
9274 frobnicate
9275}
9276";
9277 fs.insert("/main.ir", text.as_bytes().to_vec());
9278 let result = compile_ir(&opts, "/main.ir", &fs);
9279 assert!(result.failed());
9280 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
9281 }
9282
9283 #[test]
9284 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
9285 let mut opts = options();
9288 opts.emit = EmitKind::Ir;
9289 let mut fs = MemoryFileSystem::new();
9290 let text = "\
9291; ModuleID = 'a.c'
9292; format 0
9293target triple = \"x86_64-unknown-linux-gnu\"
9294target datalayout = \"e-p:64:64-i64:64-S128\"
9295
9296func @f(), linkage(external) {
9297block0:
9298 %0 = iconst.i32 1
9299 return %0
9300}
9301";
9302 fs.insert("/main.ir", text.as_bytes().to_vec());
9303 let result = compile_ir(&opts, "/main.ir", &fs);
9304 assert!(result.failed());
9305 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
9306 }
9307
9308 #[test]
9309 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
9310 let mut fs = MemoryFileSystem::new();
9312 fs.insert("/main.ir", Vec::new());
9313 let result = compile_ir(&options(), "/main.ir", &fs);
9314 assert!(result.failed());
9315 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
9316 }
9317
9318 #[test]
9319 fn the_printed_ir_reads_back_as_the_same_module() {
9320 let text = ir("\
9323struct point { int x, y; };
9324static const char greeting[] = \"hi\";
9325int table[4] = { 1, 2, 3 };
9326int puts(const char *);
9327double half(double x) { return x / 2.0; }
9328int f(int n) {
9329 int total = 0;
9330 for (int i = 0; i < n; i++) {
9331 if (i == 3) continue;
9332 total += table[i];
9333 }
9334 switch (n) {
9335 case 0: total = 1;
9336 case 1: total++; break;
9337 default: total = -total;
9338 }
9339 struct point p = { total, 1 };
9340 int *q = &p.y;
9341 puts(greeting);
9342 return p.x + *q;
9343}
9344int dispatch(int c) {
9345 void *p = c ? &&one : &&two;
9346 goto *p;
9347one:
9348 return 1;
9349two:
9350 return 2;
9351}
9352int assembly(int x, int *p) {
9353 int r;
9354 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
9355 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
9356 return r;
9357away:
9358 return 0;
9359}
9360");
9361 let mut names = Interner::new();
9362 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
9363 assert_eq!(rucc_ir::print(&module, &names), text);
9364 }
9365
9366 #[test]
9367 fn what_save_temps_keeps_is_the_text_that_was_compiled_and_the_assembly_that_was_assembled() {
9368 let mut opts = options();
9372 opts.emit = EmitKind::Object;
9373 opts.save_temps = rucc_session::SaveTemps::Object;
9374 let result = run(&opts, "#define N 2\nint a[N];\n");
9375 assert_eq!(result.messages, Vec::<String>::new());
9376 let text = result.temps.preprocessed.expect("the preprocessed text");
9377 assert!(text.contains("int a[2];"), "{text}");
9378 assert!(text.starts_with("# 1 \"/main.c\""), "{text}");
9379 let asm = result.temps.assembly.expect("the assembly");
9380 assert!(asm.contains("a:"), "{asm}");
9381 assert!(matches!(result.artifact, Artifact::Object { .. }), "{:?}", result.artifact);
9382 }
9383
9384 #[test]
9385 fn nothing_is_kept_unless_the_flag_asked_for_it() {
9386 let mut opts = options();
9389 opts.emit = EmitKind::Object;
9390 assert_eq!(run(&opts, "int a;\n").temps, Temps::default());
9391 }
9392
9393 #[test]
9394 fn a_compilation_that_stops_before_the_back_end_keeps_the_text_and_no_assembly() {
9395 let mut opts = options();
9398 opts.emit = EmitKind::Ir;
9399 opts.save_temps = rucc_session::SaveTemps::Cwd;
9400 let result = run(&opts, "int a;\n");
9401 assert!(result.temps.preprocessed.is_some());
9402 assert_eq!(result.temps.assembly, None);
9403 }
9404
9405 fn span(from: u64, len: u64, held: rucc_debug::Held) -> rucc_debug::Span {
9407 rucc_debug::Span { from, len, held }
9408 }
9409
9410 #[test]
9411 fn two_stretches_that_meet_and_agree_come_out_as_one() {
9412 let one = span(0, 4, rucc_debug::Held::Reg(3));
9413 let two = span(4, 4, rucc_debug::Held::Reg(3));
9414 assert_eq!(settle(vec![two, one]), vec![span(0, 8, rucc_debug::Held::Reg(3))]);
9415 }
9416
9417 #[test]
9418 fn a_stretch_another_starts_inside_and_disagrees_with_ends_where_the_other_starts() {
9419 let one = span(0, 8, rucc_debug::Held::Reg(3));
9420 let two = span(4, 8, rucc_debug::Held::Reg(4));
9421 let settled = settle(vec![one, two]);
9424 assert_eq!(
9425 settled,
9426 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 8, rucc_debug::Held::Reg(4))]
9427 );
9428 }
9429
9430 #[test]
9431 fn a_stretch_cut_by_one_that_ends_first_does_not_come_back_after_it() {
9432 let one = span(0, 16, rucc_debug::Held::Reg(3));
9435 let two = span(4, 4, rucc_debug::Held::Reg(4));
9436 assert_eq!(
9437 settle(vec![one, two]),
9438 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 4, rucc_debug::Held::Reg(4))]
9439 );
9440 }
9441
9442 #[test]
9443 fn a_stretch_inside_another_that_agrees_with_it_cuts_nothing() {
9444 let one = span(0, 16, rucc_debug::Held::Reg(3));
9445 let two = span(4, 4, rucc_debug::Held::Reg(3));
9446 assert_eq!(settle(vec![one, two]), vec![span(0, 16, rucc_debug::Held::Reg(3))]);
9447 }
9448
9449 #[test]
9450 fn a_stretch_two_others_disagree_over_the_whole_of_says_nothing_at_all() {
9451 let one = span(0, 8, rucc_debug::Held::Reg(3));
9452 let two = span(0, 8, rucc_debug::Held::Frame(-16));
9453 assert_eq!(settle(vec![one, two]), Vec::new());
9454 }
9455
9456 #[test]
9457 fn stretches_with_a_gap_between_them_keep_the_gap() {
9458 let one = span(0, 4, rucc_debug::Held::Reg(3));
9459 let two = span(16, 4, rucc_debug::Held::Reg(3));
9460 assert_eq!(settle(vec![one, two]), vec![one, two]);
9461 }
9462
9463 fn extent(len: usize) -> rucc_object::Extent {
9465 rucc_object::Extent {
9466 name: "f".to_owned(),
9467 start: 0,
9468 len,
9469 align: 1,
9470 binding: rucc_object::Binding::Global,
9471 visibility: rucc_object::Visibility::Default,
9472 patch: None,
9473 }
9474 }
9475
9476 fn row(at: usize, lo: u32, hi: u32) -> rucc_asm::Row {
9478 let span = Span::new(lo, hi);
9479 rucc_asm::Row { at, span, inst: None }
9480 }
9481
9482 #[test]
9483 fn a_row_ends_where_the_next_address_begins() {
9484 let rows = [row(0, 0, 1), row(4, 1, 2), row(10, 2, 3)];
9485 assert_eq!(ends(&extent(16), &rows), vec![4, 10, 16]);
9486 }
9487
9488 #[test]
9489 fn rows_sharing_an_address_all_end_where_the_next_address_begins() {
9490 let rows = [row(0, 0, 1), row(4, 1, 2), row(4, 2, 3), row(4, 3, 4)];
9493 assert_eq!(ends(&extent(12), &rows), vec![4, 12, 12, 12]);
9494 }
9495
9496 #[test]
9497 fn the_rows_of_a_scope_that_are_next_to_each_other_come_out_as_one_stretch() {
9498 let rows = [row(0, 0, 4), row(4, 10, 14), row(8, 14, 18), row(12, 40, 44)];
9499 let ends = ends(&extent(16), &rows);
9500 let scope = Span::new(8, 20);
9501 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 8 }]);
9502 }
9503
9504 #[test]
9505 fn a_scope_the_back_end_split_in_two_comes_out_as_two_stretches() {
9506 let rows = [row(0, 10, 14), row(4, 40, 44), row(8, 14, 18)];
9507 let ends = ends(&extent(12), &rows);
9508 let scope = Span::new(8, 20);
9509 let over = spread(scope, &ends, &rows);
9510 assert_eq!(
9511 over,
9512 vec![rucc_debug::Reach { from: 0, len: 4 }, rucc_debug::Reach { from: 8, len: 4 }]
9513 );
9514 }
9515
9516 #[test]
9517 fn a_row_with_no_source_of_its_own_belongs_to_no_scope() {
9518 let rows = [rucc_asm::Row { at: 0, span: Span::DUMMY, inst: None }, row(4, 10, 14)];
9520 let ends = ends(&extent(8), &rows);
9521 let scope = Span::new(0, 20);
9522 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 4 }]);
9523 }
9524
9525 fn scope(parent: Option<usize>, lo: u32, hi: u32) -> crate::shapes::Scope {
9527 let span = Span::new(lo, hi);
9528 crate::shapes::Scope { parent, span }
9529 }
9530
9531 #[test]
9532 fn a_function_gets_the_scopes_its_own_locals_are_in_and_nothing_else() {
9533 let scopes = [scope(None, 0, 10), scope(None, 20, 30), scope(Some(1), 22, 26)];
9535 let rows = [row(0, 22, 24), row(4, 26, 28)];
9536 let (out, at) = nests(&[Some(2)], &scopes, &extent(8), &rows);
9537 assert_eq!(at.get(&1), Some(&0));
9540 assert_eq!(at.get(&2), Some(&1));
9541 assert_eq!(at.get(&0), None);
9542 assert_eq!(out.len(), 2);
9543 assert_eq!(out[0].parent, None);
9544 assert_eq!(out[1].parent, Some(0));
9545 assert_eq!(out[0].over, vec![rucc_debug::Reach { from: 0, len: 8 }]);
9546 assert_eq!(out[1].over, vec![rucc_debug::Reach { from: 0, len: 4 }]);
9547 }
9548
9549 #[test]
9550 fn a_local_written_straight_into_the_body_pulls_no_scope_in() {
9551 let scopes = [scope(None, 20, 30)];
9552 let rows = [row(0, 22, 24)];
9553 let (out, at) = nests(&[None], &scopes, &extent(4), &rows);
9554 assert_eq!(out, Vec::new());
9555 assert!(at.is_empty());
9556 }
9557
9558 #[test]
9559 fn a_scope_whose_code_all_went_away_is_still_one_of_the_functions_scopes() {
9560 let scopes = [scope(None, 20, 30)];
9564 let rows = [row(0, 40, 44)];
9565 let (out, at) = nests(&[Some(0)], &scopes, &extent(4), &rows);
9566 assert_eq!(at.get(&0), Some(&0));
9567 assert_eq!(out.len(), 1);
9568 assert_eq!(out[0].over, Vec::new());
9569 }
9570}