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 let assembled = rucc_asm::assemble(&funcs, names, target, unwind, opts.debug_info)
970 .map_err(refused)?;
971 let text = assembled.text;
972 let data = globals.image();
973 let info = if opts.debug_info {
977 describe(&text, &data, &assembled.lines, &funcs, origin, opts, target)
978 .map_err(|why| vec![internal(&why)])?
979 } else {
980 rucc_object::Info::default()
981 };
982 let bytes =
985 rucc_object::write(&text, &data, &aliases, target, output(opts, target), &info)
986 .map_err(wrote)?;
987 let defines = rucc_object::defines(&text, &data, &aliases, target).map_err(wrote)?;
992 Ok(Artifact::Object { bytes, defines })
993 }
994 _ => Ok(Artifact::Text(rucc_mir::print(&funcs, names, target.regs))),
995 }
996}
997
998fn describe(
1017 text: &rucc_object::Text,
1018 data: &rucc_object::Data,
1019 lines: &[Vec<rucc_asm::Row>],
1020 machine: &[rucc_mir::Func],
1021 origin: Origin<'_>,
1022 opts: &Options,
1023 target: &TargetInfo,
1024) -> Result<rucc_object::Info, String> {
1025 let rewrite = |path: &str| opts.prefix_map.debug.apply(path).into_owned();
1026 let mut files: Vec<String> = Vec::new();
1031 let mut funcs = Vec::with_capacity(text.funcs.len());
1032 for ((extent, rows), built) in text.funcs.iter().zip(lines).zip(machine) {
1033 let mut out: Vec<rucc_debug::Row> = Vec::with_capacity(rows.len());
1034 for row in rows {
1035 if row.span.is_dummy() {
1036 continue;
1037 }
1038 let Some(at) = origin.map.presumed(row.span.lo) else {
1039 continue;
1040 };
1041 let which = interned(&mut files, rewrite(at.name));
1042 let place = rucc_debug::Row {
1043 at: row.at as u64,
1044 file: which,
1045 line: at.line,
1046 column: at.column,
1047 };
1048 match out.last() {
1058 Some(last) if last.at == place.at => {}
1059 _ => out.push(place),
1060 }
1061 }
1062 if let Some(first) = out.first_mut() {
1069 first.at = 0;
1070 }
1071 let known = origin.meaning.funcs.get(&extent.name);
1077 let decl = known.map(|known| rucc_debug::Place {
1078 file: interned(&mut files, rewrite(&known.file)),
1079 line: known.line,
1080 });
1081 let mut sig = known.and_then(|known| known.sig.clone());
1090 let mut placed: Vec<(u32, i32)> = built.locals.clone();
1091 let mut spots = stretches(extent, rows, built, target);
1092 if let (Some(sig), Some(known)) = (sig.as_mut(), known) {
1093 for (param, decl) in sig.params.iter_mut().zip(&known.params) {
1094 let Some(decl) = *decl else { continue };
1095 if let Some(which) = placed.iter().position(|&(at, _)| at == decl) {
1096 let at = rucc_debug::Held::Frame(i64::from(placed.remove(which).1));
1097 param.spot = Some(rucc_debug::Spot::Always(at));
1098 continue;
1099 }
1100 let Some(which) = spots.iter().position(|(at, _)| *at == decl) else { continue };
1104 param.spot = Some(rucc_debug::Spot::Over(spots.remove(which).1));
1105 }
1106 }
1107 let mut locals = Vec::with_capacity(placed.len() + spots.len());
1111 let mut wants: Vec<Option<usize>> = Vec::with_capacity(locals.capacity());
1115 for (decl, at) in placed {
1116 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1117 wants.push(named.scope);
1118 locals.push(rucc_debug::Local {
1119 name: named.name.clone(),
1120 ty: named.ty,
1121 decl: Some(rucc_debug::Place {
1122 file: interned(&mut files, rewrite(&named.file)),
1123 line: named.line,
1124 }),
1125 spot: rucc_debug::Spot::Always(rucc_debug::Held::Frame(i64::from(at))),
1126 scope: None,
1127 });
1128 }
1129 spots.sort_by_key(|(decl, _)| *decl);
1133 for (decl, spans) in spots {
1134 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1135 wants.push(named.scope);
1136 locals.push(rucc_debug::Local {
1137 name: named.name.clone(),
1138 ty: named.ty,
1139 decl: Some(rucc_debug::Place {
1140 file: interned(&mut files, rewrite(&named.file)),
1141 line: named.line,
1142 }),
1143 spot: rucc_debug::Spot::Over(spans),
1144 scope: None,
1145 });
1146 }
1147 let (scopes, at) = nests(&wants, &origin.meaning.scopes, extent, rows);
1152 for (local, want) in locals.iter_mut().zip(&wants) {
1153 local.scope = want.and_then(|want| at.get(&want).copied());
1154 }
1155 funcs.push(rucc_debug::Function {
1156 name: extent.name.clone(),
1157 len: extent.len as u64,
1158 rows: out,
1159 decl,
1160 sig,
1161 external: known.is_some_and(|known| known.external),
1162 locals,
1163 scopes,
1164 });
1165 }
1166 let mut globals = Vec::new();
1173 for object in &data.objects {
1174 let Some(held) = origin.meaning.objects.get(&object.name) else { continue };
1175 globals.push(rucc_debug::Global {
1176 name: object.name.clone(),
1177 ty: held.ty,
1178 decl: Some(rucc_debug::Place {
1179 file: interned(&mut files, rewrite(&held.file)),
1180 line: held.line,
1181 }),
1182 external: held.external,
1183 });
1184 }
1185 let unit = rucc_debug::Unit {
1186 name: rewrite(origin.name),
1187 dir: rewrite(opts.working_dir.as_deref().unwrap_or(".")),
1190 producer: format!("rucc {}", crate::VERSION),
1191 files,
1192 types: origin.meaning.types.clone(),
1193 funcs,
1194 globals,
1195 pointer: u8::try_from(target.pointer_width / 8).unwrap_or(8),
1196 frames: opts.unwinds(),
1200 };
1201 rucc_debug::write(&unit).map_err(|why| why.to_string())
1202}
1203
1204fn stretches(
1215 extent: &rucc_object::Extent,
1216 rows: &[rucc_asm::Row],
1217 built: &rucc_mir::Func,
1218 target: &TargetInfo,
1219) -> Vec<(u32, Vec<rucc_debug::Span>)> {
1220 let (false, Some(regs)) = (built.kept.is_empty(), target.call_regs) else {
1223 return Vec::new();
1224 };
1225 let ends = ends(extent, rows);
1226 let mut bounds = vec![None; built.inst_count()];
1227 for (which, row) in rows.iter().enumerate() {
1228 let Some(inst) = row.inst else { continue };
1229 bounds[inst.index()] = Some((row.at as u64, ends[which]));
1230 }
1231 let mut spots: Vec<(u32, Vec<rucc_debug::Span>)> = Vec::new();
1232 for kept in &built.kept {
1233 let (Some((from, _)), Some((_, to))) = (bounds[kept.from.index()], bounds[kept.to.index()])
1234 else {
1235 continue;
1236 };
1237 if to <= from {
1238 continue;
1239 }
1240 let held = match kept.at {
1241 rucc_mir::Where::Reg { reg, class } => match regs.dwarf(class, reg) {
1244 Some(number) => rucc_debug::Held::Reg(number),
1245 None => continue,
1246 },
1247 rucc_mir::Where::Frame(at) => rucc_debug::Held::Frame(i64::from(at)),
1248 };
1249 let span = rucc_debug::Span { from, len: to - from, held };
1250 match spots.iter_mut().find(|(decl, _)| *decl == kept.decl) {
1251 Some((_, spans)) => spans.push(span),
1252 None => spots.push((kept.decl, vec![span])),
1253 }
1254 }
1255 for (_, spans) in &mut spots {
1256 *spans = settle(std::mem::take(spans));
1257 }
1258 spots.retain(|(_, spans)| !spans.is_empty());
1259 spots
1260}
1261
1262fn ends(extent: &rucc_object::Extent, rows: &[rucc_asm::Row]) -> Vec<u64> {
1272 let mut out = vec![extent.len as u64; rows.len()];
1273 let mut next = extent.len as u64;
1274 for which in (0..rows.len()).rev() {
1275 let at = rows[which].at as u64;
1276 out[which] = match next > at {
1279 true => next,
1280 false => out.get(which + 1).copied().unwrap_or(extent.len as u64),
1281 };
1282 next = next.min(at);
1283 }
1284 out
1285}
1286
1287fn nests(
1303 wants: &[Option<usize>],
1304 scopes: &[crate::shapes::Scope],
1305 extent: &rucc_object::Extent,
1306 rows: &[rucc_asm::Row],
1307) -> (Vec<rucc_debug::Scope>, HashMap<usize, usize>) {
1308 let mut needed: Vec<usize> = Vec::new();
1309 for &want in wants {
1310 let mut up = want;
1311 while let Some(which) = up {
1312 if needed.contains(&which) {
1313 break;
1314 }
1315 needed.push(which);
1316 up = scopes.get(which).and_then(|scope| scope.parent);
1317 }
1318 }
1319 needed.sort_unstable();
1322 let at: HashMap<usize, usize> =
1323 needed.iter().enumerate().map(|(which, &scope)| (scope, which)).collect();
1324 let ends = ends(extent, rows);
1325 let out = needed
1326 .iter()
1327 .map(|&which| {
1328 let scope = &scopes[which];
1329 rucc_debug::Scope {
1330 parent: scope.parent.and_then(|parent| at.get(&parent).copied()),
1331 over: spread(scope.span, &ends, rows),
1332 }
1333 })
1334 .collect();
1335 (out, at)
1336}
1337
1338fn spread(span: Span, ends: &[u64], rows: &[rucc_asm::Row]) -> Vec<rucc_debug::Reach> {
1346 let mut out: Vec<rucc_debug::Reach> = Vec::new();
1347 for (which, row) in rows.iter().enumerate() {
1348 if row.span.is_dummy() || row.span.lo < span.lo || row.span.hi > span.hi {
1349 continue;
1350 }
1351 let (from, to) = (row.at as u64, ends[which]);
1352 if to <= from {
1353 continue;
1354 }
1355 match out.last_mut() {
1356 Some(last) if last.from + last.len >= from => {
1357 last.len = to.saturating_sub(last.from).max(last.len);
1358 }
1359 _ => out.push(rucc_debug::Reach { from, len: to - from }),
1360 }
1361 }
1362 out
1363}
1364
1365fn settle(mut spans: Vec<rucc_debug::Span>) -> Vec<rucc_debug::Span> {
1376 spans.sort_by_key(|span| (span.from, span.len));
1377 let mut edges: Vec<u64> =
1380 spans.iter().flat_map(|span| [span.from, span.from + span.len]).collect();
1381 edges.sort_unstable();
1382 edges.dedup();
1383 let mut out: Vec<rucc_debug::Span> = Vec::new();
1384 let mut first = 0;
1385 for pair in edges.windows(2) {
1386 let (from, to) = (pair[0], pair[1]);
1387 while spans.get(first).is_some_and(|span| span.from + span.len <= from) {
1391 first += 1;
1392 }
1393 let mut held = None;
1394 let mut agreed = true;
1395 for span in &spans[first..] {
1396 if span.from >= to {
1397 break;
1398 }
1399 if span.from > from || span.from + span.len < to {
1400 continue;
1401 }
1402 match held {
1403 None => held = Some(span.held),
1404 Some(seen) => agreed &= seen == span.held,
1405 }
1406 }
1407 let (Some(held), true) = (held, agreed) else { continue };
1408 match out.last_mut() {
1409 Some(last) if last.from + last.len == from && last.held == held => {
1410 last.len += to - from
1411 }
1412 _ => out.push(rucc_debug::Span { from, len: to - from, held }),
1413 }
1414 }
1415 out
1416}
1417
1418fn interned(files: &mut Vec<String>, name: String) -> usize {
1424 match files.iter().position(|have| *have == name) {
1425 Some(which) => which,
1426 None => {
1427 files.push(name);
1428 files.len() - 1
1429 }
1430 }
1431}
1432
1433fn output(opts: &Options, target: &TargetInfo) -> rucc_object::Output {
1445 let mut features = 0;
1446 if target.tuple.arch() == Arch::X86_64 {
1447 if opts.control.branch() {
1448 features |= rucc_object::Property::IBT;
1449 }
1450 if opts.control.ret() {
1451 features |= rucc_object::Property::SHSTK;
1452 }
1453 }
1454 rucc_object::Output {
1455 sections: rucc_object::Sections {
1456 functions: opts.function_sections,
1457 data: opts.data_sections,
1458 },
1459 property: rucc_object::Property { features },
1460 }
1461}
1462
1463fn wrote(why: rucc_object::Error) -> Vec<Diagnostic> {
1469 match why {
1470 rucc_object::Error::Format { .. } => vec![unsupported(&why.to_string())],
1471 rucc_object::Error::Refused { .. } => vec![internal(&why.to_string())],
1472 }
1473}
1474
1475fn refused(why: rucc_asm::Error) -> Vec<Diagnostic> {
1482 match why {
1483 rucc_asm::Error::Thread { .. }
1484 | rucc_asm::Error::IFunc { .. }
1485 | rucc_asm::Error::Frame { .. } => {
1486 vec![unsupported(&why.to_string())]
1487 }
1488 _ => vec![internal(&why.to_string())],
1489 }
1490}
1491
1492fn unsupported(message: &str) -> Diagnostic {
1498 unsupported_at(message, Span::DUMMY)
1499}
1500
1501fn unsupported_at(message: &str, span: Span) -> Diagnostic {
1507 Diagnostic::error(message.to_owned(), span)
1508 .with_code("E0653")
1509 .note("this construct is not lowered yet, see https://github.com/tamnd/rucc/issues", span)
1510}
1511
1512fn invalid(message: &str) -> Diagnostic {
1514 Diagnostic::error(message.to_owned(), Span::DUMMY).with_code("E0661")
1515}
1516
1517fn internal(message: &str) -> Diagnostic {
1519 Diagnostic::error(format!("internal error: {message}"), Span::DUMMY)
1520 .with_code("E0652")
1521 .note("this is a bug in rucc rather than in the program, please report it", Span::DUMMY)
1522}
1523
1524fn failure(message: String) -> Compiled {
1527 Compiled {
1528 artifact: Artifact::Nothing,
1529 messages: vec![format!("rucc: error: {message}")],
1530 errors: 1,
1531 fired: Fired::new(),
1532 pressure: Pressure::new(),
1533 lowerings: Lowerings::new(),
1534 dumps: Vec::new(),
1535 remarks: String::new(),
1536 deps: Vec::new(),
1537 temps: Temps::default(),
1538 }
1539}
1540
1541#[cfg(test)]
1542mod tests {
1543 use rucc_session::{MemoryFileSystem, Std};
1544 use rucc_target::Triple;
1545
1546 use super::*;
1547
1548 fn options() -> Options {
1549 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1550 opts.emit = EmitKind::Tast;
1551 opts
1552 }
1553
1554 fn run(opts: &Options, source: &str) -> Compiled {
1555 let mut fs = MemoryFileSystem::new();
1556 fs.insert("/main.c", source.to_owned().into_bytes());
1557 compile(opts, "/main.c", &fs)
1558 }
1559
1560 fn freestanding() -> Options {
1564 let mut opts = options();
1565 opts.hosted = false;
1566 opts.search.push_system(rucc_session::runtime::DIR);
1567 opts
1568 }
1569
1570 fn shipped(source: &str) -> String {
1572 let result = run(&freestanding(), source);
1573 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1574 result.text().to_owned()
1575 }
1576
1577 fn tast(source: &str) -> String {
1579 let result = run(&options(), source);
1580 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1581 result.text().to_owned()
1582 }
1583
1584 #[test]
1585 fn the_shipped_stdarg_declares_a_list_and_the_four_operators() {
1586 let text = shipped(concat!(
1587 "#include <stdarg.h>\n",
1588 "int sum(int n, ...) {\n",
1589 " va_list ap, copy;\n",
1590 " va_start(ap, n);\n",
1591 " va_copy(copy, ap);\n",
1592 " int total = va_arg(ap, int) + va_arg(copy, int);\n",
1593 " va_end(ap);\n",
1594 " va_end(copy);\n",
1595 " return total;\n",
1596 "}\n",
1597 ));
1598 assert!(text.contains("va-start"), "{text}");
1599 assert!(text.contains("va-copy"), "{text}");
1600 assert!(text.contains("va-arg"), "{text}");
1601 assert!(text.contains("va-end"), "{text}");
1602 }
1603
1604 #[test]
1608 fn stdarg_hands_out_the_type_alone_when_that_is_all_that_was_asked_for() {
1609 let text = shipped(concat!(
1610 "#define __need___va_list\n",
1611 "#include <stdarg.h>\n",
1612 "int vprint(const char *f, __gnuc_va_list ap);\n",
1613 "#ifdef va_start\n",
1614 "#error va_start should not be defined\n",
1615 "#endif\n",
1616 "#ifdef _VA_LIST_DEFINED\n",
1617 "#error va_list should not have been made\n",
1618 "#endif\n",
1619 ));
1620 assert!(text.contains("vprint"), "{text}");
1621 }
1622
1623 #[test]
1626 fn stddef_answers_one_piece_at_a_time_and_the_next_request_still_gets_through() {
1627 let text = shipped(concat!(
1628 "#define __need_size_t\n",
1629 "#include <stddef.h>\n",
1630 "#ifdef offsetof\n",
1631 "#error offsetof should not be defined yet\n",
1632 "#endif\n",
1633 "#define __need_ptrdiff_t\n",
1634 "#include <stddef.h>\n",
1635 "#include <stddef.h>\n",
1636 "size_t a;\n",
1637 "ptrdiff_t b;\n",
1638 "wchar_t c;\n",
1639 "max_align_t d;\n",
1640 "void *e = NULL;\n",
1641 "struct P { int x; long y; };\n",
1642 "size_t f = offsetof(struct P, y);\n",
1643 ));
1644 assert!(text.contains("decl #0 a : unsigned long"), "{text}");
1645 assert!(text.contains("decl #1 b : long"), "{text}");
1646 }
1647
1648 #[test]
1649 fn the_shipped_limits_and_float_are_the_targets_own_answers() {
1650 let text = shipped(concat!(
1651 "#include <limits.h>\n",
1652 "#include <float.h>\n",
1653 "int bits = CHAR_BIT;\n",
1654 "long big = LONG_MAX;\n",
1655 "int low = INT_MIN;\n",
1656 "int radix = FLT_RADIX;\n",
1657 "int digits = DBL_MANT_DIG;\n",
1658 ));
1659 assert!(text.contains("const 8 : int"), "{text}");
1660 assert!(text.contains("const 9223372036854775807 : long"), "{text}");
1661 assert!(text.contains("const 2 : int"), "{text}");
1662 assert!(text.contains("const 53 : int"), "{text}");
1663 }
1664
1665 #[test]
1669 fn the_shipped_stdint_writes_the_whole_set_when_there_is_no_library_to_defer_to() {
1670 let text = shipped(concat!(
1671 "#include <stdint.h>\n",
1672 "int64_t a = INT64_C(1);\n",
1673 "uint_least16_t b;\n",
1674 "intptr_t c;\n",
1675 "uintmax_t d = UINTMAX_MAX;\n",
1676 "int wide = sizeof(int_fast64_t);\n",
1677 ));
1678 assert!(text.contains("decl #0 a : long"), "{text}");
1679 assert!(text.contains("decl #1 b : unsigned short"), "{text}");
1680 assert!(text.contains("decl #2 c : long"), "{text}");
1681 }
1682
1683 #[test]
1694 fn the_shipped_mmintrin_defines_the_mmx_type_and_the_operations_over_it() {
1695 let text = shipped(concat!(
1696 "#include <mmintrin.h>\n",
1697 "__m64 add(__m64 a, __m64 b) { return _mm_add_pi16(a, b); }\n",
1698 "__m64 pack(__m64 a, __m64 b) { return _m_packsswb(a, b); }\n",
1699 "__m64 shift(__m64 a) { return _mm_srai_pi32(a, 3); }\n",
1700 "int low(__m64 a) { return _mm_cvtsi64_si32(a); }\n",
1701 "void done(void) { _mm_empty(); }\n",
1702 ));
1703 assert!(text.contains("add"), "{text}");
1704 assert!(text.contains("pack"), "{text}");
1705 assert!(text.contains("shift"), "{text}");
1706 }
1707
1708 #[test]
1713 fn the_shipped_mm_malloc_asks_for_aligned_memory_and_gives_it_back() {
1714 let text = shipped(concat!(
1715 "#include <mm_malloc.h>\n",
1716 "void *get(void) { return _mm_malloc(64, 16); }\n",
1717 "void put(void *p) { _mm_free(p); }\n",
1718 ));
1719 assert!(text.contains("get"), "{text}");
1720 assert!(text.contains("put"), "{text}");
1721 }
1722
1723 #[test]
1735 fn the_shipped_xmmintrin_defines_the_sse_type_and_the_operations_over_it() {
1736 let text = shipped(concat!(
1737 "#include <xmmintrin.h>\n",
1738 "__m128 add(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1739 "__m128 one(__m128 a, __m128 b) { return _mm_max_ss(a, b); }\n",
1740 "__m128 mask(__m128 a, __m128 b) { return _mm_cmpnle_ps(a, b); }\n",
1741 "__m128 pick(__m128 a, __m128 b) { return _mm_shuffle_ps(a, b, _MM_SHUFFLE(0,1,2,3)); }\n",
1742 "int bits(__m128 a) { return _mm_movemask_ps(a); }\n",
1743 "int near(__m128 a) { return _mm_cvtss_si32(a); }\n",
1744 "__m128 wide(__m64 a) { return _mm_cvtpi16_ps(a); }\n",
1745 "void *room(void) { return _mm_malloc(64, 16); }\n",
1746 "void hint(const float *p) { _mm_prefetch(p, _MM_HINT_T0); _mm_sfence(); }\n",
1747 ));
1748 assert!(text.contains("add"), "{text}");
1749 assert!(text.contains("mask"), "{text}");
1750 assert!(text.contains("pick"), "{text}");
1751 assert!(text.contains("wide"), "{text}");
1752 }
1753
1754 #[test]
1761 fn the_shipped_xmmintrin_leaves_out_the_names_that_need_an_instruction() {
1762 let text = rucc_session::runtime::header("xmmintrin.h").expect("xmmintrin.h is shipped");
1763 for absent in [
1764 "_mm_sqrt_ps",
1765 "_mm_sqrt_ss",
1766 "_mm_rsqrt_ps",
1767 "_mm_rsqrt_ss",
1768 "_mm_getcsr",
1769 "_mm_setcsr",
1770 ] {
1771 let defined = text.contains(&format!("{absent}("));
1772 assert!(!defined, "{absent} is defined and the header says it is not");
1773 assert!(text.contains(absent), "{absent} is absent and unexplained");
1774 }
1775 }
1776
1777 #[test]
1778 fn the_shipped_emmintrin_defines_both_sse2_types_and_the_operations_over_them() {
1779 let text = shipped(concat!(
1780 "#include <emmintrin.h>\n",
1781 "__m128i add(__m128i a, __m128i b) { return _mm_add_epi64(a, b); }\n",
1782 "__m128i wide(__m128i a, __m128i b) { return _mm_mul_epu32(a, b); }\n",
1783 "__m128i pick(__m128i a) { return _mm_shuffle_epi32(a, _MM_SHUFFLE(0,1,2,3)); }\n",
1784 "__m128i up(__m128i a) { return _mm_slli_epi64(a, 13); }\n",
1785 "__m128i down(__m128i a) { return _mm_srli_si128(a, 3); }\n",
1786 "__m128i pack(__m128i a, __m128i b) { return _mm_packus_epi16(a, b); }\n",
1787 "int bits(__m128i a) { return _mm_movemask_epi8(a); }\n",
1788 "__m128d sum(__m128d a, __m128d b) { return _mm_add_sd(a, b); }\n",
1789 "__m128d mask(__m128d a, __m128d b) { return _mm_cmpunord_pd(a, b); }\n",
1790 "__m128i near(__m128d a) { return _mm_cvtpd_epi32(a); }\n",
1791 "__m128d over(__m128 a) { return _mm_cvtps_pd(a); }\n",
1792 "__m128i half(__m64 a) { return _mm_movpi64_epi64(a); }\n",
1793 "__m128i grab(void const *p) { return _mm_loadu_si128(p); }\n",
1794 "void wall(void) { _mm_lfence(); _mm_mfence(); }\n",
1795 ));
1796 assert!(text.contains("wide"), "{text}");
1797 assert!(text.contains("pack"), "{text}");
1798 assert!(text.contains("near"), "{text}");
1799 assert!(text.contains("half"), "{text}");
1800 }
1801
1802 #[test]
1806 fn the_shipped_immintrin_reaches_the_names_the_headers_under_it_define() {
1807 let text = shipped(concat!(
1808 "#include <immintrin.h>\n",
1809 "unsigned long long matching(unsigned char tag, unsigned char const *bucket) {\n",
1810 " __m128i const want = _mm_set1_epi8((char)tag);\n",
1811 " __m128i const chunk = _mm_loadu_si128((__m128i const *)(void const *)bucket);\n",
1812 " __m128i const same = _mm_cmpeq_epi8(chunk, want);\n",
1813 " return (unsigned long long)_mm_movemask_epi8(same);\n",
1814 "}\n",
1815 "__m64 narrow(__m64 a, __m64 b) { return _mm_add_pi32(a, b); }\n",
1816 "__m128 single(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1817 ));
1818 assert!(text.contains("matching"), "{text}");
1819 assert!(text.contains("narrow"), "the MMX header is not reached: {text}");
1820 assert!(text.contains("single"), "the SSE header is not reached: {text}");
1821 }
1822
1823 #[test]
1827 fn the_shipped_x86intrin_reaches_the_fences_windows_headers_ask_it_for() {
1828 let text = shipped(concat!(
1829 "#include <x86intrin.h>\n",
1830 "void barriers(void *p) {\n",
1831 " _mm_lfence();\n",
1832 " _mm_sfence();\n",
1833 " _mm_mfence();\n",
1834 " _mm_pause();\n",
1835 " _mm_clflush(p);\n",
1836 "}\n",
1837 "__m128i wide(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1838 ));
1839 assert!(text.contains("barriers"), "{text}");
1840 assert!(text.contains("wide"), "the SSE2 header is not reached: {text}");
1841 }
1842
1843 #[test]
1847 fn the_umbrella_and_the_header_under_it_can_both_be_included() {
1848 let text = shipped(concat!(
1849 "#include <immintrin.h>\n",
1850 "#include <emmintrin.h>\n",
1851 "#include <immintrin.h>\n",
1852 "#include <x86intrin.h>\n",
1853 "__m128i twice(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1854 ));
1855 assert!(text.contains("twice"), "{text}");
1856 }
1857
1858 #[test]
1862 fn the_shipped_emmintrin_leaves_out_the_two_square_roots() {
1863 let text = rucc_session::runtime::header("emmintrin.h").expect("emmintrin.h is shipped");
1864 for absent in ["_mm_sqrt_pd", "_mm_sqrt_sd"] {
1865 let defined = text.contains(&format!("{absent}("));
1866 assert!(!defined, "{absent} is defined and the header says it is not");
1867 assert!(text.contains(absent), "{absent} is absent and unexplained");
1868 }
1869 }
1870
1871 #[test]
1872 fn the_three_formality_headers_still_have_to_work() {
1873 let text = shipped(concat!(
1874 "#include <stdbool.h>\n",
1875 "#include <stdalign.h>\n",
1876 "#include <iso646.h>\n",
1877 "#include <stdnoreturn.h>\n",
1878 "int t = true and not false;\n",
1879 "_Alignas(16) char buf[16];\n",
1880 "int a = alignof(long);\n",
1881 ));
1882 assert!(text.contains("decl #0 t : int"), "{text}");
1883 assert!(text.contains("const 8 : unsigned long"), "{text}");
1884 }
1885
1886 #[test]
1894 fn every_shipped_header_can_be_included_twice() {
1895 let once: String = rucc_session::runtime::names()
1896 .iter()
1897 .map(|name| format!("#include <{name}>\n"))
1898 .collect();
1899 let twice = once.repeat(2);
1900 assert_eq!(shipped(&format!("{once}int x;\n")), shipped(&format!("{twice}int x;\n")));
1901 }
1902
1903 #[test]
1904 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
1905 let fs = MemoryFileSystem::new();
1906 let result = compile(&options(), "/nope.c", &fs);
1907 assert!(result.failed());
1908 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
1909 assert!(result.text().is_empty());
1910 }
1911
1912 #[test]
1913 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
1914 let text = tast("int x = 1;\n");
1915 let expected = "\
1916decl #0 x : int object external static defined
1917 init
1918 +0
1919 const 1 : int
1920";
1921 assert_eq!(text, expected);
1922 }
1923
1924 #[test]
1925 fn the_macros_are_expanded_before_anything_is_parsed() {
1926 let text = tast("#define N 2\nint a[N];\n");
1930 assert!(text.starts_with("decl #0 a : int[2] object external static tentative"), "{text}");
1931 }
1932
1933 #[test]
1939 fn a_pragma_is_not_a_declaration_and_the_parse_walks_past_the_ones_it_does_not_read() {
1940 let text = tast(concat!(
1941 "#pragma pack(4)\n",
1942 "struct s { int a; };\n",
1943 "#pragma pack()\n",
1944 "int b;\n",
1945 "_Pragma(\"GCC visibility push(default)\") int c;\n",
1946 ));
1947 assert!(text.contains("decl #0 b : int"), "{text}");
1948 assert!(text.contains("decl #1 c : int"), "{text}");
1949 }
1950
1951 #[test]
1959 fn the_layout_attributes_move_the_members_and_the_record_the_way_gcc_lays_them_out() {
1960 tast(concat!(
1961 "struct A { char c; int i; } __attribute__((packed));\n",
1962 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
1963 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
1964 "struct B { char c; int i; } __attribute__((aligned));\n",
1967 "_Static_assert(sizeof(struct B) == 16 && _Alignof(struct B) == 16, \"B\");\n",
1968 "struct C { char c; int i __attribute__((packed)); };\n",
1969 "_Static_assert(sizeof(struct C) == 5 && _Alignof(struct C) == 1, \"C\");\n",
1970 "_Static_assert(__builtin_offsetof(struct C, i) == 1, \"C.i\");\n",
1971 "struct D { char c; int i; } __attribute__((packed, aligned(4)));\n",
1972 "_Static_assert(sizeof(struct D) == 8 && _Alignof(struct D) == 4, \"D\");\n",
1973 "_Static_assert(__builtin_offsetof(struct D, i) == 1, \"D.i\");\n",
1974 "struct E { char c; _Alignas(8) int i; };\n",
1975 "_Static_assert(sizeof(struct E) == 16 && _Alignof(struct E) == 8, \"E\");\n",
1976 "_Static_assert(__builtin_offsetof(struct E, i) == 8, \"E.i\");\n",
1977 "struct F { char c; int i __attribute__((aligned(8))); };\n",
1978 "_Static_assert(sizeof(struct F) == 16 && _Alignof(struct F) == 8, \"F\");\n",
1979 "struct G { char c; short s; } __attribute__((aligned(2)));\n",
1982 "_Static_assert(sizeof(struct G) == 4 && _Alignof(struct G) == 2, \"G\");\n",
1983 "struct H { char c; int i; } __attribute__((aligned(2)));\n",
1984 "_Static_assert(sizeof(struct H) == 8 && _Alignof(struct H) == 4, \"H\");\n",
1985 "struct I { [[gnu::packed]] char c; int i; };\n",
1988 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
1989 "struct J { char c; [[gnu::packed]] int i; };\n",
1990 "_Static_assert(sizeof(struct J) == 5 && _Alignof(struct J) == 1, \"J\");\n",
1991 "struct M { char c; int i : 5; int j : 20; } __attribute__((packed));\n",
1992 "_Static_assert(sizeof(struct M) == 5 && _Alignof(struct M) == 1, \"M\");\n",
1993 "struct N { char c; long long l; } __attribute__((aligned(32)));\n",
1994 "_Static_assert(sizeof(struct N) == 32 && _Alignof(struct N) == 32, \"N\");\n",
1995 "union L { char c; int i; } __attribute__((packed));\n",
1996 "_Static_assert(sizeof(union L) == 4 && _Alignof(union L) == 1, \"L\");\n",
1997 "struct O { char c; int i; } __attribute__((__packed__));\n",
2001 "_Static_assert(sizeof(struct O) == 5 && _Alignof(struct O) == 1, \"O\");\n",
2002 "struct P { char c; int i; } __attribute__((__aligned__(8)));\n",
2003 "_Static_assert(sizeof(struct P) == 8 && _Alignof(struct P) == 8, \"P\");\n",
2004 ));
2005 }
2006
2007 #[test]
2020 fn a_transparent_union_takes_the_member_a_value_fits_and_is_declared_either_way() {
2021 let text = tast(concat!(
2022 "struct one { int x; };\n",
2023 "struct two { long y; };\n",
2024 "typedef union { struct one *a; struct two *b; void *any; }\n",
2025 " __attribute__((__transparent_union__)) arg;\n",
2026 "int takes(arg v);\n",
2027 "int f(struct one *p, struct two *q, char *c) {\n",
2028 " return takes(p) + takes(q) + takes(c) + takes(0);\n",
2029 "}\n",
2030 "int takes(struct one *p);\n",
2032 "int (*as_a_member)(struct one *) = takes;\n",
2033 "int (*as_the_union)(arg) = takes;\n",
2034 ));
2035 assert!(text.contains("compound-literal"), "{text}");
2036 }
2037
2038 #[test]
2044 fn the_attribute_on_the_declarator_of_a_typedef_is_the_one_glibc_writes() {
2045 let text = tast(concat!(
2046 "struct sockaddr { int family; };\n",
2047 "struct sockaddr_in { int family; int addr; };\n",
2048 "typedef union { struct sockaddr *plain; struct sockaddr_in *inet; }\n",
2049 " addr_arg __attribute__((__transparent_union__));\n",
2050 "int bind_to(int fd, addr_arg where);\n",
2051 "int f(struct sockaddr_in *where) { return bind_to(0, where); }\n",
2052 ));
2053 assert!(text.contains("compound-literal"), "{text}");
2054 }
2055
2056 #[test]
2064 fn a_transparent_union_that_cannot_keep_the_promise_is_dropped_with_a_word_about_it() {
2065 let result = run(
2066 &options(),
2067 concat!(
2068 "union wider { int small; double large; } __attribute__((transparent_union));\n",
2069 "struct plain { int x; } __attribute__((transparent_union));\n",
2070 ),
2071 );
2072 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
2073 assert!(!result.failed(), "{:?}", result.messages);
2074 for message in &result.messages {
2075 assert!(message.contains("'transparent_union' attribute ignored"), "{message}");
2076 }
2077 assert!(result.messages[0].contains("first member"), "{:?}", result.messages);
2078 assert!(result.messages[1].contains("only a union"), "{:?}", result.messages);
2079 }
2080
2081 #[test]
2090 fn an_access_to_a_packed_member_says_the_alignment_the_layout_left_it() {
2091 let packed = body(concat!(
2092 "struct P { char c; int v; } __attribute__((packed));\n",
2093 "int f(struct P *p) { return p->v; }\n",
2094 ));
2095 assert!(packed.contains("load.i32 %2, align 1,"), "{packed}");
2096 let plain = body(concat!(
2098 "struct P { char c; int v; };\n",
2099 "int f(struct P *p) { return p->v; }\n",
2100 ));
2101 assert!(plain.contains("load.i32 %2, align 4,"), "{plain}");
2102 }
2103
2104 #[test]
2111 fn what_is_inside_a_packed_member_is_no_more_aligned_than_the_member_is() {
2112 let stepped = body(concat!(
2113 "struct P { char c; int v[4]; } __attribute__((packed));\n",
2114 "int f(struct P *p, int i) { return p->v[i]; }\n",
2115 ));
2116 assert!(stepped.contains(", align 1,"), "{stepped}");
2117 assert!(!stepped.contains(", align 4,"), "{stepped}");
2118 let nested = body(concat!(
2119 "struct Inner { int v; };\n",
2120 "struct P { char c; struct Inner in; } __attribute__((packed));\n",
2121 "int f(struct P *p) { return p->in.v; }\n",
2122 ));
2123 assert!(nested.contains(", align 1,"), "{nested}");
2124 assert!(!nested.contains(", align 4,"), "{nested}");
2125 }
2126
2127 #[test]
2143 fn an_access_through_a_typedef_that_lowered_its_alignment_says_the_one_the_typedef_asked_for() {
2144 let through = body(concat!(
2145 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2146 "unsigned int f(const void *p) { return *(const unalign32 *)p; }\n",
2147 ));
2148 assert!(through.contains("load.i32 %0, align 1,"), "{through}");
2149 let stepped = body(concat!(
2152 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2153 "unsigned int f(unalign32 *p, int i) { return p[i]; }\n",
2154 ));
2155 assert!(stepped.contains(", align 1,"), "{stepped}");
2156 assert!(!stepped.contains(", align 4,"), "{stepped}");
2157 let plain = body(concat!(
2160 "typedef unsigned int word;\n",
2161 "unsigned int f(const void *p) { return *(const word *)p; }\n",
2162 ));
2163 assert!(plain.contains("load.i32 %0, align 4,"), "{plain}");
2164 }
2165
2166 #[test]
2177 fn a_vector_read_through_a_typedef_that_lowered_its_alignment_comes_back_a_piece_at_a_time() {
2178 let prefix = concat!(
2179 "typedef long long v2di __attribute__((__vector_size__(16)));\n",
2180 "typedef long long v2di_u __attribute__((__vector_size__(16), __aligned__(1)));\n",
2181 );
2182 let loaded =
2183 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di_u *)p; }}"));
2184 assert_eq!(loaded.matches("align 1\n").count(), 2, "{loaded}");
2185 assert!(!loaded.contains("align 16"), "{loaded}");
2186 let stored = body(&format!("{prefix}void f(void *p, v2di b) {{ *(v2di_u *)p = b; }}"));
2189 assert!(stored.contains("memcpy %0, %3, size 16, align 1"), "{stored}");
2190 let aligned =
2192 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di *)p; }}"));
2193 assert!(aligned.contains("align 16"), "{aligned}");
2194 }
2195
2196 #[test]
2205 fn the_aligned_attribute_on_a_declaration_raises_what_that_one_object_is_aligned_to() {
2206 tast(concat!(
2207 "int v __attribute__((aligned(64)));\n",
2208 "_Static_assert(__alignof__(v) == 64, \"v\");\n",
2209 "__attribute__((aligned(32))) int w;\n",
2212 "_Static_assert(__alignof__(w) == 32, \"w\");\n",
2213 "[[gnu::aligned(16)]] int x;\n",
2214 "_Static_assert(__alignof__(x) == 16, \"x\");\n",
2215 "int y __attribute__((aligned(2)));\n",
2218 "_Static_assert(__alignof__(y) == 4, \"y\");\n",
2219 "void f(void) { int a __attribute__((aligned(128)));\n",
2221 "_Static_assert(__alignof__(a) == 128, \"a\"); (void)a; }\n",
2222 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2225 "void g(void) __attribute__((aligned(256)));\n",
2228 "void g(void) {}\n",
2229 "_Static_assert(__alignof__(g) == 256, \"g\");\n",
2230 ));
2231 }
2232
2233 #[test]
2237 fn what_a_declaration_asked_to_be_aligned_to_is_what_the_assembler_is_told() {
2238 let text = asm(concat!(
2239 "int v __attribute__((aligned(64)));\n",
2240 "void g(void) __attribute__((aligned(256)));\n",
2241 "void g(void) {}\n",
2242 "void plain(void) {}\n",
2243 ));
2244 assert!(text.contains("\t.p2align\t6\n\t.type\tv, @object\n"), "{text}");
2245 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2246 assert!(text.contains("\t.p2align\t4, 0x90\n\t.globl\tplain\n"), "{text}");
2247 }
2248
2249 #[test]
2255 fn the_alignment_the_command_line_asked_of_every_function_is_a_floor_under_all_of_them() {
2256 let source = concat!(
2257 "void g(void) __attribute__((aligned(256)));\n",
2258 "void g(void) {}\n",
2259 "void small(void) __attribute__((aligned(4)));\n",
2260 "void small(void) {}\n",
2261 "void plain(void) {}\n",
2262 );
2263 let listing = |align: Option<u32>| {
2264 let mut opts = options();
2265 opts.emit = EmitKind::Asm;
2266 opts.align_functions = align;
2267 let result = run(&opts, source);
2268 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
2269 result.text().to_owned()
2270 };
2271
2272 let text = listing(Some(32));
2273 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "the larger one wins: {text}");
2274 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tsmall\n"), "{text}");
2275 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tplain\n"), "{text}");
2276
2277 let text = listing(Some(8));
2280 assert!(text.contains("\t.p2align\t3, 0x90\n\t.globl\tplain\n"), "{text}");
2281 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2282 }
2283
2284 #[test]
2293 fn an_aligned_typedef_says_what_an_object_of_it_is_aligned_to_and_may_lower_it() {
2294 tast(concat!(
2295 "typedef int L __attribute__((aligned(2)));\n",
2296 "_Static_assert(__alignof__(L) == 2, \"L\");\n",
2297 "_Static_assert(_Alignof(L) == 2, \"L alignof\");\n",
2298 "_Static_assert(sizeof(L) == 4, \"L size\");\n",
2300 "struct T { char c; L x; };\n",
2301 "_Static_assert(sizeof(struct T) == 6, \"T\");\n",
2302 "_Static_assert(__builtin_offsetof(struct T, x) == 2, \"T.x\");\n",
2303 "typedef int H __attribute__((aligned(16)));\n",
2305 "_Static_assert(__alignof__(H) == 16, \"H\");\n",
2306 "_Static_assert(sizeof(H) == 4, \"H size\");\n",
2307 "struct U { char c; H x; };\n",
2308 "_Static_assert(sizeof(struct U) == 32, \"U\");\n",
2309 "_Static_assert(__builtin_offsetof(struct U, x) == 16, \"U.x\");\n",
2310 "typedef L M __attribute__((aligned(8)));\n",
2313 "_Static_assert(__alignof__(M) == 8, \"M\");\n",
2314 "typedef L N;\n",
2317 "_Static_assert(__alignof__(N) == 2, \"N\");\n",
2318 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2320 ));
2321 let text = asm(concat!(
2322 "typedef int L __attribute__((aligned(2)));\n",
2323 "typedef int H __attribute__((aligned(16)));\n",
2324 "L low;\n",
2325 "H high;\n",
2326 ));
2327 assert!(text.contains("\t.p2align\t1\n\t.type\tlow, @object\n"), "{text}");
2328 assert!(text.contains("\t.p2align\t4\n\t.type\thigh, @object\n"), "{text}");
2329 }
2330
2331 #[test]
2339 fn the_vector_size_attribute_builds_a_type_of_lanes_and_measures_it_in_bytes() {
2340 tast(concat!(
2341 "typedef int __attribute__((vector_size(16))) v4si;\n",
2342 "_Static_assert(sizeof(v4si) == 16 && _Alignof(v4si) == 16, \"v4si\");\n",
2343 "typedef char __attribute__((vector_size(16))) v16qi;\n",
2344 "_Static_assert(sizeof(v16qi) == 16, \"v16qi\");\n",
2345 "typedef int __attribute__((vector_size(4))) v1si;\n",
2348 "_Static_assert(sizeof(v1si) == 4, \"v1si\");\n",
2349 "typedef float __attribute__((__vector_size__(8))) v2sf;\n",
2351 "_Static_assert(sizeof(v2sf) == 8, \"v2sf\");\n",
2352 "typedef short [[gnu::vector_size(8)]] v4hi;\n",
2353 "_Static_assert(sizeof(v4hi) == 8, \"v4hi\");\n",
2354 "v4si g;\n",
2357 "_Static_assert(sizeof(g[0]) == 4, \"lane\");\n",
2358 "_Static_assert(sizeof(g + g) == 16, \"whole\");\n",
2359 "_Static_assert(sizeof(g + 1) == 16, \"broadcast\");\n",
2362 "_Static_assert(sizeof(v4si[3]) == 48, \"array\");\n",
2364 ));
2365 }
2366
2367 #[test]
2377 fn a_vector_is_written_whole_into_an_array_of_them_and_named_by_a_type_name() {
2378 tast(concat!(
2379 "typedef int __attribute__((vector_size(8))) v2si;\n",
2380 "v2si table[] = { (v2si){ 1, 2 }, (v2si){ 3, 4 } };\n",
2381 "_Static_assert(sizeof(table) == 16, \"two of them and not eight lanes\");\n",
2382 "v2si written = (int __attribute__((vector_size(8)))){ 5, 6 };\n",
2384 "_Static_assert(sizeof((int __attribute__((vector_size(16)))){ 0 }) == 16, \"named\");\n",
2385 "v2si lanes[2] = { 1, 2, 3, 4 };\n",
2388 "_Static_assert(sizeof(lanes) == 16, \"still elided\");\n",
2389 ));
2390 }
2391
2392 #[test]
2400 fn a_lane_is_assignable_and_a_shift_takes_a_count_of_its_own_lane() {
2401 let result = run(
2402 &options(),
2403 concat!(
2404 "typedef int __attribute__((vector_size(16))) v4si;\n",
2405 "typedef unsigned __attribute__((vector_size(16))) v4ui;\n",
2406 "void write(v4si *out, v4ui a, v4si b, int n) {\n",
2407 " v4si v = { 1, 2, 3, 4 };\n",
2408 " v[0] = n;\n",
2409 " v[1] += n;\n",
2410 " v[2]++;\n",
2411 " *&v[3] = n;\n",
2412 " v4ui shifted = a >> b;\n",
2414 " shifted <<= b;\n",
2415 " *out = v + (v4si)shifted + (1 << b);\n",
2418 "}\n",
2419 "void refused(const v4si c) {\n",
2422 " c[0] = 1;\n",
2423 "}\n",
2424 ),
2425 );
2426 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
2427 assert!(result.messages[0].contains("assignment of read-only"), "{:?}", result.messages);
2428 }
2429
2430 #[test]
2442 fn a_record_that_asks_for_the_other_byte_order_swaps_every_scalar_it_holds() {
2443 let read = "int f(struct s *p) { return p->i; }\n";
2444 let big = "struct s { int i; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
2445 assert!(body(&format!("{big}{read}")).contains("bswap"), "{big}");
2446
2447 let armoured =
2448 "struct s { int i; } __attribute__((__scalar_storage_order__(\"big-endian\")));\n";
2449 assert!(body(&format!("{armoured}{read}")).contains("bswap"), "{armoured}");
2450
2451 let front = "struct __attribute__((scalar_storage_order(\"big-endian\"))) s { int i; };\n";
2452 assert!(body(&format!("{front}{read}")).contains("bswap"), "{front}");
2453
2454 let standard = "struct s { int i; } [[gnu::scalar_storage_order(\"big-endian\")]];\n";
2455 assert!(body(&format!("{standard}{read}")).contains("bswap"), "{standard}");
2456
2457 let same =
2458 "struct s { int i; } __attribute__((scalar_storage_order(\"little-endian\")));\n";
2459 assert!(!body(&format!("{same}{read}")).contains("bswap"), "{same}");
2460
2461 let byte = "struct s { char c; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
2463 let source = format!("{byte}int f(struct s *p) {{ return p->c; }}\n");
2464 assert!(!body(&source).contains("bswap"), "{byte}");
2465
2466 tast(concat!(
2467 "struct s { int i; short h; char c; }",
2468 " __attribute__((scalar_storage_order(\"big-endian\")));\n",
2469 "_Static_assert(sizeof(struct s) == 8 && _Alignof(struct s) == 4, \"s\");\n",
2470 "_Static_assert(__builtin_offsetof(struct s, h) == 4, \"s.h\");\n",
2471 "_Static_assert(__builtin_offsetof(struct s, c) == 6, \"s.c\");\n",
2472 ));
2473 }
2474
2475 #[test]
2481 fn a_bit_field_in_one_of_those_records_is_counted_from_the_top_of_its_bytes() {
2482 let members = "short i : 12; char c1 : 1; char c2 : 1; char c3 : 1; char c4 : 1;";
2483 let read = "int f(struct s *p) { return p->i; }\n";
2484 let plain = format!("struct s {{ {members} }};\n{read}");
2485 let reversed = format!(
2486 "struct s {{ {members} }} __attribute__((scalar_storage_order(\"big-endian\")));\n\
2487 {read}"
2488 );
2489 assert!(body(&plain).contains("shl"), "{}", body(&plain));
2490 assert!(!body(&plain).contains("bswap"), "{}", body(&plain));
2491 let built = body(&reversed);
2494 assert!(built.contains("bswap"), "{built}");
2495 assert!(!built.contains("shl"), "{built}");
2496 assert!(built.contains("ashr"), "{built}");
2497 }
2498
2499 #[test]
2506 fn the_address_of_a_scalar_stored_the_other_way_round_is_refused() {
2507 let opts = options();
2508 let record = "struct s { int i; int a[2]; struct in { int n; } w; }\n\
2509 __attribute__((scalar_storage_order(\"big-endian\")));\n";
2510 let taken = format!("{record}int *f(struct s *p) {{ return &p->i; }}\n");
2511 assert_eq!(
2512 run(&opts, &taken).messages,
2513 ["/main.c:3:30: error: cannot take address of scalar with reverse storage order \
2514 [E0712]"]
2515 );
2516 let element = format!("{record}int *f(struct s *p) {{ return &p->a[0]; }}\n");
2517 let messages = run(&opts, &element).messages;
2518 assert!(messages[0].contains("[E0712]"), "{messages:?}");
2519
2520 let whole = format!("{record}int *f(struct s *p) {{ return (int *) &p->w; }}\n");
2521 assert_eq!(run(&opts, &whole).messages, Vec::<String>::new(), "{whole}");
2522 }
2523
2524 #[test]
2528 fn a_storage_order_that_names_neither_end_is_refused_with_the_two_words_that_are_taken() {
2529 let opts = options();
2530 let wrong = "struct s { int i; } __attribute__((scalar_storage_order(\"middle\")));\n";
2531 assert_eq!(
2532 run(&opts, wrong).messages,
2533 ["/main.c:1:36: error: 'scalar_storage_order' argument must be one of \"big-endian\" \
2534 or \"little-endian\" [E0688]"]
2535 );
2536 let bare = "struct s { int i; } __attribute__((scalar_storage_order));\n";
2537 let messages = run(&opts, bare).messages;
2538 assert!(messages[0].contains("[E0688]"), "{messages:?}");
2539 }
2540
2541 #[test]
2551 fn packing_is_what_decides_whether_a_bit_field_may_straddle_its_own_storage() {
2552 assert_eq!(bit_field_byte("struct s { int x : 12; char y : 6; };"), 2);
2554 assert_eq!(
2555 bit_field_byte("struct s { int x : 12; char y : 6; } __attribute__((packed));"),
2556 1
2557 );
2558 assert_eq!(
2559 bit_field_byte("struct s { int x : 12; __attribute__((packed)) char y : 6; };"),
2560 1
2561 );
2562 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { int x : 12; char y : 6; };"), 1);
2563 assert_eq!(bit_field_byte("struct s { char x; int y : 30; };"), 4);
2565 assert_eq!(bit_field_byte("struct s { char x; int y : 30; } __attribute__((packed));"), 1);
2566 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { char x; int y : 30; };"), 1);
2568 assert_eq!(bit_field_byte("#pragma pack(2)\nstruct s { char x; int y : 30; };"), 1);
2569 }
2570
2571 fn bit_field_byte(record: &str) -> u64 {
2573 let source = format!("{record}\nint f(struct s *p) {{ return p->y; }}\n");
2574 let body = body(&source);
2575 let Some((before, _)) = body.split_once("ptr_add") else { return 0 };
2576 let (_, constant) = before.rsplit_once("iconst.i64 ").expect("an offset constant");
2577 constant.lines().next().expect("a line").trim().parse().expect("a byte offset")
2578 }
2579
2580 #[test]
2586 fn an_attribute_among_the_specifiers_is_kept_beside_the_ones_written_in_front() {
2587 tast(concat!(
2588 "struct a { char c; __attribute__((aligned(8))) int i; };\n",
2589 "_Static_assert(sizeof(struct a) == 16 && _Alignof(struct a) == 8, \"a\");\n",
2590 "_Static_assert(__builtin_offsetof(struct a, i) == 8, \"a.i\");\n",
2591 "struct b { char c; __attribute__((packed)) int i; };\n",
2592 "_Static_assert(sizeof(struct b) == 5 && _Alignof(struct b) == 1, \"b\");\n",
2593 "_Static_assert(__builtin_offsetof(struct b, i) == 1, \"b.i\");\n",
2594 "typedef struct { char c; int i; } __attribute__((packed)) c;\n",
2595 "_Static_assert(sizeof(c) == 5 && _Alignof(c) == 1, \"c\");\n",
2596 ));
2597 }
2598
2599 #[test]
2605 fn pragma_pack_caps_every_member_and_is_read_where_the_body_closes() {
2606 tast(concat!(
2607 "#pragma pack(1)\n",
2608 "struct A { char c; int i; };\n",
2609 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2610 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2611 "#pragma pack()\n",
2612 "struct B { char c; int i; };\n",
2613 "_Static_assert(sizeof(struct B) == 8 && _Alignof(struct B) == 4, \"B\");\n",
2614 "#pragma pack(2)\n",
2615 "struct C { char c; int i; double d; };\n",
2616 "_Static_assert(sizeof(struct C) == 14 && _Alignof(struct C) == 2, \"C\");\n",
2617 "_Static_assert(__builtin_offsetof(struct C, d) == 6, \"C.d\");\n",
2618 "struct K { char c; int i __attribute__((aligned(8))); };\n",
2620 "_Static_assert(sizeof(struct K) == 6 && _Alignof(struct K) == 2, \"K\");\n",
2621 "_Static_assert(__builtin_offsetof(struct K, i) == 2, \"K.i\");\n",
2622 "struct J { char c; int i; } __attribute__((aligned(8)));\n",
2624 "_Static_assert(sizeof(struct J) == 8 && _Alignof(struct J) == 8, \"J\");\n",
2625 "#pragma pack()\n",
2626 "#pragma pack(push, 1)\n",
2627 "struct D { char c; short s; };\n",
2628 "_Static_assert(sizeof(struct D) == 3 && _Alignof(struct D) == 1, \"D\");\n",
2629 "#pragma pack(pop)\n",
2630 "struct E { char c; short s; };\n",
2631 "_Static_assert(sizeof(struct E) == 4 && _Alignof(struct E) == 2, \"E\");\n",
2632 "struct H { char c;\n",
2634 "#pragma pack(1)\n",
2635 " int i; };\n",
2636 "_Static_assert(sizeof(struct H) == 5 && _Alignof(struct H) == 1, \"H\");\n",
2637 "#pragma pack(1)\n",
2638 "struct I { char c;\n",
2639 "#pragma pack()\n",
2640 " int i; };\n",
2641 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2642 "#pragma pack()\n",
2643 "#pragma pack(push, 8)\n",
2645 "#pragma pack(push, 1)\n",
2646 "struct P { char c; int i; };\n",
2647 "_Static_assert(sizeof(struct P) == 5 && _Alignof(struct P) == 1, \"P\");\n",
2648 "#pragma pack(pop)\n",
2649 "struct Q { char c; int i; };\n",
2650 "_Static_assert(sizeof(struct Q) == 8 && _Alignof(struct Q) == 4, \"Q\");\n",
2651 "#pragma pack(pop)\n",
2652 "#pragma pack(16)\n",
2654 "struct R { char c; int i; };\n",
2655 "_Static_assert(sizeof(struct R) == 8 && _Alignof(struct R) == 4, \"R\");\n",
2656 "#pragma pack()\n",
2657 "#pragma pack(1)\n",
2658 "struct S { char c; int i : 5; int j : 20; };\n",
2659 "_Static_assert(sizeof(struct S) == 5 && _Alignof(struct S) == 1, \"S\");\n",
2660 "union T { char c; int i; };\n",
2661 "_Static_assert(sizeof(union T) == 4 && _Alignof(union T) == 1, \"T\");\n",
2662 "#pragma pack()\n",
2663 ));
2664 }
2665
2666 #[test]
2670 fn a_pack_line_that_is_not_one_is_reported_in_the_words_gcc_uses() {
2671 let result = run(
2672 &options(),
2673 concat!(
2674 "#pragma pack 4\n",
2675 "#pragma pack(pop)\n",
2676 "#pragma pack(3)\n",
2677 "#pragma pack(1) junk\n",
2678 "#pragma pack(push, 1\n",
2679 "#pragma pack(x)\n",
2680 "#pragma pack(0)\n",
2683 "#pragma pack(push)\n",
2684 "struct s { char c; int i; };\n",
2685 "#pragma pack(pop)\n",
2686 "#pragma pack(pop, foo)\n",
2687 ),
2688 );
2689 let expected = [
2690 "missing `(` after `#pragma pack` - ignored",
2691 "`#pragma pack (pop)` encountered without matching `#pragma pack (push)`",
2692 "alignment must be a small power of two, not 3",
2693 "junk at end of `#pragma pack`",
2694 "malformed `#pragma pack(push[, id][, <n>])` - ignored",
2695 "unknown action `x` for `#pragma pack` - ignored",
2696 "`#pragma pack(pop, foo)` encountered without matching `#pragma pack(push, foo)`",
2697 ];
2698 assert_eq!(result.messages.len(), expected.len(), "{:?}", result.messages);
2699 for (message, want) in result.messages.iter().zip(expected) {
2700 assert!(message.contains(want), "expected {want:?} in {message:?}");
2701 }
2702 }
2703
2704 #[test]
2711 fn a_declaration_behind_an_empty_macro_is_not_eaten_by_the_pragma_above_it() {
2712 let result = run(
2713 &options(),
2714 concat!(
2715 "#pragma pack(push, 1)\n",
2716 "#pragma pack(pop)\n",
2717 "#define API\n",
2718 "API const char version[] = \"3.53.4\";\n",
2719 "const char *get(void) { return version; }\n",
2720 ),
2721 );
2722 assert!(result.messages.is_empty(), "{:?}", result.messages);
2723 }
2724
2725 #[test]
2729 fn the_wide_integer_answers_to_all_three_of_its_names() {
2730 let text = tast("__uint128_t a; __int128_t b; unsigned __int128 c;\n");
2731 assert!(text.contains("decl #0 a : unsigned __int128"), "{text}");
2732 assert!(text.contains("decl #1 b : __int128"), "{text}");
2733 assert!(text.contains("decl #2 c : unsigned __int128"), "{text}");
2734 }
2735
2736 #[test]
2737 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
2738 let text = tast("long f(int a, long b) { return a + b; }\n");
2742 assert!(text.contains("convert arithmetic"), "{text}");
2743 }
2744
2745 #[test]
2746 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
2747 for source in [
2748 "#error stop\n",
2749 "int f(void) { return 1 + ; }\n",
2750 "int f(void) { return undeclared; }\n",
2751 ] {
2752 let result = run(&options(), source);
2753 assert!(result.failed(), "expected this to fail:\n{source}");
2754 assert!(
2755 result.text().is_empty(),
2756 "a file that did not compile wrote a tree:\n{source}"
2757 );
2758 }
2759 }
2760
2761 #[test]
2762 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
2763 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
2767 assert_eq!(result.errors, 1, "{:?}", result.messages);
2768 }
2769
2770 #[test]
2771 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
2772 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
2776 assert_eq!(result.errors, 1, "{:?}", result.messages);
2777 }
2778
2779 #[test]
2780 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
2781 let source = "int f(void) { char c = 300; return c; }\n";
2782 let plain = run(&options(), source);
2783 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
2784 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
2785 assert!(!plain.text().is_empty(), "a warning is not a reason to write nothing");
2786
2787 let mut opts = options();
2788 opts.warnings_are_errors = true;
2789 let strict = run(&opts, source);
2790 assert!(strict.failed());
2791 assert!(strict.text().is_empty(), "and under -Werror it is a reason to write nothing");
2792 for message in &strict.messages {
2793 assert!(!message.contains("warning:"), "{message}");
2794 }
2795 }
2796
2797 #[test]
2798 fn w_drops_the_warning_before_werror_can_promote_it() {
2799 let source = "int f(void) { char c = 300; return c; }\n";
2800 let mut opts = options();
2801 opts.warnings = false;
2802 let quiet = run(&opts, source);
2803 assert_eq!(quiet.messages, Vec::<String>::new());
2804 assert_eq!(quiet.errors, 0);
2805 assert!(!quiet.text().is_empty(), "and the file still compiles");
2806
2807 opts.warnings_are_errors = true;
2810 let both = run(&opts, source);
2811 assert_eq!(both.messages, Vec::<String>::new());
2812 assert!(!both.failed(), "-w -Werror is not an error about a warning nobody saw");
2813 }
2814
2815 #[test]
2816 fn the_dialect_reaches_the_keywords_and_the_checking() {
2817 let source = "typeof(1) x;\n";
2820 let mut opts = options();
2821 opts.std = Std::C23;
2822 opts.gnu_extensions = false;
2823 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
2824
2825 opts.std = Std::C17;
2826 assert!(run(&opts, source).failed());
2827 }
2828
2829 #[test]
2830 fn asking_for_a_kind_that_is_not_written_yet_runs_the_front_end_and_writes_nothing() {
2831 let mut opts = options();
2832 opts.emit = EmitKind::Object;
2833 let result = run(&opts, "int x = 1;\n");
2834 assert!(!result.failed(), "{:?}", result.messages);
2835 assert!(result.text().is_empty());
2836 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
2839 }
2840
2841 fn mir(source: &str) -> String {
2843 let mut opts = options();
2844 opts.emit = EmitKind::MirFinal;
2845 let result = run(&opts, source);
2846 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2847 result.text().to_owned()
2848 }
2849
2850 #[test]
2856 fn a_function_goes_from_c_to_instructions_with_real_registers_in_them() {
2857 let text = mir("int add(int a, int b) { return a + b; }\n");
2858 assert!(text.starts_with("mfunc @add {"), "{text}");
2859 assert!(text.contains("x64.add_rr_32"), "{text}");
2860 assert!(text.contains("x64.ret"), "{text}");
2861 assert!(!text.contains('%'), "{text}");
2864 }
2865
2866 #[test]
2868 fn a_function_with_no_body_produces_no_machine_function() {
2869 let text = mir("int g(int);\nint f(int a) { return g(a); }\n");
2870 assert_eq!(text.matches("mfunc @").count(), 1, "{text}");
2871 assert!(text.contains("mfunc @f {"), "{text}");
2872 assert!(text.contains("x64.call"), "{text}");
2873 }
2874
2875 #[test]
2877 fn every_definition_in_the_file_is_generated_and_they_keep_their_order() {
2878 let text = mir("int a(int x) { return x; }\nint b(int x) { return x; }\n");
2879 let first = text.find("mfunc @a").expect("the first function");
2880 let second = text.find("mfunc @b").expect("the second function");
2881 assert!(first < second, "{text}");
2882 }
2883
2884 #[test]
2886 fn the_target_decides_which_convention_the_generated_code_follows() {
2887 let mut opts = options();
2888 opts.emit = EmitKind::MirFinal;
2889 let linux = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2890 assert!(linux.contains("$rdi"), "{linux}");
2891
2892 opts.target = "x86_64-pc-windows-msvc".parse::<Triple>().unwrap();
2893 let windows = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2894 assert!(windows.contains("$rcx"), "{windows}");
2895 assert!(!windows.contains("$rdi"), "{windows}");
2896 }
2897
2898 #[test]
2906 fn a_tagged_member_with_no_name_is_a_member_on_windows_and_nothing_on_linux() {
2907 let source = concat!(
2908 "struct S { union U { int i; void *p; }; unsigned long tymed; };\n",
2909 "int size(void) { return sizeof(struct S); }\n",
2910 "int f(struct S *s) { s->i = 1; return s->i; }\n",
2911 );
2912
2913 let mut opts = options();
2914 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
2915 let windows = run(&opts, source);
2916 assert!(windows.messages.is_empty(), "{:?}", windows.messages);
2917
2918 let linux = run(&options(), source);
2919 assert_eq!(linux.messages.len(), 3, "{:?}", linux.messages);
2920 assert!(linux.messages[0].contains("does not declare anything"), "{:?}", linux.messages);
2921
2922 let mut opts = options();
2925 opts.ms_extensions = Some(true);
2926 let asked = run(&opts, source);
2927 assert!(asked.messages.is_empty(), "{:?}", asked.messages);
2928 }
2929
2930 #[test]
2932 fn a_target_this_has_no_back_end_for_is_reported_rather_than_generated() {
2933 let mut opts = options();
2934 opts.emit = EmitKind::MirFinal;
2935 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2936 let result = run(&opts, "int f(int a) { return a; }\n");
2937 assert!(result.failed());
2938 assert!(result.messages[0].contains("no back end for aarch64"), "{:?}", result.messages);
2939 assert!(result.text().is_empty());
2940 }
2941
2942 #[test]
2954 fn a_construct_the_back_end_cannot_reach_yet_is_reported_against_its_function() {
2955 let mut opts = options();
2956 opts.emit = EmitKind::MirFinal;
2957 let source = "void a(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
2958 s; s.x = 1; v[0] = s.x; }\n\
2959 void b(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
2960 s; s.x = 1; v[0] = s.x; }\n";
2961 let result = run(&opts, source);
2962 assert!(result.failed());
2963 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
2964 assert!(result.messages[0].contains("cannot generate code for 'a'"), "{:?}", result);
2965 assert!(result.messages[0].contains("wants more alignment"), "{:?}", result);
2966 assert!(result.messages[1].contains("cannot generate code for 'b'"), "{:?}", result);
2967 assert!(result.text().is_empty());
2968 }
2969
2970 #[test]
2978 fn a_variable_length_array_walks_its_pages_where_every_page_of_the_frame_is_to_be_touched() {
2979 let mut opts = options();
2980 opts.emit = EmitKind::MirFinal;
2981 let source = "void a(int n) { int v[n]; v[0] = 1; }\n";
2982 let plain = run(&opts, source);
2983 assert!(!plain.failed(), "{:?}", plain.messages);
2984 assert!(!plain.text().contains("cmp_set_a_64"), "{}", plain.text());
2985
2986 opts.stack_clash = true;
2987 let result = run(&opts, source);
2988 assert!(!result.failed(), "{:?}", result.messages);
2989 assert!(result.text().contains("cmp_set_a_64"), "{}", result.text());
2990 assert!(result.text().contains("or_mi_8"), "{}", result.text());
2991 }
2992
2993 #[test]
3003 fn a_function_that_keeps_a_frame_pointer_on_windows_reaches_an_object_file() {
3004 let mut opts = options();
3005 opts.emit = EmitKind::Object;
3006 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3007 let source = concat!(
3008 "void use(void *p);\n",
3009 "void array(int n) { int v[n]; v[0] = 1; use(v); }\n",
3010 "void taken(unsigned long n) { use(__builtin_alloca(n)); }\n",
3011 );
3012 let result = run(&opts, source);
3013 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3014 let bytes = match result.artifact {
3015 Artifact::Object { bytes, .. } => bytes,
3016 other => panic!("expected an object, got {other:?}"),
3017 };
3018 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3019
3020 let mut opts = options();
3023 opts.emit = EmitKind::Object;
3024 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3025 }
3026
3027 #[test]
3038 fn the_address_of_a_function_this_file_only_declares_reaches_a_windows_object() {
3039 let source = concat!(
3040 "void other(void *p);\n",
3041 "void takes(void (*f)(void *));\n",
3042 "void (*held)(void *);\n",
3043 "void pass(void) { takes(other); }\n",
3044 "void keep(void) { held = other; }\n",
3045 "void call(void) { other(0); }\n",
3046 );
3047 let mut opts = options();
3048 opts.emit = EmitKind::Object;
3049 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3050 let result = run(&opts, source);
3051 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3052 let bytes = match result.artifact {
3053 Artifact::Object { bytes, .. } => bytes,
3054 other => panic!("expected an object, got {other:?}"),
3055 };
3056 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3057
3058 let mut opts = options();
3061 opts.emit = EmitKind::Object;
3062 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3063 }
3064
3065 #[test]
3079 fn an_opcode_with_no_name_in_the_rule_language_is_named_by_its_own_spelling() {
3080 let mut opts = options();
3081 opts.emit = EmitKind::MirFinal;
3082 let source =
3083 "long double f(int a) {\n __int128 wide = a;\n return (long double) wide;\n}\n";
3084 let result = run(&opts, source);
3085 assert!(result.failed());
3086 assert!(
3087 result.messages[0].contains("no rule lowers a `sext` producing a `i128`"),
3088 "{result:?}"
3089 );
3090 assert!(result.messages[0].contains(":2:"), "the line the widening is on: {result:?}");
3091 assert!(!result.messages[0].contains("this instruction"), "{result:?}");
3092 }
3093
3094 #[test]
3096 fn the_note_on_unfinished_work_points_at_the_issues_rather_than_at_the_plan() {
3097 let mut opts = options();
3098 opts.emit = EmitKind::MirFinal;
3099 let source = "long double f(int a) { __int128 wide = a; return (long double) wide; }\n";
3100 let result = run(&opts, source);
3101 assert!(result.failed());
3102 let note = result.messages.iter().find(|line| line.contains("note:")).expect("a note");
3103 assert!(note.contains("https://github.com/tamnd/rucc/issues"), "{note}");
3104 assert!(!note.contains("spec/17-milestones.md"), "{note}");
3105 }
3106
3107 #[test]
3109 fn the_frame_flags_on_the_command_line_reach_the_generated_frame() {
3110 let source = "int f(int a) { return a; }\n";
3111 assert!(!mir(source).contains("$rbp"), "a leaf needs no frame pointer by default");
3112
3113 let mut opts = options();
3114 opts.emit = EmitKind::MirFinal;
3115 opts.frame_pointer = true;
3116 let kept = run(&opts, source).text().to_owned();
3117 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
3118 }
3119
3120 fn asm(source: &str) -> String {
3122 let mut opts = options();
3123 opts.emit = EmitKind::Asm;
3124 let result = run(&opts, source);
3125 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3126 result.text().to_owned()
3127 }
3128
3129 #[test]
3136 fn a_function_goes_from_c_to_assembly_an_assembler_would_take() {
3137 let text = asm("int add(int a, int b) { return a + b; }\n");
3138 assert!(text.contains("\t.globl\tadd\n"), "{text}");
3139 assert!(text.contains("\t.type\tadd, @function\n"), "{text}");
3140 assert!(text.contains("\nadd:\n"), "{text}");
3141 assert!(text.contains("\taddl\t"), "{text}");
3142 assert!(text.contains("\tret\n"), "{text}");
3143 assert!(text.contains("\t.size\tadd, .-add\n"), "{text}");
3144 assert!(text.contains(".note.GNU-stack"), "{text}");
3147 }
3148
3149 #[test]
3155 fn a_call_through_a_function_pointer_goes_through_the_register_it_is_in() {
3156 let text = asm("int g(int);\nint f(int (*p)(int), int a) { return p(a) + g(a); }\n");
3157 assert!(text.contains("\tcall\t*%"), "{text}");
3158 assert!(text.contains("\tcall\tg\n"), "{text}");
3159 assert!(text.contains("%rdi"), "{text}");
3163 }
3164
3165 #[test]
3169 fn the_address_of_a_global_is_read_from_the_instruction_pointer() {
3170 let text = asm("extern int counter;\nint f(void) { return counter; }\n");
3171 assert!(text.contains("\tmovl\tcounter(%rip), %eax\n"), "{text}");
3172 }
3173
3174 #[test]
3183 fn a_branch_on_a_comparison_jumps_on_the_opposite_of_what_it_compared() {
3184 let arms = "return 1; return 2;";
3185 let signed = [("==", "jne"), ("!=", "je"), ("<", "jge"), ("<=", "jg"), (">", "jle")];
3186 for (operator, jump) in signed.into_iter().chain([(">=", "jl")]) {
3187 let text = asm(&format!("int f(int a, int b) {{ if (a {operator} b) {arms} }}\n"));
3188 assert!(
3189 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
3190 "{operator}: {text}"
3191 );
3192 assert!(!text.contains("\tset"), "{operator}: {text}");
3193 assert!(!text.contains("\ttest"), "{operator}: {text}");
3194 }
3195 let unsigned = [("<", "jae"), ("<=", "ja"), (">", "jbe"), (">=", "jb")];
3196 for (operator, jump) in unsigned {
3197 let source =
3198 format!("int f(unsigned a, unsigned b) {{ if (a {operator} b) {arms} }}\n");
3199 let text = asm(&source);
3200 assert!(
3201 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
3202 "{operator}: {text}"
3203 );
3204 }
3205
3206 let text = asm("int f(int a) { if (a < 7) return 1; return 2; }\n");
3209 assert!(text.contains("\tcmpl\t$7, %edi\n\tjge\t"), "{text}");
3210 }
3211
3212 #[test]
3218 fn a_comparison_whose_answer_the_program_wanted_still_writes_a_byte() {
3219 let text = asm("int f(int a, int b) { return a < b; }\n");
3220 assert!(text.contains("\tsetl\t"), "{text}");
3221 }
3222
3223 fn optimized(source: &str) -> String {
3225 let mut opts = options();
3226 opts.emit = EmitKind::Asm;
3227 opts.opt_level = rucc_session::OptLevel::O2;
3228 let result = run(&opts, source);
3229 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3230 result.text().to_owned()
3231 }
3232
3233 #[test]
3243 fn a_switch_whose_arms_are_a_function_of_the_label_is_a_range_check_and_arithmetic() {
3244 let arms: String =
3245 (0..16).map(|k| format!("case {k}: return {};", k + 1)).collect::<Vec<_>>().join(" ");
3246 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3247 assert!(text.contains("\tcmpl\t$15, %edi\n\tja\t"), "{text}");
3248 assert!(text.contains("\taddl\t$1, %edi"), "{text}");
3249 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
3250 }
3251
3252 #[test]
3259 fn a_dense_switch_whose_arms_are_not_a_line_keeps_its_comparisons() {
3260 let arms: String = (0..16)
3261 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
3262 .collect::<Vec<_>>()
3263 .join(" ");
3264 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3265 assert!(text.matches("\tcmp").count() > 1, "{text}");
3266 }
3267
3268 #[test]
3276 fn a_conversion_from_a_constant_double_is_the_number_it_converts_to() {
3277 let text = optimized("int f(void) { double d = 2.75; return (int) d; }\n");
3278 assert!(text.contains("movl\t$2, %eax"), "{text}");
3279 assert!(!text.contains("cvttsd2si"), "{text}");
3280 }
3281
3282 #[test]
3290 fn a_slot_of_a_read_only_table_is_the_value_the_table_holds() {
3291 let text =
3292 optimized("static const int t[4] = {10, 20, 30, 40};\nint f(void) { return t[2]; }\n");
3293 assert!(text.contains("movl\t$30, %eax"), "{text}");
3294 assert!(!text.contains("t(%rip)"), "{text}");
3295 }
3296
3297 #[test]
3300 fn a_byte_of_a_read_only_string_is_the_byte_the_string_spells() {
3301 let text = optimized("static const char s[] = \"abc\";\nint f(void) { return s[1]; }\n");
3302 assert!(text.contains("movl\t$98, %eax"), "{text}");
3303 }
3304
3305 #[test]
3309 fn a_table_that_is_not_read_only_keeps_its_load() {
3310 let text = optimized(
3311 "static int t[4] = {10, 20, 30, 40};\nvoid g(int x) { t[2] = x; }\nint f(void) { return t[2]; }\n",
3312 );
3313 assert!(!text.contains("movl\t$30, %eax"), "{text}");
3314 }
3315
3316 #[test]
3323 fn a_call_guarded_by_a_condition_a_read_only_object_settles_is_not_emitted() {
3324 let text = optimized(
3325 "void link_error(void);\nconst double one = 1.0;\nint main(void) { if ((int) one != 1) link_error(); return 0; }\n",
3326 );
3327 assert!(!text.contains("call\tlink_error"), "{text}");
3328 }
3329
3330 #[test]
3332 fn a_cast_between_a_pointer_and_an_integer_leaves_the_value_where_it_is() {
3333 let text = asm("long f(void *p) { return (long)p; }\n");
3334 for line in text.lines().filter(|line| line.starts_with('\t') && !line.contains('.')) {
3339 let mnemonic = line.split_whitespace().next().unwrap_or("");
3340 assert!(matches!(mnemonic, "movq" | "ret"), "{line} in\n{text}");
3341 }
3342 }
3343
3344 #[test]
3348 fn an_argument_past_the_last_register_is_read_out_of_the_caller_s_stack() {
3349 let six = "long a, long b, long c, long d, long e, long f";
3350 let text = asm(&format!("long f({six}, long g, long h) {{ return g + h; }}\n"));
3351
3352 assert!(text.contains("\tmovq\t8(%rsp), "), "{text}");
3359 assert!(text.contains("\taddq\t16(%rsp), "), "{text}");
3360
3361 let narrow = asm(&format!("int f({six}, int g) {{ return g; }}\n"));
3365 assert!(narrow.contains("\tmovl\t8(%rsp), "), "{narrow}");
3366 let eight =
3367 "double a, double b, double c, double d, double e, double f, double g, double h";
3368 let float = asm(&format!("double f({eight}, double i) {{ return i; }}\n"));
3369 assert!(float.contains("\tmovsd\t8(%rsp), "), "{float}");
3370 }
3371
3372 #[test]
3375 fn a_call_writes_the_arguments_with_no_register_left_at_the_stack_pointer() {
3376 let six = "1, 2, 3, 4, 5, 6";
3377 let decl = "long g(long, long, long, long, long, long, long, long);\n";
3378 let text = asm(&format!("{decl}long f(void) {{ return g({six}, 7, 8); }}\n"));
3379
3380 assert!(text.contains("\tmovq\t%"), "{text}");
3381 assert!(text.contains(", (%rsp)\n"), "{text}");
3382 assert!(text.contains(", 8(%rsp)\n"), "{text}");
3383 assert!(text.contains("\tsubq\t$"), "{text}");
3385
3386 let narrow = "int g(int, int, int, int, int, int, int);\n";
3388 let text = asm(&format!("{narrow}int f(void) {{ return g({six}, 7); }}\n"));
3389 assert!(text.contains("\tmovl\t%"), "{text}");
3390 assert!(text.contains(", (%rsp)\n"), "{text}");
3391 }
3392
3393 #[test]
3396 fn a_variadic_call_counts_registers_and_not_arguments() {
3397 let nine = "1., 2., 3., 4., 5., 6., 7., 8., 9.";
3398 let decl = "int g(int, ...);\n";
3399 let text = asm(&format!("{decl}int f(void) {{ return g(0, {nine}); }}\n"));
3400
3401 assert!(text.contains("\tmovl\t$8, "), "eight registers, not nine: {text}");
3402 assert!(text.contains("\tmovsd\t%"), "{text}");
3403 assert!(text.contains(", (%rsp)\n"), "{text}");
3404 }
3405
3406 #[test]
3411 fn a_variadic_function_writes_the_argument_registers_it_was_handed_into_its_frame() {
3412 let body =
3413 "__builtin_va_list ap; __builtin_va_start(ap, n); __builtin_va_end(ap); return n;";
3414 let text = asm(&format!("int f(int n, ...) {{ {body} }}\n"));
3415
3416 let stores = |mnemonic: &str| text.matches(&format!("\t{mnemonic}\t%")).count();
3419 assert!(text.contains(", 8(%r"), "the second slot, not the first: {text}");
3420 assert!(!text.contains(", 0(%r"), "{text}");
3421 assert_eq!(stores("movaps"), 8, "every vector register: {text}");
3424 assert_eq!(stores("movsd"), 0, "and the whole of each one: {text}");
3425
3426 assert!(text.contains("\tsubq\t$"), "{text}");
3428 }
3429
3430 #[test]
3433 fn va_start_writes_the_four_fields_the_psabi_describes() {
3434 let start = "__builtin_va_list ap; __builtin_va_start(ap, d);";
3435 let params = "int a, int b, int c, double d";
3436 let text = asm(&format!("int f({params}, ...) {{ {start} return a; }}\n"));
3437
3438 assert!(text.contains(" movl $24, "), "{text}");
3442 assert!(text.contains(" movl $64, "), "{text}");
3443 assert!(text.contains(", 8(%r"), "{text}");
3447 assert!(text.contains(", 16(%r"), "{text}");
3448 let frame: u32 = text
3449 .lines()
3450 .find_map(|line| line.trim().strip_prefix("subq $")?.split(',').next()?.parse().ok())
3451 .expect("a variadic function takes a frame for the save area");
3452 let above = |line: &str| {
3453 let at: u32 = line.trim().strip_prefix("leaq ")?.split('(').next()?.parse().ok()?;
3454 Some(at > frame)
3455 };
3456 assert!(text.lines().filter_map(above).any(|it| it), "{frame}: {text}");
3457 }
3458
3459 #[test]
3462 fn va_arg_branches_on_whether_the_argument_is_still_in_the_save_area() {
3463 let read = "__builtin_va_list ap; __builtin_va_start(ap, n);";
3464 let ints = format!("int f(int n, ...) {{ {read} return __builtin_va_arg(ap, int); }}\n");
3465 let text = asm(&ints);
3466
3467 assert!(text.contains("$40, "), "{text}");
3470 assert!(text.contains(" cmpl "), "{text}");
3471 assert!(text.contains(" ja "), "{text}");
3475
3476 let arg = "__builtin_va_arg(ap, double)";
3477 let text = asm(&format!("double f(int n, ...) {{ {read} return {arg}; }}\n"));
3478 assert!(text.contains("$160, "), "the last vector slot: {text}");
3479 }
3480
3481 #[test]
3484 fn a_structure_assignment_is_a_move_for_each_word_of_it() {
3485 let decl = "struct pair { long a, b; };\n";
3486 let body = "struct pair p = *q; return p.a + p.b;";
3487 let text = asm(&format!("{decl}long f(struct pair *q) {{ {body} }}\n"));
3488
3489 assert!(!text.contains("memcpy"), "nothing calls the library: {text}");
3490 assert!(!text.contains("\tcall"), "{text}");
3491 assert!(text.matches("\tmovq\t").count() >= 4, "two words each way: {text}");
3493 }
3494
3495 #[test]
3498 fn how_wide_a_word_of_a_copy_is_follows_the_alignment() {
3499 let decl = "struct bytes { char a[8]; };\n";
3500 let body = "struct bytes p = *q; return p.a[0];";
3501 let text = asm(&format!("{decl}int f(struct bytes *q) {{ {body} }}\n"));
3502
3503 assert!(text.matches("\tmovb\t").count() >= 16, "a byte at a time: {text}");
3505 }
3506
3507 #[test]
3510 fn the_part_of_an_initialiser_that_names_nothing_is_stored_as_zero() {
3511 let decl = "struct wide { long a, b, c; };\n";
3512 let text = asm(&format!("{decl}long f(void) {{ struct wide w = {{ 7 }}; return w.c; }}\n"));
3513
3514 assert!(!text.contains("memset"), "nothing calls the library: {text}");
3515 assert!(text.contains("\tmovq\t$0, ") || text.contains("\txorl\t"), "the zero: {text}");
3520 }
3521
3522 #[test]
3525 fn a_copy_too_large_to_unroll_calls_the_runtime() {
3526 let decl = "struct huge { char a[4096]; };\n";
3527 let mut opts = options();
3528 opts.emit = EmitKind::Asm;
3529 let source = format!("{decl}void f(struct huge *p, struct huge *q) {{ *p = *q; }}\n");
3530 let result = run(&opts, &source);
3531 assert!(!result.failed(), "{:?}", result.messages);
3532 let text = result.text();
3533 assert!(text.contains("call") && text.contains("memcpy"), "{text}");
3534 assert!(text.contains("4096"), "the size travels: {text}");
3537 }
3538
3539 #[test]
3546 fn a_structure_too_large_to_unroll_is_copied_into_the_argument_area_by_the_runtime() {
3547 let decl = "struct huge { char a[4096]; };\nint take(struct huge);\n";
3548 let text = asm(&format!("{decl}int f(struct huge *p) {{ return take(*p); }}\n"));
3549
3550 let copy = text.find("call\tmemcpy").expect("the copy");
3551 let call = text.find("call\ttake").expect("the call");
3552 assert!(copy < call, "the copy comes first: {text}");
3553 assert!(text.contains("movq\t%rsp, %rdi"), "the destination: {text}");
3558 assert!(text.contains("$4096, %edx"), "the size: {text}");
3559 }
3560
3561 #[test]
3564 fn a_realigned_frame_reads_them_through_the_frame_pointer() {
3565 let six = "long a, long b, long c, long d, long e, long f";
3566 let body = "_Alignas(32) long wide[4]; wide[0] = g; return wide[0];";
3567 let text = asm(&format!("long f({six}, long g) {{ {body} }}\n"));
3568
3569 assert!(text.contains("\tandq\t$-32, %rsp"), "{text}");
3573 assert!(text.contains("\tmovq\t16(%rbp), "), "{text}");
3574 assert!(!text.contains("\tmovq\t16(%rsp), "), "{text}");
3575 }
3576
3577 #[test]
3579 fn the_target_decides_how_the_assembly_is_spelled() {
3580 let mut opts = options();
3581 opts.emit = EmitKind::Asm;
3582 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
3583 let text = run(&opts, "int f(void) { return 0; }\n").text().to_owned();
3584 assert!(text.contains("__TEXT,__text"), "{text}");
3585 assert!(text.contains("\n_f:\n"), "{text}");
3586 assert!(!text.contains(".note.GNU-stack"), "{text}");
3587 }
3588
3589 fn obj(source: &str) -> Vec<u8> {
3591 let mut opts = options();
3592 opts.emit = EmitKind::Object;
3593 let result = run(&opts, source);
3594 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3595 match result.artifact {
3596 Artifact::Object { bytes, .. } => bytes,
3597 other => panic!("expected an object, got {other:?}"),
3598 }
3599 }
3600
3601 #[test]
3607 fn a_function_goes_from_c_to_an_object_a_linker_would_take() {
3608 let bytes = obj("int add(int a, int b) { return a + b; }\n");
3609 assert_eq!(&bytes[..4], b"\x7fELF", "an object file starts by saying it is one");
3610 let text = asm("int add(int a, int b) { return a + b; }\n");
3611 assert!(
3612 text.contains("\taddl\t"),
3613 "and the listing of it is the same instructions:\n{text}"
3614 );
3615 }
3616
3617 #[test]
3619 fn a_variable_goes_from_c_to_the_section_it_belongs_in() {
3620 let text = asm("int counter = 42;\nstatic int hidden;\nconst int fixed = 7;\n");
3621 assert!(text.contains("\t.data\n\t.globl\tcounter\n"), "{text}");
3622 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
3623 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
3624 assert!(text.contains("\t.bss\n\t.p2align\t2\n"), "{text}");
3627 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
3628 assert!(!text.contains(".globl\thidden"), "{text}");
3629 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
3632 }
3633
3634 #[test]
3641 fn a_bit_field_initializer_writes_every_byte_of_the_value_and_not_only_the_ones_that_are_set() {
3642 let text = asm("struct s { unsigned f : 20; } x = { 0x12300 };\n");
3643 assert!(text.contains("\t.data\n"), "there is something to write: {text}");
3644 assert!(text.contains("\nx:\n\t.ascii\t\"\\000#\\001\"\n"), "and it is the value: {text}");
3645
3646 let text = asm("struct s { unsigned a : 8; unsigned b : 8; } x = { 0, 3 };\n");
3649 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\003\"\n"), "{text}");
3650
3651 let text = asm("struct s { unsigned long long f : 40; } x = { 0x100000 };\n");
3654 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\000\\020\"\n\t.space\t5\n"), "{text}");
3655
3656 let text = asm("struct s { unsigned f : 20; } x = { 0 };\n");
3658 assert!(text.contains("\t.bss\n"), "an object of zeroes is zeroes: {text}");
3659 assert!(text.contains("\nx:\n\t.space\t4\n"), "{text}");
3660 }
3661
3662 #[test]
3664 fn a_string_literal_is_a_variable_with_a_name_no_program_could_write() {
3665 let text = asm("const char *f(void) { return \"hi\"; }\n");
3666 assert!(text.contains("\t.ascii\t\"hi\\000\"\n"), "{text}");
3667 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
3668 let label = text
3669 .lines()
3670 .find(|line| line.starts_with(".Lstr"))
3671 .unwrap_or_else(|| panic!("a label for the literal in\n{text}"));
3672 assert!(!text.contains(&format!(".globl\t{}", label.trim_end_matches(':'))), "{text}");
3673 }
3674
3675 #[test]
3677 fn an_address_in_an_initializer_is_left_to_the_linker() {
3678 let source = "int counter;\nint *p = &counter;\n";
3679 let text = asm(source);
3680 assert!(text.contains("\np:\n\t.quad\tcounter\n"), "{text}");
3681 let bytes = obj(source);
3684 assert!(bytes.windows(8).any(|w| w == b"counter\0"), "the object has to name it");
3685 }
3686
3687 #[test]
3696 fn a_constant_holding_an_address_goes_in_the_section_the_loader_may_write_once() {
3697 let text = asm("static void a(void) {}\nstatic void b(void) {}\n\
3700 struct m { void (*x)(void); void (*y)(void); };\n\
3701 const struct m t = { a, b };\n");
3702 assert!(text.contains("\t.section\t.data.rel.ro.local,\"aw\",@progbits\n"), "{text}");
3703 assert!(text.contains("\nt:\n\t.quad\ta\n\t.quad\tb\n"), "{text}");
3704
3705 let text =
3708 asm("void a(void);\nstruct m { void (*x)(void); };\nconst struct m t = { a };\n");
3709 assert!(text.contains("\t.section\t.data.rel.ro,\"aw\",@progbits\n"), "{text}");
3710
3711 let text = asm("const int fixed = 7;\n");
3713 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
3714 }
3715
3716 #[test]
3723 fn a_thread_local_variable_is_storage_a_thread_gets_a_copy_of_and_an_offset_into_it() {
3724 let text = asm("_Thread_local int x = 1;\nint read(void) { return x; }\n");
3725 assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
3728 assert!(text.contains("\t.type\tx, @tls_object\n"), "{text}");
3729 assert!(text.contains("x@GOTTPOFF(%rip)"), "{text}");
3732 assert!(text.contains("%fs:0"), "{text}");
3733 }
3734
3735 #[test]
3741 fn the_address_of_this_thread_s_own_storage_is_read_out_of_the_segment_register() {
3742 let text = asm("void *here(void) { return __builtin_thread_pointer(); }\n");
3743 assert!(text.contains("movq\t%fs:0, "), "{text}");
3744 assert!(!text.contains("GOTTPOFF"), "{text}");
3746 }
3747
3748 #[test]
3759 fn a_prefetch_is_one_of_four_instructions_and_the_locality_is_what_picks() {
3760 for (locality, wanted) in
3761 [(0, "prefetchnta"), (1, "prefetcht2"), (2, "prefetcht1"), (3, "prefetcht0")]
3762 {
3763 let source =
3764 format!("void warm(void *p) {{ __builtin_prefetch(p, 0, {locality}); }}\n");
3765 let text = asm(&source);
3766 assert!(text.contains(&format!("\t{wanted}\t")), "locality {locality}: {text}");
3767 }
3768 let text = asm("void warm(void *p) { __builtin_prefetch(p); }\n");
3770 assert!(text.contains("\tprefetcht0\t"), "{text}");
3771 let text = asm("void warm(void *p) { __builtin_prefetch(p, 1); }\n");
3774 assert!(text.contains("\tprefetcht0\t"), "{text}");
3775 assert!(!text.contains("prefetchw"), "{text}");
3776 }
3777
3778 #[test]
3789 fn a_trap_is_the_instruction_the_machine_has_no_meaning_for() {
3790 let text = asm("void stop(void) { __builtin_trap(); }\n");
3791 assert!(text.contains("\tud2\n"), "{text}");
3792 assert!(!text.contains("\tcall"), "a stop is not a call to anything: {text}");
3793
3794 let text = asm("int stop(int a) { __builtin_trap(); return a + 1; }\n");
3795 assert!(text.contains("\tud2\n"), "{text}");
3796 assert!(text.contains("\taddl\t"), "the block goes on after a stop: {text}");
3797 }
3798
3799 #[test]
3811 fn assume_aligned_is_its_first_argument_and_keeps_the_rest() {
3812 let text = asm("void *aligned(char *p) { return __builtin_assume_aligned(p, 16); }\n");
3813 assert!(!text.contains("assume_aligned"), "{text}");
3814 assert!(!text.contains("\tcall"), "nothing is called for an alignment fact: {text}");
3815
3816 let source = "unsigned long width(void);\n\
3817 void *aligned(char *p) { return __builtin_assume_aligned(p, width()); }\n";
3818 let text = asm(source);
3819 assert!(!text.contains("assume_aligned"), "{text}");
3820 assert!(text.contains("width"), "the argument that is not the answer still runs: {text}");
3821 }
3822
3823 #[test]
3833 fn the_frame_address_is_the_frame_pointer_after_walking_that_many_links() {
3834 let text = asm("void *here(void) { return __builtin_frame_address(0); }\n");
3835 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
3836 assert!(text.contains("movq\t%rbp, %rax"), "{text}");
3837 assert!(!text.contains("\tcall"), "a frame address is not a call to anything: {text}");
3838
3839 let walk = |depth: u32| {
3840 let source = format!("void *up(void) {{ return __builtin_frame_address({depth}); }}\n");
3841 asm(&source).matches("movq\t(%r").count()
3842 };
3843 assert_eq!(walk(1), 1, "one link is one load");
3844 assert_eq!(walk(3), 3, "three links are three loads");
3845 }
3846
3847 #[test]
3857 fn the_return_address_is_one_word_above_the_frame_the_walk_ended_at() {
3858 let text = asm("void *back(void) { return __builtin_return_address(0); }\n");
3859 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
3860 assert!(text.contains("movq\t8(%rbp), %rax"), "{text}");
3861 assert!(!text.contains("\tcall"), "a return address is not a call to anything: {text}");
3862
3863 let text = asm("void *back(void) { return __builtin_return_address(2); }\n");
3864 assert_eq!(text.matches("movq\t(%r").count(), 2, "two links are two loads: {text}");
3865 assert!(text.contains("movq\t8(%r"), "and the answer is above the last of them: {text}");
3866 }
3867
3868 #[test]
3879 fn a_depth_that_is_not_a_small_constant_is_refused() {
3880 let mut opts = options();
3881 opts.emit = EmitKind::Ir;
3882 for source in [
3883 "void *up(int n) { return __builtin_return_address(n); }\n",
3884 "void *up(void) { return __builtin_frame_address(1000); }\n",
3885 ] {
3886 let messages = run(&opts, source).messages;
3887 let named = messages.iter().any(|m| m.contains("E0705"));
3888 assert!(named, "expected a refusal in {messages:?}");
3889 }
3890 }
3891
3892 #[test]
3904 fn an_alloca_takes_the_bytes_off_the_stack_pointer_and_answers_where_they_are() {
3905 let text =
3906 asm("void use(void *p); void f(unsigned long n) { use(__builtin_alloca(n)); }\n");
3907 assert!(text.contains("andq\t$-16"), "the size is rounded up to sixteen: {text}");
3908 assert!(text.contains("subq\t%rdi, %rsp"), "and taken off the stack pointer: {text}");
3909 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
3910
3911 let plain = concat!(
3914 "extern void *alloca(__SIZE_TYPE__);\n",
3915 "void use(void *p);\n",
3916 "void f(unsigned long n) { use(alloca(n)); }\n",
3917 );
3918 let text = asm(plain);
3919 assert!(text.contains("subq\t%rdi, %rsp"), "the plain name is the same bytes: {text}");
3920 assert_eq!(text.matches("\tcall").count(), 1, "and is not a call either: {text}");
3921
3922 let own = concat!(
3925 "static void *alloca(unsigned long n) { return 0; }\n",
3926 "void *f(unsigned long n) { return alloca(n); }\n",
3927 );
3928 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
3929 }
3930
3931 #[test]
3946 fn a_builtin_the_program_never_declared_is_the_builtin_rather_than_the_one_c89_wrote_down() {
3947 let mut opts = options();
3951 opts.permissive = true;
3952 let undeclared = "void use(void *p);
3953void f(unsigned long n) { use(alloca(n)); }
3954";
3955 assert_eq!(
3956 run(&opts, undeclared).messages,
3957 [
3958 "/main.c:2:31: warning: implicit declaration of function 'alloca' [E0521]",
3959 "/main.c:2:31: warning: incompatible implicit declaration of built-in function \
3960 'alloca' [E0713]",
3961 ]
3962 );
3963
3964 opts.emit = EmitKind::Asm;
3965 let text = run(&opts, undeclared).text().to_owned();
3966 assert!(text.contains("subq\t%rdi, %rsp"), "the bytes come off the stack: {text}");
3967 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
3968
3969 let string = "unsigned long f(void) { return strlen(\"abc\"); }\n";
3972 let text = run(&opts, string).text().to_owned();
3973 assert!(text.contains("call\tstrlen"), "strlen is still a call: {text}");
3974
3975 let own = concat!(
3978 "static void *alloca(unsigned long n) { return 0; }\n",
3979 "void *f(unsigned long n) { return alloca(n); }\n",
3980 );
3981 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
3982 }
3983
3984 #[test]
3994 fn the_bytes_an_alloca_took_are_still_there_at_the_end_of_the_block_that_took_them() {
3995 let inner = "{ use(__builtin_alloca(n)); }";
3996 for body in [inner.to_owned(), format!("int a[n]; {inner} use(a);")] {
3997 let source = format!("void use(void *p);\nvoid f(unsigned long n) {{ {body} }}\n");
3998 let text = asm(&source);
3999 for line in text.lines().filter(|line| line.trim_end().ends_with(", %rsp")) {
4003 let taking = line.contains("subq");
4004 let leaving = line.contains("%rbp");
4005 assert!(taking || leaving, "nothing puts the stack back: {line} in {text}");
4006 }
4007 }
4008 }
4009
4010 #[test]
4012 fn the_object_and_the_listing_are_two_spellings_of_one_compilation() {
4013 let source = "int callee(void); int g(void) { return callee(); }\n";
4017 let bytes = obj(source);
4018 assert!(
4019 bytes.windows(7).any(|w| w == b"callee\0"),
4020 "the object has to name the callee for the linker to find it"
4021 );
4022 let text = asm(source);
4023 assert!(text.contains("\tcall\tcallee\n"), "{text}");
4024 }
4025
4026 #[test]
4032 fn compiling_for_an_executable_produces_an_object_and_not_a_dump() {
4033 let mut opts = options();
4034 opts.emit = EmitKind::Executable;
4036 let result = run(&opts, "int main(void) { return 0; }\n");
4037 assert_eq!(result.messages, Vec::<String>::new());
4038 match result.artifact {
4039 Artifact::Object { bytes, .. } => assert_eq!(&bytes[..4], b"\x7fELF"),
4040 other => panic!("expected an object, got {other:?}"),
4041 }
4042 }
4043
4044 #[test]
4046 fn a_platform_with_no_object_writer_is_said_so_rather_than_written_as_elf() {
4047 let mut opts = options();
4048 opts.emit = EmitKind::Object;
4049 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
4050 let result = run(&opts, "int f(void) { return 0; }\n");
4051 assert!(result.failed(), "an object nobody can read is worse than a message");
4052 assert!(
4053 result.messages.iter().any(|m| m.contains("no object writer")),
4054 "{:?}",
4055 result.messages
4056 );
4057 }
4058
4059 fn ir(source: &str) -> String {
4061 let mut opts = options();
4062 opts.emit = EmitKind::Ir;
4063 let result = run(&opts, source);
4064 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4065 result.text().to_owned()
4066 }
4067
4068 fn errors(source: &str) -> Vec<String> {
4070 let mut opts = options();
4071 opts.emit = EmitKind::Ir;
4072 let result = run(&opts, source);
4073 assert!(result.failed(), "expected this to be refused:\n{source}");
4074 result.messages
4075 }
4076
4077 fn body(source: &str) -> String {
4079 let text = ir(source);
4080 let (_, rest) = text.split_once("{\n").expect("a function definition");
4081 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
4082 body.to_owned()
4083 }
4084
4085 #[test]
4093 fn gnu89_inline_is_what_decides_whether_a_bare_inline_definition_reaches_the_module() {
4094 let source = "inline int f(int x) { return x + 1; }\n";
4095 let with = |flag: bool| {
4096 let mut opts = options();
4097 opts.emit = EmitKind::Ir;
4098 opts.gnu89_inline = flag;
4099 let result = run(&opts, source);
4100 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
4101 result.text().to_owned()
4102 };
4103
4104 assert!(!with(false).contains("block0"), "no body: {}", with(false));
4107
4108 assert!(with(true).contains("block0"), "a body: {}", with(true));
4111 }
4112
4113 #[test]
4120 fn an_access_through_a_type_names_the_type_it_went_through() {
4121 let source = "\
4122struct s { int a; float b; };\n\
4123union u { int i; float f; };\n\
4124int scalar(int *p) { return *p; }\n\
4125float member(struct s *p) { p->a = 1; return p->b; }\n\
4126int element(int *a, long i) { return a[i]; }\n\
4127float through_a_union(union u *p) { p->i = 1; return p->f; }\n";
4128 let text = ir(source);
4129 assert!(text.contains(r#"!0 = tbaa "char""#), "the root: {text}");
4130 assert!(text.contains(r#"tbaa "int", parent !0"#), "int under it: {text}");
4131 assert!(text.contains(r#"tbaa "float", parent !0"#), "float under it: {text}");
4132 let named = text.lines().filter(|line| line.contains(", tbaa !")).count();
4135 assert_eq!(named, 6, "six accesses: {text}");
4136 }
4137
4138 #[test]
4145 fn turning_strict_aliasing_off_leaves_the_type_off_every_access() {
4146 let source = "int punned(float *f, int *i) { *i = 1; *f = 2.0f; return *i; }\n";
4147 let mut opts = options();
4148 opts.emit = EmitKind::Ir;
4149 opts.strict_aliasing = false;
4150 let result = run(&opts, source);
4151 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
4152 let text = result.text().to_owned();
4153 assert!(!text.contains("tbaa"), "not even the root: {text}");
4154 }
4155
4156 #[test]
4164 fn a_bare_return_from_a_function_that_promised_a_value_gives_back_a_zero() {
4165 let mut opts = options();
4166 opts.emit = EmitKind::Ir;
4167 opts.std = Std::C89;
4168 let compiled = |source: &str| {
4169 let result = run(&opts, source);
4170 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
4171 result.text().to_owned()
4172 };
4173
4174 let text = compiled("int f(int x) { if (x) return; return 3; }\n");
4175 assert!(text.contains("iconst.i32 0\n return"), "zero goes back: {text}");
4176 assert!(!text.contains("unreachable"), "the branch that reached it is kept: {text}");
4177
4178 let text = compiled("double f(int x) { if (x) return; return 1.0; }\n");
4180 assert!(text.contains("fconst.f64 0x0\n return"), "a float zero goes back: {text}");
4181 }
4182
4183 #[test]
4191 fn a_call_to_a_name_nothing_declared_declares_it_as_c89_said_to() {
4192 let mut opts = options();
4193 opts.emit = EmitKind::Ir;
4194 opts.std = Std::C89;
4195 let compiled = |source: &str| {
4196 let result = run(&opts, source);
4197 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
4198 result.text().to_owned()
4199 };
4200
4201 let text = compiled("int f(void) { return g(); }\n");
4203 assert!(text.contains("call @g"), "the call is to the name that was written: {text}");
4204 assert!(text.contains("i32"), "and it gives back an int: {text}");
4205
4206 let text = compiled("int f(char c) { return g(c); }\n");
4209 assert!(text.contains("sext.i32"), "the argument is promoted: {text}");
4210
4211 let mut opts = options();
4214 opts.std = Std::C89;
4215 let said = run(&opts, "int f(void) { return h; }\n").messages.join("\n");
4216 assert!(said.contains("'h' undeclared"), "not a call, so not declared: {said}");
4217 }
4218
4219 #[test]
4229 fn a_name_called_before_it_is_defined_still_gets_its_definition() {
4230 let mut opts = options();
4231 opts.emit = EmitKind::Ir;
4232 opts.std = Std::C89;
4233 let text = run(&opts, "int f(void) { return dummy(); }\ndummy () { return 7; }\n")
4234 .text()
4235 .to_owned();
4236 assert!(text.contains("func @f()"), "the caller is there: {text}");
4237 assert!(text.contains("func @dummy"), "and so is what it calls: {text}");
4238 assert!(text.contains("iconst.i32 7"), "with the body it was given: {text}");
4239 }
4240
4241 #[test]
4249 fn an_old_style_parameter_is_converted_from_what_the_call_promoted_it_to() {
4250 let mut opts = options();
4251 opts.emit = EmitKind::Ir;
4252 opts.std = Std::C89;
4253 let compiled = |source: &str| run(&opts, source).text().to_owned();
4254
4255 let text = compiled("f (c) unsigned char c; { return c; }\n");
4256 assert!(text.contains("func @f(i32"), "an int arrives: {text}");
4257 assert!(text.contains("trunc.i8"), "and is cut down to what was declared: {text}");
4258 assert!(text.contains("zext.i32"), "then read back unsigned: {text}");
4259
4260 let text = compiled("f (s) short s; { return s; }\n");
4262 assert!(text.contains("trunc.i16"), "cut down: {text}");
4263 assert!(text.contains("sext.i32"), "and read back signed: {text}");
4264
4265 let text = compiled("f (x) float x; { return x * 2; }\n");
4268 assert!(text.contains("func @f(f64"), "a double arrives: {text}");
4269 assert!(text.contains("fptrunc.f32"), "and is narrowed to the float: {text}");
4270
4271 let text = compiled("int f(unsigned char c) { return c; }\n");
4274 assert!(text.contains("func @f(i8)"), "the declared type arrives: {text}");
4275 assert!(!text.contains("trunc"), "so there is nothing to cut down: {text}");
4276 }
4277
4278 #[test]
4287 fn the_rules_gcc_promoted_are_decided_by_the_dialect_and_by_fpermissive() {
4288 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
4290 let cases = [
4291 ("static counted;\n", ["", "error", "warning", "error"]),
4292 ("int f(void) { return g(); }\n", ["", "error", "warning", "error"]),
4293 ("int f(x) { return x; }\n", ["", "error", "warning", "error"]),
4294 ("int *p;\nvoid h(void) { p = 1; }\n", ["warning", "error", "warning", "error"]),
4295 (
4296 "char *q;\nint *r;\nvoid k(void) { r = q; }\n",
4297 ["warning", "error", "warning", "error"],
4298 ),
4299 ("int f(void) { return; }\n", ["", "error", "warning", "error"]),
4300 ("void g(void) { return 1; }\n", ["warning", "error", "warning", "error"]),
4301 ];
4302
4303 for (source, wanted) in cases {
4304 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
4305 let mut opts = options();
4306 opts.std = std;
4307 opts.permissive = permissive;
4308 let said = run(&opts, source).messages.join("\n");
4309 let severity = if said.contains(": error: ") {
4310 "error"
4311 } else if said.contains(": warning: ") {
4312 "warning"
4313 } else {
4314 ""
4315 };
4316 let how = if permissive { " -fpermissive" } else { "" };
4317 assert_eq!(
4318 severity,
4319 wanted,
4320 "under -std={}{how}, {source} was answered with `{said}`",
4321 std.as_str()
4322 );
4323 if wanted.is_empty() {
4324 assert!(said.is_empty(), "nothing to say, but said `{said}`");
4325 }
4326 }
4327 }
4328 }
4329
4330 #[test]
4339 fn the_three_variadic_builtins_answer_a_bad_list_the_way_a_call_answers_a_bad_argument() {
4340 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
4341 let cases = [
4342 (
4343 "int f(int n, ...) { char *p; return __builtin_va_arg(p, int); }\n",
4344 "first argument to 'va_arg' not of type 'va_list'",
4345 ["error", "error", "error", "error"],
4346 ),
4347 (
4348 "void f(int n, ...) { char *p; __builtin_va_start(p, n); }\n",
4349 "passing argument 1 of '__builtin_va_start' from incompatible pointer type",
4350 ["warning", "error", "warning", "error"],
4351 ),
4352 (
4353 "void f(int n, ...) { int x; __builtin_va_end(x); }\n",
4354 "passing argument 1 of '__builtin_va_end' makes pointer from integer without a \
4355 cast",
4356 ["warning", "error", "warning", "error"],
4357 ),
4358 (
4359 "void f(int n, ...) { __builtin_va_list a; char *p; __builtin_va_copy(a, p); }\n",
4360 "passing argument 2 of '__builtin_va_copy' from incompatible pointer type",
4361 ["warning", "error", "warning", "error"],
4362 ),
4363 ];
4364
4365 for (source, message, wanted) in cases {
4366 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
4367 let mut opts = options();
4368 opts.std = std;
4369 opts.permissive = permissive;
4370 let said = run(&opts, source).messages.join("\n");
4371 let how = if permissive { " -fpermissive" } else { "" };
4372 assert!(
4373 said.contains(&format!(": {wanted}: {message}")),
4374 "under -std={}{how}, {source} was answered with `{said}`",
4375 std.as_str()
4376 );
4377 }
4378 }
4379 }
4380
4381 fn safe_ir(tier: rucc_session::Safety, source: &str) -> String {
4383 let mut opts = options();
4384 opts.emit = EmitKind::Ir;
4385 opts.safety = tier;
4386 let result = run(&opts, source);
4387 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4388 result.text().to_owned()
4389 }
4390
4391 const READS_THROUGH_A_POINTER: &str = "int read(int *p) { return p[1]; }\n";
4392
4393 fn padded_ir(padding: Padding, source: &str) -> String {
4395 let mut opts = options();
4396 opts.emit = EmitKind::Ir;
4397 opts.safety = rucc_session::Safety::Detect;
4398 opts.padding = padding;
4399 let result = run(&opts, source);
4400 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4401 result.text().to_owned()
4402 }
4403
4404 const FILLS_A_RECORD_A_MEMBER_AT_A_TIME: &str = "struct padded { char tag; int value; };\n\
4405 void fill(struct padded *p) { p->tag = 1; p->value = 2; }\n";
4406
4407 #[test]
4408 fn a_record_filled_a_member_at_a_time_comes_out_whole_when_padding_does_not_participate() {
4409 let text = padded_ir(Padding::Ignored, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
4413 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
4414 }
4415
4416 #[test]
4417 fn a_store_says_only_what_it_wrote_when_padding_does_participate() {
4418 let text = padded_ir(Padding::Tracked, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
4421 assert!(!text.contains("owns"), "{text}");
4422 }
4423
4424 #[test]
4425 fn a_member_of_a_union_owns_nothing_after_it() {
4426 let text = padded_ir(
4430 Padding::Ignored,
4431 "union u { char tag; long wide; };\nvoid fill(union u *p) { p->tag = 1; }\n",
4432 );
4433 assert!(!text.contains("owns"), "{text}");
4434 }
4435
4436 #[test]
4437 fn an_inner_records_trailing_padding_reaches_the_outer_records() {
4438 let text = padded_ir(
4443 Padding::Ignored,
4444 "struct inner { char c; };\n\
4445 struct outer { struct inner in; int x; };\n\
4446 void fill(struct outer *p) { p->in.c = 1; p->x = 2; }\n",
4447 );
4448 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
4449 }
4450
4451 #[test]
4452 fn a_build_that_did_not_ask_for_the_monitor_is_compiled_the_way_it_always_was() {
4453 let text = ir(READS_THROUGH_A_POINTER);
4457 assert!(!text.contains("check_"), "{text}");
4458 assert!(!text.contains("cap_of"), "{text}");
4459 }
4460
4461 #[test]
4462 fn asking_for_a_tier_puts_the_checks_in_before_the_optimizer_sees_them() {
4463 let text = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4464 assert!(text.contains("cap_of"), "{text}");
4465 assert!(text.contains("check_bounds"), "{text}");
4466 assert!(text.contains("check_live"), "{text}");
4467 assert!(text.contains("check_deriv"), "{text}");
4469 assert!(text.contains("check_type"), "{text}");
4471 }
4472
4473 #[test]
4474 fn the_three_tiers_that_are_not_off_all_check_the_same_accesses_so_far() {
4475 let detect = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4479 for tier in [rucc_session::Safety::Enforce, rucc_session::Safety::Kernel] {
4480 assert_eq!(safe_ir(tier, READS_THROUGH_A_POINTER), detect, "{tier}");
4481 }
4482 }
4483
4484 fn summary(tier: rucc_session::Safety, source: &str) -> String {
4486 let mut opts = options();
4487 opts.emit = EmitKind::SafetySummary;
4488 opts.safety = tier;
4489 let result = run(&opts, source);
4490 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4491 result.text().to_owned()
4492 }
4493
4494 #[test]
4495 fn the_summary_counts_the_checks_that_went_in_and_the_ones_still_standing() {
4496 let text = summary(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4497 assert!(text.contains("\"tier\": \"detect\""), "{text}");
4498 assert!(
4500 text.contains("\"bounds\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"),
4501 "{text}"
4502 );
4503 assert!(
4504 text.contains(
4505 "\"derivation\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"
4506 ),
4507 "{text}"
4508 );
4509 }
4510
4511 #[test]
4512 fn a_build_without_the_monitor_summarises_as_a_build_with_no_checks_in_it() {
4513 let text = summary(rucc_session::Safety::Off, READS_THROUGH_A_POINTER);
4517 assert!(text.contains("\"tier\": \"off\""), "{text}");
4518 assert!(
4519 text.contains("\"bounds\": { \"emitted\": 0, \"remaining\": 0, \"discharged\": 0 }"),
4520 "{text}"
4521 );
4522 }
4523
4524 #[test]
4525 fn a_call_the_boundary_models_is_counted_apart_from_one_it_does_not() {
4526 let text = summary(
4527 rucc_session::Safety::Detect,
4528 "void *memcpy(void *, const void *, unsigned long);\n\
4529 int puts(const char *);\n\
4530 void f(char *d, char *s) { memcpy(d, s, 4); puts(d); }\n",
4531 );
4532 assert!(text.contains("\"interposed\": 1"), "{text}");
4533 assert!(text.contains("\"puts\""), "{text}");
4534 assert!(!text.contains("__rucc_wrap_memcpy\""), "{text}");
4538 }
4539
4540 #[test]
4541 fn an_address_taken_of_a_library_function_is_counted_the_way_a_call_to_one_is() {
4542 let text = summary(
4547 rucc_session::Safety::Detect,
4548 "void *memcpy(void *, const void *, unsigned long);\n\
4549 int puts(const char *);\n\
4550 void *table[2] = { (void *)memcpy, (void *)puts };\n\
4551 void *f(int i) { return table[i]; }\n",
4552 );
4553 assert!(text.contains("\"interposed\": 1"), "{text}");
4554 assert!(text.contains("\"puts\""), "{text}");
4555 assert!(!text.contains("\"memcpy\""), "{text}");
4556 }
4557
4558 #[test]
4559 fn the_two_directions_a_pointer_crosses_the_boundary_are_counted_apart() {
4560 let text = summary(
4564 rucc_session::Safety::Detect,
4565 "void *notes_open(void);\n\
4566 char *f(char *p) { char *q = notes_open(); return q ? q : p; }\n",
4567 );
4568 assert!(text.contains("\"crossings\": { \"entered\": 1, \"returned\": 1 }"), "{text}");
4569 assert!(text.contains("\"notes_open\""), "{text}");
4570 }
4571
4572 #[test]
4573 fn a_static_function_nobody_takes_the_address_of_is_not_a_crossing() {
4574 let text = summary(
4577 rucc_session::Safety::Detect,
4578 "static int len(const char *p) { return p ? 1 : 0; }\n\
4579 int f(void) { return len(\"x\"); }\n",
4580 );
4581 assert!(text.contains("\"crossings\": { \"entered\": 0, \"returned\": 0 }"), "{text}");
4582 }
4583
4584 fn granules(source: &str) -> String {
4586 let mut opts = options();
4587 opts.emit = EmitKind::TypeGranules;
4588 let result = run(&opts, source);
4589 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4590 result.text().to_owned()
4591 }
4592
4593 #[test]
4594 fn the_granule_report_names_every_record_and_both_keyings() {
4595 let text = granules(
4596 "struct hot { char *p; int a; int b; };\n\
4597 int f(struct hot *h) { return h->a; }\n",
4598 );
4599 assert!(text.contains("struct hot"), "{text}");
4600 assert!(text.contains("every type distinct"), "{text}");
4603 assert!(text.contains("every pointer one type"), "{text}");
4604 assert!(text.contains("budget"), "{text}");
4605 }
4606
4607 #[test]
4608 fn a_record_nothing_uses_is_still_measured() {
4609 let text = granules("struct unused { long a; double b; };\nint f(void) { return 0; }\n");
4612 assert!(text.contains("struct unused"), "{text}");
4613 }
4614
4615 #[test]
4616 fn the_granule_report_stops_before_anything_is_lowered() {
4617 let text = granules(
4621 "struct wide { long double d; };\n\
4622 long double f(long double x) { return x * x; }\n",
4623 );
4624 assert!(text.contains("struct wide"), "{text}");
4625 }
4626
4627 #[test]
4628 fn a_witness_reaches_the_assembler_as_a_call_to_the_runtime() {
4629 let text = safe_asm(rucc_session::Safety::Detect, "char *f(char *p) { return p; }\n");
4632 assert!(text.contains("\tcall\t__rucc_cap_witness\n"), "{text}");
4633 }
4634
4635 #[test]
4636 fn a_pointer_turned_into_an_integer_is_on_the_trust_set() {
4637 let text = summary(
4638 rucc_session::Safety::Detect,
4639 "unsigned long f(int *p) { return (unsigned long) p; }\n",
4640 );
4641 assert!(text.contains("\"exposed\": 1"), "{text}");
4642 }
4643
4644 fn safe_asm(tier: rucc_session::Safety, source: &str) -> String {
4646 let mut opts = options();
4647 opts.emit = EmitKind::Asm;
4648 opts.safety = tier;
4649 let result = run(&opts, source);
4650 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4651 result.text().to_owned()
4652 }
4653
4654 #[test]
4655 fn a_check_reaches_the_assembler_as_a_call_to_the_runtime() {
4656 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4657 assert!(text.contains("\tcall\t__rucc_check_bounds\n"), "{text}");
4658 assert!(text.contains("\tcall\t__rucc_check_live\n"), "{text}");
4659 assert!(text.contains("\tcall\t__rucc_check_deriv\n"), "{text}");
4660 assert!(text.contains("\tcall\t__rucc_check_typed_init\n"), "{text}");
4663 }
4664
4665 #[test]
4666 fn every_check_that_reached_the_assembler_has_a_row_describing_it() {
4667 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4673 let section = format!("\t.section\t{},", rucc_safety::SECTION);
4674 assert_eq!(text.matches(§ion).count(), 4, "{text}");
4675 for index in 0..4 {
4676 let name = format!("__rucc_safety_desc_{index}");
4677 assert!(text.contains(&format!("{name}:\n")), "{text}");
4680 assert!(text.contains(&format!("{name}(%rip)")), "{text}");
4681 }
4682 assert!(!text.contains("__rucc_safety_desc_4"), "{text}");
4683 }
4684
4685 #[test]
4693 fn builtin_constant_p_is_folded_where_it_is_written_rather_than_called() {
4694 let text = ir(concat!(
4695 "int g;\n",
4696 "int a = __builtin_constant_p(1);\n",
4697 "int b = __builtin_constant_p(g);\n",
4698 "int c = __builtin_constant_p(\"abc\");\n",
4699 "int d = __builtin_constant_p(&g);\n",
4700 "int e = __builtin_constant_p(1.5);\n",
4701 "int h = __builtin_choose_expr(__builtin_constant_p(3), 11, 22);\n",
4702 ));
4703 assert!(text.contains("global @a : i32 = 1,"), "{text}");
4704 assert!(text.contains("global @b : i32 = 0,"), "{text}");
4705 assert!(text.contains("global @c : i32 = 1,"), "{text}");
4706 assert!(text.contains("global @d : i32 = 0,"), "{text}");
4707 assert!(text.contains("global @e : i32 = 1,"), "{text}");
4708 assert!(text.contains("global @h : i32 = 11,"), "{text}");
4709 assert!(!text.contains("__builtin_constant_p"), "it is not a call to anything:\n{text}");
4710
4711 let text = body("int f(void) { int i = 0; __builtin_constant_p(i++); return i; }\n");
4715 assert_eq!(text, "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 0\n return %0\n");
4716 }
4717
4718 #[test]
4727 fn a_call_to_a_library_builtin_reaches_the_library_function() {
4728 let text = body("void f(void) { __builtin_abort(); }\n");
4729 assert_eq!(text, "block0:\n call @abort() : ()\n return\n");
4730
4731 let text = ir("int f(const char *s) { return __builtin_puts(s) + __builtin_strlen(s); }\n");
4734 assert!(text.contains("call @puts(%0) : (ptr) -> i32"), "{text}");
4735 assert!(text.contains("call @strlen(%0) : (ptr) -> i64"), "{text}");
4736 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
4737 }
4738
4739 #[test]
4752 fn a_chk_builtin_reaches_the_checking_function_and_keeps_the_size() {
4753 let text = ir(concat!(
4754 "char d[8];\n",
4755 "void f(const char *s, unsigned long n) {\n",
4756 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
4757 " __builtin___strcpy_chk(d, s, __builtin_object_size(d, 1));\n",
4758 " __builtin___memset_chk(d, 0, n, 8);\n",
4759 "}\n",
4760 ));
4761 assert!(text.contains("call @__memcpy_chk("), "{text}");
4762 assert!(text.contains("call @__strcpy_chk("), "{text}");
4763 assert!(text.contains("call @__memset_chk("), "{text}");
4764 assert!(text.contains("iconst.i64 8"), "the object size reaches the call: {text}");
4765 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
4766 }
4767
4768 #[test]
4776 fn a_checking_call_whose_size_says_nothing_is_known_is_the_plain_library_call() {
4777 let text = ir(concat!(
4778 "extern char *p;\n",
4779 "char d[8];\n",
4780 "void f(const char *s, unsigned long n) {\n",
4781 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
4782 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4783 " __builtin___strcpy_chk(p, s, __builtin_object_size(p, 0));\n",
4784 " __builtin___stpncpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4785 " __builtin___sprintf_chk(p, 1, __builtin_object_size(p, 0), s);\n",
4786 "}\n",
4787 ));
4788
4789 assert!(
4791 text.contains("call @__memcpy_chk(%2, %0, %1, %3) : (ptr, ptr, i64, i64)"),
4792 "{text}"
4793 );
4794
4795 assert!(text.contains("call @memcpy(%6, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
4798 assert!(text.contains("call @strcpy(%10, %0) : (ptr, ptr) -> ptr"), "{text}");
4799 assert!(text.contains("call @stpncpy(%14, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
4800
4801 assert!(text.contains("call @__sprintf_chk("), "{text}");
4804
4805 let asm = asm(concat!(
4808 "void f(char *p, const char *s, unsigned long n) {\n",
4809 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4810 "}\n",
4811 ));
4812 assert!(asm.contains("call\tmemcpy"), "{asm}");
4813 assert!(!asm.contains("$-1"), "the size that went away leaves no instruction:\n{asm}");
4814 }
4815
4816 #[test]
4824 fn the_v_spellings_of_the_chk_family_take_the_list_a_va_list_parameter_holds() {
4825 let text = ir(concat!(
4826 "char d[64];\n",
4827 "int f(const char *fmt, ...) {\n",
4828 " __builtin_va_list ap;\n",
4829 " __builtin_va_start(ap, fmt);\n",
4830 " int n = __builtin___vsprintf_chk(d, 1, __builtin_object_size(d, 0), fmt, ap);\n",
4831 " __builtin_va_end(ap);\n",
4832 " return n;\n",
4833 "}\n",
4834 ));
4835 assert!(text.contains("call @__vsprintf_chk("), "{text}");
4836 assert!(text.contains("iconst.i64 64"), "the object size reaches the call: {text}");
4837 }
4838
4839 #[test]
4850 fn the_absolute_value_family_is_the_magnitude_and_not_a_call() {
4851 let text = body(concat!(
4852 "long long llabs(long long);\n",
4853 "long long f(long long x) { return llabs(x); }\n",
4854 ));
4855 assert!(text.contains("%1 = iconst.i64 63"), "{text}");
4856 assert!(text.contains("%2 = ashr %0, %1"), "{text}");
4857 assert!(text.contains("%3 = xor %0, %2"), "{text}");
4858 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4859 assert!(!text.contains("call"), "the call does not happen:\n{text}");
4860
4861 let text = body("int abs(int);\nint f(int x) { return abs(x); }\n");
4864 assert!(text.contains("iconst.i32 31"), "{text}");
4865 let text = body("long labs(long);\nlong f(long x) { return labs(x); }\n");
4866 assert!(text.contains("iconst.i64 63"), "{text}");
4867
4868 let text = body("long long f(long long x) { return __builtin_llabs(x); }\n");
4871 assert!(!text.contains("call"), "{text}");
4872
4873 let text = ir(concat!(
4875 "long long llabs(long long b);\n",
4876 "long long g(long long x) { return llabs(x); }\n",
4877 "long long llabs(long long b) { return 7; }\n",
4878 ));
4879 assert!(!text.contains("call @llabs"), "{text}");
4880 }
4881
4882 #[test]
4889 fn a_byte_swap_is_arithmetic_and_not_a_call() {
4890 let text = body("unsigned f(unsigned x) { return __builtin_bswap32(x); }\n");
4891 assert_eq!(text, "block0(%0: i32):\n %1 = bswap %0\n return %1\n");
4892
4893 let text = body("unsigned f(unsigned char c) { return __builtin_bswap32(c); }\n");
4896 assert!(text.contains("zext.i32 %0"), "widened first: {text}");
4897 assert!(text.contains("bswap %1"), "and swapped at four bytes: {text}");
4898 }
4899
4900 #[test]
4906 fn the_byte_swaps_reverse_at_the_width_their_name_says() {
4907 for (name, ty, width) in [
4908 ("__builtin_bswap16", "unsigned short", "i16"),
4909 ("__builtin_bswap32", "unsigned", "i32"),
4910 ("__builtin_bswap64", "unsigned long long", "i64"),
4911 ] {
4912 let source = format!("{ty} f({ty} x) {{ return {name}(x); }}\n");
4913 let text = body(&source);
4914 assert_eq!(
4915 text,
4916 format!("block0(%0: {width}):\n %1 = bswap %0\n return %1\n"),
4917 "{name}"
4918 );
4919 }
4920 }
4921
4922 #[test]
4929 fn the_bit_counts_are_instructions_and_not_calls() {
4930 let text = body("int f(unsigned x) { return __builtin_clz(x); }\n");
4931 assert_eq!(text, "block0(%0: i32):\n %1 = ctlz %0\n return %1\n");
4932
4933 let text = body("int f(unsigned x) { return __builtin_ctz(x); }\n");
4934 assert_eq!(text, "block0(%0: i32):\n %1 = cttz %0\n return %1\n");
4935
4936 let text = body("int f(unsigned x) { return __builtin_popcount(x); }\n");
4937 assert_eq!(text, "block0(%0: i32):\n %1 = ctpop %0\n return %1\n");
4938 }
4939
4940 #[test]
4949 fn the_bit_counts_ask_about_the_width_their_name_says() {
4950 let text = body("int f(unsigned long long x) { return __builtin_clzll(x); }\n");
4951 assert!(text.starts_with("block0(%0: i64):"), "counted at eight bytes: {text}");
4952 assert!(text.contains("%1 = ctlz %0"), "{text}");
4953 assert!(text.contains("trunc.i32 %1"), "and answered in an int: {text}");
4954
4955 let text = body("int f(unsigned long long x) { return __builtin_clz(x); }\n");
4958 assert!(text.contains("trunc.i32 %0"), "narrowed to what was asked about: {text}");
4959 assert!(text.contains("ctlz %1"), "and counted there: {text}");
4960
4961 let text = body("int f(unsigned long x) { return __builtin_popcountl(x); }\n");
4962 assert!(text.contains("%1 = ctpop %0"), "{text}");
4963 assert!(!text.contains("call"), "{text}");
4964 }
4965
4966 #[test]
4971 fn a_parity_is_the_low_bit_of_the_set_bit_count() {
4972 let text = body("int f(unsigned x) { return __builtin_parity(x); }\n");
4973 assert!(text.contains("%1 = ctpop %0"), "{text}");
4974 assert!(text.contains("iconst.i32 1"), "{text}");
4975 assert!(text.contains("and %1, %2"), "the low bit of it: {text}");
4976 }
4977
4978 #[test]
4984 fn the_first_set_bit_is_one_based_and_zero_for_a_zero() {
4985 let text = body("int f(int x) { return __builtin_ffs(x); }\n");
4986 assert!(text.contains("%1 = cttz %0"), "{text}");
4987 assert!(text.contains("%4 = add %1, %2"), "one more than the count: {text}");
4988 assert!(text.contains("%5 = icmp ne %0, %3"), "whether there was a bit at all: {text}");
4989 assert!(text.contains("%7 = sub %3, %6"), "spread to a mask: {text}");
4990 assert!(text.contains("%8 = and %4, %7"), "and kept only then: {text}");
4991 assert!(!text.contains("br_if"), "no branch: {text}");
4992 }
4993
4994 #[test]
5004 fn the_redundant_sign_bit_count_is_instructions_and_not_a_call() {
5005 let text = body("int f(int x) { return __builtin_clrsb(x); }\n");
5006 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
5007 assert!(text.contains("%2 = ashr %0, %1"), "the sign over every bit: {text}");
5008 assert!(text.contains("%3 = xor %0, %2"), "folded onto it: {text}");
5009 assert!(text.contains("%5 = shl %3, %4"), "one less than the count: {text}");
5010 assert!(text.contains("%6 = or %5, %4"), "with something to count at zero: {text}");
5011 assert!(text.contains("%7 = ctlz %6"), "{text}");
5012 assert!(!text.contains("call"), "{text}");
5013 assert!(!text.contains("br_if"), "no branch: {text}");
5014 }
5015
5016 #[test]
5022 fn the_unsigned_absolute_value_family_answers_in_the_unsigned_type() {
5023 let text = body("unsigned f(int x) { return __builtin_uabs(x); }\n");
5024 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
5025 assert!(text.contains("%4 = sub %3, %2"), "{text}");
5026 assert!(!text.contains("call"), "nothing declares uabs, so a call would not link: {text}");
5027
5028 let text = body("unsigned long long f(long long x) { return __builtin_ullabs(x); }\n");
5029 assert!(text.contains("iconst.i64 63"), "at the width the name says: {text}");
5030
5031 let text = body("int f(int x) { return __builtin_uabs(x) > 2147483647u; }\n");
5034 assert!(text.contains("icmp ugt"), "compared unsigned: {text}");
5035 }
5036
5037 #[test]
5045 fn the_widest_absolute_value_is_whichever_type_the_target_makes_intmax_t() {
5046 let text = body("long f(long x) { return __builtin_imaxabs(x); }\n");
5047 assert!(text.contains("iconst.i64 63"), "{text}");
5048 assert!(text.contains("%4 = sub %3, %2"), "{text}");
5049 assert!(!text.contains("call"), "{text}");
5050
5051 let text = body("unsigned long f(long x) { return __builtin_umaxabs(x); }\n");
5052 assert!(text.contains("iconst.i64 63"), "{text}");
5053 assert!(!text.contains("call"), "{text}");
5054 }
5055
5056 #[test]
5064 fn an_overflow_predicate_writes_nothing_and_answers_the_bit_the_check_would() {
5065 let text =
5066 body("int f(int a, int b) { return __builtin_add_overflow_p(a, b, (int) 0); }\n");
5067 assert!(text.contains("%2, %3 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
5068 assert!(!text.contains("store"), "nothing is written: {text}");
5069 assert!(!text.contains("call"), "{text}");
5070
5071 let text =
5074 body("int f(int a, int b) { return __builtin_mul_overflow_p(a, b, (long long) 0); }\n");
5075 assert!(text.contains("smul_overflow.(i64, i1)"), "{text}");
5076 assert!(!text.contains("store"), "{text}");
5077
5078 let text = body(concat!(
5081 "int g(void);\n",
5082 "int f(int a, int b) { return __builtin_sub_overflow_p(a, b, g()); }\n",
5083 ));
5084 assert!(!text.contains("call @g"), "the third argument is not evaluated: {text}");
5085 }
5086
5087 #[test]
5097 fn an_overflow_check_is_arithmetic_and_not_a_call() {
5098 let text =
5099 body("int f(int a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
5100 assert!(text.contains("%3, %4 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
5101 assert!(text.contains("store %3 -> %2"), "{text}");
5102 assert!(!text.contains("call"), "{text}");
5103
5104 let text =
5105 body("int f(int a, int b, int *r) { return __builtin_sub_overflow(a, b, r); }\n");
5106 assert!(text.contains("ssub_overflow.(i32, i1) %0, %1"), "{text}");
5107
5108 let text =
5109 body("int f(int a, int b, int *r) { return __builtin_mul_overflow(a, b, r); }\n");
5110 assert!(text.contains("smul_overflow.(i32, i1) %0, %1"), "{text}");
5111
5112 let text = body(
5115 "int f(unsigned a, unsigned b, unsigned *r) { return __builtin_add_overflow(a, b, r); }\n",
5116 );
5117 assert!(text.contains("uadd_overflow.(i32, i1) %0, %1"), "{text}");
5118 }
5119
5120 #[test]
5128 fn an_overflow_check_is_done_at_a_type_that_holds_every_operand() {
5129 let text = body(
5130 "int f(unsigned a, int b, long long *r) { return __builtin_add_overflow(a, b, r); }\n",
5131 );
5132 assert!(text.contains("%3 = zext.i64 %0"), "the unsigned operand keeps its value: {text}");
5133 assert!(text.contains("%4 = sext.i64 %1"), "and so does the signed one: {text}");
5134 assert!(text.contains("sadd_overflow.(i64, i1) %3, %4"), "{text}");
5135
5136 let text = body(
5139 "int f(long long a, long long b, long long *r) { return __builtin_mul_overflow(a, b, r); }\n",
5140 );
5141 assert!(text.contains("smul_overflow.(i64, i1) %0, %1"), "{text}");
5142 assert!(!text.contains("sext."), "{text}");
5143 assert!(!text.contains("zext.i64"), "{text}");
5145 }
5146
5147 #[test]
5155 fn an_overflow_check_writes_the_wrapped_answer_whether_or_not_it_fit() {
5156 let text =
5157 body("int f(int a, int b, char *r) { return __builtin_sub_overflow(a, b, r); }\n");
5158 assert!(text.contains("%3, %4 = ssub_overflow.(i32, i1) %0, %1"), "{text}");
5159 assert!(text.contains("%5 = trunc.i8 %3"), "narrowed to where it goes: {text}");
5160 assert!(text.contains("%6 = sext.i32 %5"), "and back: {text}");
5161 assert!(text.contains("%7 = icmp ne %6, %3"), "which is whether it fit: {text}");
5162 assert!(text.contains("store %5 -> %2"), "the narrowed value is stored either way: {text}");
5163 assert!(text.contains("%8 = or %4, %7"), "and either bit is an overflow: {text}");
5164 }
5165
5166 #[test]
5173 fn a_call_needing_more_than_the_widest_type_still_compiles() {
5174 for name in ["add", "sub", "mul"] {
5175 let source = format!(
5176 "int f(unsigned __int128 a, long long b, __int128 *r) {{\n \
5177 return __builtin_{name}_overflow(a, b, r);\n}}\n"
5178 );
5179 let mut opts = options();
5180 opts.emit = EmitKind::MirFinal;
5181 assert!(!run(&opts, &source).failed(), "{name} was refused or stopped the back end");
5182 }
5183 }
5184
5185 #[test]
5188 fn an_overflow_check_over_something_that_is_not_an_integer_says_so() {
5189 let messages =
5190 errors("int f(double a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
5191 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
5192
5193 let messages =
5194 errors("int f(int a, int b, double *r) { return __builtin_add_overflow(a, b, r); }\n");
5195 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
5196 }
5197
5198 #[test]
5209 fn an_ordered_access_is_ordered_in_the_ir() {
5210 let text = body("int f(int *p) { return __atomic_load_n(p, 0); }\n");
5211 assert!(text.contains("atomic_load.i32 %0, align 4, relaxed"), "{text}");
5212
5213 let text = body("long f(long *p) { return __atomic_load_n(p, 2); }\n");
5214 assert!(text.contains("atomic_load.i64 %0, align 8, acquire"), "{text}");
5215
5216 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
5217 assert!(text.contains("atomic_store %1 -> %0, align 4, release"), "{text}");
5218
5219 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
5220 assert!(text.contains("atomic_store %1 -> %0, align 4, seq_cst"), "{text}");
5221
5222 let text = body("void f(char *p, int v) { __atomic_store_n(p, v, 0); }\n");
5225 assert!(text.contains("trunc.i8 %1"), "{text}");
5226 assert!(text.contains("atomic_store %2 -> %0, align 1, relaxed"), "{text}");
5227 }
5228
5229 #[test]
5238 fn an_ordered_access_is_the_plain_instruction_on_this_machine() {
5239 let text = asm("int f(int *p) { return __atomic_load_n(p, 5); }\n");
5240 assert!(text.contains("movl\t(%rdi), %eax"), "{text}");
5241 assert!(!text.contains("mfence"), "a load needs no barrier here: {text}");
5242
5243 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
5244 assert!(text.contains("movl\t%esi, (%rdi)"), "{text}");
5245 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
5246
5247 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
5248 let (before, after) = text.split_once("mfence").expect("a barrier: {text}");
5249 assert!(before.contains("movl\t%esi, (%rdi)"), "the store comes first: {text}");
5250 assert!(!after.contains("movl"), "and nothing else is between them: {text}");
5251 }
5252
5253 #[test]
5263 fn a_barrier_is_one_instruction_at_the_strongest_ordering_and_none_below_it() {
5264 assert!(asm("void f(void) { __atomic_thread_fence(5); }\n").contains("mfence"));
5265 assert!(asm("void f(void) { __sync_synchronize(); }\n").contains("mfence"));
5266
5267 for weaker in ["1", "2", "3", "4"] {
5268 let source = format!("void f(void) {{ __atomic_thread_fence({weaker}); }}\n");
5269 assert!(!asm(&source).contains("mfence"), "{weaker} costs nothing here");
5270 }
5271 }
5272
5273 #[test]
5283 fn the_three_x86_fences_are_the_barrier_the_strongest_ordering_gives() {
5284 for name in ["__builtin_ia32_sfence", "__builtin_ia32_lfence", "__builtin_ia32_mfence"] {
5285 let source = format!("void f(void) {{ {name}(); }}\n");
5286 assert!(asm(&source).contains("mfence"), "{name} is a barrier");
5287 let text = body(&source);
5288 assert!(text.contains("fence seq_cst"), "{name}: {text}");
5289 }
5290
5291 let result = run(&options(), "void f(void) { __builtin_ia32_sfence(1); }\n");
5292 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
5293 assert!(result.messages[0].contains("too many arguments"), "{:?}", result.messages);
5294 }
5295
5296 #[test]
5302 fn a_compare_and_exchange_is_one_instruction_answering_two_things() {
5303 let text =
5306 body("int f(int *p, int e, int d) { return __sync_val_compare_and_swap(p, e, d); }\n");
5307 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
5308 assert!(text.contains("return %3"), "the value it found: {text}");
5309
5310 let text =
5311 body("int f(int *p, int e, int d) { return __sync_bool_compare_and_swap(p, e, d); }\n");
5312 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
5313 assert!(text.contains("zext.i32 %4"), "whether it happened: {text}");
5314
5315 let text = body(
5318 "int f(int *p, int *e, int d) { return __atomic_compare_exchange_n(p, e, d, 0, 4, 2); }\n",
5319 );
5320 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
5321 assert!(text.contains("%4, %5 = cmpxchg.(i32, i1) %0, %3, %2, align 4, acq_rel"), "{text}");
5322 assert!(text.contains("br_if %5, block2, block1"), "{text}");
5323 assert!(text.contains("store %4 -> %1, align 4"), "{text}");
5324
5325 let text = body(
5328 "int f(int *p, int *e, int *d) { return __atomic_compare_exchange(p, e, d, 0, 5, 5); }\n",
5329 );
5330 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
5331 assert!(text.contains("%4 = load.i32 %2, align 4"), "{text}");
5332 assert!(text.contains("%5, %6 = cmpxchg.(i32, i1) %0, %3, %4, align 4, seq_cst"), "{text}");
5333 }
5334
5335 #[test]
5342 fn a_compare_and_exchange_is_a_locked_instruction_at_the_width_of_the_object() {
5343 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
5344 for (ty, suffix, reg) in widths {
5345 let source = format!(
5346 "int f({ty} *p, {ty} e, {ty} d) {{ return __sync_bool_compare_and_swap(p, e, d); }}\n"
5347 );
5348 let text = asm(&source);
5349 assert!(text.contains("\tlock\n"), "{ty}: {text}");
5350 assert!(text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
5351 assert!(text.contains("sete\t"), "{ty}: {text}");
5352 }
5353 let source =
5354 "int f(long *p, long e, long d) { return __sync_bool_compare_and_swap(p, e, d); }\n";
5355 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
5356
5357 for order in ["0", "2", "3", "4", "5"] {
5361 let call = format!("__atomic_compare_exchange_n(p, e, d, 0, {order}, 0)");
5362 let source = format!("int f(int *p, int *e, int d) {{ return {call}; }}\n");
5363 let text = asm(&source);
5364 assert!(text.contains("cmpxchgl\t"), "{order}: {text}");
5365 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
5366 }
5367 }
5368
5369 #[test]
5381 fn a_read_modify_write_is_one_instruction_and_the_arithmetic_a_name_asks_for() {
5382 let text = body("int f(int *p, int v) { return __atomic_fetch_add(p, v, 5); }\n");
5383 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
5384 assert!(text.contains("return %2"), "the value that was there: {text}");
5385
5386 let text = body("int f(int *p, int v) { return __atomic_add_fetch(p, v, 5); }\n");
5387 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
5388 assert!(text.contains("%3 = add %2, %1"), "and the value afterwards: {text}");
5389
5390 let text = body("int f(int *p, int v) { return __atomic_sub_fetch(p, v, 5); }\n");
5391 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
5392 assert!(text.contains("%3 = sub %2, %1"), "{text}");
5393
5394 let text = body("int f(int *p, int v) { return __sync_fetch_and_sub(p, v); }\n");
5396 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
5397
5398 let text = body("int f(int *p, int v) { return __atomic_exchange_n(p, v, 5); }\n");
5401 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, seq_cst"), "{text}");
5402
5403 let text = body("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
5404 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, acquire"), "{text}");
5405
5406 let text = body("void f(int *p) { __sync_lock_release(p); }\n");
5409 assert!(text.contains("release"), "{text}");
5410 assert!(text.contains("%1 = iconst.i32 0"), "{text}");
5411
5412 let text = body("void f(int *p, int guard) { __sync_lock_release(p, guard); }\n");
5416 assert!(text.contains("%2 = iconst.i32 0"), "{text}");
5417 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
5418
5419 let text = body("int f(int *p, int v) { return __atomic_fetch_and(p, v, 5); }\n");
5422 assert!(text.contains("%2 = atomic_rmw.i32 and %0, %1, align 4, seq_cst"), "{text}");
5423
5424 let text = body("int f(int *p, int v) { return __sync_or_and_fetch(p, v); }\n");
5425 assert!(text.contains("%2 = atomic_rmw.i32 or %0, %1, align 4, seq_cst"), "{text}");
5426 assert!(text.contains("%3 = or %2, %1"), "and the value afterwards: {text}");
5427
5428 let text = body("int f(int *p, int v) { return __atomic_nand_fetch(p, v, 5); }\n");
5431 assert!(text.contains("%2 = atomic_rmw.i32 nand %0, %1, align 4, seq_cst"), "{text}");
5432 assert!(text.contains("%3 = and %2, %1"), "{text}");
5433 assert!(text.contains("%4 = iconst.i32 -1"), "{text}");
5434 assert!(text.contains("%5 = xor %3, %4"), "{text}");
5435 }
5436
5437 #[test]
5448 fn a_bitwise_read_modify_write_is_a_loop_around_the_compare_and_exchange() {
5449 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
5450 for (ty, suffix, reg) in widths {
5451 for (name, call, insn) in [
5452 ("and", "__atomic_fetch_and(p, v, 5)", "and"),
5453 ("or", "__sync_fetch_and_or(p, v)", "or"),
5454 ("xor", "__atomic_xor_fetch(p, v, 5)", "xor"),
5455 ] {
5456 let source = format!("{ty} f({ty} *p, {ty} v) {{ return {call}; }}\n");
5457 let text = asm(&source);
5458 assert!(text.contains("\tlock\n"), "{ty} {name}: {text}");
5459 assert!(
5460 text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")),
5461 "{ty} {name}: {text}"
5462 );
5463 assert!(text.contains(&format!("{insn}{suffix}\t")), "{ty} {name}: {text}");
5464 assert!(!text.contains("\txadd"), "{ty} {name} is not an add: {text}");
5466 assert!(!text.contains("\txchg"), "{ty} {name} is not an exchange: {text}");
5467 }
5468 }
5469 let source = "long f(long *p, long v) { return __atomic_fetch_or(p, v, 5); }\n";
5470 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
5471
5472 let text = asm("int f(int *p, int v) { return __sync_fetch_and_nand(p, v); }\n");
5476 assert!(text.contains("cmpxchgl\t"), "{text}");
5477 assert!(text.contains("andl\t"), "{text}");
5478 assert!(text.contains("notl\t"), "{text}");
5479 }
5480
5481 #[test]
5490 fn an_access_through_a_second_pointer_is_the_same_access_and_one_more() {
5491 let text = body("void f(int *p, int *r) { __atomic_load(p, r, 5); }\n");
5492 assert!(text.contains("%2 = atomic_load.i32 %0, align 4, seq_cst"), "{text}");
5493 assert!(text.contains("store %2 -> %1, align 4"), "and out through the place: {text}");
5494
5495 let text = body("void f(int *p, int *v) { __atomic_store(p, v, 3); }\n");
5496 assert!(text.contains("%2 = load.i32 %1, align 4"), "in through the place: {text}");
5497 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
5498
5499 let text = body("void f(int *p, int *v, int *r) { __atomic_exchange(p, v, r, 5); }\n");
5502 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
5503 assert!(text.contains("%4 = atomic_rmw.i32 xchg %0, %3, align 4, seq_cst"), "{text}");
5504 assert!(text.contains("store %4 -> %2, align 4"), "{text}");
5505 }
5506
5507 #[test]
5518 fn a_flag_is_an_exchange_of_one_byte_and_a_store_of_a_zero_over_the_same_byte() {
5519 for pointer in ["char", "int", "void"] {
5520 let source = format!("int f({pointer} *p) {{ return __atomic_test_and_set(p, 5); }}\n");
5521 let text = body(&source);
5522 assert!(text.contains("%1 = iconst.i8 1"), "{pointer}: {text}");
5523 assert!(
5524 text.contains("%2 = atomic_rmw.i8 xchg %0, %1, align 1, seq_cst"),
5525 "{pointer}: {text}"
5526 );
5527 assert!(text.contains("%4 = icmp ne %2, %3"), "{pointer}: {text}");
5528
5529 let source = format!("void f({pointer} *p) {{ __atomic_clear(p, 3); }}\n");
5530 let text = body(&source);
5531 assert!(text.contains("atomic_store %2 -> %0, align 1, release"), "{pointer}: {text}");
5532 }
5533
5534 let text = asm("int f(int *p) { return __atomic_test_and_set(p, 5); }\n");
5537 assert!(text.contains("xchgb\t%al, (%rdi)"), "{text}");
5538 assert!(text.contains("setne\t"), "{text}");
5539 }
5540
5541 #[test]
5549 fn a_read_modify_write_is_an_exchange_or_a_locked_add_at_the_width_of_the_object() {
5550 let widths = [("char", "b", "%sil"), ("short", "w", "%si"), ("int", "l", "%esi")];
5551 for (ty, suffix, reg) in widths {
5552 let source =
5553 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_fetch_add(p, v, 5); }}\n");
5554 let text = asm(&source);
5555 assert!(text.contains("\tlock\n"), "{ty}: {text}");
5556 assert!(text.contains(&format!("xadd{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
5557
5558 let source =
5559 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_exchange_n(p, v, 5); }}\n");
5560 let text = asm(&source);
5561 assert!(text.contains(&format!("xchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
5562 assert!(!text.contains("\tlock\n"), "an exchange is locked already: {ty}: {text}");
5563 }
5564 let source = "long f(long *p, long v) { return __atomic_fetch_add(p, v, 5); }\n";
5565 assert!(asm(source).contains("xaddq\t%rsi, (%rdi)"), "{}", asm(source));
5566
5567 let source = "int f(int *p, int v) { return __atomic_fetch_sub(p, v, 5); }\n";
5570 let text = asm(source);
5571 assert!(text.contains("negl\t"), "{text}");
5572 assert!(text.contains("xaddl\t"), "{text}");
5573
5574 for order in ["0", "2", "3", "4", "5"] {
5577 let source =
5578 format!("int f(int *p, int v) {{ return __atomic_fetch_add(p, v, {order}); }}\n");
5579 let text = asm(&source);
5580 assert!(text.contains("xaddl\t"), "{order}: {text}");
5581 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
5582 }
5583
5584 let text = asm("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
5588 assert!(text.contains("xchgl\t%esi, (%rdi)"), "{text}");
5589 let text = asm("void f(int *p) { __sync_lock_release(p); }\n");
5596 assert!(text.contains("xorl\t%eax, %eax"), "{text}");
5597 assert!(text.contains("movl\t%eax, (%rdi)"), "{text}");
5598 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
5599 }
5600
5601 #[test]
5613 fn the_lock_free_questions_are_answered_as_constants() {
5614 for size in ["1", "2", "4", "8"] {
5615 let source =
5616 format!("int f(void) {{ return __atomic_always_lock_free({size}, 0); }}\n");
5617 let text = asm(&source);
5618 assert!(text.contains("movb\t$1, %al"), "{size} bytes is lock free: {text}");
5619 assert!(!text.contains("call"), "and is not a call: {text}");
5620 }
5621 for size in ["3", "16", "sizeof(long double)"] {
5622 let source = format!("int f(void) {{ return __atomic_is_lock_free({size}, 0); }}\n");
5623 let text = asm(&source);
5624 assert!(text.contains("movb\t$0, %al"), "{size} bytes is not: {text}");
5625 assert!(!text.contains("call"), "and is not a call either: {text}");
5626 }
5627
5628 let text = asm("int f(int n) { return __atomic_is_lock_free(n, 0); }\n");
5632 assert!(text.contains("movb\t$0, %al"), "a size nobody knows is not lock free: {text}");
5633 let text = asm("int f(int *p) { return __atomic_always_lock_free(8, p); }\n");
5634 assert!(text.contains("movb\t$0, %al"), "eight bytes at four is not: {text}");
5635 let text = asm("int f(long *p) { return __atomic_always_lock_free(8, p); }\n");
5636 assert!(text.contains("movb\t$1, %al"), "and at eight it is: {text}");
5637 }
5638
5639 #[test]
5651 fn a_memory_order_an_operation_cannot_carry_is_read_as_the_strongest() {
5652 let mut opts = options();
5653 opts.emit = EmitKind::Ir;
5654
5655 let acquire_store = run(&opts, "void f(int *p, int v) { __atomic_store_n(p, v, 2); }\n");
5656 assert!(acquire_store.text().contains("seq_cst"), "{:?}", acquire_store.text());
5657 assert!(acquire_store.messages[0].contains("[W0333]"), "{:?}", acquire_store.messages);
5658
5659 let nonsense = run(&opts, "int f(int *p) { return __atomic_load_n(p, 99); }\n");
5660 assert!(nonsense.text().contains("seq_cst"), "{:?}", nonsense.text());
5661 assert!(nonsense.messages[0].contains("[W0333]"), "{:?}", nonsense.messages);
5662
5663 let computed = run(&opts, "int f(int *p, int n) { return __atomic_load_n(p, n); }\n");
5664 assert!(computed.text().contains("seq_cst"), "{:?}", computed.text());
5665 assert_eq!(computed.messages, Vec::<String>::new(), "a computed order is not a mistake");
5666 }
5667
5668 #[test]
5680 fn a_conversion_between_a_float_and_the_widest_unsigned_integer_is_written_without_a_branch() {
5681 let text = asm("double f(unsigned long long x) { return (double)x; }\n");
5682 assert!(text.contains("cvtsi2sdq"), "the signed conversion is what runs: {text}");
5683 assert!(text.contains("shrq"), "with the value halved first: {text}");
5684 assert!(text.contains("addsd"), "and doubled after: {text}");
5685 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
5686
5687 let text = asm("unsigned long long f(double d) { return (unsigned long long)d; }\n");
5688 assert!(text.contains("cvttsd2siq"), "the signed conversion is what runs: {text}");
5689 assert!(text.contains("subsd"), "with half the range taken off first: {text}");
5690 assert!(text.contains("shlq\t$63"), "and the top bit put back: {text}");
5691 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
5692 }
5693
5694 #[test]
5705 fn a_plain_name_the_program_took_is_the_programs_own_function() {
5706 let taken = concat!(
5707 "static long long llabs(long long b) { return 7; }\n",
5708 "long long f(long long x) { return llabs(x); }\n",
5709 );
5710 assert!(ir(taken).contains("call @llabs"), "a static definition is the program's own");
5711
5712 let retyped = concat!("int llabs(int b);\n", "int f(int x) { return llabs(x); }\n",);
5713 assert!(ir(retyped).contains("call @llabs"), "another type is another function");
5714
5715 let plain = concat!(
5716 "long long llabs(long long b);\n",
5717 "long long f(long long x) { return llabs(x); }\n",
5718 );
5719 let mut opts = options();
5720 opts.emit = EmitKind::Ir;
5721 assert!(!run(&opts, plain).text().contains("call @llabs"), "the library's by default");
5722
5723 opts.builtins = false;
5724 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin");
5725
5726 opts.builtins = true;
5727 opts.no_builtin = vec!["llabs".to_owned()];
5728 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin-llabs");
5729 let one = "long labs(long b);\nlong f(long x) { return labs(x); }\n";
5730 assert!(!run(&opts, one).text().contains("call @labs"), "one name and not the family");
5731
5732 opts.no_builtin = Vec::new();
5735 opts.builtins = false;
5736 let prefixed = "long long f(long long x) { return __builtin_llabs(x); }\n";
5737 assert!(!run(&opts, prefixed).text().contains("call @llabs"), "the prefix is a promise");
5738 }
5739
5740 #[test]
5753 fn the_hint_builtins_are_their_first_argument_and_the_hint_leaves_no_trace() {
5754 let text = ir(concat!(
5755 "long a = __builtin_expect(7, 1);\n",
5756 "long b = __builtin_expect_with_probability(9, 1, 0.9);\n",
5757 "unsigned long c = sizeof(__builtin_expect((char)1, 1));\n",
5758 ));
5759 assert!(text.contains("global @a : i64 = 7,"), "{text}");
5760 assert!(text.contains("global @b : i64 = 9,"), "{text}");
5761 assert!(text.contains("global @c : i64 = 8,"), "{text}");
5762 assert!(!text.contains("__builtin_expect"), "it is not a call to anything:\n{text}");
5763
5764 let text = body("long f(char c) { return __builtin_expect(c, 1); }\n");
5767 assert!(text.contains("sext"), "{text}");
5768
5769 let one = "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 1\n %2 = sext.i64 %1\n return %0\n";
5773 assert_eq!(body("int f(void) { int i = 0; __builtin_expect(1, i++); return i; }\n"), one);
5774 let source = "int g(void) { int i = 0; __builtin_expect_with_probability(1, i++, 0.5); return i; }\n";
5775 assert_eq!(body(source), one);
5776
5777 let kept = body("int f(int n) { int i = 0; __builtin_expect(n, i++); return i; }\n");
5782 assert!(kept.contains("add.nsw"), "the hint still runs: {kept}");
5783 assert!(kept.ends_with("return %3\n"), "and the answer is what it left behind: {kept}");
5784 let both = "int g(int n) { int i = 0; __builtin_expect_with_probability(n, i++, 0.5); return i; }\n";
5785 assert!(body(both).contains("add.nsw"), "and so does the one with three arguments");
5786 }
5787
5788 #[test]
5800 fn a_promise_that_control_does_not_arrive_writes_no_instruction() {
5801 let promised = "int f(int x) { if (x) return 1; __builtin_unreachable(); }\n";
5802 let text = ir(promised);
5803 assert!(text.contains(" unreachable_hint\n"), "{text}");
5804 assert!(!text.contains("call"), "it is not a call to anything:\n{text}");
5805
5806 let after = body("int g(int x) { __builtin_unreachable(); return x; }\n");
5810 assert!(after.contains("return"), "{after}");
5811
5812 let text = asm(promised);
5815 let mine = text.split_once("\nf:\n").expect("a definition").1;
5816 let mine = mine.split_once("\t.size").expect("a definition").0;
5817 let plain = asm("int f(int x) { if (x) return 1; }\n");
5818 let plain = plain.split_once("\nf:\n").expect("a definition").1;
5819 let plain = plain.split_once("\t.size").expect("a definition").0;
5820 assert_eq!(mine, plain);
5821 let last = mine.lines().rfind(|line| !line.trim_start().starts_with('.'));
5824 assert_eq!(last.map(str::trim), Some("ret"), "{mine}");
5825 assert!(!mine.contains("ud2"), "{mine}");
5826 }
5827
5828 #[test]
5835 fn a_library_builtin_is_diagnosed_under_the_name_the_program_wrote() {
5836 let mut opts = options();
5837 opts.emit = EmitKind::Ir;
5838 let messages = run(&opts, "void f(void) { __builtin_abort(1); }\n").messages;
5839 assert!(
5840 messages.iter().any(|m| m.contains("__builtin_abort")),
5841 "expected the written name in {messages:?}"
5842 );
5843 }
5844
5845 #[test]
5853 fn a_builtin_nothing_lowers_is_refused_by_name() {
5854 let mut opts = options();
5855 opts.emit = EmitKind::Ir;
5856 let builtin = "__atomic_signal_fence";
5857 let source = format!("int counter;\nint f(void) {{ return ({builtin}(5), 0); }}\n");
5858 let messages = run(&opts, &source).messages;
5859 let named = messages.iter().any(|m| m.contains(builtin) && m.contains("E0686"));
5860 assert!(named, "expected {builtin} to be refused by name in {messages:?}");
5861 }
5862
5863 #[test]
5872 fn what_is_refused_is_the_call_and_not_the_name() {
5873 let text = ir(concat!(
5874 "void __atomic_signal_fence(int order) { (void)order; }\n",
5875 "void f(void) { __atomic_signal_fence(5); }\n",
5876 ));
5877 assert!(text.contains("call @__atomic_signal_fence"), "{text}");
5878 }
5879
5880 #[test]
5889 fn the_object_size_of_an_address_is_what_the_layout_leaves_in_front_of_it() {
5890 let text = ir(concat!(
5891 "struct S { char a[8]; int n; char b[12]; };\n",
5892 "char g[32];\n",
5893 "struct S gs;\n",
5894 "unsigned long whole = __builtin_object_size(g, 0);\n",
5895 "unsigned long moved = __builtin_object_size(g + 4, 0);\n",
5896 "unsigned long back = __builtin_object_size(g + 30 - 2, 0);\n",
5897 "unsigned long outer = __builtin_object_size(gs.a, 0);\n",
5898 "unsigned long inner = __builtin_object_size(gs.a, 1);\n",
5899 "unsigned long scalar = __builtin_object_size(&gs.n, 1);\n",
5900 "unsigned long after = __builtin_object_size(&gs.n, 0);\n",
5901 "unsigned long into = __builtin_object_size(&gs.b[2], 1);\n",
5902 "unsigned long text = __builtin_object_size(\"hello\", 0);\n",
5903 "unsigned long dyn = __builtin_dynamic_object_size(gs.b, 1);\n",
5904 ));
5905 for (name, size) in [
5906 ("whole", 32),
5907 ("moved", 28),
5908 ("back", 4),
5909 ("outer", 24),
5910 ("inner", 8),
5911 ("scalar", 4),
5912 ("after", 16),
5913 ("into", 10),
5914 ("text", 6),
5915 ("dyn", 12),
5916 ] {
5917 let said = format!("global @{name} : i64 = {size},");
5918 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
5919 }
5920 }
5921
5922 #[test]
5930 fn the_object_behind_an_address_can_be_one_with_automatic_storage() {
5931 let text = body(concat!(
5932 "struct S { char a[8]; int n; char b[12]; };\n",
5933 "unsigned long f(void) {\n",
5934 " char loc[20];\n",
5935 " struct S ls;\n",
5936 " return __builtin_object_size(loc + 3, 0) + __builtin_object_size(ls.b + 2, 1);\n",
5937 "}\n",
5938 ));
5939 assert!(text.contains("iconst.i64 17"), "twenty bytes with three used: {text}");
5940 assert!(text.contains("iconst.i64 10"), "twelve bytes with two used: {text}");
5941 }
5942
5943 #[test]
5953 fn an_address_with_no_object_in_sight_answers_at_the_end_of_the_range_its_kind_asks_for() {
5954 let text = ir(concat!(
5955 "struct T { int n; char f[]; };\n",
5956 "extern char *p;\n",
5957 "extern struct T *t;\n",
5958 "unsigned long largest = __builtin_object_size(p, 0);\n",
5959 "unsigned long nearest = __builtin_object_size(p, 1);\n",
5960 "unsigned long least = __builtin_object_size(p, 2);\n",
5961 "unsigned long tight = __builtin_object_size(p, 3);\n",
5962 "unsigned long flex = __builtin_object_size(t->f, 1);\n",
5963 "int says = __builtin_object_size(p, 0) == (unsigned long)-1;\n",
5964 ));
5965 for name in ["largest", "nearest", "flex"] {
5966 let said = format!("global @{name} : i64 = -1,");
5970 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
5971 }
5972 for name in ["least", "tight"] {
5973 let said = format!("global @{name} : i64 = 0,");
5974 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
5975 }
5976 assert!(text.contains("global @says : i32 = 1,"), "{text}");
5977 }
5978
5979 #[test]
5986 fn the_address_an_object_size_is_asked_about_is_not_evaluated() {
5987 let text = body(concat!(
5988 "extern char *side(void);\n",
5989 "unsigned long f(void) { return __builtin_object_size(side(), 0); }\n",
5990 ));
5991 assert!(!text.contains("call"), "nothing is called: {text}");
5992 }
5993
5994 #[test]
5999 fn a_kind_that_is_not_one_of_the_four_is_refused() {
6000 for source in [
6001 "extern char *p;\nextern int k;\nunsigned long f(void) ".to_owned()
6002 + "{ return __builtin_object_size(p, k); }\n",
6003 "extern char *p;\nunsigned long f(void) { return __builtin_object_size(p, 4); }\n"
6004 .to_owned(),
6005 "extern char *p;\nunsigned long f(void) ".to_owned()
6006 + "{ return __builtin_dynamic_object_size(p, -1); }\n",
6007 ] {
6008 let messages = errors(&source);
6009 let named = messages.iter().any(|m| m.contains("E0709") && m.contains("0 to 3"));
6010 assert!(named, "expected a complaint about the kind in {messages:?}");
6011 }
6012 }
6013
6014 #[test]
6020 fn the_pair_that_saves_a_place_lowers_to_the_two_markers() {
6021 let text = ir(concat!(
6022 "void *buf[5];\n",
6023 "int f(void) {\n",
6024 " if (__builtin_setjmp(buf)) return 2;\n",
6025 " return 1;\n",
6026 "}\n",
6027 "void g(void) { __builtin_longjmp(buf, 1); }\n",
6028 ));
6029 assert!(text.contains("= setjmp_marker.i32 %0\n"), "the save answers a value: {text}");
6030 assert!(text.contains(" longjmp_marker %0\n"), "the restore answers nothing: {text}");
6031 assert!(!text.contains("call @"), "neither of them is a call: {text}");
6032 }
6033
6034 #[test]
6042 fn a_local_of_a_function_that_saves_a_place_gets_a_slot() {
6043 let text = ir(concat!(
6044 "void *buf[5];\n",
6045 "int f(int x) { int a = 0; if (__builtin_setjmp(buf)) return a; a = 1; return x; }\n",
6046 "int g(int x) { int a = 0; if (x) return a; a = 1; return x; }\n",
6047 ));
6048 let (saves, plain) = text.split_once("func @g").expect("both functions");
6049 assert_eq!(saves.matches("= alloca").count(), 2, "the parameter and the local: {text}");
6050 assert!(saves.contains("store %9 -> %2"), "the local is written through: {text}");
6051 assert!(!plain.contains("alloca"), "nothing in the plain one needs a slot: {text}");
6052 }
6053
6054 #[test]
6063 fn the_save_writes_four_words_and_carries_on_in_a_new_block() {
6064 let text =
6065 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
6066 let body = text.split_once("\nf:\n").expect("the function").1;
6067 assert!(body.contains("\tmovq\t%rsp, %rbp\n"), "a frame pointer whatever: {text}");
6068 assert!(body.contains("\tsubq\t$8, %rsp\n"), "no red zone: {text}");
6069 assert!(body.contains("\tmovq\t%rbp, (%rax)\n"), "the frame pointer: {text}");
6070 assert!(body.contains("\tmovq\t%rsp, 16(%rax)\n"), "the stack pointer: {text}");
6071 assert!(body.contains("\tleaq\t.Lf_1(%rip), %rcx\n"), "where to come back to: {text}");
6072 assert!(body.contains("\tmovq\t%rcx, 8(%rax)\n"), "and that goes in the buffer: {text}");
6073 let back = body.split_once(".Lf_1:\n").expect("the block control comes back to").1;
6074 assert!(back.starts_with("\tmovq\t(%rsp), %rax\n"), "the answer is read back: {text}");
6075 }
6076
6077 #[test]
6085 fn a_save_destroys_every_register_the_allocator_hands_out() {
6086 let text =
6087 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
6088 for reg in ["%rbx", "%r12", "%r13", "%r14", "%r15"] {
6089 assert!(text.contains(&format!("\tpushq\t{reg}\n")), "{reg} is saved: {text}");
6090 assert!(text.contains(&format!("\tpopq\t{reg}\n")), "{reg} is restored: {text}");
6091 }
6092 }
6093
6094 #[test]
6101 fn the_restore_puts_the_frame_back_before_it_jumps() {
6102 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
6103 let mut opts = options();
6104 opts.emit = EmitKind::Asm;
6105 opts.opt_level = level;
6106 let source = "void *buf[5];\nvoid g(void) { __builtin_longjmp(buf, 1); }\n";
6107 let result = run(&opts, source);
6108 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
6109 let text = result.text().to_owned();
6110 let jump = text.find("\tjmp\t*%").unwrap_or_else(|| panic!("an indirect jump: {text}"));
6111 let stack = text.find(", %rsp\n").unwrap_or_else(|| panic!("the stack back: {text}"));
6112 let frame = text.find(", %rbp\n").unwrap_or_else(|| panic!("the frame back: {text}"));
6113 assert!(stack < jump, "the stack goes back first at {level:?}: {text}");
6114 assert!(frame < jump, "and so does the frame at {level:?}: {text}");
6115 }
6116 }
6117
6118 #[test]
6124 fn a_longjmp_whose_second_argument_is_not_one_is_turned_down() {
6125 for source in [
6126 "void *buf[5];\nvoid f(void) { __builtin_longjmp(buf, 0); }\n",
6127 "void *buf[5];\nextern int v;\nvoid f(void) { __builtin_longjmp(buf, v); }\n",
6128 ] {
6129 let messages = errors(source);
6130 let named = messages.iter().any(|m| m.contains("E0710"));
6131 assert!(named, "expected a complaint about the value in {messages:?}");
6132 }
6133 }
6134
6135 #[test]
6140 fn a_static_function_nothing_refers_to_is_not_emitted() {
6141 let text = ir("static int dropped(void) { return 1; }\n\
6142 static int kept(void) { return 2; }\n\
6143 int main(void) { return kept(); }\n");
6144 assert!(text.contains("func @kept"), "{text}");
6145 assert!(!text.contains("dropped"), "{text}");
6146 }
6147
6148 #[test]
6154 fn two_static_functions_that_only_call_each_other_are_both_dropped() {
6155 let text = ir("static int ping(void);\n\
6156 static int pong(void) { return ping(); }\n\
6157 static int ping(void) { return pong(); }\n\
6158 int main(void) { return 0; }\n");
6159 assert!(!text.contains("ping"), "{text}");
6160 assert!(!text.contains("pong"), "{text}");
6161 }
6162
6163 #[test]
6169 fn naming_a_static_function_anywhere_keeps_it() {
6170 let text = ir("static int by_address(void) { return 1; }\n\
6171 static int in_an_image(void) { return 2; }\n\
6172 static int deeper(void) { return 3; }\n\
6173 static int reaches_deeper(void) { return deeper(); }\n\
6174 static int (*table[1])(void) = {in_an_image};\n\
6175 int main(void) {\n\
6176 int (*p)(void) = by_address;\n\
6177 return p() + table[0]() + reaches_deeper();\n\
6178 }\n");
6179 for kept in ["by_address", "in_an_image", "deeper", "reaches_deeper"] {
6180 assert!(text.contains(&format!("func @{kept}")), "expected {kept} in:\n{text}");
6181 }
6182 }
6183
6184 #[test]
6190 fn an_attribute_keeps_a_static_function_nothing_refers_to() {
6191 for attribute in ["used", "retain", "constructor", "destructor", "__used__"] {
6192 let source = format!(
6193 "__attribute__(({attribute})) static int kept(void) {{ return 1; }}\n\
6194 int main(void) {{ return 0; }}\n"
6195 );
6196 let text = ir(&source);
6197 assert!(text.contains("func @kept"), "for {attribute}:\n{text}");
6198 }
6199 }
6200
6201 #[test]
6204 fn a_function_anything_could_call_is_emitted_without_being_called() {
6205 let text =
6206 ir("int nobody_here_calls_it(void) { return 1; }\nint main(void) { return 0; }\n");
6207 assert!(text.contains("func @nobody_here_calls_it"), "{text}");
6208 }
6209
6210 #[test]
6217 fn a_classification_c_has_an_operator_for_is_that_operator() {
6218 for (builtin, operator) in [
6219 ("__builtin_isgreater", "binary >"),
6220 ("__builtin_isgreaterequal", "binary >="),
6221 ("__builtin_isless", "binary <"),
6222 ("__builtin_islessequal", "binary <="),
6223 ] {
6224 let source = format!("int f(double x, double y) {{ return {builtin}(x, y); }}\n");
6225 let text = tast(&source);
6226 assert!(text.contains(&format!("{operator} : int")), "for {builtin}:\n{text}");
6227 }
6228 }
6229
6230 #[test]
6239 fn the_classification_builtins_are_comparisons_and_not_calls() {
6240 let text = body("int f(double x, double y) { return __builtin_isunordered(x, y); }\n");
6241 assert_eq!(
6242 text,
6243 "block0(%0: f64, %1: f64):\n %2 = fcmp uno %0, %1\n %3 = zext.i32 \
6244 %2\n return %3\n"
6245 );
6246
6247 let text = body("int f(double x, double y) { return __builtin_islessgreater(x, y); }\n");
6249 assert!(text.contains("fcmp one %0, %1"), "{text}");
6250
6251 let text = body("int f(double x) { return __builtin_isnan(x); }\n");
6252 assert!(text.contains("fcmp uno %0, %0"), "{text}");
6253
6254 let text = body("int f(double x) { return __builtin_isinf(x); }\n");
6255 assert!(text.contains("fconst.f64 0x7ff0000000000000"), "{text}");
6256 assert!(text.contains("fconst.f64 0xfff0000000000000"), "{text}");
6257 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
6258 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
6259 assert!(text.contains("%5 = or %3, %4"), "{text}");
6260
6261 let text = body("int f(double x) { return __builtin_isfinite(x); }\n");
6264 assert!(text.contains("%3 = fcmp olt %2, %0"), "{text}");
6265 assert!(text.contains("%4 = fcmp olt %0, %1"), "{text}");
6266 assert!(text.contains("%5 = and %3, %4"), "{text}");
6267
6268 let text = body("int f(double x) { return __builtin_signbit(x); }\n");
6269 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
6270 assert!(text.contains("icmp slt %1, %2"), "{text}");
6271
6272 let text = body("int f(long double x) { return __builtin_signbitl(x); }\n");
6275 assert!(text.contains("%1 = bitcast.i80 %0"), "{text}");
6276
6277 let text = body("double g(void);\nint f(void) { return __builtin_isnan(g()); }\n");
6280 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
6281 }
6282
6283 #[test]
6290 fn a_classification_spelling_that_names_a_width_converts_before_it_asks() {
6291 let text = ir(concat!(
6292 "int a = __builtin_isinff(1e300);\n",
6293 "int b = __builtin_isinf(1e300);\n",
6294 "int c = __builtin_isnan(0.0);\n",
6298 "int d = __builtin_signbit(-0.0);\n",
6299 "int e = __builtin_islessgreater(1.0, 2.0);\n",
6300 ));
6301 assert!(text.contains("global @a : i32 = 1,"), "{text}");
6302 assert!(text.contains("global @b : i32 = 0,"), "{text}");
6303 assert!(text.contains("global @c : i32 = 0,"), "{text}");
6304 assert!(text.contains("global @d : i32 = 1,"), "{text}");
6305 assert!(text.contains("global @e : i32 = 1,"), "{text}");
6306 }
6307
6308 #[test]
6310 fn a_classification_builtin_refuses_an_argument_that_is_not_floating_point() {
6311 let mut opts = options();
6312 opts.emit = EmitKind::Ir;
6313 let source = concat!(
6314 "int a(int x) { return __builtin_isnan(x); }\n",
6315 "int b(int x, int y) { return __builtin_isunordered(x, y); }\n",
6316 "int c(double x) { return __builtin_isnan(x, x); }\n",
6317 );
6318 let messages = run(&opts, source).messages;
6319 assert_eq!(
6320 messages,
6321 [
6322 "/main.c:1:23: error: non-floating-point argument in call to function \
6323 '__builtin_isnan' [E0685]",
6324 "/main.c:2:30: error: non-floating-point arguments in call to function \
6325 '__builtin_isunordered' [E0685]",
6326 "/main.c:3:26: error: too many arguments to function '__builtin_isnan' [E0511]",
6327 ]
6328 );
6329 }
6330
6331 #[test]
6340 fn the_last_three_classification_builtins_are_comparisons_and_not_calls() {
6341 let text = body("int f(double x) { return __builtin_isnormal(x); }\n");
6342 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
6346 assert!(text.contains("%2 = iconst.i64 9223372036854775807"), "{text}");
6347 assert!(text.contains("%3 = and %1, %2"), "{text}");
6348 assert!(text.contains("%4 = iconst.i64 4503599627370496"), "{text}");
6349 assert!(text.contains("%5 = iconst.i64 9218868437227405312"), "{text}");
6350 assert!(text.contains("%6 = icmp uge %3, %4"), "{text}");
6351 assert!(text.contains("%7 = icmp ult %3, %5"), "{text}");
6352 assert!(text.contains("%8 = and %6, %7"), "{text}");
6353
6354 let text = body("int f(long double x) { return __builtin_isnormal(x); }\n");
6358 assert!(text.contains("%4 = iconst.i80 27670116110564327424"), "{text}");
6359 assert!(text.contains("%5 = iconst.i80 604453686435277732577280"), "{text}");
6360
6361 let text = body("int f(double x) { return __builtin_isinf_sign(x); }\n");
6362 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
6363 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
6364 assert!(text.contains("%7 = sub %5, %6"), "{text}");
6365
6366 let text = body("int f(double x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n");
6367 assert!(text.contains("fcmp uno %0, %0"), "{text}");
6368 assert!(text.contains("fcmp oeq %0, %6"), "{text}");
6369 assert_eq!(text.matches(" = zext.i32 ").count(), 4, "{text}");
6373 assert_eq!(text.matches(" = xor ").count(), 4, "{text}");
6374 assert!(!text.contains("call"), "{text}");
6375
6376 let text = body(concat!(
6379 "double g(void);\n",
6380 "int f(void) { return __builtin_fpclassify(0, 1, 2, 3, 4, g()); }\n",
6381 ));
6382 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
6383 }
6384
6385 #[test]
6392 fn the_last_three_classification_builtins_fold_where_their_operand_is_a_constant() {
6393 let text = ir(concat!(
6394 "int a = __builtin_isnormal(1.0);\n",
6395 "int b = __builtin_isnormal(0.0);\n",
6396 "int c = __builtin_isnormal(1.0 / 0.0);\n",
6397 "int d = __builtin_isinf_sign(-1.0 / 0.0);\n",
6398 "int e = __builtin_isinf_sign(1.0);\n",
6399 "int g = __builtin_fpclassify(0, 1, 2, 3, 4, 0.0);\n",
6400 "int h = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0);\n",
6401 "int i = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0 / 0.0);\n",
6402 ));
6403 assert!(text.contains("global @a : i32 = 1,"), "{text}");
6404 assert!(text.contains("global @b : i32 = 0,"), "{text}");
6405 assert!(text.contains("global @c : i32 = 0,"), "{text}");
6406 assert!(text.contains("global @d : i32 = -1,"), "{text}");
6407 assert!(text.contains("global @e : i32 = 0,"), "{text}");
6408 assert!(text.contains("global @g : i32 = 4,"), "{text}");
6409 assert!(text.contains("global @h : i32 = 2,"), "{text}");
6410 assert!(text.contains("global @i : i32 = 1,"), "{text}");
6411 }
6412
6413 #[test]
6419 fn fpclassify_refuses_an_answer_that_is_not_an_integer_constant() {
6420 let mut opts = options();
6421 opts.emit = EmitKind::Ir;
6422 let source = concat!(
6423 "int a(double x, int n) { return __builtin_fpclassify(0, 1, n, 3, 4, x); }\n",
6424 "int b(double x) { return __builtin_fpclassify(0, 1, 2, 3, x); }\n",
6425 "int c(int x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n",
6426 );
6427 let messages = run(&opts, source).messages;
6428 assert_eq!(
6429 messages,
6430 [
6431 "/main.c:1:60: error: non-const integer argument 3 in call to function \
6432 '__builtin_fpclassify' [E0687]",
6433 "/main.c:2:26: error: too few arguments to function '__builtin_fpclassify' \
6434 [E0511]",
6435 "/main.c:3:23: error: non-floating-point argument in call to function \
6436 '__builtin_fpclassify' [E0685]",
6437 ]
6438 );
6439 }
6440
6441 #[test]
6449 fn a_builtin_whose_answer_is_a_constant_is_one_and_not_a_call() {
6450 let text = ir(concat!(
6451 "double a = __builtin_inf();\n",
6452 "float b = __builtin_huge_valf();\n",
6453 "long double c = __builtin_infl();\n",
6454 "double d = __builtin_huge_val();\n",
6455 ));
6456 assert!(text.contains("global @a : f64 = 0x7ff0000000000000,"), "{text}");
6457 assert!(text.contains("global @b : f32 = 0x7f800000,"), "{text}");
6458 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
6459 assert!(text.contains("global @d : f64 = 0x7ff0000000000000,"), "{text}");
6460 assert!(!text.contains("call"), "{text}");
6461 }
6462
6463 #[test]
6472 fn a_nan_is_written_with_the_payload_the_program_asked_for() {
6473 let text = ir(concat!(
6474 "double a = __builtin_nan(\"\");\n",
6475 "double b = __builtin_nan(\"0x1\");\n",
6476 "double c = __builtin_nan(\"010\");\n",
6478 "double d = __builtin_nans(\"\");\n",
6479 "double e = __builtin_nans(\"0x1\");\n",
6480 "float f = __builtin_nanf(\"0x1\");\n",
6481 "float g = __builtin_nansf(\"\");\n",
6482 "long double h = __builtin_nansl(\"\");\n",
6483 ));
6484 assert!(text.contains("global @a : f64 = 0x7ff8000000000000,"), "{text}");
6485 assert!(text.contains("global @b : f64 = 0x7ff8000000000001,"), "{text}");
6486 assert!(text.contains("global @c : f64 = 0x7ff8000000000008,"), "{text}");
6487 assert!(text.contains("global @d : f64 = 0x7ff4000000000000,"), "{text}");
6488 assert!(text.contains("global @e : f64 = 0x7ff0000000000001,"), "{text}");
6489 assert!(text.contains("global @f : f32 = 0x7fc00001,"), "{text}");
6490 assert!(text.contains("global @g : f32 = 0x7fa00000,"), "{text}");
6491 assert!(text.contains("f80 0x7fffa000000000000000"), "{text}");
6492
6493 let text = ir(concat!(
6496 "double f(const char *p) { return __builtin_nan(p); }\n",
6497 "double g(void) { return __builtin_nans(\"1x\"); }\n",
6498 ));
6499 assert_eq!(text.matches("call @nan(").count(), 1, "{text}");
6500 assert_eq!(text.matches("call @nans(").count(), 1, "{text}");
6501 }
6502
6503 #[test]
6511 fn the_length_and_the_order_of_a_string_literal_are_known_here() {
6512 let text = ir(concat!(
6513 "unsigned long a = __builtin_strlen(\"hello\");\n",
6514 "unsigned long b = __builtin_strlen(\"a\\0bc\");\n",
6515 "int c = __builtin_strcmp(\"X\", \"X\\376\") < 0;\n",
6516 "int d = __builtin_strcmp(\"abc\", \"abc\");\n",
6517 "int e = __builtin_strcmp(\"abc\", \"ab\") > 0;\n",
6518 ));
6519 assert!(text.contains("global @a : i64 = 5,"), "{text}");
6520 assert!(text.contains("global @b : i64 = 1,"), "{text}");
6521 assert!(text.contains("global @c : i32 = 1,"), "{text}");
6522 assert!(text.contains("global @d : i32 = 0,"), "{text}");
6523 assert!(text.contains("global @e : i32 = 1,"), "{text}");
6524 assert!(!text.contains("call"), "{text}");
6525
6526 let text = ir("unsigned long f(const char *p) { return __builtin_strlen(p); }\n");
6528 assert!(text.contains("call @strlen("), "{text}");
6529 }
6530
6531 #[test]
6538 fn a_sign_builtin_is_a_mask_over_the_bits_and_not_a_call() {
6539 let text = body("double f(double x) { return __builtin_fabs(x); }\n");
6540 assert!(text.contains("bitcast.i64 %0"), "{text}");
6541 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
6542 assert!(text.contains("and %1, %2"), "{text}");
6543 assert!(text.contains("bitcast.f64 %3"), "{text}");
6544 assert!(!text.contains("call"), "{text}");
6545
6546 let text = body("double f(double x, double y) { return __builtin_copysign(x, y); }\n");
6547 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
6548 assert!(text.contains("%8 = or %4, %7"), "{text}");
6549 assert!(!text.contains("call"), "{text}");
6550
6551 let text = body("long double f(long double x) { return __builtin_fabsl(x); }\n");
6554 assert!(text.contains("bitcast.i80 %0"), "{text}");
6555 assert!(text.contains("bitcast.f80"), "{text}");
6556
6557 let text = body("double f(float x) { return __builtin_fabs(x); }\n");
6560 assert!(text.contains("fpext.f64 %0"), "{text}");
6561 assert!(text.contains("bitcast.i64 %1"), "{text}");
6562 }
6563
6564 #[test]
6573 fn the_plain_math_names_are_the_same_mask_and_not_a_call() {
6574 let text =
6575 body(concat!("double fabs(double x);\n", "double f(double x) { return fabs(x); }\n",));
6576 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
6577 assert!(!text.contains("call"), "{text}");
6578
6579 let text =
6580 body(concat!("float fabsf(float x);\n", "float f(float x) { return fabsf(x); }\n",));
6581 assert!(text.contains("bitcast.i32 %0"), "{text}");
6582 assert!(!text.contains("call"), "{text}");
6583
6584 let text = body(concat!(
6585 "double copysign(double x, double y);\n",
6586 "double f(double x, double y) { return copysign(x, y); }\n",
6587 ));
6588 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
6589 assert!(!text.contains("call"), "{text}");
6590
6591 let text = body(concat!(
6592 "float copysignf(float x, float y);\n",
6593 "float f(float x, float y) { return copysignf(x, y); }\n",
6594 ));
6595 assert!(!text.contains("call"), "{text}");
6596
6597 let text = ir(concat!(
6601 "long double fabsl(long double x);\n",
6602 "long double f(long double x) { return fabsl(x); }\n",
6603 ));
6604 assert!(text.contains("call @fabsl"), "{text}");
6605 }
6606
6607 #[test]
6615 fn a_plain_math_name_the_program_took_is_the_programs_own_function() {
6616 let taken = concat!(
6617 "static double fabs(double b) { return 7; }\n",
6618 "double f(double x) { return fabs(x); }\n",
6619 );
6620 assert!(ir(taken).contains("call @fabs"), "a static definition is the program's own");
6621
6622 let retyped = concat!("int fabs(int b);\n", "int f(int x) { return fabs(x); }\n");
6623 assert!(ir(retyped).contains("call @fabs"), "another type is another function");
6624
6625 let plain = concat!("double fabs(double b);\n", "double f(double x) { return fabs(x); }\n");
6626 let mut opts = options();
6627 opts.emit = EmitKind::Ir;
6628 assert!(!run(&opts, plain).text().contains("call @fabs"), "the library's by default");
6629
6630 opts.builtins = false;
6631 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin");
6632
6633 opts.builtins = true;
6634 opts.no_builtin = vec!["fabs".to_owned()];
6635 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin-fabs");
6636 let one = concat!(
6637 "double copysign(double a, double b);\n",
6638 "double f(double x) { return copysign(x, 1.0); }\n",
6639 );
6640 assert!(!run(&opts, one).text().contains("call @copysign"), "one name and not the family");
6641
6642 opts.no_builtin = Vec::new();
6644 opts.builtins = false;
6645 let prefixed = "double f(double x) { return __builtin_fabs(x); }\n";
6646 assert!(!run(&opts, prefixed).text().contains("call @fabs"), "the prefix is not a library");
6647 }
6648
6649 #[test]
6658 fn the_sign_builtins_answer_a_zero_and_a_nan_the_way_the_bits_say() {
6659 let text = ir(concat!(
6660 "double a = __builtin_fabs(-3.5);\n",
6661 "double b = __builtin_copysign(1.0, -0.0);\n",
6662 "double c = __builtin_copysign(0.0, -2.0);\n",
6663 "double d = __builtin_copysign(-__builtin_nan(\"\"), 1.0);\n",
6665 "double e = __builtin_fabs(-__builtin_nan(\"0x1\"));\n",
6666 "float g = __builtin_copysignf(-0.0f, 2.0f);\n",
6667 "long double h = __builtin_copysignl(1.0L, -1.0L);\n",
6668 "long double i = __builtin_fabsl(-__builtin_infl());\n",
6669 ));
6670 assert!(text.contains("global @a : f64 = 0x400c000000000000,"), "{text}");
6671 assert!(text.contains("global @b : f64 = 0xbff0000000000000,"), "{text}");
6672 assert!(text.contains("global @c : f64 = 0x8000000000000000,"), "{text}");
6673 assert!(text.contains("global @d : f64 = 0x7ff8000000000000,"), "{text}");
6674 assert!(text.contains("global @e : f64 = 0x7ff8000000000001,"), "{text}");
6675 assert!(text.contains("global @g : f32 = 0x0,"), "{text}");
6676 assert!(text.contains("f80 0xbfff8000000000000000"), "{text}");
6677 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
6678 }
6679
6680 #[test]
6688 fn the_complex_builtins_are_the_halves_of_the_value_and_not_a_call() {
6689 let text = body("double f(_Complex double z) { return __builtin_creal(z); }\n");
6690 assert!(!text.contains("call"), "{text}");
6691 let text = body("double f(_Complex double z) { return __builtin_cimag(z); }\n");
6692 assert!(!text.contains("call"), "{text}");
6693
6694 let text = body("_Complex double f(_Complex double z) { return __builtin_conj(z); }\n");
6697 assert_eq!(text.matches("fneg").count(), 1, "{text}");
6698 assert!(!text.contains("call"), "{text}");
6699 let negated = body("_Complex double f(_Complex double z) { return -z; }\n");
6700 assert_eq!(negated.matches("fneg").count(), 2, "{negated}");
6701
6702 let written = body("_Complex double f(_Complex double z) { return ~z; }\n");
6705 assert_eq!(written, text, "the name and the operator are the same thing");
6706
6707 let text = body(concat!(
6709 "double creal(_Complex double z);\n",
6710 "double f(_Complex double z) { return creal(z); }\n",
6711 ));
6712 assert!(!text.contains("call"), "{text}");
6713 let text = body(concat!(
6714 "_Complex float conjf(_Complex float z);\n",
6715 "_Complex float f(_Complex float z) { return conjf(z); }\n",
6716 ));
6717 assert_eq!(text.matches("fneg").count(), 1, "{text}");
6718 assert!(!text.contains("call"), "{text}");
6719
6720 let taken = concat!(
6723 "static double creal(_Complex double z) { return 7; }\n",
6724 "double f(_Complex double z) { return creal(z); }\n",
6725 );
6726 assert!(ir(taken).contains("call @creal"), "a static definition is the program's own");
6727 let retyped = concat!("int cimag(int z);\n", "int f(int z) { return cimag(z); }\n");
6728 assert!(ir(retyped).contains("call @cimag"), "another type is another function");
6729 let plain = concat!(
6730 "double cimag(_Complex double z);\n",
6731 "double f(_Complex double z) { return cimag(z); }\n",
6732 );
6733 let mut opts = options();
6734 opts.emit = EmitKind::Ir;
6735 opts.builtins = false;
6736 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin");
6737 opts.builtins = true;
6738 opts.no_builtin = vec!["cimag".to_owned()];
6739 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin-cimag");
6740
6741 let text = ir(concat!(
6743 "double a = __builtin_creal(1.5 + 2.5i);\n",
6744 "double b = __builtin_cimag(1.5 + 2.5i);\n",
6745 "_Complex double c = __builtin_conj(1.5 + 2.5i);\n",
6746 ));
6747 assert!(text.contains("global @a : f64 = 0x3ff8000000000000,"), "{text}");
6748 assert!(text.contains("global @b : f64 = 0x4004000000000000,"), "{text}");
6749 assert!(
6750 text.contains("{ f64 0x3ff8000000000000, f64 0xc004000000000000 }"),
6751 "the conjugate of a constant is the constant with the second half negated: {text}"
6752 );
6753 assert!(!text.contains("call"), "{text}");
6754 }
6755
6756 #[test]
6764 fn a_math_library_builtin_of_a_constant_is_the_answer_and_not_a_call() {
6765 let text = ir(concat!(
6766 "double a = __builtin_ceil(1.5);\n",
6767 "double b = __builtin_floor(1.5);\n",
6768 "double c = __builtin_trunc(-1.5);\n",
6769 "double d = __builtin_round(2.5);\n",
6772 "double e = __builtin_ceil(-0.5);\n",
6774 "double f = __builtin_fmax(1.0, 2.0);\n",
6775 "double g = __builtin_fmin(1.0, 2.0);\n",
6776 "float h = __builtin_ceilf(1.25f);\n",
6777 "double ceil(double x);\n",
6780 "double i = ceil(2.25);\n",
6781 ));
6782 assert!(text.contains("global @a : f64 = 0x4000000000000000,"), "{text}");
6783 assert!(text.contains("global @b : f64 = 0x3ff0000000000000,"), "{text}");
6784 assert!(text.contains("global @c : f64 = 0xbff0000000000000,"), "{text}");
6785 assert!(text.contains("global @d : f64 = 0x4008000000000000,"), "{text}");
6786 assert!(text.contains("global @e : f64 = 0x8000000000000000,"), "{text}");
6787 assert!(text.contains("global @f : f64 = 0x4000000000000000,"), "{text}");
6788 assert!(text.contains("global @g : f64 = 0x3ff0000000000000,"), "{text}");
6789 assert!(text.contains("global @h : f32 = 0x40000000,"), "{text}");
6790 assert!(text.contains("global @i : f64 = 0x4008000000000000,"), "{text}");
6791 assert!(!text.contains("call"), "{text}");
6792 }
6793
6794 #[test]
6802 fn a_math_library_builtin_of_anything_else_is_a_call_to_the_library() {
6803 let text = ir(concat!(
6804 "double f(double x) { return __builtin_ceil(x); }\n",
6805 "float g(float x) { return __builtin_floorf(x); }\n",
6806 "double h(double x, double y) { return __builtin_fmax(x, y); }\n",
6807 ));
6808 assert!(text.contains("call @ceil("), "{text}");
6809 assert!(text.contains("call @floorf("), "{text}");
6810 assert!(text.contains("call @fmax("), "{text}");
6811
6812 let text = ir(concat!(
6816 "double f(void) { return __builtin_rint(2.5); }\n",
6817 "double g(void) { return __builtin_nearbyint(2.5); }\n",
6818 ));
6819 assert!(text.contains("call @rint("), "{text}");
6820 assert!(text.contains("call @nearbyint("), "{text}");
6821
6822 let text = ir("double f(void) { return __builtin_fmin(__builtin_nan(\"\"), 1.0); }\n");
6825 assert!(text.contains("call @fmin("), "{text}");
6826
6827 let plain = concat!("double ceil(double x);\n", "double f(void) { return ceil(2.25); }\n");
6830 let mut opts = options();
6831 opts.emit = EmitKind::Ir;
6832 opts.no_builtin = vec!["ceil".to_owned()];
6833 assert!(run(&opts, plain).text().contains("call @ceil("), "-fno-builtin-ceil");
6834 }
6835
6836 #[test]
6843 fn a_constexpr_object_is_a_constant_wherever_one_is_required() {
6844 let text = ir(concat!(
6845 "constexpr int side = 4;\n",
6846 "constexpr int wider = side + 1;\n",
6847 "constexpr double half = 1.5;\n",
6848 "struct point { int x; int y; };\n",
6849 "constexpr struct point origin = { 5, 6 };\n",
6850 "int square[side * side];\n",
6851 "int rectangle[wider];\n",
6852 "int rounded[(int)half * 2];\n",
6853 "int across[origin.y];\n",
6854 "enum named { four = side };\n",
6855 "int e = four;\n",
6856 ));
6857 assert!(text.contains("global @square : bytes 64 ="), "{text}");
6858 assert!(text.contains("global @rectangle : bytes 20 ="), "{text}");
6859 assert!(text.contains("global @rounded : bytes 8 ="), "{text}");
6860 assert!(text.contains("global @across : bytes 24 ="), "{text}");
6861 assert!(text.contains("global @e : i32 = 4,"), "{text}");
6862
6863 let mut opts = options();
6866 opts.emit = EmitKind::Ir;
6867 let konst = "const int n = 1;\nint a[n];\n";
6868 let message = "/main.c:2:5: error: variably modified 'a' at file scope [E0538]";
6869 assert_eq!(run(&opts, konst).messages, [message]);
6870
6871 let subscript = "constexpr int t[3] = { 1, 2, 3 };\nint a[t[1]];\n";
6873 assert_eq!(run(&opts, subscript).messages, [message]);
6874
6875 let address = "constexpr int c = 3;\nint *p = &c;\n";
6877 let warning = "/main.c:2:6: warning: initialization discards 'const' qualifier from \
6878 pointer target type [E0514]";
6879 assert_eq!(run(&opts, address).messages, [warning]);
6880 }
6881
6882 #[test]
6891 fn a_member_whose_size_was_refused_is_not_a_flexible_array_member() {
6892 let mut opts = options();
6893 opts.emit = EmitKind::Ir;
6894
6895 let alone = "int k;\nextern struct D { int a[k]; } ed;\n";
6896 let message = "/main.c:2:23: error: variably modified 'a' at file scope [E0538]";
6897 assert_eq!(run(&opts, alone).messages, [message]);
6898
6899 let first = "int k;\nextern struct E { int a[k]; int b; } ee;\n";
6901 assert_eq!(run(&opts, first).messages, [message]);
6902
6903 let negative = "struct F { int a[-1]; };\n";
6906 let refused = "/main.c:1:18: error: size of array 'a' is negative [E0536]";
6907 assert_eq!(run(&opts, negative).messages, [refused]);
6908
6909 let flexible = "struct G { int a[]; };\n";
6912 let named = "/main.c:1:16: error: flexible array member in a struct with no named \
6913 members [E0554]";
6914 assert_eq!(run(&opts, flexible).messages, [named]);
6915 }
6916
6917 #[test]
6932 fn a_pointer_to_an_array_gains_a_qualifier_the_same_way_a_pointer_to_anything_else_does() {
6933 let mut opts = options();
6934 opts.emit = EmitKind::Ir;
6935 let prefix = "typedef unsigned int B[4];\nstruct H { B category[2]; };\n";
6936
6937 let adding = format!("{prefix}const B *f(struct H *h) {{ return &h->category[0]; }}\n");
6939 assert_eq!(run(&opts, &adding).messages, [] as [String; 0]);
6940
6941 let plain = concat!(
6944 "const unsigned int (*f(unsigned int (*p)[4]))[4] { return p; }\n",
6945 "const unsigned int (*g(unsigned int (*p)[2][3]))[2][3] { return p; }\n",
6946 );
6947 assert_eq!(run(&opts, plain).messages, [] as [String; 0]);
6948
6949 let dropping = format!("{prefix}B *f(const B *p) {{ return p; }}\n");
6952 let warning = "/main.c:3:27: warning: return discards 'const' qualifier from pointer target type \
6953 [E0514]";
6954 assert_eq!(run(&opts, &dropping).messages, [warning]);
6955
6956 let wrong = "const unsigned int (*f(unsigned short (*p)[4]))[4] { return p; }\n";
6959 let error = "/main.c:1:61: error: returning 'unsigned short (*)[4]' from a function with \
6960 incompatible return type 'const unsigned int (*)[4]' [E0512]";
6961 assert_eq!(run(&opts, wrong).messages, [error]);
6962 }
6963
6964 #[test]
6973 fn an_old_style_definition_takes_its_types_from_the_declarations_under_its_list() {
6974 let mut opts = options();
6977 opts.std = Std::C17;
6978 let source = concat!(
6979 "int add(a, b)\n",
6980 "int a;\n",
6981 "int b;\n",
6982 "{ return a + b; }\n",
6983 "int promoted(c)\n",
6984 "char c;\n",
6985 "{ return c; }\n",
6986 "int narrow(char);\n",
6987 "int narrow(c)\n",
6988 "char c;\n",
6989 "{ return c; }\n",
6990 "int first(a)\n",
6991 "int a[4];\n",
6992 "{ return a[0]; }\n",
6993 );
6994 let result = run(&opts, source);
6995 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
6996 let text = result.text();
6997 assert!(text.contains("add : int(int, int) function external defined"), "{text}");
6998 assert!(text.contains("promoted : int(int) function external defined"), "{text}");
6999 assert!(text.contains("c : char object automatic defined"), "{text}");
7001 assert!(text.contains("narrow : int(char) function external defined"), "{text}");
7002 assert!(text.contains("first : int(int *) function external defined"), "{text}");
7004 }
7005
7006 #[test]
7013 fn the_two_halves_of_an_old_style_parameter_list_have_to_agree() {
7014 let mut opts = options();
7015 opts.std = Std::C17;
7016 for (source, message) in [
7017 ("int f(a, a)\nint a;\n{ return a; }\n", "1:10: error: multiple parameters named 'a'"),
7018 (
7019 "int f(a)\nint a;\nint b;\n{ return a; }\n",
7020 "3:5: error: declaration for parameter 'b' but no such parameter",
7021 ),
7022 ("int f(a)\nint a;\nint a;\n{ return a; }\n", "3:5: error: redefinition of parameter"),
7023 ("int f(a)\nint a = 1;\n{ return a; }\n", "2:5: error: parameter 'a' is initialized"),
7024 (
7025 "int f(a)\nstatic int a;\n{ return a; }\n",
7026 "2:12: error: storage class specified for parameter 'a'",
7027 ),
7028 (
7029 "int f(char);\nint f(a)\nshort a;\n{ return a; }\n",
7030 "2:7: error: argument 'a' doesn't match prototype",
7031 ),
7032 ] {
7033 let result = run(&opts, source);
7034 assert!(result.failed(), "expected this to fail:\n{source}");
7035 assert!(result.messages[0].contains(message), "{:?}", result.messages);
7036 }
7037
7038 let implicit = "int f(a, b)\nint a;\n{ return a + b; }\n";
7041 let mut older = options();
7042 older.std = Std::C89;
7043 assert!(!run(&older, implicit).failed(), "{:?}", run(&older, implicit).messages);
7044 let result = run(&opts, implicit);
7045 assert!(
7046 result.messages[0].contains("1:10: error: type of 'b' defaults to 'int'"),
7047 "{:?}",
7048 result.messages
7049 );
7050
7051 let mut newer = options();
7055 newer.std = Std::C23;
7056 let plain = "int f(a)\nint a;\n{ return a; }\n";
7057 let result = run(&newer, plain);
7058 assert!(!result.failed(), "{:?}", result.messages);
7059 assert_eq!(
7060 result.messages,
7061 ["/main.c:1:5: warning: old-style function definition [E0412]"]
7062 );
7063 assert!(run(&opts, plain).messages.is_empty(), "and nothing to say in the dialects before");
7064 }
7065
7066 #[test]
7073 fn the_obsolete_designators_are_taken_and_are_pedantic_warnings() {
7074 let array = "int a[8] = { [3] 7 };\n";
7075 let member = "struct s { int x; } v = { x: 7 };\n";
7076 for source in [array, member] {
7077 let result = run(&options(), source);
7078 assert!(!result.failed(), "{:?}", result.messages);
7079 assert!(result.messages.is_empty(), "nothing to say: {:?}", result.messages);
7080 }
7081
7082 let mut asked = options();
7083 asked.pedantic = true;
7084 assert_eq!(
7085 run(&asked, array).messages,
7086 ["/main.c:1:18: warning: obsolete designator, write `[i] =` instead [E0415]"]
7087 );
7088 assert_eq!(
7089 run(&asked, member).messages,
7090 ["/main.c:1:27: warning: obsolete designator, write `.field =` instead [E0413]"]
7091 );
7092 }
7093
7094 #[test]
7101 fn a_type_is_refused_when_it_passes_the_largest_object_and_not_before() {
7102 let text = ir(concat!(
7103 "struct huge_struct { short buf[(1L << 62) - 256]; int a, b, c, d; };\n",
7104 "struct brim { char buf[9223372036854775807L]; };\n",
7105 "struct bitty { char buf[9223372036854775800L]; int x : 1; };\n",
7106 "unsigned long h = sizeof(struct huge_struct);\n",
7107 "unsigned long b = sizeof(struct brim);\n",
7108 "unsigned long y = sizeof(struct bitty);\n",
7109 ));
7110 assert!(text.contains("global @h : i64 = 9223372036854775312,"), "{text}");
7111 assert!(text.contains("global @b : i64 = 9223372036854775807,"), "{text}");
7112 assert!(text.contains("global @y : i64 = 9223372036854775804,"), "{text}");
7113
7114 let mut opts = options();
7115 opts.emit = EmitKind::Ir;
7116 let over = "struct over { char buf[9223372036854775800L]; char x[8]; };\n";
7117 let message = "/main.c:1:1: error: type 'struct over' is too large [E0560]";
7118 assert_eq!(run(&opts, over).messages, [message]);
7119 let array = "struct wide { short buf[1L << 62]; };\n";
7120 let message = "/main.c:1:25: error: size of array 'buf' exceeds \
7121 maximum object size '9223372036854775807' [E0537]";
7122 assert_eq!(run(&opts, array).messages[0], message);
7123 }
7124
7125 fn compile_bytes(source: &[u8]) -> Compiled {
7130 let mut opts = options();
7131 opts.emit = EmitKind::Ir;
7132 let mut fs = MemoryFileSystem::new();
7133 fs.insert("/main.c", source.to_vec());
7134 compile(&opts, "/main.c", &fs)
7135 }
7136
7137 #[test]
7144 fn a_byte_that_is_not_a_character_is_kept_in_a_literal_and_refused_outside_one() {
7145 let mut source = b"char s[] = \"a".to_vec();
7146 source.push(0xff);
7147 source.extend_from_slice(b"b\";\nchar c = '");
7148 source.push(0xff);
7149 source.extend_from_slice(b"';\n");
7150 let result = compile_bytes(&source);
7151 assert_eq!(result.messages, Vec::<String>::new(), "a raw byte in a literal is that byte");
7152 assert!(result.text().contains(r#"bytes "a\ffb\00""#), "{}", result.text());
7153 assert!(result.text().contains("global @c : i8 = -1,"), "{}", result.text());
7155
7156 let mut stray = b"int a".to_vec();
7157 stray.push(0xff);
7158 stray.extend_from_slice(b" = 1;\n");
7159 let result = compile_bytes(&stray);
7160 assert!(
7161 result.messages.iter().any(|m| m.contains("source is not valid UTF-8 here")),
7162 "{:?}",
7163 result.messages
7164 );
7165 }
7166
7167 #[test]
7168 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
7169 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
7170 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
7171 let expected = "\
7172func @add(i32, i32) -> i32, linkage(external) {
7173block0(%0: i32, %1: i32):
7174 %2 = add.nsw %0, %1
7175 return %2
7176}
7177";
7178 assert!(text.contains(expected), "{text}");
7179 }
7180
7181 #[test]
7182 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
7183 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
7184 assert!(!text.contains("alloca"), "{text}");
7185 assert!(!text.contains("load"), "{text}");
7186 assert!(!text.contains("store"), "{text}");
7187 }
7188
7189 #[test]
7190 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
7191 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
7192 let expected = "\
7193block0:
7194 %0 = alloca, size 4, align 4
7195 %1 = iconst.i32 1
7196 store %1 -> %0, align 4, tbaa !1
7197 %2 = call @g(%0) : (ptr) -> i32
7198 return %2
7199";
7200 assert_eq!(text, expected);
7201 }
7202
7203 #[test]
7204 fn a_loop_carries_what_it_changes_as_block_parameters() {
7205 let text = body(
7208 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
7209 return total;\n}\n",
7210 );
7211 assert!(!text.contains("alloca"), "{text}");
7212 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
7213 assert!(text.contains("jump block1("), "{text}");
7214 }
7215
7216 #[test]
7217 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
7218 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
7219 assert!(text.contains("icmp slt %0, %1"), "{text}");
7220 assert!(!text.contains("zext"), "{text}");
7221 }
7222
7223 #[test]
7224 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
7225 let text = body("int f(int a, int b) { return a && b; }\n");
7226 let expected = "\
7227block0(%0: i32, %1: i32):
7228 %2 = iconst.i32 0
7229 %3 = icmp ne %0, %2
7230 %4 = iconst.i1 0
7231 br_if %3, block1, block2(%4)
7232
7233block1:
7234 %5 = iconst.i32 0
7235 %6 = icmp ne %1, %5
7236 jump block2(%6)
7237
7238block2(%7: i1):
7239 %8 = zext.i32 %7
7240 return %8
7241";
7242 assert_eq!(text, expected);
7243 }
7244
7245 #[test]
7246 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
7247 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
7248 assert!(!text.contains("block3"), "{text}");
7251 assert!(!text.contains("iconst.i32 3"), "{text}");
7252 }
7253
7254 #[test]
7255 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
7256 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
7257 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
7258 assert!(body("int f(void) { }\n").contains("unreachable"));
7259 }
7260
7261 #[test]
7262 fn a_structure_is_copied_rather_than_held_in_a_value() {
7263 let text = body(
7264 "struct point { int x, y; };\n\
7265 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
7266 );
7267 assert!(text.contains("memcpy"), "{text}");
7268 }
7269
7270 #[test]
7271 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
7272 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
7273 assert!(text.contains("memset"), "{text}");
7274 }
7275
7276 #[test]
7277 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
7278 let text = body(
7279 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
7280 default: r = 4; } return r; }\n",
7281 );
7282 let expected = "\
7283block0(%0: i32):
7284 %1 = iconst.i32 0
7285 switch %0, block1, [1 => block2, 2 => block3(%1)]
7286
7287block1:
7288 %2 = iconst.i32 4
7289 jump block4(%2)
7290
7291block2:
7292 %3 = iconst.i32 1
7293 jump block3(%3)
7294
7295block3(%4: i32):
7296 %5 = iconst.i32 2
7297 %6 = add.nsw %4, %5
7298 jump block4(%6)
7299
7300block4(%7: i32):
7301 return %7
7302";
7303 assert_eq!(text, expected);
7304 }
7305
7306 #[test]
7307 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
7308 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
7311 assert!(text.contains("%2 = sub %0, %1"), "{text}");
7312 assert!(text.contains("icmp ule"), "{text}");
7313 assert!(!text.contains("switch"), "{text}");
7314 }
7315
7316 #[test]
7317 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
7318 let text = body(
7319 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
7320 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
7321 );
7322 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
7325 assert!(text.contains("block5:\n jump block7("), "{text}");
7326 assert!(text.contains("block6:\n jump block8("), "{text}");
7327 }
7328
7329 #[test]
7330 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
7331 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
7332 }
7333
7334 #[test]
7335 fn a_label_a_loop_is_only_entered_through_builds_the_loop_around_it() {
7336 let text = body(
7341 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
7342 return n; }\n",
7343 );
7344 assert!(text.contains("switch %0, block1(%1), [1 => block2, 2 => block3(%1)]"), "{text}");
7347 assert!(text.contains("block3(%3: i32):\n %4 = iconst.i32 1"), "{text}");
7348 assert!(text.contains("block4:\n jump block3("), "{text}");
7349 }
7350
7351 #[test]
7352 fn a_goto_into_a_loop_body_enters_it_without_the_test() {
7353 let text = body("int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n");
7356 assert!(text.starts_with("block0(%0: i32, %1: i32):\n jump block1(%1)"), "{text}");
7357 assert!(text.contains("block1(%2: i32):\n %3 = iconst.i32 1"), "{text}");
7358 assert!(text.contains("br_if %6, block2, block3"), "{text}");
7359 }
7360
7361 #[test]
7362 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
7363 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
7364 assert!(!text.contains("alloca"), "{text}");
7368 assert!(text.contains("block2(%4: i32):\n return %4"), "{text}");
7369 assert_eq!(text.matches("jump block2(").count(), 2, "{text}");
7370 }
7371
7372 #[test]
7373 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
7374 let text =
7375 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
7376 assert!(!text.contains("alloca"), "{text}");
7377 assert!(text.contains("block1(%2: i32):"), "{text}");
7378 assert!(text.contains("jump block1(%5)"), "{text}");
7379 }
7380
7381 #[test]
7382 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
7383 assert_eq!(
7386 body("int f(int x) { return x; spare: return 0; }\n"),
7387 "block0(%0: i32):\n return %0\n"
7388 );
7389 }
7390
7391 #[test]
7392 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
7393 let text = body(
7394 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
7395 );
7396 assert_eq!(
7399 text,
7400 "\
7401block0(%0: ptr):
7402 %1 = load.i8 %0, align 1
7403 %2 = iconst.i8 3
7404 %3 = ashr %1, %2
7405 %4 = sext.i32 %3
7406 return %4
7407"
7408 );
7409 }
7410
7411 #[test]
7412 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
7413 let text =
7417 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
7418 assert_eq!(
7419 text,
7420 "\
7421block0(%0: ptr, %1: i32):
7422 %2 = iconst.i32 16777215
7423 %3 = and %1, %2
7424 %4 = trunc.i16 %3
7425 store %4 -> %0, align 2
7426 %5 = iconst.i32 16
7427 %6 = lshr %3, %5
7428 %7 = trunc.i8 %6
7429 %8 = iconst.i64 2
7430 %9 = ptr_add %0, %8
7431 store %7 -> %9, align 1
7432 return
7433"
7434 );
7435 }
7436
7437 #[test]
7438 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
7439 let text =
7440 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
7441 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
7444 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
7445 }
7446
7447 #[test]
7448 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
7449 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
7452 assert_eq!(text.matches("ashr").count(), 0, "{text}");
7453 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
7454 }
7455
7456 #[test]
7457 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
7458 let text = body(
7462 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
7463 );
7464 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
7465 }
7466
7467 #[test]
7468 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
7469 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
7472 assert!(
7473 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
7474 "{text}"
7475 );
7476 }
7477
7478 #[test]
7479 fn an_initialized_flexible_array_member_makes_the_object_larger_than_its_type() {
7480 let text = ir(concat!(
7485 "struct a { int i; int j[]; } x = { 1, { 2, 0, 2, 3 } };\n",
7486 "struct b { char c; char p[]; } y = { 'o', \"wx\" };\n",
7487 "struct c { char c; char p[]; } z = { '9', { 'e', 'b' } };\n",
7488 "char s[2] = \"hi\";\n",
7489 ));
7490 assert!(
7491 text.contains("global @x : bytes 20 = { i32 1, i32 2, i32 0, i32 2, i32 3 }"),
7492 "{text}"
7493 );
7494 assert!(text.contains("global @y : bytes 4 = { i8 111, bytes \"wx\\00\" }"), "{text}");
7495 assert!(text.contains("global @z : bytes 3 = { i8 57, i8 101, i8 98 }"), "{text}");
7496 assert!(text.contains("global @s : bytes 2 = { bytes \"hi\" }"), "{text}");
7499 }
7500
7501 #[test]
7502 fn a_definition_takes_a_parameter_it_left_unnamed() {
7503 let text = ir("int f(int a, int) { return a; }\n");
7507 assert!(text.contains("func @f(i32, i32) -> i32"), "{text}");
7508 assert!(text.contains("block0(%0: i32, %1: i32):"), "{text}");
7509
7510 let text = ir("int g(int, int n) { return n; }\n");
7513 assert!(text.contains("block0(%0: i32, %1: i32):\n return %1\n"), "{text}");
7514 }
7515
7516 #[test]
7517 fn an_assignment_of_a_structure_is_the_object_it_wrote() {
7518 let text = body(concat!(
7523 "struct s { int f; int g; };\n",
7524 "void h(struct s *a, struct s *c, struct s *d, struct s *e)\n",
7525 "{ *d = *e = a[0] = *c; }\n",
7526 ));
7527 assert_eq!(text.matches("memcpy").count(), 3, "{text}");
7528 assert!(text.contains("memcpy %8, %1, size 8, align 4\n"), "{text}");
7529 assert!(text.contains("memcpy %3, %8, size 8, align 4\n"), "{text}");
7530 assert!(text.contains("memcpy %2, %3, size 8, align 4\n"), "{text}");
7531 }
7532
7533 #[test]
7534 fn a_string_literal_stops_at_the_end_of_the_array_it_is_filling() {
7535 let mut opts = options();
7540 opts.emit = EmitKind::Ir;
7541 let result = run(
7542 &opts,
7543 concat!(
7544 "const char a[2][3] = { \"1234\", \"xyz\" };\n",
7545 "static const char b[3][5] = { \"12345\", \"678\", \"9\" };\n",
7546 "union u { struct { char x[4]; char y[4]; }; struct { char z[8]; }; };\n",
7547 "const union u c = { { \"1234\", \"567\" } };\n",
7548 ),
7549 );
7550 let text = result.text();
7551 assert_eq!(
7552 result.messages,
7553 ["/main.c:1:24: warning: initializer-string for array of 'const char' is too long \
7554 (5 chars into 3 available) [E0637]"]
7555 );
7556 assert!(text.contains("global @a : bytes 6 = { bytes \"123\", bytes \"xyz\" }"), "{text}");
7557 assert!(
7558 text.contains(
7559 "global @b : bytes 15 = { bytes \"12345\", bytes \"678\\00\", zero 1, \
7560 bytes \"9\\00\", zero 3 }"
7561 ),
7562 "{text}"
7563 );
7564 assert!(
7567 text.contains("global @c : bytes 8 = { bytes \"1234\", bytes \"567\\00\" }"),
7568 "{text}"
7569 );
7570 }
7571
7572 #[test]
7573 fn a_cast_of_a_record_to_its_own_type_is_the_object_that_was_cast() {
7574 let text = body(concat!(
7578 "struct s { int a, b; };\nstruct v { struct s s; int t; };\n",
7579 "void g(struct v *);\n",
7580 "void f(struct s *p) { struct v w = { (struct s)*p, 5 }; g(&w); }\n",
7581 ));
7582 assert_eq!(text.matches("memcpy").count(), 1, "{text}");
7583 }
7584
7585 #[test]
7586 fn a_compound_literal_read_in_a_static_initializer_lays_its_bytes_into_the_image() {
7587 let text = ir(concat!(
7592 "struct s { int x; };\n",
7593 "struct t { struct s s; int o; } a = { (struct s){ 2 }, 3 };\n",
7594 "int n = (int){ 7 };\n",
7595 "struct u { struct s p; struct s q; } b = { (struct s){ 1 }, (struct s){ } };\n",
7596 ));
7597 assert!(text.contains("global @a : bytes 8 = { i32 2, i32 3 }"), "{text}");
7598 assert!(text.contains("global @n : i32 = 7,"), "{text}");
7599 assert!(text.contains("global @b : bytes 8 = { i32 1, zero 4 }"), "{text}");
7602 }
7603
7604 #[test]
7605 fn the_address_of_a_compound_literal_asks_for_the_object_it_points_at() {
7606 let text = ir("struct s { int x; };\nstruct s *q = &(struct s){ 9 };\n");
7610 assert!(text.contains("global @.Lanon.0 : i32 = 9, align 4, linkage(internal)"), "{text}");
7611 assert!(text.contains("global @q : bytes 8 = { addr.8 @.Lanon.0 }"), "{text}");
7612 }
7613
7614 #[test]
7615 fn an_object_of_no_size_at_all_has_an_image_with_nothing_in_it() {
7616 let text = ir("unsigned char foo[1][0];\n");
7620 assert!(text.contains("global @foo : bytes 0 = {}, align 1"), "{text}");
7621 }
7622
7623 #[test]
7624 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
7625 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
7628 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
7629 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
7630 }
7631
7632 #[test]
7633 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
7634 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
7638 assert!(
7639 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
7640 "{text}"
7641 );
7642 }
7643
7644 #[test]
7645 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
7646 let text = body(
7651 "\
7652struct s { int a, b; };
7653struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
7654",
7655 );
7656 assert!(text.contains("block3(%7: ptr)"), "{text}");
7658 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
7659 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
7660 }
7661
7662 #[test]
7670 fn the_left_side_of_a_conditional_with_no_middle_is_evaluated_once() {
7671 let text = body("int f(int i) { return ++i ?: 10; }\n");
7672 assert!(text.contains("jump block3(%2)"), "the arm is the value that was tested: {text}");
7673 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
7674
7675 let text = body("long f(int i) { return ++i ?: 10L; }\n");
7678 assert!(text.contains("%5 = sext.i64 %2"), "the arm widens what was tested: {text}");
7679 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
7680
7681 let text = body("int g(void);\nint f(void) { return g() ?: 10; }\n");
7683 assert_eq!(text.matches("call @g").count(), 1, "called once: {text}");
7684
7685 let text = body("int f(int i) { return ++i ? ++i : 10; }\n");
7688 assert_eq!(text.matches("add.nsw").count(), 2, "incremented twice: {text}");
7689 }
7690
7691 #[test]
7692 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
7693 let text = ir("\
7697struct pair { int a, b; };
7698struct pair make(int a, int b);
7699struct pair twice(struct pair p) { return make(p.a, p.b); }
7700");
7701 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
7702 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
7703 }
7704
7705 #[test]
7706 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
7707 let text = ir("\
7711struct big { double v[8]; };
7712struct big grow(struct big b);
7713struct big twice(struct big b) { return grow(grow(b)); }
7714");
7715 assert!(
7716 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
7717 "{text}"
7718 );
7719 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
7720 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
7723 }
7724
7725 #[test]
7726 fn a_structure_passed_to_a_variadic_function_says_so_at_the_call() {
7727 let text = ir("\
7732struct big { double v[8]; };
7733struct pair { int a, b; };
7734int p(const char *, ...);
7735int f(struct big b, struct pair q) { return p(\"\", 1, b, q); }
7736");
7737 assert!(
7738 text.contains("call @p(%4, %5, %2 byval(64, align 8), %6) : (ptr, ...) -> i32"),
7739 "{text}"
7740 );
7741 }
7742
7743 #[test]
7744 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
7745 let body = body(
7748 "\
7749struct pair { int a, b; };
7750struct pair make(int a, int b);
7751int second(void) { return make(1, 2).b; }
7752",
7753 );
7754 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
7755 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
7756 }
7757
7758 #[test]
7759 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
7760 let source = "\
7764struct hfa { float x, y, z; };
7765int take(struct hfa h);
7766int give(struct hfa h) { return take(h); }
7767";
7768 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
7769 let mut opts = options();
7770 opts.emit = EmitKind::Ir;
7771 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
7772 let result = run(&opts, source);
7773 assert_eq!(result.messages, Vec::<String>::new());
7774 assert!(result.text().contains("func @take(f32, f32, f32) -> i32"), "{}", result.text());
7775 }
7776
7777 #[test]
7778 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
7779 let source = "\
7782int use(int *);
7783void f(int n) {
7784 {
7785 int a[n];
7786 use(a);
7787 }
7788 use(0);
7789}
7790";
7791 let body = body(source);
7792 assert!(body.contains("mul.nsw"), "{body}");
7793 assert!(body.contains("stacksave"), "{body}");
7794 assert!(body.contains("alloca %"), "{body}");
7795 assert!(body.contains("stackrestore"), "{body}");
7796 }
7797
7798 #[test]
7799 fn a_goto_out_of_the_scope_of_one_gives_its_stack_back_on_the_way() {
7800 let source = "\
7805int use(int *);
7806int f(int n) {
7807 {
7808 int a[n];
7809 if (use(a)) goto out;
7810 use(0);
7811 }
7812out:
7813 return 0;
7814}
7815";
7816 let body = body(source);
7817 assert_eq!(body.matches("stackrestore").count(), 2, "{body}");
7819 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
7820 assert!(after.starts_with(" %4\n jump block"), "{body}");
7821 }
7822
7823 #[test]
7824 fn a_goto_to_a_label_the_array_is_still_alive_at_leaves_the_stack_alone() {
7825 let source = "\
7829int use(int *);
7830int f(int n) {
7831 int a[n];
7832again:
7833 if (use(a)) goto again;
7834 return 0;
7835}
7836";
7837 let body = body(source);
7838 assert!(body.contains("stacksave"), "{body}");
7839 assert!(!body.contains("stackrestore"), "{body}");
7840 }
7841
7842 #[test]
7843 fn a_goto_back_to_a_label_in_front_of_one_gives_it_back_every_time_round() {
7844 let source = "\
7849int use(int *);
7850int f(int n) {
7851again:
7852 {
7853 int a[n];
7854 if (use(a)) goto again;
7855 }
7856 return 0;
7857}
7858";
7859 let body = body(source);
7860 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
7861 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
7862 assert!(after.starts_with(" %4\n jump block1\n"), "{body}");
7863 }
7864
7865 #[test]
7866 fn the_head_of_a_for_loop_is_a_scope_that_closes_where_the_loop_is_left() {
7867 let source = "\
7873int f(void);
7874void t(void) {
7875 int count = 10;
7876 for (; count--;) {
7877 int b[f()];
7878 int i;
7879 for (i = 0; i < f(); i++) {
7880 b[i] = count;
7881 }
7882 }
7883}
7884";
7885 let body = body(source);
7886 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
7890 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
7891 let next = after.split("\n\n").next().expect("the block the restore is in");
7894 assert!(next.contains("jump block1("), "{body}");
7895 }
7896
7897 #[test]
7898 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
7899 let source = "\
7902unsigned long f(int n) {
7903 int a[n];
7904 n = 0;
7905 return sizeof a;
7906}
7907";
7908 let body = body(source);
7909 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
7911 }
7912
7913 #[test]
7914 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
7915 let source = "\
7918int use(int);
7919int f(int x) {
7920 return ({
7921 int t = use(x);
7922 t * t;
7923 });
7924}
7925";
7926 let expected = "\
7927block0(%0: i32):
7928 %1 = call @use(%0) : (i32) -> i32
7929 %2 = mul.nsw %1, %1
7930 return %2
7931";
7932 assert_eq!(body(source), expected);
7933 }
7934
7935 #[test]
7936 fn a_comma_whose_value_is_an_object_names_the_object_the_right_side_named() {
7937 let source = "\
7941struct pair { int a, b; };
7942void bail(void);
7943int f(struct pair p) {
7944 return (bail(), p).b;
7945}
7946";
7947 let expected = "\
7948block0(%0: i64):
7949 %1 = alloca, size 8, align 4
7950 store %0 -> %1, align 4
7951 call @bail() : ()
7952 %2 = iconst.i64 4
7953 %3 = ptr_add %1, %2
7954 %4 = load.i32 %3, align 4, tbaa !1
7955 return %4
7956";
7957 assert_eq!(body(source), expected);
7958 }
7959
7960 #[test]
7961 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
7962 let source = "int f(int x) { return ({ return x; 0; }); }\n";
7966 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
7967 }
7968
7969 #[test]
7970 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
7971 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
7975 let expected = "\
7976block0(%0: ptr):
7977 %1 = va_arg.f64 %0
7978 %2 = va_arg.f64 %0
7979 %3 = fadd %1, %2
7980 return %3
7981";
7982 assert_eq!(body(source), expected);
7983 }
7984
7985 #[test]
7986 fn one_that_reads_a_structure_answers_where_the_object_is() {
7987 let source = "\
8001struct s { int a; long b; };
8002long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.b; }
8003";
8004 let expected = "\
8005block0(%0: ptr):
8006 %1 = alloca, size 16, align 16
8007 %2 = va_object %0, size 16, align 8, in(int 8 at 0, int 8 at 8)
8008 memcpy %1, %2, size 16, align 8
8009 %3 = iconst.i64 8
8010 %4 = ptr_add %1, %3
8011 %5 = load.i64 %4, align 8, tbaa !1
8012 return %5
8013";
8014 assert_eq!(body(source), expected);
8015 }
8016
8017 #[test]
8021 fn the_classification_says_which_registers_the_object_arrived_in() {
8022 let source = "\
8023struct s { double a; double b; };
8024double f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a; }
8025";
8026 assert!(
8027 body(source)
8028 .contains("va_object %0, size 16, align 8, in(float f64 at 0, float f64 at 8)"),
8029 "{}",
8030 body(source)
8031 );
8032
8033 let big = "\
8034struct s { long a[4]; };
8035long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a[0]; }
8036";
8037 assert!(body(big).contains("va_object %0, size 32, align 8\n"), "{}", body(big));
8038 }
8039
8040 #[test]
8041 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
8042 let source = "\
8046int f(int c) {
8047 void *p = c ? &&one : &&two;
8048 goto *p;
8049one:
8050 return 1;
8051two:
8052 return 2;
8053}
8054";
8055 let expected = "\
8056block0(%0: i32):
8057 %1 = iconst.i32 0
8058 %2 = icmp ne %0, %1
8059 br_if %2, block1, block2
8060
8061block1:
8062 %3 = block_addr block3
8063 jump block4(%3)
8064
8065block2:
8066 %4 = block_addr block5
8067 jump block4(%4)
8068
8069block3:
8070 %5 = iconst.i32 1
8071 return %5
8072
8073block4(%6: ptr):
8074 indirect_br %6, block3, block5
8075
8076block5:
8077 %7 = iconst.i32 2
8078 return %7
8079";
8080 assert_eq!(body(source), expected);
8081 }
8082
8083 fn dispatch(labels: usize) -> String {
8086 let mask = labels - 1;
8087 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
8088 for index in 0..labels {
8089 source.push_str(&format!(" &&a{index},"));
8090 }
8091 source.push_str(" };\n\tint w = n, x = n + 1, y = n + 2, z = n + 3;\n");
8092 source.push_str(&format!("\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
8093 for index in 0..labels {
8094 let step = match index % 4 {
8095 0 => "w += x;",
8096 1 => "x += y;",
8097 2 => "y += z;",
8098 _ => "z += w;",
8099 };
8100 source.push_str(&format!("a{index}:\n\t{step}\n"));
8101 source.push_str("\tif (--n <= 0) return w + x + y + z;\n");
8102 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
8103 }
8104 source.push_str("}\n");
8105 source
8106 }
8107
8108 fn in_front_of_the_jump(text: &str) -> usize {
8110 let (before, _) = text.split_once("\tjmp\t*%").expect("a jump through a register");
8111 before.lines().rev().take_while(|line| line.starts_with("\tmov")).count()
8112 }
8113
8114 #[test]
8124 fn a_jump_through_a_register_writes_what_it_carries_and_not_the_whole_table() {
8125 let small = in_front_of_the_jump(&asm(&dispatch(4)));
8126 let large = in_front_of_the_jump(&asm(&dispatch(32)));
8127 assert_eq!(small, large, "eight times the labels and the same values in hand");
8128 assert!(large <= 8, "the values the loop keeps, and not a set of them per label: {large}");
8129 }
8130
8131 fn crowded(labels: usize) -> String {
8134 const VALUES: usize = 24;
8135 let mask = labels - 1;
8136 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
8137 for index in 0..labels {
8138 source.push_str(&format!(" &&a{index},"));
8139 }
8140 source.push_str(" };\n\t");
8141 for value in 0..VALUES {
8142 source.push_str(&format!("int v{value} = n + {value}; "));
8143 }
8144 let sum: Vec<String> = (0..VALUES).map(|value| format!("v{value}")).collect();
8145 source.push_str(&format!("\n\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
8146 for index in 0..labels {
8147 let (to, from) = (index % VALUES, (index + 1) % VALUES);
8148 source.push_str(&format!("a{index}:\n\tv{to} += v{from};\n"));
8149 source.push_str(&format!("\tif (--n <= 0) return {};\n", sum.join(" + ")));
8150 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
8151 }
8152 source.push_str("}\n");
8153 source
8154 }
8155
8156 fn the_frame(text: &str) -> u64 {
8158 text.lines()
8159 .find_map(|line| {
8160 let (size, _) = line.strip_prefix("\tsubq\t$")?.split_once(", %rsp")?;
8161 size.parse().ok()
8162 })
8163 .expect("a function that opens a frame")
8164 }
8165
8166 #[test]
8175 fn a_frame_holds_what_is_wanted_at_once_and_not_a_slot_for_every_label() {
8176 let small = the_frame(&asm(&crowded(16)));
8177 let large = the_frame(&asm(&crowded(64)));
8178 assert_eq!(small, large, "four times the labels and the same values: {small}, {large}");
8179 }
8180
8181 #[test]
8190 fn a_template_that_names_its_own_registers_gets_the_ones_it_named() {
8191 let source = "void save(void *nlr) {
8192 __asm volatile (
8193 \"movq %%rsp, 32(%%rdi) \\n\"
8194 \"movq %%rbx, 40(%%rdi) \\n\"
8195 \"movq %%r12, 48(%%rdi) \\n\"
8196 : : \"D\" (nlr) : \"memory\");
8197}
8198";
8199 let text = asm(source);
8200 assert!(text.contains("\tmovq\t%rsp, 32(%rdi)\n"), "{text}");
8201 assert!(text.contains("\tmovq\t%rbx, 40(%rdi)\n"), "{text}");
8202 assert!(text.contains("\tmovq\t%r12, 48(%rdi)\n"), "{text}");
8203 }
8204
8205 #[test]
8206 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
8207 let source = "void **next(void);
8210void f(void) { goto *next(); }
8211";
8212 let expected = "\
8213block0:
8214 %0 = call @next() : () -> ptr
8215 unreachable
8216";
8217 assert_eq!(body(source), expected);
8218 }
8219
8220 #[test]
8221 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
8222 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
8225 let expected = "\
8226block0:
8227 inline_asm.volatile \"mfence\", \"\", \"memory\"()
8228 return
8229";
8230 assert_eq!(body(source), expected);
8231 }
8232
8233 #[test]
8234 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
8235 let source = "\
8238int f(int x, int y) {
8239 int r;
8240 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
8241 return r + y;
8242}
8243";
8244 let expected = "\
8245block0(%0: i32, %1: i32):
8246 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
8247 %4 = add.nsw %2, %3
8248 return %4
8249";
8250 assert_eq!(body(source), expected);
8251 }
8252
8253 #[test]
8254 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
8255 let source = "\
8260struct pair { int a, b; };
8261int f(int x) {
8262 int slot = x;
8263 struct pair p = { x, x };
8264 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
8265 return slot + p.a;
8266}
8267";
8268 let text = body(source);
8269 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
8270 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
8271 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
8272 }
8273
8274 #[test]
8275 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
8276 let source = "\
8281int f(int x) {
8282 int r = 7;
8283 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
8284 return r;
8285away:
8286 return r;
8287}
8288";
8289 let expected = "\
8290block0(%0: i32):
8291 %1 = iconst.i32 7
8292 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
8293
8294block1:
8295 return %2
8296
8297block2:
8298 return %1
8299";
8300 assert_eq!(body(source), expected);
8301 }
8302
8303 #[test]
8304 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
8305 let mut opts = options();
8309 opts.emit = EmitKind::Ir;
8310 for (source, expected) in [
8311 (
8312 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
8313 "output operand constraint lacks '='",
8314 ),
8315 (
8316 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
8317 "lvalue required in 'asm' statement",
8318 ),
8319 (
8320 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
8321 "read-only variable 'g' used as 'asm' output",
8322 ),
8323 (
8324 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
8325 "input operand constraint contains '='",
8326 ),
8327 (
8328 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
8329 "memory input 0 is not directly addressable",
8330 ),
8331 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
8332 (
8333 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
8334 "duplicate asm operand name 'a'",
8335 ),
8336 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
8337 ] {
8338 let result = run(&opts, source);
8339 assert!(result.failed(), "expected this to be reported:\n{source}");
8340 assert!(
8341 result.messages.iter().any(|m| m.contains(expected)),
8342 "{expected}\n{:?}",
8343 result.messages
8344 );
8345 }
8346 }
8347
8348 #[test]
8353 fn an_asm_at_file_scope_that_is_directives_becomes_the_objects_it_defines() {
8354 let text = ir(concat!(
8355 "__asm__(\n",
8356 " \".section .rodata\\n\"\n",
8357 " \".globl first\\n\"\n",
8358 " \".balign 8\\n\"\n",
8359 " \"first:\\n\"\n",
8360 " \".long 1\\n\"\n",
8361 " \".long 2\\n\"\n",
8362 " \".globl last\\n\"\n",
8363 " \"last:\\n\"\n",
8364 " \".quad last - first\\n\");\n",
8365 "extern const int first[];\n",
8366 "extern const long last;\n",
8367 ));
8368 assert!(text.contains("global @first : bytes 8 = { i32 1, i32 2 }, align 8"), "{text}");
8369 assert!(text.contains("global @last : i64 = 8"), "{text}");
8370 }
8371
8372 #[test]
8376 fn a_name_an_asm_at_file_scope_defined_is_not_undone_by_a_declaration_of_it() {
8377 let text = ir(concat!(
8378 "__asm__(\".data\\n.globl counter\\ncounter:\\n.long 7\\n\");\n",
8379 "extern int counter;\n",
8380 "int read(void) { return counter; }\n",
8381 ));
8382 assert!(text.contains("global @counter : i32 = 7"), "{text}");
8383 }
8384
8385 #[test]
8390 fn bytes_under_no_label_at_file_scope_are_a_global_in_front_of_the_label() {
8391 let text = ir(concat!(
8392 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n662:\\n",
8393 ".pushsection .data.ignore\\n.byte 7\\n.popsection\\n.byte 662b - 661b\\n\");\n",
8394 "extern unsigned char stuff[];\n",
8395 "int read(void) { return stuff[0]; }\n",
8396 ));
8397 let under = text.find("global @.Lasm.0 : i8 = 41").expect(&text);
8398 let named = text.find("global @stuff : i8 = 42").expect(&text);
8399 assert!(under < named, "the bytes under no label come first: {text}");
8400 assert!(text.contains("global @.Lasm.1 : i8 = 7, align 1, linkage(internal), section"));
8401 let after = text.find("global @.Lasm.2 : i8 = 1").expect(&text);
8406 assert!(named < after, "{text}");
8407 }
8408
8409 #[test]
8414 fn a_distance_from_here_at_file_scope_is_a_hole_naming_the_global_it_measures_to() {
8415 let text = ir(concat!(
8416 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n",
8417 ".pushsection .data.ignore\\n.long 661b - .\\n.popsection\\n\");\n",
8418 "extern unsigned char stuff[];\n",
8419 "int read(void) { return stuff[0]; }\n",
8420 ));
8421 assert!(text.contains("global @.Lasm.1 : bytes 4 = { away.4 @stuff }"), "{text}");
8425 }
8426
8427 #[test]
8432 fn a_set_at_file_scope_is_a_second_name_for_what_it_names() {
8433 let text = ir(concat!(
8434 "void base(void) {}\n",
8435 "__asm__(\".weak one\\n.set one, base\");\n",
8436 "__asm__(\".globl two\\n.set two, base\");\n",
8437 "__asm__(\".set three, base\");\n",
8438 "void three(void) {}\n",
8439 ));
8440 assert!(text.contains("alias @one = @base, linkage(weak)"), "{text}");
8441 assert!(text.contains("alias @two = @base"), "{text}");
8442 assert!(!text.contains("alias @three"), "a definition of the name wins: {text}");
8443 assert!(text.contains("func @three"), "{text}");
8444 }
8445
8446 #[test]
8451 fn a_set_of_a_name_this_file_does_not_define_says_so() {
8452 let messages = errors("__asm__(\".set here, elsewhere\");\n");
8453 assert!(
8454 messages
8455 .iter()
8456 .any(|m| m.contains("'here' is aliased to undefined symbol 'elsewhere'")
8457 && m.contains("E0697")),
8458 "{messages:?}"
8459 );
8460 }
8461
8462 #[test]
8465 fn an_incbin_at_file_scope_is_the_bytes_of_the_file_it_names() {
8466 let mut opts = options();
8467 opts.emit = EmitKind::Ir;
8468 let mut fs = MemoryFileSystem::new();
8469 fs.insert(
8470 "/main.c",
8471 b"__asm__(\".data\\n.globl blob\\nblob:\\n.incbin \\\"seed\\\"\\n\");\n".to_vec(),
8472 );
8473 fs.insert("seed", b"hi".to_vec());
8474 let result = compile(&opts, "/main.c", &fs);
8475 assert_eq!(result.messages, Vec::<String>::new());
8476 let text = result.text();
8477 assert!(text.contains("global @blob : bytes 2 = { bytes \"hi\" }"), "{text}");
8478 }
8479
8480 #[test]
8483 fn an_incbin_naming_a_file_that_is_not_there_says_which_file() {
8484 let messages = errors("__asm__(\".data\\nb:\\n.incbin \\\"nowhere\\\"\\n\");\n");
8485 assert!(
8486 messages
8487 .iter()
8488 .any(|m| m.contains("cannot open 'nowhere' for reading") && m.contains("E0702")),
8489 "{messages:?}"
8490 );
8491 }
8492
8493 #[test]
8496 fn an_instruction_in_an_asm_at_file_scope_is_refused_rather_than_ignored() {
8497 for source in [
8498 "__asm__(\".text\\n.globl f\\nf:\\n ret\\n\");\n",
8499 "__asm__(\".data\\n.set alias, 4\\n\");\n",
8500 ] {
8501 let messages = errors(source);
8502 assert!(
8503 messages
8504 .iter()
8505 .any(|m| m.contains("not supported yet")
8506 && m.contains("in an `asm` at file scope")),
8507 "{source}\n{messages:?}"
8508 );
8509 }
8510 }
8511
8512 #[test]
8522 fn a_naked_function_is_its_own_prologue_and_its_own_ending() {
8523 let text = asm(concat!(
8524 "unsigned nlr_push_tail(void *nlr);\n",
8525 "__attribute__((naked)) unsigned nlr_push(void *nlr) {\n",
8526 " __asm volatile(\n",
8527 " \"movq (%rsp), %rax\\n\"\n",
8528 " \"movq %rax, 16(%rdi)\\n\"\n",
8529 " \"movq %rbx, 40(%rdi)\\n\"\n",
8530 " \"jmp nlr_push_tail\\n\");\n",
8531 "}\n",
8532 ));
8533 assert!(text.contains("\tmovq\t(%rsp), %rax\n"), "{text}");
8534 assert!(text.contains("\tjmp\tnlr_push_tail\n"), "{text}");
8535 assert!(text.contains("\tud2\n"), "{text}");
8536 assert!(!text.contains("\tpushq\t"), "nothing is saved in front of it: {text}");
8537 assert!(!text.contains("\tret\n"), "the jump is where it ends: {text}");
8538 }
8539
8540 #[test]
8543 fn what_a_function_without_a_prologue_cannot_be_given_is_refused() {
8544 let mut opts = options();
8545 opts.emit = EmitKind::Asm;
8546 for (source, why) in [
8547 (
8548 "__attribute__((naked)) void f(void) { volatile long a[8]; a[0] = 1; }\n",
8549 "bytes of frame",
8550 ),
8551 (
8552 "__attribute__((naked)) void f(int n) { char a[n]; __asm(\"nop\" ::\"r\"(a)); }\n",
8553 "has no prologue to point a frame pointer at it with",
8554 ),
8555 ("void elsewhere(void); void f(void) { __asm(\"jmp elsewhere\"); }\n", "jumps out of"),
8556 ] {
8557 let result = run(&opts, source);
8558 assert!(result.failed(), "expected this to be refused:\n{source}");
8559 assert!(
8560 result.messages.iter().any(|message| message.contains(why)),
8561 "{:?}",
8562 result.messages
8563 );
8564 }
8565 }
8566
8567 #[test]
8568 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
8569 let mut opts = options();
8570 opts.emit = EmitKind::Ir;
8571 for source in [
8572 "int f(int n) { void *p = &&out; if (n) goto *p; { int a[n]; out: return 1; } }\n",
8573 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
8574 ] {
8575 let result = run(&opts, source);
8576 assert!(result.failed(), "expected this to be reported:\n{source}");
8577 assert!(
8578 result.messages.iter().any(|m| m.contains("not supported yet")),
8579 "{:?}",
8580 result.messages
8581 );
8582 }
8583 }
8584
8585 fn round_trip(source: &str) -> (String, String) {
8587 let printed = ir(source);
8588 let mut opts = options();
8589 opts.emit = EmitKind::Ir;
8590 let mut fs = MemoryFileSystem::new();
8591 fs.insert("/main.ir", printed.clone().into_bytes());
8592 let result = compile_ir(&opts, "/main.ir", &fs);
8593 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
8594 (printed, result.text().to_owned())
8595 }
8596
8597 #[test]
8598 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
8599 let (printed, again) = round_trip(
8603 "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",
8604 );
8605 assert_eq!(printed, again);
8606 }
8607
8608 #[test]
8609 fn ir_that_is_not_ir_says_which_line_stopped_it() {
8610 let mut opts = options();
8611 opts.emit = EmitKind::Ir;
8612 let mut fs = MemoryFileSystem::new();
8613 let text = "\
8614; ModuleID = 'a.c'
8615; format 0
8616target triple = \"x86_64-unknown-linux-gnu\"
8617target datalayout = \"e-p:64:64-i64:64-S128\"
8618
8619func @f(), linkage(external) {
8620block0:
8621 frobnicate
8622}
8623";
8624 fs.insert("/main.ir", text.as_bytes().to_vec());
8625 let result = compile_ir(&opts, "/main.ir", &fs);
8626 assert!(result.failed());
8627 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
8628 }
8629
8630 #[test]
8631 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
8632 let mut opts = options();
8635 opts.emit = EmitKind::Ir;
8636 let mut fs = MemoryFileSystem::new();
8637 let text = "\
8638; ModuleID = 'a.c'
8639; format 0
8640target triple = \"x86_64-unknown-linux-gnu\"
8641target datalayout = \"e-p:64:64-i64:64-S128\"
8642
8643func @f(), linkage(external) {
8644block0:
8645 %0 = iconst.i32 1
8646 return %0
8647}
8648";
8649 fs.insert("/main.ir", text.as_bytes().to_vec());
8650 let result = compile_ir(&opts, "/main.ir", &fs);
8651 assert!(result.failed());
8652 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
8653 }
8654
8655 #[test]
8656 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
8657 let mut fs = MemoryFileSystem::new();
8659 fs.insert("/main.ir", Vec::new());
8660 let result = compile_ir(&options(), "/main.ir", &fs);
8661 assert!(result.failed());
8662 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
8663 }
8664
8665 #[test]
8666 fn the_printed_ir_reads_back_as_the_same_module() {
8667 let text = ir("\
8670struct point { int x, y; };
8671static const char greeting[] = \"hi\";
8672int table[4] = { 1, 2, 3 };
8673int puts(const char *);
8674double half(double x) { return x / 2.0; }
8675int f(int n) {
8676 int total = 0;
8677 for (int i = 0; i < n; i++) {
8678 if (i == 3) continue;
8679 total += table[i];
8680 }
8681 switch (n) {
8682 case 0: total = 1;
8683 case 1: total++; break;
8684 default: total = -total;
8685 }
8686 struct point p = { total, 1 };
8687 int *q = &p.y;
8688 puts(greeting);
8689 return p.x + *q;
8690}
8691int dispatch(int c) {
8692 void *p = c ? &&one : &&two;
8693 goto *p;
8694one:
8695 return 1;
8696two:
8697 return 2;
8698}
8699int assembly(int x, int *p) {
8700 int r;
8701 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
8702 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
8703 return r;
8704away:
8705 return 0;
8706}
8707");
8708 let mut names = Interner::new();
8709 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
8710 assert_eq!(rucc_ir::print(&module, &names), text);
8711 }
8712
8713 #[test]
8714 fn what_save_temps_keeps_is_the_text_that_was_compiled_and_the_assembly_that_was_assembled() {
8715 let mut opts = options();
8719 opts.emit = EmitKind::Object;
8720 opts.save_temps = rucc_session::SaveTemps::Object;
8721 let result = run(&opts, "#define N 2\nint a[N];\n");
8722 assert_eq!(result.messages, Vec::<String>::new());
8723 let text = result.temps.preprocessed.expect("the preprocessed text");
8724 assert!(text.contains("int a[2];"), "{text}");
8725 assert!(text.starts_with("# 1 \"/main.c\""), "{text}");
8726 let asm = result.temps.assembly.expect("the assembly");
8727 assert!(asm.contains("a:"), "{asm}");
8728 assert!(matches!(result.artifact, Artifact::Object { .. }), "{:?}", result.artifact);
8729 }
8730
8731 #[test]
8732 fn nothing_is_kept_unless_the_flag_asked_for_it() {
8733 let mut opts = options();
8736 opts.emit = EmitKind::Object;
8737 assert_eq!(run(&opts, "int a;\n").temps, Temps::default());
8738 }
8739
8740 #[test]
8741 fn a_compilation_that_stops_before_the_back_end_keeps_the_text_and_no_assembly() {
8742 let mut opts = options();
8745 opts.emit = EmitKind::Ir;
8746 opts.save_temps = rucc_session::SaveTemps::Cwd;
8747 let result = run(&opts, "int a;\n");
8748 assert!(result.temps.preprocessed.is_some());
8749 assert_eq!(result.temps.assembly, None);
8750 }
8751
8752 fn span(from: u64, len: u64, held: rucc_debug::Held) -> rucc_debug::Span {
8754 rucc_debug::Span { from, len, held }
8755 }
8756
8757 #[test]
8758 fn two_stretches_that_meet_and_agree_come_out_as_one() {
8759 let one = span(0, 4, rucc_debug::Held::Reg(3));
8760 let two = span(4, 4, rucc_debug::Held::Reg(3));
8761 assert_eq!(settle(vec![two, one]), vec![span(0, 8, rucc_debug::Held::Reg(3))]);
8762 }
8763
8764 #[test]
8765 fn two_stretches_that_disagree_leave_the_addresses_they_share_unanswered() {
8766 let one = span(0, 8, rucc_debug::Held::Reg(3));
8767 let two = span(4, 8, rucc_debug::Held::Reg(4));
8768 let settled = settle(vec![one, two]);
8771 assert_eq!(
8772 settled,
8773 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(8, 4, rucc_debug::Held::Reg(4))]
8774 );
8775 }
8776
8777 #[test]
8778 fn a_stretch_two_others_disagree_over_the_whole_of_says_nothing_at_all() {
8779 let one = span(0, 8, rucc_debug::Held::Reg(3));
8780 let two = span(0, 8, rucc_debug::Held::Frame(-16));
8781 assert_eq!(settle(vec![one, two]), Vec::new());
8782 }
8783
8784 #[test]
8785 fn stretches_with_a_gap_between_them_keep_the_gap() {
8786 let one = span(0, 4, rucc_debug::Held::Reg(3));
8787 let two = span(16, 4, rucc_debug::Held::Reg(3));
8788 assert_eq!(settle(vec![one, two]), vec![one, two]);
8789 }
8790
8791 fn extent(len: usize) -> rucc_object::Extent {
8793 rucc_object::Extent {
8794 name: "f".to_owned(),
8795 start: 0,
8796 len,
8797 align: 1,
8798 binding: rucc_object::Binding::Global,
8799 visibility: rucc_object::Visibility::Default,
8800 patch: None,
8801 }
8802 }
8803
8804 fn row(at: usize, lo: u32, hi: u32) -> rucc_asm::Row {
8806 let span = Span::new(lo, hi);
8807 rucc_asm::Row { at, span, inst: None }
8808 }
8809
8810 #[test]
8811 fn a_row_ends_where_the_next_address_begins() {
8812 let rows = [row(0, 0, 1), row(4, 1, 2), row(10, 2, 3)];
8813 assert_eq!(ends(&extent(16), &rows), vec![4, 10, 16]);
8814 }
8815
8816 #[test]
8817 fn rows_sharing_an_address_all_end_where_the_next_address_begins() {
8818 let rows = [row(0, 0, 1), row(4, 1, 2), row(4, 2, 3), row(4, 3, 4)];
8821 assert_eq!(ends(&extent(12), &rows), vec![4, 12, 12, 12]);
8822 }
8823
8824 #[test]
8825 fn the_rows_of_a_scope_that_are_next_to_each_other_come_out_as_one_stretch() {
8826 let rows = [row(0, 0, 4), row(4, 10, 14), row(8, 14, 18), row(12, 40, 44)];
8827 let ends = ends(&extent(16), &rows);
8828 let scope = Span::new(8, 20);
8829 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 8 }]);
8830 }
8831
8832 #[test]
8833 fn a_scope_the_back_end_split_in_two_comes_out_as_two_stretches() {
8834 let rows = [row(0, 10, 14), row(4, 40, 44), row(8, 14, 18)];
8835 let ends = ends(&extent(12), &rows);
8836 let scope = Span::new(8, 20);
8837 let over = spread(scope, &ends, &rows);
8838 assert_eq!(
8839 over,
8840 vec![rucc_debug::Reach { from: 0, len: 4 }, rucc_debug::Reach { from: 8, len: 4 }]
8841 );
8842 }
8843
8844 #[test]
8845 fn a_row_with_no_source_of_its_own_belongs_to_no_scope() {
8846 let rows = [rucc_asm::Row { at: 0, span: Span::DUMMY, inst: None }, row(4, 10, 14)];
8848 let ends = ends(&extent(8), &rows);
8849 let scope = Span::new(0, 20);
8850 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 4 }]);
8851 }
8852
8853 fn scope(parent: Option<usize>, lo: u32, hi: u32) -> crate::shapes::Scope {
8855 let span = Span::new(lo, hi);
8856 crate::shapes::Scope { parent, span }
8857 }
8858
8859 #[test]
8860 fn a_function_gets_the_scopes_its_own_locals_are_in_and_nothing_else() {
8861 let scopes = [scope(None, 0, 10), scope(None, 20, 30), scope(Some(1), 22, 26)];
8863 let rows = [row(0, 22, 24), row(4, 26, 28)];
8864 let (out, at) = nests(&[Some(2)], &scopes, &extent(8), &rows);
8865 assert_eq!(at.get(&1), Some(&0));
8868 assert_eq!(at.get(&2), Some(&1));
8869 assert_eq!(at.get(&0), None);
8870 assert_eq!(out.len(), 2);
8871 assert_eq!(out[0].parent, None);
8872 assert_eq!(out[1].parent, Some(0));
8873 assert_eq!(out[0].over, vec![rucc_debug::Reach { from: 0, len: 8 }]);
8874 assert_eq!(out[1].over, vec![rucc_debug::Reach { from: 0, len: 4 }]);
8875 }
8876
8877 #[test]
8878 fn a_local_written_straight_into_the_body_pulls_no_scope_in() {
8879 let scopes = [scope(None, 20, 30)];
8880 let rows = [row(0, 22, 24)];
8881 let (out, at) = nests(&[None], &scopes, &extent(4), &rows);
8882 assert_eq!(out, Vec::new());
8883 assert!(at.is_empty());
8884 }
8885
8886 #[test]
8887 fn a_scope_whose_code_all_went_away_is_still_one_of_the_functions_scopes() {
8888 let scopes = [scope(None, 20, 30)];
8892 let rows = [row(0, 40, 44)];
8893 let (out, at) = nests(&[Some(0)], &scopes, &extent(4), &rows);
8894 assert_eq!(at.get(&0), Some(&0));
8895 assert_eq!(out.len(), 1);
8896 assert_eq!(out[0].over, Vec::new());
8897 }
8898}