1use std::collections::HashMap;
14use std::path::Path;
15
16use rucc_base::Interner;
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 parsed = rucc_parse::parse(
259 &tokens,
260 rucc_parse::Context {
261 interner: &sess.interner,
262 std: opts.std,
263 gnu: opts.gnu_extensions,
264 pedantic: opts.pedantic,
265 error_limit: opts.error_limit as usize,
266 },
267 );
268 let parse_failed = parsed.diagnostics.iter().any(|d| d.severity.is_fatal());
269 diagnostics.extend(parsed.diagnostics);
270
271 let mut artifact = Artifact::Nothing;
272 let mut instrumented = Instrumented::default();
275 if !parse_failed {
276 let mut checker = Checker::new(
277 &parsed.ast,
278 CheckContext {
279 names: &sess.interner,
280 target: &sess.target,
281 std: opts.std,
282 gnu: opts.gnu_extensions,
283 pedantic: opts.pedantic,
284 permissive: opts.permissive,
285 gnu89_inline: opts.gnu89_inline,
286 error_limit: opts.error_limit as usize,
287 builtins: opts.builtins && opts.hosted,
290 no_builtin: &opts.no_builtin,
291 short_enums: opts.short_enums,
292 ms_extensions: sess.ms_extensions(),
293 trapping_math: opts.trapping_math,
294 },
295 );
296 checker.check_unit();
297 let checked = checker.finish();
298 if !checked.failed() {
299 match opts.emit {
300 EmitKind::Tast => {
301 artifact = Artifact::Text(rucc_sema::print(
302 &checked.tast,
303 &checked.types,
304 &sess.interner,
305 ));
306 }
307 EmitKind::TypeGranules => {
311 artifact = Artifact::Text(rucc_types::granule_report(
312 &checked.types,
313 &sess.interner,
314 &sess.target,
315 ));
316 }
317 EmitKind::Ir
318 | EmitKind::MirFinal
319 | EmitKind::Asm
320 | EmitKind::Object
321 | EmitKind::Archive
322 | EmitKind::Executable
323 | EmitKind::SafetySummary => {
324 let mut read = |named: &str| {
329 fs.read(Path::new(named))
330 .map(|bytes| bytes.as_slice().to_vec())
331 .map_err(|why| why.to_string())
332 };
333 let meaning = if opts.debug_info {
339 crate::shapes::collect(
340 &checked.tast,
341 &checked.types,
342 &sess.target,
343 &sess.interner,
344 &sess.sources,
345 )
346 } else {
347 crate::shapes::Meaning::default()
348 };
349 let mut lowered = rucc_lower::lower(
350 crate::phase::source_name(name),
351 rucc_lower::Context {
352 tast: &checked.tast,
353 types: &checked.types,
354 target: &sess.target,
355 names: &mut sess.interner,
356 visibility: match opts.visibility {
357 Visibility::Default => IrVisibility::Default,
358 Visibility::Hidden => IrVisibility::Hidden,
359 Visibility::Protected => IrVisibility::Protected,
360 },
361 protector: match opts.protector {
362 Protector::None => LowerProtector::None,
363 Protector::Buffers => LowerProtector::Buffers,
364 Protector::Strong => LowerProtector::Strong,
365 Protector::All => LowerProtector::All,
366 },
367 wrapping: rucc_lower::Wrapping {
368 signed: opts.wrapping.signed,
369 pointer: opts.wrapping.pointer,
370 trap: opts.wrapping.trap,
371 },
372 aliasing: opts.strict_aliasing,
373 padding: opts.padding == Padding::Ignored,
374 contract: match opts.fp_contract {
375 Contract::Off => FpContract::Off,
376 Contract::On => FpContract::On,
377 Contract::Fast => FpContract::Fast,
378 },
379 align: opts.align_functions,
380 read: &mut read,
381 },
382 );
383 let failed = lowered.diagnostics.iter().any(|d| d.severity.is_fatal());
387 if !failed {
388 if let Err(errors) = rucc_ir::verify(&lowered.module, &sess.interner) {
393 for error in errors {
394 diagnostics.push(internal(&format!("invalid IR, {error}")));
395 }
396 } else if let Err(complaints) =
397 instrument(&mut lowered.module, &mut sess.interner, opts)
398 .map(|done| instrumented = done)
399 {
400 diagnostics.extend(complaints);
401 } else if let Err(complaints) = optimize(
402 &mut lowered.module,
403 &mut sess.interner,
404 &sess.target,
405 opts,
406 name,
407 &mut dumps,
408 &mut remarks,
409 ) {
410 diagnostics.extend(complaints);
411 } else if opts.emit == EmitKind::SafetySummary {
412 artifact = Artifact::Text(
417 rucc_safety::summarize(
418 &lowered.module,
419 &sess.interner,
420 name,
421 opts.safety.as_str(),
422 instrumented.checks,
423 instrumented.interposed,
424 instrumented.crossings,
425 )
426 .render(),
427 );
428 } else if opts.emit == EmitKind::Ir {
429 artifact =
434 Artifact::Text(rucc_ir::print(&lowered.module, &sess.interner));
435 } else {
436 match generate(
439 &mut lowered.module,
440 &mut sess.interner,
441 &sess.target,
442 opts,
443 &mut Recording {
444 fired: &mut fired,
445 pressure: &mut pressure,
446 lowerings: &mut lowerings,
447 },
448 &mut temps.assembly,
449 Origin { map: &sess.sources, name, meaning: &meaning },
450 ) {
451 Ok(made) => artifact = made,
452 Err(complaints) => diagnostics.extend(complaints),
453 }
454 }
455 }
456 diagnostics.extend(lowered.diagnostics);
457 }
458 _ => {}
459 }
460 }
461 diagnostics.extend(checked.diagnostics);
462 }
463
464 let mut messages = Vec::with_capacity(diagnostics.len());
465 let mut errors = 0;
466 for diag in &diagnostics {
467 if rucc_diag::dropped(diag, &sess.sources, opts.warnings, opts.system_header_warnings) {
473 continue;
474 }
475 if diag.severity.is_fatal()
476 || (diag.severity == Severity::Warning && opts.warnings_are_errors)
477 {
478 errors += 1;
479 }
480 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
481 }
482 if errors > 0 {
483 artifact = Artifact::Nothing;
485 }
486 Compiled { artifact, messages, errors, fired, pressure, lowerings, dumps, remarks, deps, temps }
489}
490
491#[must_use]
501pub fn compile_ir(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
502 let mut sess = Session::new(opts.clone());
503 if opts.emit != EmitKind::Ir {
504 return failure(format!(
505 "{name}: an input of IR can only be emitted as IR, and `--emit={}` asks for what \
506 the C in front of it became",
507 opts.emit.as_str()
508 ));
509 }
510 let bytes = match fs.read(Path::new(name)) {
511 Ok(bytes) => bytes,
512 Err(e) => return failure(format!("{name}: {e}")),
513 };
514 let Ok(text) = std::str::from_utf8(bytes.as_slice()) else {
515 return failure(format!("{name}: this is not text, so it is not IR"));
516 };
517
518 let module = match rucc_ir::parse(text, &mut sess.interner) {
519 Ok(module) => module,
520 Err(error) => {
521 return failure(format!("{name}:{}: {}", error.line, error.message));
522 }
523 };
524 let mut diagnostics: Vec<Diagnostic> = Vec::new();
525 if let Err(errors) = rucc_ir::verify(&module, &sess.interner) {
526 for error in errors {
527 diagnostics.push(invalid(&format!("invalid IR, {error}")));
528 }
529 }
530 let mut messages = Vec::with_capacity(diagnostics.len());
531 for diag in &diagnostics {
532 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
533 }
534 let errors = u32::try_from(messages.len()).unwrap_or(u32::MAX);
535 let artifact = if errors > 0 {
536 Artifact::Nothing
537 } else {
538 Artifact::Text(rucc_ir::print(&module, &sess.interner))
539 };
540 Compiled {
542 artifact,
543 messages,
544 errors,
545 fired: Fired::new(),
546 pressure: Pressure::new(),
547 lowerings: Lowerings::new(),
548 dumps: Vec::new(),
549 remarks: String::new(),
550 deps: Vec::new(),
551 temps: Temps::default(),
552 }
553}
554
555fn instrument(
578 module: &mut rucc_ir::Module,
579 names: &mut Interner,
580 opts: &Options,
581) -> Result<Instrumented, Vec<Diagnostic>> {
582 if !opts.safety.instruments() {
583 return Ok(Instrumented::default());
584 }
585 let mut checks = rucc_safety::run(module, opts.subobject, opts.promise, opts.races);
586 checks.freed = rucc_safety::ending::checks(module, names);
594 let interposed = rucc_safety::redirect(module, names);
599 let crossings = rucc_safety::witness(module, names);
602 match rucc_ir::verify(module, names) {
603 Ok(()) => Ok(Instrumented { checks, interposed, crossings }),
604 Err(errors) => Err(errors
605 .iter()
606 .map(|e| internal(&format!("invalid IR after check insertion, {e}")))
607 .collect()),
608 }
609}
610
611#[derive(Clone, Copy, Debug, Default)]
617struct Instrumented {
618 checks: rucc_safety::Counts,
620 interposed: usize,
622 crossings: rucc_safety::Sites,
624}
625
626fn optimize(
638 module: &mut rucc_ir::Module,
639 names: &mut Interner,
640 target: &TargetInfo,
641 opts: &Options,
642 file: &str,
643 dumps: &mut Vec<rucc_opt::Dump>,
644 remarks: &mut String,
645) -> Result<(), Vec<Diagnostic>> {
646 let mut settings = rucc_opt::Options::for_level(opts.opt_level);
647 settings.interposition = match opts.interposition {
653 true => replaceable(target, opts),
654 false => IrPic::Executable,
655 };
656 settings.toggles.clone_from(&opts.passes);
657 settings.builtins = opts.builtins && opts.hosted;
662 settings.no_builtin.clone_from(&opts.no_builtin);
663 settings.fuel = opts.pass_fuel.iter().cloned().collect();
664 settings.global_fuel = opts.pass_fuel_global;
665 settings.verify |= opts.verify_each;
666 for (on, spec) in &opts.pass_gates {
667 if let Err(why) = settings.gates.add(*on, spec) {
670 return Err(vec![internal(&why)]);
671 }
672 }
673 for spec in &opts.dump_ir {
674 if let Err(why) = settings.dumps.add(spec) {
677 return Err(vec![internal(&why)]);
678 }
679 }
680 let mut wants = rucc_opt::Wants::none();
681 for spec in &opts.opt_info {
682 if let Err(why) = wants.add(spec) {
685 return Err(vec![internal(&why)]);
686 }
687 }
688 let report = rucc_opt::run(module, names, &settings);
689 remarks.push_str(&rucc_opt::optinfo::render(file, &report, names, wants));
690 dumps.extend(report.dumps);
691 match report.broke.is_empty() {
692 true => Ok(()),
693 false => Err(report.broke.iter().map(|why| internal(why)).collect()),
694 }
695}
696
697fn replaceable(target: &TargetInfo, opts: &Options) -> IrPic {
731 match (target.tuple.os().object_format(), opts.pic) {
732 (Some(ObjectFormat::Elf), Pic::Library) => IrPic::Library,
733 _ => IrPic::Executable,
734 }
735}
736
737#[derive(Clone, Copy)]
744struct Origin<'a> {
745 map: &'a SourceMap,
747 name: &'a str,
749 meaning: &'a crate::shapes::Meaning,
751}
752
753fn generate(
754 module: &mut rucc_ir::Module,
755 names: &mut Interner,
756 target: &TargetInfo,
757 opts: &Options,
758 recording: &mut Recording<'_>,
759 assembly: &mut Option<String>,
760 origin: Origin<'_>,
761) -> Result<Artifact, Vec<Diagnostic>> {
762 let Some(machine) = Machine::for_target(target) else {
763 return Err(vec![unsupported(&format!(
764 "there is no back end for {} in this compiler yet, so there is nothing to generate",
765 target.tuple
766 ))]);
767 };
768 if opts.protector != Protector::None && machine.conv.guard.is_none() {
773 return Err(vec![unsupported(&format!(
774 "{} is not supported for {} yet, because the stack protector on that target is not \
775 the one this compiler writes",
776 opts.protector, target.tuple
777 ))]);
778 }
779 if opts.control.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
785 return Err(vec![unsupported(&format!(
786 "-fcf-protection={} is not supported for {} yet, because what says a file was built \
787 for it there is not the note this compiler writes",
788 opts.control, target.tuple
789 ))]);
790 }
791 let profile = match machine.conv.trace {
797 Some(trace) => opts.profile.then(|| opts.hook.early(trace.fentry)),
798 None if opts.profile => {
799 return Err(vec![unsupported(&format!(
800 "-pg is not supported for {} yet, because the profiler's hook on that target is \
801 not the one this compiler calls",
802 target.tuple
803 ))]);
804 }
805 None => None,
806 };
807 if opts.patchable.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
812 return Err(vec![unsupported(&format!(
813 "-fpatchable-function-entry= is not supported for {} yet, because what records where \
814 the room is there is not the section this compiler writes",
815 target.tuple
816 ))]);
817 }
818 let flags = pipeline::Flags {
819 frame_pointer: opts.frame_pointer,
820 red_zone: opts.red_zone,
821 stack_clash: opts.stack_clash,
822 landing: opts.control.branch(),
823 profile: match profile {
824 None => pipeline::Profile::No,
825 Some(true) => pipeline::Profile::Early,
826 Some(false) => pipeline::Profile::Late,
827 },
828 patch: pipeline::Room { after: opts.patchable.after(), before: opts.patchable.before },
829 reorder: opts.reorder_blocks.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
836 reuse: opts.stack_reuse.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
841 schedule: opts.schedule_insns.unwrap_or_else(|| opts.opt_level.schedules()),
846 accurate: opts.cycle_accurate_model,
848 verify: opts.verify_each,
851 goal: Goal::for_size(opts.opt_level.is_size()),
856 };
857
858 if opts.safety.instruments() {
867 rucc_opt::heap::annotate(module, names);
877 rucc_safety::handover::arrange(module);
884 rucc_safety::lower(module, names);
885 if let Err(errors) = rucc_ir::verify(module, names) {
886 return Err(errors
887 .iter()
888 .map(|e| internal(&format!("invalid IR after check lowering, {e}")))
889 .collect());
890 }
891 }
892
893 let elsewhere = Elsewhere::of(module, replaceable(target, opts), target.object_format);
902
903 let mut funcs = Vec::new();
904 let mut complaints = Vec::new();
905 for id in module.funcs() {
906 if module[id].is_declaration() {
907 continue;
908 }
909 match pipeline::compile_recording(
910 &mut module[id],
911 names,
912 &machine,
913 &elsewhere,
914 flags,
915 recording,
916 ) {
917 Ok(func) => funcs.push(func),
918 Err(why) => {
919 let name = names.resolve(module[id].name).to_owned();
920 let span = why.inst().map_or(Span::DUMMY, |inst| module[id].span(inst));
923 let said = format!("cannot generate code for '{name}': {why}");
924 complaints.push(unsupported_at(&said, span));
925 }
926 }
927 }
928 if !complaints.is_empty() {
929 return Err(complaints);
930 }
931 let (globals, aliases) = match opts.emit {
937 EmitKind::Asm | EmitKind::Object | EmitKind::Archive | EmitKind::Executable => (
938 rucc_asm::globals(module, names, target.object_format).map_err(refused)?,
939 rucc_asm::aliases(module, names).map_err(refused)?,
940 ),
941 _ => (rucc_asm::Globals::default(), Vec::new()),
942 };
943 let unwind = opts.unwinds();
947 match opts.emit {
948 EmitKind::Asm => {
949 rucc_asm::print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
950 .map(Artifact::Text)
951 .map_err(refused)
952 }
953 EmitKind::Object | EmitKind::Archive | EmitKind::Executable => {
957 if opts.save_temps.wanted() {
958 let listing = rucc_asm::print(
959 &funcs,
960 &globals,
961 &aliases,
962 names,
963 target,
964 unwind,
965 output(opts, target),
966 );
967 *assembly = Some(listing.map_err(refused)?);
968 }
969 if rucc_asm::kept(&funcs, names) {
975 if opts.debug_info {
976 return Err(vec![unsupported(
977 "debug information for a unit with an `asm` template kept as text",
978 )]);
979 }
980 let listing = rucc_asm::print(
981 &funcs,
982 &globals,
983 &aliases,
984 names,
985 target,
986 unwind,
987 output(opts, target),
988 )
989 .map_err(refused)?;
990 let read = rucc_asm::read(&listing).map_err(|trouble| {
991 vec![unsupported(&format!(
992 "an `asm` template kept as text, whose listing the assembler stopped at on \
993 line {}: {}",
994 trouble.line, trouble.why
995 ))]
996 })?;
997 let defines = rucc_object::assembled_defines(&read);
998 let bytes =
999 rucc_object::assembled(&read, &TargetInfo::new(opts.target)).map_err(wrote)?;
1000 return Ok(Artifact::Object { bytes, defines });
1001 }
1002 let assembled = rucc_asm::assemble(&funcs, names, target, unwind, opts.debug_info)
1003 .map_err(refused)?;
1004 let data = globals.image();
1005 let info = if opts.debug_info {
1009 describe(&assembled, &data, &funcs, origin, opts, target)
1010 .map_err(|why| vec![internal(&why)])?
1011 } else {
1012 rucc_object::Info::default()
1013 };
1014 let text = assembled.text;
1015 let bytes =
1018 rucc_object::write(&text, &data, &aliases, target, output(opts, target), &info)
1019 .map_err(wrote)?;
1020 let defines = rucc_object::defines(&text, &data, &aliases, target).map_err(wrote)?;
1025 Ok(Artifact::Object { bytes, defines })
1026 }
1027 _ => Ok(Artifact::Text(rucc_mir::print(&funcs, names, target.regs))),
1028 }
1029}
1030
1031fn describe(
1050 assembled: &rucc_asm::Assembled,
1051 data: &rucc_object::Data,
1052 machine: &[rucc_mir::Func],
1053 origin: Origin<'_>,
1054 opts: &Options,
1055 target: &TargetInfo,
1056) -> Result<rucc_object::Info, String> {
1057 let rucc_asm::Assembled { text, lines, frames } = assembled;
1058 let rewrite = |path: &str| opts.prefix_map.debug.apply(path).into_owned();
1059 let mut files: Vec<String> = Vec::new();
1064 let mut funcs = Vec::with_capacity(text.funcs.len());
1065 for ((extent, rows), built) in text.funcs.iter().zip(lines).zip(machine) {
1066 let mut out: Vec<rucc_debug::Row> = Vec::with_capacity(rows.len());
1067 for row in rows {
1068 if row.span.is_dummy() {
1069 continue;
1070 }
1071 let Some(at) = origin.map.presumed(row.span.lo) else {
1072 continue;
1073 };
1074 let which = interned(&mut files, rewrite(at.name));
1075 let place = rucc_debug::Row {
1076 at: row.at as u64,
1077 file: which,
1078 line: at.line,
1079 column: at.column,
1080 };
1081 match out.last() {
1091 Some(last) if last.at == place.at => {}
1092 _ => out.push(place),
1093 }
1094 }
1095 if let Some(first) = out.first_mut() {
1102 first.at = 0;
1103 }
1104 let known = origin.meaning.funcs.get(&extent.name);
1110 let decl = known.map(|known| rucc_debug::Place {
1111 file: interned(&mut files, rewrite(&known.file)),
1112 line: known.line,
1113 });
1114 let mut sig = known.and_then(|known| known.sig.clone());
1123 let mut placed: Vec<(u32, i32)> = built.locals.clone();
1124 let mut spots = stretches(extent, rows, built, target);
1125 for &decl in &built.sharing {
1130 if !spots.iter().any(|(at, _)| *at == decl) {
1131 spots.push((decl, Vec::new()));
1132 }
1133 }
1134 if let (Some(sig), Some(known)) = (sig.as_mut(), known) {
1135 for (param, decl) in sig.params.iter_mut().zip(&known.params) {
1136 let Some(decl) = *decl else { continue };
1137 if let Some(which) = placed.iter().position(|&(at, _)| at == decl) {
1138 let at = rucc_debug::Held::Frame(i64::from(placed.remove(which).1));
1139 param.spot = Some(rucc_debug::Spot::Always(at));
1140 continue;
1141 }
1142 let Some(which) = spots.iter().position(|(at, _)| *at == decl) else { continue };
1146 param.spot = Some(rucc_debug::Spot::Over(spots.remove(which).1));
1147 }
1148 }
1149 let mut locals = Vec::with_capacity(placed.len() + spots.len());
1153 let mut wants: Vec<Option<usize>> = Vec::with_capacity(locals.capacity());
1157 for (decl, at) in placed {
1158 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1159 wants.push(named.scope);
1160 locals.push(rucc_debug::Local {
1161 name: named.name.clone(),
1162 ty: named.ty,
1163 decl: Some(rucc_debug::Place {
1164 file: interned(&mut files, rewrite(&named.file)),
1165 line: named.line,
1166 }),
1167 spot: rucc_debug::Spot::Always(rucc_debug::Held::Frame(i64::from(at))),
1168 scope: None,
1169 });
1170 }
1171 spots.sort_by_key(|(decl, _)| *decl);
1175 for (decl, spans) in spots {
1176 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1177 wants.push(named.scope);
1178 locals.push(rucc_debug::Local {
1179 name: named.name.clone(),
1180 ty: named.ty,
1181 decl: Some(rucc_debug::Place {
1182 file: interned(&mut files, rewrite(&named.file)),
1183 line: named.line,
1184 }),
1185 spot: rucc_debug::Spot::Over(spans),
1186 scope: None,
1187 });
1188 }
1189 let (scopes, at) = nests(&wants, &origin.meaning.scopes, extent, rows);
1194 for (local, want) in locals.iter_mut().zip(&wants) {
1195 local.scope = want.and_then(|want| at.get(&want).copied());
1196 }
1197 funcs.push(rucc_debug::Function {
1198 name: extent.name.clone(),
1199 len: extent.len as u64,
1200 rows: out,
1201 decl,
1202 sig,
1203 external: known.is_some_and(|known| known.external),
1204 locals,
1205 scopes,
1206 });
1207 }
1208 let mut globals = Vec::new();
1215 for object in &data.objects {
1216 let Some(held) = origin.meaning.objects.get(&object.name) else { continue };
1217 globals.push(rucc_debug::Global {
1218 name: object.name.clone(),
1219 ty: held.ty,
1220 decl: Some(rucc_debug::Place {
1221 file: interned(&mut files, rewrite(&held.file)),
1222 line: held.line,
1223 }),
1224 external: held.external,
1225 });
1226 }
1227 let unit = rucc_debug::Unit {
1228 name: rewrite(origin.name),
1229 dir: rewrite(opts.working_dir.as_deref().unwrap_or(".")),
1232 producer: format!("rucc {}", crate::VERSION),
1233 files,
1234 types: origin.meaning.types.clone(),
1235 funcs,
1236 globals,
1237 pointer: u8::try_from(target.pointer_width / 8).unwrap_or(8),
1238 frames: opts.unwinds() || frames.is_some(),
1243 };
1244 let mut info = rucc_debug::write(&unit).map_err(|why| why.to_string())?;
1245 info.chunks.extend(frames.clone());
1246 Ok(info)
1247}
1248
1249fn stretches(
1260 extent: &rucc_object::Extent,
1261 rows: &[rucc_asm::Row],
1262 built: &rucc_mir::Func,
1263 target: &TargetInfo,
1264) -> Vec<(u32, Vec<rucc_debug::Span>)> {
1265 let (false, Some(regs)) = (built.kept.is_empty(), target.call_regs) else {
1268 return Vec::new();
1269 };
1270 let ends = ends(extent, rows);
1271 let mut bounds = vec![None; built.inst_count()];
1272 for (which, row) in rows.iter().enumerate() {
1273 let Some(inst) = row.inst else { continue };
1274 bounds[inst.index()] = Some((row.at as u64, ends[which]));
1275 }
1276 let mut spots: Vec<(u32, Vec<rucc_debug::Span>)> = Vec::new();
1277 for kept in &built.kept {
1278 let (Some((from, _)), Some((_, to))) = (bounds[kept.from.index()], bounds[kept.to.index()])
1279 else {
1280 continue;
1281 };
1282 if to <= from {
1283 continue;
1284 }
1285 let held = match kept.at {
1286 rucc_mir::Where::Reg { reg, class } => match regs.dwarf(class, reg) {
1289 Some(number) => rucc_debug::Held::Reg(number),
1290 None => continue,
1291 },
1292 rucc_mir::Where::Frame(at) => rucc_debug::Held::Frame(i64::from(at)),
1293 };
1294 let span = rucc_debug::Span { from, len: to - from, held };
1295 match spots.iter_mut().find(|(decl, _)| *decl == kept.decl) {
1296 Some((_, spans)) => spans.push(span),
1297 None => spots.push((kept.decl, vec![span])),
1298 }
1299 }
1300 for (_, spans) in &mut spots {
1301 *spans = settle(std::mem::take(spans));
1302 }
1303 spots.retain(|(_, spans)| !spans.is_empty());
1304 spots
1305}
1306
1307fn ends(extent: &rucc_object::Extent, rows: &[rucc_asm::Row]) -> Vec<u64> {
1317 let mut out = vec![extent.len as u64; rows.len()];
1318 let mut next = extent.len as u64;
1319 for which in (0..rows.len()).rev() {
1320 let at = rows[which].at as u64;
1321 out[which] = match next > at {
1324 true => next,
1325 false => out.get(which + 1).copied().unwrap_or(extent.len as u64),
1326 };
1327 next = next.min(at);
1328 }
1329 out
1330}
1331
1332fn nests(
1348 wants: &[Option<usize>],
1349 scopes: &[crate::shapes::Scope],
1350 extent: &rucc_object::Extent,
1351 rows: &[rucc_asm::Row],
1352) -> (Vec<rucc_debug::Scope>, HashMap<usize, usize>) {
1353 let mut needed: Vec<usize> = Vec::new();
1354 for &want in wants {
1355 let mut up = want;
1356 while let Some(which) = up {
1357 if needed.contains(&which) {
1358 break;
1359 }
1360 needed.push(which);
1361 up = scopes.get(which).and_then(|scope| scope.parent);
1362 }
1363 }
1364 needed.sort_unstable();
1367 let at: HashMap<usize, usize> =
1368 needed.iter().enumerate().map(|(which, &scope)| (scope, which)).collect();
1369 let ends = ends(extent, rows);
1370 let out = needed
1371 .iter()
1372 .map(|&which| {
1373 let scope = &scopes[which];
1374 rucc_debug::Scope {
1375 parent: scope.parent.and_then(|parent| at.get(&parent).copied()),
1376 over: spread(scope.span, &ends, rows),
1377 }
1378 })
1379 .collect();
1380 (out, at)
1381}
1382
1383fn spread(span: Span, ends: &[u64], rows: &[rucc_asm::Row]) -> Vec<rucc_debug::Reach> {
1391 let mut out: Vec<rucc_debug::Reach> = Vec::new();
1392 for (which, row) in rows.iter().enumerate() {
1393 if row.span.is_dummy() || row.span.lo < span.lo || row.span.hi > span.hi {
1394 continue;
1395 }
1396 let (from, to) = (row.at as u64, ends[which]);
1397 if to <= from {
1398 continue;
1399 }
1400 match out.last_mut() {
1401 Some(last) if last.from + last.len >= from => {
1402 last.len = to.saturating_sub(last.from).max(last.len);
1403 }
1404 _ => out.push(rucc_debug::Reach { from, len: to - from }),
1405 }
1406 }
1407 out
1408}
1409
1410fn settle(mut spans: Vec<rucc_debug::Span>) -> Vec<rucc_debug::Span> {
1421 spans.sort_by_key(|span| (span.from, span.len));
1422 let mut edges: Vec<u64> =
1425 spans.iter().flat_map(|span| [span.from, span.from + span.len]).collect();
1426 edges.sort_unstable();
1427 edges.dedup();
1428 let mut out: Vec<rucc_debug::Span> = Vec::new();
1429 let mut first = 0;
1430 for pair in edges.windows(2) {
1431 let (from, to) = (pair[0], pair[1]);
1432 while spans.get(first).is_some_and(|span| span.from + span.len <= from) {
1436 first += 1;
1437 }
1438 let mut held = None;
1439 let mut agreed = true;
1440 for span in &spans[first..] {
1441 if span.from >= to {
1442 break;
1443 }
1444 if span.from > from || span.from + span.len < to {
1445 continue;
1446 }
1447 match held {
1448 None => held = Some(span.held),
1449 Some(seen) => agreed &= seen == span.held,
1450 }
1451 }
1452 let (Some(held), true) = (held, agreed) else { continue };
1453 match out.last_mut() {
1454 Some(last) if last.from + last.len == from && last.held == held => {
1455 last.len += to - from
1456 }
1457 _ => out.push(rucc_debug::Span { from, len: to - from, held }),
1458 }
1459 }
1460 out
1461}
1462
1463fn interned(files: &mut Vec<String>, name: String) -> usize {
1469 match files.iter().position(|have| *have == name) {
1470 Some(which) => which,
1471 None => {
1472 files.push(name);
1473 files.len() - 1
1474 }
1475 }
1476}
1477
1478fn output(opts: &Options, target: &TargetInfo) -> rucc_object::Output {
1490 let mut features = 0;
1491 if target.tuple.arch() == Arch::X86_64 {
1492 if opts.control.branch() {
1493 features |= rucc_object::Property::IBT;
1494 }
1495 if opts.control.ret() {
1496 features |= rucc_object::Property::SHSTK;
1497 }
1498 }
1499 rucc_object::Output {
1500 sections: rucc_object::Sections {
1501 functions: opts.function_sections,
1502 data: opts.data_sections,
1503 },
1504 property: rucc_object::Property { features },
1505 }
1506}
1507
1508fn wrote(why: rucc_object::Error) -> Vec<Diagnostic> {
1514 match why {
1515 rucc_object::Error::Format { .. } => vec![unsupported(&why.to_string())],
1516 rucc_object::Error::Refused { .. } => vec![internal(&why.to_string())],
1517 }
1518}
1519
1520fn refused(why: rucc_asm::Error) -> Vec<Diagnostic> {
1527 match why {
1528 rucc_asm::Error::Thread { .. }
1529 | rucc_asm::Error::IFunc { .. }
1530 | rucc_asm::Error::Frame { .. } => {
1531 vec![unsupported(&why.to_string())]
1532 }
1533 _ => vec![internal(&why.to_string())],
1534 }
1535}
1536
1537fn unsupported(message: &str) -> Diagnostic {
1543 unsupported_at(message, Span::DUMMY)
1544}
1545
1546fn unsupported_at(message: &str, span: Span) -> Diagnostic {
1552 Diagnostic::error(message.to_owned(), span)
1553 .with_code("E0653")
1554 .note("this construct is not lowered yet, see https://github.com/tamnd/rucc/issues", span)
1555}
1556
1557fn invalid(message: &str) -> Diagnostic {
1559 Diagnostic::error(message.to_owned(), Span::DUMMY).with_code("E0661")
1560}
1561
1562fn internal(message: &str) -> Diagnostic {
1564 Diagnostic::error(format!("internal error: {message}"), Span::DUMMY)
1565 .with_code("E0652")
1566 .note("this is a bug in rucc rather than in the program, please report it", Span::DUMMY)
1567}
1568
1569fn failure(message: String) -> Compiled {
1572 Compiled {
1573 artifact: Artifact::Nothing,
1574 messages: vec![format!("rucc: error: {message}")],
1575 errors: 1,
1576 fired: Fired::new(),
1577 pressure: Pressure::new(),
1578 lowerings: Lowerings::new(),
1579 dumps: Vec::new(),
1580 remarks: String::new(),
1581 deps: Vec::new(),
1582 temps: Temps::default(),
1583 }
1584}
1585
1586#[cfg(test)]
1587mod tests {
1588 use rucc_session::{MemoryFileSystem, Std};
1589 use rucc_target::Triple;
1590
1591 use super::*;
1592
1593 fn options() -> Options {
1594 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1595 opts.emit = EmitKind::Tast;
1596 opts
1597 }
1598
1599 fn run(opts: &Options, source: &str) -> Compiled {
1600 let mut fs = MemoryFileSystem::new();
1601 fs.insert("/main.c", source.to_owned().into_bytes());
1602 compile(opts, "/main.c", &fs)
1603 }
1604
1605 fn freestanding() -> Options {
1609 let mut opts = options();
1610 opts.hosted = false;
1611 opts.search.push_system(rucc_session::runtime::DIR);
1612 opts
1613 }
1614
1615 fn shipped(source: &str) -> String {
1617 let result = run(&freestanding(), source);
1618 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1619 result.text().to_owned()
1620 }
1621
1622 fn tast(source: &str) -> String {
1624 let result = run(&options(), source);
1625 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1626 result.text().to_owned()
1627 }
1628
1629 #[test]
1630 fn the_shipped_stdarg_declares_a_list_and_the_four_operators() {
1631 let text = shipped(concat!(
1632 "#include <stdarg.h>\n",
1633 "int sum(int n, ...) {\n",
1634 " va_list ap, copy;\n",
1635 " va_start(ap, n);\n",
1636 " va_copy(copy, ap);\n",
1637 " int total = va_arg(ap, int) + va_arg(copy, int);\n",
1638 " va_end(ap);\n",
1639 " va_end(copy);\n",
1640 " return total;\n",
1641 "}\n",
1642 ));
1643 assert!(text.contains("va-start"), "{text}");
1644 assert!(text.contains("va-copy"), "{text}");
1645 assert!(text.contains("va-arg"), "{text}");
1646 assert!(text.contains("va-end"), "{text}");
1647 }
1648
1649 #[test]
1653 fn stdarg_hands_out_the_type_alone_when_that_is_all_that_was_asked_for() {
1654 let text = shipped(concat!(
1655 "#define __need___va_list\n",
1656 "#include <stdarg.h>\n",
1657 "int vprint(const char *f, __gnuc_va_list ap);\n",
1658 "#ifdef va_start\n",
1659 "#error va_start should not be defined\n",
1660 "#endif\n",
1661 "#ifdef _VA_LIST_DEFINED\n",
1662 "#error va_list should not have been made\n",
1663 "#endif\n",
1664 ));
1665 assert!(text.contains("vprint"), "{text}");
1666 }
1667
1668 #[test]
1671 fn stddef_answers_one_piece_at_a_time_and_the_next_request_still_gets_through() {
1672 let text = shipped(concat!(
1673 "#define __need_size_t\n",
1674 "#include <stddef.h>\n",
1675 "#ifdef offsetof\n",
1676 "#error offsetof should not be defined yet\n",
1677 "#endif\n",
1678 "#define __need_ptrdiff_t\n",
1679 "#include <stddef.h>\n",
1680 "#include <stddef.h>\n",
1681 "size_t a;\n",
1682 "ptrdiff_t b;\n",
1683 "wchar_t c;\n",
1684 "max_align_t d;\n",
1685 "void *e = NULL;\n",
1686 "struct P { int x; long y; };\n",
1687 "size_t f = offsetof(struct P, y);\n",
1688 ));
1689 assert!(text.contains("decl #0 a : unsigned long"), "{text}");
1690 assert!(text.contains("decl #1 b : long"), "{text}");
1691 }
1692
1693 #[test]
1694 fn the_shipped_limits_and_float_are_the_targets_own_answers() {
1695 let text = shipped(concat!(
1696 "#include <limits.h>\n",
1697 "#include <float.h>\n",
1698 "int bits = CHAR_BIT;\n",
1699 "long big = LONG_MAX;\n",
1700 "int low = INT_MIN;\n",
1701 "int radix = FLT_RADIX;\n",
1702 "int digits = DBL_MANT_DIG;\n",
1703 ));
1704 assert!(text.contains("const 8 : int"), "{text}");
1705 assert!(text.contains("const 9223372036854775807 : long"), "{text}");
1706 assert!(text.contains("const 2 : int"), "{text}");
1707 assert!(text.contains("const 53 : int"), "{text}");
1708 }
1709
1710 #[test]
1714 fn the_shipped_stdint_writes_the_whole_set_when_there_is_no_library_to_defer_to() {
1715 let text = shipped(concat!(
1716 "#include <stdint.h>\n",
1717 "int64_t a = INT64_C(1);\n",
1718 "uint_least16_t b;\n",
1719 "intptr_t c;\n",
1720 "uintmax_t d = UINTMAX_MAX;\n",
1721 "int wide = sizeof(int_fast64_t);\n",
1722 ));
1723 assert!(text.contains("decl #0 a : long"), "{text}");
1724 assert!(text.contains("decl #1 b : unsigned short"), "{text}");
1725 assert!(text.contains("decl #2 c : long"), "{text}");
1726 }
1727
1728 #[test]
1739 fn the_shipped_mmintrin_defines_the_mmx_type_and_the_operations_over_it() {
1740 let text = shipped(concat!(
1741 "#include <mmintrin.h>\n",
1742 "__m64 add(__m64 a, __m64 b) { return _mm_add_pi16(a, b); }\n",
1743 "__m64 pack(__m64 a, __m64 b) { return _m_packsswb(a, b); }\n",
1744 "__m64 shift(__m64 a) { return _mm_srai_pi32(a, 3); }\n",
1745 "int low(__m64 a) { return _mm_cvtsi64_si32(a); }\n",
1746 "void done(void) { _mm_empty(); }\n",
1747 ));
1748 assert!(text.contains("add"), "{text}");
1749 assert!(text.contains("pack"), "{text}");
1750 assert!(text.contains("shift"), "{text}");
1751 }
1752
1753 #[test]
1758 fn the_shipped_mm_malloc_asks_for_aligned_memory_and_gives_it_back() {
1759 let text = shipped(concat!(
1760 "#include <mm_malloc.h>\n",
1761 "void *get(void) { return _mm_malloc(64, 16); }\n",
1762 "void put(void *p) { _mm_free(p); }\n",
1763 ));
1764 assert!(text.contains("get"), "{text}");
1765 assert!(text.contains("put"), "{text}");
1766 }
1767
1768 #[test]
1780 fn the_shipped_xmmintrin_defines_the_sse_type_and_the_operations_over_it() {
1781 let text = shipped(concat!(
1782 "#include <xmmintrin.h>\n",
1783 "__m128 add(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1784 "__m128 one(__m128 a, __m128 b) { return _mm_max_ss(a, b); }\n",
1785 "__m128 mask(__m128 a, __m128 b) { return _mm_cmpnle_ps(a, b); }\n",
1786 "__m128 pick(__m128 a, __m128 b) { return _mm_shuffle_ps(a, b, _MM_SHUFFLE(0,1,2,3)); }\n",
1787 "int bits(__m128 a) { return _mm_movemask_ps(a); }\n",
1788 "int near(__m128 a) { return _mm_cvtss_si32(a); }\n",
1789 "__m128 wide(__m64 a) { return _mm_cvtpi16_ps(a); }\n",
1790 "void *room(void) { return _mm_malloc(64, 16); }\n",
1791 "void hint(const float *p) { _mm_prefetch(p, _MM_HINT_T0); _mm_sfence(); }\n",
1792 ));
1793 assert!(text.contains("add"), "{text}");
1794 assert!(text.contains("mask"), "{text}");
1795 assert!(text.contains("pick"), "{text}");
1796 assert!(text.contains("wide"), "{text}");
1797 }
1798
1799 #[test]
1806 fn the_shipped_xmmintrin_leaves_out_the_names_that_need_an_instruction() {
1807 let text = rucc_session::runtime::header("xmmintrin.h").expect("xmmintrin.h is shipped");
1808 for absent in [
1809 "_mm_sqrt_ps",
1810 "_mm_sqrt_ss",
1811 "_mm_rsqrt_ps",
1812 "_mm_rsqrt_ss",
1813 "_mm_getcsr",
1814 "_mm_setcsr",
1815 ] {
1816 let defined = text.contains(&format!("{absent}("));
1817 assert!(!defined, "{absent} is defined and the header says it is not");
1818 assert!(text.contains(absent), "{absent} is absent and unexplained");
1819 }
1820 }
1821
1822 #[test]
1823 fn the_shipped_emmintrin_defines_both_sse2_types_and_the_operations_over_them() {
1824 let text = shipped(concat!(
1825 "#include <emmintrin.h>\n",
1826 "__m128i add(__m128i a, __m128i b) { return _mm_add_epi64(a, b); }\n",
1827 "__m128i wide(__m128i a, __m128i b) { return _mm_mul_epu32(a, b); }\n",
1828 "__m128i pick(__m128i a) { return _mm_shuffle_epi32(a, _MM_SHUFFLE(0,1,2,3)); }\n",
1829 "__m128i up(__m128i a) { return _mm_slli_epi64(a, 13); }\n",
1830 "__m128i down(__m128i a) { return _mm_srli_si128(a, 3); }\n",
1831 "__m128i pack(__m128i a, __m128i b) { return _mm_packus_epi16(a, b); }\n",
1832 "int bits(__m128i a) { return _mm_movemask_epi8(a); }\n",
1833 "__m128d sum(__m128d a, __m128d b) { return _mm_add_sd(a, b); }\n",
1834 "__m128d mask(__m128d a, __m128d b) { return _mm_cmpunord_pd(a, b); }\n",
1835 "__m128i near(__m128d a) { return _mm_cvtpd_epi32(a); }\n",
1836 "__m128d over(__m128 a) { return _mm_cvtps_pd(a); }\n",
1837 "__m128i half(__m64 a) { return _mm_movpi64_epi64(a); }\n",
1838 "__m128i grab(void const *p) { return _mm_loadu_si128(p); }\n",
1839 "void wall(void) { _mm_lfence(); _mm_mfence(); }\n",
1840 ));
1841 assert!(text.contains("wide"), "{text}");
1842 assert!(text.contains("pack"), "{text}");
1843 assert!(text.contains("near"), "{text}");
1844 assert!(text.contains("half"), "{text}");
1845 }
1846
1847 #[test]
1851 fn the_shipped_immintrin_reaches_the_names_the_headers_under_it_define() {
1852 let text = shipped(concat!(
1853 "#include <immintrin.h>\n",
1854 "unsigned long long matching(unsigned char tag, unsigned char const *bucket) {\n",
1855 " __m128i const want = _mm_set1_epi8((char)tag);\n",
1856 " __m128i const chunk = _mm_loadu_si128((__m128i const *)(void const *)bucket);\n",
1857 " __m128i const same = _mm_cmpeq_epi8(chunk, want);\n",
1858 " return (unsigned long long)_mm_movemask_epi8(same);\n",
1859 "}\n",
1860 "__m64 narrow(__m64 a, __m64 b) { return _mm_add_pi32(a, b); }\n",
1861 "__m128 single(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1862 ));
1863 assert!(text.contains("matching"), "{text}");
1864 assert!(text.contains("narrow"), "the MMX header is not reached: {text}");
1865 assert!(text.contains("single"), "the SSE header is not reached: {text}");
1866 }
1867
1868 #[test]
1872 fn the_shipped_x86intrin_reaches_the_fences_windows_headers_ask_it_for() {
1873 let text = shipped(concat!(
1874 "#include <x86intrin.h>\n",
1875 "void barriers(void *p) {\n",
1876 " _mm_lfence();\n",
1877 " _mm_sfence();\n",
1878 " _mm_mfence();\n",
1879 " _mm_pause();\n",
1880 " _mm_clflush(p);\n",
1881 "}\n",
1882 "__m128i wide(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1883 ));
1884 assert!(text.contains("barriers"), "{text}");
1885 assert!(text.contains("wide"), "the SSE2 header is not reached: {text}");
1886 }
1887
1888 #[test]
1892 fn the_umbrella_and_the_header_under_it_can_both_be_included() {
1893 let text = shipped(concat!(
1894 "#include <immintrin.h>\n",
1895 "#include <emmintrin.h>\n",
1896 "#include <immintrin.h>\n",
1897 "#include <x86intrin.h>\n",
1898 "__m128i twice(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1899 ));
1900 assert!(text.contains("twice"), "{text}");
1901 }
1902
1903 #[test]
1907 fn the_shipped_emmintrin_leaves_out_the_two_square_roots() {
1908 let text = rucc_session::runtime::header("emmintrin.h").expect("emmintrin.h is shipped");
1909 for absent in ["_mm_sqrt_pd", "_mm_sqrt_sd"] {
1910 let defined = text.contains(&format!("{absent}("));
1911 assert!(!defined, "{absent} is defined and the header says it is not");
1912 assert!(text.contains(absent), "{absent} is absent and unexplained");
1913 }
1914 }
1915
1916 #[test]
1917 fn the_three_formality_headers_still_have_to_work() {
1918 let text = shipped(concat!(
1919 "#include <stdbool.h>\n",
1920 "#include <stdalign.h>\n",
1921 "#include <iso646.h>\n",
1922 "#include <stdnoreturn.h>\n",
1923 "int t = true and not false;\n",
1924 "_Alignas(16) char buf[16];\n",
1925 "int a = alignof(long);\n",
1926 ));
1927 assert!(text.contains("decl #0 t : int"), "{text}");
1928 assert!(text.contains("const 8 : unsigned long"), "{text}");
1929 }
1930
1931 #[test]
1939 fn every_shipped_header_can_be_included_twice() {
1940 let once: String = rucc_session::runtime::names()
1941 .iter()
1942 .map(|name| format!("#include <{name}>\n"))
1943 .collect();
1944 let twice = once.repeat(2);
1945 assert_eq!(shipped(&format!("{once}int x;\n")), shipped(&format!("{twice}int x;\n")));
1946 }
1947
1948 #[test]
1949 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
1950 let fs = MemoryFileSystem::new();
1951 let result = compile(&options(), "/nope.c", &fs);
1952 assert!(result.failed());
1953 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
1954 assert!(result.text().is_empty());
1955 }
1956
1957 #[test]
1958 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
1959 let text = tast("int x = 1;\n");
1960 let expected = "\
1961decl #0 x : int object external static defined
1962 init
1963 +0
1964 const 1 : int
1965";
1966 assert_eq!(text, expected);
1967 }
1968
1969 #[test]
1970 fn the_macros_are_expanded_before_anything_is_parsed() {
1971 let text = tast("#define N 2\nint a[N];\n");
1975 assert!(text.starts_with("decl #0 a : int[2] object external static tentative"), "{text}");
1976 }
1977
1978 #[test]
1984 fn a_pragma_is_not_a_declaration_and_the_parse_walks_past_the_ones_it_does_not_read() {
1985 let text = tast(concat!(
1986 "#pragma pack(4)\n",
1987 "struct s { int a; };\n",
1988 "#pragma pack()\n",
1989 "int b;\n",
1990 "_Pragma(\"GCC visibility push(default)\") int c;\n",
1991 ));
1992 assert!(text.contains("decl #0 b : int"), "{text}");
1993 assert!(text.contains("decl #1 c : int"), "{text}");
1994 }
1995
1996 #[test]
2004 fn the_layout_attributes_move_the_members_and_the_record_the_way_gcc_lays_them_out() {
2005 tast(concat!(
2006 "struct A { char c; int i; } __attribute__((packed));\n",
2007 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2008 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2009 "struct B { char c; int i; } __attribute__((aligned));\n",
2012 "_Static_assert(sizeof(struct B) == 16 && _Alignof(struct B) == 16, \"B\");\n",
2013 "struct C { char c; int i __attribute__((packed)); };\n",
2014 "_Static_assert(sizeof(struct C) == 5 && _Alignof(struct C) == 1, \"C\");\n",
2015 "_Static_assert(__builtin_offsetof(struct C, i) == 1, \"C.i\");\n",
2016 "struct D { char c; int i; } __attribute__((packed, aligned(4)));\n",
2017 "_Static_assert(sizeof(struct D) == 8 && _Alignof(struct D) == 4, \"D\");\n",
2018 "_Static_assert(__builtin_offsetof(struct D, i) == 1, \"D.i\");\n",
2019 "struct E { char c; _Alignas(8) int i; };\n",
2020 "_Static_assert(sizeof(struct E) == 16 && _Alignof(struct E) == 8, \"E\");\n",
2021 "_Static_assert(__builtin_offsetof(struct E, i) == 8, \"E.i\");\n",
2022 "struct F { char c; int i __attribute__((aligned(8))); };\n",
2023 "_Static_assert(sizeof(struct F) == 16 && _Alignof(struct F) == 8, \"F\");\n",
2024 "struct G { char c; short s; } __attribute__((aligned(2)));\n",
2027 "_Static_assert(sizeof(struct G) == 4 && _Alignof(struct G) == 2, \"G\");\n",
2028 "struct H { char c; int i; } __attribute__((aligned(2)));\n",
2029 "_Static_assert(sizeof(struct H) == 8 && _Alignof(struct H) == 4, \"H\");\n",
2030 "struct I { [[gnu::packed]] char c; int i; };\n",
2033 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2034 "struct J { char c; [[gnu::packed]] int i; };\n",
2035 "_Static_assert(sizeof(struct J) == 5 && _Alignof(struct J) == 1, \"J\");\n",
2036 "struct M { char c; int i : 5; int j : 20; } __attribute__((packed));\n",
2037 "_Static_assert(sizeof(struct M) == 5 && _Alignof(struct M) == 1, \"M\");\n",
2038 "struct N { char c; long long l; } __attribute__((aligned(32)));\n",
2039 "_Static_assert(sizeof(struct N) == 32 && _Alignof(struct N) == 32, \"N\");\n",
2040 "union L { char c; int i; } __attribute__((packed));\n",
2041 "_Static_assert(sizeof(union L) == 4 && _Alignof(union L) == 1, \"L\");\n",
2042 "struct O { char c; int i; } __attribute__((__packed__));\n",
2046 "_Static_assert(sizeof(struct O) == 5 && _Alignof(struct O) == 1, \"O\");\n",
2047 "struct P { char c; int i; } __attribute__((__aligned__(8)));\n",
2048 "_Static_assert(sizeof(struct P) == 8 && _Alignof(struct P) == 8, \"P\");\n",
2049 ));
2050 }
2051
2052 #[test]
2065 fn a_transparent_union_takes_the_member_a_value_fits_and_is_declared_either_way() {
2066 let text = tast(concat!(
2067 "struct one { int x; };\n",
2068 "struct two { long y; };\n",
2069 "typedef union { struct one *a; struct two *b; void *any; }\n",
2070 " __attribute__((__transparent_union__)) arg;\n",
2071 "int takes(arg v);\n",
2072 "int f(struct one *p, struct two *q, char *c) {\n",
2073 " return takes(p) + takes(q) + takes(c) + takes(0);\n",
2074 "}\n",
2075 "int takes(struct one *p);\n",
2077 "int (*as_a_member)(struct one *) = takes;\n",
2078 "int (*as_the_union)(arg) = takes;\n",
2079 ));
2080 assert!(text.contains("compound-literal"), "{text}");
2081 }
2082
2083 #[test]
2089 fn the_attribute_on_the_declarator_of_a_typedef_is_the_one_glibc_writes() {
2090 let text = tast(concat!(
2091 "struct sockaddr { int family; };\n",
2092 "struct sockaddr_in { int family; int addr; };\n",
2093 "typedef union { struct sockaddr *plain; struct sockaddr_in *inet; }\n",
2094 " addr_arg __attribute__((__transparent_union__));\n",
2095 "int bind_to(int fd, addr_arg where);\n",
2096 "int f(struct sockaddr_in *where) { return bind_to(0, where); }\n",
2097 ));
2098 assert!(text.contains("compound-literal"), "{text}");
2099 }
2100
2101 #[test]
2109 fn a_transparent_union_that_cannot_keep_the_promise_is_dropped_with_a_word_about_it() {
2110 let result = run(
2111 &options(),
2112 concat!(
2113 "union wider { int small; double large; } __attribute__((transparent_union));\n",
2114 "struct plain { int x; } __attribute__((transparent_union));\n",
2115 ),
2116 );
2117 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
2118 assert!(!result.failed(), "{:?}", result.messages);
2119 for message in &result.messages {
2120 assert!(message.contains("'transparent_union' attribute ignored"), "{message}");
2121 }
2122 assert!(result.messages[0].contains("first member"), "{:?}", result.messages);
2123 assert!(result.messages[1].contains("only a union"), "{:?}", result.messages);
2124 }
2125
2126 #[test]
2135 fn an_access_to_a_packed_member_says_the_alignment_the_layout_left_it() {
2136 let packed = body(concat!(
2137 "struct P { char c; int v; } __attribute__((packed));\n",
2138 "int f(struct P *p) { return p->v; }\n",
2139 ));
2140 assert!(packed.contains("load.i32 %2, align 1,"), "{packed}");
2141 let plain = body(concat!(
2143 "struct P { char c; int v; };\n",
2144 "int f(struct P *p) { return p->v; }\n",
2145 ));
2146 assert!(plain.contains("load.i32 %2, align 4,"), "{plain}");
2147 }
2148
2149 #[test]
2156 fn what_is_inside_a_packed_member_is_no_more_aligned_than_the_member_is() {
2157 let stepped = body(concat!(
2158 "struct P { char c; int v[4]; } __attribute__((packed));\n",
2159 "int f(struct P *p, int i) { return p->v[i]; }\n",
2160 ));
2161 assert!(stepped.contains(", align 1,"), "{stepped}");
2162 assert!(!stepped.contains(", align 4,"), "{stepped}");
2163 let nested = body(concat!(
2164 "struct Inner { int v; };\n",
2165 "struct P { char c; struct Inner in; } __attribute__((packed));\n",
2166 "int f(struct P *p) { return p->in.v; }\n",
2167 ));
2168 assert!(nested.contains(", align 1,"), "{nested}");
2169 assert!(!nested.contains(", align 4,"), "{nested}");
2170 }
2171
2172 #[test]
2188 fn an_access_through_a_typedef_that_lowered_its_alignment_says_the_one_the_typedef_asked_for() {
2189 let through = body(concat!(
2190 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2191 "unsigned int f(const void *p) { return *(const unalign32 *)p; }\n",
2192 ));
2193 assert!(through.contains("load.i32 %0, align 1,"), "{through}");
2194 let stepped = body(concat!(
2197 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2198 "unsigned int f(unalign32 *p, int i) { return p[i]; }\n",
2199 ));
2200 assert!(stepped.contains(", align 1,"), "{stepped}");
2201 assert!(!stepped.contains(", align 4,"), "{stepped}");
2202 let plain = body(concat!(
2205 "typedef unsigned int word;\n",
2206 "unsigned int f(const void *p) { return *(const word *)p; }\n",
2207 ));
2208 assert!(plain.contains("load.i32 %0, align 4,"), "{plain}");
2209 }
2210
2211 #[test]
2222 fn a_vector_read_through_a_typedef_that_lowered_its_alignment_comes_back_a_piece_at_a_time() {
2223 let prefix = concat!(
2224 "typedef long long v2di __attribute__((__vector_size__(16)));\n",
2225 "typedef long long v2di_u __attribute__((__vector_size__(16), __aligned__(1)));\n",
2226 );
2227 let loaded =
2228 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di_u *)p; }}"));
2229 assert_eq!(loaded.matches("align 1\n").count(), 2, "{loaded}");
2230 assert!(!loaded.contains("align 16"), "{loaded}");
2231 let stored = body(&format!("{prefix}void f(void *p, v2di b) {{ *(v2di_u *)p = b; }}"));
2234 assert!(stored.contains("memcpy %0, %3, size 16, align 1"), "{stored}");
2235 let aligned =
2237 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di *)p; }}"));
2238 assert!(aligned.contains("align 16"), "{aligned}");
2239 }
2240
2241 #[test]
2250 fn the_aligned_attribute_on_a_declaration_raises_what_that_one_object_is_aligned_to() {
2251 tast(concat!(
2252 "int v __attribute__((aligned(64)));\n",
2253 "_Static_assert(__alignof__(v) == 64, \"v\");\n",
2254 "__attribute__((aligned(32))) int w;\n",
2257 "_Static_assert(__alignof__(w) == 32, \"w\");\n",
2258 "[[gnu::aligned(16)]] int x;\n",
2259 "_Static_assert(__alignof__(x) == 16, \"x\");\n",
2260 "int y __attribute__((aligned(2)));\n",
2263 "_Static_assert(__alignof__(y) == 4, \"y\");\n",
2264 "void f(void) { int a __attribute__((aligned(128)));\n",
2266 "_Static_assert(__alignof__(a) == 128, \"a\"); (void)a; }\n",
2267 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2270 "void g(void) __attribute__((aligned(256)));\n",
2273 "void g(void) {}\n",
2274 "_Static_assert(__alignof__(g) == 256, \"g\");\n",
2275 ));
2276 }
2277
2278 #[test]
2282 fn what_a_declaration_asked_to_be_aligned_to_is_what_the_assembler_is_told() {
2283 let text = asm(concat!(
2284 "int v __attribute__((aligned(64)));\n",
2285 "void g(void) __attribute__((aligned(256)));\n",
2286 "void g(void) {}\n",
2287 "void plain(void) {}\n",
2288 ));
2289 assert!(text.contains("\t.p2align\t6\n\t.type\tv, @object\n"), "{text}");
2290 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2291 assert!(text.contains("\t.p2align\t4, 0x90\n\t.globl\tplain\n"), "{text}");
2292 }
2293
2294 #[test]
2300 fn the_alignment_the_command_line_asked_of_every_function_is_a_floor_under_all_of_them() {
2301 let source = concat!(
2302 "void g(void) __attribute__((aligned(256)));\n",
2303 "void g(void) {}\n",
2304 "void small(void) __attribute__((aligned(4)));\n",
2305 "void small(void) {}\n",
2306 "void plain(void) {}\n",
2307 );
2308 let listing = |align: Option<u32>| {
2309 let mut opts = options();
2310 opts.emit = EmitKind::Asm;
2311 opts.align_functions = align;
2312 let result = run(&opts, source);
2313 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
2314 result.text().to_owned()
2315 };
2316
2317 let text = listing(Some(32));
2318 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "the larger one wins: {text}");
2319 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tsmall\n"), "{text}");
2320 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tplain\n"), "{text}");
2321
2322 let text = listing(Some(8));
2325 assert!(text.contains("\t.p2align\t3, 0x90\n\t.globl\tplain\n"), "{text}");
2326 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2327 }
2328
2329 #[test]
2338 fn an_aligned_typedef_says_what_an_object_of_it_is_aligned_to_and_may_lower_it() {
2339 tast(concat!(
2340 "typedef int L __attribute__((aligned(2)));\n",
2341 "_Static_assert(__alignof__(L) == 2, \"L\");\n",
2342 "_Static_assert(_Alignof(L) == 2, \"L alignof\");\n",
2343 "_Static_assert(sizeof(L) == 4, \"L size\");\n",
2345 "struct T { char c; L x; };\n",
2346 "_Static_assert(sizeof(struct T) == 6, \"T\");\n",
2347 "_Static_assert(__builtin_offsetof(struct T, x) == 2, \"T.x\");\n",
2348 "typedef int H __attribute__((aligned(16)));\n",
2350 "_Static_assert(__alignof__(H) == 16, \"H\");\n",
2351 "_Static_assert(sizeof(H) == 4, \"H size\");\n",
2352 "struct U { char c; H x; };\n",
2353 "_Static_assert(sizeof(struct U) == 32, \"U\");\n",
2354 "_Static_assert(__builtin_offsetof(struct U, x) == 16, \"U.x\");\n",
2355 "typedef L M __attribute__((aligned(8)));\n",
2358 "_Static_assert(__alignof__(M) == 8, \"M\");\n",
2359 "typedef L N;\n",
2362 "_Static_assert(__alignof__(N) == 2, \"N\");\n",
2363 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2365 ));
2366 let text = asm(concat!(
2367 "typedef int L __attribute__((aligned(2)));\n",
2368 "typedef int H __attribute__((aligned(16)));\n",
2369 "L low;\n",
2370 "H high;\n",
2371 ));
2372 assert!(text.contains("\t.p2align\t1\n\t.type\tlow, @object\n"), "{text}");
2373 assert!(text.contains("\t.p2align\t4\n\t.type\thigh, @object\n"), "{text}");
2374 }
2375
2376 #[test]
2384 fn the_vector_size_attribute_builds_a_type_of_lanes_and_measures_it_in_bytes() {
2385 tast(concat!(
2386 "typedef int __attribute__((vector_size(16))) v4si;\n",
2387 "_Static_assert(sizeof(v4si) == 16 && _Alignof(v4si) == 16, \"v4si\");\n",
2388 "typedef char __attribute__((vector_size(16))) v16qi;\n",
2389 "_Static_assert(sizeof(v16qi) == 16, \"v16qi\");\n",
2390 "typedef int __attribute__((vector_size(4))) v1si;\n",
2393 "_Static_assert(sizeof(v1si) == 4, \"v1si\");\n",
2394 "typedef float __attribute__((__vector_size__(8))) v2sf;\n",
2396 "_Static_assert(sizeof(v2sf) == 8, \"v2sf\");\n",
2397 "typedef short [[gnu::vector_size(8)]] v4hi;\n",
2398 "_Static_assert(sizeof(v4hi) == 8, \"v4hi\");\n",
2399 "v4si g;\n",
2402 "_Static_assert(sizeof(g[0]) == 4, \"lane\");\n",
2403 "_Static_assert(sizeof(g + g) == 16, \"whole\");\n",
2404 "_Static_assert(sizeof(g + 1) == 16, \"broadcast\");\n",
2407 "_Static_assert(sizeof(v4si[3]) == 48, \"array\");\n",
2409 ));
2410 }
2411
2412 #[test]
2422 fn a_vector_is_written_whole_into_an_array_of_them_and_named_by_a_type_name() {
2423 tast(concat!(
2424 "typedef int __attribute__((vector_size(8))) v2si;\n",
2425 "v2si table[] = { (v2si){ 1, 2 }, (v2si){ 3, 4 } };\n",
2426 "_Static_assert(sizeof(table) == 16, \"two of them and not eight lanes\");\n",
2427 "v2si written = (int __attribute__((vector_size(8)))){ 5, 6 };\n",
2429 "_Static_assert(sizeof((int __attribute__((vector_size(16)))){ 0 }) == 16, \"named\");\n",
2430 "v2si lanes[2] = { 1, 2, 3, 4 };\n",
2433 "_Static_assert(sizeof(lanes) == 16, \"still elided\");\n",
2434 ));
2435 }
2436
2437 #[test]
2445 fn a_lane_is_assignable_and_a_shift_takes_a_count_of_its_own_lane() {
2446 let result = run(
2447 &options(),
2448 concat!(
2449 "typedef int __attribute__((vector_size(16))) v4si;\n",
2450 "typedef unsigned __attribute__((vector_size(16))) v4ui;\n",
2451 "void write(v4si *out, v4ui a, v4si b, int n) {\n",
2452 " v4si v = { 1, 2, 3, 4 };\n",
2453 " v[0] = n;\n",
2454 " v[1] += n;\n",
2455 " v[2]++;\n",
2456 " *&v[3] = n;\n",
2457 " v4ui shifted = a >> b;\n",
2459 " shifted <<= b;\n",
2460 " *out = v + (v4si)shifted + (1 << b);\n",
2463 "}\n",
2464 "void refused(const v4si c) {\n",
2467 " c[0] = 1;\n",
2468 "}\n",
2469 ),
2470 );
2471 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
2472 assert!(result.messages[0].contains("assignment of read-only"), "{:?}", result.messages);
2473 }
2474
2475 #[test]
2487 fn a_record_that_asks_for_the_other_byte_order_swaps_every_scalar_it_holds() {
2488 let read = "int f(struct s *p) { return p->i; }\n";
2489 let big = "struct s { int i; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
2490 assert!(body(&format!("{big}{read}")).contains("bswap"), "{big}");
2491
2492 let armoured =
2493 "struct s { int i; } __attribute__((__scalar_storage_order__(\"big-endian\")));\n";
2494 assert!(body(&format!("{armoured}{read}")).contains("bswap"), "{armoured}");
2495
2496 let front = "struct __attribute__((scalar_storage_order(\"big-endian\"))) s { int i; };\n";
2497 assert!(body(&format!("{front}{read}")).contains("bswap"), "{front}");
2498
2499 let standard = "struct s { int i; } [[gnu::scalar_storage_order(\"big-endian\")]];\n";
2500 assert!(body(&format!("{standard}{read}")).contains("bswap"), "{standard}");
2501
2502 let same =
2503 "struct s { int i; } __attribute__((scalar_storage_order(\"little-endian\")));\n";
2504 assert!(!body(&format!("{same}{read}")).contains("bswap"), "{same}");
2505
2506 let byte = "struct s { char c; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
2508 let source = format!("{byte}int f(struct s *p) {{ return p->c; }}\n");
2509 assert!(!body(&source).contains("bswap"), "{byte}");
2510
2511 tast(concat!(
2512 "struct s { int i; short h; char c; }",
2513 " __attribute__((scalar_storage_order(\"big-endian\")));\n",
2514 "_Static_assert(sizeof(struct s) == 8 && _Alignof(struct s) == 4, \"s\");\n",
2515 "_Static_assert(__builtin_offsetof(struct s, h) == 4, \"s.h\");\n",
2516 "_Static_assert(__builtin_offsetof(struct s, c) == 6, \"s.c\");\n",
2517 ));
2518 }
2519
2520 #[test]
2526 fn a_bit_field_in_one_of_those_records_is_counted_from_the_top_of_its_bytes() {
2527 let members = "short i : 12; char c1 : 1; char c2 : 1; char c3 : 1; char c4 : 1;";
2528 let read = "int f(struct s *p) { return p->i; }\n";
2529 let plain = format!("struct s {{ {members} }};\n{read}");
2530 let reversed = format!(
2531 "struct s {{ {members} }} __attribute__((scalar_storage_order(\"big-endian\")));\n\
2532 {read}"
2533 );
2534 assert!(body(&plain).contains("shl"), "{}", body(&plain));
2535 assert!(!body(&plain).contains("bswap"), "{}", body(&plain));
2536 let built = body(&reversed);
2539 assert!(built.contains("bswap"), "{built}");
2540 assert!(!built.contains("shl"), "{built}");
2541 assert!(built.contains("ashr"), "{built}");
2542 }
2543
2544 #[test]
2551 fn the_address_of_a_scalar_stored_the_other_way_round_is_refused() {
2552 let opts = options();
2553 let record = "struct s { int i; int a[2]; struct in { int n; } w; }\n\
2554 __attribute__((scalar_storage_order(\"big-endian\")));\n";
2555 let taken = format!("{record}int *f(struct s *p) {{ return &p->i; }}\n");
2556 assert_eq!(
2557 run(&opts, &taken).messages,
2558 ["/main.c:3:30: error: cannot take address of scalar with reverse storage order \
2559 [E0712]"]
2560 );
2561 let element = format!("{record}int *f(struct s *p) {{ return &p->a[0]; }}\n");
2562 let messages = run(&opts, &element).messages;
2563 assert!(messages[0].contains("[E0712]"), "{messages:?}");
2564
2565 let whole = format!("{record}int *f(struct s *p) {{ return (int *) &p->w; }}\n");
2566 assert_eq!(run(&opts, &whole).messages, Vec::<String>::new(), "{whole}");
2567 }
2568
2569 #[test]
2573 fn a_storage_order_that_names_neither_end_is_refused_with_the_two_words_that_are_taken() {
2574 let opts = options();
2575 let wrong = "struct s { int i; } __attribute__((scalar_storage_order(\"middle\")));\n";
2576 assert_eq!(
2577 run(&opts, wrong).messages,
2578 ["/main.c:1:36: error: 'scalar_storage_order' argument must be one of \"big-endian\" \
2579 or \"little-endian\" [E0688]"]
2580 );
2581 let bare = "struct s { int i; } __attribute__((scalar_storage_order));\n";
2582 let messages = run(&opts, bare).messages;
2583 assert!(messages[0].contains("[E0688]"), "{messages:?}");
2584 }
2585
2586 #[test]
2596 fn packing_is_what_decides_whether_a_bit_field_may_straddle_its_own_storage() {
2597 assert_eq!(bit_field_byte("struct s { int x : 12; char y : 6; };"), 2);
2599 assert_eq!(
2600 bit_field_byte("struct s { int x : 12; char y : 6; } __attribute__((packed));"),
2601 1
2602 );
2603 assert_eq!(
2604 bit_field_byte("struct s { int x : 12; __attribute__((packed)) char y : 6; };"),
2605 1
2606 );
2607 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { int x : 12; char y : 6; };"), 1);
2608 assert_eq!(bit_field_byte("struct s { char x; int y : 30; };"), 4);
2610 assert_eq!(bit_field_byte("struct s { char x; int y : 30; } __attribute__((packed));"), 1);
2611 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { char x; int y : 30; };"), 1);
2613 assert_eq!(bit_field_byte("#pragma pack(2)\nstruct s { char x; int y : 30; };"), 1);
2614 }
2615
2616 fn bit_field_byte(record: &str) -> u64 {
2618 let source = format!("{record}\nint f(struct s *p) {{ return p->y; }}\n");
2619 let body = body(&source);
2620 let Some((before, _)) = body.split_once("ptr_add") else { return 0 };
2621 let (_, constant) = before.rsplit_once("iconst.i64 ").expect("an offset constant");
2622 constant.lines().next().expect("a line").trim().parse().expect("a byte offset")
2623 }
2624
2625 #[test]
2631 fn an_attribute_among_the_specifiers_is_kept_beside_the_ones_written_in_front() {
2632 tast(concat!(
2633 "struct a { char c; __attribute__((aligned(8))) int i; };\n",
2634 "_Static_assert(sizeof(struct a) == 16 && _Alignof(struct a) == 8, \"a\");\n",
2635 "_Static_assert(__builtin_offsetof(struct a, i) == 8, \"a.i\");\n",
2636 "struct b { char c; __attribute__((packed)) int i; };\n",
2637 "_Static_assert(sizeof(struct b) == 5 && _Alignof(struct b) == 1, \"b\");\n",
2638 "_Static_assert(__builtin_offsetof(struct b, i) == 1, \"b.i\");\n",
2639 "typedef struct { char c; int i; } __attribute__((packed)) c;\n",
2640 "_Static_assert(sizeof(c) == 5 && _Alignof(c) == 1, \"c\");\n",
2641 ));
2642 }
2643
2644 #[test]
2650 fn pragma_pack_caps_every_member_and_is_read_where_the_body_closes() {
2651 tast(concat!(
2652 "#pragma pack(1)\n",
2653 "struct A { char c; int i; };\n",
2654 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2655 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2656 "#pragma pack()\n",
2657 "struct B { char c; int i; };\n",
2658 "_Static_assert(sizeof(struct B) == 8 && _Alignof(struct B) == 4, \"B\");\n",
2659 "#pragma pack(2)\n",
2660 "struct C { char c; int i; double d; };\n",
2661 "_Static_assert(sizeof(struct C) == 14 && _Alignof(struct C) == 2, \"C\");\n",
2662 "_Static_assert(__builtin_offsetof(struct C, d) == 6, \"C.d\");\n",
2663 "struct K { char c; int i __attribute__((aligned(8))); };\n",
2665 "_Static_assert(sizeof(struct K) == 6 && _Alignof(struct K) == 2, \"K\");\n",
2666 "_Static_assert(__builtin_offsetof(struct K, i) == 2, \"K.i\");\n",
2667 "struct J { char c; int i; } __attribute__((aligned(8)));\n",
2669 "_Static_assert(sizeof(struct J) == 8 && _Alignof(struct J) == 8, \"J\");\n",
2670 "#pragma pack()\n",
2671 "#pragma pack(push, 1)\n",
2672 "struct D { char c; short s; };\n",
2673 "_Static_assert(sizeof(struct D) == 3 && _Alignof(struct D) == 1, \"D\");\n",
2674 "#pragma pack(pop)\n",
2675 "struct E { char c; short s; };\n",
2676 "_Static_assert(sizeof(struct E) == 4 && _Alignof(struct E) == 2, \"E\");\n",
2677 "struct H { char c;\n",
2679 "#pragma pack(1)\n",
2680 " int i; };\n",
2681 "_Static_assert(sizeof(struct H) == 5 && _Alignof(struct H) == 1, \"H\");\n",
2682 "#pragma pack(1)\n",
2683 "struct I { char c;\n",
2684 "#pragma pack()\n",
2685 " int i; };\n",
2686 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2687 "#pragma pack()\n",
2688 "#pragma pack(push, 8)\n",
2690 "#pragma pack(push, 1)\n",
2691 "struct P { char c; int i; };\n",
2692 "_Static_assert(sizeof(struct P) == 5 && _Alignof(struct P) == 1, \"P\");\n",
2693 "#pragma pack(pop)\n",
2694 "struct Q { char c; int i; };\n",
2695 "_Static_assert(sizeof(struct Q) == 8 && _Alignof(struct Q) == 4, \"Q\");\n",
2696 "#pragma pack(pop)\n",
2697 "#pragma pack(16)\n",
2699 "struct R { char c; int i; };\n",
2700 "_Static_assert(sizeof(struct R) == 8 && _Alignof(struct R) == 4, \"R\");\n",
2701 "#pragma pack()\n",
2702 "#pragma pack(1)\n",
2703 "struct S { char c; int i : 5; int j : 20; };\n",
2704 "_Static_assert(sizeof(struct S) == 5 && _Alignof(struct S) == 1, \"S\");\n",
2705 "union T { char c; int i; };\n",
2706 "_Static_assert(sizeof(union T) == 4 && _Alignof(union T) == 1, \"T\");\n",
2707 "#pragma pack()\n",
2708 ));
2709 }
2710
2711 #[test]
2715 fn a_pack_line_that_is_not_one_is_reported_in_the_words_gcc_uses() {
2716 let result = run(
2717 &options(),
2718 concat!(
2719 "#pragma pack 4\n",
2720 "#pragma pack(pop)\n",
2721 "#pragma pack(3)\n",
2722 "#pragma pack(1) junk\n",
2723 "#pragma pack(push, 1\n",
2724 "#pragma pack(x)\n",
2725 "#pragma pack(0)\n",
2728 "#pragma pack(push)\n",
2729 "struct s { char c; int i; };\n",
2730 "#pragma pack(pop)\n",
2731 "#pragma pack(pop, foo)\n",
2732 ),
2733 );
2734 let expected = [
2735 "missing `(` after `#pragma pack` - ignored",
2736 "`#pragma pack (pop)` encountered without matching `#pragma pack (push)`",
2737 "alignment must be a small power of two, not 3",
2738 "junk at end of `#pragma pack`",
2739 "malformed `#pragma pack(push[, id][, <n>])` - ignored",
2740 "unknown action `x` for `#pragma pack` - ignored",
2741 "`#pragma pack(pop, foo)` encountered without matching `#pragma pack(push, foo)`",
2742 ];
2743 assert_eq!(result.messages.len(), expected.len(), "{:?}", result.messages);
2744 for (message, want) in result.messages.iter().zip(expected) {
2745 assert!(message.contains(want), "expected {want:?} in {message:?}");
2746 }
2747 }
2748
2749 #[test]
2756 fn a_declaration_behind_an_empty_macro_is_not_eaten_by_the_pragma_above_it() {
2757 let result = run(
2758 &options(),
2759 concat!(
2760 "#pragma pack(push, 1)\n",
2761 "#pragma pack(pop)\n",
2762 "#define API\n",
2763 "API const char version[] = \"3.53.4\";\n",
2764 "const char *get(void) { return version; }\n",
2765 ),
2766 );
2767 assert!(result.messages.is_empty(), "{:?}", result.messages);
2768 }
2769
2770 #[test]
2774 fn the_wide_integer_answers_to_all_three_of_its_names() {
2775 let text = tast("__uint128_t a; __int128_t b; unsigned __int128 c;\n");
2776 assert!(text.contains("decl #0 a : unsigned __int128"), "{text}");
2777 assert!(text.contains("decl #1 b : __int128"), "{text}");
2778 assert!(text.contains("decl #2 c : unsigned __int128"), "{text}");
2779 }
2780
2781 #[test]
2782 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
2783 let text = tast("long f(int a, long b) { return a + b; }\n");
2787 assert!(text.contains("convert arithmetic"), "{text}");
2788 }
2789
2790 #[test]
2791 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
2792 for source in [
2793 "#error stop\n",
2794 "int f(void) { return 1 + ; }\n",
2795 "int f(void) { return undeclared; }\n",
2796 ] {
2797 let result = run(&options(), source);
2798 assert!(result.failed(), "expected this to fail:\n{source}");
2799 assert!(
2800 result.text().is_empty(),
2801 "a file that did not compile wrote a tree:\n{source}"
2802 );
2803 }
2804 }
2805
2806 #[test]
2807 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
2808 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
2812 assert_eq!(result.errors, 1, "{:?}", result.messages);
2813 }
2814
2815 #[test]
2816 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
2817 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
2821 assert_eq!(result.errors, 1, "{:?}", result.messages);
2822 }
2823
2824 #[test]
2825 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
2826 let source = "int f(void) { char c = 300; return c; }\n";
2827 let plain = run(&options(), source);
2828 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
2829 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
2830 assert!(!plain.text().is_empty(), "a warning is not a reason to write nothing");
2831
2832 let mut opts = options();
2833 opts.warnings_are_errors = true;
2834 let strict = run(&opts, source);
2835 assert!(strict.failed());
2836 assert!(strict.text().is_empty(), "and under -Werror it is a reason to write nothing");
2837 for message in &strict.messages {
2838 assert!(!message.contains("warning:"), "{message}");
2839 }
2840 }
2841
2842 #[test]
2843 fn w_drops_the_warning_before_werror_can_promote_it() {
2844 let source = "int f(void) { char c = 300; return c; }\n";
2845 let mut opts = options();
2846 opts.warnings = false;
2847 let quiet = run(&opts, source);
2848 assert_eq!(quiet.messages, Vec::<String>::new());
2849 assert_eq!(quiet.errors, 0);
2850 assert!(!quiet.text().is_empty(), "and the file still compiles");
2851
2852 opts.warnings_are_errors = true;
2855 let both = run(&opts, source);
2856 assert_eq!(both.messages, Vec::<String>::new());
2857 assert!(!both.failed(), "-w -Werror is not an error about a warning nobody saw");
2858 }
2859
2860 #[test]
2861 fn the_dialect_reaches_the_keywords_and_the_checking() {
2862 let source = "typeof(1) x;\n";
2865 let mut opts = options();
2866 opts.std = Std::C23;
2867 opts.gnu_extensions = false;
2868 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
2869
2870 opts.std = Std::C17;
2871 assert!(run(&opts, source).failed());
2872 }
2873
2874 #[test]
2875 fn asking_for_a_kind_that_is_not_written_yet_runs_the_front_end_and_writes_nothing() {
2876 let mut opts = options();
2877 opts.emit = EmitKind::Object;
2878 let result = run(&opts, "int x = 1;\n");
2879 assert!(!result.failed(), "{:?}", result.messages);
2880 assert!(result.text().is_empty());
2881 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
2884 }
2885
2886 fn mir(source: &str) -> String {
2888 let mut opts = options();
2889 opts.emit = EmitKind::MirFinal;
2890 let result = run(&opts, source);
2891 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2892 result.text().to_owned()
2893 }
2894
2895 #[test]
2901 fn a_function_goes_from_c_to_instructions_with_real_registers_in_them() {
2902 let text = mir("int add(int a, int b) { return a + b; }\n");
2903 assert!(text.starts_with("mfunc @add {"), "{text}");
2904 assert!(text.contains("x64.add_rr_32"), "{text}");
2905 assert!(text.contains("x64.ret"), "{text}");
2906 assert!(!text.contains('%'), "{text}");
2909 }
2910
2911 #[test]
2913 fn a_function_with_no_body_produces_no_machine_function() {
2914 let text = mir("int g(int);\nint f(int a) { return g(a); }\n");
2915 assert_eq!(text.matches("mfunc @").count(), 1, "{text}");
2916 assert!(text.contains("mfunc @f {"), "{text}");
2917 assert!(text.contains("x64.call"), "{text}");
2918 }
2919
2920 #[test]
2922 fn every_definition_in_the_file_is_generated_and_they_keep_their_order() {
2923 let text = mir("int a(int x) { return x; }\nint b(int x) { return x; }\n");
2924 let first = text.find("mfunc @a").expect("the first function");
2925 let second = text.find("mfunc @b").expect("the second function");
2926 assert!(first < second, "{text}");
2927 }
2928
2929 #[test]
2931 fn the_target_decides_which_convention_the_generated_code_follows() {
2932 let mut opts = options();
2933 opts.emit = EmitKind::MirFinal;
2934 let linux = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2935 assert!(linux.contains("$rdi"), "{linux}");
2936
2937 opts.target = "x86_64-pc-windows-msvc".parse::<Triple>().unwrap();
2938 let windows = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2939 assert!(windows.contains("$rcx"), "{windows}");
2940 assert!(!windows.contains("$rdi"), "{windows}");
2941 }
2942
2943 #[test]
2951 fn a_tagged_member_with_no_name_is_a_member_on_windows_and_nothing_on_linux() {
2952 let source = concat!(
2953 "struct S { union U { int i; void *p; }; unsigned long tymed; };\n",
2954 "int size(void) { return sizeof(struct S); }\n",
2955 "int f(struct S *s) { s->i = 1; return s->i; }\n",
2956 );
2957
2958 let mut opts = options();
2959 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
2960 let windows = run(&opts, source);
2961 assert!(windows.messages.is_empty(), "{:?}", windows.messages);
2962
2963 let linux = run(&options(), source);
2964 assert_eq!(linux.messages.len(), 3, "{:?}", linux.messages);
2965 assert!(linux.messages[0].contains("does not declare anything"), "{:?}", linux.messages);
2966
2967 let mut opts = options();
2970 opts.ms_extensions = Some(true);
2971 let asked = run(&opts, source);
2972 assert!(asked.messages.is_empty(), "{:?}", asked.messages);
2973 }
2974
2975 #[test]
2977 fn a_target_this_has_no_back_end_for_is_reported_rather_than_generated() {
2978 let mut opts = options();
2979 opts.emit = EmitKind::MirFinal;
2980 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2981 let result = run(&opts, "int f(int a) { return a; }\n");
2982 assert!(result.failed());
2983 assert!(result.messages[0].contains("no back end for aarch64"), "{:?}", result.messages);
2984 assert!(result.text().is_empty());
2985 }
2986
2987 #[test]
2999 fn a_construct_the_back_end_cannot_reach_yet_is_reported_against_its_function() {
3000 let mut opts = options();
3001 opts.emit = EmitKind::MirFinal;
3002 let source = "void a(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
3003 s; s.x = 1; v[0] = s.x; }\n\
3004 void b(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
3005 s; s.x = 1; v[0] = s.x; }\n";
3006 let result = run(&opts, source);
3007 assert!(result.failed());
3008 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
3009 assert!(result.messages[0].contains("cannot generate code for 'a'"), "{:?}", result);
3010 assert!(result.messages[0].contains("wants more alignment"), "{:?}", result);
3011 assert!(result.messages[1].contains("cannot generate code for 'b'"), "{:?}", result);
3012 assert!(result.text().is_empty());
3013 }
3014
3015 #[test]
3023 fn a_variable_length_array_walks_its_pages_where_every_page_of_the_frame_is_to_be_touched() {
3024 let mut opts = options();
3025 opts.emit = EmitKind::MirFinal;
3026 let source = "void a(int n) { int v[n]; v[0] = 1; }\n";
3027 let plain = run(&opts, source);
3028 assert!(!plain.failed(), "{:?}", plain.messages);
3029 assert!(!plain.text().contains("cmp_set_a_64"), "{}", plain.text());
3030
3031 opts.stack_clash = true;
3032 let result = run(&opts, source);
3033 assert!(!result.failed(), "{:?}", result.messages);
3034 assert!(result.text().contains("cmp_set_a_64"), "{}", result.text());
3035 assert!(result.text().contains("or_mi_8"), "{}", result.text());
3036 }
3037
3038 #[test]
3048 fn a_function_that_keeps_a_frame_pointer_on_windows_reaches_an_object_file() {
3049 let mut opts = options();
3050 opts.emit = EmitKind::Object;
3051 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3052 let source = concat!(
3053 "void use(void *p);\n",
3054 "void array(int n) { int v[n]; v[0] = 1; use(v); }\n",
3055 "void taken(unsigned long n) { use(__builtin_alloca(n)); }\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, .. } => bytes,
3061 other => panic!("expected an object, got {other:?}"),
3062 };
3063 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3064
3065 let mut opts = options();
3068 opts.emit = EmitKind::Object;
3069 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3070 }
3071
3072 #[test]
3083 fn the_address_of_a_function_this_file_only_declares_reaches_a_windows_object() {
3084 let source = concat!(
3085 "void other(void *p);\n",
3086 "void takes(void (*f)(void *));\n",
3087 "void (*held)(void *);\n",
3088 "void pass(void) { takes(other); }\n",
3089 "void keep(void) { held = other; }\n",
3090 "void call(void) { other(0); }\n",
3091 );
3092 let mut opts = options();
3093 opts.emit = EmitKind::Object;
3094 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3095 let result = run(&opts, source);
3096 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3097 let bytes = match result.artifact {
3098 Artifact::Object { bytes, .. } => bytes,
3099 other => panic!("expected an object, got {other:?}"),
3100 };
3101 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3102
3103 let mut opts = options();
3106 opts.emit = EmitKind::Object;
3107 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3108 }
3109
3110 #[test]
3124 fn an_opcode_with_no_name_in_the_rule_language_is_named_by_its_own_spelling() {
3125 let mut opts = options();
3126 opts.emit = EmitKind::MirFinal;
3127 let source =
3128 "long double f(int a) {\n __int128 wide = a;\n return (long double) wide;\n}\n";
3129 let result = run(&opts, source);
3130 assert!(result.failed());
3131 assert!(
3132 result.messages[0].contains("no rule lowers a `sext` producing a `i128`"),
3133 "{result:?}"
3134 );
3135 assert!(result.messages[0].contains(":2:"), "the line the widening is on: {result:?}");
3136 assert!(!result.messages[0].contains("this instruction"), "{result:?}");
3137 }
3138
3139 #[test]
3141 fn the_note_on_unfinished_work_points_at_the_issues_rather_than_at_the_plan() {
3142 let mut opts = options();
3143 opts.emit = EmitKind::MirFinal;
3144 let source = "long double f(int a) { __int128 wide = a; return (long double) wide; }\n";
3145 let result = run(&opts, source);
3146 assert!(result.failed());
3147 let note = result.messages.iter().find(|line| line.contains("note:")).expect("a note");
3148 assert!(note.contains("https://github.com/tamnd/rucc/issues"), "{note}");
3149 assert!(!note.contains("spec/17-milestones.md"), "{note}");
3150 }
3151
3152 #[test]
3154 fn the_frame_flags_on_the_command_line_reach_the_generated_frame() {
3155 let source = "int f(int a) { return a; }\n";
3156 assert!(!mir(source).contains("$rbp"), "a leaf needs no frame pointer by default");
3157
3158 let mut opts = options();
3159 opts.emit = EmitKind::MirFinal;
3160 opts.frame_pointer = true;
3161 let kept = run(&opts, source).text().to_owned();
3162 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
3163 }
3164
3165 fn asm(source: &str) -> String {
3167 let mut opts = options();
3168 opts.emit = EmitKind::Asm;
3169 let result = run(&opts, source);
3170 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3171 result.text().to_owned()
3172 }
3173
3174 #[test]
3181 fn a_function_goes_from_c_to_assembly_an_assembler_would_take() {
3182 let text = asm("int add(int a, int b) { return a + b; }\n");
3183 assert!(text.contains("\t.globl\tadd\n"), "{text}");
3184 assert!(text.contains("\t.type\tadd, @function\n"), "{text}");
3185 assert!(text.contains("\nadd:\n"), "{text}");
3186 assert!(text.contains("\taddl\t"), "{text}");
3187 assert!(text.contains("\tret\n"), "{text}");
3188 assert!(text.contains("\t.size\tadd, .-add\n"), "{text}");
3189 assert!(text.contains(".note.GNU-stack"), "{text}");
3192 }
3193
3194 #[test]
3200 fn a_call_through_a_function_pointer_goes_through_the_register_it_is_in() {
3201 let text = asm("int g(int);\nint f(int (*p)(int), int a) { return p(a) + g(a); }\n");
3202 assert!(text.contains("\tcall\t*%"), "{text}");
3203 assert!(text.contains("\tcall\tg\n"), "{text}");
3204 assert!(text.contains("%rdi"), "{text}");
3208 }
3209
3210 #[test]
3214 fn the_address_of_a_global_is_read_from_the_instruction_pointer() {
3215 let text = asm("extern int counter;\nint f(void) { return counter; }\n");
3216 assert!(text.contains("\tmovl\tcounter(%rip), %eax\n"), "{text}");
3217 }
3218
3219 #[test]
3228 fn a_branch_on_a_comparison_jumps_on_the_opposite_of_what_it_compared() {
3229 let arms = "return 1; return 2;";
3230 let signed = [("==", "jne"), ("!=", "je"), ("<", "jge"), ("<=", "jg"), (">", "jle")];
3231 for (operator, jump) in signed.into_iter().chain([(">=", "jl")]) {
3232 let text = asm(&format!("int f(int a, int b) {{ if (a {operator} b) {arms} }}\n"));
3233 assert!(
3234 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
3235 "{operator}: {text}"
3236 );
3237 assert!(!text.contains("\tset"), "{operator}: {text}");
3238 assert!(!text.contains("\ttest"), "{operator}: {text}");
3239 }
3240 let unsigned = [("<", "jae"), ("<=", "ja"), (">", "jbe"), (">=", "jb")];
3241 for (operator, jump) in unsigned {
3242 let source =
3243 format!("int f(unsigned a, unsigned b) {{ if (a {operator} b) {arms} }}\n");
3244 let text = asm(&source);
3245 assert!(
3246 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
3247 "{operator}: {text}"
3248 );
3249 }
3250
3251 let text = asm("int f(int a) { if (a < 7) return 1; return 2; }\n");
3254 assert!(text.contains("\tcmpl\t$7, %edi\n\tjge\t"), "{text}");
3255 }
3256
3257 #[test]
3263 fn a_comparison_whose_answer_the_program_wanted_still_writes_a_byte() {
3264 let text = asm("int f(int a, int b) { return a < b; }\n");
3265 assert!(text.contains("\tsetl\t"), "{text}");
3266 }
3267
3268 fn optimized(source: &str) -> String {
3270 let mut opts = options();
3271 opts.emit = EmitKind::Asm;
3272 opts.opt_level = rucc_session::OptLevel::O2;
3273 let result = run(&opts, source);
3274 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3275 result.text().to_owned()
3276 }
3277
3278 #[test]
3288 fn a_switch_whose_arms_are_a_function_of_the_label_is_a_range_check_and_arithmetic() {
3289 let arms: String =
3290 (0..16).map(|k| format!("case {k}: return {};", k + 1)).collect::<Vec<_>>().join(" ");
3291 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3292 assert!(text.contains("\tcmpl\t$15, %edi\n\tja\t"), "{text}");
3293 assert!(text.contains("\taddl\t$1, %edi"), "{text}");
3294 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
3295 }
3296
3297 #[test]
3305 fn a_dense_switch_whose_arms_are_not_a_line_is_a_load_from_a_table() {
3306 let arms: String = (0..16)
3307 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
3308 .collect::<Vec<_>>()
3309 .join(" ");
3310 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3311 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
3312 assert!(!text.contains("\tjmp\t*"), "{text}");
3313 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
3314 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
3315 let section = text[..text.find("CSWTCH.0:").unwrap_or(0)].rfind("\t.section\t.rodata");
3316 assert!(section.is_some(), "{text}");
3317 assert_eq!(table.matches("\t.long\t").count(), 16, "{text}");
3318 assert!(table.contains("\t.long\t100\n"), "{text}");
3319 }
3320
3321 #[test]
3327 fn a_table_at_os_has_cells_as_narrow_as_its_answers() {
3328 let arms: String = (0..16)
3329 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
3330 .collect::<Vec<_>>()
3331 .join(" ");
3332 let mut opts = options();
3333 opts.emit = EmitKind::Asm;
3334 opts.opt_level = rucc_session::OptLevel::Os;
3335 let result = run(&opts, &format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3336 assert_eq!(result.messages, Vec::<String>::new());
3337 let text = result.text();
3338 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
3339 assert_eq!(table.matches("\t.byte\t").count(), 16, "{text}");
3340 assert!(text.contains("\tmovsbl\t"), "{text}");
3341 }
3342
3343 #[test]
3350 fn a_table_that_covers_its_operand_has_no_range_check() {
3351 let text = optimized(
3352 "int f(unsigned x) { switch (x & 3) { case 0: return 5; case 1: return 9; \
3353 case 2: return 2; case 3: return 7; } return -1; }\n",
3354 );
3355 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
3356 assert!(!text.contains("\tcmp"), "{text}");
3357 assert!(!text.contains("$-1"), "{text}");
3358 }
3359
3360 #[test]
3368 fn a_conversion_from_a_constant_double_is_the_number_it_converts_to() {
3369 let text = optimized("int f(void) { double d = 2.75; return (int) d; }\n");
3370 assert!(text.contains("movl\t$2, %eax"), "{text}");
3371 assert!(!text.contains("cvttsd2si"), "{text}");
3372 }
3373
3374 #[test]
3382 fn a_slot_of_a_read_only_table_is_the_value_the_table_holds() {
3383 let text =
3384 optimized("static const int t[4] = {10, 20, 30, 40};\nint f(void) { return t[2]; }\n");
3385 assert!(text.contains("movl\t$30, %eax"), "{text}");
3386 assert!(!text.contains("t(%rip)"), "{text}");
3387 }
3388
3389 #[test]
3392 fn a_byte_of_a_read_only_string_is_the_byte_the_string_spells() {
3393 let text = optimized("static const char s[] = \"abc\";\nint f(void) { return s[1]; }\n");
3394 assert!(text.contains("movl\t$98, %eax"), "{text}");
3395 }
3396
3397 #[test]
3401 fn a_table_that_is_not_read_only_keeps_its_load() {
3402 let text = optimized(
3403 "static int t[4] = {10, 20, 30, 40};\nvoid g(int x) { t[2] = x; }\nint f(void) { return t[2]; }\n",
3404 );
3405 assert!(!text.contains("movl\t$30, %eax"), "{text}");
3406 }
3407
3408 #[test]
3415 fn a_call_guarded_by_a_condition_a_read_only_object_settles_is_not_emitted() {
3416 let text = optimized(
3417 "void link_error(void);\nconst double one = 1.0;\nint main(void) { if ((int) one != 1) link_error(); return 0; }\n",
3418 );
3419 assert!(!text.contains("call\tlink_error"), "{text}");
3420 }
3421
3422 #[test]
3424 fn a_cast_between_a_pointer_and_an_integer_leaves_the_value_where_it_is() {
3425 let text = asm("long f(void *p) { return (long)p; }\n");
3426 for line in text.lines().filter(|line| line.starts_with('\t') && !line.contains('.')) {
3431 let mnemonic = line.split_whitespace().next().unwrap_or("");
3432 assert!(matches!(mnemonic, "movq" | "ret"), "{line} in\n{text}");
3433 }
3434 }
3435
3436 #[test]
3440 fn an_argument_past_the_last_register_is_read_out_of_the_caller_s_stack() {
3441 let six = "long a, long b, long c, long d, long e, long f";
3442 let text = asm(&format!("long f({six}, long g, long h) {{ return g + h; }}\n"));
3443
3444 assert!(text.contains("\tmovq\t8(%rsp), "), "{text}");
3451 assert!(text.contains("\taddq\t16(%rsp), "), "{text}");
3452
3453 let narrow = asm(&format!("int f({six}, int g) {{ return g; }}\n"));
3457 assert!(narrow.contains("\tmovl\t8(%rsp), "), "{narrow}");
3458 let eight =
3459 "double a, double b, double c, double d, double e, double f, double g, double h";
3460 let float = asm(&format!("double f({eight}, double i) {{ return i; }}\n"));
3461 assert!(float.contains("\tmovsd\t8(%rsp), "), "{float}");
3462 }
3463
3464 #[test]
3467 fn a_call_writes_the_arguments_with_no_register_left_at_the_stack_pointer() {
3468 let six = "1, 2, 3, 4, 5, 6";
3469 let decl = "long g(long, long, long, long, long, long, long, long);\n";
3470 let text = asm(&format!("{decl}long f(void) {{ return g({six}, 7, 8); }}\n"));
3471
3472 assert!(text.contains("\tmovq\t%"), "{text}");
3473 assert!(text.contains(", (%rsp)\n"), "{text}");
3474 assert!(text.contains(", 8(%rsp)\n"), "{text}");
3475 assert!(text.contains("\tsubq\t$"), "{text}");
3477
3478 let narrow = "int g(int, int, int, int, int, int, int);\n";
3480 let text = asm(&format!("{narrow}int f(void) {{ return g({six}, 7); }}\n"));
3481 assert!(text.contains("\tmovl\t%"), "{text}");
3482 assert!(text.contains(", (%rsp)\n"), "{text}");
3483 }
3484
3485 #[test]
3488 fn a_variadic_call_counts_registers_and_not_arguments() {
3489 let nine = "1., 2., 3., 4., 5., 6., 7., 8., 9.";
3490 let decl = "int g(int, ...);\n";
3491 let text = asm(&format!("{decl}int f(void) {{ return g(0, {nine}); }}\n"));
3492
3493 assert!(text.contains("\tmovl\t$8, "), "eight registers, not nine: {text}");
3494 assert!(text.contains("\tmovsd\t%"), "{text}");
3495 assert!(text.contains(", (%rsp)\n"), "{text}");
3496 }
3497
3498 #[test]
3503 fn a_variadic_function_writes_the_argument_registers_it_was_handed_into_its_frame() {
3504 let body =
3505 "__builtin_va_list ap; __builtin_va_start(ap, n); __builtin_va_end(ap); return n;";
3506 let text = asm(&format!("int f(int n, ...) {{ {body} }}\n"));
3507
3508 let stores = |mnemonic: &str| text.matches(&format!("\t{mnemonic}\t%")).count();
3511 assert!(text.contains(", 8(%r"), "the second slot, not the first: {text}");
3512 assert!(!text.contains(", 0(%r"), "{text}");
3513 assert_eq!(stores("movaps"), 8, "every vector register: {text}");
3516 assert_eq!(stores("movsd"), 0, "and the whole of each one: {text}");
3517
3518 assert!(text.contains("\tsubq\t$"), "{text}");
3520 }
3521
3522 #[test]
3525 fn va_start_writes_the_four_fields_the_psabi_describes() {
3526 let start = "__builtin_va_list ap; __builtin_va_start(ap, d);";
3527 let params = "int a, int b, int c, double d";
3528 let text = asm(&format!("int f({params}, ...) {{ {start} return a; }}\n"));
3529
3530 assert!(text.contains(" movl $24, "), "{text}");
3534 assert!(text.contains(" movl $64, "), "{text}");
3535 assert!(text.contains(", 8(%r"), "{text}");
3539 assert!(text.contains(", 16(%r"), "{text}");
3540 let frame: u32 = text
3541 .lines()
3542 .find_map(|line| line.trim().strip_prefix("subq $")?.split(',').next()?.parse().ok())
3543 .expect("a variadic function takes a frame for the save area");
3544 let above = |line: &str| {
3545 let at: u32 = line.trim().strip_prefix("leaq ")?.split('(').next()?.parse().ok()?;
3546 Some(at > frame)
3547 };
3548 assert!(text.lines().filter_map(above).any(|it| it), "{frame}: {text}");
3549 }
3550
3551 #[test]
3554 fn va_arg_branches_on_whether_the_argument_is_still_in_the_save_area() {
3555 let read = "__builtin_va_list ap; __builtin_va_start(ap, n);";
3556 let ints = format!("int f(int n, ...) {{ {read} return __builtin_va_arg(ap, int); }}\n");
3557 let text = asm(&ints);
3558
3559 assert!(text.contains("$40, "), "{text}");
3562 assert!(text.contains(" cmpl "), "{text}");
3563 assert!(text.contains(" ja "), "{text}");
3567
3568 let arg = "__builtin_va_arg(ap, double)";
3569 let text = asm(&format!("double f(int n, ...) {{ {read} return {arg}; }}\n"));
3570 assert!(text.contains("$160, "), "the last vector slot: {text}");
3571 }
3572
3573 #[test]
3576 fn a_structure_assignment_is_a_move_for_each_word_of_it() {
3577 let decl = "struct pair { long a, b; };\n";
3578 let body = "struct pair p = *q; return p.a + p.b;";
3579 let text = asm(&format!("{decl}long f(struct pair *q) {{ {body} }}\n"));
3580
3581 assert!(!text.contains("memcpy"), "nothing calls the library: {text}");
3582 assert!(!text.contains("\tcall"), "{text}");
3583 assert!(text.matches("\tmovq\t").count() >= 4, "two words each way: {text}");
3585 }
3586
3587 #[test]
3590 fn how_wide_a_word_of_a_copy_is_follows_the_alignment() {
3591 let decl = "struct bytes { char a[8]; };\n";
3592 let body = "struct bytes p = *q; return p.a[0];";
3593 let text = asm(&format!("{decl}int f(struct bytes *q) {{ {body} }}\n"));
3594
3595 assert!(text.matches("\tmovb\t").count() >= 16, "a byte at a time: {text}");
3597 }
3598
3599 #[test]
3602 fn the_part_of_an_initialiser_that_names_nothing_is_stored_as_zero() {
3603 let decl = "struct wide { long a, b, c; };\n";
3604 let text = asm(&format!("{decl}long f(void) {{ struct wide w = {{ 7 }}; return w.c; }}\n"));
3605
3606 assert!(!text.contains("memset"), "nothing calls the library: {text}");
3607 assert!(text.contains("\tmovq\t$0, ") || text.contains("\txorl\t"), "the zero: {text}");
3612 }
3613
3614 #[test]
3617 fn a_copy_too_large_to_unroll_calls_the_runtime() {
3618 let decl = "struct huge { char a[4096]; };\n";
3619 let mut opts = options();
3620 opts.emit = EmitKind::Asm;
3621 let source = format!("{decl}void f(struct huge *p, struct huge *q) {{ *p = *q; }}\n");
3622 let result = run(&opts, &source);
3623 assert!(!result.failed(), "{:?}", result.messages);
3624 let text = result.text();
3625 assert!(text.contains("call") && text.contains("memcpy"), "{text}");
3626 assert!(text.contains("4096"), "the size travels: {text}");
3629 }
3630
3631 #[test]
3638 fn a_structure_too_large_to_unroll_is_copied_into_the_argument_area_by_the_runtime() {
3639 let decl = "struct huge { char a[4096]; };\nint take(struct huge);\n";
3640 let text = asm(&format!("{decl}int f(struct huge *p) {{ return take(*p); }}\n"));
3641
3642 let copy = text.find("call\tmemcpy").expect("the copy");
3643 let call = text.find("call\ttake").expect("the call");
3644 assert!(copy < call, "the copy comes first: {text}");
3645 assert!(text.contains("movq\t%rsp, %rdi"), "the destination: {text}");
3650 assert!(text.contains("$4096, %edx"), "the size: {text}");
3651 }
3652
3653 #[test]
3656 fn a_realigned_frame_reads_them_through_the_frame_pointer() {
3657 let six = "long a, long b, long c, long d, long e, long f";
3658 let body = "_Alignas(32) long wide[4]; wide[0] = g; return wide[0];";
3659 let text = asm(&format!("long f({six}, long g) {{ {body} }}\n"));
3660
3661 assert!(text.contains("\tandq\t$-32, %rsp"), "{text}");
3665 assert!(text.contains("\tmovq\t16(%rbp), "), "{text}");
3666 assert!(!text.contains("\tmovq\t16(%rsp), "), "{text}");
3667 }
3668
3669 #[test]
3671 fn the_target_decides_how_the_assembly_is_spelled() {
3672 let mut opts = options();
3673 opts.emit = EmitKind::Asm;
3674 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
3675 let text = run(&opts, "int f(void) { return 0; }\n").text().to_owned();
3676 assert!(text.contains("__TEXT,__text"), "{text}");
3677 assert!(text.contains("\n_f:\n"), "{text}");
3678 assert!(!text.contains(".note.GNU-stack"), "{text}");
3679 }
3680
3681 fn obj(source: &str) -> Vec<u8> {
3683 let mut opts = options();
3684 opts.emit = EmitKind::Object;
3685 let result = run(&opts, source);
3686 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3687 match result.artifact {
3688 Artifact::Object { bytes, .. } => bytes,
3689 other => panic!("expected an object, got {other:?}"),
3690 }
3691 }
3692
3693 #[test]
3699 fn a_function_goes_from_c_to_an_object_a_linker_would_take() {
3700 let bytes = obj("int add(int a, int b) { return a + b; }\n");
3701 assert_eq!(&bytes[..4], b"\x7fELF", "an object file starts by saying it is one");
3702 let text = asm("int add(int a, int b) { return a + b; }\n");
3703 assert!(
3704 text.contains("\taddl\t"),
3705 "and the listing of it is the same instructions:\n{text}"
3706 );
3707 }
3708
3709 #[test]
3711 fn a_variable_goes_from_c_to_the_section_it_belongs_in() {
3712 let text = asm("int counter = 42;\nstatic int hidden;\nconst int fixed = 7;\n");
3713 assert!(text.contains("\t.data\n\t.globl\tcounter\n"), "{text}");
3714 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
3715 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
3716 assert!(text.contains("\t.bss\n\t.p2align\t2\n"), "{text}");
3719 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
3720 assert!(!text.contains(".globl\thidden"), "{text}");
3721 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
3724 }
3725
3726 #[test]
3733 fn a_bit_field_initializer_writes_every_byte_of_the_value_and_not_only_the_ones_that_are_set() {
3734 let text = asm("struct s { unsigned f : 20; } x = { 0x12300 };\n");
3735 assert!(text.contains("\t.data\n"), "there is something to write: {text}");
3736 assert!(text.contains("\nx:\n\t.ascii\t\"\\000#\\001\"\n"), "and it is the value: {text}");
3737
3738 let text = asm("struct s { unsigned a : 8; unsigned b : 8; } x = { 0, 3 };\n");
3741 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\003\"\n"), "{text}");
3742
3743 let text = asm("struct s { unsigned long long f : 40; } x = { 0x100000 };\n");
3746 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\000\\020\"\n\t.space\t5\n"), "{text}");
3747
3748 let text = asm("struct s { unsigned f : 20; } x = { 0 };\n");
3750 assert!(text.contains("\t.bss\n"), "an object of zeroes is zeroes: {text}");
3751 assert!(text.contains("\nx:\n\t.space\t4\n"), "{text}");
3752 }
3753
3754 #[test]
3756 fn a_string_literal_is_a_variable_with_a_name_no_program_could_write() {
3757 let text = asm("const char *f(void) { return \"hi\"; }\n");
3758 assert!(text.contains("\t.ascii\t\"hi\\000\"\n"), "{text}");
3759 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
3760 let label = text
3761 .lines()
3762 .find(|line| line.starts_with(".Lstr"))
3763 .unwrap_or_else(|| panic!("a label for the literal in\n{text}"));
3764 assert!(!text.contains(&format!(".globl\t{}", label.trim_end_matches(':'))), "{text}");
3765 }
3766
3767 #[test]
3769 fn an_address_in_an_initializer_is_left_to_the_linker() {
3770 let source = "int counter;\nint *p = &counter;\n";
3771 let text = asm(source);
3772 assert!(text.contains("\np:\n\t.quad\tcounter\n"), "{text}");
3773 let bytes = obj(source);
3776 assert!(bytes.windows(8).any(|w| w == b"counter\0"), "the object has to name it");
3777 }
3778
3779 #[test]
3788 fn a_constant_holding_an_address_goes_in_the_section_the_loader_may_write_once() {
3789 let text = asm("static void a(void) {}\nstatic void b(void) {}\n\
3792 struct m { void (*x)(void); void (*y)(void); };\n\
3793 const struct m t = { a, b };\n");
3794 assert!(text.contains("\t.section\t.data.rel.ro.local,\"aw\",@progbits\n"), "{text}");
3795 assert!(text.contains("\nt:\n\t.quad\ta\n\t.quad\tb\n"), "{text}");
3796
3797 let text =
3800 asm("void a(void);\nstruct m { void (*x)(void); };\nconst struct m t = { a };\n");
3801 assert!(text.contains("\t.section\t.data.rel.ro,\"aw\",@progbits\n"), "{text}");
3802
3803 let text = asm("const int fixed = 7;\n");
3805 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
3806 }
3807
3808 #[test]
3815 fn a_thread_local_variable_is_storage_a_thread_gets_a_copy_of_and_an_offset_into_it() {
3816 let text = asm("_Thread_local int x = 1;\nint read(void) { return x; }\n");
3817 assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
3820 assert!(text.contains("\t.type\tx, @tls_object\n"), "{text}");
3821 assert!(text.contains("x@GOTTPOFF(%rip)"), "{text}");
3824 assert!(text.contains("%fs:0"), "{text}");
3825 }
3826
3827 #[test]
3833 fn the_address_of_this_thread_s_own_storage_is_read_out_of_the_segment_register() {
3834 let text = asm("void *here(void) { return __builtin_thread_pointer(); }\n");
3835 assert!(text.contains("movq\t%fs:0, "), "{text}");
3836 assert!(!text.contains("GOTTPOFF"), "{text}");
3838 }
3839
3840 #[test]
3851 fn a_prefetch_is_one_of_four_instructions_and_the_locality_is_what_picks() {
3852 for (locality, wanted) in
3853 [(0, "prefetchnta"), (1, "prefetcht2"), (2, "prefetcht1"), (3, "prefetcht0")]
3854 {
3855 let source =
3856 format!("void warm(void *p) {{ __builtin_prefetch(p, 0, {locality}); }}\n");
3857 let text = asm(&source);
3858 assert!(text.contains(&format!("\t{wanted}\t")), "locality {locality}: {text}");
3859 }
3860 let text = asm("void warm(void *p) { __builtin_prefetch(p); }\n");
3862 assert!(text.contains("\tprefetcht0\t"), "{text}");
3863 let text = asm("void warm(void *p) { __builtin_prefetch(p, 1); }\n");
3866 assert!(text.contains("\tprefetcht0\t"), "{text}");
3867 assert!(!text.contains("prefetchw"), "{text}");
3868 }
3869
3870 #[test]
3881 fn a_trap_is_the_instruction_the_machine_has_no_meaning_for() {
3882 let text = asm("void stop(void) { __builtin_trap(); }\n");
3883 assert!(text.contains("\tud2\n"), "{text}");
3884 assert!(!text.contains("\tcall"), "a stop is not a call to anything: {text}");
3885
3886 let text = asm("int stop(int a) { __builtin_trap(); return a + 1; }\n");
3887 assert!(text.contains("\tud2\n"), "{text}");
3888 assert!(text.contains("\taddl\t"), "the block goes on after a stop: {text}");
3889 }
3890
3891 #[test]
3903 fn assume_aligned_is_its_first_argument_and_keeps_the_rest() {
3904 let text = asm("void *aligned(char *p) { return __builtin_assume_aligned(p, 16); }\n");
3905 assert!(!text.contains("assume_aligned"), "{text}");
3906 assert!(!text.contains("\tcall"), "nothing is called for an alignment fact: {text}");
3907
3908 let source = "unsigned long width(void);\n\
3909 void *aligned(char *p) { return __builtin_assume_aligned(p, width()); }\n";
3910 let text = asm(source);
3911 assert!(!text.contains("assume_aligned"), "{text}");
3912 assert!(text.contains("width"), "the argument that is not the answer still runs: {text}");
3913 }
3914
3915 #[test]
3925 fn the_frame_address_is_the_frame_pointer_after_walking_that_many_links() {
3926 let text = asm("void *here(void) { return __builtin_frame_address(0); }\n");
3927 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
3928 assert!(text.contains("movq\t%rbp, %rax"), "{text}");
3929 assert!(!text.contains("\tcall"), "a frame address is not a call to anything: {text}");
3930
3931 let walk = |depth: u32| {
3932 let source = format!("void *up(void) {{ return __builtin_frame_address({depth}); }}\n");
3933 asm(&source).matches("movq\t(%r").count()
3934 };
3935 assert_eq!(walk(1), 1, "one link is one load");
3936 assert_eq!(walk(3), 3, "three links are three loads");
3937 }
3938
3939 #[test]
3949 fn the_return_address_is_one_word_above_the_frame_the_walk_ended_at() {
3950 let text = asm("void *back(void) { return __builtin_return_address(0); }\n");
3951 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
3952 assert!(text.contains("movq\t8(%rbp), %rax"), "{text}");
3953 assert!(!text.contains("\tcall"), "a return address is not a call to anything: {text}");
3954
3955 let text = asm("void *back(void) { return __builtin_return_address(2); }\n");
3956 assert_eq!(text.matches("movq\t(%r").count(), 2, "two links are two loads: {text}");
3957 assert!(text.contains("movq\t8(%r"), "and the answer is above the last of them: {text}");
3958 }
3959
3960 #[test]
3971 fn a_depth_that_is_not_a_small_constant_is_refused() {
3972 let mut opts = options();
3973 opts.emit = EmitKind::Ir;
3974 for source in [
3975 "void *up(int n) { return __builtin_return_address(n); }\n",
3976 "void *up(void) { return __builtin_frame_address(1000); }\n",
3977 ] {
3978 let messages = run(&opts, source).messages;
3979 let named = messages.iter().any(|m| m.contains("E0705"));
3980 assert!(named, "expected a refusal in {messages:?}");
3981 }
3982 }
3983
3984 #[test]
3996 fn an_alloca_takes_the_bytes_off_the_stack_pointer_and_answers_where_they_are() {
3997 let text =
3998 asm("void use(void *p); void f(unsigned long n) { use(__builtin_alloca(n)); }\n");
3999 assert!(text.contains("andq\t$-16"), "the size is rounded up to sixteen: {text}");
4000 assert!(text.contains("subq\t%rdi, %rsp"), "and taken off the stack pointer: {text}");
4001 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
4002
4003 let plain = concat!(
4006 "extern void *alloca(__SIZE_TYPE__);\n",
4007 "void use(void *p);\n",
4008 "void f(unsigned long n) { use(alloca(n)); }\n",
4009 );
4010 let text = asm(plain);
4011 assert!(text.contains("subq\t%rdi, %rsp"), "the plain name is the same bytes: {text}");
4012 assert_eq!(text.matches("\tcall").count(), 1, "and is not a call either: {text}");
4013
4014 let own = concat!(
4017 "static void *alloca(unsigned long n) { return 0; }\n",
4018 "void *f(unsigned long n) { return alloca(n); }\n",
4019 );
4020 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
4021 }
4022
4023 #[test]
4038 fn a_builtin_the_program_never_declared_is_the_builtin_rather_than_the_one_c89_wrote_down() {
4039 let mut opts = options();
4043 opts.permissive = true;
4044 let undeclared = "void use(void *p);
4045void f(unsigned long n) { use(alloca(n)); }
4046";
4047 assert_eq!(
4048 run(&opts, undeclared).messages,
4049 [
4050 "/main.c:2:31: warning: implicit declaration of function 'alloca' [E0521]",
4051 "/main.c:2:31: warning: incompatible implicit declaration of built-in function \
4052 'alloca' [E0713]",
4053 ]
4054 );
4055
4056 opts.emit = EmitKind::Asm;
4057 let text = run(&opts, undeclared).text().to_owned();
4058 assert!(text.contains("subq\t%rdi, %rsp"), "the bytes come off the stack: {text}");
4059 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
4060
4061 let string = "unsigned long f(void) { return strlen(\"abc\"); }\n";
4064 let text = run(&opts, string).text().to_owned();
4065 assert!(text.contains("call\tstrlen"), "strlen is still a call: {text}");
4066
4067 let own = concat!(
4070 "static void *alloca(unsigned long n) { return 0; }\n",
4071 "void *f(unsigned long n) { return alloca(n); }\n",
4072 );
4073 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
4074 }
4075
4076 #[test]
4086 fn the_bytes_an_alloca_took_are_still_there_at_the_end_of_the_block_that_took_them() {
4087 let inner = "{ use(__builtin_alloca(n)); }";
4088 for body in [inner.to_owned(), format!("int a[n]; {inner} use(a);")] {
4089 let source = format!("void use(void *p);\nvoid f(unsigned long n) {{ {body} }}\n");
4090 let text = asm(&source);
4091 for line in text.lines().filter(|line| line.trim_end().ends_with(", %rsp")) {
4095 let taking = line.contains("subq");
4096 let leaving = line.contains("%rbp");
4097 assert!(taking || leaving, "nothing puts the stack back: {line} in {text}");
4098 }
4099 }
4100 }
4101
4102 #[test]
4104 fn the_object_and_the_listing_are_two_spellings_of_one_compilation() {
4105 let source = "int callee(void); int g(void) { return callee(); }\n";
4109 let bytes = obj(source);
4110 assert!(
4111 bytes.windows(7).any(|w| w == b"callee\0"),
4112 "the object has to name the callee for the linker to find it"
4113 );
4114 let text = asm(source);
4115 assert!(text.contains("\tcall\tcallee\n"), "{text}");
4116 }
4117
4118 #[test]
4124 fn compiling_for_an_executable_produces_an_object_and_not_a_dump() {
4125 let mut opts = options();
4126 opts.emit = EmitKind::Executable;
4128 let result = run(&opts, "int main(void) { return 0; }\n");
4129 assert_eq!(result.messages, Vec::<String>::new());
4130 match result.artifact {
4131 Artifact::Object { bytes, .. } => assert_eq!(&bytes[..4], b"\x7fELF"),
4132 other => panic!("expected an object, got {other:?}"),
4133 }
4134 }
4135
4136 #[test]
4138 fn a_platform_with_no_object_writer_is_said_so_rather_than_written_as_elf() {
4139 let mut opts = options();
4140 opts.emit = EmitKind::Object;
4141 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
4142 let result = run(&opts, "int f(void) { return 0; }\n");
4143 assert!(result.failed(), "an object nobody can read is worse than a message");
4144 assert!(
4145 result.messages.iter().any(|m| m.contains("no object writer")),
4146 "{:?}",
4147 result.messages
4148 );
4149 }
4150
4151 fn ir(source: &str) -> String {
4153 let mut opts = options();
4154 opts.emit = EmitKind::Ir;
4155 let result = run(&opts, source);
4156 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4157 result.text().to_owned()
4158 }
4159
4160 fn errors(source: &str) -> Vec<String> {
4162 let mut opts = options();
4163 opts.emit = EmitKind::Ir;
4164 let result = run(&opts, source);
4165 assert!(result.failed(), "expected this to be refused:\n{source}");
4166 result.messages
4167 }
4168
4169 fn body(source: &str) -> String {
4171 let text = ir(source);
4172 let (_, rest) = text.split_once("{\n").expect("a function definition");
4173 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
4174 body.to_owned()
4175 }
4176
4177 #[test]
4185 fn gnu89_inline_is_what_decides_whether_a_bare_inline_definition_reaches_the_module() {
4186 let source = "inline int f(int x) { return x + 1; }\n";
4187 let with = |flag: bool| {
4188 let mut opts = options();
4189 opts.emit = EmitKind::Ir;
4190 opts.gnu89_inline = flag;
4191 let result = run(&opts, source);
4192 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
4193 result.text().to_owned()
4194 };
4195
4196 assert!(!with(false).contains("block0"), "no body: {}", with(false));
4199
4200 assert!(with(true).contains("block0"), "a body: {}", with(true));
4203 }
4204
4205 #[test]
4212 fn an_access_through_a_type_names_the_type_it_went_through() {
4213 let source = "\
4214struct s { int a; float b; };\n\
4215union u { int i; float f; };\n\
4216int scalar(int *p) { return *p; }\n\
4217float member(struct s *p) { p->a = 1; return p->b; }\n\
4218int element(int *a, long i) { return a[i]; }\n\
4219float through_a_union(union u *p) { p->i = 1; return p->f; }\n";
4220 let text = ir(source);
4221 assert!(text.contains(r#"!0 = tbaa "char""#), "the root: {text}");
4222 assert!(text.contains(r#"tbaa "int", parent !0"#), "int under it: {text}");
4223 assert!(text.contains(r#"tbaa "float", parent !0"#), "float under it: {text}");
4224 let named = text.lines().filter(|line| line.contains(", tbaa !")).count();
4227 assert_eq!(named, 6, "six accesses: {text}");
4228 }
4229
4230 #[test]
4237 fn turning_strict_aliasing_off_leaves_the_type_off_every_access() {
4238 let source = "int punned(float *f, int *i) { *i = 1; *f = 2.0f; return *i; }\n";
4239 let mut opts = options();
4240 opts.emit = EmitKind::Ir;
4241 opts.strict_aliasing = false;
4242 let result = run(&opts, source);
4243 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
4244 let text = result.text().to_owned();
4245 assert!(!text.contains("tbaa"), "not even the root: {text}");
4246 }
4247
4248 #[test]
4256 fn a_bare_return_from_a_function_that_promised_a_value_gives_back_a_zero() {
4257 let mut opts = options();
4258 opts.emit = EmitKind::Ir;
4259 opts.std = Std::C89;
4260 let compiled = |source: &str| {
4261 let result = run(&opts, source);
4262 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
4263 result.text().to_owned()
4264 };
4265
4266 let text = compiled("int f(int x) { if (x) return; return 3; }\n");
4267 assert!(text.contains("iconst.i32 0\n return"), "zero goes back: {text}");
4268 assert!(!text.contains("unreachable"), "the branch that reached it is kept: {text}");
4269
4270 let text = compiled("double f(int x) { if (x) return; return 1.0; }\n");
4272 assert!(text.contains("fconst.f64 0x0\n return"), "a float zero goes back: {text}");
4273 }
4274
4275 #[test]
4283 fn a_call_to_a_name_nothing_declared_declares_it_as_c89_said_to() {
4284 let mut opts = options();
4285 opts.emit = EmitKind::Ir;
4286 opts.std = Std::C89;
4287 let compiled = |source: &str| {
4288 let result = run(&opts, source);
4289 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
4290 result.text().to_owned()
4291 };
4292
4293 let text = compiled("int f(void) { return g(); }\n");
4295 assert!(text.contains("call @g"), "the call is to the name that was written: {text}");
4296 assert!(text.contains("i32"), "and it gives back an int: {text}");
4297
4298 let text = compiled("int f(char c) { return g(c); }\n");
4301 assert!(text.contains("sext.i32"), "the argument is promoted: {text}");
4302
4303 let mut opts = options();
4306 opts.std = Std::C89;
4307 let said = run(&opts, "int f(void) { return h; }\n").messages.join("\n");
4308 assert!(said.contains("'h' undeclared"), "not a call, so not declared: {said}");
4309 }
4310
4311 #[test]
4321 fn a_name_called_before_it_is_defined_still_gets_its_definition() {
4322 let mut opts = options();
4323 opts.emit = EmitKind::Ir;
4324 opts.std = Std::C89;
4325 let text = run(&opts, "int f(void) { return dummy(); }\ndummy () { return 7; }\n")
4326 .text()
4327 .to_owned();
4328 assert!(text.contains("func @f()"), "the caller is there: {text}");
4329 assert!(text.contains("func @dummy"), "and so is what it calls: {text}");
4330 assert!(text.contains("iconst.i32 7"), "with the body it was given: {text}");
4331 }
4332
4333 #[test]
4341 fn an_old_style_parameter_is_converted_from_what_the_call_promoted_it_to() {
4342 let mut opts = options();
4343 opts.emit = EmitKind::Ir;
4344 opts.std = Std::C89;
4345 let compiled = |source: &str| run(&opts, source).text().to_owned();
4346
4347 let text = compiled("f (c) unsigned char c; { return c; }\n");
4348 assert!(text.contains("func @f(i32"), "an int arrives: {text}");
4349 assert!(text.contains("trunc.i8"), "and is cut down to what was declared: {text}");
4350 assert!(text.contains("zext.i32"), "then read back unsigned: {text}");
4351
4352 let text = compiled("f (s) short s; { return s; }\n");
4354 assert!(text.contains("trunc.i16"), "cut down: {text}");
4355 assert!(text.contains("sext.i32"), "and read back signed: {text}");
4356
4357 let text = compiled("f (x) float x; { return x * 2; }\n");
4360 assert!(text.contains("func @f(f64"), "a double arrives: {text}");
4361 assert!(text.contains("fptrunc.f32"), "and is narrowed to the float: {text}");
4362
4363 let text = compiled("int f(unsigned char c) { return c; }\n");
4366 assert!(text.contains("func @f(i8)"), "the declared type arrives: {text}");
4367 assert!(!text.contains("trunc"), "so there is nothing to cut down: {text}");
4368 }
4369
4370 #[test]
4379 fn the_rules_gcc_promoted_are_decided_by_the_dialect_and_by_fpermissive() {
4380 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
4382 let cases = [
4383 ("static counted;\n", ["", "error", "warning", "error"]),
4384 ("int f(void) { return g(); }\n", ["", "error", "warning", "error"]),
4385 ("int f(x) { return x; }\n", ["", "error", "warning", "error"]),
4386 ("int *p;\nvoid h(void) { p = 1; }\n", ["warning", "error", "warning", "error"]),
4387 (
4388 "char *q;\nint *r;\nvoid k(void) { r = q; }\n",
4389 ["warning", "error", "warning", "error"],
4390 ),
4391 ("int f(void) { return; }\n", ["", "error", "warning", "error"]),
4392 ("void g(void) { return 1; }\n", ["warning", "error", "warning", "error"]),
4393 ];
4394
4395 for (source, wanted) in cases {
4396 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
4397 let mut opts = options();
4398 opts.std = std;
4399 opts.permissive = permissive;
4400 let said = run(&opts, source).messages.join("\n");
4401 let severity = if said.contains(": error: ") {
4402 "error"
4403 } else if said.contains(": warning: ") {
4404 "warning"
4405 } else {
4406 ""
4407 };
4408 let how = if permissive { " -fpermissive" } else { "" };
4409 assert_eq!(
4410 severity,
4411 wanted,
4412 "under -std={}{how}, {source} was answered with `{said}`",
4413 std.as_str()
4414 );
4415 if wanted.is_empty() {
4416 assert!(said.is_empty(), "nothing to say, but said `{said}`");
4417 }
4418 }
4419 }
4420 }
4421
4422 #[test]
4431 fn the_three_variadic_builtins_answer_a_bad_list_the_way_a_call_answers_a_bad_argument() {
4432 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
4433 let cases = [
4434 (
4435 "int f(int n, ...) { char *p; return __builtin_va_arg(p, int); }\n",
4436 "first argument to 'va_arg' not of type 'va_list'",
4437 ["error", "error", "error", "error"],
4438 ),
4439 (
4440 "void f(int n, ...) { char *p; __builtin_va_start(p, n); }\n",
4441 "passing argument 1 of '__builtin_va_start' from incompatible pointer type",
4442 ["warning", "error", "warning", "error"],
4443 ),
4444 (
4445 "void f(int n, ...) { int x; __builtin_va_end(x); }\n",
4446 "passing argument 1 of '__builtin_va_end' makes pointer from integer without a \
4447 cast",
4448 ["warning", "error", "warning", "error"],
4449 ),
4450 (
4451 "void f(int n, ...) { __builtin_va_list a; char *p; __builtin_va_copy(a, p); }\n",
4452 "passing argument 2 of '__builtin_va_copy' from incompatible pointer type",
4453 ["warning", "error", "warning", "error"],
4454 ),
4455 ];
4456
4457 for (source, message, wanted) in cases {
4458 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
4459 let mut opts = options();
4460 opts.std = std;
4461 opts.permissive = permissive;
4462 let said = run(&opts, source).messages.join("\n");
4463 let how = if permissive { " -fpermissive" } else { "" };
4464 assert!(
4465 said.contains(&format!(": {wanted}: {message}")),
4466 "under -std={}{how}, {source} was answered with `{said}`",
4467 std.as_str()
4468 );
4469 }
4470 }
4471 }
4472
4473 fn safe_ir(tier: rucc_session::Safety, source: &str) -> String {
4475 let mut opts = options();
4476 opts.emit = EmitKind::Ir;
4477 opts.safety = tier;
4478 let result = run(&opts, source);
4479 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4480 result.text().to_owned()
4481 }
4482
4483 const READS_THROUGH_A_POINTER: &str = "int read(int *p) { return p[1]; }\n";
4484
4485 fn padded_ir(padding: Padding, source: &str) -> String {
4487 let mut opts = options();
4488 opts.emit = EmitKind::Ir;
4489 opts.safety = rucc_session::Safety::Detect;
4490 opts.padding = padding;
4491 let result = run(&opts, source);
4492 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4493 result.text().to_owned()
4494 }
4495
4496 const FILLS_A_RECORD_A_MEMBER_AT_A_TIME: &str = "struct padded { char tag; int value; };\n\
4497 void fill(struct padded *p) { p->tag = 1; p->value = 2; }\n";
4498
4499 #[test]
4500 fn a_record_filled_a_member_at_a_time_comes_out_whole_when_padding_does_not_participate() {
4501 let text = padded_ir(Padding::Ignored, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
4505 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
4506 }
4507
4508 #[test]
4509 fn a_store_says_only_what_it_wrote_when_padding_does_participate() {
4510 let text = padded_ir(Padding::Tracked, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
4513 assert!(!text.contains("owns"), "{text}");
4514 }
4515
4516 #[test]
4517 fn a_member_of_a_union_owns_nothing_after_it() {
4518 let text = padded_ir(
4522 Padding::Ignored,
4523 "union u { char tag; long wide; };\nvoid fill(union u *p) { p->tag = 1; }\n",
4524 );
4525 assert!(!text.contains("owns"), "{text}");
4526 }
4527
4528 #[test]
4529 fn an_inner_records_trailing_padding_reaches_the_outer_records() {
4530 let text = padded_ir(
4535 Padding::Ignored,
4536 "struct inner { char c; };\n\
4537 struct outer { struct inner in; int x; };\n\
4538 void fill(struct outer *p) { p->in.c = 1; p->x = 2; }\n",
4539 );
4540 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
4541 }
4542
4543 #[test]
4544 fn a_build_that_did_not_ask_for_the_monitor_is_compiled_the_way_it_always_was() {
4545 let text = ir(READS_THROUGH_A_POINTER);
4549 assert!(!text.contains("check_"), "{text}");
4550 assert!(!text.contains("cap_of"), "{text}");
4551 }
4552
4553 #[test]
4554 fn asking_for_a_tier_puts_the_checks_in_before_the_optimizer_sees_them() {
4555 let text = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4556 assert!(text.contains("cap_of"), "{text}");
4557 assert!(text.contains("check_bounds"), "{text}");
4558 assert!(text.contains("check_live"), "{text}");
4559 assert!(text.contains("check_deriv"), "{text}");
4561 assert!(text.contains("check_type"), "{text}");
4563 }
4564
4565 #[test]
4566 fn the_three_tiers_that_are_not_off_all_check_the_same_accesses_so_far() {
4567 let detect = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4571 for tier in [rucc_session::Safety::Enforce, rucc_session::Safety::Kernel] {
4572 assert_eq!(safe_ir(tier, READS_THROUGH_A_POINTER), detect, "{tier}");
4573 }
4574 }
4575
4576 fn summary(tier: rucc_session::Safety, source: &str) -> String {
4578 let mut opts = options();
4579 opts.emit = EmitKind::SafetySummary;
4580 opts.safety = tier;
4581 let result = run(&opts, source);
4582 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4583 result.text().to_owned()
4584 }
4585
4586 #[test]
4587 fn the_summary_counts_the_checks_that_went_in_and_the_ones_still_standing() {
4588 let text = summary(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4589 assert!(text.contains("\"tier\": \"detect\""), "{text}");
4590 assert!(
4592 text.contains("\"bounds\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"),
4593 "{text}"
4594 );
4595 assert!(
4596 text.contains(
4597 "\"derivation\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"
4598 ),
4599 "{text}"
4600 );
4601 }
4602
4603 #[test]
4604 fn a_build_without_the_monitor_summarises_as_a_build_with_no_checks_in_it() {
4605 let text = summary(rucc_session::Safety::Off, READS_THROUGH_A_POINTER);
4609 assert!(text.contains("\"tier\": \"off\""), "{text}");
4610 assert!(
4611 text.contains("\"bounds\": { \"emitted\": 0, \"remaining\": 0, \"discharged\": 0 }"),
4612 "{text}"
4613 );
4614 }
4615
4616 #[test]
4617 fn a_call_the_boundary_models_is_counted_apart_from_one_it_does_not() {
4618 let text = summary(
4619 rucc_session::Safety::Detect,
4620 "void *memcpy(void *, const void *, unsigned long);\n\
4621 int puts(const char *);\n\
4622 void f(char *d, char *s) { memcpy(d, s, 4); puts(d); }\n",
4623 );
4624 assert!(text.contains("\"interposed\": 1"), "{text}");
4625 assert!(text.contains("\"puts\""), "{text}");
4626 assert!(!text.contains("__rucc_wrap_memcpy\""), "{text}");
4630 }
4631
4632 #[test]
4633 fn an_address_taken_of_a_library_function_is_counted_the_way_a_call_to_one_is() {
4634 let text = summary(
4639 rucc_session::Safety::Detect,
4640 "void *memcpy(void *, const void *, unsigned long);\n\
4641 int puts(const char *);\n\
4642 void *table[2] = { (void *)memcpy, (void *)puts };\n\
4643 void *f(int i) { return table[i]; }\n",
4644 );
4645 assert!(text.contains("\"interposed\": 1"), "{text}");
4646 assert!(text.contains("\"puts\""), "{text}");
4647 assert!(!text.contains("\"memcpy\""), "{text}");
4648 }
4649
4650 #[test]
4651 fn the_two_directions_a_pointer_crosses_the_boundary_are_counted_apart() {
4652 let text = summary(
4656 rucc_session::Safety::Detect,
4657 "void *notes_open(void);\n\
4658 char *f(char *p) { char *q = notes_open(); return q ? q : p; }\n",
4659 );
4660 assert!(text.contains("\"crossings\": { \"entered\": 1, \"returned\": 1 }"), "{text}");
4661 assert!(text.contains("\"notes_open\""), "{text}");
4662 }
4663
4664 #[test]
4665 fn a_static_function_nobody_takes_the_address_of_is_not_a_crossing() {
4666 let text = summary(
4669 rucc_session::Safety::Detect,
4670 "static int len(const char *p) { return p ? 1 : 0; }\n\
4671 int f(void) { return len(\"x\"); }\n",
4672 );
4673 assert!(text.contains("\"crossings\": { \"entered\": 0, \"returned\": 0 }"), "{text}");
4674 }
4675
4676 fn granules(source: &str) -> String {
4678 let mut opts = options();
4679 opts.emit = EmitKind::TypeGranules;
4680 let result = run(&opts, source);
4681 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4682 result.text().to_owned()
4683 }
4684
4685 #[test]
4686 fn the_granule_report_names_every_record_and_both_keyings() {
4687 let text = granules(
4688 "struct hot { char *p; int a; int b; };\n\
4689 int f(struct hot *h) { return h->a; }\n",
4690 );
4691 assert!(text.contains("struct hot"), "{text}");
4692 assert!(text.contains("every type distinct"), "{text}");
4695 assert!(text.contains("every pointer one type"), "{text}");
4696 assert!(text.contains("budget"), "{text}");
4697 }
4698
4699 #[test]
4700 fn a_record_nothing_uses_is_still_measured() {
4701 let text = granules("struct unused { long a; double b; };\nint f(void) { return 0; }\n");
4704 assert!(text.contains("struct unused"), "{text}");
4705 }
4706
4707 #[test]
4708 fn the_granule_report_stops_before_anything_is_lowered() {
4709 let text = granules(
4713 "struct wide { long double d; };\n\
4714 long double f(long double x) { return x * x; }\n",
4715 );
4716 assert!(text.contains("struct wide"), "{text}");
4717 }
4718
4719 #[test]
4720 fn a_witness_reaches_the_assembler_as_a_call_to_the_runtime() {
4721 let text = safe_asm(rucc_session::Safety::Detect, "char *f(char *p) { return p; }\n");
4724 assert!(text.contains("\tcall\t__rucc_cap_witness\n"), "{text}");
4725 }
4726
4727 #[test]
4728 fn a_pointer_turned_into_an_integer_is_on_the_trust_set() {
4729 let text = summary(
4730 rucc_session::Safety::Detect,
4731 "unsigned long f(int *p) { return (unsigned long) p; }\n",
4732 );
4733 assert!(text.contains("\"exposed\": 1"), "{text}");
4734 }
4735
4736 fn safe_asm(tier: rucc_session::Safety, source: &str) -> String {
4738 let mut opts = options();
4739 opts.emit = EmitKind::Asm;
4740 opts.safety = tier;
4741 let result = run(&opts, source);
4742 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4743 result.text().to_owned()
4744 }
4745
4746 #[test]
4747 fn a_check_reaches_the_assembler_as_a_call_to_the_runtime() {
4748 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4749 assert!(text.contains("\tcall\t__rucc_check_bounds\n"), "{text}");
4750 assert!(text.contains("\tcall\t__rucc_check_live\n"), "{text}");
4751 assert!(text.contains("\tcall\t__rucc_check_deriv\n"), "{text}");
4752 assert!(text.contains("\tcall\t__rucc_check_typed_init\n"), "{text}");
4755 }
4756
4757 #[test]
4758 fn every_check_that_reached_the_assembler_has_a_row_describing_it() {
4759 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4765 let section = format!("\t.section\t{},", rucc_safety::SECTION);
4766 assert_eq!(text.matches(§ion).count(), 4, "{text}");
4767 for index in 0..4 {
4768 let name = format!("__rucc_safety_desc_{index}");
4769 assert!(text.contains(&format!("{name}:\n")), "{text}");
4772 assert!(text.contains(&format!("{name}(%rip)")), "{text}");
4773 }
4774 assert!(!text.contains("__rucc_safety_desc_4"), "{text}");
4775 }
4776
4777 #[test]
4785 fn builtin_constant_p_is_folded_where_it_is_written_rather_than_called() {
4786 let text = ir(concat!(
4787 "int g;\n",
4788 "int a = __builtin_constant_p(1);\n",
4789 "int b = __builtin_constant_p(g);\n",
4790 "int c = __builtin_constant_p(\"abc\");\n",
4791 "int d = __builtin_constant_p(&g);\n",
4792 "int e = __builtin_constant_p(1.5);\n",
4793 "int h = __builtin_choose_expr(__builtin_constant_p(3), 11, 22);\n",
4794 ));
4795 assert!(text.contains("global @a : i32 = 1,"), "{text}");
4796 assert!(text.contains("global @b : i32 = 0,"), "{text}");
4797 assert!(text.contains("global @c : i32 = 1,"), "{text}");
4798 assert!(text.contains("global @d : i32 = 0,"), "{text}");
4799 assert!(text.contains("global @e : i32 = 1,"), "{text}");
4800 assert!(text.contains("global @h : i32 = 11,"), "{text}");
4801 assert!(!text.contains("__builtin_constant_p"), "it is not a call to anything:\n{text}");
4802
4803 let text = body("int f(void) { int i = 0; __builtin_constant_p(i++); return i; }\n");
4807 assert_eq!(text, "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 0\n return %0\n");
4808 }
4809
4810 #[test]
4819 fn a_call_to_a_library_builtin_reaches_the_library_function() {
4820 let text = body("void f(void) { __builtin_abort(); }\n");
4821 assert_eq!(text, "block0:\n call @abort() : ()\n return\n");
4822
4823 let text = ir("int f(const char *s) { return __builtin_puts(s) + __builtin_strlen(s); }\n");
4826 assert!(text.contains("call @puts(%0) : (ptr) -> i32"), "{text}");
4827 assert!(text.contains("call @strlen(%0) : (ptr) -> i64"), "{text}");
4828 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
4829 }
4830
4831 #[test]
4844 fn a_chk_builtin_reaches_the_checking_function_and_keeps_the_size() {
4845 let text = ir(concat!(
4846 "char d[8];\n",
4847 "void f(const char *s, unsigned long n) {\n",
4848 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
4849 " __builtin___strcpy_chk(d, s, __builtin_object_size(d, 1));\n",
4850 " __builtin___memset_chk(d, 0, n, 8);\n",
4851 "}\n",
4852 ));
4853 assert!(text.contains("call @__memcpy_chk("), "{text}");
4854 assert!(text.contains("call @__strcpy_chk("), "{text}");
4855 assert!(text.contains("call @__memset_chk("), "{text}");
4856 assert!(text.contains("iconst.i64 8"), "the object size reaches the call: {text}");
4857 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
4858 }
4859
4860 #[test]
4868 fn a_checking_call_whose_size_says_nothing_is_known_is_the_plain_library_call() {
4869 let text = ir(concat!(
4870 "extern char *p;\n",
4871 "char d[8];\n",
4872 "void f(const char *s, unsigned long n) {\n",
4873 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
4874 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4875 " __builtin___strcpy_chk(p, s, __builtin_object_size(p, 0));\n",
4876 " __builtin___stpncpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4877 " __builtin___sprintf_chk(p, 1, __builtin_object_size(p, 0), s);\n",
4878 "}\n",
4879 ));
4880
4881 assert!(
4883 text.contains("call @__memcpy_chk(%2, %0, %1, %3) : (ptr, ptr, i64, i64)"),
4884 "{text}"
4885 );
4886
4887 assert!(text.contains("call @memcpy(%6, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
4890 assert!(text.contains("call @strcpy(%10, %0) : (ptr, ptr) -> ptr"), "{text}");
4891 assert!(text.contains("call @stpncpy(%14, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
4892
4893 assert!(text.contains("call @__sprintf_chk("), "{text}");
4896
4897 let asm = asm(concat!(
4900 "void f(char *p, const char *s, unsigned long n) {\n",
4901 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4902 "}\n",
4903 ));
4904 assert!(asm.contains("call\tmemcpy"), "{asm}");
4905 assert!(!asm.contains("$-1"), "the size that went away leaves no instruction:\n{asm}");
4906 }
4907
4908 #[test]
4916 fn the_v_spellings_of_the_chk_family_take_the_list_a_va_list_parameter_holds() {
4917 let text = ir(concat!(
4918 "char d[64];\n",
4919 "int f(const char *fmt, ...) {\n",
4920 " __builtin_va_list ap;\n",
4921 " __builtin_va_start(ap, fmt);\n",
4922 " int n = __builtin___vsprintf_chk(d, 1, __builtin_object_size(d, 0), fmt, ap);\n",
4923 " __builtin_va_end(ap);\n",
4924 " return n;\n",
4925 "}\n",
4926 ));
4927 assert!(text.contains("call @__vsprintf_chk("), "{text}");
4928 assert!(text.contains("iconst.i64 64"), "the object size reaches the call: {text}");
4929 }
4930
4931 #[test]
4942 fn the_absolute_value_family_is_the_magnitude_and_not_a_call() {
4943 let text = body(concat!(
4944 "long long llabs(long long);\n",
4945 "long long f(long long x) { return llabs(x); }\n",
4946 ));
4947 assert!(text.contains("%1 = iconst.i64 63"), "{text}");
4948 assert!(text.contains("%2 = ashr %0, %1"), "{text}");
4949 assert!(text.contains("%3 = xor %0, %2"), "{text}");
4950 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4951 assert!(!text.contains("call"), "the call does not happen:\n{text}");
4952
4953 let text = body("int abs(int);\nint f(int x) { return abs(x); }\n");
4956 assert!(text.contains("iconst.i32 31"), "{text}");
4957 let text = body("long labs(long);\nlong f(long x) { return labs(x); }\n");
4958 assert!(text.contains("iconst.i64 63"), "{text}");
4959
4960 let text = body("long long f(long long x) { return __builtin_llabs(x); }\n");
4963 assert!(!text.contains("call"), "{text}");
4964
4965 let text = ir(concat!(
4967 "long long llabs(long long b);\n",
4968 "long long g(long long x) { return llabs(x); }\n",
4969 "long long llabs(long long b) { return 7; }\n",
4970 ));
4971 assert!(!text.contains("call @llabs"), "{text}");
4972 }
4973
4974 #[test]
4981 fn a_byte_swap_is_arithmetic_and_not_a_call() {
4982 let text = body("unsigned f(unsigned x) { return __builtin_bswap32(x); }\n");
4983 assert_eq!(text, "block0(%0: i32):\n %1 = bswap %0\n return %1\n");
4984
4985 let text = body("unsigned f(unsigned char c) { return __builtin_bswap32(c); }\n");
4988 assert!(text.contains("zext.i32 %0"), "widened first: {text}");
4989 assert!(text.contains("bswap %1"), "and swapped at four bytes: {text}");
4990 }
4991
4992 #[test]
4998 fn the_byte_swaps_reverse_at_the_width_their_name_says() {
4999 for (name, ty, width) in [
5000 ("__builtin_bswap16", "unsigned short", "i16"),
5001 ("__builtin_bswap32", "unsigned", "i32"),
5002 ("__builtin_bswap64", "unsigned long long", "i64"),
5003 ] {
5004 let source = format!("{ty} f({ty} x) {{ return {name}(x); }}\n");
5005 let text = body(&source);
5006 assert_eq!(
5007 text,
5008 format!("block0(%0: {width}):\n %1 = bswap %0\n return %1\n"),
5009 "{name}"
5010 );
5011 }
5012 }
5013
5014 #[test]
5021 fn the_bit_counts_are_instructions_and_not_calls() {
5022 let text = body("int f(unsigned x) { return __builtin_clz(x); }\n");
5023 assert_eq!(text, "block0(%0: i32):\n %1 = ctlz %0\n return %1\n");
5024
5025 let text = body("int f(unsigned x) { return __builtin_ctz(x); }\n");
5026 assert_eq!(text, "block0(%0: i32):\n %1 = cttz %0\n return %1\n");
5027
5028 let text = body("int f(unsigned x) { return __builtin_popcount(x); }\n");
5029 assert_eq!(text, "block0(%0: i32):\n %1 = ctpop %0\n return %1\n");
5030 }
5031
5032 #[test]
5041 fn the_bit_counts_ask_about_the_width_their_name_says() {
5042 let text = body("int f(unsigned long long x) { return __builtin_clzll(x); }\n");
5043 assert!(text.starts_with("block0(%0: i64):"), "counted at eight bytes: {text}");
5044 assert!(text.contains("%1 = ctlz %0"), "{text}");
5045 assert!(text.contains("trunc.i32 %1"), "and answered in an int: {text}");
5046
5047 let text = body("int f(unsigned long long x) { return __builtin_clz(x); }\n");
5050 assert!(text.contains("trunc.i32 %0"), "narrowed to what was asked about: {text}");
5051 assert!(text.contains("ctlz %1"), "and counted there: {text}");
5052
5053 let text = body("int f(unsigned long x) { return __builtin_popcountl(x); }\n");
5054 assert!(text.contains("%1 = ctpop %0"), "{text}");
5055 assert!(!text.contains("call"), "{text}");
5056 }
5057
5058 #[test]
5063 fn a_parity_is_the_low_bit_of_the_set_bit_count() {
5064 let text = body("int f(unsigned x) { return __builtin_parity(x); }\n");
5065 assert!(text.contains("%1 = ctpop %0"), "{text}");
5066 assert!(text.contains("iconst.i32 1"), "{text}");
5067 assert!(text.contains("and %1, %2"), "the low bit of it: {text}");
5068 }
5069
5070 #[test]
5076 fn the_first_set_bit_is_one_based_and_zero_for_a_zero() {
5077 let text = body("int f(int x) { return __builtin_ffs(x); }\n");
5078 assert!(text.contains("%1 = cttz %0"), "{text}");
5079 assert!(text.contains("%4 = add %1, %2"), "one more than the count: {text}");
5080 assert!(text.contains("%5 = icmp ne %0, %3"), "whether there was a bit at all: {text}");
5081 assert!(text.contains("%7 = sub %3, %6"), "spread to a mask: {text}");
5082 assert!(text.contains("%8 = and %4, %7"), "and kept only then: {text}");
5083 assert!(!text.contains("br_if"), "no branch: {text}");
5084 }
5085
5086 #[test]
5096 fn the_redundant_sign_bit_count_is_instructions_and_not_a_call() {
5097 let text = body("int f(int x) { return __builtin_clrsb(x); }\n");
5098 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
5099 assert!(text.contains("%2 = ashr %0, %1"), "the sign over every bit: {text}");
5100 assert!(text.contains("%3 = xor %0, %2"), "folded onto it: {text}");
5101 assert!(text.contains("%5 = shl %3, %4"), "one less than the count: {text}");
5102 assert!(text.contains("%6 = or %5, %4"), "with something to count at zero: {text}");
5103 assert!(text.contains("%7 = ctlz %6"), "{text}");
5104 assert!(!text.contains("call"), "{text}");
5105 assert!(!text.contains("br_if"), "no branch: {text}");
5106 }
5107
5108 #[test]
5114 fn the_unsigned_absolute_value_family_answers_in_the_unsigned_type() {
5115 let text = body("unsigned f(int x) { return __builtin_uabs(x); }\n");
5116 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
5117 assert!(text.contains("%4 = sub %3, %2"), "{text}");
5118 assert!(!text.contains("call"), "nothing declares uabs, so a call would not link: {text}");
5119
5120 let text = body("unsigned long long f(long long x) { return __builtin_ullabs(x); }\n");
5121 assert!(text.contains("iconst.i64 63"), "at the width the name says: {text}");
5122
5123 let text = body("int f(int x) { return __builtin_uabs(x) > 2147483647u; }\n");
5126 assert!(text.contains("icmp ugt"), "compared unsigned: {text}");
5127 }
5128
5129 #[test]
5137 fn the_widest_absolute_value_is_whichever_type_the_target_makes_intmax_t() {
5138 let text = body("long f(long x) { return __builtin_imaxabs(x); }\n");
5139 assert!(text.contains("iconst.i64 63"), "{text}");
5140 assert!(text.contains("%4 = sub %3, %2"), "{text}");
5141 assert!(!text.contains("call"), "{text}");
5142
5143 let text = body("unsigned long f(long x) { return __builtin_umaxabs(x); }\n");
5144 assert!(text.contains("iconst.i64 63"), "{text}");
5145 assert!(!text.contains("call"), "{text}");
5146 }
5147
5148 #[test]
5156 fn an_overflow_predicate_writes_nothing_and_answers_the_bit_the_check_would() {
5157 let text =
5158 body("int f(int a, int b) { return __builtin_add_overflow_p(a, b, (int) 0); }\n");
5159 assert!(text.contains("%2, %3 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
5160 assert!(!text.contains("store"), "nothing is written: {text}");
5161 assert!(!text.contains("call"), "{text}");
5162
5163 let text =
5166 body("int f(int a, int b) { return __builtin_mul_overflow_p(a, b, (long long) 0); }\n");
5167 assert!(text.contains("smul_overflow.(i64, i1)"), "{text}");
5168 assert!(!text.contains("store"), "{text}");
5169
5170 let text = body(concat!(
5173 "int g(void);\n",
5174 "int f(int a, int b) { return __builtin_sub_overflow_p(a, b, g()); }\n",
5175 ));
5176 assert!(!text.contains("call @g"), "the third argument is not evaluated: {text}");
5177 }
5178
5179 #[test]
5189 fn an_overflow_check_is_arithmetic_and_not_a_call() {
5190 let text =
5191 body("int f(int a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
5192 assert!(text.contains("%3, %4 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
5193 assert!(text.contains("store %3 -> %2"), "{text}");
5194 assert!(!text.contains("call"), "{text}");
5195
5196 let text =
5197 body("int f(int a, int b, int *r) { return __builtin_sub_overflow(a, b, r); }\n");
5198 assert!(text.contains("ssub_overflow.(i32, i1) %0, %1"), "{text}");
5199
5200 let text =
5201 body("int f(int a, int b, int *r) { return __builtin_mul_overflow(a, b, r); }\n");
5202 assert!(text.contains("smul_overflow.(i32, i1) %0, %1"), "{text}");
5203
5204 let text = body(
5207 "int f(unsigned a, unsigned b, unsigned *r) { return __builtin_add_overflow(a, b, r); }\n",
5208 );
5209 assert!(text.contains("uadd_overflow.(i32, i1) %0, %1"), "{text}");
5210 }
5211
5212 #[test]
5220 fn an_overflow_check_is_done_at_a_type_that_holds_every_operand() {
5221 let text = body(
5222 "int f(unsigned a, int b, long long *r) { return __builtin_add_overflow(a, b, r); }\n",
5223 );
5224 assert!(text.contains("%3 = zext.i64 %0"), "the unsigned operand keeps its value: {text}");
5225 assert!(text.contains("%4 = sext.i64 %1"), "and so does the signed one: {text}");
5226 assert!(text.contains("sadd_overflow.(i64, i1) %3, %4"), "{text}");
5227
5228 let text = body(
5231 "int f(long long a, long long b, long long *r) { return __builtin_mul_overflow(a, b, r); }\n",
5232 );
5233 assert!(text.contains("smul_overflow.(i64, i1) %0, %1"), "{text}");
5234 assert!(!text.contains("sext."), "{text}");
5235 assert!(!text.contains("zext.i64"), "{text}");
5237 }
5238
5239 #[test]
5247 fn an_overflow_check_writes_the_wrapped_answer_whether_or_not_it_fit() {
5248 let text =
5249 body("int f(int a, int b, char *r) { return __builtin_sub_overflow(a, b, r); }\n");
5250 assert!(text.contains("%3, %4 = ssub_overflow.(i32, i1) %0, %1"), "{text}");
5251 assert!(text.contains("%5 = trunc.i8 %3"), "narrowed to where it goes: {text}");
5252 assert!(text.contains("%6 = sext.i32 %5"), "and back: {text}");
5253 assert!(text.contains("%7 = icmp ne %6, %3"), "which is whether it fit: {text}");
5254 assert!(text.contains("store %5 -> %2"), "the narrowed value is stored either way: {text}");
5255 assert!(text.contains("%8 = or %4, %7"), "and either bit is an overflow: {text}");
5256 }
5257
5258 #[test]
5265 fn a_call_needing_more_than_the_widest_type_still_compiles() {
5266 for name in ["add", "sub", "mul"] {
5267 let source = format!(
5268 "int f(unsigned __int128 a, long long b, __int128 *r) {{\n \
5269 return __builtin_{name}_overflow(a, b, r);\n}}\n"
5270 );
5271 let mut opts = options();
5272 opts.emit = EmitKind::MirFinal;
5273 assert!(!run(&opts, &source).failed(), "{name} was refused or stopped the back end");
5274 }
5275 }
5276
5277 #[test]
5280 fn an_overflow_check_over_something_that_is_not_an_integer_says_so() {
5281 let messages =
5282 errors("int f(double a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
5283 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
5284
5285 let messages =
5286 errors("int f(int a, int b, double *r) { return __builtin_add_overflow(a, b, r); }\n");
5287 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
5288 }
5289
5290 #[test]
5301 fn an_ordered_access_is_ordered_in_the_ir() {
5302 let text = body("int f(int *p) { return __atomic_load_n(p, 0); }\n");
5303 assert!(text.contains("atomic_load.i32 %0, align 4, relaxed"), "{text}");
5304
5305 let text = body("long f(long *p) { return __atomic_load_n(p, 2); }\n");
5306 assert!(text.contains("atomic_load.i64 %0, align 8, acquire"), "{text}");
5307
5308 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
5309 assert!(text.contains("atomic_store %1 -> %0, align 4, release"), "{text}");
5310
5311 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
5312 assert!(text.contains("atomic_store %1 -> %0, align 4, seq_cst"), "{text}");
5313
5314 let text = body("void f(char *p, int v) { __atomic_store_n(p, v, 0); }\n");
5317 assert!(text.contains("trunc.i8 %1"), "{text}");
5318 assert!(text.contains("atomic_store %2 -> %0, align 1, relaxed"), "{text}");
5319 }
5320
5321 #[test]
5330 fn an_ordered_access_is_the_plain_instruction_on_this_machine() {
5331 let text = asm("int f(int *p) { return __atomic_load_n(p, 5); }\n");
5332 assert!(text.contains("movl\t(%rdi), %eax"), "{text}");
5333 assert!(!text.contains("mfence"), "a load needs no barrier here: {text}");
5334
5335 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
5336 assert!(text.contains("movl\t%esi, (%rdi)"), "{text}");
5337 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
5338
5339 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
5340 let (before, after) = text.split_once("mfence").expect("a barrier: {text}");
5341 assert!(before.contains("movl\t%esi, (%rdi)"), "the store comes first: {text}");
5342 assert!(!after.contains("movl"), "and nothing else is between them: {text}");
5343 }
5344
5345 #[test]
5355 fn a_barrier_is_one_instruction_at_the_strongest_ordering_and_none_below_it() {
5356 assert!(asm("void f(void) { __atomic_thread_fence(5); }\n").contains("mfence"));
5357 assert!(asm("void f(void) { __sync_synchronize(); }\n").contains("mfence"));
5358
5359 for weaker in ["1", "2", "3", "4"] {
5360 let source = format!("void f(void) {{ __atomic_thread_fence({weaker}); }}\n");
5361 assert!(!asm(&source).contains("mfence"), "{weaker} costs nothing here");
5362 }
5363 }
5364
5365 #[test]
5375 fn the_three_x86_fences_are_the_barrier_the_strongest_ordering_gives() {
5376 for name in ["__builtin_ia32_sfence", "__builtin_ia32_lfence", "__builtin_ia32_mfence"] {
5377 let source = format!("void f(void) {{ {name}(); }}\n");
5378 assert!(asm(&source).contains("mfence"), "{name} is a barrier");
5379 let text = body(&source);
5380 assert!(text.contains("fence seq_cst"), "{name}: {text}");
5381 }
5382
5383 let result = run(&options(), "void f(void) { __builtin_ia32_sfence(1); }\n");
5384 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
5385 assert!(result.messages[0].contains("too many arguments"), "{:?}", result.messages);
5386 }
5387
5388 #[test]
5394 fn a_compare_and_exchange_is_one_instruction_answering_two_things() {
5395 let text =
5398 body("int f(int *p, int e, int d) { return __sync_val_compare_and_swap(p, e, d); }\n");
5399 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
5400 assert!(text.contains("return %3"), "the value it found: {text}");
5401
5402 let text =
5403 body("int f(int *p, int e, int d) { return __sync_bool_compare_and_swap(p, e, d); }\n");
5404 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
5405 assert!(text.contains("zext.i32 %4"), "whether it happened: {text}");
5406
5407 let text = body(
5410 "int f(int *p, int *e, int d) { return __atomic_compare_exchange_n(p, e, d, 0, 4, 2); }\n",
5411 );
5412 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
5413 assert!(text.contains("%4, %5 = cmpxchg.(i32, i1) %0, %3, %2, align 4, acq_rel"), "{text}");
5414 assert!(text.contains("br_if %5, block2, block1"), "{text}");
5415 assert!(text.contains("store %4 -> %1, align 4"), "{text}");
5416
5417 let text = body(
5420 "int f(int *p, int *e, int *d) { return __atomic_compare_exchange(p, e, d, 0, 5, 5); }\n",
5421 );
5422 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
5423 assert!(text.contains("%4 = load.i32 %2, align 4"), "{text}");
5424 assert!(text.contains("%5, %6 = cmpxchg.(i32, i1) %0, %3, %4, align 4, seq_cst"), "{text}");
5425 }
5426
5427 #[test]
5434 fn a_compare_and_exchange_is_a_locked_instruction_at_the_width_of_the_object() {
5435 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
5436 for (ty, suffix, reg) in widths {
5437 let source = format!(
5438 "int f({ty} *p, {ty} e, {ty} d) {{ return __sync_bool_compare_and_swap(p, e, d); }}\n"
5439 );
5440 let text = asm(&source);
5441 assert!(text.contains("\tlock\n"), "{ty}: {text}");
5442 assert!(text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
5443 assert!(text.contains("sete\t"), "{ty}: {text}");
5444 }
5445 let source =
5446 "int f(long *p, long e, long d) { return __sync_bool_compare_and_swap(p, e, d); }\n";
5447 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
5448
5449 for order in ["0", "2", "3", "4", "5"] {
5453 let call = format!("__atomic_compare_exchange_n(p, e, d, 0, {order}, 0)");
5454 let source = format!("int f(int *p, int *e, int d) {{ return {call}; }}\n");
5455 let text = asm(&source);
5456 assert!(text.contains("cmpxchgl\t"), "{order}: {text}");
5457 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
5458 }
5459 }
5460
5461 #[test]
5473 fn a_read_modify_write_is_one_instruction_and_the_arithmetic_a_name_asks_for() {
5474 let text = body("int f(int *p, int v) { return __atomic_fetch_add(p, v, 5); }\n");
5475 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
5476 assert!(text.contains("return %2"), "the value that was there: {text}");
5477
5478 let text = body("int f(int *p, int v) { return __atomic_add_fetch(p, v, 5); }\n");
5479 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
5480 assert!(text.contains("%3 = add %2, %1"), "and the value afterwards: {text}");
5481
5482 let text = body("int f(int *p, int v) { return __atomic_sub_fetch(p, v, 5); }\n");
5483 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
5484 assert!(text.contains("%3 = sub %2, %1"), "{text}");
5485
5486 let text = body("int f(int *p, int v) { return __sync_fetch_and_sub(p, v); }\n");
5488 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
5489
5490 let text = body("int f(int *p, int v) { return __atomic_exchange_n(p, v, 5); }\n");
5493 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, seq_cst"), "{text}");
5494
5495 let text = body("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
5496 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, acquire"), "{text}");
5497
5498 let text = body("void f(int *p) { __sync_lock_release(p); }\n");
5501 assert!(text.contains("release"), "{text}");
5502 assert!(text.contains("%1 = iconst.i32 0"), "{text}");
5503
5504 let text = body("void f(int *p, int guard) { __sync_lock_release(p, guard); }\n");
5508 assert!(text.contains("%2 = iconst.i32 0"), "{text}");
5509 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
5510
5511 let text = body("int f(int *p, int v) { return __atomic_fetch_and(p, v, 5); }\n");
5514 assert!(text.contains("%2 = atomic_rmw.i32 and %0, %1, align 4, seq_cst"), "{text}");
5515
5516 let text = body("int f(int *p, int v) { return __sync_or_and_fetch(p, v); }\n");
5517 assert!(text.contains("%2 = atomic_rmw.i32 or %0, %1, align 4, seq_cst"), "{text}");
5518 assert!(text.contains("%3 = or %2, %1"), "and the value afterwards: {text}");
5519
5520 let text = body("int f(int *p, int v) { return __atomic_nand_fetch(p, v, 5); }\n");
5523 assert!(text.contains("%2 = atomic_rmw.i32 nand %0, %1, align 4, seq_cst"), "{text}");
5524 assert!(text.contains("%3 = and %2, %1"), "{text}");
5525 assert!(text.contains("%4 = iconst.i32 -1"), "{text}");
5526 assert!(text.contains("%5 = xor %3, %4"), "{text}");
5527 }
5528
5529 #[test]
5540 fn a_bitwise_read_modify_write_is_a_loop_around_the_compare_and_exchange() {
5541 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
5542 for (ty, suffix, reg) in widths {
5543 for (name, call, insn) in [
5544 ("and", "__atomic_fetch_and(p, v, 5)", "and"),
5545 ("or", "__sync_fetch_and_or(p, v)", "or"),
5546 ("xor", "__atomic_xor_fetch(p, v, 5)", "xor"),
5547 ] {
5548 let source = format!("{ty} f({ty} *p, {ty} v) {{ return {call}; }}\n");
5549 let text = asm(&source);
5550 assert!(text.contains("\tlock\n"), "{ty} {name}: {text}");
5551 assert!(
5552 text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")),
5553 "{ty} {name}: {text}"
5554 );
5555 assert!(text.contains(&format!("{insn}{suffix}\t")), "{ty} {name}: {text}");
5556 assert!(!text.contains("\txadd"), "{ty} {name} is not an add: {text}");
5558 assert!(!text.contains("\txchg"), "{ty} {name} is not an exchange: {text}");
5559 }
5560 }
5561 let source = "long f(long *p, long v) { return __atomic_fetch_or(p, v, 5); }\n";
5562 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
5563
5564 let text = asm("int f(int *p, int v) { return __sync_fetch_and_nand(p, v); }\n");
5568 assert!(text.contains("cmpxchgl\t"), "{text}");
5569 assert!(text.contains("andl\t"), "{text}");
5570 assert!(text.contains("notl\t"), "{text}");
5571 }
5572
5573 #[test]
5582 fn an_access_through_a_second_pointer_is_the_same_access_and_one_more() {
5583 let text = body("void f(int *p, int *r) { __atomic_load(p, r, 5); }\n");
5584 assert!(text.contains("%2 = atomic_load.i32 %0, align 4, seq_cst"), "{text}");
5585 assert!(text.contains("store %2 -> %1, align 4"), "and out through the place: {text}");
5586
5587 let text = body("void f(int *p, int *v) { __atomic_store(p, v, 3); }\n");
5588 assert!(text.contains("%2 = load.i32 %1, align 4"), "in through the place: {text}");
5589 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
5590
5591 let text = body("void f(int *p, int *v, int *r) { __atomic_exchange(p, v, r, 5); }\n");
5594 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
5595 assert!(text.contains("%4 = atomic_rmw.i32 xchg %0, %3, align 4, seq_cst"), "{text}");
5596 assert!(text.contains("store %4 -> %2, align 4"), "{text}");
5597 }
5598
5599 #[test]
5610 fn a_flag_is_an_exchange_of_one_byte_and_a_store_of_a_zero_over_the_same_byte() {
5611 for pointer in ["char", "int", "void"] {
5612 let source = format!("int f({pointer} *p) {{ return __atomic_test_and_set(p, 5); }}\n");
5613 let text = body(&source);
5614 assert!(text.contains("%1 = iconst.i8 1"), "{pointer}: {text}");
5615 assert!(
5616 text.contains("%2 = atomic_rmw.i8 xchg %0, %1, align 1, seq_cst"),
5617 "{pointer}: {text}"
5618 );
5619 assert!(text.contains("%4 = icmp ne %2, %3"), "{pointer}: {text}");
5620
5621 let source = format!("void f({pointer} *p) {{ __atomic_clear(p, 3); }}\n");
5622 let text = body(&source);
5623 assert!(text.contains("atomic_store %2 -> %0, align 1, release"), "{pointer}: {text}");
5624 }
5625
5626 let text = asm("int f(int *p) { return __atomic_test_and_set(p, 5); }\n");
5629 assert!(text.contains("xchgb\t%al, (%rdi)"), "{text}");
5630 assert!(text.contains("setne\t"), "{text}");
5631 }
5632
5633 #[test]
5641 fn a_read_modify_write_is_an_exchange_or_a_locked_add_at_the_width_of_the_object() {
5642 let widths = [("char", "b", "%sil"), ("short", "w", "%si"), ("int", "l", "%esi")];
5643 for (ty, suffix, reg) in widths {
5644 let source =
5645 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_fetch_add(p, v, 5); }}\n");
5646 let text = asm(&source);
5647 assert!(text.contains("\tlock\n"), "{ty}: {text}");
5648 assert!(text.contains(&format!("xadd{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
5649
5650 let source =
5651 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_exchange_n(p, v, 5); }}\n");
5652 let text = asm(&source);
5653 assert!(text.contains(&format!("xchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
5654 assert!(!text.contains("\tlock\n"), "an exchange is locked already: {ty}: {text}");
5655 }
5656 let source = "long f(long *p, long v) { return __atomic_fetch_add(p, v, 5); }\n";
5657 assert!(asm(source).contains("xaddq\t%rsi, (%rdi)"), "{}", asm(source));
5658
5659 let source = "int f(int *p, int v) { return __atomic_fetch_sub(p, v, 5); }\n";
5662 let text = asm(source);
5663 assert!(text.contains("negl\t"), "{text}");
5664 assert!(text.contains("xaddl\t"), "{text}");
5665
5666 for order in ["0", "2", "3", "4", "5"] {
5669 let source =
5670 format!("int f(int *p, int v) {{ return __atomic_fetch_add(p, v, {order}); }}\n");
5671 let text = asm(&source);
5672 assert!(text.contains("xaddl\t"), "{order}: {text}");
5673 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
5674 }
5675
5676 let text = asm("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
5680 assert!(text.contains("xchgl\t%esi, (%rdi)"), "{text}");
5681 let text = asm("void f(int *p) { __sync_lock_release(p); }\n");
5688 assert!(text.contains("xorl\t%eax, %eax"), "{text}");
5689 assert!(text.contains("movl\t%eax, (%rdi)"), "{text}");
5690 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
5691 }
5692
5693 #[test]
5705 fn the_lock_free_questions_are_answered_as_constants() {
5706 for size in ["1", "2", "4", "8"] {
5707 let source =
5708 format!("int f(void) {{ return __atomic_always_lock_free({size}, 0); }}\n");
5709 let text = asm(&source);
5710 assert!(text.contains("movb\t$1, %al"), "{size} bytes is lock free: {text}");
5711 assert!(!text.contains("call"), "and is not a call: {text}");
5712 }
5713 for size in ["3", "16", "sizeof(long double)"] {
5714 let source = format!("int f(void) {{ return __atomic_is_lock_free({size}, 0); }}\n");
5715 let text = asm(&source);
5716 assert!(text.contains("movb\t$0, %al"), "{size} bytes is not: {text}");
5717 assert!(!text.contains("call"), "and is not a call either: {text}");
5718 }
5719
5720 let text = asm("int f(int n) { return __atomic_is_lock_free(n, 0); }\n");
5724 assert!(text.contains("movb\t$0, %al"), "a size nobody knows is not lock free: {text}");
5725 let text = asm("int f(int *p) { return __atomic_always_lock_free(8, p); }\n");
5726 assert!(text.contains("movb\t$0, %al"), "eight bytes at four is not: {text}");
5727 let text = asm("int f(long *p) { return __atomic_always_lock_free(8, p); }\n");
5728 assert!(text.contains("movb\t$1, %al"), "and at eight it is: {text}");
5729 }
5730
5731 #[test]
5743 fn a_memory_order_an_operation_cannot_carry_is_read_as_the_strongest() {
5744 let mut opts = options();
5745 opts.emit = EmitKind::Ir;
5746
5747 let acquire_store = run(&opts, "void f(int *p, int v) { __atomic_store_n(p, v, 2); }\n");
5748 assert!(acquire_store.text().contains("seq_cst"), "{:?}", acquire_store.text());
5749 assert!(acquire_store.messages[0].contains("[W0333]"), "{:?}", acquire_store.messages);
5750
5751 let nonsense = run(&opts, "int f(int *p) { return __atomic_load_n(p, 99); }\n");
5752 assert!(nonsense.text().contains("seq_cst"), "{:?}", nonsense.text());
5753 assert!(nonsense.messages[0].contains("[W0333]"), "{:?}", nonsense.messages);
5754
5755 let computed = run(&opts, "int f(int *p, int n) { return __atomic_load_n(p, n); }\n");
5756 assert!(computed.text().contains("seq_cst"), "{:?}", computed.text());
5757 assert_eq!(computed.messages, Vec::<String>::new(), "a computed order is not a mistake");
5758 }
5759
5760 #[test]
5772 fn a_conversion_between_a_float_and_the_widest_unsigned_integer_is_written_without_a_branch() {
5773 let text = asm("double f(unsigned long long x) { return (double)x; }\n");
5774 assert!(text.contains("cvtsi2sdq"), "the signed conversion is what runs: {text}");
5775 assert!(text.contains("shrq"), "with the value halved first: {text}");
5776 assert!(text.contains("addsd"), "and doubled after: {text}");
5777 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
5778
5779 let text = asm("unsigned long long f(double d) { return (unsigned long long)d; }\n");
5780 assert!(text.contains("cvttsd2siq"), "the signed conversion is what runs: {text}");
5781 assert!(text.contains("subsd"), "with half the range taken off first: {text}");
5782 assert!(text.contains("shlq\t$63"), "and the top bit put back: {text}");
5783 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
5784 }
5785
5786 #[test]
5797 fn a_plain_name_the_program_took_is_the_programs_own_function() {
5798 let taken = concat!(
5799 "static long long llabs(long long b) { return 7; }\n",
5800 "long long f(long long x) { return llabs(x); }\n",
5801 );
5802 assert!(ir(taken).contains("call @llabs"), "a static definition is the program's own");
5803
5804 let retyped = concat!("int llabs(int b);\n", "int f(int x) { return llabs(x); }\n",);
5805 assert!(ir(retyped).contains("call @llabs"), "another type is another function");
5806
5807 let plain = concat!(
5808 "long long llabs(long long b);\n",
5809 "long long f(long long x) { return llabs(x); }\n",
5810 );
5811 let mut opts = options();
5812 opts.emit = EmitKind::Ir;
5813 assert!(!run(&opts, plain).text().contains("call @llabs"), "the library's by default");
5814
5815 opts.builtins = false;
5816 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin");
5817
5818 opts.builtins = true;
5819 opts.no_builtin = vec!["llabs".to_owned()];
5820 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin-llabs");
5821 let one = "long labs(long b);\nlong f(long x) { return labs(x); }\n";
5822 assert!(!run(&opts, one).text().contains("call @labs"), "one name and not the family");
5823
5824 opts.no_builtin = Vec::new();
5827 opts.builtins = false;
5828 let prefixed = "long long f(long long x) { return __builtin_llabs(x); }\n";
5829 assert!(!run(&opts, prefixed).text().contains("call @llabs"), "the prefix is a promise");
5830 }
5831
5832 #[test]
5845 fn the_hint_builtins_are_their_first_argument_and_the_hint_leaves_no_trace() {
5846 let text = ir(concat!(
5847 "long a = __builtin_expect(7, 1);\n",
5848 "long b = __builtin_expect_with_probability(9, 1, 0.9);\n",
5849 "unsigned long c = sizeof(__builtin_expect((char)1, 1));\n",
5850 ));
5851 assert!(text.contains("global @a : i64 = 7,"), "{text}");
5852 assert!(text.contains("global @b : i64 = 9,"), "{text}");
5853 assert!(text.contains("global @c : i64 = 8,"), "{text}");
5854 assert!(!text.contains("__builtin_expect"), "it is not a call to anything:\n{text}");
5855
5856 let text = body("long f(char c) { return __builtin_expect(c, 1); }\n");
5859 assert!(text.contains("sext"), "{text}");
5860
5861 let one = "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 1\n %2 = sext.i64 %1\n return %0\n";
5865 assert_eq!(body("int f(void) { int i = 0; __builtin_expect(1, i++); return i; }\n"), one);
5866 let source = "int g(void) { int i = 0; __builtin_expect_with_probability(1, i++, 0.5); return i; }\n";
5867 assert_eq!(body(source), one);
5868
5869 let kept = body("int f(int n) { int i = 0; __builtin_expect(n, i++); return i; }\n");
5874 assert!(kept.contains("add.nsw"), "the hint still runs: {kept}");
5875 assert!(kept.ends_with("return %3\n"), "and the answer is what it left behind: {kept}");
5876 let both = "int g(int n) { int i = 0; __builtin_expect_with_probability(n, i++, 0.5); return i; }\n";
5877 assert!(body(both).contains("add.nsw"), "and so does the one with three arguments");
5878 }
5879
5880 #[test]
5892 fn a_promise_that_control_does_not_arrive_writes_no_instruction() {
5893 let promised = "int f(int x) { if (x) return 1; __builtin_unreachable(); }\n";
5894 let text = ir(promised);
5895 assert!(text.contains(" unreachable_hint\n"), "{text}");
5896 assert!(!text.contains("call"), "it is not a call to anything:\n{text}");
5897
5898 let after = body("int g(int x) { __builtin_unreachable(); return x; }\n");
5902 assert!(after.contains("return"), "{after}");
5903
5904 let text = asm(promised);
5907 let mine = text.split_once("\nf:\n").expect("a definition").1;
5908 let mine = mine.split_once("\t.size").expect("a definition").0;
5909 let plain = asm("int f(int x) { if (x) return 1; }\n");
5910 let plain = plain.split_once("\nf:\n").expect("a definition").1;
5911 let plain = plain.split_once("\t.size").expect("a definition").0;
5912 assert_eq!(mine, plain);
5913 let last = mine.lines().rfind(|line| !line.trim_start().starts_with('.'));
5916 assert_eq!(last.map(str::trim), Some("ret"), "{mine}");
5917 assert!(!mine.contains("ud2"), "{mine}");
5918 }
5919
5920 #[test]
5927 fn a_library_builtin_is_diagnosed_under_the_name_the_program_wrote() {
5928 let mut opts = options();
5929 opts.emit = EmitKind::Ir;
5930 let messages = run(&opts, "void f(void) { __builtin_abort(1); }\n").messages;
5931 assert!(
5932 messages.iter().any(|m| m.contains("__builtin_abort")),
5933 "expected the written name in {messages:?}"
5934 );
5935 }
5936
5937 #[test]
5945 fn a_builtin_nothing_lowers_is_refused_by_name() {
5946 let mut opts = options();
5947 opts.emit = EmitKind::Ir;
5948 let builtin = "__atomic_signal_fence";
5949 let source = format!("int counter;\nint f(void) {{ return ({builtin}(5), 0); }}\n");
5950 let messages = run(&opts, &source).messages;
5951 let named = messages.iter().any(|m| m.contains(builtin) && m.contains("E0686"));
5952 assert!(named, "expected {builtin} to be refused by name in {messages:?}");
5953 }
5954
5955 #[test]
5964 fn what_is_refused_is_the_call_and_not_the_name() {
5965 let text = ir(concat!(
5966 "void __atomic_signal_fence(int order) { (void)order; }\n",
5967 "void f(void) { __atomic_signal_fence(5); }\n",
5968 ));
5969 assert!(text.contains("call @__atomic_signal_fence"), "{text}");
5970 }
5971
5972 #[test]
5981 fn the_object_size_of_an_address_is_what_the_layout_leaves_in_front_of_it() {
5982 let text = ir(concat!(
5983 "struct S { char a[8]; int n; char b[12]; };\n",
5984 "char g[32];\n",
5985 "struct S gs;\n",
5986 "unsigned long whole = __builtin_object_size(g, 0);\n",
5987 "unsigned long moved = __builtin_object_size(g + 4, 0);\n",
5988 "unsigned long back = __builtin_object_size(g + 30 - 2, 0);\n",
5989 "unsigned long outer = __builtin_object_size(gs.a, 0);\n",
5990 "unsigned long inner = __builtin_object_size(gs.a, 1);\n",
5991 "unsigned long scalar = __builtin_object_size(&gs.n, 1);\n",
5992 "unsigned long after = __builtin_object_size(&gs.n, 0);\n",
5993 "unsigned long into = __builtin_object_size(&gs.b[2], 1);\n",
5994 "unsigned long text = __builtin_object_size(\"hello\", 0);\n",
5995 "unsigned long dyn = __builtin_dynamic_object_size(gs.b, 1);\n",
5996 ));
5997 for (name, size) in [
5998 ("whole", 32),
5999 ("moved", 28),
6000 ("back", 4),
6001 ("outer", 24),
6002 ("inner", 8),
6003 ("scalar", 4),
6004 ("after", 16),
6005 ("into", 10),
6006 ("text", 6),
6007 ("dyn", 12),
6008 ] {
6009 let said = format!("global @{name} : i64 = {size},");
6010 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
6011 }
6012 }
6013
6014 #[test]
6022 fn the_object_behind_an_address_can_be_one_with_automatic_storage() {
6023 let text = body(concat!(
6024 "struct S { char a[8]; int n; char b[12]; };\n",
6025 "unsigned long f(void) {\n",
6026 " char loc[20];\n",
6027 " struct S ls;\n",
6028 " return __builtin_object_size(loc + 3, 0) + __builtin_object_size(ls.b + 2, 1);\n",
6029 "}\n",
6030 ));
6031 assert!(text.contains("iconst.i64 17"), "twenty bytes with three used: {text}");
6032 assert!(text.contains("iconst.i64 10"), "twelve bytes with two used: {text}");
6033 }
6034
6035 #[test]
6045 fn an_address_with_no_object_in_sight_answers_at_the_end_of_the_range_its_kind_asks_for() {
6046 let text = ir(concat!(
6047 "struct T { int n; char f[]; };\n",
6048 "extern char *p;\n",
6049 "extern struct T *t;\n",
6050 "unsigned long largest = __builtin_object_size(p, 0);\n",
6051 "unsigned long nearest = __builtin_object_size(p, 1);\n",
6052 "unsigned long least = __builtin_object_size(p, 2);\n",
6053 "unsigned long tight = __builtin_object_size(p, 3);\n",
6054 "unsigned long flex = __builtin_object_size(t->f, 1);\n",
6055 "int says = __builtin_object_size(p, 0) == (unsigned long)-1;\n",
6056 ));
6057 for name in ["largest", "nearest", "flex"] {
6058 let said = format!("global @{name} : i64 = -1,");
6062 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
6063 }
6064 for name in ["least", "tight"] {
6065 let said = format!("global @{name} : i64 = 0,");
6066 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
6067 }
6068 assert!(text.contains("global @says : i32 = 1,"), "{text}");
6069 }
6070
6071 #[test]
6078 fn the_address_an_object_size_is_asked_about_is_not_evaluated() {
6079 let text = body(concat!(
6080 "extern char *side(void);\n",
6081 "unsigned long f(void) { return __builtin_object_size(side(), 0); }\n",
6082 ));
6083 assert!(!text.contains("call"), "nothing is called: {text}");
6084 }
6085
6086 #[test]
6091 fn a_kind_that_is_not_one_of_the_four_is_refused() {
6092 for source in [
6093 "extern char *p;\nextern int k;\nunsigned long f(void) ".to_owned()
6094 + "{ return __builtin_object_size(p, k); }\n",
6095 "extern char *p;\nunsigned long f(void) { return __builtin_object_size(p, 4); }\n"
6096 .to_owned(),
6097 "extern char *p;\nunsigned long f(void) ".to_owned()
6098 + "{ return __builtin_dynamic_object_size(p, -1); }\n",
6099 ] {
6100 let messages = errors(&source);
6101 let named = messages.iter().any(|m| m.contains("E0709") && m.contains("0 to 3"));
6102 assert!(named, "expected a complaint about the kind in {messages:?}");
6103 }
6104 }
6105
6106 #[test]
6112 fn the_pair_that_saves_a_place_lowers_to_the_two_markers() {
6113 let text = ir(concat!(
6114 "void *buf[5];\n",
6115 "int f(void) {\n",
6116 " if (__builtin_setjmp(buf)) return 2;\n",
6117 " return 1;\n",
6118 "}\n",
6119 "void g(void) { __builtin_longjmp(buf, 1); }\n",
6120 ));
6121 assert!(text.contains("= setjmp_marker.i32 %0\n"), "the save answers a value: {text}");
6122 assert!(text.contains(" longjmp_marker %0\n"), "the restore answers nothing: {text}");
6123 assert!(!text.contains("call @"), "neither of them is a call: {text}");
6124 }
6125
6126 #[test]
6134 fn a_local_of_a_function_that_saves_a_place_gets_a_slot() {
6135 let text = ir(concat!(
6136 "void *buf[5];\n",
6137 "int f(int x) { int a = 0; if (__builtin_setjmp(buf)) return a; a = 1; return x; }\n",
6138 "int g(int x) { int a = 0; if (x) return a; a = 1; return x; }\n",
6139 ));
6140 let (saves, plain) = text.split_once("func @g").expect("both functions");
6141 assert_eq!(saves.matches("= alloca").count(), 2, "the parameter and the local: {text}");
6142 assert!(saves.contains("store %9 -> %2"), "the local is written through: {text}");
6143 assert!(!plain.contains("alloca"), "nothing in the plain one needs a slot: {text}");
6144 }
6145
6146 #[test]
6155 fn the_save_writes_four_words_and_carries_on_in_a_new_block() {
6156 let text =
6157 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
6158 let body = text.split_once("\nf:\n").expect("the function").1;
6159 assert!(body.contains("\tmovq\t%rsp, %rbp\n"), "a frame pointer whatever: {text}");
6160 assert!(body.contains("\tsubq\t$8, %rsp\n"), "no red zone: {text}");
6161 assert!(body.contains("\tmovq\t%rbp, (%rax)\n"), "the frame pointer: {text}");
6162 assert!(body.contains("\tmovq\t%rsp, 16(%rax)\n"), "the stack pointer: {text}");
6163 assert!(body.contains("\tleaq\t.Lf_1(%rip), %rcx\n"), "where to come back to: {text}");
6164 assert!(body.contains("\tmovq\t%rcx, 8(%rax)\n"), "and that goes in the buffer: {text}");
6165 let back = body.split_once(".Lf_1:\n").expect("the block control comes back to").1;
6166 assert!(back.starts_with("\tmovq\t(%rsp), %rax\n"), "the answer is read back: {text}");
6167 }
6168
6169 #[test]
6177 fn a_save_destroys_every_register_the_allocator_hands_out() {
6178 let text =
6179 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
6180 for reg in ["%rbx", "%r12", "%r13", "%r14", "%r15"] {
6181 assert!(text.contains(&format!("\tpushq\t{reg}\n")), "{reg} is saved: {text}");
6182 assert!(text.contains(&format!("\tpopq\t{reg}\n")), "{reg} is restored: {text}");
6183 }
6184 }
6185
6186 #[test]
6193 fn the_restore_puts_the_frame_back_before_it_jumps() {
6194 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
6195 let mut opts = options();
6196 opts.emit = EmitKind::Asm;
6197 opts.opt_level = level;
6198 let source = "void *buf[5];\nvoid g(void) { __builtin_longjmp(buf, 1); }\n";
6199 let result = run(&opts, source);
6200 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
6201 let text = result.text().to_owned();
6202 let jump = text.find("\tjmp\t*%").unwrap_or_else(|| panic!("an indirect jump: {text}"));
6203 let stack = text.find(", %rsp\n").unwrap_or_else(|| panic!("the stack back: {text}"));
6204 let frame = text.find(", %rbp\n").unwrap_or_else(|| panic!("the frame back: {text}"));
6205 assert!(stack < jump, "the stack goes back first at {level:?}: {text}");
6206 assert!(frame < jump, "and so does the frame at {level:?}: {text}");
6207 }
6208 }
6209
6210 #[test]
6216 fn a_longjmp_whose_second_argument_is_not_one_is_turned_down() {
6217 for source in [
6218 "void *buf[5];\nvoid f(void) { __builtin_longjmp(buf, 0); }\n",
6219 "void *buf[5];\nextern int v;\nvoid f(void) { __builtin_longjmp(buf, v); }\n",
6220 ] {
6221 let messages = errors(source);
6222 let named = messages.iter().any(|m| m.contains("E0710"));
6223 assert!(named, "expected a complaint about the value in {messages:?}");
6224 }
6225 }
6226
6227 #[test]
6232 fn a_static_function_nothing_refers_to_is_not_emitted() {
6233 let text = ir("static int dropped(void) { return 1; }\n\
6234 static int kept(void) { return 2; }\n\
6235 int main(void) { return kept(); }\n");
6236 assert!(text.contains("func @kept"), "{text}");
6237 assert!(!text.contains("dropped"), "{text}");
6238 }
6239
6240 #[test]
6246 fn two_static_functions_that_only_call_each_other_are_both_dropped() {
6247 let text = ir("static int ping(void);\n\
6248 static int pong(void) { return ping(); }\n\
6249 static int ping(void) { return pong(); }\n\
6250 int main(void) { return 0; }\n");
6251 assert!(!text.contains("ping"), "{text}");
6252 assert!(!text.contains("pong"), "{text}");
6253 }
6254
6255 #[test]
6261 fn naming_a_static_function_anywhere_keeps_it() {
6262 let text = ir("static int by_address(void) { return 1; }\n\
6263 static int in_an_image(void) { return 2; }\n\
6264 static int deeper(void) { return 3; }\n\
6265 static int reaches_deeper(void) { return deeper(); }\n\
6266 static int (*table[1])(void) = {in_an_image};\n\
6267 int main(void) {\n\
6268 int (*p)(void) = by_address;\n\
6269 return p() + table[0]() + reaches_deeper();\n\
6270 }\n");
6271 for kept in ["by_address", "in_an_image", "deeper", "reaches_deeper"] {
6272 assert!(text.contains(&format!("func @{kept}")), "expected {kept} in:\n{text}");
6273 }
6274 }
6275
6276 #[test]
6282 fn an_attribute_keeps_a_static_function_nothing_refers_to() {
6283 for attribute in ["used", "retain", "constructor", "destructor", "__used__"] {
6284 let source = format!(
6285 "__attribute__(({attribute})) static int kept(void) {{ return 1; }}\n\
6286 int main(void) {{ return 0; }}\n"
6287 );
6288 let text = ir(&source);
6289 assert!(text.contains("func @kept"), "for {attribute}:\n{text}");
6290 }
6291 }
6292
6293 #[test]
6296 fn a_function_anything_could_call_is_emitted_without_being_called() {
6297 let text =
6298 ir("int nobody_here_calls_it(void) { return 1; }\nint main(void) { return 0; }\n");
6299 assert!(text.contains("func @nobody_here_calls_it"), "{text}");
6300 }
6301
6302 #[test]
6309 fn a_classification_c_has_an_operator_for_is_that_operator() {
6310 for (builtin, operator) in [
6311 ("__builtin_isgreater", "binary >"),
6312 ("__builtin_isgreaterequal", "binary >="),
6313 ("__builtin_isless", "binary <"),
6314 ("__builtin_islessequal", "binary <="),
6315 ] {
6316 let source = format!("int f(double x, double y) {{ return {builtin}(x, y); }}\n");
6317 let text = tast(&source);
6318 assert!(text.contains(&format!("{operator} : int")), "for {builtin}:\n{text}");
6319 }
6320 }
6321
6322 #[test]
6331 fn the_classification_builtins_are_comparisons_and_not_calls() {
6332 let text = body("int f(double x, double y) { return __builtin_isunordered(x, y); }\n");
6333 assert_eq!(
6334 text,
6335 "block0(%0: f64, %1: f64):\n %2 = fcmp uno %0, %1\n %3 = zext.i32 \
6336 %2\n return %3\n"
6337 );
6338
6339 let text = body("int f(double x, double y) { return __builtin_islessgreater(x, y); }\n");
6341 assert!(text.contains("fcmp one %0, %1"), "{text}");
6342
6343 let text = body("int f(double x) { return __builtin_isnan(x); }\n");
6344 assert!(text.contains("fcmp uno %0, %0"), "{text}");
6345
6346 let text = body("int f(double x) { return __builtin_isinf(x); }\n");
6347 assert!(text.contains("fconst.f64 0x7ff0000000000000"), "{text}");
6348 assert!(text.contains("fconst.f64 0xfff0000000000000"), "{text}");
6349 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
6350 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
6351 assert!(text.contains("%5 = or %3, %4"), "{text}");
6352
6353 let text = body("int f(double x) { return __builtin_isfinite(x); }\n");
6356 assert!(text.contains("%3 = fcmp olt %2, %0"), "{text}");
6357 assert!(text.contains("%4 = fcmp olt %0, %1"), "{text}");
6358 assert!(text.contains("%5 = and %3, %4"), "{text}");
6359
6360 let text = body("int f(double x) { return __builtin_signbit(x); }\n");
6361 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
6362 assert!(text.contains("icmp slt %1, %2"), "{text}");
6363
6364 let text = body("int f(long double x) { return __builtin_signbitl(x); }\n");
6367 assert!(text.contains("%1 = bitcast.i80 %0"), "{text}");
6368
6369 let text = body("double g(void);\nint f(void) { return __builtin_isnan(g()); }\n");
6372 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
6373 }
6374
6375 #[test]
6382 fn a_classification_spelling_that_names_a_width_converts_before_it_asks() {
6383 let text = ir(concat!(
6384 "int a = __builtin_isinff(1e300);\n",
6385 "int b = __builtin_isinf(1e300);\n",
6386 "int c = __builtin_isnan(0.0);\n",
6390 "int d = __builtin_signbit(-0.0);\n",
6391 "int e = __builtin_islessgreater(1.0, 2.0);\n",
6392 ));
6393 assert!(text.contains("global @a : i32 = 1,"), "{text}");
6394 assert!(text.contains("global @b : i32 = 0,"), "{text}");
6395 assert!(text.contains("global @c : i32 = 0,"), "{text}");
6396 assert!(text.contains("global @d : i32 = 1,"), "{text}");
6397 assert!(text.contains("global @e : i32 = 1,"), "{text}");
6398 }
6399
6400 #[test]
6402 fn a_classification_builtin_refuses_an_argument_that_is_not_floating_point() {
6403 let mut opts = options();
6404 opts.emit = EmitKind::Ir;
6405 let source = concat!(
6406 "int a(int x) { return __builtin_isnan(x); }\n",
6407 "int b(int x, int y) { return __builtin_isunordered(x, y); }\n",
6408 "int c(double x) { return __builtin_isnan(x, x); }\n",
6409 );
6410 let messages = run(&opts, source).messages;
6411 assert_eq!(
6412 messages,
6413 [
6414 "/main.c:1:23: error: non-floating-point argument in call to function \
6415 '__builtin_isnan' [E0685]",
6416 "/main.c:2:30: error: non-floating-point arguments in call to function \
6417 '__builtin_isunordered' [E0685]",
6418 "/main.c:3:26: error: too many arguments to function '__builtin_isnan' [E0511]",
6419 ]
6420 );
6421 }
6422
6423 #[test]
6432 fn the_last_three_classification_builtins_are_comparisons_and_not_calls() {
6433 let text = body("int f(double x) { return __builtin_isnormal(x); }\n");
6434 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
6438 assert!(text.contains("%2 = iconst.i64 9223372036854775807"), "{text}");
6439 assert!(text.contains("%3 = and %1, %2"), "{text}");
6440 assert!(text.contains("%4 = iconst.i64 4503599627370496"), "{text}");
6441 assert!(text.contains("%5 = iconst.i64 9218868437227405312"), "{text}");
6442 assert!(text.contains("%6 = icmp uge %3, %4"), "{text}");
6443 assert!(text.contains("%7 = icmp ult %3, %5"), "{text}");
6444 assert!(text.contains("%8 = and %6, %7"), "{text}");
6445
6446 let text = body("int f(long double x) { return __builtin_isnormal(x); }\n");
6450 assert!(text.contains("%4 = iconst.i80 27670116110564327424"), "{text}");
6451 assert!(text.contains("%5 = iconst.i80 604453686435277732577280"), "{text}");
6452
6453 let text = body("int f(double x) { return __builtin_isinf_sign(x); }\n");
6454 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
6455 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
6456 assert!(text.contains("%7 = sub %5, %6"), "{text}");
6457
6458 let text = body("int f(double x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n");
6459 assert!(text.contains("fcmp uno %0, %0"), "{text}");
6460 assert!(text.contains("fcmp oeq %0, %6"), "{text}");
6461 assert_eq!(text.matches(" = zext.i32 ").count(), 4, "{text}");
6465 assert_eq!(text.matches(" = xor ").count(), 4, "{text}");
6466 assert!(!text.contains("call"), "{text}");
6467
6468 let text = body(concat!(
6471 "double g(void);\n",
6472 "int f(void) { return __builtin_fpclassify(0, 1, 2, 3, 4, g()); }\n",
6473 ));
6474 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
6475 }
6476
6477 #[test]
6484 fn the_last_three_classification_builtins_fold_where_their_operand_is_a_constant() {
6485 let text = ir(concat!(
6486 "int a = __builtin_isnormal(1.0);\n",
6487 "int b = __builtin_isnormal(0.0);\n",
6488 "int c = __builtin_isnormal(1.0 / 0.0);\n",
6489 "int d = __builtin_isinf_sign(-1.0 / 0.0);\n",
6490 "int e = __builtin_isinf_sign(1.0);\n",
6491 "int g = __builtin_fpclassify(0, 1, 2, 3, 4, 0.0);\n",
6492 "int h = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0);\n",
6493 "int i = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0 / 0.0);\n",
6494 ));
6495 assert!(text.contains("global @a : i32 = 1,"), "{text}");
6496 assert!(text.contains("global @b : i32 = 0,"), "{text}");
6497 assert!(text.contains("global @c : i32 = 0,"), "{text}");
6498 assert!(text.contains("global @d : i32 = -1,"), "{text}");
6499 assert!(text.contains("global @e : i32 = 0,"), "{text}");
6500 assert!(text.contains("global @g : i32 = 4,"), "{text}");
6501 assert!(text.contains("global @h : i32 = 2,"), "{text}");
6502 assert!(text.contains("global @i : i32 = 1,"), "{text}");
6503 }
6504
6505 #[test]
6511 fn fpclassify_refuses_an_answer_that_is_not_an_integer_constant() {
6512 let mut opts = options();
6513 opts.emit = EmitKind::Ir;
6514 let source = concat!(
6515 "int a(double x, int n) { return __builtin_fpclassify(0, 1, n, 3, 4, x); }\n",
6516 "int b(double x) { return __builtin_fpclassify(0, 1, 2, 3, x); }\n",
6517 "int c(int x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n",
6518 );
6519 let messages = run(&opts, source).messages;
6520 assert_eq!(
6521 messages,
6522 [
6523 "/main.c:1:60: error: non-const integer argument 3 in call to function \
6524 '__builtin_fpclassify' [E0687]",
6525 "/main.c:2:26: error: too few arguments to function '__builtin_fpclassify' \
6526 [E0511]",
6527 "/main.c:3:23: error: non-floating-point argument in call to function \
6528 '__builtin_fpclassify' [E0685]",
6529 ]
6530 );
6531 }
6532
6533 #[test]
6541 fn a_builtin_whose_answer_is_a_constant_is_one_and_not_a_call() {
6542 let text = ir(concat!(
6543 "double a = __builtin_inf();\n",
6544 "float b = __builtin_huge_valf();\n",
6545 "long double c = __builtin_infl();\n",
6546 "double d = __builtin_huge_val();\n",
6547 ));
6548 assert!(text.contains("global @a : f64 = 0x7ff0000000000000,"), "{text}");
6549 assert!(text.contains("global @b : f32 = 0x7f800000,"), "{text}");
6550 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
6551 assert!(text.contains("global @d : f64 = 0x7ff0000000000000,"), "{text}");
6552 assert!(!text.contains("call"), "{text}");
6553 }
6554
6555 #[test]
6564 fn a_nan_is_written_with_the_payload_the_program_asked_for() {
6565 let text = ir(concat!(
6566 "double a = __builtin_nan(\"\");\n",
6567 "double b = __builtin_nan(\"0x1\");\n",
6568 "double c = __builtin_nan(\"010\");\n",
6570 "double d = __builtin_nans(\"\");\n",
6571 "double e = __builtin_nans(\"0x1\");\n",
6572 "float f = __builtin_nanf(\"0x1\");\n",
6573 "float g = __builtin_nansf(\"\");\n",
6574 "long double h = __builtin_nansl(\"\");\n",
6575 ));
6576 assert!(text.contains("global @a : f64 = 0x7ff8000000000000,"), "{text}");
6577 assert!(text.contains("global @b : f64 = 0x7ff8000000000001,"), "{text}");
6578 assert!(text.contains("global @c : f64 = 0x7ff8000000000008,"), "{text}");
6579 assert!(text.contains("global @d : f64 = 0x7ff4000000000000,"), "{text}");
6580 assert!(text.contains("global @e : f64 = 0x7ff0000000000001,"), "{text}");
6581 assert!(text.contains("global @f : f32 = 0x7fc00001,"), "{text}");
6582 assert!(text.contains("global @g : f32 = 0x7fa00000,"), "{text}");
6583 assert!(text.contains("f80 0x7fffa000000000000000"), "{text}");
6584
6585 let text = ir(concat!(
6588 "double f(const char *p) { return __builtin_nan(p); }\n",
6589 "double g(void) { return __builtin_nans(\"1x\"); }\n",
6590 ));
6591 assert_eq!(text.matches("call @nan(").count(), 1, "{text}");
6592 assert_eq!(text.matches("call @nans(").count(), 1, "{text}");
6593 }
6594
6595 #[test]
6603 fn the_length_and_the_order_of_a_string_literal_are_known_here() {
6604 let text = ir(concat!(
6605 "unsigned long a = __builtin_strlen(\"hello\");\n",
6606 "unsigned long b = __builtin_strlen(\"a\\0bc\");\n",
6607 "int c = __builtin_strcmp(\"X\", \"X\\376\") < 0;\n",
6608 "int d = __builtin_strcmp(\"abc\", \"abc\");\n",
6609 "int e = __builtin_strcmp(\"abc\", \"ab\") > 0;\n",
6610 ));
6611 assert!(text.contains("global @a : i64 = 5,"), "{text}");
6612 assert!(text.contains("global @b : i64 = 1,"), "{text}");
6613 assert!(text.contains("global @c : i32 = 1,"), "{text}");
6614 assert!(text.contains("global @d : i32 = 0,"), "{text}");
6615 assert!(text.contains("global @e : i32 = 1,"), "{text}");
6616 assert!(!text.contains("call"), "{text}");
6617
6618 let text = ir("unsigned long f(const char *p) { return __builtin_strlen(p); }\n");
6620 assert!(text.contains("call @strlen("), "{text}");
6621 }
6622
6623 #[test]
6630 fn a_sign_builtin_is_a_mask_over_the_bits_and_not_a_call() {
6631 let text = body("double f(double x) { return __builtin_fabs(x); }\n");
6632 assert!(text.contains("bitcast.i64 %0"), "{text}");
6633 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
6634 assert!(text.contains("and %1, %2"), "{text}");
6635 assert!(text.contains("bitcast.f64 %3"), "{text}");
6636 assert!(!text.contains("call"), "{text}");
6637
6638 let text = body("double f(double x, double y) { return __builtin_copysign(x, y); }\n");
6639 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
6640 assert!(text.contains("%8 = or %4, %7"), "{text}");
6641 assert!(!text.contains("call"), "{text}");
6642
6643 let text = body("long double f(long double x) { return __builtin_fabsl(x); }\n");
6646 assert!(text.contains("bitcast.i80 %0"), "{text}");
6647 assert!(text.contains("bitcast.f80"), "{text}");
6648
6649 let text = body("double f(float x) { return __builtin_fabs(x); }\n");
6652 assert!(text.contains("fpext.f64 %0"), "{text}");
6653 assert!(text.contains("bitcast.i64 %1"), "{text}");
6654 }
6655
6656 #[test]
6665 fn the_plain_math_names_are_the_same_mask_and_not_a_call() {
6666 let text =
6667 body(concat!("double fabs(double x);\n", "double f(double x) { return fabs(x); }\n",));
6668 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
6669 assert!(!text.contains("call"), "{text}");
6670
6671 let text =
6672 body(concat!("float fabsf(float x);\n", "float f(float x) { return fabsf(x); }\n",));
6673 assert!(text.contains("bitcast.i32 %0"), "{text}");
6674 assert!(!text.contains("call"), "{text}");
6675
6676 let text = body(concat!(
6677 "double copysign(double x, double y);\n",
6678 "double f(double x, double y) { return copysign(x, y); }\n",
6679 ));
6680 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
6681 assert!(!text.contains("call"), "{text}");
6682
6683 let text = body(concat!(
6684 "float copysignf(float x, float y);\n",
6685 "float f(float x, float y) { return copysignf(x, y); }\n",
6686 ));
6687 assert!(!text.contains("call"), "{text}");
6688
6689 let text = ir(concat!(
6693 "long double fabsl(long double x);\n",
6694 "long double f(long double x) { return fabsl(x); }\n",
6695 ));
6696 assert!(text.contains("call @fabsl"), "{text}");
6697 }
6698
6699 #[test]
6707 fn a_plain_math_name_the_program_took_is_the_programs_own_function() {
6708 let taken = concat!(
6709 "static double fabs(double b) { return 7; }\n",
6710 "double f(double x) { return fabs(x); }\n",
6711 );
6712 assert!(ir(taken).contains("call @fabs"), "a static definition is the program's own");
6713
6714 let retyped = concat!("int fabs(int b);\n", "int f(int x) { return fabs(x); }\n");
6715 assert!(ir(retyped).contains("call @fabs"), "another type is another function");
6716
6717 let plain = concat!("double fabs(double b);\n", "double f(double x) { return fabs(x); }\n");
6718 let mut opts = options();
6719 opts.emit = EmitKind::Ir;
6720 assert!(!run(&opts, plain).text().contains("call @fabs"), "the library's by default");
6721
6722 opts.builtins = false;
6723 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin");
6724
6725 opts.builtins = true;
6726 opts.no_builtin = vec!["fabs".to_owned()];
6727 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin-fabs");
6728 let one = concat!(
6729 "double copysign(double a, double b);\n",
6730 "double f(double x) { return copysign(x, 1.0); }\n",
6731 );
6732 assert!(!run(&opts, one).text().contains("call @copysign"), "one name and not the family");
6733
6734 opts.no_builtin = Vec::new();
6736 opts.builtins = false;
6737 let prefixed = "double f(double x) { return __builtin_fabs(x); }\n";
6738 assert!(!run(&opts, prefixed).text().contains("call @fabs"), "the prefix is not a library");
6739 }
6740
6741 #[test]
6750 fn the_sign_builtins_answer_a_zero_and_a_nan_the_way_the_bits_say() {
6751 let text = ir(concat!(
6752 "double a = __builtin_fabs(-3.5);\n",
6753 "double b = __builtin_copysign(1.0, -0.0);\n",
6754 "double c = __builtin_copysign(0.0, -2.0);\n",
6755 "double d = __builtin_copysign(-__builtin_nan(\"\"), 1.0);\n",
6757 "double e = __builtin_fabs(-__builtin_nan(\"0x1\"));\n",
6758 "float g = __builtin_copysignf(-0.0f, 2.0f);\n",
6759 "long double h = __builtin_copysignl(1.0L, -1.0L);\n",
6760 "long double i = __builtin_fabsl(-__builtin_infl());\n",
6761 ));
6762 assert!(text.contains("global @a : f64 = 0x400c000000000000,"), "{text}");
6763 assert!(text.contains("global @b : f64 = 0xbff0000000000000,"), "{text}");
6764 assert!(text.contains("global @c : f64 = 0x8000000000000000,"), "{text}");
6765 assert!(text.contains("global @d : f64 = 0x7ff8000000000000,"), "{text}");
6766 assert!(text.contains("global @e : f64 = 0x7ff8000000000001,"), "{text}");
6767 assert!(text.contains("global @g : f32 = 0x0,"), "{text}");
6768 assert!(text.contains("f80 0xbfff8000000000000000"), "{text}");
6769 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
6770 }
6771
6772 #[test]
6780 fn the_complex_builtins_are_the_halves_of_the_value_and_not_a_call() {
6781 let text = body("double f(_Complex double z) { return __builtin_creal(z); }\n");
6782 assert!(!text.contains("call"), "{text}");
6783 let text = body("double f(_Complex double z) { return __builtin_cimag(z); }\n");
6784 assert!(!text.contains("call"), "{text}");
6785
6786 let text = body("_Complex double f(_Complex double z) { return __builtin_conj(z); }\n");
6789 assert_eq!(text.matches("fneg").count(), 1, "{text}");
6790 assert!(!text.contains("call"), "{text}");
6791 let negated = body("_Complex double f(_Complex double z) { return -z; }\n");
6792 assert_eq!(negated.matches("fneg").count(), 2, "{negated}");
6793
6794 let written = body("_Complex double f(_Complex double z) { return ~z; }\n");
6797 assert_eq!(written, text, "the name and the operator are the same thing");
6798
6799 let text = body(concat!(
6801 "double creal(_Complex double z);\n",
6802 "double f(_Complex double z) { return creal(z); }\n",
6803 ));
6804 assert!(!text.contains("call"), "{text}");
6805 let text = body(concat!(
6806 "_Complex float conjf(_Complex float z);\n",
6807 "_Complex float f(_Complex float z) { return conjf(z); }\n",
6808 ));
6809 assert_eq!(text.matches("fneg").count(), 1, "{text}");
6810 assert!(!text.contains("call"), "{text}");
6811
6812 let taken = concat!(
6815 "static double creal(_Complex double z) { return 7; }\n",
6816 "double f(_Complex double z) { return creal(z); }\n",
6817 );
6818 assert!(ir(taken).contains("call @creal"), "a static definition is the program's own");
6819 let retyped = concat!("int cimag(int z);\n", "int f(int z) { return cimag(z); }\n");
6820 assert!(ir(retyped).contains("call @cimag"), "another type is another function");
6821 let plain = concat!(
6822 "double cimag(_Complex double z);\n",
6823 "double f(_Complex double z) { return cimag(z); }\n",
6824 );
6825 let mut opts = options();
6826 opts.emit = EmitKind::Ir;
6827 opts.builtins = false;
6828 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin");
6829 opts.builtins = true;
6830 opts.no_builtin = vec!["cimag".to_owned()];
6831 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin-cimag");
6832
6833 let text = ir(concat!(
6835 "double a = __builtin_creal(1.5 + 2.5i);\n",
6836 "double b = __builtin_cimag(1.5 + 2.5i);\n",
6837 "_Complex double c = __builtin_conj(1.5 + 2.5i);\n",
6838 ));
6839 assert!(text.contains("global @a : f64 = 0x3ff8000000000000,"), "{text}");
6840 assert!(text.contains("global @b : f64 = 0x4004000000000000,"), "{text}");
6841 assert!(
6842 text.contains("{ f64 0x3ff8000000000000, f64 0xc004000000000000 }"),
6843 "the conjugate of a constant is the constant with the second half negated: {text}"
6844 );
6845 assert!(!text.contains("call"), "{text}");
6846 }
6847
6848 #[test]
6856 fn a_math_library_builtin_of_a_constant_is_the_answer_and_not_a_call() {
6857 let text = ir(concat!(
6858 "double a = __builtin_ceil(1.5);\n",
6859 "double b = __builtin_floor(1.5);\n",
6860 "double c = __builtin_trunc(-1.5);\n",
6861 "double d = __builtin_round(2.5);\n",
6864 "double e = __builtin_ceil(-0.5);\n",
6866 "double f = __builtin_fmax(1.0, 2.0);\n",
6867 "double g = __builtin_fmin(1.0, 2.0);\n",
6868 "float h = __builtin_ceilf(1.25f);\n",
6869 "double ceil(double x);\n",
6872 "double i = ceil(2.25);\n",
6873 ));
6874 assert!(text.contains("global @a : f64 = 0x4000000000000000,"), "{text}");
6875 assert!(text.contains("global @b : f64 = 0x3ff0000000000000,"), "{text}");
6876 assert!(text.contains("global @c : f64 = 0xbff0000000000000,"), "{text}");
6877 assert!(text.contains("global @d : f64 = 0x4008000000000000,"), "{text}");
6878 assert!(text.contains("global @e : f64 = 0x8000000000000000,"), "{text}");
6879 assert!(text.contains("global @f : f64 = 0x4000000000000000,"), "{text}");
6880 assert!(text.contains("global @g : f64 = 0x3ff0000000000000,"), "{text}");
6881 assert!(text.contains("global @h : f32 = 0x40000000,"), "{text}");
6882 assert!(text.contains("global @i : f64 = 0x4008000000000000,"), "{text}");
6883 assert!(!text.contains("call"), "{text}");
6884 }
6885
6886 #[test]
6894 fn a_math_library_builtin_of_anything_else_is_a_call_to_the_library() {
6895 let text = ir(concat!(
6896 "double f(double x) { return __builtin_ceil(x); }\n",
6897 "float g(float x) { return __builtin_floorf(x); }\n",
6898 "double h(double x, double y) { return __builtin_fmax(x, y); }\n",
6899 ));
6900 assert!(text.contains("call @ceil("), "{text}");
6901 assert!(text.contains("call @floorf("), "{text}");
6902 assert!(text.contains("call @fmax("), "{text}");
6903
6904 let text = ir(concat!(
6908 "double f(void) { return __builtin_rint(2.5); }\n",
6909 "double g(void) { return __builtin_nearbyint(2.5); }\n",
6910 ));
6911 assert!(text.contains("call @rint("), "{text}");
6912 assert!(text.contains("call @nearbyint("), "{text}");
6913
6914 let text = ir("double f(void) { return __builtin_fmin(__builtin_nan(\"\"), 1.0); }\n");
6917 assert!(text.contains("call @fmin("), "{text}");
6918
6919 let plain = concat!("double ceil(double x);\n", "double f(void) { return ceil(2.25); }\n");
6922 let mut opts = options();
6923 opts.emit = EmitKind::Ir;
6924 opts.no_builtin = vec!["ceil".to_owned()];
6925 assert!(run(&opts, plain).text().contains("call @ceil("), "-fno-builtin-ceil");
6926 }
6927
6928 #[test]
6935 fn a_constexpr_object_is_a_constant_wherever_one_is_required() {
6936 let text = ir(concat!(
6937 "constexpr int side = 4;\n",
6938 "constexpr int wider = side + 1;\n",
6939 "constexpr double half = 1.5;\n",
6940 "struct point { int x; int y; };\n",
6941 "constexpr struct point origin = { 5, 6 };\n",
6942 "int square[side * side];\n",
6943 "int rectangle[wider];\n",
6944 "int rounded[(int)half * 2];\n",
6945 "int across[origin.y];\n",
6946 "enum named { four = side };\n",
6947 "int e = four;\n",
6948 ));
6949 assert!(text.contains("global @square : bytes 64 ="), "{text}");
6950 assert!(text.contains("global @rectangle : bytes 20 ="), "{text}");
6951 assert!(text.contains("global @rounded : bytes 8 ="), "{text}");
6952 assert!(text.contains("global @across : bytes 24 ="), "{text}");
6953 assert!(text.contains("global @e : i32 = 4,"), "{text}");
6954
6955 let mut opts = options();
6958 opts.emit = EmitKind::Ir;
6959 let konst = "const int n = 1;\nint a[n];\n";
6960 let message = "/main.c:2:5: error: variably modified 'a' at file scope [E0538]";
6961 assert_eq!(run(&opts, konst).messages, [message]);
6962
6963 let subscript = "constexpr int t[3] = { 1, 2, 3 };\nint a[t[1]];\n";
6965 assert_eq!(run(&opts, subscript).messages, [message]);
6966
6967 let address = "constexpr int c = 3;\nint *p = &c;\n";
6969 let warning = "/main.c:2:6: warning: initialization discards 'const' qualifier from \
6970 pointer target type [E0514]";
6971 assert_eq!(run(&opts, address).messages, [warning]);
6972 }
6973
6974 #[test]
6983 fn a_member_whose_size_was_refused_is_not_a_flexible_array_member() {
6984 let mut opts = options();
6985 opts.emit = EmitKind::Ir;
6986
6987 let alone = "int k;\nextern struct D { int a[k]; } ed;\n";
6988 let message = "/main.c:2:23: error: variably modified 'a' at file scope [E0538]";
6989 assert_eq!(run(&opts, alone).messages, [message]);
6990
6991 let first = "int k;\nextern struct E { int a[k]; int b; } ee;\n";
6993 assert_eq!(run(&opts, first).messages, [message]);
6994
6995 let negative = "struct F { int a[-1]; };\n";
6998 let refused = "/main.c:1:18: error: size of array 'a' is negative [E0536]";
6999 assert_eq!(run(&opts, negative).messages, [refused]);
7000
7001 let flexible = "struct G { int a[]; };\n";
7004 let named = "/main.c:1:16: error: flexible array member in a struct with no named \
7005 members [E0554]";
7006 assert_eq!(run(&opts, flexible).messages, [named]);
7007 }
7008
7009 #[test]
7024 fn a_pointer_to_an_array_gains_a_qualifier_the_same_way_a_pointer_to_anything_else_does() {
7025 let mut opts = options();
7026 opts.emit = EmitKind::Ir;
7027 let prefix = "typedef unsigned int B[4];\nstruct H { B category[2]; };\n";
7028
7029 let adding = format!("{prefix}const B *f(struct H *h) {{ return &h->category[0]; }}\n");
7031 assert_eq!(run(&opts, &adding).messages, [] as [String; 0]);
7032
7033 let plain = concat!(
7036 "const unsigned int (*f(unsigned int (*p)[4]))[4] { return p; }\n",
7037 "const unsigned int (*g(unsigned int (*p)[2][3]))[2][3] { return p; }\n",
7038 );
7039 assert_eq!(run(&opts, plain).messages, [] as [String; 0]);
7040
7041 let dropping = format!("{prefix}B *f(const B *p) {{ return p; }}\n");
7044 let warning = "/main.c:3:27: warning: return discards 'const' qualifier from pointer target type \
7045 [E0514]";
7046 assert_eq!(run(&opts, &dropping).messages, [warning]);
7047
7048 let wrong = "const unsigned int (*f(unsigned short (*p)[4]))[4] { return p; }\n";
7051 let error = "/main.c:1:61: error: returning 'unsigned short (*)[4]' from a function with \
7052 incompatible return type 'const unsigned int (*)[4]' [E0512]";
7053 assert_eq!(run(&opts, wrong).messages, [error]);
7054 }
7055
7056 #[test]
7065 fn an_old_style_definition_takes_its_types_from_the_declarations_under_its_list() {
7066 let mut opts = options();
7069 opts.std = Std::C17;
7070 let source = concat!(
7071 "int add(a, b)\n",
7072 "int a;\n",
7073 "int b;\n",
7074 "{ return a + b; }\n",
7075 "int promoted(c)\n",
7076 "char c;\n",
7077 "{ return c; }\n",
7078 "int narrow(char);\n",
7079 "int narrow(c)\n",
7080 "char c;\n",
7081 "{ return c; }\n",
7082 "int first(a)\n",
7083 "int a[4];\n",
7084 "{ return a[0]; }\n",
7085 );
7086 let result = run(&opts, source);
7087 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
7088 let text = result.text();
7089 assert!(text.contains("add : int(int, int) function external defined"), "{text}");
7090 assert!(text.contains("promoted : int(int) function external defined"), "{text}");
7091 assert!(text.contains("c : char object automatic defined"), "{text}");
7093 assert!(text.contains("narrow : int(char) function external defined"), "{text}");
7094 assert!(text.contains("first : int(int *) function external defined"), "{text}");
7096 }
7097
7098 #[test]
7105 fn the_two_halves_of_an_old_style_parameter_list_have_to_agree() {
7106 let mut opts = options();
7107 opts.std = Std::C17;
7108 for (source, message) in [
7109 ("int f(a, a)\nint a;\n{ return a; }\n", "1:10: error: multiple parameters named 'a'"),
7110 (
7111 "int f(a)\nint a;\nint b;\n{ return a; }\n",
7112 "3:5: error: declaration for parameter 'b' but no such parameter",
7113 ),
7114 ("int f(a)\nint a;\nint a;\n{ return a; }\n", "3:5: error: redefinition of parameter"),
7115 ("int f(a)\nint a = 1;\n{ return a; }\n", "2:5: error: parameter 'a' is initialized"),
7116 (
7117 "int f(a)\nstatic int a;\n{ return a; }\n",
7118 "2:12: error: storage class specified for parameter 'a'",
7119 ),
7120 (
7121 "int f(char);\nint f(a)\nshort a;\n{ return a; }\n",
7122 "2:7: error: argument 'a' doesn't match prototype",
7123 ),
7124 ] {
7125 let result = run(&opts, source);
7126 assert!(result.failed(), "expected this to fail:\n{source}");
7127 assert!(result.messages[0].contains(message), "{:?}", result.messages);
7128 }
7129
7130 let implicit = "int f(a, b)\nint a;\n{ return a + b; }\n";
7133 let mut older = options();
7134 older.std = Std::C89;
7135 assert!(!run(&older, implicit).failed(), "{:?}", run(&older, implicit).messages);
7136 let result = run(&opts, implicit);
7137 assert!(
7138 result.messages[0].contains("1:10: error: type of 'b' defaults to 'int'"),
7139 "{:?}",
7140 result.messages
7141 );
7142
7143 let mut newer = options();
7147 newer.std = Std::C23;
7148 let plain = "int f(a)\nint a;\n{ return a; }\n";
7149 let result = run(&newer, plain);
7150 assert!(!result.failed(), "{:?}", result.messages);
7151 assert_eq!(
7152 result.messages,
7153 ["/main.c:1:5: warning: old-style function definition [E0412]"]
7154 );
7155 assert!(run(&opts, plain).messages.is_empty(), "and nothing to say in the dialects before");
7156 }
7157
7158 #[test]
7165 fn the_obsolete_designators_are_taken_and_are_pedantic_warnings() {
7166 let array = "int a[8] = { [3] 7 };\n";
7167 let member = "struct s { int x; } v = { x: 7 };\n";
7168 for source in [array, member] {
7169 let result = run(&options(), source);
7170 assert!(!result.failed(), "{:?}", result.messages);
7171 assert!(result.messages.is_empty(), "nothing to say: {:?}", result.messages);
7172 }
7173
7174 let mut asked = options();
7175 asked.pedantic = true;
7176 assert_eq!(
7177 run(&asked, array).messages,
7178 ["/main.c:1:18: warning: obsolete designator, write `[i] =` instead [E0415]"]
7179 );
7180 assert_eq!(
7181 run(&asked, member).messages,
7182 ["/main.c:1:27: warning: obsolete designator, write `.field =` instead [E0413]"]
7183 );
7184 }
7185
7186 #[test]
7193 fn a_type_is_refused_when_it_passes_the_largest_object_and_not_before() {
7194 let text = ir(concat!(
7195 "struct huge_struct { short buf[(1L << 62) - 256]; int a, b, c, d; };\n",
7196 "struct brim { char buf[9223372036854775807L]; };\n",
7197 "struct bitty { char buf[9223372036854775800L]; int x : 1; };\n",
7198 "unsigned long h = sizeof(struct huge_struct);\n",
7199 "unsigned long b = sizeof(struct brim);\n",
7200 "unsigned long y = sizeof(struct bitty);\n",
7201 ));
7202 assert!(text.contains("global @h : i64 = 9223372036854775312,"), "{text}");
7203 assert!(text.contains("global @b : i64 = 9223372036854775807,"), "{text}");
7204 assert!(text.contains("global @y : i64 = 9223372036854775804,"), "{text}");
7205
7206 let mut opts = options();
7207 opts.emit = EmitKind::Ir;
7208 let over = "struct over { char buf[9223372036854775800L]; char x[8]; };\n";
7209 let message = "/main.c:1:1: error: type 'struct over' is too large [E0560]";
7210 assert_eq!(run(&opts, over).messages, [message]);
7211 let array = "struct wide { short buf[1L << 62]; };\n";
7212 let message = "/main.c:1:25: error: size of array 'buf' exceeds \
7213 maximum object size '9223372036854775807' [E0537]";
7214 assert_eq!(run(&opts, array).messages[0], message);
7215 }
7216
7217 fn compile_bytes(source: &[u8]) -> Compiled {
7222 let mut opts = options();
7223 opts.emit = EmitKind::Ir;
7224 let mut fs = MemoryFileSystem::new();
7225 fs.insert("/main.c", source.to_vec());
7226 compile(&opts, "/main.c", &fs)
7227 }
7228
7229 #[test]
7236 fn a_byte_that_is_not_a_character_is_kept_in_a_literal_and_refused_outside_one() {
7237 let mut source = b"char s[] = \"a".to_vec();
7238 source.push(0xff);
7239 source.extend_from_slice(b"b\";\nchar c = '");
7240 source.push(0xff);
7241 source.extend_from_slice(b"';\n");
7242 let result = compile_bytes(&source);
7243 assert_eq!(result.messages, Vec::<String>::new(), "a raw byte in a literal is that byte");
7244 assert!(result.text().contains(r#"bytes "a\ffb\00""#), "{}", result.text());
7245 assert!(result.text().contains("global @c : i8 = -1,"), "{}", result.text());
7247
7248 let mut stray = b"int a".to_vec();
7249 stray.push(0xff);
7250 stray.extend_from_slice(b" = 1;\n");
7251 let result = compile_bytes(&stray);
7252 assert!(
7253 result.messages.iter().any(|m| m.contains("source is not valid UTF-8 here")),
7254 "{:?}",
7255 result.messages
7256 );
7257 }
7258
7259 #[test]
7260 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
7261 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
7262 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
7263 let expected = "\
7264func @add(i32, i32) -> i32, linkage(external) {
7265block0(%0: i32, %1: i32):
7266 %2 = add.nsw %0, %1
7267 return %2
7268}
7269";
7270 assert!(text.contains(expected), "{text}");
7271 }
7272
7273 #[test]
7274 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
7275 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
7276 assert!(!text.contains("alloca"), "{text}");
7277 assert!(!text.contains("load"), "{text}");
7278 assert!(!text.contains("store"), "{text}");
7279 }
7280
7281 #[test]
7282 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
7283 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
7284 let expected = "\
7285block0:
7286 %0 = alloca, size 4, align 4
7287 %1 = iconst.i32 1
7288 store %1 -> %0, align 4, tbaa !1
7289 %2 = call @g(%0) : (ptr) -> i32
7290 return %2
7291";
7292 assert_eq!(text, expected);
7293 }
7294
7295 #[test]
7296 fn a_loop_carries_what_it_changes_as_block_parameters() {
7297 let text = body(
7300 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
7301 return total;\n}\n",
7302 );
7303 assert!(!text.contains("alloca"), "{text}");
7304 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
7305 assert!(text.contains("jump block1("), "{text}");
7306 }
7307
7308 #[test]
7309 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
7310 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
7311 assert!(text.contains("icmp slt %0, %1"), "{text}");
7312 assert!(!text.contains("zext"), "{text}");
7313 }
7314
7315 #[test]
7316 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
7317 let text = body("int f(int a, int b) { return a && b; }\n");
7318 let expected = "\
7319block0(%0: i32, %1: i32):
7320 %2 = iconst.i32 0
7321 %3 = icmp ne %0, %2
7322 %4 = iconst.i1 0
7323 br_if %3, block1, block2(%4)
7324
7325block1:
7326 %5 = iconst.i32 0
7327 %6 = icmp ne %1, %5
7328 jump block2(%6)
7329
7330block2(%7: i1):
7331 %8 = zext.i32 %7
7332 return %8
7333";
7334 assert_eq!(text, expected);
7335 }
7336
7337 #[test]
7338 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
7339 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
7340 assert!(!text.contains("block3"), "{text}");
7343 assert!(!text.contains("iconst.i32 3"), "{text}");
7344 }
7345
7346 #[test]
7347 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
7348 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
7349 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
7350 assert!(body("int f(void) { }\n").contains("unreachable"));
7351 }
7352
7353 #[test]
7354 fn a_structure_is_copied_rather_than_held_in_a_value() {
7355 let text = body(
7356 "struct point { int x, y; };\n\
7357 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
7358 );
7359 assert!(text.contains("memcpy"), "{text}");
7360 }
7361
7362 #[test]
7363 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
7364 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
7365 assert!(text.contains("memset"), "{text}");
7366 }
7367
7368 #[test]
7369 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
7370 let text = body(
7371 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
7372 default: r = 4; } return r; }\n",
7373 );
7374 let expected = "\
7375block0(%0: i32):
7376 %1 = iconst.i32 0
7377 switch %0, block1, [1 => block2, 2 => block3(%1)]
7378
7379block1:
7380 %2 = iconst.i32 4
7381 jump block4(%2)
7382
7383block2:
7384 %3 = iconst.i32 1
7385 jump block3(%3)
7386
7387block3(%4: i32):
7388 %5 = iconst.i32 2
7389 %6 = add.nsw %4, %5
7390 jump block4(%6)
7391
7392block4(%7: i32):
7393 return %7
7394";
7395 assert_eq!(text, expected);
7396 }
7397
7398 #[test]
7399 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
7400 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
7403 assert!(text.contains("%2 = sub %0, %1"), "{text}");
7404 assert!(text.contains("icmp ule"), "{text}");
7405 assert!(!text.contains("switch"), "{text}");
7406 }
7407
7408 #[test]
7409 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
7410 let text = body(
7411 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
7412 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
7413 );
7414 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
7417 assert!(text.contains("block5:\n jump block7("), "{text}");
7418 assert!(text.contains("block6:\n jump block8("), "{text}");
7419 }
7420
7421 #[test]
7422 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
7423 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
7424 }
7425
7426 #[test]
7427 fn a_label_a_loop_is_only_entered_through_builds_the_loop_around_it() {
7428 let text = body(
7433 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
7434 return n; }\n",
7435 );
7436 assert!(text.contains("switch %0, block1(%1), [1 => block2, 2 => block3(%1)]"), "{text}");
7439 assert!(text.contains("block3(%3: i32):\n %4 = iconst.i32 1"), "{text}");
7440 assert!(text.contains("block4:\n jump block3("), "{text}");
7441 }
7442
7443 #[test]
7444 fn a_goto_into_a_loop_body_enters_it_without_the_test() {
7445 let text = body("int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n");
7448 assert!(text.starts_with("block0(%0: i32, %1: i32):\n jump block1(%1)"), "{text}");
7449 assert!(text.contains("block1(%2: i32):\n %3 = iconst.i32 1"), "{text}");
7450 assert!(text.contains("br_if %6, block2, block3"), "{text}");
7451 }
7452
7453 #[test]
7454 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
7455 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
7456 assert!(!text.contains("alloca"), "{text}");
7460 assert!(text.contains("block2(%4: i32):\n return %4"), "{text}");
7461 assert_eq!(text.matches("jump block2(").count(), 2, "{text}");
7462 }
7463
7464 #[test]
7465 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
7466 let text =
7467 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
7468 assert!(!text.contains("alloca"), "{text}");
7469 assert!(text.contains("block1(%2: i32):"), "{text}");
7470 assert!(text.contains("jump block1(%5)"), "{text}");
7471 }
7472
7473 #[test]
7474 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
7475 assert_eq!(
7478 body("int f(int x) { return x; spare: return 0; }\n"),
7479 "block0(%0: i32):\n return %0\n"
7480 );
7481 }
7482
7483 #[test]
7484 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
7485 let text = body(
7486 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
7487 );
7488 assert_eq!(
7491 text,
7492 "\
7493block0(%0: ptr):
7494 %1 = load.i8 %0, align 1
7495 %2 = iconst.i8 3
7496 %3 = ashr %1, %2
7497 %4 = sext.i32 %3
7498 return %4
7499"
7500 );
7501 }
7502
7503 #[test]
7504 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
7505 let text =
7509 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
7510 assert_eq!(
7511 text,
7512 "\
7513block0(%0: ptr, %1: i32):
7514 %2 = iconst.i32 16777215
7515 %3 = and %1, %2
7516 %4 = trunc.i16 %3
7517 store %4 -> %0, align 2
7518 %5 = iconst.i32 16
7519 %6 = lshr %3, %5
7520 %7 = trunc.i8 %6
7521 %8 = iconst.i64 2
7522 %9 = ptr_add %0, %8
7523 store %7 -> %9, align 1
7524 return
7525"
7526 );
7527 }
7528
7529 #[test]
7530 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
7531 let text =
7532 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
7533 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
7536 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
7537 }
7538
7539 #[test]
7540 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
7541 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
7544 assert_eq!(text.matches("ashr").count(), 0, "{text}");
7545 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
7546 }
7547
7548 #[test]
7549 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
7550 let text = body(
7554 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
7555 );
7556 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
7557 }
7558
7559 #[test]
7560 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
7561 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
7564 assert!(
7565 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
7566 "{text}"
7567 );
7568 }
7569
7570 #[test]
7571 fn an_initialized_flexible_array_member_makes_the_object_larger_than_its_type() {
7572 let text = ir(concat!(
7577 "struct a { int i; int j[]; } x = { 1, { 2, 0, 2, 3 } };\n",
7578 "struct b { char c; char p[]; } y = { 'o', \"wx\" };\n",
7579 "struct c { char c; char p[]; } z = { '9', { 'e', 'b' } };\n",
7580 "char s[2] = \"hi\";\n",
7581 ));
7582 assert!(
7583 text.contains("global @x : bytes 20 = { i32 1, i32 2, i32 0, i32 2, i32 3 }"),
7584 "{text}"
7585 );
7586 assert!(text.contains("global @y : bytes 4 = { i8 111, bytes \"wx\\00\" }"), "{text}");
7587 assert!(text.contains("global @z : bytes 3 = { i8 57, i8 101, i8 98 }"), "{text}");
7588 assert!(text.contains("global @s : bytes 2 = { bytes \"hi\" }"), "{text}");
7591 }
7592
7593 #[test]
7594 fn a_definition_takes_a_parameter_it_left_unnamed() {
7595 let text = ir("int f(int a, int) { return a; }\n");
7599 assert!(text.contains("func @f(i32, i32) -> i32"), "{text}");
7600 assert!(text.contains("block0(%0: i32, %1: i32):"), "{text}");
7601
7602 let text = ir("int g(int, int n) { return n; }\n");
7605 assert!(text.contains("block0(%0: i32, %1: i32):\n return %1\n"), "{text}");
7606 }
7607
7608 #[test]
7609 fn an_assignment_of_a_structure_is_the_object_it_wrote() {
7610 let text = body(concat!(
7615 "struct s { int f; int g; };\n",
7616 "void h(struct s *a, struct s *c, struct s *d, struct s *e)\n",
7617 "{ *d = *e = a[0] = *c; }\n",
7618 ));
7619 assert_eq!(text.matches("memcpy").count(), 3, "{text}");
7620 assert!(text.contains("memcpy %8, %1, size 8, align 4\n"), "{text}");
7621 assert!(text.contains("memcpy %3, %8, size 8, align 4\n"), "{text}");
7622 assert!(text.contains("memcpy %2, %3, size 8, align 4\n"), "{text}");
7623 }
7624
7625 #[test]
7626 fn a_string_literal_stops_at_the_end_of_the_array_it_is_filling() {
7627 let mut opts = options();
7632 opts.emit = EmitKind::Ir;
7633 let result = run(
7634 &opts,
7635 concat!(
7636 "const char a[2][3] = { \"1234\", \"xyz\" };\n",
7637 "static const char b[3][5] = { \"12345\", \"678\", \"9\" };\n",
7638 "union u { struct { char x[4]; char y[4]; }; struct { char z[8]; }; };\n",
7639 "const union u c = { { \"1234\", \"567\" } };\n",
7640 ),
7641 );
7642 let text = result.text();
7643 assert_eq!(
7644 result.messages,
7645 ["/main.c:1:24: warning: initializer-string for array of 'const char' is too long \
7646 (5 chars into 3 available) [E0637]"]
7647 );
7648 assert!(text.contains("global @a : bytes 6 = { bytes \"123\", bytes \"xyz\" }"), "{text}");
7649 assert!(
7650 text.contains(
7651 "global @b : bytes 15 = { bytes \"12345\", bytes \"678\\00\", zero 1, \
7652 bytes \"9\\00\", zero 3 }"
7653 ),
7654 "{text}"
7655 );
7656 assert!(
7659 text.contains("global @c : bytes 8 = { bytes \"1234\", bytes \"567\\00\" }"),
7660 "{text}"
7661 );
7662 }
7663
7664 #[test]
7665 fn a_cast_of_a_record_to_its_own_type_is_the_object_that_was_cast() {
7666 let text = body(concat!(
7670 "struct s { int a, b; };\nstruct v { struct s s; int t; };\n",
7671 "void g(struct v *);\n",
7672 "void f(struct s *p) { struct v w = { (struct s)*p, 5 }; g(&w); }\n",
7673 ));
7674 assert_eq!(text.matches("memcpy").count(), 1, "{text}");
7675 }
7676
7677 #[test]
7678 fn a_compound_literal_read_in_a_static_initializer_lays_its_bytes_into_the_image() {
7679 let text = ir(concat!(
7684 "struct s { int x; };\n",
7685 "struct t { struct s s; int o; } a = { (struct s){ 2 }, 3 };\n",
7686 "int n = (int){ 7 };\n",
7687 "struct u { struct s p; struct s q; } b = { (struct s){ 1 }, (struct s){ } };\n",
7688 ));
7689 assert!(text.contains("global @a : bytes 8 = { i32 2, i32 3 }"), "{text}");
7690 assert!(text.contains("global @n : i32 = 7,"), "{text}");
7691 assert!(text.contains("global @b : bytes 8 = { i32 1, zero 4 }"), "{text}");
7694 }
7695
7696 #[test]
7697 fn the_address_of_a_compound_literal_asks_for_the_object_it_points_at() {
7698 let text = ir("struct s { int x; };\nstruct s *q = &(struct s){ 9 };\n");
7702 assert!(text.contains("global @.Lanon.0 : i32 = 9, align 4, linkage(internal)"), "{text}");
7703 assert!(text.contains("global @q : bytes 8 = { addr.8 @.Lanon.0 }"), "{text}");
7704 }
7705
7706 #[test]
7707 fn an_object_of_no_size_at_all_has_an_image_with_nothing_in_it() {
7708 let text = ir("unsigned char foo[1][0];\n");
7712 assert!(text.contains("global @foo : bytes 0 = {}, align 1"), "{text}");
7713 }
7714
7715 #[test]
7716 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
7717 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
7720 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
7721 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
7722 }
7723
7724 #[test]
7725 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
7726 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
7730 assert!(
7731 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
7732 "{text}"
7733 );
7734 }
7735
7736 #[test]
7737 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
7738 let text = body(
7743 "\
7744struct s { int a, b; };
7745struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
7746",
7747 );
7748 assert!(text.contains("block3(%7: ptr)"), "{text}");
7750 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
7751 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
7752 }
7753
7754 #[test]
7762 fn the_left_side_of_a_conditional_with_no_middle_is_evaluated_once() {
7763 let text = body("int f(int i) { return ++i ?: 10; }\n");
7764 assert!(text.contains("jump block3(%2)"), "the arm is the value that was tested: {text}");
7765 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
7766
7767 let text = body("long f(int i) { return ++i ?: 10L; }\n");
7770 assert!(text.contains("%5 = sext.i64 %2"), "the arm widens what was tested: {text}");
7771 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
7772
7773 let text = body("int g(void);\nint f(void) { return g() ?: 10; }\n");
7775 assert_eq!(text.matches("call @g").count(), 1, "called once: {text}");
7776
7777 let text = body("int f(int i) { return ++i ? ++i : 10; }\n");
7780 assert_eq!(text.matches("add.nsw").count(), 2, "incremented twice: {text}");
7781 }
7782
7783 #[test]
7784 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
7785 let text = ir("\
7789struct pair { int a, b; };
7790struct pair make(int a, int b);
7791struct pair twice(struct pair p) { return make(p.a, p.b); }
7792");
7793 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
7794 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
7795 }
7796
7797 #[test]
7798 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
7799 let text = ir("\
7803struct big { double v[8]; };
7804struct big grow(struct big b);
7805struct big twice(struct big b) { return grow(grow(b)); }
7806");
7807 assert!(
7808 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
7809 "{text}"
7810 );
7811 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
7812 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
7815 }
7816
7817 #[test]
7818 fn a_structure_passed_to_a_variadic_function_says_so_at_the_call() {
7819 let text = ir("\
7824struct big { double v[8]; };
7825struct pair { int a, b; };
7826int p(const char *, ...);
7827int f(struct big b, struct pair q) { return p(\"\", 1, b, q); }
7828");
7829 assert!(
7830 text.contains("call @p(%4, %5, %2 byval(64, align 8), %6) : (ptr, ...) -> i32"),
7831 "{text}"
7832 );
7833 }
7834
7835 #[test]
7836 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
7837 let body = body(
7840 "\
7841struct pair { int a, b; };
7842struct pair make(int a, int b);
7843int second(void) { return make(1, 2).b; }
7844",
7845 );
7846 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
7847 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
7848 }
7849
7850 #[test]
7851 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
7852 let source = "\
7856struct hfa { float x, y, z; };
7857int take(struct hfa h);
7858int give(struct hfa h) { return take(h); }
7859";
7860 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
7861 let mut opts = options();
7862 opts.emit = EmitKind::Ir;
7863 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
7864 let result = run(&opts, source);
7865 assert_eq!(result.messages, Vec::<String>::new());
7866 assert!(result.text().contains("func @take(f32, f32, f32) -> i32"), "{}", result.text());
7867 }
7868
7869 #[test]
7870 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
7871 let source = "\
7874int use(int *);
7875void f(int n) {
7876 {
7877 int a[n];
7878 use(a);
7879 }
7880 use(0);
7881}
7882";
7883 let body = body(source);
7884 assert!(body.contains("mul.nsw"), "{body}");
7885 assert!(body.contains("stacksave"), "{body}");
7886 assert!(body.contains("alloca %"), "{body}");
7887 assert!(body.contains("stackrestore"), "{body}");
7888 }
7889
7890 #[test]
7891 fn a_goto_out_of_the_scope_of_one_gives_its_stack_back_on_the_way() {
7892 let source = "\
7897int use(int *);
7898int f(int n) {
7899 {
7900 int a[n];
7901 if (use(a)) goto out;
7902 use(0);
7903 }
7904out:
7905 return 0;
7906}
7907";
7908 let body = body(source);
7909 assert_eq!(body.matches("stackrestore").count(), 2, "{body}");
7911 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
7912 assert!(after.starts_with(" %4\n jump block"), "{body}");
7913 }
7914
7915 #[test]
7916 fn a_goto_to_a_label_the_array_is_still_alive_at_leaves_the_stack_alone() {
7917 let source = "\
7921int use(int *);
7922int f(int n) {
7923 int a[n];
7924again:
7925 if (use(a)) goto again;
7926 return 0;
7927}
7928";
7929 let body = body(source);
7930 assert!(body.contains("stacksave"), "{body}");
7931 assert!(!body.contains("stackrestore"), "{body}");
7932 }
7933
7934 #[test]
7935 fn a_goto_back_to_a_label_in_front_of_one_gives_it_back_every_time_round() {
7936 let source = "\
7941int use(int *);
7942int f(int n) {
7943again:
7944 {
7945 int a[n];
7946 if (use(a)) goto again;
7947 }
7948 return 0;
7949}
7950";
7951 let body = body(source);
7952 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
7953 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
7954 assert!(after.starts_with(" %4\n jump block1\n"), "{body}");
7955 }
7956
7957 #[test]
7958 fn the_head_of_a_for_loop_is_a_scope_that_closes_where_the_loop_is_left() {
7959 let source = "\
7965int f(void);
7966void t(void) {
7967 int count = 10;
7968 for (; count--;) {
7969 int b[f()];
7970 int i;
7971 for (i = 0; i < f(); i++) {
7972 b[i] = count;
7973 }
7974 }
7975}
7976";
7977 let body = body(source);
7978 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
7982 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
7983 let next = after.split("\n\n").next().expect("the block the restore is in");
7986 assert!(next.contains("jump block1("), "{body}");
7987 }
7988
7989 #[test]
7990 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
7991 let source = "\
7994unsigned long f(int n) {
7995 int a[n];
7996 n = 0;
7997 return sizeof a;
7998}
7999";
8000 let body = body(source);
8001 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
8003 }
8004
8005 #[test]
8006 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
8007 let source = "\
8010int use(int);
8011int f(int x) {
8012 return ({
8013 int t = use(x);
8014 t * t;
8015 });
8016}
8017";
8018 let expected = "\
8019block0(%0: i32):
8020 %1 = call @use(%0) : (i32) -> i32
8021 %2 = mul.nsw %1, %1
8022 return %2
8023";
8024 assert_eq!(body(source), expected);
8025 }
8026
8027 #[test]
8028 fn a_comma_whose_value_is_an_object_names_the_object_the_right_side_named() {
8029 let source = "\
8033struct pair { int a, b; };
8034void bail(void);
8035int f(struct pair p) {
8036 return (bail(), p).b;
8037}
8038";
8039 let expected = "\
8040block0(%0: i64):
8041 %1 = alloca, size 8, align 4
8042 store %0 -> %1, align 4
8043 call @bail() : ()
8044 %2 = iconst.i64 4
8045 %3 = ptr_add %1, %2
8046 %4 = load.i32 %3, align 4, tbaa !1
8047 return %4
8048";
8049 assert_eq!(body(source), expected);
8050 }
8051
8052 #[test]
8053 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
8054 let source = "int f(int x) { return ({ return x; 0; }); }\n";
8058 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
8059 }
8060
8061 #[test]
8062 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
8063 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
8067 let expected = "\
8068block0(%0: ptr):
8069 %1 = va_arg.f64 %0
8070 %2 = va_arg.f64 %0
8071 %3 = fadd %1, %2
8072 return %3
8073";
8074 assert_eq!(body(source), expected);
8075 }
8076
8077 #[test]
8078 fn one_that_reads_a_structure_answers_where_the_object_is() {
8079 let source = "\
8093struct s { int a; long b; };
8094long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.b; }
8095";
8096 let expected = "\
8097block0(%0: ptr):
8098 %1 = alloca, size 16, align 16
8099 %2 = va_object %0, size 16, align 8, in(int 8 at 0, int 8 at 8)
8100 memcpy %1, %2, size 16, align 8
8101 %3 = iconst.i64 8
8102 %4 = ptr_add %1, %3
8103 %5 = load.i64 %4, align 8, tbaa !1
8104 return %5
8105";
8106 assert_eq!(body(source), expected);
8107 }
8108
8109 #[test]
8113 fn the_classification_says_which_registers_the_object_arrived_in() {
8114 let source = "\
8115struct s { double a; double b; };
8116double f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a; }
8117";
8118 assert!(
8119 body(source)
8120 .contains("va_object %0, size 16, align 8, in(float f64 at 0, float f64 at 8)"),
8121 "{}",
8122 body(source)
8123 );
8124
8125 let big = "\
8126struct s { long a[4]; };
8127long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a[0]; }
8128";
8129 assert!(body(big).contains("va_object %0, size 32, align 8\n"), "{}", body(big));
8130 }
8131
8132 #[test]
8133 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
8134 let source = "\
8138int f(int c) {
8139 void *p = c ? &&one : &&two;
8140 goto *p;
8141one:
8142 return 1;
8143two:
8144 return 2;
8145}
8146";
8147 let expected = "\
8148block0(%0: i32):
8149 %1 = iconst.i32 0
8150 %2 = icmp ne %0, %1
8151 br_if %2, block1, block2
8152
8153block1:
8154 %3 = block_addr block3
8155 jump block4(%3)
8156
8157block2:
8158 %4 = block_addr block5
8159 jump block4(%4)
8160
8161block3:
8162 %5 = iconst.i32 1
8163 return %5
8164
8165block4(%6: ptr):
8166 indirect_br %6, block3, block5
8167
8168block5:
8169 %7 = iconst.i32 2
8170 return %7
8171";
8172 assert_eq!(body(source), expected);
8173 }
8174
8175 fn dispatch(labels: usize) -> String {
8178 let mask = labels - 1;
8179 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
8180 for index in 0..labels {
8181 source.push_str(&format!(" &&a{index},"));
8182 }
8183 source.push_str(" };\n\tint w = n, x = n + 1, y = n + 2, z = n + 3;\n");
8184 source.push_str(&format!("\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
8185 for index in 0..labels {
8186 let step = match index % 4 {
8187 0 => "w += x;",
8188 1 => "x += y;",
8189 2 => "y += z;",
8190 _ => "z += w;",
8191 };
8192 source.push_str(&format!("a{index}:\n\t{step}\n"));
8193 source.push_str("\tif (--n <= 0) return w + x + y + z;\n");
8194 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
8195 }
8196 source.push_str("}\n");
8197 source
8198 }
8199
8200 fn in_front_of_the_jump(text: &str) -> usize {
8202 let (before, _) = text.split_once("\tjmp\t*%").expect("a jump through a register");
8203 before.lines().rev().take_while(|line| line.starts_with("\tmov")).count()
8204 }
8205
8206 #[test]
8216 fn a_jump_through_a_register_writes_what_it_carries_and_not_the_whole_table() {
8217 let small = in_front_of_the_jump(&asm(&dispatch(4)));
8218 let large = in_front_of_the_jump(&asm(&dispatch(32)));
8219 assert_eq!(small, large, "eight times the labels and the same values in hand");
8220 assert!(large <= 8, "the values the loop keeps, and not a set of them per label: {large}");
8221 }
8222
8223 fn crowded(labels: usize) -> String {
8226 const VALUES: usize = 24;
8227 let mask = labels - 1;
8228 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
8229 for index in 0..labels {
8230 source.push_str(&format!(" &&a{index},"));
8231 }
8232 source.push_str(" };\n\t");
8233 for value in 0..VALUES {
8234 source.push_str(&format!("int v{value} = n + {value}; "));
8235 }
8236 let sum: Vec<String> = (0..VALUES).map(|value| format!("v{value}")).collect();
8237 source.push_str(&format!("\n\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
8238 for index in 0..labels {
8239 let (to, from) = (index % VALUES, (index + 1) % VALUES);
8240 source.push_str(&format!("a{index}:\n\tv{to} += v{from};\n"));
8241 source.push_str(&format!("\tif (--n <= 0) return {};\n", sum.join(" + ")));
8242 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
8243 }
8244 source.push_str("}\n");
8245 source
8246 }
8247
8248 fn the_frame(text: &str) -> u64 {
8250 text.lines()
8251 .find_map(|line| {
8252 let (size, _) = line.strip_prefix("\tsubq\t$")?.split_once(", %rsp")?;
8253 size.parse().ok()
8254 })
8255 .expect("a function that opens a frame")
8256 }
8257
8258 #[test]
8267 fn a_frame_holds_what_is_wanted_at_once_and_not_a_slot_for_every_label() {
8268 let small = the_frame(&asm(&crowded(16)));
8269 let large = the_frame(&asm(&crowded(64)));
8270 assert_eq!(small, large, "four times the labels and the same values: {small}, {large}");
8271 }
8272
8273 #[test]
8282 fn a_template_that_names_its_own_registers_gets_the_ones_it_named() {
8283 let source = "void save(void *nlr) {
8284 __asm volatile (
8285 \"movq %%rsp, 32(%%rdi) \\n\"
8286 \"movq %%rbx, 40(%%rdi) \\n\"
8287 \"movq %%r12, 48(%%rdi) \\n\"
8288 : : \"D\" (nlr) : \"memory\");
8289}
8290";
8291 let text = asm(source);
8292 assert!(text.contains("\tmovq\t%rsp, 32(%rdi)\n"), "{text}");
8293 assert!(text.contains("\tmovq\t%rbx, 40(%rdi)\n"), "{text}");
8294 assert!(text.contains("\tmovq\t%r12, 48(%rdi)\n"), "{text}");
8295 }
8296
8297 #[test]
8298 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
8299 let source = "void **next(void);
8302void f(void) { goto *next(); }
8303";
8304 let expected = "\
8305block0:
8306 %0 = call @next() : () -> ptr
8307 unreachable
8308";
8309 assert_eq!(body(source), expected);
8310 }
8311
8312 #[test]
8313 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
8314 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
8317 let expected = "\
8318block0:
8319 inline_asm.volatile \"mfence\", \"\", \"memory\"()
8320 return
8321";
8322 assert_eq!(body(source), expected);
8323 }
8324
8325 #[test]
8326 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
8327 let source = "\
8330int f(int x, int y) {
8331 int r;
8332 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
8333 return r + y;
8334}
8335";
8336 let expected = "\
8337block0(%0: i32, %1: i32):
8338 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
8339 %4 = add.nsw %2, %3
8340 return %4
8341";
8342 assert_eq!(body(source), expected);
8343 }
8344
8345 #[test]
8346 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
8347 let source = "\
8352struct pair { int a, b; };
8353int f(int x) {
8354 int slot = x;
8355 struct pair p = { x, x };
8356 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
8357 return slot + p.a;
8358}
8359";
8360 let text = body(source);
8361 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
8362 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
8363 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
8364 }
8365
8366 #[test]
8367 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
8368 let source = "\
8373int f(int x) {
8374 int r = 7;
8375 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
8376 return r;
8377away:
8378 return r;
8379}
8380";
8381 let expected = "\
8382block0(%0: i32):
8383 %1 = iconst.i32 7
8384 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
8385
8386block1:
8387 return %2
8388
8389block2:
8390 return %1
8391";
8392 assert_eq!(body(source), expected);
8393 }
8394
8395 #[test]
8396 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
8397 let mut opts = options();
8401 opts.emit = EmitKind::Ir;
8402 for (source, expected) in [
8403 (
8404 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
8405 "output operand constraint lacks '='",
8406 ),
8407 (
8408 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
8409 "lvalue required in 'asm' statement",
8410 ),
8411 (
8412 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
8413 "read-only variable 'g' used as 'asm' output",
8414 ),
8415 (
8416 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
8417 "input operand constraint contains '='",
8418 ),
8419 (
8420 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
8421 "memory input 0 is not directly addressable",
8422 ),
8423 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
8424 (
8425 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
8426 "duplicate asm operand name 'a'",
8427 ),
8428 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
8429 ] {
8430 let result = run(&opts, source);
8431 assert!(result.failed(), "expected this to be reported:\n{source}");
8432 assert!(
8433 result.messages.iter().any(|m| m.contains(expected)),
8434 "{expected}\n{:?}",
8435 result.messages
8436 );
8437 }
8438 }
8439
8440 #[test]
8445 fn an_asm_at_file_scope_that_is_directives_becomes_the_objects_it_defines() {
8446 let text = ir(concat!(
8447 "__asm__(\n",
8448 " \".section .rodata\\n\"\n",
8449 " \".globl first\\n\"\n",
8450 " \".balign 8\\n\"\n",
8451 " \"first:\\n\"\n",
8452 " \".long 1\\n\"\n",
8453 " \".long 2\\n\"\n",
8454 " \".globl last\\n\"\n",
8455 " \"last:\\n\"\n",
8456 " \".quad last - first\\n\");\n",
8457 "extern const int first[];\n",
8458 "extern const long last;\n",
8459 ));
8460 assert!(text.contains("global @first : bytes 8 = { i32 1, i32 2 }, align 8"), "{text}");
8461 assert!(text.contains("global @last : i64 = 8"), "{text}");
8462 }
8463
8464 #[test]
8468 fn a_name_an_asm_at_file_scope_defined_is_not_undone_by_a_declaration_of_it() {
8469 let text = ir(concat!(
8470 "__asm__(\".data\\n.globl counter\\ncounter:\\n.long 7\\n\");\n",
8471 "extern int counter;\n",
8472 "int read(void) { return counter; }\n",
8473 ));
8474 assert!(text.contains("global @counter : i32 = 7"), "{text}");
8475 }
8476
8477 #[test]
8482 fn bytes_under_no_label_at_file_scope_are_a_global_in_front_of_the_label() {
8483 let text = ir(concat!(
8484 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n662:\\n",
8485 ".pushsection .data.ignore\\n.byte 7\\n.popsection\\n.byte 662b - 661b\\n\");\n",
8486 "extern unsigned char stuff[];\n",
8487 "int read(void) { return stuff[0]; }\n",
8488 ));
8489 let under = text.find("global @.Lasm.0 : i8 = 41").expect(&text);
8490 let named = text.find("global @stuff : i8 = 42").expect(&text);
8491 assert!(under < named, "the bytes under no label come first: {text}");
8492 assert!(text.contains("global @.Lasm.1 : i8 = 7, align 1, linkage(internal), section"));
8493 let after = text.find("global @.Lasm.2 : i8 = 1").expect(&text);
8498 assert!(named < after, "{text}");
8499 }
8500
8501 #[test]
8506 fn a_distance_from_here_at_file_scope_is_a_hole_naming_the_global_it_measures_to() {
8507 let text = ir(concat!(
8508 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n",
8509 ".pushsection .data.ignore\\n.long 661b - .\\n.popsection\\n\");\n",
8510 "extern unsigned char stuff[];\n",
8511 "int read(void) { return stuff[0]; }\n",
8512 ));
8513 assert!(text.contains("global @.Lasm.1 : bytes 4 = { away.4 @stuff }"), "{text}");
8517 }
8518
8519 #[test]
8524 fn a_set_at_file_scope_is_a_second_name_for_what_it_names() {
8525 let text = ir(concat!(
8526 "void base(void) {}\n",
8527 "__asm__(\".weak one\\n.set one, base\");\n",
8528 "__asm__(\".globl two\\n.set two, base\");\n",
8529 "__asm__(\".set three, base\");\n",
8530 "void three(void) {}\n",
8531 ));
8532 assert!(text.contains("alias @one = @base, linkage(weak)"), "{text}");
8533 assert!(text.contains("alias @two = @base"), "{text}");
8534 assert!(!text.contains("alias @three"), "a definition of the name wins: {text}");
8535 assert!(text.contains("func @three"), "{text}");
8536 }
8537
8538 #[test]
8543 fn a_set_of_a_name_this_file_does_not_define_says_so() {
8544 let messages = errors("__asm__(\".set here, elsewhere\");\n");
8545 assert!(
8546 messages
8547 .iter()
8548 .any(|m| m.contains("'here' is aliased to undefined symbol 'elsewhere'")
8549 && m.contains("E0697")),
8550 "{messages:?}"
8551 );
8552 }
8553
8554 #[test]
8557 fn an_incbin_at_file_scope_is_the_bytes_of_the_file_it_names() {
8558 let mut opts = options();
8559 opts.emit = EmitKind::Ir;
8560 let mut fs = MemoryFileSystem::new();
8561 fs.insert(
8562 "/main.c",
8563 b"__asm__(\".data\\n.globl blob\\nblob:\\n.incbin \\\"seed\\\"\\n\");\n".to_vec(),
8564 );
8565 fs.insert("seed", b"hi".to_vec());
8566 let result = compile(&opts, "/main.c", &fs);
8567 assert_eq!(result.messages, Vec::<String>::new());
8568 let text = result.text();
8569 assert!(text.contains("global @blob : bytes 2 = { bytes \"hi\" }"), "{text}");
8570 }
8571
8572 #[test]
8575 fn an_incbin_naming_a_file_that_is_not_there_says_which_file() {
8576 let messages = errors("__asm__(\".data\\nb:\\n.incbin \\\"nowhere\\\"\\n\");\n");
8577 assert!(
8578 messages
8579 .iter()
8580 .any(|m| m.contains("cannot open 'nowhere' for reading") && m.contains("E0702")),
8581 "{messages:?}"
8582 );
8583 }
8584
8585 #[test]
8588 fn an_instruction_in_an_asm_at_file_scope_is_refused_rather_than_ignored() {
8589 for source in [
8590 "__asm__(\".text\\n.globl f\\nf:\\n ret\\n\");\n",
8591 "__asm__(\".data\\n.set alias, 4\\n\");\n",
8592 ] {
8593 let messages = errors(source);
8594 assert!(
8595 messages
8596 .iter()
8597 .any(|m| m.contains("not supported yet")
8598 && m.contains("in an `asm` at file scope")),
8599 "{source}\n{messages:?}"
8600 );
8601 }
8602 }
8603
8604 #[test]
8614 fn a_naked_function_is_its_own_prologue_and_its_own_ending() {
8615 let text = asm(concat!(
8616 "unsigned nlr_push_tail(void *nlr);\n",
8617 "__attribute__((naked)) unsigned nlr_push(void *nlr) {\n",
8618 " __asm volatile(\n",
8619 " \"movq (%rsp), %rax\\n\"\n",
8620 " \"movq %rax, 16(%rdi)\\n\"\n",
8621 " \"movq %rbx, 40(%rdi)\\n\"\n",
8622 " \"jmp nlr_push_tail\\n\");\n",
8623 "}\n",
8624 ));
8625 assert!(text.contains("\tmovq\t(%rsp), %rax\n"), "{text}");
8626 assert!(text.contains("\tjmp\tnlr_push_tail\n"), "{text}");
8627 assert!(text.contains("\tud2\n"), "{text}");
8628 assert!(!text.contains("\tpushq\t"), "nothing is saved in front of it: {text}");
8629 assert!(!text.contains("\tret\n"), "the jump is where it ends: {text}");
8630 }
8631
8632 #[test]
8635 fn what_a_function_without_a_prologue_cannot_be_given_is_refused() {
8636 let mut opts = options();
8637 opts.emit = EmitKind::Asm;
8638 for (source, why) in [
8639 (
8640 "__attribute__((naked)) void f(void) { volatile long a[8]; a[0] = 1; }\n",
8641 "bytes of frame",
8642 ),
8643 (
8644 "__attribute__((naked)) void f(int n) { char a[n]; __asm(\"nop\" ::\"r\"(a)); }\n",
8645 "has no prologue to point a frame pointer at it with",
8646 ),
8647 ("void elsewhere(void); void f(void) { __asm(\"jmp elsewhere\"); }\n", "jumps out of"),
8648 ] {
8649 let result = run(&opts, source);
8650 assert!(result.failed(), "expected this to be refused:\n{source}");
8651 assert!(
8652 result.messages.iter().any(|message| message.contains(why)),
8653 "{:?}",
8654 result.messages
8655 );
8656 }
8657 }
8658
8659 #[test]
8660 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
8661 let mut opts = options();
8662 opts.emit = EmitKind::Ir;
8663 for source in [
8664 "int f(int n) { void *p = &&out; if (n) goto *p; { int a[n]; out: return 1; } }\n",
8665 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
8666 ] {
8667 let result = run(&opts, source);
8668 assert!(result.failed(), "expected this to be reported:\n{source}");
8669 assert!(
8670 result.messages.iter().any(|m| m.contains("not supported yet")),
8671 "{:?}",
8672 result.messages
8673 );
8674 }
8675 }
8676
8677 fn round_trip(source: &str) -> (String, String) {
8679 let printed = ir(source);
8680 let mut opts = options();
8681 opts.emit = EmitKind::Ir;
8682 let mut fs = MemoryFileSystem::new();
8683 fs.insert("/main.ir", printed.clone().into_bytes());
8684 let result = compile_ir(&opts, "/main.ir", &fs);
8685 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
8686 (printed, result.text().to_owned())
8687 }
8688
8689 #[test]
8690 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
8691 let (printed, again) = round_trip(
8695 "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",
8696 );
8697 assert_eq!(printed, again);
8698 }
8699
8700 #[test]
8701 fn ir_that_is_not_ir_says_which_line_stopped_it() {
8702 let mut opts = options();
8703 opts.emit = EmitKind::Ir;
8704 let mut fs = MemoryFileSystem::new();
8705 let text = "\
8706; ModuleID = 'a.c'
8707; format 0
8708target triple = \"x86_64-unknown-linux-gnu\"
8709target datalayout = \"e-p:64:64-i64:64-S128\"
8710
8711func @f(), linkage(external) {
8712block0:
8713 frobnicate
8714}
8715";
8716 fs.insert("/main.ir", text.as_bytes().to_vec());
8717 let result = compile_ir(&opts, "/main.ir", &fs);
8718 assert!(result.failed());
8719 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
8720 }
8721
8722 #[test]
8723 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
8724 let mut opts = options();
8727 opts.emit = EmitKind::Ir;
8728 let mut fs = MemoryFileSystem::new();
8729 let text = "\
8730; ModuleID = 'a.c'
8731; format 0
8732target triple = \"x86_64-unknown-linux-gnu\"
8733target datalayout = \"e-p:64:64-i64:64-S128\"
8734
8735func @f(), linkage(external) {
8736block0:
8737 %0 = iconst.i32 1
8738 return %0
8739}
8740";
8741 fs.insert("/main.ir", text.as_bytes().to_vec());
8742 let result = compile_ir(&opts, "/main.ir", &fs);
8743 assert!(result.failed());
8744 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
8745 }
8746
8747 #[test]
8748 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
8749 let mut fs = MemoryFileSystem::new();
8751 fs.insert("/main.ir", Vec::new());
8752 let result = compile_ir(&options(), "/main.ir", &fs);
8753 assert!(result.failed());
8754 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
8755 }
8756
8757 #[test]
8758 fn the_printed_ir_reads_back_as_the_same_module() {
8759 let text = ir("\
8762struct point { int x, y; };
8763static const char greeting[] = \"hi\";
8764int table[4] = { 1, 2, 3 };
8765int puts(const char *);
8766double half(double x) { return x / 2.0; }
8767int f(int n) {
8768 int total = 0;
8769 for (int i = 0; i < n; i++) {
8770 if (i == 3) continue;
8771 total += table[i];
8772 }
8773 switch (n) {
8774 case 0: total = 1;
8775 case 1: total++; break;
8776 default: total = -total;
8777 }
8778 struct point p = { total, 1 };
8779 int *q = &p.y;
8780 puts(greeting);
8781 return p.x + *q;
8782}
8783int dispatch(int c) {
8784 void *p = c ? &&one : &&two;
8785 goto *p;
8786one:
8787 return 1;
8788two:
8789 return 2;
8790}
8791int assembly(int x, int *p) {
8792 int r;
8793 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
8794 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
8795 return r;
8796away:
8797 return 0;
8798}
8799");
8800 let mut names = Interner::new();
8801 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
8802 assert_eq!(rucc_ir::print(&module, &names), text);
8803 }
8804
8805 #[test]
8806 fn what_save_temps_keeps_is_the_text_that_was_compiled_and_the_assembly_that_was_assembled() {
8807 let mut opts = options();
8811 opts.emit = EmitKind::Object;
8812 opts.save_temps = rucc_session::SaveTemps::Object;
8813 let result = run(&opts, "#define N 2\nint a[N];\n");
8814 assert_eq!(result.messages, Vec::<String>::new());
8815 let text = result.temps.preprocessed.expect("the preprocessed text");
8816 assert!(text.contains("int a[2];"), "{text}");
8817 assert!(text.starts_with("# 1 \"/main.c\""), "{text}");
8818 let asm = result.temps.assembly.expect("the assembly");
8819 assert!(asm.contains("a:"), "{asm}");
8820 assert!(matches!(result.artifact, Artifact::Object { .. }), "{:?}", result.artifact);
8821 }
8822
8823 #[test]
8824 fn nothing_is_kept_unless_the_flag_asked_for_it() {
8825 let mut opts = options();
8828 opts.emit = EmitKind::Object;
8829 assert_eq!(run(&opts, "int a;\n").temps, Temps::default());
8830 }
8831
8832 #[test]
8833 fn a_compilation_that_stops_before_the_back_end_keeps_the_text_and_no_assembly() {
8834 let mut opts = options();
8837 opts.emit = EmitKind::Ir;
8838 opts.save_temps = rucc_session::SaveTemps::Cwd;
8839 let result = run(&opts, "int a;\n");
8840 assert!(result.temps.preprocessed.is_some());
8841 assert_eq!(result.temps.assembly, None);
8842 }
8843
8844 fn span(from: u64, len: u64, held: rucc_debug::Held) -> rucc_debug::Span {
8846 rucc_debug::Span { from, len, held }
8847 }
8848
8849 #[test]
8850 fn two_stretches_that_meet_and_agree_come_out_as_one() {
8851 let one = span(0, 4, rucc_debug::Held::Reg(3));
8852 let two = span(4, 4, rucc_debug::Held::Reg(3));
8853 assert_eq!(settle(vec![two, one]), vec![span(0, 8, rucc_debug::Held::Reg(3))]);
8854 }
8855
8856 #[test]
8857 fn two_stretches_that_disagree_leave_the_addresses_they_share_unanswered() {
8858 let one = span(0, 8, rucc_debug::Held::Reg(3));
8859 let two = span(4, 8, rucc_debug::Held::Reg(4));
8860 let settled = settle(vec![one, two]);
8863 assert_eq!(
8864 settled,
8865 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(8, 4, rucc_debug::Held::Reg(4))]
8866 );
8867 }
8868
8869 #[test]
8870 fn a_stretch_two_others_disagree_over_the_whole_of_says_nothing_at_all() {
8871 let one = span(0, 8, rucc_debug::Held::Reg(3));
8872 let two = span(0, 8, rucc_debug::Held::Frame(-16));
8873 assert_eq!(settle(vec![one, two]), Vec::new());
8874 }
8875
8876 #[test]
8877 fn stretches_with_a_gap_between_them_keep_the_gap() {
8878 let one = span(0, 4, rucc_debug::Held::Reg(3));
8879 let two = span(16, 4, rucc_debug::Held::Reg(3));
8880 assert_eq!(settle(vec![one, two]), vec![one, two]);
8881 }
8882
8883 fn extent(len: usize) -> rucc_object::Extent {
8885 rucc_object::Extent {
8886 name: "f".to_owned(),
8887 start: 0,
8888 len,
8889 align: 1,
8890 binding: rucc_object::Binding::Global,
8891 visibility: rucc_object::Visibility::Default,
8892 patch: None,
8893 }
8894 }
8895
8896 fn row(at: usize, lo: u32, hi: u32) -> rucc_asm::Row {
8898 let span = Span::new(lo, hi);
8899 rucc_asm::Row { at, span, inst: None }
8900 }
8901
8902 #[test]
8903 fn a_row_ends_where_the_next_address_begins() {
8904 let rows = [row(0, 0, 1), row(4, 1, 2), row(10, 2, 3)];
8905 assert_eq!(ends(&extent(16), &rows), vec![4, 10, 16]);
8906 }
8907
8908 #[test]
8909 fn rows_sharing_an_address_all_end_where_the_next_address_begins() {
8910 let rows = [row(0, 0, 1), row(4, 1, 2), row(4, 2, 3), row(4, 3, 4)];
8913 assert_eq!(ends(&extent(12), &rows), vec![4, 12, 12, 12]);
8914 }
8915
8916 #[test]
8917 fn the_rows_of_a_scope_that_are_next_to_each_other_come_out_as_one_stretch() {
8918 let rows = [row(0, 0, 4), row(4, 10, 14), row(8, 14, 18), row(12, 40, 44)];
8919 let ends = ends(&extent(16), &rows);
8920 let scope = Span::new(8, 20);
8921 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 8 }]);
8922 }
8923
8924 #[test]
8925 fn a_scope_the_back_end_split_in_two_comes_out_as_two_stretches() {
8926 let rows = [row(0, 10, 14), row(4, 40, 44), row(8, 14, 18)];
8927 let ends = ends(&extent(12), &rows);
8928 let scope = Span::new(8, 20);
8929 let over = spread(scope, &ends, &rows);
8930 assert_eq!(
8931 over,
8932 vec![rucc_debug::Reach { from: 0, len: 4 }, rucc_debug::Reach { from: 8, len: 4 }]
8933 );
8934 }
8935
8936 #[test]
8937 fn a_row_with_no_source_of_its_own_belongs_to_no_scope() {
8938 let rows = [rucc_asm::Row { at: 0, span: Span::DUMMY, inst: None }, row(4, 10, 14)];
8940 let ends = ends(&extent(8), &rows);
8941 let scope = Span::new(0, 20);
8942 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 4 }]);
8943 }
8944
8945 fn scope(parent: Option<usize>, lo: u32, hi: u32) -> crate::shapes::Scope {
8947 let span = Span::new(lo, hi);
8948 crate::shapes::Scope { parent, span }
8949 }
8950
8951 #[test]
8952 fn a_function_gets_the_scopes_its_own_locals_are_in_and_nothing_else() {
8953 let scopes = [scope(None, 0, 10), scope(None, 20, 30), scope(Some(1), 22, 26)];
8955 let rows = [row(0, 22, 24), row(4, 26, 28)];
8956 let (out, at) = nests(&[Some(2)], &scopes, &extent(8), &rows);
8957 assert_eq!(at.get(&1), Some(&0));
8960 assert_eq!(at.get(&2), Some(&1));
8961 assert_eq!(at.get(&0), None);
8962 assert_eq!(out.len(), 2);
8963 assert_eq!(out[0].parent, None);
8964 assert_eq!(out[1].parent, Some(0));
8965 assert_eq!(out[0].over, vec![rucc_debug::Reach { from: 0, len: 8 }]);
8966 assert_eq!(out[1].over, vec![rucc_debug::Reach { from: 0, len: 4 }]);
8967 }
8968
8969 #[test]
8970 fn a_local_written_straight_into_the_body_pulls_no_scope_in() {
8971 let scopes = [scope(None, 20, 30)];
8972 let rows = [row(0, 22, 24)];
8973 let (out, at) = nests(&[None], &scopes, &extent(4), &rows);
8974 assert_eq!(out, Vec::new());
8975 assert!(at.is_empty());
8976 }
8977
8978 #[test]
8979 fn a_scope_whose_code_all_went_away_is_still_one_of_the_functions_scopes() {
8980 let scopes = [scope(None, 20, 30)];
8984 let rows = [row(0, 40, 44)];
8985 let (out, at) = nests(&[Some(0)], &scopes, &extent(4), &rows);
8986 assert_eq!(at.get(&0), Some(&0));
8987 assert_eq!(out.len(), 1);
8988 assert_eq!(out[0].over, Vec::new());
8989 }
8990}