1use std::collections::HashMap;
14use std::path::Path;
15
16use rucc_base::Interner;
17use rucc_codegen::coverage::Fired;
18use rucc_codegen::elsewhere::Elsewhere;
19use rucc_codegen::lowering::Lowerings;
20use rucc_codegen::pipeline::{self, Machine, Recording};
21use rucc_codegen::pressure::Pressure;
22use rucc_cost::Goal;
23use rucc_diag::{Diagnostic, Severity, SourceMap, Span};
24use rucc_ir::{FpContract, Pic as IrPic, Visibility as IrVisibility};
25use rucc_lex::{Convert, Keywords, PpToken, convert};
26use rucc_lower::Protector as LowerProtector;
27use rucc_sema::{Checker, Context as CheckContext};
28use rucc_session::{
29 Contract, EmitKind, FileSystem, Options, Padding, Pic, Protector, Session, Visibility,
30};
31use rucc_target::TargetInfo;
32use rucc_tuple::{Arch, ObjectFormat};
33
34use crate::preprocess::render;
35
36#[derive(Debug, Clone, PartialEq, Eq, Default)]
43pub enum Artifact {
44 #[default]
47 Nothing,
48 Text(String),
50 Object {
57 bytes: Vec<u8>,
59 defines: Vec<String>,
63 },
64}
65
66impl Artifact {
67 #[must_use]
69 pub fn bytes(&self) -> &[u8] {
70 match self {
71 Artifact::Nothing => &[],
72 Artifact::Text(text) => text.as_bytes(),
73 Artifact::Object { bytes, .. } => bytes,
74 }
75 }
76}
77
78#[derive(Debug, Clone, PartialEq, Eq)]
80pub struct Compiled {
81 pub artifact: Artifact,
83 pub messages: Vec<String>,
85 pub errors: u32,
87 pub fired: Fired,
93 pub pressure: Pressure,
98 pub lowerings: Lowerings,
103 pub dumps: Vec<rucc_opt::Dump>,
109 pub remarks: String,
115 pub deps: Vec<rucc_pp::Dependency>,
120 pub temps: Temps,
127}
128
129#[derive(Debug, Clone, PartialEq, Eq, Default)]
136pub struct Temps {
137 pub preprocessed: Option<String>,
139 pub assembly: Option<String>,
141}
142
143impl Compiled {
144 #[must_use]
146 pub fn failed(&self) -> bool {
147 self.errors > 0
148 }
149
150 #[must_use]
155 pub fn text(&self) -> &str {
156 match &self.artifact {
157 Artifact::Text(text) => text,
158 _ => "",
159 }
160 }
161}
162
163#[must_use]
176pub fn compile(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
177 let mut sess = Session::new(opts.clone());
178 let keywords = Keywords::new(&mut sess.interner, opts.std, opts.gnu_extensions);
182 let mut diagnostics: Vec<Diagnostic> = Vec::new();
183 let mut fired = Fired::new();
185 let mut pressure = Pressure::new();
187 let mut lowerings = Lowerings::asked(opts.lowering_dump.is_some());
188 let mut dumps = Vec::new();
190 let mut remarks = String::new();
191 let mut temps = Temps::default();
193
194 let bytes = match fs.read(Path::new(name)) {
195 Ok(bytes) => bytes,
196 Err(e) => return failure(format!("{name}: {e}")),
197 };
198 let Ok(file) = sess.sources.add_shared(crate::phase::source_name(name), bytes, None) else {
199 return failure(format!("{name}: the source map has no room left for this file"));
200 };
201
202 let mut pp = rucc_pp::Preprocessor::with_prefix_map(opts.prefix_map.macros.clone());
206 let predef = rucc_pp::Predef::for_options(opts);
207 let expanded: Vec<PpToken> = {
208 let mut tokens = Vec::new();
209 {
214 let mut cx =
215 rucc_pp::Context::new(&mut sess.interner, &mut sess.sources, fs, &opts.search);
216 cx.lex = rucc_lex::Options::for_dialect(opts.std, opts.gnu_extensions);
217 cx.pedantic = opts.pedantic;
218 if pp.predefine(&sess.target, &predef, &mut cx).is_err() {
219 return failure(format!(
220 "{name}: the source map has no room for the built in macros"
221 ));
222 }
223 if pp.preinclude(&opts.preincludes, &mut tokens, &mut cx).is_err() {
224 return failure(format!("{name}: the source map has no room for the command line"));
225 }
226 tokens.append(&mut pp.run(file, &mut cx));
227 }
228 if opts.save_temps.wanted() {
229 temps.preprocessed = Some(rucc_pp::print(
230 file,
231 &tokens,
232 pp.line_directives(),
233 &sess.sources,
234 &sess.interner,
235 rucc_pp::PrintOptions { line_markers: opts.line_markers },
236 ));
237 }
238 tokens.iter().map(|token| token.to_pp()).collect()
239 };
240 diagnostics.extend(pp.take_diagnostics());
241 let deps = pp.dependencies().to_vec();
244
245 let cx = Convert {
248 keywords: &keywords,
249 interner: &sess.interner,
250 target: &sess.target,
251 std: opts.std,
252 gnu: opts.gnu_extensions,
253 pedantic: opts.pedantic,
254 };
255 let (tokens, complaints) = convert(&expanded, &cx);
256 diagnostics.extend(complaints);
257
258 let parsed = rucc_parse::parse(
259 &tokens,
260 rucc_parse::Context {
261 interner: &sess.interner,
262 std: opts.std,
263 gnu: opts.gnu_extensions,
264 pedantic: opts.pedantic,
265 error_limit: opts.error_limit as usize,
266 },
267 );
268 let parse_failed = parsed.diagnostics.iter().any(|d| d.severity.is_fatal());
269 diagnostics.extend(parsed.diagnostics);
270
271 let mut artifact = Artifact::Nothing;
272 let mut instrumented = Instrumented::default();
275 if !parse_failed {
276 let mut checker = Checker::new(
277 &parsed.ast,
278 CheckContext {
279 names: &sess.interner,
280 target: &sess.target,
281 std: opts.std,
282 gnu: opts.gnu_extensions,
283 pedantic: opts.pedantic,
284 permissive: opts.permissive,
285 gnu89_inline: opts.gnu89_inline,
286 error_limit: opts.error_limit as usize,
287 builtins: opts.builtins && opts.hosted,
290 no_builtin: &opts.no_builtin,
291 short_enums: opts.short_enums,
292 ms_extensions: sess.ms_extensions(),
293 trapping_math: opts.trapping_math,
294 },
295 );
296 checker.check_unit();
297 let checked = checker.finish();
298 if !checked.failed() {
299 match opts.emit {
300 EmitKind::Tast => {
301 artifact = Artifact::Text(rucc_sema::print(
302 &checked.tast,
303 &checked.types,
304 &sess.interner,
305 ));
306 }
307 EmitKind::TypeGranules => {
311 artifact = Artifact::Text(rucc_types::granule_report(
312 &checked.types,
313 &sess.interner,
314 &sess.target,
315 ));
316 }
317 EmitKind::Ir
318 | EmitKind::MirFinal
319 | EmitKind::Asm
320 | EmitKind::Object
321 | EmitKind::Archive
322 | EmitKind::Executable
323 | EmitKind::SafetySummary => {
324 let mut read = |named: &str| {
329 fs.read(Path::new(named))
330 .map(|bytes| bytes.as_slice().to_vec())
331 .map_err(|why| why.to_string())
332 };
333 let meaning = if opts.debug_info {
339 crate::shapes::collect(
340 &checked.tast,
341 &checked.types,
342 &sess.target,
343 &sess.interner,
344 &sess.sources,
345 )
346 } else {
347 crate::shapes::Meaning::default()
348 };
349 let mut lowered = rucc_lower::lower(
350 crate::phase::source_name(name),
351 rucc_lower::Context {
352 tast: &checked.tast,
353 types: &checked.types,
354 target: &sess.target,
355 names: &mut sess.interner,
356 visibility: match opts.visibility {
357 Visibility::Default => IrVisibility::Default,
358 Visibility::Hidden => IrVisibility::Hidden,
359 Visibility::Protected => IrVisibility::Protected,
360 },
361 protector: match opts.protector {
362 Protector::None => LowerProtector::None,
363 Protector::Buffers => LowerProtector::Buffers,
364 Protector::Strong => LowerProtector::Strong,
365 Protector::All => LowerProtector::All,
366 },
367 wrapping: rucc_lower::Wrapping {
368 signed: opts.wrapping.signed,
369 pointer: opts.wrapping.pointer,
370 trap: opts.wrapping.trap,
371 },
372 aliasing: opts.strict_aliasing,
373 padding: opts.padding == Padding::Ignored,
374 contract: match opts.fp_contract {
375 Contract::Off => FpContract::Off,
376 Contract::On => FpContract::On,
377 Contract::Fast => FpContract::Fast,
378 },
379 align: opts.align_functions,
380 read: &mut read,
381 },
382 );
383 let failed = lowered.diagnostics.iter().any(|d| d.severity.is_fatal());
387 if !failed {
388 if let Err(errors) = rucc_ir::verify(&lowered.module, &sess.interner) {
393 for error in errors {
394 diagnostics.push(internal(&format!("invalid IR, {error}")));
395 }
396 } else if let Err(complaints) =
397 instrument(&mut lowered.module, &mut sess.interner, opts)
398 .map(|done| instrumented = done)
399 {
400 diagnostics.extend(complaints);
401 } else if let Err(complaints) = optimize(
402 &mut lowered.module,
403 &mut sess.interner,
404 &sess.target,
405 opts,
406 name,
407 &mut dumps,
408 &mut remarks,
409 ) {
410 diagnostics.extend(complaints);
411 } else if opts.emit == EmitKind::SafetySummary {
412 artifact = Artifact::Text(
417 rucc_safety::summarize(
418 &lowered.module,
419 &sess.interner,
420 name,
421 opts.safety.as_str(),
422 instrumented.checks,
423 instrumented.interposed,
424 instrumented.crossings,
425 )
426 .render(),
427 );
428 } else if opts.emit == EmitKind::Ir {
429 artifact =
434 Artifact::Text(rucc_ir::print(&lowered.module, &sess.interner));
435 } else {
436 match generate(
439 &mut lowered.module,
440 &mut sess.interner,
441 &sess.target,
442 opts,
443 &mut Recording {
444 fired: &mut fired,
445 pressure: &mut pressure,
446 lowerings: &mut lowerings,
447 },
448 &mut temps.assembly,
449 Origin { map: &sess.sources, name, meaning: &meaning },
450 ) {
451 Ok(made) => artifact = made,
452 Err(complaints) => diagnostics.extend(complaints),
453 }
454 }
455 }
456 diagnostics.extend(lowered.diagnostics);
457 }
458 _ => {}
459 }
460 }
461 diagnostics.extend(checked.diagnostics);
462 }
463
464 let mut messages = Vec::with_capacity(diagnostics.len());
465 let mut errors = 0;
466 for diag in &diagnostics {
467 if rucc_diag::dropped(diag, &sess.sources, opts.warnings, opts.system_header_warnings) {
473 continue;
474 }
475 if diag.severity.is_fatal()
476 || (diag.severity == Severity::Warning && opts.warnings_are_errors)
477 {
478 errors += 1;
479 }
480 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
481 }
482 if errors > 0 {
483 artifact = Artifact::Nothing;
485 }
486 Compiled { artifact, messages, errors, fired, pressure, lowerings, dumps, remarks, deps, temps }
489}
490
491#[must_use]
501pub fn compile_ir(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
502 let mut sess = Session::new(opts.clone());
503 if opts.emit != EmitKind::Ir {
504 return failure(format!(
505 "{name}: an input of IR can only be emitted as IR, and `--emit={}` asks for what \
506 the C in front of it became",
507 opts.emit.as_str()
508 ));
509 }
510 let bytes = match fs.read(Path::new(name)) {
511 Ok(bytes) => bytes,
512 Err(e) => return failure(format!("{name}: {e}")),
513 };
514 let Ok(text) = std::str::from_utf8(bytes.as_slice()) else {
515 return failure(format!("{name}: this is not text, so it is not IR"));
516 };
517
518 let module = match rucc_ir::parse(text, &mut sess.interner) {
519 Ok(module) => module,
520 Err(error) => {
521 return failure(format!("{name}:{}: {}", error.line, error.message));
522 }
523 };
524 let mut diagnostics: Vec<Diagnostic> = Vec::new();
525 if let Err(errors) = rucc_ir::verify(&module, &sess.interner) {
526 for error in errors {
527 diagnostics.push(invalid(&format!("invalid IR, {error}")));
528 }
529 }
530 let mut messages = Vec::with_capacity(diagnostics.len());
531 for diag in &diagnostics {
532 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
533 }
534 let errors = u32::try_from(messages.len()).unwrap_or(u32::MAX);
535 let artifact = if errors > 0 {
536 Artifact::Nothing
537 } else {
538 Artifact::Text(rucc_ir::print(&module, &sess.interner))
539 };
540 Compiled {
542 artifact,
543 messages,
544 errors,
545 fired: Fired::new(),
546 pressure: Pressure::new(),
547 lowerings: Lowerings::new(),
548 dumps: Vec::new(),
549 remarks: String::new(),
550 deps: Vec::new(),
551 temps: Temps::default(),
552 }
553}
554
555fn instrument(
578 module: &mut rucc_ir::Module,
579 names: &mut Interner,
580 opts: &Options,
581) -> Result<Instrumented, Vec<Diagnostic>> {
582 if !opts.safety.instruments() {
583 return Ok(Instrumented::default());
584 }
585 let mut checks = rucc_safety::run(module, opts.subobject, opts.promise, opts.races);
586 checks.freed = rucc_safety::ending::checks(module, names);
594 let interposed = rucc_safety::redirect(module, names);
599 let crossings = rucc_safety::witness(module, names);
602 match rucc_ir::verify(module, names) {
603 Ok(()) => Ok(Instrumented { checks, interposed, crossings }),
604 Err(errors) => Err(errors
605 .iter()
606 .map(|e| internal(&format!("invalid IR after check insertion, {e}")))
607 .collect()),
608 }
609}
610
611#[derive(Clone, Copy, Debug, Default)]
617struct Instrumented {
618 checks: rucc_safety::Counts,
620 interposed: usize,
622 crossings: rucc_safety::Sites,
624}
625
626fn optimize(
638 module: &mut rucc_ir::Module,
639 names: &mut Interner,
640 target: &TargetInfo,
641 opts: &Options,
642 file: &str,
643 dumps: &mut Vec<rucc_opt::Dump>,
644 remarks: &mut String,
645) -> Result<(), Vec<Diagnostic>> {
646 let mut settings = rucc_opt::Options::for_level(opts.opt_level);
647 settings.interposition = match opts.interposition {
653 true => replaceable(target, opts),
654 false => IrPic::Executable,
655 };
656 settings.toggles.clone_from(&opts.passes);
657 settings.builtins = opts.builtins && opts.hosted;
662 settings.no_builtin.clone_from(&opts.no_builtin);
663 settings.fuel = opts.pass_fuel.iter().cloned().collect();
664 settings.global_fuel = opts.pass_fuel_global;
665 settings.verify |= opts.verify_each;
666 for (on, spec) in &opts.pass_gates {
667 if let Err(why) = settings.gates.add(*on, spec) {
670 return Err(vec![internal(&why)]);
671 }
672 }
673 for spec in &opts.dump_ir {
674 if let Err(why) = settings.dumps.add(spec) {
677 return Err(vec![internal(&why)]);
678 }
679 }
680 let mut wants = rucc_opt::Wants::none();
681 for spec in &opts.opt_info {
682 if let Err(why) = wants.add(spec) {
685 return Err(vec![internal(&why)]);
686 }
687 }
688 let report = rucc_opt::run(module, names, &settings);
689 remarks.push_str(&rucc_opt::optinfo::render(file, &report, names, wants));
690 dumps.extend(report.dumps);
691 match report.broke.is_empty() {
692 true => Ok(()),
693 false => Err(report.broke.iter().map(|why| internal(why)).collect()),
694 }
695}
696
697fn replaceable(target: &TargetInfo, opts: &Options) -> IrPic {
731 match (target.tuple.os().object_format(), opts.pic) {
732 (Some(ObjectFormat::Elf), Pic::Library) => IrPic::Library,
733 _ => IrPic::Executable,
734 }
735}
736
737#[derive(Clone, Copy)]
744struct Origin<'a> {
745 map: &'a SourceMap,
747 name: &'a str,
749 meaning: &'a crate::shapes::Meaning,
751}
752
753fn generate(
754 module: &mut rucc_ir::Module,
755 names: &mut Interner,
756 target: &TargetInfo,
757 opts: &Options,
758 recording: &mut Recording<'_>,
759 assembly: &mut Option<String>,
760 origin: Origin<'_>,
761) -> Result<Artifact, Vec<Diagnostic>> {
762 let Some(machine) = Machine::for_target(target) else {
763 return Err(vec![unsupported(&format!(
764 "there is no back end for {} in this compiler yet, so there is nothing to generate",
765 target.tuple
766 ))]);
767 };
768 if opts.protector != Protector::None && machine.conv.guard.is_none() {
773 return Err(vec![unsupported(&format!(
774 "{} is not supported for {} yet, because the stack protector on that target is not \
775 the one this compiler writes",
776 opts.protector, target.tuple
777 ))]);
778 }
779 if opts.control.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
785 return Err(vec![unsupported(&format!(
786 "-fcf-protection={} is not supported for {} yet, because what says a file was built \
787 for it there is not the note this compiler writes",
788 opts.control, target.tuple
789 ))]);
790 }
791 let profile = match machine.conv.trace {
797 Some(trace) => opts.profile.then(|| opts.hook.early(trace.fentry)),
798 None if opts.profile => {
799 return Err(vec![unsupported(&format!(
800 "-pg is not supported for {} yet, because the profiler's hook on that target is \
801 not the one this compiler calls",
802 target.tuple
803 ))]);
804 }
805 None => None,
806 };
807 if opts.patchable.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
812 return Err(vec![unsupported(&format!(
813 "-fpatchable-function-entry= is not supported for {} yet, because what records where \
814 the room is there is not the section this compiler writes",
815 target.tuple
816 ))]);
817 }
818 let flags = pipeline::Flags {
819 frame_pointer: opts.frame_pointer,
820 red_zone: opts.red_zone,
821 stack_clash: opts.stack_clash,
822 landing: opts.control.branch(),
823 profile: match profile {
824 None => pipeline::Profile::No,
825 Some(true) => pipeline::Profile::Early,
826 Some(false) => pipeline::Profile::Late,
827 },
828 patch: pipeline::Room { after: opts.patchable.after(), before: opts.patchable.before },
829 reorder: opts.reorder_blocks.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
836 reuse: opts.stack_reuse.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
841 schedule: opts.schedule_insns.unwrap_or_else(|| opts.opt_level.schedules()),
846 accurate: opts.cycle_accurate_model,
848 verify: opts.verify_each,
851 goal: Goal::for_size(opts.opt_level.is_size()),
856 };
857
858 if opts.safety.instruments() {
867 rucc_opt::heap::annotate(module, names);
877 rucc_safety::handover::arrange(module);
884 rucc_safety::lower(module, names);
885 if let Err(errors) = rucc_ir::verify(module, names) {
886 return Err(errors
887 .iter()
888 .map(|e| internal(&format!("invalid IR after check lowering, {e}")))
889 .collect());
890 }
891 }
892
893 let elsewhere = Elsewhere::of(module, replaceable(target, opts), target.object_format);
902
903 let mut funcs = Vec::new();
904 let mut complaints = Vec::new();
905 for id in module.funcs() {
906 if module[id].is_declaration() {
907 continue;
908 }
909 match pipeline::compile_recording(
910 &mut module[id],
911 names,
912 &machine,
913 &elsewhere,
914 flags,
915 recording,
916 ) {
917 Ok(func) => funcs.push(func),
918 Err(why) => {
919 let name = names.resolve(module[id].name).to_owned();
920 let span = why.inst().map_or(Span::DUMMY, |inst| module[id].span(inst));
923 let said = format!("cannot generate code for '{name}': {why}");
924 complaints.push(unsupported_at(&said, span));
925 }
926 }
927 }
928 if !complaints.is_empty() {
929 return Err(complaints);
930 }
931 let (globals, aliases) = match opts.emit {
937 EmitKind::Asm | EmitKind::Object | EmitKind::Archive | EmitKind::Executable => (
938 rucc_asm::globals(module, names, target.object_format).map_err(refused)?,
939 rucc_asm::aliases(module, names).map_err(refused)?,
940 ),
941 _ => (rucc_asm::Globals::default(), Vec::new()),
942 };
943 let unwind = opts.unwinds();
947 match opts.emit {
948 EmitKind::Asm => {
949 rucc_asm::print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
950 .map(Artifact::Text)
951 .map_err(refused)
952 }
953 EmitKind::Object | EmitKind::Archive | EmitKind::Executable => {
957 if opts.save_temps.wanted() {
958 let listing = rucc_asm::print(
959 &funcs,
960 &globals,
961 &aliases,
962 names,
963 target,
964 unwind,
965 output(opts, target),
966 );
967 *assembly = Some(listing.map_err(refused)?);
968 }
969 if rucc_asm::kept(&funcs, names) {
975 if opts.debug_info {
976 return Err(vec![unsupported(
977 "debug information for a unit with an `asm` template kept as text",
978 )]);
979 }
980 let listing = rucc_asm::print(
981 &funcs,
982 &globals,
983 &aliases,
984 names,
985 target,
986 unwind,
987 output(opts, target),
988 )
989 .map_err(refused)?;
990 let read = rucc_asm::read(&listing).map_err(|trouble| {
991 vec![unsupported(&format!(
992 "an `asm` template kept as text, whose listing the assembler stopped at on \
993 line {}: {}",
994 trouble.line, trouble.why
995 ))]
996 })?;
997 let defines = rucc_object::assembled_defines(&read);
998 let bytes =
999 rucc_object::assembled(&read, &TargetInfo::new(opts.target)).map_err(wrote)?;
1000 return Ok(Artifact::Object { bytes, defines });
1001 }
1002 let assembled = rucc_asm::assemble(&funcs, names, target, unwind, opts.debug_info)
1003 .map_err(refused)?;
1004 let data = globals.image();
1005 let info = if opts.debug_info {
1009 describe(&assembled, &data, &funcs, origin, opts, target)
1010 .map_err(|why| vec![internal(&why)])?
1011 } else {
1012 rucc_object::Info::default()
1013 };
1014 let text = assembled.text;
1015 let bytes =
1018 rucc_object::write(&text, &data, &aliases, target, output(opts, target), &info)
1019 .map_err(wrote)?;
1020 let defines = rucc_object::defines(&text, &data, &aliases, target).map_err(wrote)?;
1025 Ok(Artifact::Object { bytes, defines })
1026 }
1027 _ => Ok(Artifact::Text(rucc_mir::print(&funcs, names, target.regs))),
1028 }
1029}
1030
1031fn describe(
1050 assembled: &rucc_asm::Assembled,
1051 data: &rucc_object::Data,
1052 machine: &[rucc_mir::Func],
1053 origin: Origin<'_>,
1054 opts: &Options,
1055 target: &TargetInfo,
1056) -> Result<rucc_object::Info, String> {
1057 let rucc_asm::Assembled { text, lines, frames } = assembled;
1058 let rewrite = |path: &str| opts.prefix_map.debug.apply(path).into_owned();
1059 let mut files: Vec<String> = Vec::new();
1064 let mut funcs = Vec::with_capacity(text.funcs.len());
1065 for ((extent, rows), built) in text.funcs.iter().zip(lines).zip(machine) {
1066 let mut out: Vec<rucc_debug::Row> = Vec::with_capacity(rows.len());
1067 for row in rows {
1068 if row.span.is_dummy() {
1069 continue;
1070 }
1071 let Some(at) = origin.map.presumed(row.span.lo) else {
1072 continue;
1073 };
1074 let which = interned(&mut files, rewrite(at.name));
1075 let place = rucc_debug::Row {
1076 at: row.at as u64,
1077 file: which,
1078 line: at.line,
1079 column: at.column,
1080 };
1081 match out.last() {
1091 Some(last) if last.at == place.at => {}
1092 _ => out.push(place),
1093 }
1094 }
1095 if let Some(first) = out.first_mut() {
1102 first.at = 0;
1103 }
1104 let known = origin.meaning.funcs.get(&extent.name);
1110 let decl = known.map(|known| rucc_debug::Place {
1111 file: interned(&mut files, rewrite(&known.file)),
1112 line: known.line,
1113 });
1114 let mut sig = known.and_then(|known| known.sig.clone());
1123 let mut placed: Vec<(u32, i32)> = built.locals.clone();
1124 let mut spots = stretches(extent, rows, built, target);
1125 for &decl in &built.sharing {
1130 if !spots.iter().any(|(at, _)| *at == decl) {
1131 spots.push((decl, Vec::new()));
1132 }
1133 }
1134 if let (Some(sig), Some(known)) = (sig.as_mut(), known) {
1135 for (param, decl) in sig.params.iter_mut().zip(&known.params) {
1136 let Some(decl) = *decl else { continue };
1137 if let Some(which) = placed.iter().position(|&(at, _)| at == decl) {
1138 let at = rucc_debug::Held::Frame(i64::from(placed.remove(which).1));
1139 param.spot = Some(rucc_debug::Spot::Always(at));
1140 continue;
1141 }
1142 let Some(which) = spots.iter().position(|(at, _)| *at == decl) else { continue };
1146 param.spot = Some(rucc_debug::Spot::Over(spots.remove(which).1));
1147 }
1148 }
1149 let mut locals = Vec::with_capacity(placed.len() + spots.len());
1153 let mut wants: Vec<Option<usize>> = Vec::with_capacity(locals.capacity());
1157 for (decl, at) in placed {
1158 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1159 wants.push(named.scope);
1160 locals.push(rucc_debug::Local {
1161 name: named.name.clone(),
1162 ty: named.ty,
1163 decl: Some(rucc_debug::Place {
1164 file: interned(&mut files, rewrite(&named.file)),
1165 line: named.line,
1166 }),
1167 spot: rucc_debug::Spot::Always(rucc_debug::Held::Frame(i64::from(at))),
1168 scope: None,
1169 });
1170 }
1171 spots.sort_by_key(|(decl, _)| *decl);
1175 for (decl, spans) in spots {
1176 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1177 wants.push(named.scope);
1178 locals.push(rucc_debug::Local {
1179 name: named.name.clone(),
1180 ty: named.ty,
1181 decl: Some(rucc_debug::Place {
1182 file: interned(&mut files, rewrite(&named.file)),
1183 line: named.line,
1184 }),
1185 spot: rucc_debug::Spot::Over(spans),
1186 scope: None,
1187 });
1188 }
1189 let (scopes, at) = nests(&wants, &origin.meaning.scopes, extent, rows);
1194 for (local, want) in locals.iter_mut().zip(&wants) {
1195 local.scope = want.and_then(|want| at.get(&want).copied());
1196 }
1197 funcs.push(rucc_debug::Function {
1198 name: extent.name.clone(),
1199 len: extent.len as u64,
1200 rows: out,
1201 decl,
1202 sig,
1203 external: known.is_some_and(|known| known.external),
1204 locals,
1205 scopes,
1206 });
1207 }
1208 let mut globals = Vec::new();
1215 for object in &data.objects {
1216 let Some(held) = origin.meaning.objects.get(&object.name) else { continue };
1217 globals.push(rucc_debug::Global {
1218 name: object.name.clone(),
1219 ty: held.ty,
1220 decl: Some(rucc_debug::Place {
1221 file: interned(&mut files, rewrite(&held.file)),
1222 line: held.line,
1223 }),
1224 external: held.external,
1225 });
1226 }
1227 let unit = rucc_debug::Unit {
1228 name: rewrite(origin.name),
1229 dir: rewrite(opts.working_dir.as_deref().unwrap_or(".")),
1232 producer: format!("rucc {}", crate::VERSION),
1233 files,
1234 types: origin.meaning.types.clone(),
1235 funcs,
1236 globals,
1237 pointer: u8::try_from(target.pointer_width / 8).unwrap_or(8),
1238 frames: opts.unwinds() || frames.is_some(),
1243 };
1244 let mut info = rucc_debug::write(&unit).map_err(|why| why.to_string())?;
1245 info.chunks.extend(frames.clone());
1246 Ok(info)
1247}
1248
1249fn stretches(
1260 extent: &rucc_object::Extent,
1261 rows: &[rucc_asm::Row],
1262 built: &rucc_mir::Func,
1263 target: &TargetInfo,
1264) -> Vec<(u32, Vec<rucc_debug::Span>)> {
1265 let (false, Some(regs)) = (built.kept.is_empty(), target.call_regs) else {
1268 return Vec::new();
1269 };
1270 let ends = ends(extent, rows);
1271 let mut bounds = vec![None; built.inst_count()];
1272 for (which, row) in rows.iter().enumerate() {
1273 let Some(inst) = row.inst else { continue };
1274 bounds[inst.index()] = Some((row.at as u64, ends[which]));
1275 }
1276 let mut spots: Vec<(u32, Vec<rucc_debug::Span>)> = Vec::new();
1277 for kept in &built.kept {
1278 let (Some((from, _)), Some((_, to))) = (bounds[kept.from.index()], bounds[kept.to.index()])
1279 else {
1280 continue;
1281 };
1282 if to <= from {
1283 continue;
1284 }
1285 let held = match kept.at {
1286 rucc_mir::Where::Reg { reg, class } => match regs.dwarf(class, reg) {
1289 Some(number) => rucc_debug::Held::Reg(number),
1290 None => continue,
1291 },
1292 rucc_mir::Where::Frame(at) => rucc_debug::Held::Frame(i64::from(at)),
1293 };
1294 let span = rucc_debug::Span { from, len: to - from, held };
1295 match spots.iter_mut().find(|(decl, _)| *decl == kept.decl) {
1296 Some((_, spans)) => spans.push(span),
1297 None => spots.push((kept.decl, vec![span])),
1298 }
1299 }
1300 for (_, spans) in &mut spots {
1301 *spans = settle(std::mem::take(spans));
1302 }
1303 spots.retain(|(_, spans)| !spans.is_empty());
1304 spots
1305}
1306
1307fn ends(extent: &rucc_object::Extent, rows: &[rucc_asm::Row]) -> Vec<u64> {
1317 let mut out = vec![extent.len as u64; rows.len()];
1318 let mut next = extent.len as u64;
1319 for which in (0..rows.len()).rev() {
1320 let at = rows[which].at as u64;
1321 out[which] = match next > at {
1324 true => next,
1325 false => out.get(which + 1).copied().unwrap_or(extent.len as u64),
1326 };
1327 next = next.min(at);
1328 }
1329 out
1330}
1331
1332fn nests(
1348 wants: &[Option<usize>],
1349 scopes: &[crate::shapes::Scope],
1350 extent: &rucc_object::Extent,
1351 rows: &[rucc_asm::Row],
1352) -> (Vec<rucc_debug::Scope>, HashMap<usize, usize>) {
1353 let mut needed: Vec<usize> = Vec::new();
1354 for &want in wants {
1355 let mut up = want;
1356 while let Some(which) = up {
1357 if needed.contains(&which) {
1358 break;
1359 }
1360 needed.push(which);
1361 up = scopes.get(which).and_then(|scope| scope.parent);
1362 }
1363 }
1364 needed.sort_unstable();
1367 let at: HashMap<usize, usize> =
1368 needed.iter().enumerate().map(|(which, &scope)| (scope, which)).collect();
1369 let ends = ends(extent, rows);
1370 let out = needed
1371 .iter()
1372 .map(|&which| {
1373 let scope = &scopes[which];
1374 rucc_debug::Scope {
1375 parent: scope.parent.and_then(|parent| at.get(&parent).copied()),
1376 over: spread(scope.span, &ends, rows),
1377 }
1378 })
1379 .collect();
1380 (out, at)
1381}
1382
1383fn spread(span: Span, ends: &[u64], rows: &[rucc_asm::Row]) -> Vec<rucc_debug::Reach> {
1391 let mut out: Vec<rucc_debug::Reach> = Vec::new();
1392 for (which, row) in rows.iter().enumerate() {
1393 if row.span.is_dummy() || row.span.lo < span.lo || row.span.hi > span.hi {
1394 continue;
1395 }
1396 let (from, to) = (row.at as u64, ends[which]);
1397 if to <= from {
1398 continue;
1399 }
1400 match out.last_mut() {
1401 Some(last) if last.from + last.len >= from => {
1402 last.len = to.saturating_sub(last.from).max(last.len);
1403 }
1404 _ => out.push(rucc_debug::Reach { from, len: to - from }),
1405 }
1406 }
1407 out
1408}
1409
1410fn settle(mut spans: Vec<rucc_debug::Span>) -> Vec<rucc_debug::Span> {
1427 spans.sort_by_key(|span| (span.from, span.len));
1428 for which in 0..spans.len() {
1429 let (from, end, held) =
1430 (spans[which].from, spans[which].from + spans[which].len, spans[which].held);
1431 let later = spans[which + 1..]
1432 .iter()
1433 .take_while(|later| later.from < end)
1434 .find(|later| later.from > from && later.held != held);
1435 if let Some(later) = later {
1436 spans[which].len = later.from - from;
1437 }
1438 }
1439 let mut edges: Vec<u64> =
1442 spans.iter().flat_map(|span| [span.from, span.from + span.len]).collect();
1443 edges.sort_unstable();
1444 edges.dedup();
1445 let mut out: Vec<rucc_debug::Span> = Vec::new();
1446 let mut first = 0;
1447 for pair in edges.windows(2) {
1448 let (from, to) = (pair[0], pair[1]);
1449 while spans.get(first).is_some_and(|span| span.from + span.len <= from) {
1453 first += 1;
1454 }
1455 let mut held = None;
1456 let mut agreed = true;
1457 for span in &spans[first..] {
1458 if span.from >= to {
1459 break;
1460 }
1461 if span.from > from || span.from + span.len < to {
1462 continue;
1463 }
1464 match held {
1465 None => held = Some(span.held),
1466 Some(seen) => agreed &= seen == span.held,
1467 }
1468 }
1469 let (Some(held), true) = (held, agreed) else { continue };
1470 match out.last_mut() {
1471 Some(last) if last.from + last.len == from && last.held == held => {
1472 last.len += to - from
1473 }
1474 _ => out.push(rucc_debug::Span { from, len: to - from, held }),
1475 }
1476 }
1477 out
1478}
1479
1480fn interned(files: &mut Vec<String>, name: String) -> usize {
1486 match files.iter().position(|have| *have == name) {
1487 Some(which) => which,
1488 None => {
1489 files.push(name);
1490 files.len() - 1
1491 }
1492 }
1493}
1494
1495fn output(opts: &Options, target: &TargetInfo) -> rucc_object::Output {
1507 let mut features = 0;
1508 if target.tuple.arch() == Arch::X86_64 {
1509 if opts.control.branch() {
1510 features |= rucc_object::Property::IBT;
1511 }
1512 if opts.control.ret() {
1513 features |= rucc_object::Property::SHSTK;
1514 }
1515 }
1516 rucc_object::Output {
1517 sections: rucc_object::Sections {
1518 functions: opts.function_sections,
1519 data: opts.data_sections,
1520 },
1521 property: rucc_object::Property { features },
1522 }
1523}
1524
1525fn wrote(why: rucc_object::Error) -> Vec<Diagnostic> {
1531 match why {
1532 rucc_object::Error::Format { .. } => vec![unsupported(&why.to_string())],
1533 rucc_object::Error::Refused { .. } => vec![internal(&why.to_string())],
1534 }
1535}
1536
1537fn refused(why: rucc_asm::Error) -> Vec<Diagnostic> {
1544 match why {
1545 rucc_asm::Error::Thread { .. }
1546 | rucc_asm::Error::IFunc { .. }
1547 | rucc_asm::Error::Frame { .. } => {
1548 vec![unsupported(&why.to_string())]
1549 }
1550 _ => vec![internal(&why.to_string())],
1551 }
1552}
1553
1554fn unsupported(message: &str) -> Diagnostic {
1560 unsupported_at(message, Span::DUMMY)
1561}
1562
1563fn unsupported_at(message: &str, span: Span) -> Diagnostic {
1569 Diagnostic::error(message.to_owned(), span)
1570 .with_code("E0653")
1571 .note("this construct is not lowered yet, see https://github.com/tamnd/rucc/issues", span)
1572}
1573
1574fn invalid(message: &str) -> Diagnostic {
1576 Diagnostic::error(message.to_owned(), Span::DUMMY).with_code("E0661")
1577}
1578
1579fn internal(message: &str) -> Diagnostic {
1581 Diagnostic::error(format!("internal error: {message}"), Span::DUMMY)
1582 .with_code("E0652")
1583 .note("this is a bug in rucc rather than in the program, please report it", Span::DUMMY)
1584}
1585
1586fn failure(message: String) -> Compiled {
1589 Compiled {
1590 artifact: Artifact::Nothing,
1591 messages: vec![format!("rucc: error: {message}")],
1592 errors: 1,
1593 fired: Fired::new(),
1594 pressure: Pressure::new(),
1595 lowerings: Lowerings::new(),
1596 dumps: Vec::new(),
1597 remarks: String::new(),
1598 deps: Vec::new(),
1599 temps: Temps::default(),
1600 }
1601}
1602
1603#[cfg(test)]
1604mod tests {
1605 use rucc_session::{MemoryFileSystem, Std};
1606 use rucc_target::Triple;
1607
1608 use super::*;
1609
1610 fn options() -> Options {
1611 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1612 opts.emit = EmitKind::Tast;
1613 opts
1614 }
1615
1616 fn run(opts: &Options, source: &str) -> Compiled {
1617 let mut fs = MemoryFileSystem::new();
1618 fs.insert("/main.c", source.to_owned().into_bytes());
1619 compile(opts, "/main.c", &fs)
1620 }
1621
1622 fn freestanding() -> Options {
1626 let mut opts = options();
1627 opts.hosted = false;
1628 opts.search.push_system(rucc_session::runtime::DIR);
1629 opts
1630 }
1631
1632 fn shipped(source: &str) -> String {
1634 let result = run(&freestanding(), source);
1635 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1636 result.text().to_owned()
1637 }
1638
1639 fn tast(source: &str) -> String {
1641 let result = run(&options(), source);
1642 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1643 result.text().to_owned()
1644 }
1645
1646 #[test]
1647 fn the_shipped_stdarg_declares_a_list_and_the_four_operators() {
1648 let text = shipped(concat!(
1649 "#include <stdarg.h>\n",
1650 "int sum(int n, ...) {\n",
1651 " va_list ap, copy;\n",
1652 " va_start(ap, n);\n",
1653 " va_copy(copy, ap);\n",
1654 " int total = va_arg(ap, int) + va_arg(copy, int);\n",
1655 " va_end(ap);\n",
1656 " va_end(copy);\n",
1657 " return total;\n",
1658 "}\n",
1659 ));
1660 assert!(text.contains("va-start"), "{text}");
1661 assert!(text.contains("va-copy"), "{text}");
1662 assert!(text.contains("va-arg"), "{text}");
1663 assert!(text.contains("va-end"), "{text}");
1664 }
1665
1666 #[test]
1670 fn stdarg_hands_out_the_type_alone_when_that_is_all_that_was_asked_for() {
1671 let text = shipped(concat!(
1672 "#define __need___va_list\n",
1673 "#include <stdarg.h>\n",
1674 "int vprint(const char *f, __gnuc_va_list ap);\n",
1675 "#ifdef va_start\n",
1676 "#error va_start should not be defined\n",
1677 "#endif\n",
1678 "#ifdef _VA_LIST_DEFINED\n",
1679 "#error va_list should not have been made\n",
1680 "#endif\n",
1681 ));
1682 assert!(text.contains("vprint"), "{text}");
1683 }
1684
1685 #[test]
1688 fn stddef_answers_one_piece_at_a_time_and_the_next_request_still_gets_through() {
1689 let text = shipped(concat!(
1690 "#define __need_size_t\n",
1691 "#include <stddef.h>\n",
1692 "#ifdef offsetof\n",
1693 "#error offsetof should not be defined yet\n",
1694 "#endif\n",
1695 "#define __need_ptrdiff_t\n",
1696 "#include <stddef.h>\n",
1697 "#include <stddef.h>\n",
1698 "size_t a;\n",
1699 "ptrdiff_t b;\n",
1700 "wchar_t c;\n",
1701 "max_align_t d;\n",
1702 "void *e = NULL;\n",
1703 "struct P { int x; long y; };\n",
1704 "size_t f = offsetof(struct P, y);\n",
1705 ));
1706 assert!(text.contains("decl #0 a : unsigned long"), "{text}");
1707 assert!(text.contains("decl #1 b : long"), "{text}");
1708 }
1709
1710 #[test]
1711 fn the_shipped_limits_and_float_are_the_targets_own_answers() {
1712 let text = shipped(concat!(
1713 "#include <limits.h>\n",
1714 "#include <float.h>\n",
1715 "int bits = CHAR_BIT;\n",
1716 "long big = LONG_MAX;\n",
1717 "int low = INT_MIN;\n",
1718 "int radix = FLT_RADIX;\n",
1719 "int digits = DBL_MANT_DIG;\n",
1720 ));
1721 assert!(text.contains("const 8 : int"), "{text}");
1722 assert!(text.contains("const 9223372036854775807 : long"), "{text}");
1723 assert!(text.contains("const 2 : int"), "{text}");
1724 assert!(text.contains("const 53 : int"), "{text}");
1725 }
1726
1727 #[test]
1731 fn the_shipped_stdint_writes_the_whole_set_when_there_is_no_library_to_defer_to() {
1732 let text = shipped(concat!(
1733 "#include <stdint.h>\n",
1734 "int64_t a = INT64_C(1);\n",
1735 "uint_least16_t b;\n",
1736 "intptr_t c;\n",
1737 "uintmax_t d = UINTMAX_MAX;\n",
1738 "int wide = sizeof(int_fast64_t);\n",
1739 ));
1740 assert!(text.contains("decl #0 a : long"), "{text}");
1741 assert!(text.contains("decl #1 b : unsigned short"), "{text}");
1742 assert!(text.contains("decl #2 c : long"), "{text}");
1743 }
1744
1745 #[test]
1756 fn the_shipped_mmintrin_defines_the_mmx_type_and_the_operations_over_it() {
1757 let text = shipped(concat!(
1758 "#include <mmintrin.h>\n",
1759 "__m64 add(__m64 a, __m64 b) { return _mm_add_pi16(a, b); }\n",
1760 "__m64 pack(__m64 a, __m64 b) { return _m_packsswb(a, b); }\n",
1761 "__m64 shift(__m64 a) { return _mm_srai_pi32(a, 3); }\n",
1762 "int low(__m64 a) { return _mm_cvtsi64_si32(a); }\n",
1763 "void done(void) { _mm_empty(); }\n",
1764 ));
1765 assert!(text.contains("add"), "{text}");
1766 assert!(text.contains("pack"), "{text}");
1767 assert!(text.contains("shift"), "{text}");
1768 }
1769
1770 #[test]
1775 fn the_shipped_mm_malloc_asks_for_aligned_memory_and_gives_it_back() {
1776 let text = shipped(concat!(
1777 "#include <mm_malloc.h>\n",
1778 "void *get(void) { return _mm_malloc(64, 16); }\n",
1779 "void put(void *p) { _mm_free(p); }\n",
1780 ));
1781 assert!(text.contains("get"), "{text}");
1782 assert!(text.contains("put"), "{text}");
1783 }
1784
1785 #[test]
1797 fn the_shipped_xmmintrin_defines_the_sse_type_and_the_operations_over_it() {
1798 let text = shipped(concat!(
1799 "#include <xmmintrin.h>\n",
1800 "__m128 add(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1801 "__m128 one(__m128 a, __m128 b) { return _mm_max_ss(a, b); }\n",
1802 "__m128 mask(__m128 a, __m128 b) { return _mm_cmpnle_ps(a, b); }\n",
1803 "__m128 pick(__m128 a, __m128 b) { return _mm_shuffle_ps(a, b, _MM_SHUFFLE(0,1,2,3)); }\n",
1804 "int bits(__m128 a) { return _mm_movemask_ps(a); }\n",
1805 "int near(__m128 a) { return _mm_cvtss_si32(a); }\n",
1806 "__m128 wide(__m64 a) { return _mm_cvtpi16_ps(a); }\n",
1807 "void *room(void) { return _mm_malloc(64, 16); }\n",
1808 "void hint(const float *p) { _mm_prefetch(p, _MM_HINT_T0); _mm_sfence(); }\n",
1809 ));
1810 assert!(text.contains("add"), "{text}");
1811 assert!(text.contains("mask"), "{text}");
1812 assert!(text.contains("pick"), "{text}");
1813 assert!(text.contains("wide"), "{text}");
1814 }
1815
1816 #[test]
1823 fn the_shipped_xmmintrin_leaves_out_the_names_that_need_an_instruction() {
1824 let text = rucc_session::runtime::header("xmmintrin.h").expect("xmmintrin.h is shipped");
1825 for absent in [
1826 "_mm_sqrt_ps",
1827 "_mm_sqrt_ss",
1828 "_mm_rsqrt_ps",
1829 "_mm_rsqrt_ss",
1830 "_mm_getcsr",
1831 "_mm_setcsr",
1832 ] {
1833 let defined = text.contains(&format!("{absent}("));
1834 assert!(!defined, "{absent} is defined and the header says it is not");
1835 assert!(text.contains(absent), "{absent} is absent and unexplained");
1836 }
1837 }
1838
1839 #[test]
1840 fn the_shipped_emmintrin_defines_both_sse2_types_and_the_operations_over_them() {
1841 let text = shipped(concat!(
1842 "#include <emmintrin.h>\n",
1843 "__m128i add(__m128i a, __m128i b) { return _mm_add_epi64(a, b); }\n",
1844 "__m128i wide(__m128i a, __m128i b) { return _mm_mul_epu32(a, b); }\n",
1845 "__m128i pick(__m128i a) { return _mm_shuffle_epi32(a, _MM_SHUFFLE(0,1,2,3)); }\n",
1846 "__m128i up(__m128i a) { return _mm_slli_epi64(a, 13); }\n",
1847 "__m128i down(__m128i a) { return _mm_srli_si128(a, 3); }\n",
1848 "__m128i pack(__m128i a, __m128i b) { return _mm_packus_epi16(a, b); }\n",
1849 "int bits(__m128i a) { return _mm_movemask_epi8(a); }\n",
1850 "__m128d sum(__m128d a, __m128d b) { return _mm_add_sd(a, b); }\n",
1851 "__m128d mask(__m128d a, __m128d b) { return _mm_cmpunord_pd(a, b); }\n",
1852 "__m128i near(__m128d a) { return _mm_cvtpd_epi32(a); }\n",
1853 "__m128d over(__m128 a) { return _mm_cvtps_pd(a); }\n",
1854 "__m128i half(__m64 a) { return _mm_movpi64_epi64(a); }\n",
1855 "__m128i grab(void const *p) { return _mm_loadu_si128(p); }\n",
1856 "void wall(void) { _mm_lfence(); _mm_mfence(); }\n",
1857 ));
1858 assert!(text.contains("wide"), "{text}");
1859 assert!(text.contains("pack"), "{text}");
1860 assert!(text.contains("near"), "{text}");
1861 assert!(text.contains("half"), "{text}");
1862 }
1863
1864 #[test]
1868 fn the_shipped_immintrin_reaches_the_names_the_headers_under_it_define() {
1869 let text = shipped(concat!(
1870 "#include <immintrin.h>\n",
1871 "unsigned long long matching(unsigned char tag, unsigned char const *bucket) {\n",
1872 " __m128i const want = _mm_set1_epi8((char)tag);\n",
1873 " __m128i const chunk = _mm_loadu_si128((__m128i const *)(void const *)bucket);\n",
1874 " __m128i const same = _mm_cmpeq_epi8(chunk, want);\n",
1875 " return (unsigned long long)_mm_movemask_epi8(same);\n",
1876 "}\n",
1877 "__m64 narrow(__m64 a, __m64 b) { return _mm_add_pi32(a, b); }\n",
1878 "__m128 single(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1879 ));
1880 assert!(text.contains("matching"), "{text}");
1881 assert!(text.contains("narrow"), "the MMX header is not reached: {text}");
1882 assert!(text.contains("single"), "the SSE header is not reached: {text}");
1883 }
1884
1885 #[test]
1889 fn the_shipped_x86intrin_reaches_the_fences_windows_headers_ask_it_for() {
1890 let text = shipped(concat!(
1891 "#include <x86intrin.h>\n",
1892 "void barriers(void *p) {\n",
1893 " _mm_lfence();\n",
1894 " _mm_sfence();\n",
1895 " _mm_mfence();\n",
1896 " _mm_pause();\n",
1897 " _mm_clflush(p);\n",
1898 "}\n",
1899 "__m128i wide(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1900 ));
1901 assert!(text.contains("barriers"), "{text}");
1902 assert!(text.contains("wide"), "the SSE2 header is not reached: {text}");
1903 }
1904
1905 #[test]
1909 fn the_umbrella_and_the_header_under_it_can_both_be_included() {
1910 let text = shipped(concat!(
1911 "#include <immintrin.h>\n",
1912 "#include <emmintrin.h>\n",
1913 "#include <immintrin.h>\n",
1914 "#include <x86intrin.h>\n",
1915 "__m128i twice(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1916 ));
1917 assert!(text.contains("twice"), "{text}");
1918 }
1919
1920 #[test]
1924 fn the_shipped_emmintrin_leaves_out_the_two_square_roots() {
1925 let text = rucc_session::runtime::header("emmintrin.h").expect("emmintrin.h is shipped");
1926 for absent in ["_mm_sqrt_pd", "_mm_sqrt_sd"] {
1927 let defined = text.contains(&format!("{absent}("));
1928 assert!(!defined, "{absent} is defined and the header says it is not");
1929 assert!(text.contains(absent), "{absent} is absent and unexplained");
1930 }
1931 }
1932
1933 #[test]
1934 fn the_three_formality_headers_still_have_to_work() {
1935 let text = shipped(concat!(
1936 "#include <stdbool.h>\n",
1937 "#include <stdalign.h>\n",
1938 "#include <iso646.h>\n",
1939 "#include <stdnoreturn.h>\n",
1940 "int t = true and not false;\n",
1941 "_Alignas(16) char buf[16];\n",
1942 "int a = alignof(long);\n",
1943 ));
1944 assert!(text.contains("decl #0 t : int"), "{text}");
1945 assert!(text.contains("const 8 : unsigned long"), "{text}");
1946 }
1947
1948 #[test]
1956 fn every_shipped_header_can_be_included_twice() {
1957 let once: String = rucc_session::runtime::names()
1958 .iter()
1959 .map(|name| format!("#include <{name}>\n"))
1960 .collect();
1961 let twice = once.repeat(2);
1962 assert_eq!(shipped(&format!("{once}int x;\n")), shipped(&format!("{twice}int x;\n")));
1963 }
1964
1965 #[test]
1966 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
1967 let fs = MemoryFileSystem::new();
1968 let result = compile(&options(), "/nope.c", &fs);
1969 assert!(result.failed());
1970 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
1971 assert!(result.text().is_empty());
1972 }
1973
1974 #[test]
1975 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
1976 let text = tast("int x = 1;\n");
1977 let expected = "\
1978decl #0 x : int object external static defined
1979 init
1980 +0
1981 const 1 : int
1982";
1983 assert_eq!(text, expected);
1984 }
1985
1986 #[test]
1987 fn the_macros_are_expanded_before_anything_is_parsed() {
1988 let text = tast("#define N 2\nint a[N];\n");
1992 assert!(text.starts_with("decl #0 a : int[2] object external static tentative"), "{text}");
1993 }
1994
1995 #[test]
2001 fn a_pragma_is_not_a_declaration_and_the_parse_walks_past_the_ones_it_does_not_read() {
2002 let text = tast(concat!(
2003 "#pragma pack(4)\n",
2004 "struct s { int a; };\n",
2005 "#pragma pack()\n",
2006 "int b;\n",
2007 "_Pragma(\"GCC visibility push(default)\") int c;\n",
2008 ));
2009 assert!(text.contains("decl #0 b : int"), "{text}");
2010 assert!(text.contains("decl #1 c : int"), "{text}");
2011 }
2012
2013 #[test]
2021 fn the_layout_attributes_move_the_members_and_the_record_the_way_gcc_lays_them_out() {
2022 tast(concat!(
2023 "struct A { char c; int i; } __attribute__((packed));\n",
2024 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2025 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2026 "struct B { char c; int i; } __attribute__((aligned));\n",
2029 "_Static_assert(sizeof(struct B) == 16 && _Alignof(struct B) == 16, \"B\");\n",
2030 "struct C { char c; int i __attribute__((packed)); };\n",
2031 "_Static_assert(sizeof(struct C) == 5 && _Alignof(struct C) == 1, \"C\");\n",
2032 "_Static_assert(__builtin_offsetof(struct C, i) == 1, \"C.i\");\n",
2033 "struct D { char c; int i; } __attribute__((packed, aligned(4)));\n",
2034 "_Static_assert(sizeof(struct D) == 8 && _Alignof(struct D) == 4, \"D\");\n",
2035 "_Static_assert(__builtin_offsetof(struct D, i) == 1, \"D.i\");\n",
2036 "struct E { char c; _Alignas(8) int i; };\n",
2037 "_Static_assert(sizeof(struct E) == 16 && _Alignof(struct E) == 8, \"E\");\n",
2038 "_Static_assert(__builtin_offsetof(struct E, i) == 8, \"E.i\");\n",
2039 "struct F { char c; int i __attribute__((aligned(8))); };\n",
2040 "_Static_assert(sizeof(struct F) == 16 && _Alignof(struct F) == 8, \"F\");\n",
2041 "struct G { char c; short s; } __attribute__((aligned(2)));\n",
2044 "_Static_assert(sizeof(struct G) == 4 && _Alignof(struct G) == 2, \"G\");\n",
2045 "struct H { char c; int i; } __attribute__((aligned(2)));\n",
2046 "_Static_assert(sizeof(struct H) == 8 && _Alignof(struct H) == 4, \"H\");\n",
2047 "struct I { [[gnu::packed]] char c; int i; };\n",
2050 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2051 "struct J { char c; [[gnu::packed]] int i; };\n",
2052 "_Static_assert(sizeof(struct J) == 5 && _Alignof(struct J) == 1, \"J\");\n",
2053 "struct M { char c; int i : 5; int j : 20; } __attribute__((packed));\n",
2054 "_Static_assert(sizeof(struct M) == 5 && _Alignof(struct M) == 1, \"M\");\n",
2055 "struct N { char c; long long l; } __attribute__((aligned(32)));\n",
2056 "_Static_assert(sizeof(struct N) == 32 && _Alignof(struct N) == 32, \"N\");\n",
2057 "union L { char c; int i; } __attribute__((packed));\n",
2058 "_Static_assert(sizeof(union L) == 4 && _Alignof(union L) == 1, \"L\");\n",
2059 "struct O { char c; int i; } __attribute__((__packed__));\n",
2063 "_Static_assert(sizeof(struct O) == 5 && _Alignof(struct O) == 1, \"O\");\n",
2064 "struct P { char c; int i; } __attribute__((__aligned__(8)));\n",
2065 "_Static_assert(sizeof(struct P) == 8 && _Alignof(struct P) == 8, \"P\");\n",
2066 ));
2067 }
2068
2069 #[test]
2082 fn a_transparent_union_takes_the_member_a_value_fits_and_is_declared_either_way() {
2083 let text = tast(concat!(
2084 "struct one { int x; };\n",
2085 "struct two { long y; };\n",
2086 "typedef union { struct one *a; struct two *b; void *any; }\n",
2087 " __attribute__((__transparent_union__)) arg;\n",
2088 "int takes(arg v);\n",
2089 "int f(struct one *p, struct two *q, char *c) {\n",
2090 " return takes(p) + takes(q) + takes(c) + takes(0);\n",
2091 "}\n",
2092 "int takes(struct one *p);\n",
2094 "int (*as_a_member)(struct one *) = takes;\n",
2095 "int (*as_the_union)(arg) = takes;\n",
2096 ));
2097 assert!(text.contains("compound-literal"), "{text}");
2098 }
2099
2100 #[test]
2106 fn the_attribute_on_the_declarator_of_a_typedef_is_the_one_glibc_writes() {
2107 let text = tast(concat!(
2108 "struct sockaddr { int family; };\n",
2109 "struct sockaddr_in { int family; int addr; };\n",
2110 "typedef union { struct sockaddr *plain; struct sockaddr_in *inet; }\n",
2111 " addr_arg __attribute__((__transparent_union__));\n",
2112 "int bind_to(int fd, addr_arg where);\n",
2113 "int f(struct sockaddr_in *where) { return bind_to(0, where); }\n",
2114 ));
2115 assert!(text.contains("compound-literal"), "{text}");
2116 }
2117
2118 #[test]
2126 fn a_transparent_union_that_cannot_keep_the_promise_is_dropped_with_a_word_about_it() {
2127 let result = run(
2128 &options(),
2129 concat!(
2130 "union wider { int small; double large; } __attribute__((transparent_union));\n",
2131 "struct plain { int x; } __attribute__((transparent_union));\n",
2132 ),
2133 );
2134 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
2135 assert!(!result.failed(), "{:?}", result.messages);
2136 for message in &result.messages {
2137 assert!(message.contains("'transparent_union' attribute ignored"), "{message}");
2138 }
2139 assert!(result.messages[0].contains("first member"), "{:?}", result.messages);
2140 assert!(result.messages[1].contains("only a union"), "{:?}", result.messages);
2141 }
2142
2143 #[test]
2152 fn an_access_to_a_packed_member_says_the_alignment_the_layout_left_it() {
2153 let packed = body(concat!(
2154 "struct P { char c; int v; } __attribute__((packed));\n",
2155 "int f(struct P *p) { return p->v; }\n",
2156 ));
2157 assert!(packed.contains("load.i32 %2, align 1,"), "{packed}");
2158 let plain = body(concat!(
2160 "struct P { char c; int v; };\n",
2161 "int f(struct P *p) { return p->v; }\n",
2162 ));
2163 assert!(plain.contains("load.i32 %2, align 4,"), "{plain}");
2164 }
2165
2166 #[test]
2173 fn what_is_inside_a_packed_member_is_no_more_aligned_than_the_member_is() {
2174 let stepped = body(concat!(
2175 "struct P { char c; int v[4]; } __attribute__((packed));\n",
2176 "int f(struct P *p, int i) { return p->v[i]; }\n",
2177 ));
2178 assert!(stepped.contains(", align 1,"), "{stepped}");
2179 assert!(!stepped.contains(", align 4,"), "{stepped}");
2180 let nested = body(concat!(
2181 "struct Inner { int v; };\n",
2182 "struct P { char c; struct Inner in; } __attribute__((packed));\n",
2183 "int f(struct P *p) { return p->in.v; }\n",
2184 ));
2185 assert!(nested.contains(", align 1,"), "{nested}");
2186 assert!(!nested.contains(", align 4,"), "{nested}");
2187 }
2188
2189 #[test]
2205 fn an_access_through_a_typedef_that_lowered_its_alignment_says_the_one_the_typedef_asked_for() {
2206 let through = body(concat!(
2207 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2208 "unsigned int f(const void *p) { return *(const unalign32 *)p; }\n",
2209 ));
2210 assert!(through.contains("load.i32 %0, align 1,"), "{through}");
2211 let stepped = body(concat!(
2214 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2215 "unsigned int f(unalign32 *p, int i) { return p[i]; }\n",
2216 ));
2217 assert!(stepped.contains(", align 1,"), "{stepped}");
2218 assert!(!stepped.contains(", align 4,"), "{stepped}");
2219 let plain = body(concat!(
2222 "typedef unsigned int word;\n",
2223 "unsigned int f(const void *p) { return *(const word *)p; }\n",
2224 ));
2225 assert!(plain.contains("load.i32 %0, align 4,"), "{plain}");
2226 }
2227
2228 #[test]
2239 fn a_vector_read_through_a_typedef_that_lowered_its_alignment_comes_back_a_piece_at_a_time() {
2240 let prefix = concat!(
2241 "typedef long long v2di __attribute__((__vector_size__(16)));\n",
2242 "typedef long long v2di_u __attribute__((__vector_size__(16), __aligned__(1)));\n",
2243 );
2244 let loaded =
2245 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di_u *)p; }}"));
2246 assert_eq!(loaded.matches("align 1\n").count(), 2, "{loaded}");
2247 assert!(!loaded.contains("align 16"), "{loaded}");
2248 let stored = body(&format!("{prefix}void f(void *p, v2di b) {{ *(v2di_u *)p = b; }}"));
2251 assert!(stored.contains("memcpy %0, %3, size 16, align 1"), "{stored}");
2252 let aligned =
2254 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di *)p; }}"));
2255 assert!(aligned.contains("align 16"), "{aligned}");
2256 }
2257
2258 #[test]
2267 fn the_aligned_attribute_on_a_declaration_raises_what_that_one_object_is_aligned_to() {
2268 tast(concat!(
2269 "int v __attribute__((aligned(64)));\n",
2270 "_Static_assert(__alignof__(v) == 64, \"v\");\n",
2271 "__attribute__((aligned(32))) int w;\n",
2274 "_Static_assert(__alignof__(w) == 32, \"w\");\n",
2275 "[[gnu::aligned(16)]] int x;\n",
2276 "_Static_assert(__alignof__(x) == 16, \"x\");\n",
2277 "int y __attribute__((aligned(2)));\n",
2280 "_Static_assert(__alignof__(y) == 4, \"y\");\n",
2281 "void f(void) { int a __attribute__((aligned(128)));\n",
2283 "_Static_assert(__alignof__(a) == 128, \"a\"); (void)a; }\n",
2284 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2287 "void g(void) __attribute__((aligned(256)));\n",
2290 "void g(void) {}\n",
2291 "_Static_assert(__alignof__(g) == 256, \"g\");\n",
2292 ));
2293 }
2294
2295 #[test]
2299 fn what_a_declaration_asked_to_be_aligned_to_is_what_the_assembler_is_told() {
2300 let text = asm(concat!(
2301 "int v __attribute__((aligned(64)));\n",
2302 "void g(void) __attribute__((aligned(256)));\n",
2303 "void g(void) {}\n",
2304 "void plain(void) {}\n",
2305 ));
2306 assert!(text.contains("\t.p2align\t6\n\t.type\tv, @object\n"), "{text}");
2307 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2308 assert!(text.contains("\t.p2align\t4, 0x90\n\t.globl\tplain\n"), "{text}");
2309 }
2310
2311 #[test]
2317 fn the_alignment_the_command_line_asked_of_every_function_is_a_floor_under_all_of_them() {
2318 let source = concat!(
2319 "void g(void) __attribute__((aligned(256)));\n",
2320 "void g(void) {}\n",
2321 "void small(void) __attribute__((aligned(4)));\n",
2322 "void small(void) {}\n",
2323 "void plain(void) {}\n",
2324 );
2325 let listing = |align: Option<u32>| {
2326 let mut opts = options();
2327 opts.emit = EmitKind::Asm;
2328 opts.align_functions = align;
2329 let result = run(&opts, source);
2330 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
2331 result.text().to_owned()
2332 };
2333
2334 let text = listing(Some(32));
2335 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "the larger one wins: {text}");
2336 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tsmall\n"), "{text}");
2337 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tplain\n"), "{text}");
2338
2339 let text = listing(Some(8));
2342 assert!(text.contains("\t.p2align\t3, 0x90\n\t.globl\tplain\n"), "{text}");
2343 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2344 }
2345
2346 #[test]
2355 fn an_aligned_typedef_says_what_an_object_of_it_is_aligned_to_and_may_lower_it() {
2356 tast(concat!(
2357 "typedef int L __attribute__((aligned(2)));\n",
2358 "_Static_assert(__alignof__(L) == 2, \"L\");\n",
2359 "_Static_assert(_Alignof(L) == 2, \"L alignof\");\n",
2360 "_Static_assert(sizeof(L) == 4, \"L size\");\n",
2362 "struct T { char c; L x; };\n",
2363 "_Static_assert(sizeof(struct T) == 6, \"T\");\n",
2364 "_Static_assert(__builtin_offsetof(struct T, x) == 2, \"T.x\");\n",
2365 "typedef int H __attribute__((aligned(16)));\n",
2367 "_Static_assert(__alignof__(H) == 16, \"H\");\n",
2368 "_Static_assert(sizeof(H) == 4, \"H size\");\n",
2369 "struct U { char c; H x; };\n",
2370 "_Static_assert(sizeof(struct U) == 32, \"U\");\n",
2371 "_Static_assert(__builtin_offsetof(struct U, x) == 16, \"U.x\");\n",
2372 "typedef L M __attribute__((aligned(8)));\n",
2375 "_Static_assert(__alignof__(M) == 8, \"M\");\n",
2376 "typedef L N;\n",
2379 "_Static_assert(__alignof__(N) == 2, \"N\");\n",
2380 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2382 ));
2383 let text = asm(concat!(
2384 "typedef int L __attribute__((aligned(2)));\n",
2385 "typedef int H __attribute__((aligned(16)));\n",
2386 "L low;\n",
2387 "H high;\n",
2388 ));
2389 assert!(text.contains("\t.p2align\t1\n\t.type\tlow, @object\n"), "{text}");
2390 assert!(text.contains("\t.p2align\t4\n\t.type\thigh, @object\n"), "{text}");
2391 }
2392
2393 #[test]
2401 fn the_vector_size_attribute_builds_a_type_of_lanes_and_measures_it_in_bytes() {
2402 tast(concat!(
2403 "typedef int __attribute__((vector_size(16))) v4si;\n",
2404 "_Static_assert(sizeof(v4si) == 16 && _Alignof(v4si) == 16, \"v4si\");\n",
2405 "typedef char __attribute__((vector_size(16))) v16qi;\n",
2406 "_Static_assert(sizeof(v16qi) == 16, \"v16qi\");\n",
2407 "typedef int __attribute__((vector_size(4))) v1si;\n",
2410 "_Static_assert(sizeof(v1si) == 4, \"v1si\");\n",
2411 "typedef float __attribute__((__vector_size__(8))) v2sf;\n",
2413 "_Static_assert(sizeof(v2sf) == 8, \"v2sf\");\n",
2414 "typedef short [[gnu::vector_size(8)]] v4hi;\n",
2415 "_Static_assert(sizeof(v4hi) == 8, \"v4hi\");\n",
2416 "v4si g;\n",
2419 "_Static_assert(sizeof(g[0]) == 4, \"lane\");\n",
2420 "_Static_assert(sizeof(g + g) == 16, \"whole\");\n",
2421 "_Static_assert(sizeof(g + 1) == 16, \"broadcast\");\n",
2424 "_Static_assert(sizeof(v4si[3]) == 48, \"array\");\n",
2426 ));
2427 }
2428
2429 #[test]
2439 fn a_vector_is_written_whole_into_an_array_of_them_and_named_by_a_type_name() {
2440 tast(concat!(
2441 "typedef int __attribute__((vector_size(8))) v2si;\n",
2442 "v2si table[] = { (v2si){ 1, 2 }, (v2si){ 3, 4 } };\n",
2443 "_Static_assert(sizeof(table) == 16, \"two of them and not eight lanes\");\n",
2444 "v2si written = (int __attribute__((vector_size(8)))){ 5, 6 };\n",
2446 "_Static_assert(sizeof((int __attribute__((vector_size(16)))){ 0 }) == 16, \"named\");\n",
2447 "v2si lanes[2] = { 1, 2, 3, 4 };\n",
2450 "_Static_assert(sizeof(lanes) == 16, \"still elided\");\n",
2451 ));
2452 }
2453
2454 #[test]
2462 fn a_lane_is_assignable_and_a_shift_takes_a_count_of_its_own_lane() {
2463 let result = run(
2464 &options(),
2465 concat!(
2466 "typedef int __attribute__((vector_size(16))) v4si;\n",
2467 "typedef unsigned __attribute__((vector_size(16))) v4ui;\n",
2468 "void write(v4si *out, v4ui a, v4si b, int n) {\n",
2469 " v4si v = { 1, 2, 3, 4 };\n",
2470 " v[0] = n;\n",
2471 " v[1] += n;\n",
2472 " v[2]++;\n",
2473 " *&v[3] = n;\n",
2474 " v4ui shifted = a >> b;\n",
2476 " shifted <<= b;\n",
2477 " *out = v + (v4si)shifted + (1 << b);\n",
2480 "}\n",
2481 "void refused(const v4si c) {\n",
2484 " c[0] = 1;\n",
2485 "}\n",
2486 ),
2487 );
2488 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
2489 assert!(result.messages[0].contains("assignment of read-only"), "{:?}", result.messages);
2490 }
2491
2492 #[test]
2504 fn a_record_that_asks_for_the_other_byte_order_swaps_every_scalar_it_holds() {
2505 let read = "int f(struct s *p) { return p->i; }\n";
2506 let big = "struct s { int i; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
2507 assert!(body(&format!("{big}{read}")).contains("bswap"), "{big}");
2508
2509 let armoured =
2510 "struct s { int i; } __attribute__((__scalar_storage_order__(\"big-endian\")));\n";
2511 assert!(body(&format!("{armoured}{read}")).contains("bswap"), "{armoured}");
2512
2513 let front = "struct __attribute__((scalar_storage_order(\"big-endian\"))) s { int i; };\n";
2514 assert!(body(&format!("{front}{read}")).contains("bswap"), "{front}");
2515
2516 let standard = "struct s { int i; } [[gnu::scalar_storage_order(\"big-endian\")]];\n";
2517 assert!(body(&format!("{standard}{read}")).contains("bswap"), "{standard}");
2518
2519 let same =
2520 "struct s { int i; } __attribute__((scalar_storage_order(\"little-endian\")));\n";
2521 assert!(!body(&format!("{same}{read}")).contains("bswap"), "{same}");
2522
2523 let byte = "struct s { char c; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
2525 let source = format!("{byte}int f(struct s *p) {{ return p->c; }}\n");
2526 assert!(!body(&source).contains("bswap"), "{byte}");
2527
2528 tast(concat!(
2529 "struct s { int i; short h; char c; }",
2530 " __attribute__((scalar_storage_order(\"big-endian\")));\n",
2531 "_Static_assert(sizeof(struct s) == 8 && _Alignof(struct s) == 4, \"s\");\n",
2532 "_Static_assert(__builtin_offsetof(struct s, h) == 4, \"s.h\");\n",
2533 "_Static_assert(__builtin_offsetof(struct s, c) == 6, \"s.c\");\n",
2534 ));
2535 }
2536
2537 #[test]
2543 fn a_bit_field_in_one_of_those_records_is_counted_from_the_top_of_its_bytes() {
2544 let members = "short i : 12; char c1 : 1; char c2 : 1; char c3 : 1; char c4 : 1;";
2545 let read = "int f(struct s *p) { return p->i; }\n";
2546 let plain = format!("struct s {{ {members} }};\n{read}");
2547 let reversed = format!(
2548 "struct s {{ {members} }} __attribute__((scalar_storage_order(\"big-endian\")));\n\
2549 {read}"
2550 );
2551 assert!(body(&plain).contains("shl"), "{}", body(&plain));
2552 assert!(!body(&plain).contains("bswap"), "{}", body(&plain));
2553 let built = body(&reversed);
2556 assert!(built.contains("bswap"), "{built}");
2557 assert!(!built.contains("shl"), "{built}");
2558 assert!(built.contains("ashr"), "{built}");
2559 }
2560
2561 #[test]
2568 fn the_address_of_a_scalar_stored_the_other_way_round_is_refused() {
2569 let opts = options();
2570 let record = "struct s { int i; int a[2]; struct in { int n; } w; }\n\
2571 __attribute__((scalar_storage_order(\"big-endian\")));\n";
2572 let taken = format!("{record}int *f(struct s *p) {{ return &p->i; }}\n");
2573 assert_eq!(
2574 run(&opts, &taken).messages,
2575 ["/main.c:3:30: error: cannot take address of scalar with reverse storage order \
2576 [E0712]"]
2577 );
2578 let element = format!("{record}int *f(struct s *p) {{ return &p->a[0]; }}\n");
2579 let messages = run(&opts, &element).messages;
2580 assert!(messages[0].contains("[E0712]"), "{messages:?}");
2581
2582 let whole = format!("{record}int *f(struct s *p) {{ return (int *) &p->w; }}\n");
2583 assert_eq!(run(&opts, &whole).messages, Vec::<String>::new(), "{whole}");
2584 }
2585
2586 #[test]
2590 fn a_storage_order_that_names_neither_end_is_refused_with_the_two_words_that_are_taken() {
2591 let opts = options();
2592 let wrong = "struct s { int i; } __attribute__((scalar_storage_order(\"middle\")));\n";
2593 assert_eq!(
2594 run(&opts, wrong).messages,
2595 ["/main.c:1:36: error: 'scalar_storage_order' argument must be one of \"big-endian\" \
2596 or \"little-endian\" [E0688]"]
2597 );
2598 let bare = "struct s { int i; } __attribute__((scalar_storage_order));\n";
2599 let messages = run(&opts, bare).messages;
2600 assert!(messages[0].contains("[E0688]"), "{messages:?}");
2601 }
2602
2603 #[test]
2613 fn packing_is_what_decides_whether_a_bit_field_may_straddle_its_own_storage() {
2614 assert_eq!(bit_field_byte("struct s { int x : 12; char y : 6; };"), 2);
2616 assert_eq!(
2617 bit_field_byte("struct s { int x : 12; char y : 6; } __attribute__((packed));"),
2618 1
2619 );
2620 assert_eq!(
2621 bit_field_byte("struct s { int x : 12; __attribute__((packed)) char y : 6; };"),
2622 1
2623 );
2624 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { int x : 12; char y : 6; };"), 1);
2625 assert_eq!(bit_field_byte("struct s { char x; int y : 30; };"), 4);
2627 assert_eq!(bit_field_byte("struct s { char x; int y : 30; } __attribute__((packed));"), 1);
2628 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { char x; int y : 30; };"), 1);
2630 assert_eq!(bit_field_byte("#pragma pack(2)\nstruct s { char x; int y : 30; };"), 1);
2631 }
2632
2633 fn bit_field_byte(record: &str) -> u64 {
2635 let source = format!("{record}\nint f(struct s *p) {{ return p->y; }}\n");
2636 let body = body(&source);
2637 let Some((before, _)) = body.split_once("ptr_add") else { return 0 };
2638 let (_, constant) = before.rsplit_once("iconst.i64 ").expect("an offset constant");
2639 constant.lines().next().expect("a line").trim().parse().expect("a byte offset")
2640 }
2641
2642 #[test]
2648 fn an_attribute_among_the_specifiers_is_kept_beside_the_ones_written_in_front() {
2649 tast(concat!(
2650 "struct a { char c; __attribute__((aligned(8))) int i; };\n",
2651 "_Static_assert(sizeof(struct a) == 16 && _Alignof(struct a) == 8, \"a\");\n",
2652 "_Static_assert(__builtin_offsetof(struct a, i) == 8, \"a.i\");\n",
2653 "struct b { char c; __attribute__((packed)) int i; };\n",
2654 "_Static_assert(sizeof(struct b) == 5 && _Alignof(struct b) == 1, \"b\");\n",
2655 "_Static_assert(__builtin_offsetof(struct b, i) == 1, \"b.i\");\n",
2656 "typedef struct { char c; int i; } __attribute__((packed)) c;\n",
2657 "_Static_assert(sizeof(c) == 5 && _Alignof(c) == 1, \"c\");\n",
2658 ));
2659 }
2660
2661 #[test]
2667 fn pragma_pack_caps_every_member_and_is_read_where_the_body_closes() {
2668 tast(concat!(
2669 "#pragma pack(1)\n",
2670 "struct A { char c; int i; };\n",
2671 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2672 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2673 "#pragma pack()\n",
2674 "struct B { char c; int i; };\n",
2675 "_Static_assert(sizeof(struct B) == 8 && _Alignof(struct B) == 4, \"B\");\n",
2676 "#pragma pack(2)\n",
2677 "struct C { char c; int i; double d; };\n",
2678 "_Static_assert(sizeof(struct C) == 14 && _Alignof(struct C) == 2, \"C\");\n",
2679 "_Static_assert(__builtin_offsetof(struct C, d) == 6, \"C.d\");\n",
2680 "struct K { char c; int i __attribute__((aligned(8))); };\n",
2682 "_Static_assert(sizeof(struct K) == 6 && _Alignof(struct K) == 2, \"K\");\n",
2683 "_Static_assert(__builtin_offsetof(struct K, i) == 2, \"K.i\");\n",
2684 "struct J { char c; int i; } __attribute__((aligned(8)));\n",
2686 "_Static_assert(sizeof(struct J) == 8 && _Alignof(struct J) == 8, \"J\");\n",
2687 "#pragma pack()\n",
2688 "#pragma pack(push, 1)\n",
2689 "struct D { char c; short s; };\n",
2690 "_Static_assert(sizeof(struct D) == 3 && _Alignof(struct D) == 1, \"D\");\n",
2691 "#pragma pack(pop)\n",
2692 "struct E { char c; short s; };\n",
2693 "_Static_assert(sizeof(struct E) == 4 && _Alignof(struct E) == 2, \"E\");\n",
2694 "struct H { char c;\n",
2696 "#pragma pack(1)\n",
2697 " int i; };\n",
2698 "_Static_assert(sizeof(struct H) == 5 && _Alignof(struct H) == 1, \"H\");\n",
2699 "#pragma pack(1)\n",
2700 "struct I { char c;\n",
2701 "#pragma pack()\n",
2702 " int i; };\n",
2703 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2704 "#pragma pack()\n",
2705 "#pragma pack(push, 8)\n",
2707 "#pragma pack(push, 1)\n",
2708 "struct P { char c; int i; };\n",
2709 "_Static_assert(sizeof(struct P) == 5 && _Alignof(struct P) == 1, \"P\");\n",
2710 "#pragma pack(pop)\n",
2711 "struct Q { char c; int i; };\n",
2712 "_Static_assert(sizeof(struct Q) == 8 && _Alignof(struct Q) == 4, \"Q\");\n",
2713 "#pragma pack(pop)\n",
2714 "#pragma pack(16)\n",
2716 "struct R { char c; int i; };\n",
2717 "_Static_assert(sizeof(struct R) == 8 && _Alignof(struct R) == 4, \"R\");\n",
2718 "#pragma pack()\n",
2719 "#pragma pack(1)\n",
2720 "struct S { char c; int i : 5; int j : 20; };\n",
2721 "_Static_assert(sizeof(struct S) == 5 && _Alignof(struct S) == 1, \"S\");\n",
2722 "union T { char c; int i; };\n",
2723 "_Static_assert(sizeof(union T) == 4 && _Alignof(union T) == 1, \"T\");\n",
2724 "#pragma pack()\n",
2725 ));
2726 }
2727
2728 #[test]
2732 fn a_pack_line_that_is_not_one_is_reported_in_the_words_gcc_uses() {
2733 let result = run(
2734 &options(),
2735 concat!(
2736 "#pragma pack 4\n",
2737 "#pragma pack(pop)\n",
2738 "#pragma pack(3)\n",
2739 "#pragma pack(1) junk\n",
2740 "#pragma pack(push, 1\n",
2741 "#pragma pack(x)\n",
2742 "#pragma pack(0)\n",
2745 "#pragma pack(push)\n",
2746 "struct s { char c; int i; };\n",
2747 "#pragma pack(pop)\n",
2748 "#pragma pack(pop, foo)\n",
2749 ),
2750 );
2751 let expected = [
2752 "missing `(` after `#pragma pack` - ignored",
2753 "`#pragma pack (pop)` encountered without matching `#pragma pack (push)`",
2754 "alignment must be a small power of two, not 3",
2755 "junk at end of `#pragma pack`",
2756 "malformed `#pragma pack(push[, id][, <n>])` - ignored",
2757 "unknown action `x` for `#pragma pack` - ignored",
2758 "`#pragma pack(pop, foo)` encountered without matching `#pragma pack(push, foo)`",
2759 ];
2760 assert_eq!(result.messages.len(), expected.len(), "{:?}", result.messages);
2761 for (message, want) in result.messages.iter().zip(expected) {
2762 assert!(message.contains(want), "expected {want:?} in {message:?}");
2763 }
2764 }
2765
2766 #[test]
2773 fn a_declaration_behind_an_empty_macro_is_not_eaten_by_the_pragma_above_it() {
2774 let result = run(
2775 &options(),
2776 concat!(
2777 "#pragma pack(push, 1)\n",
2778 "#pragma pack(pop)\n",
2779 "#define API\n",
2780 "API const char version[] = \"3.53.4\";\n",
2781 "const char *get(void) { return version; }\n",
2782 ),
2783 );
2784 assert!(result.messages.is_empty(), "{:?}", result.messages);
2785 }
2786
2787 #[test]
2791 fn the_wide_integer_answers_to_all_three_of_its_names() {
2792 let text = tast("__uint128_t a; __int128_t b; unsigned __int128 c;\n");
2793 assert!(text.contains("decl #0 a : unsigned __int128"), "{text}");
2794 assert!(text.contains("decl #1 b : __int128"), "{text}");
2795 assert!(text.contains("decl #2 c : unsigned __int128"), "{text}");
2796 }
2797
2798 #[test]
2799 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
2800 let text = tast("long f(int a, long b) { return a + b; }\n");
2804 assert!(text.contains("convert arithmetic"), "{text}");
2805 }
2806
2807 #[test]
2808 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
2809 for source in [
2810 "#error stop\n",
2811 "int f(void) { return 1 + ; }\n",
2812 "int f(void) { return undeclared; }\n",
2813 ] {
2814 let result = run(&options(), source);
2815 assert!(result.failed(), "expected this to fail:\n{source}");
2816 assert!(
2817 result.text().is_empty(),
2818 "a file that did not compile wrote a tree:\n{source}"
2819 );
2820 }
2821 }
2822
2823 #[test]
2824 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
2825 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
2829 assert_eq!(result.errors, 1, "{:?}", result.messages);
2830 }
2831
2832 #[test]
2833 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
2834 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
2838 assert_eq!(result.errors, 1, "{:?}", result.messages);
2839 }
2840
2841 #[test]
2842 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
2843 let source = "int f(void) { char c = 300; return c; }\n";
2844 let plain = run(&options(), source);
2845 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
2846 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
2847 assert!(!plain.text().is_empty(), "a warning is not a reason to write nothing");
2848
2849 let mut opts = options();
2850 opts.warnings_are_errors = true;
2851 let strict = run(&opts, source);
2852 assert!(strict.failed());
2853 assert!(strict.text().is_empty(), "and under -Werror it is a reason to write nothing");
2854 for message in &strict.messages {
2855 assert!(!message.contains("warning:"), "{message}");
2856 }
2857 }
2858
2859 #[test]
2860 fn w_drops_the_warning_before_werror_can_promote_it() {
2861 let source = "int f(void) { char c = 300; return c; }\n";
2862 let mut opts = options();
2863 opts.warnings = false;
2864 let quiet = run(&opts, source);
2865 assert_eq!(quiet.messages, Vec::<String>::new());
2866 assert_eq!(quiet.errors, 0);
2867 assert!(!quiet.text().is_empty(), "and the file still compiles");
2868
2869 opts.warnings_are_errors = true;
2872 let both = run(&opts, source);
2873 assert_eq!(both.messages, Vec::<String>::new());
2874 assert!(!both.failed(), "-w -Werror is not an error about a warning nobody saw");
2875 }
2876
2877 #[test]
2878 fn the_dialect_reaches_the_keywords_and_the_checking() {
2879 let source = "typeof(1) x;\n";
2882 let mut opts = options();
2883 opts.std = Std::C23;
2884 opts.gnu_extensions = false;
2885 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
2886
2887 opts.std = Std::C17;
2888 assert!(run(&opts, source).failed());
2889 }
2890
2891 #[test]
2892 fn asking_for_a_kind_that_is_not_written_yet_runs_the_front_end_and_writes_nothing() {
2893 let mut opts = options();
2894 opts.emit = EmitKind::Object;
2895 let result = run(&opts, "int x = 1;\n");
2896 assert!(!result.failed(), "{:?}", result.messages);
2897 assert!(result.text().is_empty());
2898 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
2901 }
2902
2903 fn mir(source: &str) -> String {
2905 let mut opts = options();
2906 opts.emit = EmitKind::MirFinal;
2907 let result = run(&opts, source);
2908 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2909 result.text().to_owned()
2910 }
2911
2912 #[test]
2918 fn a_function_goes_from_c_to_instructions_with_real_registers_in_them() {
2919 let text = mir("int add(int a, int b) { return a + b; }\n");
2920 assert!(text.starts_with("mfunc @add {"), "{text}");
2921 assert!(text.contains("x64.add_rr_32"), "{text}");
2922 assert!(text.contains("x64.ret"), "{text}");
2923 assert!(!text.contains('%'), "{text}");
2926 }
2927
2928 #[test]
2930 fn a_function_with_no_body_produces_no_machine_function() {
2931 let text = mir("int g(int);\nint f(int a) { return g(a); }\n");
2932 assert_eq!(text.matches("mfunc @").count(), 1, "{text}");
2933 assert!(text.contains("mfunc @f {"), "{text}");
2934 assert!(text.contains("x64.call"), "{text}");
2935 }
2936
2937 #[test]
2939 fn every_definition_in_the_file_is_generated_and_they_keep_their_order() {
2940 let text = mir("int a(int x) { return x; }\nint b(int x) { return x; }\n");
2941 let first = text.find("mfunc @a").expect("the first function");
2942 let second = text.find("mfunc @b").expect("the second function");
2943 assert!(first < second, "{text}");
2944 }
2945
2946 #[test]
2948 fn the_target_decides_which_convention_the_generated_code_follows() {
2949 let mut opts = options();
2950 opts.emit = EmitKind::MirFinal;
2951 let linux = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2952 assert!(linux.contains("$rdi"), "{linux}");
2953
2954 opts.target = "x86_64-pc-windows-msvc".parse::<Triple>().unwrap();
2955 let windows = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2956 assert!(windows.contains("$rcx"), "{windows}");
2957 assert!(!windows.contains("$rdi"), "{windows}");
2958 }
2959
2960 #[test]
2968 fn a_tagged_member_with_no_name_is_a_member_on_windows_and_nothing_on_linux() {
2969 let source = concat!(
2970 "struct S { union U { int i; void *p; }; unsigned long tymed; };\n",
2971 "int size(void) { return sizeof(struct S); }\n",
2972 "int f(struct S *s) { s->i = 1; return s->i; }\n",
2973 );
2974
2975 let mut opts = options();
2976 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
2977 let windows = run(&opts, source);
2978 assert!(windows.messages.is_empty(), "{:?}", windows.messages);
2979
2980 let linux = run(&options(), source);
2981 assert_eq!(linux.messages.len(), 3, "{:?}", linux.messages);
2982 assert!(linux.messages[0].contains("does not declare anything"), "{:?}", linux.messages);
2983
2984 let mut opts = options();
2987 opts.ms_extensions = Some(true);
2988 let asked = run(&opts, source);
2989 assert!(asked.messages.is_empty(), "{:?}", asked.messages);
2990 }
2991
2992 #[test]
2994 fn a_target_this_has_no_back_end_for_is_reported_rather_than_generated() {
2995 let mut opts = options();
2996 opts.emit = EmitKind::MirFinal;
2997 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2998 let result = run(&opts, "int f(int a) { return a; }\n");
2999 assert!(result.failed());
3000 assert!(result.messages[0].contains("no back end for aarch64"), "{:?}", result.messages);
3001 assert!(result.text().is_empty());
3002 }
3003
3004 #[test]
3016 fn a_construct_the_back_end_cannot_reach_yet_is_reported_against_its_function() {
3017 let mut opts = options();
3018 opts.emit = EmitKind::MirFinal;
3019 let source = "void a(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
3020 s; s.x = 1; v[0] = s.x; }\n\
3021 void b(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
3022 s; s.x = 1; v[0] = s.x; }\n";
3023 let result = run(&opts, source);
3024 assert!(result.failed());
3025 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
3026 assert!(result.messages[0].contains("cannot generate code for 'a'"), "{:?}", result);
3027 assert!(result.messages[0].contains("wants more alignment"), "{:?}", result);
3028 assert!(result.messages[1].contains("cannot generate code for 'b'"), "{:?}", result);
3029 assert!(result.text().is_empty());
3030 }
3031
3032 #[test]
3040 fn a_variable_length_array_walks_its_pages_where_every_page_of_the_frame_is_to_be_touched() {
3041 let mut opts = options();
3042 opts.emit = EmitKind::MirFinal;
3043 let source = "void a(int n) { int v[n]; v[0] = 1; }\n";
3044 let plain = run(&opts, source);
3045 assert!(!plain.failed(), "{:?}", plain.messages);
3046 assert!(!plain.text().contains("cmp_set_a_64"), "{}", plain.text());
3047
3048 opts.stack_clash = true;
3049 let result = run(&opts, source);
3050 assert!(!result.failed(), "{:?}", result.messages);
3051 assert!(result.text().contains("cmp_set_a_64"), "{}", result.text());
3052 assert!(result.text().contains("or_mi_8"), "{}", result.text());
3053 }
3054
3055 #[test]
3065 fn a_function_that_keeps_a_frame_pointer_on_windows_reaches_an_object_file() {
3066 let mut opts = options();
3067 opts.emit = EmitKind::Object;
3068 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3069 let source = concat!(
3070 "void use(void *p);\n",
3071 "void array(int n) { int v[n]; v[0] = 1; use(v); }\n",
3072 "void taken(unsigned long n) { use(__builtin_alloca(n)); }\n",
3073 );
3074 let result = run(&opts, source);
3075 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3076 let bytes = match result.artifact {
3077 Artifact::Object { bytes, .. } => bytes,
3078 other => panic!("expected an object, got {other:?}"),
3079 };
3080 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3081
3082 let mut opts = options();
3085 opts.emit = EmitKind::Object;
3086 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3087 }
3088
3089 #[test]
3100 fn the_address_of_a_function_this_file_only_declares_reaches_a_windows_object() {
3101 let source = concat!(
3102 "void other(void *p);\n",
3103 "void takes(void (*f)(void *));\n",
3104 "void (*held)(void *);\n",
3105 "void pass(void) { takes(other); }\n",
3106 "void keep(void) { held = other; }\n",
3107 "void call(void) { other(0); }\n",
3108 );
3109 let mut opts = options();
3110 opts.emit = EmitKind::Object;
3111 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3112 let result = run(&opts, source);
3113 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3114 let bytes = match result.artifact {
3115 Artifact::Object { bytes, .. } => bytes,
3116 other => panic!("expected an object, got {other:?}"),
3117 };
3118 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3119
3120 let mut opts = options();
3123 opts.emit = EmitKind::Object;
3124 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3125 }
3126
3127 #[test]
3141 fn an_opcode_with_no_name_in_the_rule_language_is_named_by_its_own_spelling() {
3142 let mut opts = options();
3143 opts.emit = EmitKind::MirFinal;
3144 let source =
3145 "long double f(int a) {\n __int128 wide = a;\n return (long double) wide;\n}\n";
3146 let result = run(&opts, source);
3147 assert!(result.failed());
3148 assert!(
3149 result.messages[0].contains("no rule lowers a `sext` producing a `i128`"),
3150 "{result:?}"
3151 );
3152 assert!(result.messages[0].contains(":2:"), "the line the widening is on: {result:?}");
3153 assert!(!result.messages[0].contains("this instruction"), "{result:?}");
3154 }
3155
3156 #[test]
3158 fn the_note_on_unfinished_work_points_at_the_issues_rather_than_at_the_plan() {
3159 let mut opts = options();
3160 opts.emit = EmitKind::MirFinal;
3161 let source = "long double f(int a) { __int128 wide = a; return (long double) wide; }\n";
3162 let result = run(&opts, source);
3163 assert!(result.failed());
3164 let note = result.messages.iter().find(|line| line.contains("note:")).expect("a note");
3165 assert!(note.contains("https://github.com/tamnd/rucc/issues"), "{note}");
3166 assert!(!note.contains("spec/17-milestones.md"), "{note}");
3167 }
3168
3169 #[test]
3171 fn the_frame_flags_on_the_command_line_reach_the_generated_frame() {
3172 let source = "int f(int a) { return a; }\n";
3173 assert!(!mir(source).contains("$rbp"), "a leaf needs no frame pointer by default");
3174
3175 let mut opts = options();
3176 opts.emit = EmitKind::MirFinal;
3177 opts.frame_pointer = true;
3178 let kept = run(&opts, source).text().to_owned();
3179 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
3180 }
3181
3182 fn asm(source: &str) -> String {
3184 let mut opts = options();
3185 opts.emit = EmitKind::Asm;
3186 let result = run(&opts, source);
3187 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3188 result.text().to_owned()
3189 }
3190
3191 #[test]
3198 fn a_function_goes_from_c_to_assembly_an_assembler_would_take() {
3199 let text = asm("int add(int a, int b) { return a + b; }\n");
3200 assert!(text.contains("\t.globl\tadd\n"), "{text}");
3201 assert!(text.contains("\t.type\tadd, @function\n"), "{text}");
3202 assert!(text.contains("\nadd:\n"), "{text}");
3203 assert!(text.contains("\taddl\t"), "{text}");
3204 assert!(text.contains("\tret\n"), "{text}");
3205 assert!(text.contains("\t.size\tadd, .-add\n"), "{text}");
3206 assert!(text.contains(".note.GNU-stack"), "{text}");
3209 }
3210
3211 #[test]
3217 fn a_call_through_a_function_pointer_goes_through_the_register_it_is_in() {
3218 let text = asm("int g(int);\nint f(int (*p)(int), int a) { return p(a) + g(a); }\n");
3219 assert!(text.contains("\tcall\t*%"), "{text}");
3220 assert!(text.contains("\tcall\tg\n"), "{text}");
3221 assert!(text.contains("%rdi"), "{text}");
3225 }
3226
3227 #[test]
3231 fn the_address_of_a_global_is_read_from_the_instruction_pointer() {
3232 let text = asm("extern int counter;\nint f(void) { return counter; }\n");
3233 assert!(text.contains("\tmovl\tcounter(%rip), %eax\n"), "{text}");
3234 }
3235
3236 #[test]
3245 fn a_branch_on_a_comparison_jumps_on_the_opposite_of_what_it_compared() {
3246 let arms = "return 1; return 2;";
3247 let signed = [("==", "jne"), ("!=", "je"), ("<", "jge"), ("<=", "jg"), (">", "jle")];
3248 for (operator, jump) in signed.into_iter().chain([(">=", "jl")]) {
3249 let text = asm(&format!("int f(int a, int b) {{ if (a {operator} b) {arms} }}\n"));
3250 assert!(
3251 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
3252 "{operator}: {text}"
3253 );
3254 assert!(!text.contains("\tset"), "{operator}: {text}");
3255 assert!(!text.contains("\ttest"), "{operator}: {text}");
3256 }
3257 let unsigned = [("<", "jae"), ("<=", "ja"), (">", "jbe"), (">=", "jb")];
3258 for (operator, jump) in unsigned {
3259 let source =
3260 format!("int f(unsigned a, unsigned b) {{ if (a {operator} b) {arms} }}\n");
3261 let text = asm(&source);
3262 assert!(
3263 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
3264 "{operator}: {text}"
3265 );
3266 }
3267
3268 let text = asm("int f(int a) { if (a < 7) return 1; return 2; }\n");
3271 assert!(text.contains("\tcmpl\t$7, %edi\n\tjge\t"), "{text}");
3272 }
3273
3274 #[test]
3280 fn a_comparison_whose_answer_the_program_wanted_still_writes_a_byte() {
3281 let text = asm("int f(int a, int b) { return a < b; }\n");
3282 assert!(text.contains("\tsetl\t"), "{text}");
3283 }
3284
3285 fn optimized(source: &str) -> String {
3287 let mut opts = options();
3288 opts.emit = EmitKind::Asm;
3289 opts.opt_level = rucc_session::OptLevel::O2;
3290 let result = run(&opts, source);
3291 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3292 result.text().to_owned()
3293 }
3294
3295 #[test]
3305 fn a_switch_whose_arms_are_a_function_of_the_label_is_a_range_check_and_arithmetic() {
3306 let arms: String =
3307 (0..16).map(|k| format!("case {k}: return {};", k + 1)).collect::<Vec<_>>().join(" ");
3308 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3309 assert!(text.contains("\tcmpl\t$15, %edi\n\tja\t"), "{text}");
3310 assert!(text.contains("\taddl\t$1, %edi"), "{text}");
3311 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
3312 }
3313
3314 #[test]
3322 fn a_dense_switch_whose_arms_are_not_a_line_is_a_load_from_a_table() {
3323 let arms: String = (0..16)
3324 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
3325 .collect::<Vec<_>>()
3326 .join(" ");
3327 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3328 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
3329 assert!(!text.contains("\tjmp\t*"), "{text}");
3330 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
3331 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
3332 let section = text[..text.find("CSWTCH.0:").unwrap_or(0)].rfind("\t.section\t.rodata");
3333 assert!(section.is_some(), "{text}");
3334 assert_eq!(table.matches("\t.long\t").count(), 16, "{text}");
3335 assert!(table.contains("\t.long\t100\n"), "{text}");
3336 }
3337
3338 #[test]
3344 fn a_table_at_os_has_cells_as_narrow_as_its_answers() {
3345 let arms: String = (0..16)
3346 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
3347 .collect::<Vec<_>>()
3348 .join(" ");
3349 let mut opts = options();
3350 opts.emit = EmitKind::Asm;
3351 opts.opt_level = rucc_session::OptLevel::Os;
3352 let result = run(&opts, &format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3353 assert_eq!(result.messages, Vec::<String>::new());
3354 let text = result.text();
3355 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
3356 assert_eq!(table.matches("\t.byte\t").count(), 16, "{text}");
3357 assert!(text.contains("\tmovsbl\t"), "{text}");
3358 }
3359
3360 #[test]
3367 fn a_table_that_covers_its_operand_has_no_range_check() {
3368 let text = optimized(
3369 "int f(unsigned x) { switch (x & 3) { case 0: return 5; case 1: return 9; \
3370 case 2: return 2; case 3: return 7; } return -1; }\n",
3371 );
3372 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
3373 assert!(!text.contains("\tcmp"), "{text}");
3374 assert!(!text.contains("$-1"), "{text}");
3375 }
3376
3377 #[test]
3382 fn a_store_to_a_local_the_loop_just_read_is_a_conditional_move() {
3383 let text = optimized(
3384 "int f(const int *v, int n, int k) { int best[8] = {0}; \
3385 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; \
3386 return best[k & 7]; }\n",
3387 );
3388 assert!(text.contains("\tcmovgl"), "{text}");
3389 assert!(!text.contains("\tset"), "{text}");
3390 assert!(!text.contains("\ttestb"), "{text}");
3391 }
3392
3393 #[test]
3395 fn a_store_to_a_global_the_loop_just_read_keeps_its_branch() {
3396 let text = optimized(
3397 "int best[8]; void f(const int *v, int n) { \
3398 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; }\n",
3399 );
3400 assert!(!text.contains("\tcmov"), "{text}");
3401 }
3402
3403 #[test]
3411 fn a_conversion_from_a_constant_double_is_the_number_it_converts_to() {
3412 let text = optimized("int f(void) { double d = 2.75; return (int) d; }\n");
3413 assert!(text.contains("movl\t$2, %eax"), "{text}");
3414 assert!(!text.contains("cvttsd2si"), "{text}");
3415 }
3416
3417 #[test]
3425 fn a_slot_of_a_read_only_table_is_the_value_the_table_holds() {
3426 let text =
3427 optimized("static const int t[4] = {10, 20, 30, 40};\nint f(void) { return t[2]; }\n");
3428 assert!(text.contains("movl\t$30, %eax"), "{text}");
3429 assert!(!text.contains("t(%rip)"), "{text}");
3430 }
3431
3432 #[test]
3435 fn a_byte_of_a_read_only_string_is_the_byte_the_string_spells() {
3436 let text = optimized("static const char s[] = \"abc\";\nint f(void) { return s[1]; }\n");
3437 assert!(text.contains("movl\t$98, %eax"), "{text}");
3438 }
3439
3440 #[test]
3444 fn a_table_that_is_not_read_only_keeps_its_load() {
3445 let text = optimized(
3446 "static int t[4] = {10, 20, 30, 40};\nvoid g(int x) { t[2] = x; }\nint f(void) { return t[2]; }\n",
3447 );
3448 assert!(!text.contains("movl\t$30, %eax"), "{text}");
3449 }
3450
3451 #[test]
3458 fn a_call_guarded_by_a_condition_a_read_only_object_settles_is_not_emitted() {
3459 let text = optimized(
3460 "void link_error(void);\nconst double one = 1.0;\nint main(void) { if ((int) one != 1) link_error(); return 0; }\n",
3461 );
3462 assert!(!text.contains("call\tlink_error"), "{text}");
3463 }
3464
3465 #[test]
3467 fn a_cast_between_a_pointer_and_an_integer_leaves_the_value_where_it_is() {
3468 let text = asm("long f(void *p) { return (long)p; }\n");
3469 for line in text.lines().filter(|line| line.starts_with('\t') && !line.contains('.')) {
3474 let mnemonic = line.split_whitespace().next().unwrap_or("");
3475 assert!(matches!(mnemonic, "movq" | "ret"), "{line} in\n{text}");
3476 }
3477 }
3478
3479 #[test]
3483 fn an_argument_past_the_last_register_is_read_out_of_the_caller_s_stack() {
3484 let six = "long a, long b, long c, long d, long e, long f";
3485 let text = asm(&format!("long f({six}, long g, long h) {{ return g + h; }}\n"));
3486
3487 assert!(text.contains("\tmovq\t8(%rsp), "), "{text}");
3494 assert!(text.contains("\taddq\t16(%rsp), "), "{text}");
3495
3496 let narrow = asm(&format!("int f({six}, int g) {{ return g; }}\n"));
3500 assert!(narrow.contains("\tmovl\t8(%rsp), "), "{narrow}");
3501 let eight =
3502 "double a, double b, double c, double d, double e, double f, double g, double h";
3503 let float = asm(&format!("double f({eight}, double i) {{ return i; }}\n"));
3504 assert!(float.contains("\tmovsd\t8(%rsp), "), "{float}");
3505 }
3506
3507 #[test]
3510 fn a_call_writes_the_arguments_with_no_register_left_at_the_stack_pointer() {
3511 let six = "1, 2, 3, 4, 5, 6";
3512 let decl = "long g(long, long, long, long, long, long, long, long);\n";
3513 let text = asm(&format!("{decl}long f(void) {{ return g({six}, 7, 8); }}\n"));
3514
3515 assert!(text.contains("\tmovq\t%"), "{text}");
3516 assert!(text.contains(", (%rsp)\n"), "{text}");
3517 assert!(text.contains(", 8(%rsp)\n"), "{text}");
3518 assert!(text.contains("\tsubq\t$"), "{text}");
3520
3521 let narrow = "int g(int, int, int, int, int, int, int);\n";
3523 let text = asm(&format!("{narrow}int f(void) {{ return g({six}, 7); }}\n"));
3524 assert!(text.contains("\tmovl\t%"), "{text}");
3525 assert!(text.contains(", (%rsp)\n"), "{text}");
3526 }
3527
3528 #[test]
3531 fn a_variadic_call_counts_registers_and_not_arguments() {
3532 let nine = "1., 2., 3., 4., 5., 6., 7., 8., 9.";
3533 let decl = "int g(int, ...);\n";
3534 let text = asm(&format!("{decl}int f(void) {{ return g(0, {nine}); }}\n"));
3535
3536 assert!(text.contains("\tmovl\t$8, "), "eight registers, not nine: {text}");
3537 assert!(text.contains("\tmovsd\t%"), "{text}");
3538 assert!(text.contains(", (%rsp)\n"), "{text}");
3539 }
3540
3541 #[test]
3546 fn a_variadic_function_writes_the_argument_registers_it_was_handed_into_its_frame() {
3547 let body =
3548 "__builtin_va_list ap; __builtin_va_start(ap, n); __builtin_va_end(ap); return n;";
3549 let text = asm(&format!("int f(int n, ...) {{ {body} }}\n"));
3550
3551 let stores = |mnemonic: &str| text.matches(&format!("\t{mnemonic}\t%")).count();
3554 assert!(text.contains(", 8(%r"), "the second slot, not the first: {text}");
3555 assert!(!text.contains(", 0(%r"), "{text}");
3556 assert_eq!(stores("movaps"), 8, "every vector register: {text}");
3559 assert_eq!(stores("movsd"), 0, "and the whole of each one: {text}");
3560
3561 assert!(text.contains("\tsubq\t$"), "{text}");
3563 }
3564
3565 #[test]
3568 fn va_start_writes_the_four_fields_the_psabi_describes() {
3569 let start = "__builtin_va_list ap; __builtin_va_start(ap, d);";
3570 let params = "int a, int b, int c, double d";
3571 let text = asm(&format!("int f({params}, ...) {{ {start} return a; }}\n"));
3572
3573 assert!(text.contains(" movl $24, "), "{text}");
3577 assert!(text.contains(" movl $64, "), "{text}");
3578 assert!(text.contains(", 8(%r"), "{text}");
3582 assert!(text.contains(", 16(%r"), "{text}");
3583 let frame: u32 = text
3584 .lines()
3585 .find_map(|line| line.trim().strip_prefix("subq $")?.split(',').next()?.parse().ok())
3586 .expect("a variadic function takes a frame for the save area");
3587 let above = |line: &str| {
3588 let at: u32 = line.trim().strip_prefix("leaq ")?.split('(').next()?.parse().ok()?;
3589 Some(at > frame)
3590 };
3591 assert!(text.lines().filter_map(above).any(|it| it), "{frame}: {text}");
3592 }
3593
3594 #[test]
3597 fn va_arg_branches_on_whether_the_argument_is_still_in_the_save_area() {
3598 let read = "__builtin_va_list ap; __builtin_va_start(ap, n);";
3599 let ints = format!("int f(int n, ...) {{ {read} return __builtin_va_arg(ap, int); }}\n");
3600 let text = asm(&ints);
3601
3602 assert!(text.contains("$40, "), "{text}");
3605 assert!(text.contains(" cmpl "), "{text}");
3606 assert!(text.contains(" ja "), "{text}");
3610
3611 let arg = "__builtin_va_arg(ap, double)";
3612 let text = asm(&format!("double f(int n, ...) {{ {read} return {arg}; }}\n"));
3613 assert!(text.contains("$160, "), "the last vector slot: {text}");
3614 }
3615
3616 #[test]
3619 fn a_structure_assignment_is_a_move_for_each_word_of_it() {
3620 let decl = "struct pair { long a, b; };\n";
3621 let body = "struct pair p = *q; return p.a + p.b;";
3622 let text = asm(&format!("{decl}long f(struct pair *q) {{ {body} }}\n"));
3623
3624 assert!(!text.contains("memcpy"), "nothing calls the library: {text}");
3625 assert!(!text.contains("\tcall"), "{text}");
3626 assert!(text.matches("\tmovq\t").count() >= 4, "two words each way: {text}");
3628 }
3629
3630 #[test]
3633 fn how_wide_a_word_of_a_copy_is_follows_the_alignment() {
3634 let decl = "struct bytes { char a[8]; };\n";
3635 let body = "struct bytes p = *q; return p.a[0];";
3636 let text = asm(&format!("{decl}int f(struct bytes *q) {{ {body} }}\n"));
3637
3638 assert!(text.matches("\tmovb\t").count() >= 16, "a byte at a time: {text}");
3640 }
3641
3642 #[test]
3645 fn the_part_of_an_initialiser_that_names_nothing_is_stored_as_zero() {
3646 let decl = "struct wide { long a, b, c; };\n";
3647 let text = asm(&format!("{decl}long f(void) {{ struct wide w = {{ 7 }}; return w.c; }}\n"));
3648
3649 assert!(!text.contains("memset"), "nothing calls the library: {text}");
3650 assert!(text.contains("\tmovq\t$0, ") || text.contains("\txorl\t"), "the zero: {text}");
3655 }
3656
3657 #[test]
3660 fn a_copy_too_large_to_unroll_calls_the_runtime() {
3661 let decl = "struct huge { char a[4096]; };\n";
3662 let mut opts = options();
3663 opts.emit = EmitKind::Asm;
3664 let source = format!("{decl}void f(struct huge *p, struct huge *q) {{ *p = *q; }}\n");
3665 let result = run(&opts, &source);
3666 assert!(!result.failed(), "{:?}", result.messages);
3667 let text = result.text();
3668 assert!(text.contains("call") && text.contains("memcpy"), "{text}");
3669 assert!(text.contains("4096"), "the size travels: {text}");
3672 }
3673
3674 #[test]
3681 fn a_structure_too_large_to_unroll_is_copied_into_the_argument_area_by_the_runtime() {
3682 let decl = "struct huge { char a[4096]; };\nint take(struct huge);\n";
3683 let text = asm(&format!("{decl}int f(struct huge *p) {{ return take(*p); }}\n"));
3684
3685 let copy = text.find("call\tmemcpy").expect("the copy");
3686 let call = text.find("call\ttake").expect("the call");
3687 assert!(copy < call, "the copy comes first: {text}");
3688 assert!(text.contains("movq\t%rsp, %rdi"), "the destination: {text}");
3693 assert!(text.contains("$4096, %edx"), "the size: {text}");
3694 }
3695
3696 #[test]
3699 fn a_realigned_frame_reads_them_through_the_frame_pointer() {
3700 let six = "long a, long b, long c, long d, long e, long f";
3701 let body = "_Alignas(32) long wide[4]; wide[0] = g; return wide[0];";
3702 let text = asm(&format!("long f({six}, long g) {{ {body} }}\n"));
3703
3704 assert!(text.contains("\tandq\t$-32, %rsp"), "{text}");
3708 assert!(text.contains("\tmovq\t16(%rbp), "), "{text}");
3709 assert!(!text.contains("\tmovq\t16(%rsp), "), "{text}");
3710 }
3711
3712 #[test]
3714 fn the_target_decides_how_the_assembly_is_spelled() {
3715 let mut opts = options();
3716 opts.emit = EmitKind::Asm;
3717 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
3718 let text = run(&opts, "int f(void) { return 0; }\n").text().to_owned();
3719 assert!(text.contains("__TEXT,__text"), "{text}");
3720 assert!(text.contains("\n_f:\n"), "{text}");
3721 assert!(!text.contains(".note.GNU-stack"), "{text}");
3722 }
3723
3724 fn obj(source: &str) -> Vec<u8> {
3726 let mut opts = options();
3727 opts.emit = EmitKind::Object;
3728 let result = run(&opts, source);
3729 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3730 match result.artifact {
3731 Artifact::Object { bytes, .. } => bytes,
3732 other => panic!("expected an object, got {other:?}"),
3733 }
3734 }
3735
3736 #[test]
3742 fn a_function_goes_from_c_to_an_object_a_linker_would_take() {
3743 let bytes = obj("int add(int a, int b) { return a + b; }\n");
3744 assert_eq!(&bytes[..4], b"\x7fELF", "an object file starts by saying it is one");
3745 let text = asm("int add(int a, int b) { return a + b; }\n");
3746 assert!(
3747 text.contains("\taddl\t"),
3748 "and the listing of it is the same instructions:\n{text}"
3749 );
3750 }
3751
3752 #[test]
3754 fn a_variable_goes_from_c_to_the_section_it_belongs_in() {
3755 let text = asm("int counter = 42;\nstatic int hidden;\nconst int fixed = 7;\n");
3756 assert!(text.contains("\t.data\n\t.globl\tcounter\n"), "{text}");
3757 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
3758 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
3759 assert!(text.contains("\t.bss\n\t.p2align\t2\n"), "{text}");
3762 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
3763 assert!(!text.contains(".globl\thidden"), "{text}");
3764 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
3767 }
3768
3769 #[test]
3776 fn a_bit_field_initializer_writes_every_byte_of_the_value_and_not_only_the_ones_that_are_set() {
3777 let text = asm("struct s { unsigned f : 20; } x = { 0x12300 };\n");
3778 assert!(text.contains("\t.data\n"), "there is something to write: {text}");
3779 assert!(text.contains("\nx:\n\t.ascii\t\"\\000#\\001\"\n"), "and it is the value: {text}");
3780
3781 let text = asm("struct s { unsigned a : 8; unsigned b : 8; } x = { 0, 3 };\n");
3784 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\003\"\n"), "{text}");
3785
3786 let text = asm("struct s { unsigned long long f : 40; } x = { 0x100000 };\n");
3789 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\000\\020\"\n\t.space\t5\n"), "{text}");
3790
3791 let text = asm("struct s { unsigned f : 20; } x = { 0 };\n");
3793 assert!(text.contains("\t.bss\n"), "an object of zeroes is zeroes: {text}");
3794 assert!(text.contains("\nx:\n\t.space\t4\n"), "{text}");
3795 }
3796
3797 #[test]
3799 fn a_string_literal_is_a_variable_with_a_name_no_program_could_write() {
3800 let text = asm("const char *f(void) { return \"hi\"; }\n");
3801 assert!(text.contains("\t.ascii\t\"hi\\000\"\n"), "{text}");
3802 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
3803 let label = text
3804 .lines()
3805 .find(|line| line.starts_with(".Lstr"))
3806 .unwrap_or_else(|| panic!("a label for the literal in\n{text}"));
3807 assert!(!text.contains(&format!(".globl\t{}", label.trim_end_matches(':'))), "{text}");
3808 }
3809
3810 #[test]
3812 fn an_address_in_an_initializer_is_left_to_the_linker() {
3813 let source = "int counter;\nint *p = &counter;\n";
3814 let text = asm(source);
3815 assert!(text.contains("\np:\n\t.quad\tcounter\n"), "{text}");
3816 let bytes = obj(source);
3819 assert!(bytes.windows(8).any(|w| w == b"counter\0"), "the object has to name it");
3820 }
3821
3822 #[test]
3831 fn a_constant_holding_an_address_goes_in_the_section_the_loader_may_write_once() {
3832 let text = asm("static void a(void) {}\nstatic void b(void) {}\n\
3835 struct m { void (*x)(void); void (*y)(void); };\n\
3836 const struct m t = { a, b };\n");
3837 assert!(text.contains("\t.section\t.data.rel.ro.local,\"aw\",@progbits\n"), "{text}");
3838 assert!(text.contains("\nt:\n\t.quad\ta\n\t.quad\tb\n"), "{text}");
3839
3840 let text =
3843 asm("void a(void);\nstruct m { void (*x)(void); };\nconst struct m t = { a };\n");
3844 assert!(text.contains("\t.section\t.data.rel.ro,\"aw\",@progbits\n"), "{text}");
3845
3846 let text = asm("const int fixed = 7;\n");
3848 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
3849 }
3850
3851 #[test]
3858 fn a_thread_local_variable_is_storage_a_thread_gets_a_copy_of_and_an_offset_into_it() {
3859 let text = asm("_Thread_local int x = 1;\nint read(void) { return x; }\n");
3860 assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
3863 assert!(text.contains("\t.type\tx, @tls_object\n"), "{text}");
3864 assert!(text.contains("x@GOTTPOFF(%rip)"), "{text}");
3867 assert!(text.contains("%fs:0"), "{text}");
3868 }
3869
3870 #[test]
3876 fn the_address_of_this_thread_s_own_storage_is_read_out_of_the_segment_register() {
3877 let text = asm("void *here(void) { return __builtin_thread_pointer(); }\n");
3878 assert!(text.contains("movq\t%fs:0, "), "{text}");
3879 assert!(!text.contains("GOTTPOFF"), "{text}");
3881 }
3882
3883 #[test]
3894 fn a_prefetch_is_one_of_four_instructions_and_the_locality_is_what_picks() {
3895 for (locality, wanted) in
3896 [(0, "prefetchnta"), (1, "prefetcht2"), (2, "prefetcht1"), (3, "prefetcht0")]
3897 {
3898 let source =
3899 format!("void warm(void *p) {{ __builtin_prefetch(p, 0, {locality}); }}\n");
3900 let text = asm(&source);
3901 assert!(text.contains(&format!("\t{wanted}\t")), "locality {locality}: {text}");
3902 }
3903 let text = asm("void warm(void *p) { __builtin_prefetch(p); }\n");
3905 assert!(text.contains("\tprefetcht0\t"), "{text}");
3906 let text = asm("void warm(void *p) { __builtin_prefetch(p, 1); }\n");
3909 assert!(text.contains("\tprefetcht0\t"), "{text}");
3910 assert!(!text.contains("prefetchw"), "{text}");
3911 }
3912
3913 #[test]
3924 fn a_trap_is_the_instruction_the_machine_has_no_meaning_for() {
3925 let text = asm("void stop(void) { __builtin_trap(); }\n");
3926 assert!(text.contains("\tud2\n"), "{text}");
3927 assert!(!text.contains("\tcall"), "a stop is not a call to anything: {text}");
3928
3929 let text = asm("int stop(int a) { __builtin_trap(); return a + 1; }\n");
3930 assert!(text.contains("\tud2\n"), "{text}");
3931 assert!(text.contains("\taddl\t"), "the block goes on after a stop: {text}");
3932 }
3933
3934 #[test]
3946 fn assume_aligned_is_its_first_argument_and_keeps_the_rest() {
3947 let text = asm("void *aligned(char *p) { return __builtin_assume_aligned(p, 16); }\n");
3948 assert!(!text.contains("assume_aligned"), "{text}");
3949 assert!(!text.contains("\tcall"), "nothing is called for an alignment fact: {text}");
3950
3951 let source = "unsigned long width(void);\n\
3952 void *aligned(char *p) { return __builtin_assume_aligned(p, width()); }\n";
3953 let text = asm(source);
3954 assert!(!text.contains("assume_aligned"), "{text}");
3955 assert!(text.contains("width"), "the argument that is not the answer still runs: {text}");
3956 }
3957
3958 #[test]
3968 fn the_frame_address_is_the_frame_pointer_after_walking_that_many_links() {
3969 let text = asm("void *here(void) { return __builtin_frame_address(0); }\n");
3970 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
3971 assert!(text.contains("movq\t%rbp, %rax"), "{text}");
3972 assert!(!text.contains("\tcall"), "a frame address is not a call to anything: {text}");
3973
3974 let walk = |depth: u32| {
3975 let source = format!("void *up(void) {{ return __builtin_frame_address({depth}); }}\n");
3976 asm(&source).matches("movq\t(%r").count()
3977 };
3978 assert_eq!(walk(1), 1, "one link is one load");
3979 assert_eq!(walk(3), 3, "three links are three loads");
3980 }
3981
3982 #[test]
3992 fn the_return_address_is_one_word_above_the_frame_the_walk_ended_at() {
3993 let text = asm("void *back(void) { return __builtin_return_address(0); }\n");
3994 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
3995 assert!(text.contains("movq\t8(%rbp), %rax"), "{text}");
3996 assert!(!text.contains("\tcall"), "a return address is not a call to anything: {text}");
3997
3998 let text = asm("void *back(void) { return __builtin_return_address(2); }\n");
3999 assert_eq!(text.matches("movq\t(%r").count(), 2, "two links are two loads: {text}");
4000 assert!(text.contains("movq\t8(%r"), "and the answer is above the last of them: {text}");
4001 }
4002
4003 #[test]
4014 fn a_depth_that_is_not_a_small_constant_is_refused() {
4015 let mut opts = options();
4016 opts.emit = EmitKind::Ir;
4017 for source in [
4018 "void *up(int n) { return __builtin_return_address(n); }\n",
4019 "void *up(void) { return __builtin_frame_address(1000); }\n",
4020 ] {
4021 let messages = run(&opts, source).messages;
4022 let named = messages.iter().any(|m| m.contains("E0705"));
4023 assert!(named, "expected a refusal in {messages:?}");
4024 }
4025 }
4026
4027 #[test]
4039 fn an_alloca_takes_the_bytes_off_the_stack_pointer_and_answers_where_they_are() {
4040 let text =
4041 asm("void use(void *p); void f(unsigned long n) { use(__builtin_alloca(n)); }\n");
4042 assert!(text.contains("andq\t$-16"), "the size is rounded up to sixteen: {text}");
4043 assert!(text.contains("subq\t%rdi, %rsp"), "and taken off the stack pointer: {text}");
4044 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
4045
4046 let plain = concat!(
4049 "extern void *alloca(__SIZE_TYPE__);\n",
4050 "void use(void *p);\n",
4051 "void f(unsigned long n) { use(alloca(n)); }\n",
4052 );
4053 let text = asm(plain);
4054 assert!(text.contains("subq\t%rdi, %rsp"), "the plain name is the same bytes: {text}");
4055 assert_eq!(text.matches("\tcall").count(), 1, "and is not a call either: {text}");
4056
4057 let own = concat!(
4060 "static void *alloca(unsigned long n) { return 0; }\n",
4061 "void *f(unsigned long n) { return alloca(n); }\n",
4062 );
4063 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
4064 }
4065
4066 #[test]
4081 fn a_builtin_the_program_never_declared_is_the_builtin_rather_than_the_one_c89_wrote_down() {
4082 let mut opts = options();
4086 opts.permissive = true;
4087 let undeclared = "void use(void *p);
4088void f(unsigned long n) { use(alloca(n)); }
4089";
4090 assert_eq!(
4091 run(&opts, undeclared).messages,
4092 [
4093 "/main.c:2:31: warning: implicit declaration of function 'alloca' [E0521]",
4094 "/main.c:2:31: warning: incompatible implicit declaration of built-in function \
4095 'alloca' [E0713]",
4096 ]
4097 );
4098
4099 opts.emit = EmitKind::Asm;
4100 let text = run(&opts, undeclared).text().to_owned();
4101 assert!(text.contains("subq\t%rdi, %rsp"), "the bytes come off the stack: {text}");
4102 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
4103
4104 let string = "unsigned long f(void) { return strlen(\"abc\"); }\n";
4107 let text = run(&opts, string).text().to_owned();
4108 assert!(text.contains("call\tstrlen"), "strlen is still a call: {text}");
4109
4110 let own = concat!(
4113 "static void *alloca(unsigned long n) { return 0; }\n",
4114 "void *f(unsigned long n) { return alloca(n); }\n",
4115 );
4116 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
4117 }
4118
4119 #[test]
4129 fn the_bytes_an_alloca_took_are_still_there_at_the_end_of_the_block_that_took_them() {
4130 let inner = "{ use(__builtin_alloca(n)); }";
4131 for body in [inner.to_owned(), format!("int a[n]; {inner} use(a);")] {
4132 let source = format!("void use(void *p);\nvoid f(unsigned long n) {{ {body} }}\n");
4133 let text = asm(&source);
4134 for line in text.lines().filter(|line| line.trim_end().ends_with(", %rsp")) {
4138 let taking = line.contains("subq");
4139 let leaving = line.contains("%rbp");
4140 assert!(taking || leaving, "nothing puts the stack back: {line} in {text}");
4141 }
4142 }
4143 }
4144
4145 #[test]
4147 fn the_object_and_the_listing_are_two_spellings_of_one_compilation() {
4148 let source = "int callee(void); int g(void) { return callee(); }\n";
4152 let bytes = obj(source);
4153 assert!(
4154 bytes.windows(7).any(|w| w == b"callee\0"),
4155 "the object has to name the callee for the linker to find it"
4156 );
4157 let text = asm(source);
4158 assert!(text.contains("\tcall\tcallee\n"), "{text}");
4159 }
4160
4161 #[test]
4167 fn compiling_for_an_executable_produces_an_object_and_not_a_dump() {
4168 let mut opts = options();
4169 opts.emit = EmitKind::Executable;
4171 let result = run(&opts, "int main(void) { return 0; }\n");
4172 assert_eq!(result.messages, Vec::<String>::new());
4173 match result.artifact {
4174 Artifact::Object { bytes, .. } => assert_eq!(&bytes[..4], b"\x7fELF"),
4175 other => panic!("expected an object, got {other:?}"),
4176 }
4177 }
4178
4179 #[test]
4181 fn a_platform_with_no_object_writer_is_said_so_rather_than_written_as_elf() {
4182 let mut opts = options();
4183 opts.emit = EmitKind::Object;
4184 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
4185 let result = run(&opts, "int f(void) { return 0; }\n");
4186 assert!(result.failed(), "an object nobody can read is worse than a message");
4187 assert!(
4188 result.messages.iter().any(|m| m.contains("no object writer")),
4189 "{:?}",
4190 result.messages
4191 );
4192 }
4193
4194 fn ir(source: &str) -> String {
4196 let mut opts = options();
4197 opts.emit = EmitKind::Ir;
4198 let result = run(&opts, source);
4199 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4200 result.text().to_owned()
4201 }
4202
4203 fn errors(source: &str) -> Vec<String> {
4205 let mut opts = options();
4206 opts.emit = EmitKind::Ir;
4207 let result = run(&opts, source);
4208 assert!(result.failed(), "expected this to be refused:\n{source}");
4209 result.messages
4210 }
4211
4212 fn body(source: &str) -> String {
4214 let text = ir(source);
4215 let (_, rest) = text.split_once("{\n").expect("a function definition");
4216 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
4217 body.to_owned()
4218 }
4219
4220 #[test]
4228 fn gnu89_inline_is_what_decides_whether_a_bare_inline_definition_reaches_the_module() {
4229 let source = "inline int f(int x) { return x + 1; }\n";
4230 let with = |flag: bool| {
4231 let mut opts = options();
4232 opts.emit = EmitKind::Ir;
4233 opts.gnu89_inline = flag;
4234 let result = run(&opts, source);
4235 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
4236 result.text().to_owned()
4237 };
4238
4239 assert!(!with(false).contains("block0"), "no body: {}", with(false));
4242
4243 assert!(with(true).contains("block0"), "a body: {}", with(true));
4246 }
4247
4248 #[test]
4255 fn an_access_through_a_type_names_the_type_it_went_through() {
4256 let source = "\
4257struct s { int a; float b; };\n\
4258union u { int i; float f; };\n\
4259int scalar(int *p) { return *p; }\n\
4260float member(struct s *p) { p->a = 1; return p->b; }\n\
4261int element(int *a, long i) { return a[i]; }\n\
4262float through_a_union(union u *p) { p->i = 1; return p->f; }\n";
4263 let text = ir(source);
4264 assert!(text.contains(r#"!0 = tbaa "char""#), "the root: {text}");
4265 assert!(text.contains(r#"tbaa "int", parent !0"#), "int under it: {text}");
4266 assert!(text.contains(r#"tbaa "float", parent !0"#), "float under it: {text}");
4267 let named = text.lines().filter(|line| line.contains(", tbaa !")).count();
4270 assert_eq!(named, 6, "six accesses: {text}");
4271 }
4272
4273 #[test]
4280 fn turning_strict_aliasing_off_leaves_the_type_off_every_access() {
4281 let source = "int punned(float *f, int *i) { *i = 1; *f = 2.0f; return *i; }\n";
4282 let mut opts = options();
4283 opts.emit = EmitKind::Ir;
4284 opts.strict_aliasing = false;
4285 let result = run(&opts, source);
4286 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
4287 let text = result.text().to_owned();
4288 assert!(!text.contains("tbaa"), "not even the root: {text}");
4289 }
4290
4291 #[test]
4299 fn a_bare_return_from_a_function_that_promised_a_value_gives_back_a_zero() {
4300 let mut opts = options();
4301 opts.emit = EmitKind::Ir;
4302 opts.std = Std::C89;
4303 let compiled = |source: &str| {
4304 let result = run(&opts, source);
4305 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
4306 result.text().to_owned()
4307 };
4308
4309 let text = compiled("int f(int x) { if (x) return; return 3; }\n");
4310 assert!(text.contains("iconst.i32 0\n return"), "zero goes back: {text}");
4311 assert!(!text.contains("unreachable"), "the branch that reached it is kept: {text}");
4312
4313 let text = compiled("double f(int x) { if (x) return; return 1.0; }\n");
4315 assert!(text.contains("fconst.f64 0x0\n return"), "a float zero goes back: {text}");
4316 }
4317
4318 #[test]
4326 fn a_call_to_a_name_nothing_declared_declares_it_as_c89_said_to() {
4327 let mut opts = options();
4328 opts.emit = EmitKind::Ir;
4329 opts.std = Std::C89;
4330 let compiled = |source: &str| {
4331 let result = run(&opts, source);
4332 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
4333 result.text().to_owned()
4334 };
4335
4336 let text = compiled("int f(void) { return g(); }\n");
4338 assert!(text.contains("call @g"), "the call is to the name that was written: {text}");
4339 assert!(text.contains("i32"), "and it gives back an int: {text}");
4340
4341 let text = compiled("int f(char c) { return g(c); }\n");
4344 assert!(text.contains("sext.i32"), "the argument is promoted: {text}");
4345
4346 let mut opts = options();
4349 opts.std = Std::C89;
4350 let said = run(&opts, "int f(void) { return h; }\n").messages.join("\n");
4351 assert!(said.contains("'h' undeclared"), "not a call, so not declared: {said}");
4352 }
4353
4354 #[test]
4364 fn a_name_called_before_it_is_defined_still_gets_its_definition() {
4365 let mut opts = options();
4366 opts.emit = EmitKind::Ir;
4367 opts.std = Std::C89;
4368 let text = run(&opts, "int f(void) { return dummy(); }\ndummy () { return 7; }\n")
4369 .text()
4370 .to_owned();
4371 assert!(text.contains("func @f()"), "the caller is there: {text}");
4372 assert!(text.contains("func @dummy"), "and so is what it calls: {text}");
4373 assert!(text.contains("iconst.i32 7"), "with the body it was given: {text}");
4374 }
4375
4376 #[test]
4384 fn an_old_style_parameter_is_converted_from_what_the_call_promoted_it_to() {
4385 let mut opts = options();
4386 opts.emit = EmitKind::Ir;
4387 opts.std = Std::C89;
4388 let compiled = |source: &str| run(&opts, source).text().to_owned();
4389
4390 let text = compiled("f (c) unsigned char c; { return c; }\n");
4391 assert!(text.contains("func @f(i32"), "an int arrives: {text}");
4392 assert!(text.contains("trunc.i8"), "and is cut down to what was declared: {text}");
4393 assert!(text.contains("zext.i32"), "then read back unsigned: {text}");
4394
4395 let text = compiled("f (s) short s; { return s; }\n");
4397 assert!(text.contains("trunc.i16"), "cut down: {text}");
4398 assert!(text.contains("sext.i32"), "and read back signed: {text}");
4399
4400 let text = compiled("f (x) float x; { return x * 2; }\n");
4403 assert!(text.contains("func @f(f64"), "a double arrives: {text}");
4404 assert!(text.contains("fptrunc.f32"), "and is narrowed to the float: {text}");
4405
4406 let text = compiled("int f(unsigned char c) { return c; }\n");
4409 assert!(text.contains("func @f(i8)"), "the declared type arrives: {text}");
4410 assert!(!text.contains("trunc"), "so there is nothing to cut down: {text}");
4411 }
4412
4413 #[test]
4422 fn the_rules_gcc_promoted_are_decided_by_the_dialect_and_by_fpermissive() {
4423 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
4425 let cases = [
4426 ("static counted;\n", ["", "error", "warning", "error"]),
4427 ("int f(void) { return g(); }\n", ["", "error", "warning", "error"]),
4428 ("int f(x) { return x; }\n", ["", "error", "warning", "error"]),
4429 ("int *p;\nvoid h(void) { p = 1; }\n", ["warning", "error", "warning", "error"]),
4430 (
4431 "char *q;\nint *r;\nvoid k(void) { r = q; }\n",
4432 ["warning", "error", "warning", "error"],
4433 ),
4434 ("int f(void) { return; }\n", ["", "error", "warning", "error"]),
4435 ("void g(void) { return 1; }\n", ["warning", "error", "warning", "error"]),
4436 ];
4437
4438 for (source, wanted) in cases {
4439 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
4440 let mut opts = options();
4441 opts.std = std;
4442 opts.permissive = permissive;
4443 let said = run(&opts, source).messages.join("\n");
4444 let severity = if said.contains(": error: ") {
4445 "error"
4446 } else if said.contains(": warning: ") {
4447 "warning"
4448 } else {
4449 ""
4450 };
4451 let how = if permissive { " -fpermissive" } else { "" };
4452 assert_eq!(
4453 severity,
4454 wanted,
4455 "under -std={}{how}, {source} was answered with `{said}`",
4456 std.as_str()
4457 );
4458 if wanted.is_empty() {
4459 assert!(said.is_empty(), "nothing to say, but said `{said}`");
4460 }
4461 }
4462 }
4463 }
4464
4465 #[test]
4474 fn the_three_variadic_builtins_answer_a_bad_list_the_way_a_call_answers_a_bad_argument() {
4475 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
4476 let cases = [
4477 (
4478 "int f(int n, ...) { char *p; return __builtin_va_arg(p, int); }\n",
4479 "first argument to 'va_arg' not of type 'va_list'",
4480 ["error", "error", "error", "error"],
4481 ),
4482 (
4483 "void f(int n, ...) { char *p; __builtin_va_start(p, n); }\n",
4484 "passing argument 1 of '__builtin_va_start' from incompatible pointer type",
4485 ["warning", "error", "warning", "error"],
4486 ),
4487 (
4488 "void f(int n, ...) { int x; __builtin_va_end(x); }\n",
4489 "passing argument 1 of '__builtin_va_end' makes pointer from integer without a \
4490 cast",
4491 ["warning", "error", "warning", "error"],
4492 ),
4493 (
4494 "void f(int n, ...) { __builtin_va_list a; char *p; __builtin_va_copy(a, p); }\n",
4495 "passing argument 2 of '__builtin_va_copy' from incompatible pointer type",
4496 ["warning", "error", "warning", "error"],
4497 ),
4498 ];
4499
4500 for (source, message, wanted) in cases {
4501 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
4502 let mut opts = options();
4503 opts.std = std;
4504 opts.permissive = permissive;
4505 let said = run(&opts, source).messages.join("\n");
4506 let how = if permissive { " -fpermissive" } else { "" };
4507 assert!(
4508 said.contains(&format!(": {wanted}: {message}")),
4509 "under -std={}{how}, {source} was answered with `{said}`",
4510 std.as_str()
4511 );
4512 }
4513 }
4514 }
4515
4516 fn safe_ir(tier: rucc_session::Safety, source: &str) -> String {
4518 let mut opts = options();
4519 opts.emit = EmitKind::Ir;
4520 opts.safety = tier;
4521 let result = run(&opts, source);
4522 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4523 result.text().to_owned()
4524 }
4525
4526 const READS_THROUGH_A_POINTER: &str = "int read(int *p) { return p[1]; }\n";
4527
4528 fn padded_ir(padding: Padding, source: &str) -> String {
4530 let mut opts = options();
4531 opts.emit = EmitKind::Ir;
4532 opts.safety = rucc_session::Safety::Detect;
4533 opts.padding = padding;
4534 let result = run(&opts, source);
4535 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4536 result.text().to_owned()
4537 }
4538
4539 const FILLS_A_RECORD_A_MEMBER_AT_A_TIME: &str = "struct padded { char tag; int value; };\n\
4540 void fill(struct padded *p) { p->tag = 1; p->value = 2; }\n";
4541
4542 #[test]
4543 fn a_record_filled_a_member_at_a_time_comes_out_whole_when_padding_does_not_participate() {
4544 let text = padded_ir(Padding::Ignored, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
4548 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
4549 }
4550
4551 #[test]
4552 fn a_store_says_only_what_it_wrote_when_padding_does_participate() {
4553 let text = padded_ir(Padding::Tracked, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
4556 assert!(!text.contains("owns"), "{text}");
4557 }
4558
4559 #[test]
4560 fn a_member_of_a_union_owns_nothing_after_it() {
4561 let text = padded_ir(
4565 Padding::Ignored,
4566 "union u { char tag; long wide; };\nvoid fill(union u *p) { p->tag = 1; }\n",
4567 );
4568 assert!(!text.contains("owns"), "{text}");
4569 }
4570
4571 #[test]
4572 fn an_inner_records_trailing_padding_reaches_the_outer_records() {
4573 let text = padded_ir(
4578 Padding::Ignored,
4579 "struct inner { char c; };\n\
4580 struct outer { struct inner in; int x; };\n\
4581 void fill(struct outer *p) { p->in.c = 1; p->x = 2; }\n",
4582 );
4583 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
4584 }
4585
4586 #[test]
4587 fn a_build_that_did_not_ask_for_the_monitor_is_compiled_the_way_it_always_was() {
4588 let text = ir(READS_THROUGH_A_POINTER);
4592 assert!(!text.contains("check_"), "{text}");
4593 assert!(!text.contains("cap_of"), "{text}");
4594 }
4595
4596 #[test]
4597 fn asking_for_a_tier_puts_the_checks_in_before_the_optimizer_sees_them() {
4598 let text = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4599 assert!(text.contains("cap_of"), "{text}");
4600 assert!(text.contains("check_bounds"), "{text}");
4601 assert!(text.contains("check_live"), "{text}");
4602 assert!(text.contains("check_deriv"), "{text}");
4604 assert!(text.contains("check_type"), "{text}");
4606 }
4607
4608 #[test]
4609 fn the_three_tiers_that_are_not_off_all_check_the_same_accesses_so_far() {
4610 let detect = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4614 for tier in [rucc_session::Safety::Enforce, rucc_session::Safety::Kernel] {
4615 assert_eq!(safe_ir(tier, READS_THROUGH_A_POINTER), detect, "{tier}");
4616 }
4617 }
4618
4619 fn summary(tier: rucc_session::Safety, source: &str) -> String {
4621 let mut opts = options();
4622 opts.emit = EmitKind::SafetySummary;
4623 opts.safety = tier;
4624 let result = run(&opts, source);
4625 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4626 result.text().to_owned()
4627 }
4628
4629 #[test]
4630 fn the_summary_counts_the_checks_that_went_in_and_the_ones_still_standing() {
4631 let text = summary(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4632 assert!(text.contains("\"tier\": \"detect\""), "{text}");
4633 assert!(
4635 text.contains("\"bounds\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"),
4636 "{text}"
4637 );
4638 assert!(
4639 text.contains(
4640 "\"derivation\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"
4641 ),
4642 "{text}"
4643 );
4644 }
4645
4646 #[test]
4647 fn a_build_without_the_monitor_summarises_as_a_build_with_no_checks_in_it() {
4648 let text = summary(rucc_session::Safety::Off, READS_THROUGH_A_POINTER);
4652 assert!(text.contains("\"tier\": \"off\""), "{text}");
4653 assert!(
4654 text.contains("\"bounds\": { \"emitted\": 0, \"remaining\": 0, \"discharged\": 0 }"),
4655 "{text}"
4656 );
4657 }
4658
4659 #[test]
4660 fn a_call_the_boundary_models_is_counted_apart_from_one_it_does_not() {
4661 let text = summary(
4662 rucc_session::Safety::Detect,
4663 "void *memcpy(void *, const void *, unsigned long);\n\
4664 int puts(const char *);\n\
4665 void f(char *d, char *s) { memcpy(d, s, 4); puts(d); }\n",
4666 );
4667 assert!(text.contains("\"interposed\": 1"), "{text}");
4668 assert!(text.contains("\"puts\""), "{text}");
4669 assert!(!text.contains("__rucc_wrap_memcpy\""), "{text}");
4673 }
4674
4675 #[test]
4676 fn an_address_taken_of_a_library_function_is_counted_the_way_a_call_to_one_is() {
4677 let text = summary(
4682 rucc_session::Safety::Detect,
4683 "void *memcpy(void *, const void *, unsigned long);\n\
4684 int puts(const char *);\n\
4685 void *table[2] = { (void *)memcpy, (void *)puts };\n\
4686 void *f(int i) { return table[i]; }\n",
4687 );
4688 assert!(text.contains("\"interposed\": 1"), "{text}");
4689 assert!(text.contains("\"puts\""), "{text}");
4690 assert!(!text.contains("\"memcpy\""), "{text}");
4691 }
4692
4693 #[test]
4694 fn the_two_directions_a_pointer_crosses_the_boundary_are_counted_apart() {
4695 let text = summary(
4699 rucc_session::Safety::Detect,
4700 "void *notes_open(void);\n\
4701 char *f(char *p) { char *q = notes_open(); return q ? q : p; }\n",
4702 );
4703 assert!(text.contains("\"crossings\": { \"entered\": 1, \"returned\": 1 }"), "{text}");
4704 assert!(text.contains("\"notes_open\""), "{text}");
4705 }
4706
4707 #[test]
4708 fn a_static_function_nobody_takes_the_address_of_is_not_a_crossing() {
4709 let text = summary(
4712 rucc_session::Safety::Detect,
4713 "static int len(const char *p) { return p ? 1 : 0; }\n\
4714 int f(void) { return len(\"x\"); }\n",
4715 );
4716 assert!(text.contains("\"crossings\": { \"entered\": 0, \"returned\": 0 }"), "{text}");
4717 }
4718
4719 fn granules(source: &str) -> String {
4721 let mut opts = options();
4722 opts.emit = EmitKind::TypeGranules;
4723 let result = run(&opts, source);
4724 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4725 result.text().to_owned()
4726 }
4727
4728 #[test]
4729 fn the_granule_report_names_every_record_and_both_keyings() {
4730 let text = granules(
4731 "struct hot { char *p; int a; int b; };\n\
4732 int f(struct hot *h) { return h->a; }\n",
4733 );
4734 assert!(text.contains("struct hot"), "{text}");
4735 assert!(text.contains("every type distinct"), "{text}");
4738 assert!(text.contains("every pointer one type"), "{text}");
4739 assert!(text.contains("budget"), "{text}");
4740 }
4741
4742 #[test]
4743 fn a_record_nothing_uses_is_still_measured() {
4744 let text = granules("struct unused { long a; double b; };\nint f(void) { return 0; }\n");
4747 assert!(text.contains("struct unused"), "{text}");
4748 }
4749
4750 #[test]
4751 fn the_granule_report_stops_before_anything_is_lowered() {
4752 let text = granules(
4756 "struct wide { long double d; };\n\
4757 long double f(long double x) { return x * x; }\n",
4758 );
4759 assert!(text.contains("struct wide"), "{text}");
4760 }
4761
4762 #[test]
4763 fn a_witness_reaches_the_assembler_as_a_call_to_the_runtime() {
4764 let text = safe_asm(rucc_session::Safety::Detect, "char *f(char *p) { return p; }\n");
4767 assert!(text.contains("\tcall\t__rucc_cap_witness\n"), "{text}");
4768 }
4769
4770 #[test]
4771 fn a_pointer_turned_into_an_integer_is_on_the_trust_set() {
4772 let text = summary(
4773 rucc_session::Safety::Detect,
4774 "unsigned long f(int *p) { return (unsigned long) p; }\n",
4775 );
4776 assert!(text.contains("\"exposed\": 1"), "{text}");
4777 }
4778
4779 fn safe_asm(tier: rucc_session::Safety, source: &str) -> String {
4781 let mut opts = options();
4782 opts.emit = EmitKind::Asm;
4783 opts.safety = tier;
4784 let result = run(&opts, source);
4785 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4786 result.text().to_owned()
4787 }
4788
4789 #[test]
4790 fn a_check_reaches_the_assembler_as_a_call_to_the_runtime() {
4791 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4792 assert!(text.contains("\tcall\t__rucc_check_bounds\n"), "{text}");
4793 assert!(text.contains("\tcall\t__rucc_check_live\n"), "{text}");
4794 assert!(text.contains("\tcall\t__rucc_check_deriv\n"), "{text}");
4795 assert!(text.contains("\tcall\t__rucc_check_typed_init\n"), "{text}");
4798 }
4799
4800 #[test]
4801 fn every_check_that_reached_the_assembler_has_a_row_describing_it() {
4802 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4808 let section = format!("\t.section\t{},", rucc_safety::SECTION);
4809 assert_eq!(text.matches(§ion).count(), 4, "{text}");
4810 for index in 0..4 {
4811 let name = format!("__rucc_safety_desc_{index}");
4812 assert!(text.contains(&format!("{name}:\n")), "{text}");
4815 assert!(text.contains(&format!("{name}(%rip)")), "{text}");
4816 }
4817 assert!(!text.contains("__rucc_safety_desc_4"), "{text}");
4818 }
4819
4820 #[test]
4828 fn builtin_constant_p_is_folded_where_it_is_written_rather_than_called() {
4829 let text = ir(concat!(
4830 "int g;\n",
4831 "int a = __builtin_constant_p(1);\n",
4832 "int b = __builtin_constant_p(g);\n",
4833 "int c = __builtin_constant_p(\"abc\");\n",
4834 "int d = __builtin_constant_p(&g);\n",
4835 "int e = __builtin_constant_p(1.5);\n",
4836 "int h = __builtin_choose_expr(__builtin_constant_p(3), 11, 22);\n",
4837 ));
4838 assert!(text.contains("global @a : i32 = 1,"), "{text}");
4839 assert!(text.contains("global @b : i32 = 0,"), "{text}");
4840 assert!(text.contains("global @c : i32 = 1,"), "{text}");
4841 assert!(text.contains("global @d : i32 = 0,"), "{text}");
4842 assert!(text.contains("global @e : i32 = 1,"), "{text}");
4843 assert!(text.contains("global @h : i32 = 11,"), "{text}");
4844 assert!(!text.contains("__builtin_constant_p"), "it is not a call to anything:\n{text}");
4845
4846 let text = body("int f(void) { int i = 0; __builtin_constant_p(i++); return i; }\n");
4850 assert_eq!(text, "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 0\n return %0\n");
4851 }
4852
4853 #[test]
4862 fn a_call_to_a_library_builtin_reaches_the_library_function() {
4863 let text = body("void f(void) { __builtin_abort(); }\n");
4864 assert_eq!(text, "block0:\n call @abort() : ()\n return\n");
4865
4866 let text = ir("int f(const char *s) { return __builtin_puts(s) + __builtin_strlen(s); }\n");
4869 assert!(text.contains("call @puts(%0) : (ptr) -> i32"), "{text}");
4870 assert!(text.contains("call @strlen(%0) : (ptr) -> i64"), "{text}");
4871 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
4872 }
4873
4874 #[test]
4887 fn a_chk_builtin_reaches_the_checking_function_and_keeps_the_size() {
4888 let text = ir(concat!(
4889 "char d[8];\n",
4890 "void f(const char *s, unsigned long n) {\n",
4891 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
4892 " __builtin___strcpy_chk(d, s, __builtin_object_size(d, 1));\n",
4893 " __builtin___memset_chk(d, 0, n, 8);\n",
4894 "}\n",
4895 ));
4896 assert!(text.contains("call @__memcpy_chk("), "{text}");
4897 assert!(text.contains("call @__strcpy_chk("), "{text}");
4898 assert!(text.contains("call @__memset_chk("), "{text}");
4899 assert!(text.contains("iconst.i64 8"), "the object size reaches the call: {text}");
4900 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
4901 }
4902
4903 #[test]
4911 fn a_checking_call_whose_size_says_nothing_is_known_is_the_plain_library_call() {
4912 let text = ir(concat!(
4913 "extern char *p;\n",
4914 "char d[8];\n",
4915 "void f(const char *s, unsigned long n) {\n",
4916 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
4917 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4918 " __builtin___strcpy_chk(p, s, __builtin_object_size(p, 0));\n",
4919 " __builtin___stpncpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4920 " __builtin___sprintf_chk(p, 1, __builtin_object_size(p, 0), s);\n",
4921 "}\n",
4922 ));
4923
4924 assert!(
4926 text.contains("call @__memcpy_chk(%2, %0, %1, %3) : (ptr, ptr, i64, i64)"),
4927 "{text}"
4928 );
4929
4930 assert!(text.contains("call @memcpy(%6, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
4933 assert!(text.contains("call @strcpy(%10, %0) : (ptr, ptr) -> ptr"), "{text}");
4934 assert!(text.contains("call @stpncpy(%14, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
4935
4936 assert!(text.contains("call @__sprintf_chk("), "{text}");
4939
4940 let asm = asm(concat!(
4943 "void f(char *p, const char *s, unsigned long n) {\n",
4944 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4945 "}\n",
4946 ));
4947 assert!(asm.contains("call\tmemcpy"), "{asm}");
4948 assert!(!asm.contains("$-1"), "the size that went away leaves no instruction:\n{asm}");
4949 }
4950
4951 #[test]
4959 fn the_v_spellings_of_the_chk_family_take_the_list_a_va_list_parameter_holds() {
4960 let text = ir(concat!(
4961 "char d[64];\n",
4962 "int f(const char *fmt, ...) {\n",
4963 " __builtin_va_list ap;\n",
4964 " __builtin_va_start(ap, fmt);\n",
4965 " int n = __builtin___vsprintf_chk(d, 1, __builtin_object_size(d, 0), fmt, ap);\n",
4966 " __builtin_va_end(ap);\n",
4967 " return n;\n",
4968 "}\n",
4969 ));
4970 assert!(text.contains("call @__vsprintf_chk("), "{text}");
4971 assert!(text.contains("iconst.i64 64"), "the object size reaches the call: {text}");
4972 }
4973
4974 #[test]
4985 fn the_absolute_value_family_is_the_magnitude_and_not_a_call() {
4986 let text = body(concat!(
4987 "long long llabs(long long);\n",
4988 "long long f(long long x) { return llabs(x); }\n",
4989 ));
4990 assert!(text.contains("%1 = iconst.i64 63"), "{text}");
4991 assert!(text.contains("%2 = ashr %0, %1"), "{text}");
4992 assert!(text.contains("%3 = xor %0, %2"), "{text}");
4993 assert!(text.contains("%4 = sub %3, %2"), "{text}");
4994 assert!(!text.contains("call"), "the call does not happen:\n{text}");
4995
4996 let text = body("int abs(int);\nint f(int x) { return abs(x); }\n");
4999 assert!(text.contains("iconst.i32 31"), "{text}");
5000 let text = body("long labs(long);\nlong f(long x) { return labs(x); }\n");
5001 assert!(text.contains("iconst.i64 63"), "{text}");
5002
5003 let text = body("long long f(long long x) { return __builtin_llabs(x); }\n");
5006 assert!(!text.contains("call"), "{text}");
5007
5008 let text = ir(concat!(
5010 "long long llabs(long long b);\n",
5011 "long long g(long long x) { return llabs(x); }\n",
5012 "long long llabs(long long b) { return 7; }\n",
5013 ));
5014 assert!(!text.contains("call @llabs"), "{text}");
5015 }
5016
5017 #[test]
5024 fn a_byte_swap_is_arithmetic_and_not_a_call() {
5025 let text = body("unsigned f(unsigned x) { return __builtin_bswap32(x); }\n");
5026 assert_eq!(text, "block0(%0: i32):\n %1 = bswap %0\n return %1\n");
5027
5028 let text = body("unsigned f(unsigned char c) { return __builtin_bswap32(c); }\n");
5031 assert!(text.contains("zext.i32 %0"), "widened first: {text}");
5032 assert!(text.contains("bswap %1"), "and swapped at four bytes: {text}");
5033 }
5034
5035 #[test]
5041 fn the_byte_swaps_reverse_at_the_width_their_name_says() {
5042 for (name, ty, width) in [
5043 ("__builtin_bswap16", "unsigned short", "i16"),
5044 ("__builtin_bswap32", "unsigned", "i32"),
5045 ("__builtin_bswap64", "unsigned long long", "i64"),
5046 ] {
5047 let source = format!("{ty} f({ty} x) {{ return {name}(x); }}\n");
5048 let text = body(&source);
5049 assert_eq!(
5050 text,
5051 format!("block0(%0: {width}):\n %1 = bswap %0\n return %1\n"),
5052 "{name}"
5053 );
5054 }
5055 }
5056
5057 #[test]
5064 fn the_bit_counts_are_instructions_and_not_calls() {
5065 let text = body("int f(unsigned x) { return __builtin_clz(x); }\n");
5066 assert_eq!(text, "block0(%0: i32):\n %1 = ctlz %0\n return %1\n");
5067
5068 let text = body("int f(unsigned x) { return __builtin_ctz(x); }\n");
5069 assert_eq!(text, "block0(%0: i32):\n %1 = cttz %0\n return %1\n");
5070
5071 let text = body("int f(unsigned x) { return __builtin_popcount(x); }\n");
5072 assert_eq!(text, "block0(%0: i32):\n %1 = ctpop %0\n return %1\n");
5073 }
5074
5075 #[test]
5084 fn the_bit_counts_ask_about_the_width_their_name_says() {
5085 let text = body("int f(unsigned long long x) { return __builtin_clzll(x); }\n");
5086 assert!(text.starts_with("block0(%0: i64):"), "counted at eight bytes: {text}");
5087 assert!(text.contains("%1 = ctlz %0"), "{text}");
5088 assert!(text.contains("trunc.i32 %1"), "and answered in an int: {text}");
5089
5090 let text = body("int f(unsigned long long x) { return __builtin_clz(x); }\n");
5093 assert!(text.contains("trunc.i32 %0"), "narrowed to what was asked about: {text}");
5094 assert!(text.contains("ctlz %1"), "and counted there: {text}");
5095
5096 let text = body("int f(unsigned long x) { return __builtin_popcountl(x); }\n");
5097 assert!(text.contains("%1 = ctpop %0"), "{text}");
5098 assert!(!text.contains("call"), "{text}");
5099 }
5100
5101 #[test]
5106 fn a_parity_is_the_low_bit_of_the_set_bit_count() {
5107 let text = body("int f(unsigned x) { return __builtin_parity(x); }\n");
5108 assert!(text.contains("%1 = ctpop %0"), "{text}");
5109 assert!(text.contains("iconst.i32 1"), "{text}");
5110 assert!(text.contains("and %1, %2"), "the low bit of it: {text}");
5111 }
5112
5113 #[test]
5119 fn the_first_set_bit_is_one_based_and_zero_for_a_zero() {
5120 let text = body("int f(int x) { return __builtin_ffs(x); }\n");
5121 assert!(text.contains("%1 = cttz %0"), "{text}");
5122 assert!(text.contains("%4 = add %1, %2"), "one more than the count: {text}");
5123 assert!(text.contains("%5 = icmp ne %0, %3"), "whether there was a bit at all: {text}");
5124 assert!(text.contains("%7 = sub %3, %6"), "spread to a mask: {text}");
5125 assert!(text.contains("%8 = and %4, %7"), "and kept only then: {text}");
5126 assert!(!text.contains("br_if"), "no branch: {text}");
5127 }
5128
5129 #[test]
5139 fn the_redundant_sign_bit_count_is_instructions_and_not_a_call() {
5140 let text = body("int f(int x) { return __builtin_clrsb(x); }\n");
5141 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
5142 assert!(text.contains("%2 = ashr %0, %1"), "the sign over every bit: {text}");
5143 assert!(text.contains("%3 = xor %0, %2"), "folded onto it: {text}");
5144 assert!(text.contains("%5 = shl %3, %4"), "one less than the count: {text}");
5145 assert!(text.contains("%6 = or %5, %4"), "with something to count at zero: {text}");
5146 assert!(text.contains("%7 = ctlz %6"), "{text}");
5147 assert!(!text.contains("call"), "{text}");
5148 assert!(!text.contains("br_if"), "no branch: {text}");
5149 }
5150
5151 #[test]
5157 fn the_unsigned_absolute_value_family_answers_in_the_unsigned_type() {
5158 let text = body("unsigned f(int x) { return __builtin_uabs(x); }\n");
5159 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
5160 assert!(text.contains("%4 = sub %3, %2"), "{text}");
5161 assert!(!text.contains("call"), "nothing declares uabs, so a call would not link: {text}");
5162
5163 let text = body("unsigned long long f(long long x) { return __builtin_ullabs(x); }\n");
5164 assert!(text.contains("iconst.i64 63"), "at the width the name says: {text}");
5165
5166 let text = body("int f(int x) { return __builtin_uabs(x) > 2147483647u; }\n");
5169 assert!(text.contains("icmp ugt"), "compared unsigned: {text}");
5170 }
5171
5172 #[test]
5180 fn the_widest_absolute_value_is_whichever_type_the_target_makes_intmax_t() {
5181 let text = body("long f(long x) { return __builtin_imaxabs(x); }\n");
5182 assert!(text.contains("iconst.i64 63"), "{text}");
5183 assert!(text.contains("%4 = sub %3, %2"), "{text}");
5184 assert!(!text.contains("call"), "{text}");
5185
5186 let text = body("unsigned long f(long x) { return __builtin_umaxabs(x); }\n");
5187 assert!(text.contains("iconst.i64 63"), "{text}");
5188 assert!(!text.contains("call"), "{text}");
5189 }
5190
5191 #[test]
5199 fn an_overflow_predicate_writes_nothing_and_answers_the_bit_the_check_would() {
5200 let text =
5201 body("int f(int a, int b) { return __builtin_add_overflow_p(a, b, (int) 0); }\n");
5202 assert!(text.contains("%2, %3 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
5203 assert!(!text.contains("store"), "nothing is written: {text}");
5204 assert!(!text.contains("call"), "{text}");
5205
5206 let text =
5209 body("int f(int a, int b) { return __builtin_mul_overflow_p(a, b, (long long) 0); }\n");
5210 assert!(text.contains("smul_overflow.(i64, i1)"), "{text}");
5211 assert!(!text.contains("store"), "{text}");
5212
5213 let text = body(concat!(
5216 "int g(void);\n",
5217 "int f(int a, int b) { return __builtin_sub_overflow_p(a, b, g()); }\n",
5218 ));
5219 assert!(!text.contains("call @g"), "the third argument is not evaluated: {text}");
5220 }
5221
5222 #[test]
5232 fn an_overflow_check_is_arithmetic_and_not_a_call() {
5233 let text =
5234 body("int f(int a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
5235 assert!(text.contains("%3, %4 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
5236 assert!(text.contains("store %3 -> %2"), "{text}");
5237 assert!(!text.contains("call"), "{text}");
5238
5239 let text =
5240 body("int f(int a, int b, int *r) { return __builtin_sub_overflow(a, b, r); }\n");
5241 assert!(text.contains("ssub_overflow.(i32, i1) %0, %1"), "{text}");
5242
5243 let text =
5244 body("int f(int a, int b, int *r) { return __builtin_mul_overflow(a, b, r); }\n");
5245 assert!(text.contains("smul_overflow.(i32, i1) %0, %1"), "{text}");
5246
5247 let text = body(
5250 "int f(unsigned a, unsigned b, unsigned *r) { return __builtin_add_overflow(a, b, r); }\n",
5251 );
5252 assert!(text.contains("uadd_overflow.(i32, i1) %0, %1"), "{text}");
5253 }
5254
5255 #[test]
5263 fn an_overflow_check_is_done_at_a_type_that_holds_every_operand() {
5264 let text = body(
5265 "int f(unsigned a, int b, long long *r) { return __builtin_add_overflow(a, b, r); }\n",
5266 );
5267 assert!(text.contains("%3 = zext.i64 %0"), "the unsigned operand keeps its value: {text}");
5268 assert!(text.contains("%4 = sext.i64 %1"), "and so does the signed one: {text}");
5269 assert!(text.contains("sadd_overflow.(i64, i1) %3, %4"), "{text}");
5270
5271 let text = body(
5274 "int f(long long a, long long b, long long *r) { return __builtin_mul_overflow(a, b, r); }\n",
5275 );
5276 assert!(text.contains("smul_overflow.(i64, i1) %0, %1"), "{text}");
5277 assert!(!text.contains("sext."), "{text}");
5278 assert!(!text.contains("zext.i64"), "{text}");
5280 }
5281
5282 #[test]
5290 fn an_overflow_check_writes_the_wrapped_answer_whether_or_not_it_fit() {
5291 let text =
5292 body("int f(int a, int b, char *r) { return __builtin_sub_overflow(a, b, r); }\n");
5293 assert!(text.contains("%3, %4 = ssub_overflow.(i32, i1) %0, %1"), "{text}");
5294 assert!(text.contains("%5 = trunc.i8 %3"), "narrowed to where it goes: {text}");
5295 assert!(text.contains("%6 = sext.i32 %5"), "and back: {text}");
5296 assert!(text.contains("%7 = icmp ne %6, %3"), "which is whether it fit: {text}");
5297 assert!(text.contains("store %5 -> %2"), "the narrowed value is stored either way: {text}");
5298 assert!(text.contains("%8 = or %4, %7"), "and either bit is an overflow: {text}");
5299 }
5300
5301 #[test]
5308 fn a_call_needing_more_than_the_widest_type_still_compiles() {
5309 for name in ["add", "sub", "mul"] {
5310 let source = format!(
5311 "int f(unsigned __int128 a, long long b, __int128 *r) {{\n \
5312 return __builtin_{name}_overflow(a, b, r);\n}}\n"
5313 );
5314 let mut opts = options();
5315 opts.emit = EmitKind::MirFinal;
5316 assert!(!run(&opts, &source).failed(), "{name} was refused or stopped the back end");
5317 }
5318 }
5319
5320 #[test]
5323 fn an_overflow_check_over_something_that_is_not_an_integer_says_so() {
5324 let messages =
5325 errors("int f(double a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
5326 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
5327
5328 let messages =
5329 errors("int f(int a, int b, double *r) { return __builtin_add_overflow(a, b, r); }\n");
5330 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
5331 }
5332
5333 #[test]
5344 fn an_ordered_access_is_ordered_in_the_ir() {
5345 let text = body("int f(int *p) { return __atomic_load_n(p, 0); }\n");
5346 assert!(text.contains("atomic_load.i32 %0, align 4, relaxed"), "{text}");
5347
5348 let text = body("long f(long *p) { return __atomic_load_n(p, 2); }\n");
5349 assert!(text.contains("atomic_load.i64 %0, align 8, acquire"), "{text}");
5350
5351 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
5352 assert!(text.contains("atomic_store %1 -> %0, align 4, release"), "{text}");
5353
5354 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
5355 assert!(text.contains("atomic_store %1 -> %0, align 4, seq_cst"), "{text}");
5356
5357 let text = body("void f(char *p, int v) { __atomic_store_n(p, v, 0); }\n");
5360 assert!(text.contains("trunc.i8 %1"), "{text}");
5361 assert!(text.contains("atomic_store %2 -> %0, align 1, relaxed"), "{text}");
5362 }
5363
5364 #[test]
5373 fn an_ordered_access_is_the_plain_instruction_on_this_machine() {
5374 let text = asm("int f(int *p) { return __atomic_load_n(p, 5); }\n");
5375 assert!(text.contains("movl\t(%rdi), %eax"), "{text}");
5376 assert!(!text.contains("mfence"), "a load needs no barrier here: {text}");
5377
5378 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
5379 assert!(text.contains("movl\t%esi, (%rdi)"), "{text}");
5380 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
5381
5382 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
5383 let (before, after) = text.split_once("mfence").expect("a barrier: {text}");
5384 assert!(before.contains("movl\t%esi, (%rdi)"), "the store comes first: {text}");
5385 assert!(!after.contains("movl"), "and nothing else is between them: {text}");
5386 }
5387
5388 #[test]
5398 fn a_barrier_is_one_instruction_at_the_strongest_ordering_and_none_below_it() {
5399 assert!(asm("void f(void) { __atomic_thread_fence(5); }\n").contains("mfence"));
5400 assert!(asm("void f(void) { __sync_synchronize(); }\n").contains("mfence"));
5401
5402 for weaker in ["1", "2", "3", "4"] {
5403 let source = format!("void f(void) {{ __atomic_thread_fence({weaker}); }}\n");
5404 assert!(!asm(&source).contains("mfence"), "{weaker} costs nothing here");
5405 }
5406 }
5407
5408 #[test]
5418 fn the_three_x86_fences_are_the_barrier_the_strongest_ordering_gives() {
5419 for name in ["__builtin_ia32_sfence", "__builtin_ia32_lfence", "__builtin_ia32_mfence"] {
5420 let source = format!("void f(void) {{ {name}(); }}\n");
5421 assert!(asm(&source).contains("mfence"), "{name} is a barrier");
5422 let text = body(&source);
5423 assert!(text.contains("fence seq_cst"), "{name}: {text}");
5424 }
5425
5426 let result = run(&options(), "void f(void) { __builtin_ia32_sfence(1); }\n");
5427 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
5428 assert!(result.messages[0].contains("too many arguments"), "{:?}", result.messages);
5429 }
5430
5431 #[test]
5437 fn a_compare_and_exchange_is_one_instruction_answering_two_things() {
5438 let text =
5441 body("int f(int *p, int e, int d) { return __sync_val_compare_and_swap(p, e, d); }\n");
5442 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
5443 assert!(text.contains("return %3"), "the value it found: {text}");
5444
5445 let text =
5446 body("int f(int *p, int e, int d) { return __sync_bool_compare_and_swap(p, e, d); }\n");
5447 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
5448 assert!(text.contains("zext.i32 %4"), "whether it happened: {text}");
5449
5450 let text = body(
5453 "int f(int *p, int *e, int d) { return __atomic_compare_exchange_n(p, e, d, 0, 4, 2); }\n",
5454 );
5455 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
5456 assert!(text.contains("%4, %5 = cmpxchg.(i32, i1) %0, %3, %2, align 4, acq_rel"), "{text}");
5457 assert!(text.contains("br_if %5, block2, block1"), "{text}");
5458 assert!(text.contains("store %4 -> %1, align 4"), "{text}");
5459
5460 let text = body(
5463 "int f(int *p, int *e, int *d) { return __atomic_compare_exchange(p, e, d, 0, 5, 5); }\n",
5464 );
5465 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
5466 assert!(text.contains("%4 = load.i32 %2, align 4"), "{text}");
5467 assert!(text.contains("%5, %6 = cmpxchg.(i32, i1) %0, %3, %4, align 4, seq_cst"), "{text}");
5468 }
5469
5470 #[test]
5477 fn a_compare_and_exchange_is_a_locked_instruction_at_the_width_of_the_object() {
5478 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
5479 for (ty, suffix, reg) in widths {
5480 let source = format!(
5481 "int f({ty} *p, {ty} e, {ty} d) {{ return __sync_bool_compare_and_swap(p, e, d); }}\n"
5482 );
5483 let text = asm(&source);
5484 assert!(text.contains("\tlock\n"), "{ty}: {text}");
5485 assert!(text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
5486 assert!(text.contains("sete\t"), "{ty}: {text}");
5487 }
5488 let source =
5489 "int f(long *p, long e, long d) { return __sync_bool_compare_and_swap(p, e, d); }\n";
5490 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
5491
5492 for order in ["0", "2", "3", "4", "5"] {
5496 let call = format!("__atomic_compare_exchange_n(p, e, d, 0, {order}, 0)");
5497 let source = format!("int f(int *p, int *e, int d) {{ return {call}; }}\n");
5498 let text = asm(&source);
5499 assert!(text.contains("cmpxchgl\t"), "{order}: {text}");
5500 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
5501 }
5502 }
5503
5504 #[test]
5516 fn a_read_modify_write_is_one_instruction_and_the_arithmetic_a_name_asks_for() {
5517 let text = body("int f(int *p, int v) { return __atomic_fetch_add(p, v, 5); }\n");
5518 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
5519 assert!(text.contains("return %2"), "the value that was there: {text}");
5520
5521 let text = body("int f(int *p, int v) { return __atomic_add_fetch(p, v, 5); }\n");
5522 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
5523 assert!(text.contains("%3 = add %2, %1"), "and the value afterwards: {text}");
5524
5525 let text = body("int f(int *p, int v) { return __atomic_sub_fetch(p, v, 5); }\n");
5526 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
5527 assert!(text.contains("%3 = sub %2, %1"), "{text}");
5528
5529 let text = body("int f(int *p, int v) { return __sync_fetch_and_sub(p, v); }\n");
5531 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
5532
5533 let text = body("int f(int *p, int v) { return __atomic_exchange_n(p, v, 5); }\n");
5536 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, seq_cst"), "{text}");
5537
5538 let text = body("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
5539 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, acquire"), "{text}");
5540
5541 let text = body("void f(int *p) { __sync_lock_release(p); }\n");
5544 assert!(text.contains("release"), "{text}");
5545 assert!(text.contains("%1 = iconst.i32 0"), "{text}");
5546
5547 let text = body("void f(int *p, int guard) { __sync_lock_release(p, guard); }\n");
5551 assert!(text.contains("%2 = iconst.i32 0"), "{text}");
5552 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
5553
5554 let text = body("int f(int *p, int v) { return __atomic_fetch_and(p, v, 5); }\n");
5557 assert!(text.contains("%2 = atomic_rmw.i32 and %0, %1, align 4, seq_cst"), "{text}");
5558
5559 let text = body("int f(int *p, int v) { return __sync_or_and_fetch(p, v); }\n");
5560 assert!(text.contains("%2 = atomic_rmw.i32 or %0, %1, align 4, seq_cst"), "{text}");
5561 assert!(text.contains("%3 = or %2, %1"), "and the value afterwards: {text}");
5562
5563 let text = body("int f(int *p, int v) { return __atomic_nand_fetch(p, v, 5); }\n");
5566 assert!(text.contains("%2 = atomic_rmw.i32 nand %0, %1, align 4, seq_cst"), "{text}");
5567 assert!(text.contains("%3 = and %2, %1"), "{text}");
5568 assert!(text.contains("%4 = iconst.i32 -1"), "{text}");
5569 assert!(text.contains("%5 = xor %3, %4"), "{text}");
5570 }
5571
5572 #[test]
5583 fn a_bitwise_read_modify_write_is_a_loop_around_the_compare_and_exchange() {
5584 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
5585 for (ty, suffix, reg) in widths {
5586 for (name, call, insn) in [
5587 ("and", "__atomic_fetch_and(p, v, 5)", "and"),
5588 ("or", "__sync_fetch_and_or(p, v)", "or"),
5589 ("xor", "__atomic_xor_fetch(p, v, 5)", "xor"),
5590 ] {
5591 let source = format!("{ty} f({ty} *p, {ty} v) {{ return {call}; }}\n");
5592 let text = asm(&source);
5593 assert!(text.contains("\tlock\n"), "{ty} {name}: {text}");
5594 assert!(
5595 text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")),
5596 "{ty} {name}: {text}"
5597 );
5598 assert!(text.contains(&format!("{insn}{suffix}\t")), "{ty} {name}: {text}");
5599 assert!(!text.contains("\txadd"), "{ty} {name} is not an add: {text}");
5601 assert!(!text.contains("\txchg"), "{ty} {name} is not an exchange: {text}");
5602 }
5603 }
5604 let source = "long f(long *p, long v) { return __atomic_fetch_or(p, v, 5); }\n";
5605 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
5606
5607 let text = asm("int f(int *p, int v) { return __sync_fetch_and_nand(p, v); }\n");
5611 assert!(text.contains("cmpxchgl\t"), "{text}");
5612 assert!(text.contains("andl\t"), "{text}");
5613 assert!(text.contains("notl\t"), "{text}");
5614 }
5615
5616 #[test]
5625 fn an_access_through_a_second_pointer_is_the_same_access_and_one_more() {
5626 let text = body("void f(int *p, int *r) { __atomic_load(p, r, 5); }\n");
5627 assert!(text.contains("%2 = atomic_load.i32 %0, align 4, seq_cst"), "{text}");
5628 assert!(text.contains("store %2 -> %1, align 4"), "and out through the place: {text}");
5629
5630 let text = body("void f(int *p, int *v) { __atomic_store(p, v, 3); }\n");
5631 assert!(text.contains("%2 = load.i32 %1, align 4"), "in through the place: {text}");
5632 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
5633
5634 let text = body("void f(int *p, int *v, int *r) { __atomic_exchange(p, v, r, 5); }\n");
5637 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
5638 assert!(text.contains("%4 = atomic_rmw.i32 xchg %0, %3, align 4, seq_cst"), "{text}");
5639 assert!(text.contains("store %4 -> %2, align 4"), "{text}");
5640 }
5641
5642 #[test]
5653 fn a_flag_is_an_exchange_of_one_byte_and_a_store_of_a_zero_over_the_same_byte() {
5654 for pointer in ["char", "int", "void"] {
5655 let source = format!("int f({pointer} *p) {{ return __atomic_test_and_set(p, 5); }}\n");
5656 let text = body(&source);
5657 assert!(text.contains("%1 = iconst.i8 1"), "{pointer}: {text}");
5658 assert!(
5659 text.contains("%2 = atomic_rmw.i8 xchg %0, %1, align 1, seq_cst"),
5660 "{pointer}: {text}"
5661 );
5662 assert!(text.contains("%4 = icmp ne %2, %3"), "{pointer}: {text}");
5663
5664 let source = format!("void f({pointer} *p) {{ __atomic_clear(p, 3); }}\n");
5665 let text = body(&source);
5666 assert!(text.contains("atomic_store %2 -> %0, align 1, release"), "{pointer}: {text}");
5667 }
5668
5669 let text = asm("int f(int *p) { return __atomic_test_and_set(p, 5); }\n");
5672 assert!(text.contains("xchgb\t%al, (%rdi)"), "{text}");
5673 assert!(text.contains("setne\t"), "{text}");
5674 }
5675
5676 #[test]
5684 fn a_read_modify_write_is_an_exchange_or_a_locked_add_at_the_width_of_the_object() {
5685 let widths = [("char", "b", "%sil"), ("short", "w", "%si"), ("int", "l", "%esi")];
5686 for (ty, suffix, reg) in widths {
5687 let source =
5688 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_fetch_add(p, v, 5); }}\n");
5689 let text = asm(&source);
5690 assert!(text.contains("\tlock\n"), "{ty}: {text}");
5691 assert!(text.contains(&format!("xadd{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
5692
5693 let source =
5694 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_exchange_n(p, v, 5); }}\n");
5695 let text = asm(&source);
5696 assert!(text.contains(&format!("xchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
5697 assert!(!text.contains("\tlock\n"), "an exchange is locked already: {ty}: {text}");
5698 }
5699 let source = "long f(long *p, long v) { return __atomic_fetch_add(p, v, 5); }\n";
5700 assert!(asm(source).contains("xaddq\t%rsi, (%rdi)"), "{}", asm(source));
5701
5702 let source = "int f(int *p, int v) { return __atomic_fetch_sub(p, v, 5); }\n";
5705 let text = asm(source);
5706 assert!(text.contains("negl\t"), "{text}");
5707 assert!(text.contains("xaddl\t"), "{text}");
5708
5709 for order in ["0", "2", "3", "4", "5"] {
5712 let source =
5713 format!("int f(int *p, int v) {{ return __atomic_fetch_add(p, v, {order}); }}\n");
5714 let text = asm(&source);
5715 assert!(text.contains("xaddl\t"), "{order}: {text}");
5716 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
5717 }
5718
5719 let text = asm("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
5723 assert!(text.contains("xchgl\t%esi, (%rdi)"), "{text}");
5724 let text = asm("void f(int *p) { __sync_lock_release(p); }\n");
5731 assert!(text.contains("xorl\t%eax, %eax"), "{text}");
5732 assert!(text.contains("movl\t%eax, (%rdi)"), "{text}");
5733 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
5734 }
5735
5736 #[test]
5748 fn the_lock_free_questions_are_answered_as_constants() {
5749 for size in ["1", "2", "4", "8"] {
5750 let source =
5751 format!("int f(void) {{ return __atomic_always_lock_free({size}, 0); }}\n");
5752 let text = asm(&source);
5753 assert!(text.contains("movb\t$1, %al"), "{size} bytes is lock free: {text}");
5754 assert!(!text.contains("call"), "and is not a call: {text}");
5755 }
5756 for size in ["3", "16", "sizeof(long double)"] {
5757 let source = format!("int f(void) {{ return __atomic_is_lock_free({size}, 0); }}\n");
5758 let text = asm(&source);
5759 assert!(text.contains("movb\t$0, %al"), "{size} bytes is not: {text}");
5760 assert!(!text.contains("call"), "and is not a call either: {text}");
5761 }
5762
5763 let text = asm("int f(int n) { return __atomic_is_lock_free(n, 0); }\n");
5767 assert!(text.contains("movb\t$0, %al"), "a size nobody knows is not lock free: {text}");
5768 let text = asm("int f(int *p) { return __atomic_always_lock_free(8, p); }\n");
5769 assert!(text.contains("movb\t$0, %al"), "eight bytes at four is not: {text}");
5770 let text = asm("int f(long *p) { return __atomic_always_lock_free(8, p); }\n");
5771 assert!(text.contains("movb\t$1, %al"), "and at eight it is: {text}");
5772 }
5773
5774 #[test]
5786 fn a_memory_order_an_operation_cannot_carry_is_read_as_the_strongest() {
5787 let mut opts = options();
5788 opts.emit = EmitKind::Ir;
5789
5790 let acquire_store = run(&opts, "void f(int *p, int v) { __atomic_store_n(p, v, 2); }\n");
5791 assert!(acquire_store.text().contains("seq_cst"), "{:?}", acquire_store.text());
5792 assert!(acquire_store.messages[0].contains("[W0333]"), "{:?}", acquire_store.messages);
5793
5794 let nonsense = run(&opts, "int f(int *p) { return __atomic_load_n(p, 99); }\n");
5795 assert!(nonsense.text().contains("seq_cst"), "{:?}", nonsense.text());
5796 assert!(nonsense.messages[0].contains("[W0333]"), "{:?}", nonsense.messages);
5797
5798 let computed = run(&opts, "int f(int *p, int n) { return __atomic_load_n(p, n); }\n");
5799 assert!(computed.text().contains("seq_cst"), "{:?}", computed.text());
5800 assert_eq!(computed.messages, Vec::<String>::new(), "a computed order is not a mistake");
5801 }
5802
5803 #[test]
5815 fn a_conversion_between_a_float_and_the_widest_unsigned_integer_is_written_without_a_branch() {
5816 let text = asm("double f(unsigned long long x) { return (double)x; }\n");
5817 assert!(text.contains("cvtsi2sdq"), "the signed conversion is what runs: {text}");
5818 assert!(text.contains("shrq"), "with the value halved first: {text}");
5819 assert!(text.contains("addsd"), "and doubled after: {text}");
5820 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
5821
5822 let text = asm("unsigned long long f(double d) { return (unsigned long long)d; }\n");
5823 assert!(text.contains("cvttsd2siq"), "the signed conversion is what runs: {text}");
5824 assert!(text.contains("subsd"), "with half the range taken off first: {text}");
5825 assert!(text.contains("shlq\t$63"), "and the top bit put back: {text}");
5826 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
5827 }
5828
5829 #[test]
5840 fn a_plain_name_the_program_took_is_the_programs_own_function() {
5841 let taken = concat!(
5842 "static long long llabs(long long b) { return 7; }\n",
5843 "long long f(long long x) { return llabs(x); }\n",
5844 );
5845 assert!(ir(taken).contains("call @llabs"), "a static definition is the program's own");
5846
5847 let retyped = concat!("int llabs(int b);\n", "int f(int x) { return llabs(x); }\n",);
5848 assert!(ir(retyped).contains("call @llabs"), "another type is another function");
5849
5850 let plain = concat!(
5851 "long long llabs(long long b);\n",
5852 "long long f(long long x) { return llabs(x); }\n",
5853 );
5854 let mut opts = options();
5855 opts.emit = EmitKind::Ir;
5856 assert!(!run(&opts, plain).text().contains("call @llabs"), "the library's by default");
5857
5858 opts.builtins = false;
5859 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin");
5860
5861 opts.builtins = true;
5862 opts.no_builtin = vec!["llabs".to_owned()];
5863 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin-llabs");
5864 let one = "long labs(long b);\nlong f(long x) { return labs(x); }\n";
5865 assert!(!run(&opts, one).text().contains("call @labs"), "one name and not the family");
5866
5867 opts.no_builtin = Vec::new();
5870 opts.builtins = false;
5871 let prefixed = "long long f(long long x) { return __builtin_llabs(x); }\n";
5872 assert!(!run(&opts, prefixed).text().contains("call @llabs"), "the prefix is a promise");
5873 }
5874
5875 #[test]
5888 fn the_hint_builtins_are_their_first_argument_and_the_hint_leaves_no_trace() {
5889 let text = ir(concat!(
5890 "long a = __builtin_expect(7, 1);\n",
5891 "long b = __builtin_expect_with_probability(9, 1, 0.9);\n",
5892 "unsigned long c = sizeof(__builtin_expect((char)1, 1));\n",
5893 ));
5894 assert!(text.contains("global @a : i64 = 7,"), "{text}");
5895 assert!(text.contains("global @b : i64 = 9,"), "{text}");
5896 assert!(text.contains("global @c : i64 = 8,"), "{text}");
5897 assert!(!text.contains("__builtin_expect"), "it is not a call to anything:\n{text}");
5898
5899 let text = body("long f(char c) { return __builtin_expect(c, 1); }\n");
5902 assert!(text.contains("sext"), "{text}");
5903
5904 let one = "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 1\n %2 = sext.i64 %1\n return %0\n";
5908 assert_eq!(body("int f(void) { int i = 0; __builtin_expect(1, i++); return i; }\n"), one);
5909 let source = "int g(void) { int i = 0; __builtin_expect_with_probability(1, i++, 0.5); return i; }\n";
5910 assert_eq!(body(source), one);
5911
5912 let kept = body("int f(int n) { int i = 0; __builtin_expect(n, i++); return i; }\n");
5917 assert!(kept.contains("add.nsw"), "the hint still runs: {kept}");
5918 assert!(kept.ends_with("return %3\n"), "and the answer is what it left behind: {kept}");
5919 let both = "int g(int n) { int i = 0; __builtin_expect_with_probability(n, i++, 0.5); return i; }\n";
5920 assert!(body(both).contains("add.nsw"), "and so does the one with three arguments");
5921 }
5922
5923 #[test]
5935 fn a_promise_that_control_does_not_arrive_writes_no_instruction() {
5936 let promised = "int f(int x) { if (x) return 1; __builtin_unreachable(); }\n";
5937 let text = ir(promised);
5938 assert!(text.contains(" unreachable_hint\n"), "{text}");
5939 assert!(!text.contains("call"), "it is not a call to anything:\n{text}");
5940
5941 let after = body("int g(int x) { __builtin_unreachable(); return x; }\n");
5945 assert!(after.contains("return"), "{after}");
5946
5947 let text = asm(promised);
5950 let mine = text.split_once("\nf:\n").expect("a definition").1;
5951 let mine = mine.split_once("\t.size").expect("a definition").0;
5952 let plain = asm("int f(int x) { if (x) return 1; }\n");
5953 let plain = plain.split_once("\nf:\n").expect("a definition").1;
5954 let plain = plain.split_once("\t.size").expect("a definition").0;
5955 assert_eq!(mine, plain);
5956 let last = mine.lines().rfind(|line| !line.trim_start().starts_with('.'));
5959 assert_eq!(last.map(str::trim), Some("ret"), "{mine}");
5960 assert!(!mine.contains("ud2"), "{mine}");
5961 }
5962
5963 #[test]
5970 fn a_library_builtin_is_diagnosed_under_the_name_the_program_wrote() {
5971 let mut opts = options();
5972 opts.emit = EmitKind::Ir;
5973 let messages = run(&opts, "void f(void) { __builtin_abort(1); }\n").messages;
5974 assert!(
5975 messages.iter().any(|m| m.contains("__builtin_abort")),
5976 "expected the written name in {messages:?}"
5977 );
5978 }
5979
5980 #[test]
5988 fn a_builtin_nothing_lowers_is_refused_by_name() {
5989 let mut opts = options();
5990 opts.emit = EmitKind::Ir;
5991 let builtin = "__atomic_signal_fence";
5992 let source = format!("int counter;\nint f(void) {{ return ({builtin}(5), 0); }}\n");
5993 let messages = run(&opts, &source).messages;
5994 let named = messages.iter().any(|m| m.contains(builtin) && m.contains("E0686"));
5995 assert!(named, "expected {builtin} to be refused by name in {messages:?}");
5996 }
5997
5998 #[test]
6007 fn what_is_refused_is_the_call_and_not_the_name() {
6008 let text = ir(concat!(
6009 "void __atomic_signal_fence(int order) { (void)order; }\n",
6010 "void f(void) { __atomic_signal_fence(5); }\n",
6011 ));
6012 assert!(text.contains("call @__atomic_signal_fence"), "{text}");
6013 }
6014
6015 #[test]
6024 fn the_object_size_of_an_address_is_what_the_layout_leaves_in_front_of_it() {
6025 let text = ir(concat!(
6026 "struct S { char a[8]; int n; char b[12]; };\n",
6027 "char g[32];\n",
6028 "struct S gs;\n",
6029 "unsigned long whole = __builtin_object_size(g, 0);\n",
6030 "unsigned long moved = __builtin_object_size(g + 4, 0);\n",
6031 "unsigned long back = __builtin_object_size(g + 30 - 2, 0);\n",
6032 "unsigned long outer = __builtin_object_size(gs.a, 0);\n",
6033 "unsigned long inner = __builtin_object_size(gs.a, 1);\n",
6034 "unsigned long scalar = __builtin_object_size(&gs.n, 1);\n",
6035 "unsigned long after = __builtin_object_size(&gs.n, 0);\n",
6036 "unsigned long into = __builtin_object_size(&gs.b[2], 1);\n",
6037 "unsigned long text = __builtin_object_size(\"hello\", 0);\n",
6038 "unsigned long dyn = __builtin_dynamic_object_size(gs.b, 1);\n",
6039 ));
6040 for (name, size) in [
6041 ("whole", 32),
6042 ("moved", 28),
6043 ("back", 4),
6044 ("outer", 24),
6045 ("inner", 8),
6046 ("scalar", 4),
6047 ("after", 16),
6048 ("into", 10),
6049 ("text", 6),
6050 ("dyn", 12),
6051 ] {
6052 let said = format!("global @{name} : i64 = {size},");
6053 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
6054 }
6055 }
6056
6057 #[test]
6065 fn the_object_behind_an_address_can_be_one_with_automatic_storage() {
6066 let text = body(concat!(
6067 "struct S { char a[8]; int n; char b[12]; };\n",
6068 "unsigned long f(void) {\n",
6069 " char loc[20];\n",
6070 " struct S ls;\n",
6071 " return __builtin_object_size(loc + 3, 0) + __builtin_object_size(ls.b + 2, 1);\n",
6072 "}\n",
6073 ));
6074 assert!(text.contains("iconst.i64 17"), "twenty bytes with three used: {text}");
6075 assert!(text.contains("iconst.i64 10"), "twelve bytes with two used: {text}");
6076 }
6077
6078 #[test]
6088 fn an_address_with_no_object_in_sight_answers_at_the_end_of_the_range_its_kind_asks_for() {
6089 let text = ir(concat!(
6090 "struct T { int n; char f[]; };\n",
6091 "extern char *p;\n",
6092 "extern struct T *t;\n",
6093 "unsigned long largest = __builtin_object_size(p, 0);\n",
6094 "unsigned long nearest = __builtin_object_size(p, 1);\n",
6095 "unsigned long least = __builtin_object_size(p, 2);\n",
6096 "unsigned long tight = __builtin_object_size(p, 3);\n",
6097 "unsigned long flex = __builtin_object_size(t->f, 1);\n",
6098 "int says = __builtin_object_size(p, 0) == (unsigned long)-1;\n",
6099 ));
6100 for name in ["largest", "nearest", "flex"] {
6101 let said = format!("global @{name} : i64 = -1,");
6105 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
6106 }
6107 for name in ["least", "tight"] {
6108 let said = format!("global @{name} : i64 = 0,");
6109 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
6110 }
6111 assert!(text.contains("global @says : i32 = 1,"), "{text}");
6112 }
6113
6114 #[test]
6121 fn the_address_an_object_size_is_asked_about_is_not_evaluated() {
6122 let text = body(concat!(
6123 "extern char *side(void);\n",
6124 "unsigned long f(void) { return __builtin_object_size(side(), 0); }\n",
6125 ));
6126 assert!(!text.contains("call"), "nothing is called: {text}");
6127 }
6128
6129 #[test]
6134 fn a_kind_that_is_not_one_of_the_four_is_refused() {
6135 for source in [
6136 "extern char *p;\nextern int k;\nunsigned long f(void) ".to_owned()
6137 + "{ return __builtin_object_size(p, k); }\n",
6138 "extern char *p;\nunsigned long f(void) { return __builtin_object_size(p, 4); }\n"
6139 .to_owned(),
6140 "extern char *p;\nunsigned long f(void) ".to_owned()
6141 + "{ return __builtin_dynamic_object_size(p, -1); }\n",
6142 ] {
6143 let messages = errors(&source);
6144 let named = messages.iter().any(|m| m.contains("E0709") && m.contains("0 to 3"));
6145 assert!(named, "expected a complaint about the kind in {messages:?}");
6146 }
6147 }
6148
6149 #[test]
6155 fn the_pair_that_saves_a_place_lowers_to_the_two_markers() {
6156 let text = ir(concat!(
6157 "void *buf[5];\n",
6158 "int f(void) {\n",
6159 " if (__builtin_setjmp(buf)) return 2;\n",
6160 " return 1;\n",
6161 "}\n",
6162 "void g(void) { __builtin_longjmp(buf, 1); }\n",
6163 ));
6164 assert!(text.contains("= setjmp_marker.i32 %0\n"), "the save answers a value: {text}");
6165 assert!(text.contains(" longjmp_marker %0\n"), "the restore answers nothing: {text}");
6166 assert!(!text.contains("call @"), "neither of them is a call: {text}");
6167 }
6168
6169 #[test]
6177 fn a_local_of_a_function_that_saves_a_place_gets_a_slot() {
6178 let text = ir(concat!(
6179 "void *buf[5];\n",
6180 "int f(int x) { int a = 0; if (__builtin_setjmp(buf)) return a; a = 1; return x; }\n",
6181 "int g(int x) { int a = 0; if (x) return a; a = 1; return x; }\n",
6182 ));
6183 let (saves, plain) = text.split_once("func @g").expect("both functions");
6184 assert_eq!(saves.matches("= alloca").count(), 2, "the parameter and the local: {text}");
6185 assert!(saves.contains("store %9 -> %2"), "the local is written through: {text}");
6186 assert!(!plain.contains("alloca"), "nothing in the plain one needs a slot: {text}");
6187 }
6188
6189 #[test]
6198 fn the_save_writes_four_words_and_carries_on_in_a_new_block() {
6199 let text =
6200 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
6201 let body = text.split_once("\nf:\n").expect("the function").1;
6202 assert!(body.contains("\tmovq\t%rsp, %rbp\n"), "a frame pointer whatever: {text}");
6203 assert!(body.contains("\tsubq\t$8, %rsp\n"), "no red zone: {text}");
6204 assert!(body.contains("\tmovq\t%rbp, (%rax)\n"), "the frame pointer: {text}");
6205 assert!(body.contains("\tmovq\t%rsp, 16(%rax)\n"), "the stack pointer: {text}");
6206 assert!(body.contains("\tleaq\t.Lf_1(%rip), %rcx\n"), "where to come back to: {text}");
6207 assert!(body.contains("\tmovq\t%rcx, 8(%rax)\n"), "and that goes in the buffer: {text}");
6208 let back = body.split_once(".Lf_1:\n").expect("the block control comes back to").1;
6209 assert!(back.starts_with("\tmovq\t(%rsp), %rax\n"), "the answer is read back: {text}");
6210 }
6211
6212 #[test]
6220 fn a_save_destroys_every_register_the_allocator_hands_out() {
6221 let text =
6222 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
6223 for reg in ["%rbx", "%r12", "%r13", "%r14", "%r15"] {
6224 assert!(text.contains(&format!("\tpushq\t{reg}\n")), "{reg} is saved: {text}");
6225 assert!(text.contains(&format!("\tpopq\t{reg}\n")), "{reg} is restored: {text}");
6226 }
6227 }
6228
6229 #[test]
6236 fn the_restore_puts_the_frame_back_before_it_jumps() {
6237 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
6238 let mut opts = options();
6239 opts.emit = EmitKind::Asm;
6240 opts.opt_level = level;
6241 let source = "void *buf[5];\nvoid g(void) { __builtin_longjmp(buf, 1); }\n";
6242 let result = run(&opts, source);
6243 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
6244 let text = result.text().to_owned();
6245 let jump = text.find("\tjmp\t*%").unwrap_or_else(|| panic!("an indirect jump: {text}"));
6246 let stack = text.find(", %rsp\n").unwrap_or_else(|| panic!("the stack back: {text}"));
6247 let frame = text.find(", %rbp\n").unwrap_or_else(|| panic!("the frame back: {text}"));
6248 assert!(stack < jump, "the stack goes back first at {level:?}: {text}");
6249 assert!(frame < jump, "and so does the frame at {level:?}: {text}");
6250 }
6251 }
6252
6253 #[test]
6259 fn a_longjmp_whose_second_argument_is_not_one_is_turned_down() {
6260 for source in [
6261 "void *buf[5];\nvoid f(void) { __builtin_longjmp(buf, 0); }\n",
6262 "void *buf[5];\nextern int v;\nvoid f(void) { __builtin_longjmp(buf, v); }\n",
6263 ] {
6264 let messages = errors(source);
6265 let named = messages.iter().any(|m| m.contains("E0710"));
6266 assert!(named, "expected a complaint about the value in {messages:?}");
6267 }
6268 }
6269
6270 #[test]
6275 fn a_static_function_nothing_refers_to_is_not_emitted() {
6276 let text = ir("static int dropped(void) { return 1; }\n\
6277 static int kept(void) { return 2; }\n\
6278 int main(void) { return kept(); }\n");
6279 assert!(text.contains("func @kept"), "{text}");
6280 assert!(!text.contains("dropped"), "{text}");
6281 }
6282
6283 #[test]
6289 fn two_static_functions_that_only_call_each_other_are_both_dropped() {
6290 let text = ir("static int ping(void);\n\
6291 static int pong(void) { return ping(); }\n\
6292 static int ping(void) { return pong(); }\n\
6293 int main(void) { return 0; }\n");
6294 assert!(!text.contains("ping"), "{text}");
6295 assert!(!text.contains("pong"), "{text}");
6296 }
6297
6298 #[test]
6304 fn naming_a_static_function_anywhere_keeps_it() {
6305 let text = ir("static int by_address(void) { return 1; }\n\
6306 static int in_an_image(void) { return 2; }\n\
6307 static int deeper(void) { return 3; }\n\
6308 static int reaches_deeper(void) { return deeper(); }\n\
6309 static int (*table[1])(void) = {in_an_image};\n\
6310 int main(void) {\n\
6311 int (*p)(void) = by_address;\n\
6312 return p() + table[0]() + reaches_deeper();\n\
6313 }\n");
6314 for kept in ["by_address", "in_an_image", "deeper", "reaches_deeper"] {
6315 assert!(text.contains(&format!("func @{kept}")), "expected {kept} in:\n{text}");
6316 }
6317 }
6318
6319 #[test]
6325 fn an_attribute_keeps_a_static_function_nothing_refers_to() {
6326 for attribute in ["used", "retain", "constructor", "destructor", "__used__"] {
6327 let source = format!(
6328 "__attribute__(({attribute})) static int kept(void) {{ return 1; }}\n\
6329 int main(void) {{ return 0; }}\n"
6330 );
6331 let text = ir(&source);
6332 assert!(text.contains("func @kept"), "for {attribute}:\n{text}");
6333 }
6334 }
6335
6336 #[test]
6339 fn a_function_anything_could_call_is_emitted_without_being_called() {
6340 let text =
6341 ir("int nobody_here_calls_it(void) { return 1; }\nint main(void) { return 0; }\n");
6342 assert!(text.contains("func @nobody_here_calls_it"), "{text}");
6343 }
6344
6345 #[test]
6352 fn a_classification_c_has_an_operator_for_is_that_operator() {
6353 for (builtin, operator) in [
6354 ("__builtin_isgreater", "binary >"),
6355 ("__builtin_isgreaterequal", "binary >="),
6356 ("__builtin_isless", "binary <"),
6357 ("__builtin_islessequal", "binary <="),
6358 ] {
6359 let source = format!("int f(double x, double y) {{ return {builtin}(x, y); }}\n");
6360 let text = tast(&source);
6361 assert!(text.contains(&format!("{operator} : int")), "for {builtin}:\n{text}");
6362 }
6363 }
6364
6365 #[test]
6374 fn the_classification_builtins_are_comparisons_and_not_calls() {
6375 let text = body("int f(double x, double y) { return __builtin_isunordered(x, y); }\n");
6376 assert_eq!(
6377 text,
6378 "block0(%0: f64, %1: f64):\n %2 = fcmp uno %0, %1\n %3 = zext.i32 \
6379 %2\n return %3\n"
6380 );
6381
6382 let text = body("int f(double x, double y) { return __builtin_islessgreater(x, y); }\n");
6384 assert!(text.contains("fcmp one %0, %1"), "{text}");
6385
6386 let text = body("int f(double x) { return __builtin_isnan(x); }\n");
6387 assert!(text.contains("fcmp uno %0, %0"), "{text}");
6388
6389 let text = body("int f(double x) { return __builtin_isinf(x); }\n");
6390 assert!(text.contains("fconst.f64 0x7ff0000000000000"), "{text}");
6391 assert!(text.contains("fconst.f64 0xfff0000000000000"), "{text}");
6392 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
6393 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
6394 assert!(text.contains("%5 = or %3, %4"), "{text}");
6395
6396 let text = body("int f(double x) { return __builtin_isfinite(x); }\n");
6399 assert!(text.contains("%3 = fcmp olt %2, %0"), "{text}");
6400 assert!(text.contains("%4 = fcmp olt %0, %1"), "{text}");
6401 assert!(text.contains("%5 = and %3, %4"), "{text}");
6402
6403 let text = body("int f(double x) { return __builtin_signbit(x); }\n");
6404 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
6405 assert!(text.contains("icmp slt %1, %2"), "{text}");
6406
6407 let text = body("int f(long double x) { return __builtin_signbitl(x); }\n");
6410 assert!(text.contains("%1 = bitcast.i80 %0"), "{text}");
6411
6412 let text = body("double g(void);\nint f(void) { return __builtin_isnan(g()); }\n");
6415 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
6416 }
6417
6418 #[test]
6425 fn a_classification_spelling_that_names_a_width_converts_before_it_asks() {
6426 let text = ir(concat!(
6427 "int a = __builtin_isinff(1e300);\n",
6428 "int b = __builtin_isinf(1e300);\n",
6429 "int c = __builtin_isnan(0.0);\n",
6433 "int d = __builtin_signbit(-0.0);\n",
6434 "int e = __builtin_islessgreater(1.0, 2.0);\n",
6435 ));
6436 assert!(text.contains("global @a : i32 = 1,"), "{text}");
6437 assert!(text.contains("global @b : i32 = 0,"), "{text}");
6438 assert!(text.contains("global @c : i32 = 0,"), "{text}");
6439 assert!(text.contains("global @d : i32 = 1,"), "{text}");
6440 assert!(text.contains("global @e : i32 = 1,"), "{text}");
6441 }
6442
6443 #[test]
6445 fn a_classification_builtin_refuses_an_argument_that_is_not_floating_point() {
6446 let mut opts = options();
6447 opts.emit = EmitKind::Ir;
6448 let source = concat!(
6449 "int a(int x) { return __builtin_isnan(x); }\n",
6450 "int b(int x, int y) { return __builtin_isunordered(x, y); }\n",
6451 "int c(double x) { return __builtin_isnan(x, x); }\n",
6452 );
6453 let messages = run(&opts, source).messages;
6454 assert_eq!(
6455 messages,
6456 [
6457 "/main.c:1:23: error: non-floating-point argument in call to function \
6458 '__builtin_isnan' [E0685]",
6459 "/main.c:2:30: error: non-floating-point arguments in call to function \
6460 '__builtin_isunordered' [E0685]",
6461 "/main.c:3:26: error: too many arguments to function '__builtin_isnan' [E0511]",
6462 ]
6463 );
6464 }
6465
6466 #[test]
6475 fn the_last_three_classification_builtins_are_comparisons_and_not_calls() {
6476 let text = body("int f(double x) { return __builtin_isnormal(x); }\n");
6477 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
6481 assert!(text.contains("%2 = iconst.i64 9223372036854775807"), "{text}");
6482 assert!(text.contains("%3 = and %1, %2"), "{text}");
6483 assert!(text.contains("%4 = iconst.i64 4503599627370496"), "{text}");
6484 assert!(text.contains("%5 = iconst.i64 9218868437227405312"), "{text}");
6485 assert!(text.contains("%6 = icmp uge %3, %4"), "{text}");
6486 assert!(text.contains("%7 = icmp ult %3, %5"), "{text}");
6487 assert!(text.contains("%8 = and %6, %7"), "{text}");
6488
6489 let text = body("int f(long double x) { return __builtin_isnormal(x); }\n");
6493 assert!(text.contains("%4 = iconst.i80 27670116110564327424"), "{text}");
6494 assert!(text.contains("%5 = iconst.i80 604453686435277732577280"), "{text}");
6495
6496 let text = body("int f(double x) { return __builtin_isinf_sign(x); }\n");
6497 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
6498 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
6499 assert!(text.contains("%7 = sub %5, %6"), "{text}");
6500
6501 let text = body("int f(double x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n");
6502 assert!(text.contains("fcmp uno %0, %0"), "{text}");
6503 assert!(text.contains("fcmp oeq %0, %6"), "{text}");
6504 assert_eq!(text.matches(" = zext.i32 ").count(), 4, "{text}");
6508 assert_eq!(text.matches(" = xor ").count(), 4, "{text}");
6509 assert!(!text.contains("call"), "{text}");
6510
6511 let text = body(concat!(
6514 "double g(void);\n",
6515 "int f(void) { return __builtin_fpclassify(0, 1, 2, 3, 4, g()); }\n",
6516 ));
6517 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
6518 }
6519
6520 #[test]
6527 fn the_last_three_classification_builtins_fold_where_their_operand_is_a_constant() {
6528 let text = ir(concat!(
6529 "int a = __builtin_isnormal(1.0);\n",
6530 "int b = __builtin_isnormal(0.0);\n",
6531 "int c = __builtin_isnormal(1.0 / 0.0);\n",
6532 "int d = __builtin_isinf_sign(-1.0 / 0.0);\n",
6533 "int e = __builtin_isinf_sign(1.0);\n",
6534 "int g = __builtin_fpclassify(0, 1, 2, 3, 4, 0.0);\n",
6535 "int h = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0);\n",
6536 "int i = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0 / 0.0);\n",
6537 ));
6538 assert!(text.contains("global @a : i32 = 1,"), "{text}");
6539 assert!(text.contains("global @b : i32 = 0,"), "{text}");
6540 assert!(text.contains("global @c : i32 = 0,"), "{text}");
6541 assert!(text.contains("global @d : i32 = -1,"), "{text}");
6542 assert!(text.contains("global @e : i32 = 0,"), "{text}");
6543 assert!(text.contains("global @g : i32 = 4,"), "{text}");
6544 assert!(text.contains("global @h : i32 = 2,"), "{text}");
6545 assert!(text.contains("global @i : i32 = 1,"), "{text}");
6546 }
6547
6548 #[test]
6554 fn fpclassify_refuses_an_answer_that_is_not_an_integer_constant() {
6555 let mut opts = options();
6556 opts.emit = EmitKind::Ir;
6557 let source = concat!(
6558 "int a(double x, int n) { return __builtin_fpclassify(0, 1, n, 3, 4, x); }\n",
6559 "int b(double x) { return __builtin_fpclassify(0, 1, 2, 3, x); }\n",
6560 "int c(int x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n",
6561 );
6562 let messages = run(&opts, source).messages;
6563 assert_eq!(
6564 messages,
6565 [
6566 "/main.c:1:60: error: non-const integer argument 3 in call to function \
6567 '__builtin_fpclassify' [E0687]",
6568 "/main.c:2:26: error: too few arguments to function '__builtin_fpclassify' \
6569 [E0511]",
6570 "/main.c:3:23: error: non-floating-point argument in call to function \
6571 '__builtin_fpclassify' [E0685]",
6572 ]
6573 );
6574 }
6575
6576 #[test]
6584 fn a_builtin_whose_answer_is_a_constant_is_one_and_not_a_call() {
6585 let text = ir(concat!(
6586 "double a = __builtin_inf();\n",
6587 "float b = __builtin_huge_valf();\n",
6588 "long double c = __builtin_infl();\n",
6589 "double d = __builtin_huge_val();\n",
6590 ));
6591 assert!(text.contains("global @a : f64 = 0x7ff0000000000000,"), "{text}");
6592 assert!(text.contains("global @b : f32 = 0x7f800000,"), "{text}");
6593 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
6594 assert!(text.contains("global @d : f64 = 0x7ff0000000000000,"), "{text}");
6595 assert!(!text.contains("call"), "{text}");
6596 }
6597
6598 #[test]
6607 fn a_nan_is_written_with_the_payload_the_program_asked_for() {
6608 let text = ir(concat!(
6609 "double a = __builtin_nan(\"\");\n",
6610 "double b = __builtin_nan(\"0x1\");\n",
6611 "double c = __builtin_nan(\"010\");\n",
6613 "double d = __builtin_nans(\"\");\n",
6614 "double e = __builtin_nans(\"0x1\");\n",
6615 "float f = __builtin_nanf(\"0x1\");\n",
6616 "float g = __builtin_nansf(\"\");\n",
6617 "long double h = __builtin_nansl(\"\");\n",
6618 ));
6619 assert!(text.contains("global @a : f64 = 0x7ff8000000000000,"), "{text}");
6620 assert!(text.contains("global @b : f64 = 0x7ff8000000000001,"), "{text}");
6621 assert!(text.contains("global @c : f64 = 0x7ff8000000000008,"), "{text}");
6622 assert!(text.contains("global @d : f64 = 0x7ff4000000000000,"), "{text}");
6623 assert!(text.contains("global @e : f64 = 0x7ff0000000000001,"), "{text}");
6624 assert!(text.contains("global @f : f32 = 0x7fc00001,"), "{text}");
6625 assert!(text.contains("global @g : f32 = 0x7fa00000,"), "{text}");
6626 assert!(text.contains("f80 0x7fffa000000000000000"), "{text}");
6627
6628 let text = ir(concat!(
6631 "double f(const char *p) { return __builtin_nan(p); }\n",
6632 "double g(void) { return __builtin_nans(\"1x\"); }\n",
6633 ));
6634 assert_eq!(text.matches("call @nan(").count(), 1, "{text}");
6635 assert_eq!(text.matches("call @nans(").count(), 1, "{text}");
6636 }
6637
6638 #[test]
6646 fn the_length_and_the_order_of_a_string_literal_are_known_here() {
6647 let text = ir(concat!(
6648 "unsigned long a = __builtin_strlen(\"hello\");\n",
6649 "unsigned long b = __builtin_strlen(\"a\\0bc\");\n",
6650 "int c = __builtin_strcmp(\"X\", \"X\\376\") < 0;\n",
6651 "int d = __builtin_strcmp(\"abc\", \"abc\");\n",
6652 "int e = __builtin_strcmp(\"abc\", \"ab\") > 0;\n",
6653 ));
6654 assert!(text.contains("global @a : i64 = 5,"), "{text}");
6655 assert!(text.contains("global @b : i64 = 1,"), "{text}");
6656 assert!(text.contains("global @c : i32 = 1,"), "{text}");
6657 assert!(text.contains("global @d : i32 = 0,"), "{text}");
6658 assert!(text.contains("global @e : i32 = 1,"), "{text}");
6659 assert!(!text.contains("call"), "{text}");
6660
6661 let text = ir("unsigned long f(const char *p) { return __builtin_strlen(p); }\n");
6663 assert!(text.contains("call @strlen("), "{text}");
6664 }
6665
6666 #[test]
6673 fn a_sign_builtin_is_a_mask_over_the_bits_and_not_a_call() {
6674 let text = body("double f(double x) { return __builtin_fabs(x); }\n");
6675 assert!(text.contains("bitcast.i64 %0"), "{text}");
6676 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
6677 assert!(text.contains("and %1, %2"), "{text}");
6678 assert!(text.contains("bitcast.f64 %3"), "{text}");
6679 assert!(!text.contains("call"), "{text}");
6680
6681 let text = body("double f(double x, double y) { return __builtin_copysign(x, y); }\n");
6682 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
6683 assert!(text.contains("%8 = or %4, %7"), "{text}");
6684 assert!(!text.contains("call"), "{text}");
6685
6686 let text = body("long double f(long double x) { return __builtin_fabsl(x); }\n");
6689 assert!(text.contains("bitcast.i80 %0"), "{text}");
6690 assert!(text.contains("bitcast.f80"), "{text}");
6691
6692 let text = body("double f(float x) { return __builtin_fabs(x); }\n");
6695 assert!(text.contains("fpext.f64 %0"), "{text}");
6696 assert!(text.contains("bitcast.i64 %1"), "{text}");
6697 }
6698
6699 #[test]
6708 fn the_plain_math_names_are_the_same_mask_and_not_a_call() {
6709 let text =
6710 body(concat!("double fabs(double x);\n", "double f(double x) { return fabs(x); }\n",));
6711 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
6712 assert!(!text.contains("call"), "{text}");
6713
6714 let text =
6715 body(concat!("float fabsf(float x);\n", "float f(float x) { return fabsf(x); }\n",));
6716 assert!(text.contains("bitcast.i32 %0"), "{text}");
6717 assert!(!text.contains("call"), "{text}");
6718
6719 let text = body(concat!(
6720 "double copysign(double x, double y);\n",
6721 "double f(double x, double y) { return copysign(x, y); }\n",
6722 ));
6723 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
6724 assert!(!text.contains("call"), "{text}");
6725
6726 let text = body(concat!(
6727 "float copysignf(float x, float y);\n",
6728 "float f(float x, float y) { return copysignf(x, y); }\n",
6729 ));
6730 assert!(!text.contains("call"), "{text}");
6731
6732 let text = ir(concat!(
6736 "long double fabsl(long double x);\n",
6737 "long double f(long double x) { return fabsl(x); }\n",
6738 ));
6739 assert!(text.contains("call @fabsl"), "{text}");
6740 }
6741
6742 #[test]
6750 fn a_plain_math_name_the_program_took_is_the_programs_own_function() {
6751 let taken = concat!(
6752 "static double fabs(double b) { return 7; }\n",
6753 "double f(double x) { return fabs(x); }\n",
6754 );
6755 assert!(ir(taken).contains("call @fabs"), "a static definition is the program's own");
6756
6757 let retyped = concat!("int fabs(int b);\n", "int f(int x) { return fabs(x); }\n");
6758 assert!(ir(retyped).contains("call @fabs"), "another type is another function");
6759
6760 let plain = concat!("double fabs(double b);\n", "double f(double x) { return fabs(x); }\n");
6761 let mut opts = options();
6762 opts.emit = EmitKind::Ir;
6763 assert!(!run(&opts, plain).text().contains("call @fabs"), "the library's by default");
6764
6765 opts.builtins = false;
6766 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin");
6767
6768 opts.builtins = true;
6769 opts.no_builtin = vec!["fabs".to_owned()];
6770 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin-fabs");
6771 let one = concat!(
6772 "double copysign(double a, double b);\n",
6773 "double f(double x) { return copysign(x, 1.0); }\n",
6774 );
6775 assert!(!run(&opts, one).text().contains("call @copysign"), "one name and not the family");
6776
6777 opts.no_builtin = Vec::new();
6779 opts.builtins = false;
6780 let prefixed = "double f(double x) { return __builtin_fabs(x); }\n";
6781 assert!(!run(&opts, prefixed).text().contains("call @fabs"), "the prefix is not a library");
6782 }
6783
6784 #[test]
6793 fn the_sign_builtins_answer_a_zero_and_a_nan_the_way_the_bits_say() {
6794 let text = ir(concat!(
6795 "double a = __builtin_fabs(-3.5);\n",
6796 "double b = __builtin_copysign(1.0, -0.0);\n",
6797 "double c = __builtin_copysign(0.0, -2.0);\n",
6798 "double d = __builtin_copysign(-__builtin_nan(\"\"), 1.0);\n",
6800 "double e = __builtin_fabs(-__builtin_nan(\"0x1\"));\n",
6801 "float g = __builtin_copysignf(-0.0f, 2.0f);\n",
6802 "long double h = __builtin_copysignl(1.0L, -1.0L);\n",
6803 "long double i = __builtin_fabsl(-__builtin_infl());\n",
6804 ));
6805 assert!(text.contains("global @a : f64 = 0x400c000000000000,"), "{text}");
6806 assert!(text.contains("global @b : f64 = 0xbff0000000000000,"), "{text}");
6807 assert!(text.contains("global @c : f64 = 0x8000000000000000,"), "{text}");
6808 assert!(text.contains("global @d : f64 = 0x7ff8000000000000,"), "{text}");
6809 assert!(text.contains("global @e : f64 = 0x7ff8000000000001,"), "{text}");
6810 assert!(text.contains("global @g : f32 = 0x0,"), "{text}");
6811 assert!(text.contains("f80 0xbfff8000000000000000"), "{text}");
6812 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
6813 }
6814
6815 #[test]
6823 fn the_complex_builtins_are_the_halves_of_the_value_and_not_a_call() {
6824 let text = body("double f(_Complex double z) { return __builtin_creal(z); }\n");
6825 assert!(!text.contains("call"), "{text}");
6826 let text = body("double f(_Complex double z) { return __builtin_cimag(z); }\n");
6827 assert!(!text.contains("call"), "{text}");
6828
6829 let text = body("_Complex double f(_Complex double z) { return __builtin_conj(z); }\n");
6832 assert_eq!(text.matches("fneg").count(), 1, "{text}");
6833 assert!(!text.contains("call"), "{text}");
6834 let negated = body("_Complex double f(_Complex double z) { return -z; }\n");
6835 assert_eq!(negated.matches("fneg").count(), 2, "{negated}");
6836
6837 let written = body("_Complex double f(_Complex double z) { return ~z; }\n");
6840 assert_eq!(written, text, "the name and the operator are the same thing");
6841
6842 let text = body(concat!(
6844 "double creal(_Complex double z);\n",
6845 "double f(_Complex double z) { return creal(z); }\n",
6846 ));
6847 assert!(!text.contains("call"), "{text}");
6848 let text = body(concat!(
6849 "_Complex float conjf(_Complex float z);\n",
6850 "_Complex float f(_Complex float z) { return conjf(z); }\n",
6851 ));
6852 assert_eq!(text.matches("fneg").count(), 1, "{text}");
6853 assert!(!text.contains("call"), "{text}");
6854
6855 let taken = concat!(
6858 "static double creal(_Complex double z) { return 7; }\n",
6859 "double f(_Complex double z) { return creal(z); }\n",
6860 );
6861 assert!(ir(taken).contains("call @creal"), "a static definition is the program's own");
6862 let retyped = concat!("int cimag(int z);\n", "int f(int z) { return cimag(z); }\n");
6863 assert!(ir(retyped).contains("call @cimag"), "another type is another function");
6864 let plain = concat!(
6865 "double cimag(_Complex double z);\n",
6866 "double f(_Complex double z) { return cimag(z); }\n",
6867 );
6868 let mut opts = options();
6869 opts.emit = EmitKind::Ir;
6870 opts.builtins = false;
6871 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin");
6872 opts.builtins = true;
6873 opts.no_builtin = vec!["cimag".to_owned()];
6874 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin-cimag");
6875
6876 let text = ir(concat!(
6878 "double a = __builtin_creal(1.5 + 2.5i);\n",
6879 "double b = __builtin_cimag(1.5 + 2.5i);\n",
6880 "_Complex double c = __builtin_conj(1.5 + 2.5i);\n",
6881 ));
6882 assert!(text.contains("global @a : f64 = 0x3ff8000000000000,"), "{text}");
6883 assert!(text.contains("global @b : f64 = 0x4004000000000000,"), "{text}");
6884 assert!(
6885 text.contains("{ f64 0x3ff8000000000000, f64 0xc004000000000000 }"),
6886 "the conjugate of a constant is the constant with the second half negated: {text}"
6887 );
6888 assert!(!text.contains("call"), "{text}");
6889 }
6890
6891 #[test]
6899 fn a_math_library_builtin_of_a_constant_is_the_answer_and_not_a_call() {
6900 let text = ir(concat!(
6901 "double a = __builtin_ceil(1.5);\n",
6902 "double b = __builtin_floor(1.5);\n",
6903 "double c = __builtin_trunc(-1.5);\n",
6904 "double d = __builtin_round(2.5);\n",
6907 "double e = __builtin_ceil(-0.5);\n",
6909 "double f = __builtin_fmax(1.0, 2.0);\n",
6910 "double g = __builtin_fmin(1.0, 2.0);\n",
6911 "float h = __builtin_ceilf(1.25f);\n",
6912 "double ceil(double x);\n",
6915 "double i = ceil(2.25);\n",
6916 ));
6917 assert!(text.contains("global @a : f64 = 0x4000000000000000,"), "{text}");
6918 assert!(text.contains("global @b : f64 = 0x3ff0000000000000,"), "{text}");
6919 assert!(text.contains("global @c : f64 = 0xbff0000000000000,"), "{text}");
6920 assert!(text.contains("global @d : f64 = 0x4008000000000000,"), "{text}");
6921 assert!(text.contains("global @e : f64 = 0x8000000000000000,"), "{text}");
6922 assert!(text.contains("global @f : f64 = 0x4000000000000000,"), "{text}");
6923 assert!(text.contains("global @g : f64 = 0x3ff0000000000000,"), "{text}");
6924 assert!(text.contains("global @h : f32 = 0x40000000,"), "{text}");
6925 assert!(text.contains("global @i : f64 = 0x4008000000000000,"), "{text}");
6926 assert!(!text.contains("call"), "{text}");
6927 }
6928
6929 #[test]
6937 fn a_math_library_builtin_of_anything_else_is_a_call_to_the_library() {
6938 let text = ir(concat!(
6939 "double f(double x) { return __builtin_ceil(x); }\n",
6940 "float g(float x) { return __builtin_floorf(x); }\n",
6941 "double h(double x, double y) { return __builtin_fmax(x, y); }\n",
6942 ));
6943 assert!(text.contains("call @ceil("), "{text}");
6944 assert!(text.contains("call @floorf("), "{text}");
6945 assert!(text.contains("call @fmax("), "{text}");
6946
6947 let text = ir(concat!(
6951 "double f(void) { return __builtin_rint(2.5); }\n",
6952 "double g(void) { return __builtin_nearbyint(2.5); }\n",
6953 ));
6954 assert!(text.contains("call @rint("), "{text}");
6955 assert!(text.contains("call @nearbyint("), "{text}");
6956
6957 let text = ir("double f(void) { return __builtin_fmin(__builtin_nan(\"\"), 1.0); }\n");
6960 assert!(text.contains("call @fmin("), "{text}");
6961
6962 let plain = concat!("double ceil(double x);\n", "double f(void) { return ceil(2.25); }\n");
6965 let mut opts = options();
6966 opts.emit = EmitKind::Ir;
6967 opts.no_builtin = vec!["ceil".to_owned()];
6968 assert!(run(&opts, plain).text().contains("call @ceil("), "-fno-builtin-ceil");
6969 }
6970
6971 #[test]
6978 fn a_constexpr_object_is_a_constant_wherever_one_is_required() {
6979 let text = ir(concat!(
6980 "constexpr int side = 4;\n",
6981 "constexpr int wider = side + 1;\n",
6982 "constexpr double half = 1.5;\n",
6983 "struct point { int x; int y; };\n",
6984 "constexpr struct point origin = { 5, 6 };\n",
6985 "int square[side * side];\n",
6986 "int rectangle[wider];\n",
6987 "int rounded[(int)half * 2];\n",
6988 "int across[origin.y];\n",
6989 "enum named { four = side };\n",
6990 "int e = four;\n",
6991 ));
6992 assert!(text.contains("global @square : bytes 64 ="), "{text}");
6993 assert!(text.contains("global @rectangle : bytes 20 ="), "{text}");
6994 assert!(text.contains("global @rounded : bytes 8 ="), "{text}");
6995 assert!(text.contains("global @across : bytes 24 ="), "{text}");
6996 assert!(text.contains("global @e : i32 = 4,"), "{text}");
6997
6998 let mut opts = options();
7001 opts.emit = EmitKind::Ir;
7002 let konst = "const int n = 1;\nint a[n];\n";
7003 let message = "/main.c:2:5: error: variably modified 'a' at file scope [E0538]";
7004 assert_eq!(run(&opts, konst).messages, [message]);
7005
7006 let subscript = "constexpr int t[3] = { 1, 2, 3 };\nint a[t[1]];\n";
7008 assert_eq!(run(&opts, subscript).messages, [message]);
7009
7010 let address = "constexpr int c = 3;\nint *p = &c;\n";
7012 let warning = "/main.c:2:6: warning: initialization discards 'const' qualifier from \
7013 pointer target type [E0514]";
7014 assert_eq!(run(&opts, address).messages, [warning]);
7015 }
7016
7017 #[test]
7026 fn a_member_whose_size_was_refused_is_not_a_flexible_array_member() {
7027 let mut opts = options();
7028 opts.emit = EmitKind::Ir;
7029
7030 let alone = "int k;\nextern struct D { int a[k]; } ed;\n";
7031 let message = "/main.c:2:23: error: variably modified 'a' at file scope [E0538]";
7032 assert_eq!(run(&opts, alone).messages, [message]);
7033
7034 let first = "int k;\nextern struct E { int a[k]; int b; } ee;\n";
7036 assert_eq!(run(&opts, first).messages, [message]);
7037
7038 let negative = "struct F { int a[-1]; };\n";
7041 let refused = "/main.c:1:18: error: size of array 'a' is negative [E0536]";
7042 assert_eq!(run(&opts, negative).messages, [refused]);
7043
7044 let flexible = "struct G { int a[]; };\n";
7047 let named = "/main.c:1:16: error: flexible array member in a struct with no named \
7048 members [E0554]";
7049 assert_eq!(run(&opts, flexible).messages, [named]);
7050 }
7051
7052 #[test]
7067 fn a_pointer_to_an_array_gains_a_qualifier_the_same_way_a_pointer_to_anything_else_does() {
7068 let mut opts = options();
7069 opts.emit = EmitKind::Ir;
7070 let prefix = "typedef unsigned int B[4];\nstruct H { B category[2]; };\n";
7071
7072 let adding = format!("{prefix}const B *f(struct H *h) {{ return &h->category[0]; }}\n");
7074 assert_eq!(run(&opts, &adding).messages, [] as [String; 0]);
7075
7076 let plain = concat!(
7079 "const unsigned int (*f(unsigned int (*p)[4]))[4] { return p; }\n",
7080 "const unsigned int (*g(unsigned int (*p)[2][3]))[2][3] { return p; }\n",
7081 );
7082 assert_eq!(run(&opts, plain).messages, [] as [String; 0]);
7083
7084 let dropping = format!("{prefix}B *f(const B *p) {{ return p; }}\n");
7087 let warning = "/main.c:3:27: warning: return discards 'const' qualifier from pointer target type \
7088 [E0514]";
7089 assert_eq!(run(&opts, &dropping).messages, [warning]);
7090
7091 let wrong = "const unsigned int (*f(unsigned short (*p)[4]))[4] { return p; }\n";
7094 let error = "/main.c:1:61: error: returning 'unsigned short (*)[4]' from a function with \
7095 incompatible return type 'const unsigned int (*)[4]' [E0512]";
7096 assert_eq!(run(&opts, wrong).messages, [error]);
7097 }
7098
7099 #[test]
7108 fn an_old_style_definition_takes_its_types_from_the_declarations_under_its_list() {
7109 let mut opts = options();
7112 opts.std = Std::C17;
7113 let source = concat!(
7114 "int add(a, b)\n",
7115 "int a;\n",
7116 "int b;\n",
7117 "{ return a + b; }\n",
7118 "int promoted(c)\n",
7119 "char c;\n",
7120 "{ return c; }\n",
7121 "int narrow(char);\n",
7122 "int narrow(c)\n",
7123 "char c;\n",
7124 "{ return c; }\n",
7125 "int first(a)\n",
7126 "int a[4];\n",
7127 "{ return a[0]; }\n",
7128 );
7129 let result = run(&opts, source);
7130 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
7131 let text = result.text();
7132 assert!(text.contains("add : int(int, int) function external defined"), "{text}");
7133 assert!(text.contains("promoted : int(int) function external defined"), "{text}");
7134 assert!(text.contains("c : char object automatic defined"), "{text}");
7136 assert!(text.contains("narrow : int(char) function external defined"), "{text}");
7137 assert!(text.contains("first : int(int *) function external defined"), "{text}");
7139 }
7140
7141 #[test]
7148 fn the_two_halves_of_an_old_style_parameter_list_have_to_agree() {
7149 let mut opts = options();
7150 opts.std = Std::C17;
7151 for (source, message) in [
7152 ("int f(a, a)\nint a;\n{ return a; }\n", "1:10: error: multiple parameters named 'a'"),
7153 (
7154 "int f(a)\nint a;\nint b;\n{ return a; }\n",
7155 "3:5: error: declaration for parameter 'b' but no such parameter",
7156 ),
7157 ("int f(a)\nint a;\nint a;\n{ return a; }\n", "3:5: error: redefinition of parameter"),
7158 ("int f(a)\nint a = 1;\n{ return a; }\n", "2:5: error: parameter 'a' is initialized"),
7159 (
7160 "int f(a)\nstatic int a;\n{ return a; }\n",
7161 "2:12: error: storage class specified for parameter 'a'",
7162 ),
7163 (
7164 "int f(char);\nint f(a)\nshort a;\n{ return a; }\n",
7165 "2:7: error: argument 'a' doesn't match prototype",
7166 ),
7167 ] {
7168 let result = run(&opts, source);
7169 assert!(result.failed(), "expected this to fail:\n{source}");
7170 assert!(result.messages[0].contains(message), "{:?}", result.messages);
7171 }
7172
7173 let implicit = "int f(a, b)\nint a;\n{ return a + b; }\n";
7176 let mut older = options();
7177 older.std = Std::C89;
7178 assert!(!run(&older, implicit).failed(), "{:?}", run(&older, implicit).messages);
7179 let result = run(&opts, implicit);
7180 assert!(
7181 result.messages[0].contains("1:10: error: type of 'b' defaults to 'int'"),
7182 "{:?}",
7183 result.messages
7184 );
7185
7186 let mut newer = options();
7190 newer.std = Std::C23;
7191 let plain = "int f(a)\nint a;\n{ return a; }\n";
7192 let result = run(&newer, plain);
7193 assert!(!result.failed(), "{:?}", result.messages);
7194 assert_eq!(
7195 result.messages,
7196 ["/main.c:1:5: warning: old-style function definition [E0412]"]
7197 );
7198 assert!(run(&opts, plain).messages.is_empty(), "and nothing to say in the dialects before");
7199 }
7200
7201 #[test]
7208 fn the_obsolete_designators_are_taken_and_are_pedantic_warnings() {
7209 let array = "int a[8] = { [3] 7 };\n";
7210 let member = "struct s { int x; } v = { x: 7 };\n";
7211 for source in [array, member] {
7212 let result = run(&options(), source);
7213 assert!(!result.failed(), "{:?}", result.messages);
7214 assert!(result.messages.is_empty(), "nothing to say: {:?}", result.messages);
7215 }
7216
7217 let mut asked = options();
7218 asked.pedantic = true;
7219 assert_eq!(
7220 run(&asked, array).messages,
7221 ["/main.c:1:18: warning: obsolete designator, write `[i] =` instead [E0415]"]
7222 );
7223 assert_eq!(
7224 run(&asked, member).messages,
7225 ["/main.c:1:27: warning: obsolete designator, write `.field =` instead [E0413]"]
7226 );
7227 }
7228
7229 #[test]
7236 fn a_type_is_refused_when_it_passes_the_largest_object_and_not_before() {
7237 let text = ir(concat!(
7238 "struct huge_struct { short buf[(1L << 62) - 256]; int a, b, c, d; };\n",
7239 "struct brim { char buf[9223372036854775807L]; };\n",
7240 "struct bitty { char buf[9223372036854775800L]; int x : 1; };\n",
7241 "unsigned long h = sizeof(struct huge_struct);\n",
7242 "unsigned long b = sizeof(struct brim);\n",
7243 "unsigned long y = sizeof(struct bitty);\n",
7244 ));
7245 assert!(text.contains("global @h : i64 = 9223372036854775312,"), "{text}");
7246 assert!(text.contains("global @b : i64 = 9223372036854775807,"), "{text}");
7247 assert!(text.contains("global @y : i64 = 9223372036854775804,"), "{text}");
7248
7249 let mut opts = options();
7250 opts.emit = EmitKind::Ir;
7251 let over = "struct over { char buf[9223372036854775800L]; char x[8]; };\n";
7252 let message = "/main.c:1:1: error: type 'struct over' is too large [E0560]";
7253 assert_eq!(run(&opts, over).messages, [message]);
7254 let array = "struct wide { short buf[1L << 62]; };\n";
7255 let message = "/main.c:1:25: error: size of array 'buf' exceeds \
7256 maximum object size '9223372036854775807' [E0537]";
7257 assert_eq!(run(&opts, array).messages[0], message);
7258 }
7259
7260 fn compile_bytes(source: &[u8]) -> Compiled {
7265 let mut opts = options();
7266 opts.emit = EmitKind::Ir;
7267 let mut fs = MemoryFileSystem::new();
7268 fs.insert("/main.c", source.to_vec());
7269 compile(&opts, "/main.c", &fs)
7270 }
7271
7272 #[test]
7279 fn a_byte_that_is_not_a_character_is_kept_in_a_literal_and_refused_outside_one() {
7280 let mut source = b"char s[] = \"a".to_vec();
7281 source.push(0xff);
7282 source.extend_from_slice(b"b\";\nchar c = '");
7283 source.push(0xff);
7284 source.extend_from_slice(b"';\n");
7285 let result = compile_bytes(&source);
7286 assert_eq!(result.messages, Vec::<String>::new(), "a raw byte in a literal is that byte");
7287 assert!(result.text().contains(r#"bytes "a\ffb\00""#), "{}", result.text());
7288 assert!(result.text().contains("global @c : i8 = -1,"), "{}", result.text());
7290
7291 let mut stray = b"int a".to_vec();
7292 stray.push(0xff);
7293 stray.extend_from_slice(b" = 1;\n");
7294 let result = compile_bytes(&stray);
7295 assert!(
7296 result.messages.iter().any(|m| m.contains("source is not valid UTF-8 here")),
7297 "{:?}",
7298 result.messages
7299 );
7300 }
7301
7302 #[test]
7303 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
7304 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
7305 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
7306 let expected = "\
7307func @add(i32, i32) -> i32, linkage(external) {
7308block0(%0: i32, %1: i32):
7309 %2 = add.nsw %0, %1
7310 return %2
7311}
7312";
7313 assert!(text.contains(expected), "{text}");
7314 }
7315
7316 #[test]
7317 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
7318 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
7319 assert!(!text.contains("alloca"), "{text}");
7320 assert!(!text.contains("load"), "{text}");
7321 assert!(!text.contains("store"), "{text}");
7322 }
7323
7324 #[test]
7325 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
7326 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
7327 let expected = "\
7328block0:
7329 %0 = alloca, size 4, align 4
7330 %1 = iconst.i32 1
7331 store %1 -> %0, align 4, tbaa !1
7332 %2 = call @g(%0) : (ptr) -> i32
7333 return %2
7334";
7335 assert_eq!(text, expected);
7336 }
7337
7338 #[test]
7339 fn a_loop_carries_what_it_changes_as_block_parameters() {
7340 let text = body(
7343 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
7344 return total;\n}\n",
7345 );
7346 assert!(!text.contains("alloca"), "{text}");
7347 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
7348 assert!(text.contains("jump block1("), "{text}");
7349 }
7350
7351 #[test]
7352 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
7353 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
7354 assert!(text.contains("icmp slt %0, %1"), "{text}");
7355 assert!(!text.contains("zext"), "{text}");
7356 }
7357
7358 #[test]
7359 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
7360 let text = body("int f(int a, int b) { return a && b; }\n");
7361 let expected = "\
7362block0(%0: i32, %1: i32):
7363 %2 = iconst.i32 0
7364 %3 = icmp ne %0, %2
7365 %4 = iconst.i1 0
7366 br_if %3, block1, block2(%4)
7367
7368block1:
7369 %5 = iconst.i32 0
7370 %6 = icmp ne %1, %5
7371 jump block2(%6)
7372
7373block2(%7: i1):
7374 %8 = zext.i32 %7
7375 return %8
7376";
7377 assert_eq!(text, expected);
7378 }
7379
7380 #[test]
7381 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
7382 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
7383 assert!(!text.contains("block3"), "{text}");
7386 assert!(!text.contains("iconst.i32 3"), "{text}");
7387 }
7388
7389 #[test]
7390 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
7391 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
7392 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
7393 assert!(body("int f(void) { }\n").contains("unreachable"));
7394 }
7395
7396 #[test]
7397 fn a_structure_is_copied_rather_than_held_in_a_value() {
7398 let text = body(
7399 "struct point { int x, y; };\n\
7400 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
7401 );
7402 assert!(text.contains("memcpy"), "{text}");
7403 }
7404
7405 #[test]
7406 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
7407 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
7408 assert!(text.contains("memset"), "{text}");
7409 }
7410
7411 #[test]
7412 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
7413 let text = body(
7414 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
7415 default: r = 4; } return r; }\n",
7416 );
7417 let expected = "\
7418block0(%0: i32):
7419 %1 = iconst.i32 0
7420 switch %0, block1, [1 => block2, 2 => block3(%1)]
7421
7422block1:
7423 %2 = iconst.i32 4
7424 jump block4(%2)
7425
7426block2:
7427 %3 = iconst.i32 1
7428 jump block3(%3)
7429
7430block3(%4: i32):
7431 %5 = iconst.i32 2
7432 %6 = add.nsw %4, %5
7433 jump block4(%6)
7434
7435block4(%7: i32):
7436 return %7
7437";
7438 assert_eq!(text, expected);
7439 }
7440
7441 #[test]
7442 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
7443 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
7446 assert!(text.contains("%2 = sub %0, %1"), "{text}");
7447 assert!(text.contains("icmp ule"), "{text}");
7448 assert!(!text.contains("switch"), "{text}");
7449 }
7450
7451 #[test]
7452 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
7453 let text = body(
7454 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
7455 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
7456 );
7457 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
7460 assert!(text.contains("block5:\n jump block7("), "{text}");
7461 assert!(text.contains("block6:\n jump block8("), "{text}");
7462 }
7463
7464 #[test]
7465 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
7466 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
7467 }
7468
7469 #[test]
7470 fn a_label_a_loop_is_only_entered_through_builds_the_loop_around_it() {
7471 let text = body(
7476 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
7477 return n; }\n",
7478 );
7479 assert!(text.contains("switch %0, block1(%1), [1 => block2, 2 => block3(%1)]"), "{text}");
7482 assert!(text.contains("block3(%3: i32):\n %4 = iconst.i32 1"), "{text}");
7483 assert!(text.contains("block4:\n jump block3("), "{text}");
7484 }
7485
7486 #[test]
7487 fn a_goto_into_a_loop_body_enters_it_without_the_test() {
7488 let text = body("int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n");
7491 assert!(text.starts_with("block0(%0: i32, %1: i32):\n jump block1(%1)"), "{text}");
7492 assert!(text.contains("block1(%2: i32):\n %3 = iconst.i32 1"), "{text}");
7493 assert!(text.contains("br_if %6, block2, block3"), "{text}");
7494 }
7495
7496 #[test]
7497 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
7498 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
7499 assert!(!text.contains("alloca"), "{text}");
7503 assert!(text.contains("block2(%4: i32):\n return %4"), "{text}");
7504 assert_eq!(text.matches("jump block2(").count(), 2, "{text}");
7505 }
7506
7507 #[test]
7508 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
7509 let text =
7510 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
7511 assert!(!text.contains("alloca"), "{text}");
7512 assert!(text.contains("block1(%2: i32):"), "{text}");
7513 assert!(text.contains("jump block1(%5)"), "{text}");
7514 }
7515
7516 #[test]
7517 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
7518 assert_eq!(
7521 body("int f(int x) { return x; spare: return 0; }\n"),
7522 "block0(%0: i32):\n return %0\n"
7523 );
7524 }
7525
7526 #[test]
7527 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
7528 let text = body(
7529 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
7530 );
7531 assert_eq!(
7534 text,
7535 "\
7536block0(%0: ptr):
7537 %1 = load.i8 %0, align 1
7538 %2 = iconst.i8 3
7539 %3 = ashr %1, %2
7540 %4 = sext.i32 %3
7541 return %4
7542"
7543 );
7544 }
7545
7546 #[test]
7547 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
7548 let text =
7552 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
7553 assert_eq!(
7554 text,
7555 "\
7556block0(%0: ptr, %1: i32):
7557 %2 = iconst.i32 16777215
7558 %3 = and %1, %2
7559 %4 = trunc.i16 %3
7560 store %4 -> %0, align 2
7561 %5 = iconst.i32 16
7562 %6 = lshr %3, %5
7563 %7 = trunc.i8 %6
7564 %8 = iconst.i64 2
7565 %9 = ptr_add %0, %8
7566 store %7 -> %9, align 1
7567 return
7568"
7569 );
7570 }
7571
7572 #[test]
7573 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
7574 let text =
7575 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
7576 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
7579 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
7580 }
7581
7582 #[test]
7583 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
7584 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
7587 assert_eq!(text.matches("ashr").count(), 0, "{text}");
7588 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
7589 }
7590
7591 #[test]
7592 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
7593 let text = body(
7597 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
7598 );
7599 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
7600 }
7601
7602 #[test]
7603 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
7604 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
7607 assert!(
7608 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
7609 "{text}"
7610 );
7611 }
7612
7613 #[test]
7614 fn an_initialized_flexible_array_member_makes_the_object_larger_than_its_type() {
7615 let text = ir(concat!(
7620 "struct a { int i; int j[]; } x = { 1, { 2, 0, 2, 3 } };\n",
7621 "struct b { char c; char p[]; } y = { 'o', \"wx\" };\n",
7622 "struct c { char c; char p[]; } z = { '9', { 'e', 'b' } };\n",
7623 "char s[2] = \"hi\";\n",
7624 ));
7625 assert!(
7626 text.contains("global @x : bytes 20 = { i32 1, i32 2, i32 0, i32 2, i32 3 }"),
7627 "{text}"
7628 );
7629 assert!(text.contains("global @y : bytes 4 = { i8 111, bytes \"wx\\00\" }"), "{text}");
7630 assert!(text.contains("global @z : bytes 3 = { i8 57, i8 101, i8 98 }"), "{text}");
7631 assert!(text.contains("global @s : bytes 2 = { bytes \"hi\" }"), "{text}");
7634 }
7635
7636 #[test]
7637 fn a_definition_takes_a_parameter_it_left_unnamed() {
7638 let text = ir("int f(int a, int) { return a; }\n");
7642 assert!(text.contains("func @f(i32, i32) -> i32"), "{text}");
7643 assert!(text.contains("block0(%0: i32, %1: i32):"), "{text}");
7644
7645 let text = ir("int g(int, int n) { return n; }\n");
7648 assert!(text.contains("block0(%0: i32, %1: i32):\n return %1\n"), "{text}");
7649 }
7650
7651 #[test]
7652 fn an_assignment_of_a_structure_is_the_object_it_wrote() {
7653 let text = body(concat!(
7658 "struct s { int f; int g; };\n",
7659 "void h(struct s *a, struct s *c, struct s *d, struct s *e)\n",
7660 "{ *d = *e = a[0] = *c; }\n",
7661 ));
7662 assert_eq!(text.matches("memcpy").count(), 3, "{text}");
7663 assert!(text.contains("memcpy %8, %1, size 8, align 4\n"), "{text}");
7664 assert!(text.contains("memcpy %3, %8, size 8, align 4\n"), "{text}");
7665 assert!(text.contains("memcpy %2, %3, size 8, align 4\n"), "{text}");
7666 }
7667
7668 #[test]
7669 fn a_string_literal_stops_at_the_end_of_the_array_it_is_filling() {
7670 let mut opts = options();
7675 opts.emit = EmitKind::Ir;
7676 let result = run(
7677 &opts,
7678 concat!(
7679 "const char a[2][3] = { \"1234\", \"xyz\" };\n",
7680 "static const char b[3][5] = { \"12345\", \"678\", \"9\" };\n",
7681 "union u { struct { char x[4]; char y[4]; }; struct { char z[8]; }; };\n",
7682 "const union u c = { { \"1234\", \"567\" } };\n",
7683 ),
7684 );
7685 let text = result.text();
7686 assert_eq!(
7687 result.messages,
7688 ["/main.c:1:24: warning: initializer-string for array of 'const char' is too long \
7689 (5 chars into 3 available) [E0637]"]
7690 );
7691 assert!(text.contains("global @a : bytes 6 = { bytes \"123\", bytes \"xyz\" }"), "{text}");
7692 assert!(
7693 text.contains(
7694 "global @b : bytes 15 = { bytes \"12345\", bytes \"678\\00\", zero 1, \
7695 bytes \"9\\00\", zero 3 }"
7696 ),
7697 "{text}"
7698 );
7699 assert!(
7702 text.contains("global @c : bytes 8 = { bytes \"1234\", bytes \"567\\00\" }"),
7703 "{text}"
7704 );
7705 }
7706
7707 #[test]
7708 fn a_cast_of_a_record_to_its_own_type_is_the_object_that_was_cast() {
7709 let text = body(concat!(
7713 "struct s { int a, b; };\nstruct v { struct s s; int t; };\n",
7714 "void g(struct v *);\n",
7715 "void f(struct s *p) { struct v w = { (struct s)*p, 5 }; g(&w); }\n",
7716 ));
7717 assert_eq!(text.matches("memcpy").count(), 1, "{text}");
7718 }
7719
7720 #[test]
7721 fn a_compound_literal_read_in_a_static_initializer_lays_its_bytes_into_the_image() {
7722 let text = ir(concat!(
7727 "struct s { int x; };\n",
7728 "struct t { struct s s; int o; } a = { (struct s){ 2 }, 3 };\n",
7729 "int n = (int){ 7 };\n",
7730 "struct u { struct s p; struct s q; } b = { (struct s){ 1 }, (struct s){ } };\n",
7731 ));
7732 assert!(text.contains("global @a : bytes 8 = { i32 2, i32 3 }"), "{text}");
7733 assert!(text.contains("global @n : i32 = 7,"), "{text}");
7734 assert!(text.contains("global @b : bytes 8 = { i32 1, zero 4 }"), "{text}");
7737 }
7738
7739 #[test]
7740 fn the_address_of_a_compound_literal_asks_for_the_object_it_points_at() {
7741 let text = ir("struct s { int x; };\nstruct s *q = &(struct s){ 9 };\n");
7745 assert!(text.contains("global @.Lanon.0 : i32 = 9, align 4, linkage(internal)"), "{text}");
7746 assert!(text.contains("global @q : bytes 8 = { addr.8 @.Lanon.0 }"), "{text}");
7747 }
7748
7749 #[test]
7750 fn an_object_of_no_size_at_all_has_an_image_with_nothing_in_it() {
7751 let text = ir("unsigned char foo[1][0];\n");
7755 assert!(text.contains("global @foo : bytes 0 = {}, align 1"), "{text}");
7756 }
7757
7758 #[test]
7759 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
7760 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
7763 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
7764 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
7765 }
7766
7767 #[test]
7768 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
7769 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
7773 assert!(
7774 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
7775 "{text}"
7776 );
7777 }
7778
7779 #[test]
7780 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
7781 let text = body(
7786 "\
7787struct s { int a, b; };
7788struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
7789",
7790 );
7791 assert!(text.contains("block3(%7: ptr)"), "{text}");
7793 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
7794 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
7795 }
7796
7797 #[test]
7805 fn the_left_side_of_a_conditional_with_no_middle_is_evaluated_once() {
7806 let text = body("int f(int i) { return ++i ?: 10; }\n");
7807 assert!(text.contains("jump block3(%2)"), "the arm is the value that was tested: {text}");
7808 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
7809
7810 let text = body("long f(int i) { return ++i ?: 10L; }\n");
7813 assert!(text.contains("%5 = sext.i64 %2"), "the arm widens what was tested: {text}");
7814 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
7815
7816 let text = body("int g(void);\nint f(void) { return g() ?: 10; }\n");
7818 assert_eq!(text.matches("call @g").count(), 1, "called once: {text}");
7819
7820 let text = body("int f(int i) { return ++i ? ++i : 10; }\n");
7823 assert_eq!(text.matches("add.nsw").count(), 2, "incremented twice: {text}");
7824 }
7825
7826 #[test]
7827 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
7828 let text = ir("\
7832struct pair { int a, b; };
7833struct pair make(int a, int b);
7834struct pair twice(struct pair p) { return make(p.a, p.b); }
7835");
7836 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
7837 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
7838 }
7839
7840 #[test]
7841 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
7842 let text = ir("\
7846struct big { double v[8]; };
7847struct big grow(struct big b);
7848struct big twice(struct big b) { return grow(grow(b)); }
7849");
7850 assert!(
7851 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
7852 "{text}"
7853 );
7854 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
7855 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
7858 }
7859
7860 #[test]
7861 fn a_structure_passed_to_a_variadic_function_says_so_at_the_call() {
7862 let text = ir("\
7867struct big { double v[8]; };
7868struct pair { int a, b; };
7869int p(const char *, ...);
7870int f(struct big b, struct pair q) { return p(\"\", 1, b, q); }
7871");
7872 assert!(
7873 text.contains("call @p(%4, %5, %2 byval(64, align 8), %6) : (ptr, ...) -> i32"),
7874 "{text}"
7875 );
7876 }
7877
7878 #[test]
7879 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
7880 let body = body(
7883 "\
7884struct pair { int a, b; };
7885struct pair make(int a, int b);
7886int second(void) { return make(1, 2).b; }
7887",
7888 );
7889 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
7890 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
7891 }
7892
7893 #[test]
7894 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
7895 let source = "\
7899struct hfa { float x, y, z; };
7900int take(struct hfa h);
7901int give(struct hfa h) { return take(h); }
7902";
7903 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
7904 let mut opts = options();
7905 opts.emit = EmitKind::Ir;
7906 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
7907 let result = run(&opts, source);
7908 assert_eq!(result.messages, Vec::<String>::new());
7909 assert!(result.text().contains("func @take(f32, f32, f32) -> i32"), "{}", result.text());
7910 }
7911
7912 #[test]
7913 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
7914 let source = "\
7917int use(int *);
7918void f(int n) {
7919 {
7920 int a[n];
7921 use(a);
7922 }
7923 use(0);
7924}
7925";
7926 let body = body(source);
7927 assert!(body.contains("mul.nsw"), "{body}");
7928 assert!(body.contains("stacksave"), "{body}");
7929 assert!(body.contains("alloca %"), "{body}");
7930 assert!(body.contains("stackrestore"), "{body}");
7931 }
7932
7933 #[test]
7934 fn a_goto_out_of_the_scope_of_one_gives_its_stack_back_on_the_way() {
7935 let source = "\
7940int use(int *);
7941int f(int n) {
7942 {
7943 int a[n];
7944 if (use(a)) goto out;
7945 use(0);
7946 }
7947out:
7948 return 0;
7949}
7950";
7951 let body = body(source);
7952 assert_eq!(body.matches("stackrestore").count(), 2, "{body}");
7954 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
7955 assert!(after.starts_with(" %4\n jump block"), "{body}");
7956 }
7957
7958 #[test]
7959 fn a_goto_to_a_label_the_array_is_still_alive_at_leaves_the_stack_alone() {
7960 let source = "\
7964int use(int *);
7965int f(int n) {
7966 int a[n];
7967again:
7968 if (use(a)) goto again;
7969 return 0;
7970}
7971";
7972 let body = body(source);
7973 assert!(body.contains("stacksave"), "{body}");
7974 assert!(!body.contains("stackrestore"), "{body}");
7975 }
7976
7977 #[test]
7978 fn a_goto_back_to_a_label_in_front_of_one_gives_it_back_every_time_round() {
7979 let source = "\
7984int use(int *);
7985int f(int n) {
7986again:
7987 {
7988 int a[n];
7989 if (use(a)) goto again;
7990 }
7991 return 0;
7992}
7993";
7994 let body = body(source);
7995 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
7996 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
7997 assert!(after.starts_with(" %4\n jump block1\n"), "{body}");
7998 }
7999
8000 #[test]
8001 fn the_head_of_a_for_loop_is_a_scope_that_closes_where_the_loop_is_left() {
8002 let source = "\
8008int f(void);
8009void t(void) {
8010 int count = 10;
8011 for (; count--;) {
8012 int b[f()];
8013 int i;
8014 for (i = 0; i < f(); i++) {
8015 b[i] = count;
8016 }
8017 }
8018}
8019";
8020 let body = body(source);
8021 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
8025 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
8026 let next = after.split("\n\n").next().expect("the block the restore is in");
8029 assert!(next.contains("jump block1("), "{body}");
8030 }
8031
8032 #[test]
8033 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
8034 let source = "\
8037unsigned long f(int n) {
8038 int a[n];
8039 n = 0;
8040 return sizeof a;
8041}
8042";
8043 let body = body(source);
8044 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
8046 }
8047
8048 #[test]
8049 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
8050 let source = "\
8053int use(int);
8054int f(int x) {
8055 return ({
8056 int t = use(x);
8057 t * t;
8058 });
8059}
8060";
8061 let expected = "\
8062block0(%0: i32):
8063 %1 = call @use(%0) : (i32) -> i32
8064 %2 = mul.nsw %1, %1
8065 return %2
8066";
8067 assert_eq!(body(source), expected);
8068 }
8069
8070 #[test]
8071 fn a_comma_whose_value_is_an_object_names_the_object_the_right_side_named() {
8072 let source = "\
8076struct pair { int a, b; };
8077void bail(void);
8078int f(struct pair p) {
8079 return (bail(), p).b;
8080}
8081";
8082 let expected = "\
8083block0(%0: i64):
8084 %1 = alloca, size 8, align 4
8085 store %0 -> %1, align 4
8086 call @bail() : ()
8087 %2 = iconst.i64 4
8088 %3 = ptr_add %1, %2
8089 %4 = load.i32 %3, align 4, tbaa !1
8090 return %4
8091";
8092 assert_eq!(body(source), expected);
8093 }
8094
8095 #[test]
8096 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
8097 let source = "int f(int x) { return ({ return x; 0; }); }\n";
8101 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
8102 }
8103
8104 #[test]
8105 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
8106 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
8110 let expected = "\
8111block0(%0: ptr):
8112 %1 = va_arg.f64 %0
8113 %2 = va_arg.f64 %0
8114 %3 = fadd %1, %2
8115 return %3
8116";
8117 assert_eq!(body(source), expected);
8118 }
8119
8120 #[test]
8121 fn one_that_reads_a_structure_answers_where_the_object_is() {
8122 let source = "\
8136struct s { int a; long b; };
8137long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.b; }
8138";
8139 let expected = "\
8140block0(%0: ptr):
8141 %1 = alloca, size 16, align 16
8142 %2 = va_object %0, size 16, align 8, in(int 8 at 0, int 8 at 8)
8143 memcpy %1, %2, size 16, align 8
8144 %3 = iconst.i64 8
8145 %4 = ptr_add %1, %3
8146 %5 = load.i64 %4, align 8, tbaa !1
8147 return %5
8148";
8149 assert_eq!(body(source), expected);
8150 }
8151
8152 #[test]
8156 fn the_classification_says_which_registers_the_object_arrived_in() {
8157 let source = "\
8158struct s { double a; double b; };
8159double f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a; }
8160";
8161 assert!(
8162 body(source)
8163 .contains("va_object %0, size 16, align 8, in(float f64 at 0, float f64 at 8)"),
8164 "{}",
8165 body(source)
8166 );
8167
8168 let big = "\
8169struct s { long a[4]; };
8170long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a[0]; }
8171";
8172 assert!(body(big).contains("va_object %0, size 32, align 8\n"), "{}", body(big));
8173 }
8174
8175 #[test]
8176 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
8177 let source = "\
8181int f(int c) {
8182 void *p = c ? &&one : &&two;
8183 goto *p;
8184one:
8185 return 1;
8186two:
8187 return 2;
8188}
8189";
8190 let expected = "\
8191block0(%0: i32):
8192 %1 = iconst.i32 0
8193 %2 = icmp ne %0, %1
8194 br_if %2, block1, block2
8195
8196block1:
8197 %3 = block_addr block3
8198 jump block4(%3)
8199
8200block2:
8201 %4 = block_addr block5
8202 jump block4(%4)
8203
8204block3:
8205 %5 = iconst.i32 1
8206 return %5
8207
8208block4(%6: ptr):
8209 indirect_br %6, block3, block5
8210
8211block5:
8212 %7 = iconst.i32 2
8213 return %7
8214";
8215 assert_eq!(body(source), expected);
8216 }
8217
8218 fn dispatch(labels: usize) -> String {
8221 let mask = labels - 1;
8222 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
8223 for index in 0..labels {
8224 source.push_str(&format!(" &&a{index},"));
8225 }
8226 source.push_str(" };\n\tint w = n, x = n + 1, y = n + 2, z = n + 3;\n");
8227 source.push_str(&format!("\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
8228 for index in 0..labels {
8229 let step = match index % 4 {
8230 0 => "w += x;",
8231 1 => "x += y;",
8232 2 => "y += z;",
8233 _ => "z += w;",
8234 };
8235 source.push_str(&format!("a{index}:\n\t{step}\n"));
8236 source.push_str("\tif (--n <= 0) return w + x + y + z;\n");
8237 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
8238 }
8239 source.push_str("}\n");
8240 source
8241 }
8242
8243 fn in_front_of_the_jump(text: &str) -> usize {
8245 let (before, _) = text.split_once("\tjmp\t*%").expect("a jump through a register");
8246 before.lines().rev().take_while(|line| line.starts_with("\tmov")).count()
8247 }
8248
8249 #[test]
8259 fn a_jump_through_a_register_writes_what_it_carries_and_not_the_whole_table() {
8260 let small = in_front_of_the_jump(&asm(&dispatch(4)));
8261 let large = in_front_of_the_jump(&asm(&dispatch(32)));
8262 assert_eq!(small, large, "eight times the labels and the same values in hand");
8263 assert!(large <= 8, "the values the loop keeps, and not a set of them per label: {large}");
8264 }
8265
8266 fn crowded(labels: usize) -> String {
8269 const VALUES: usize = 24;
8270 let mask = labels - 1;
8271 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
8272 for index in 0..labels {
8273 source.push_str(&format!(" &&a{index},"));
8274 }
8275 source.push_str(" };\n\t");
8276 for value in 0..VALUES {
8277 source.push_str(&format!("int v{value} = n + {value}; "));
8278 }
8279 let sum: Vec<String> = (0..VALUES).map(|value| format!("v{value}")).collect();
8280 source.push_str(&format!("\n\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
8281 for index in 0..labels {
8282 let (to, from) = (index % VALUES, (index + 1) % VALUES);
8283 source.push_str(&format!("a{index}:\n\tv{to} += v{from};\n"));
8284 source.push_str(&format!("\tif (--n <= 0) return {};\n", sum.join(" + ")));
8285 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
8286 }
8287 source.push_str("}\n");
8288 source
8289 }
8290
8291 fn the_frame(text: &str) -> u64 {
8293 text.lines()
8294 .find_map(|line| {
8295 let (size, _) = line.strip_prefix("\tsubq\t$")?.split_once(", %rsp")?;
8296 size.parse().ok()
8297 })
8298 .expect("a function that opens a frame")
8299 }
8300
8301 #[test]
8310 fn a_frame_holds_what_is_wanted_at_once_and_not_a_slot_for_every_label() {
8311 let small = the_frame(&asm(&crowded(16)));
8312 let large = the_frame(&asm(&crowded(64)));
8313 assert_eq!(small, large, "four times the labels and the same values: {small}, {large}");
8314 }
8315
8316 #[test]
8325 fn a_template_that_names_its_own_registers_gets_the_ones_it_named() {
8326 let source = "void save(void *nlr) {
8327 __asm volatile (
8328 \"movq %%rsp, 32(%%rdi) \\n\"
8329 \"movq %%rbx, 40(%%rdi) \\n\"
8330 \"movq %%r12, 48(%%rdi) \\n\"
8331 : : \"D\" (nlr) : \"memory\");
8332}
8333";
8334 let text = asm(source);
8335 assert!(text.contains("\tmovq\t%rsp, 32(%rdi)\n"), "{text}");
8336 assert!(text.contains("\tmovq\t%rbx, 40(%rdi)\n"), "{text}");
8337 assert!(text.contains("\tmovq\t%r12, 48(%rdi)\n"), "{text}");
8338 }
8339
8340 #[test]
8341 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
8342 let source = "void **next(void);
8345void f(void) { goto *next(); }
8346";
8347 let expected = "\
8348block0:
8349 %0 = call @next() : () -> ptr
8350 unreachable
8351";
8352 assert_eq!(body(source), expected);
8353 }
8354
8355 #[test]
8356 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
8357 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
8360 let expected = "\
8361block0:
8362 inline_asm.volatile \"mfence\", \"\", \"memory\"()
8363 return
8364";
8365 assert_eq!(body(source), expected);
8366 }
8367
8368 #[test]
8369 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
8370 let source = "\
8373int f(int x, int y) {
8374 int r;
8375 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
8376 return r + y;
8377}
8378";
8379 let expected = "\
8380block0(%0: i32, %1: i32):
8381 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
8382 %4 = add.nsw %2, %3
8383 return %4
8384";
8385 assert_eq!(body(source), expected);
8386 }
8387
8388 #[test]
8389 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
8390 let source = "\
8395struct pair { int a, b; };
8396int f(int x) {
8397 int slot = x;
8398 struct pair p = { x, x };
8399 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
8400 return slot + p.a;
8401}
8402";
8403 let text = body(source);
8404 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
8405 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
8406 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
8407 }
8408
8409 #[test]
8410 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
8411 let source = "\
8416int f(int x) {
8417 int r = 7;
8418 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
8419 return r;
8420away:
8421 return r;
8422}
8423";
8424 let expected = "\
8425block0(%0: i32):
8426 %1 = iconst.i32 7
8427 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
8428
8429block1:
8430 return %2
8431
8432block2:
8433 return %1
8434";
8435 assert_eq!(body(source), expected);
8436 }
8437
8438 #[test]
8439 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
8440 let mut opts = options();
8444 opts.emit = EmitKind::Ir;
8445 for (source, expected) in [
8446 (
8447 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
8448 "output operand constraint lacks '='",
8449 ),
8450 (
8451 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
8452 "lvalue required in 'asm' statement",
8453 ),
8454 (
8455 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
8456 "read-only variable 'g' used as 'asm' output",
8457 ),
8458 (
8459 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
8460 "input operand constraint contains '='",
8461 ),
8462 (
8463 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
8464 "memory input 0 is not directly addressable",
8465 ),
8466 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
8467 (
8468 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
8469 "duplicate asm operand name 'a'",
8470 ),
8471 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
8472 ] {
8473 let result = run(&opts, source);
8474 assert!(result.failed(), "expected this to be reported:\n{source}");
8475 assert!(
8476 result.messages.iter().any(|m| m.contains(expected)),
8477 "{expected}\n{:?}",
8478 result.messages
8479 );
8480 }
8481 }
8482
8483 #[test]
8488 fn an_asm_at_file_scope_that_is_directives_becomes_the_objects_it_defines() {
8489 let text = ir(concat!(
8490 "__asm__(\n",
8491 " \".section .rodata\\n\"\n",
8492 " \".globl first\\n\"\n",
8493 " \".balign 8\\n\"\n",
8494 " \"first:\\n\"\n",
8495 " \".long 1\\n\"\n",
8496 " \".long 2\\n\"\n",
8497 " \".globl last\\n\"\n",
8498 " \"last:\\n\"\n",
8499 " \".quad last - first\\n\");\n",
8500 "extern const int first[];\n",
8501 "extern const long last;\n",
8502 ));
8503 assert!(text.contains("global @first : bytes 8 = { i32 1, i32 2 }, align 8"), "{text}");
8504 assert!(text.contains("global @last : i64 = 8"), "{text}");
8505 }
8506
8507 #[test]
8511 fn a_name_an_asm_at_file_scope_defined_is_not_undone_by_a_declaration_of_it() {
8512 let text = ir(concat!(
8513 "__asm__(\".data\\n.globl counter\\ncounter:\\n.long 7\\n\");\n",
8514 "extern int counter;\n",
8515 "int read(void) { return counter; }\n",
8516 ));
8517 assert!(text.contains("global @counter : i32 = 7"), "{text}");
8518 }
8519
8520 #[test]
8525 fn bytes_under_no_label_at_file_scope_are_a_global_in_front_of_the_label() {
8526 let text = ir(concat!(
8527 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n662:\\n",
8528 ".pushsection .data.ignore\\n.byte 7\\n.popsection\\n.byte 662b - 661b\\n\");\n",
8529 "extern unsigned char stuff[];\n",
8530 "int read(void) { return stuff[0]; }\n",
8531 ));
8532 let under = text.find("global @.Lasm.0 : i8 = 41").expect(&text);
8533 let named = text.find("global @stuff : i8 = 42").expect(&text);
8534 assert!(under < named, "the bytes under no label come first: {text}");
8535 assert!(text.contains("global @.Lasm.1 : i8 = 7, align 1, linkage(internal), section"));
8536 let after = text.find("global @.Lasm.2 : i8 = 1").expect(&text);
8541 assert!(named < after, "{text}");
8542 }
8543
8544 #[test]
8549 fn a_distance_from_here_at_file_scope_is_a_hole_naming_the_global_it_measures_to() {
8550 let text = ir(concat!(
8551 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n",
8552 ".pushsection .data.ignore\\n.long 661b - .\\n.popsection\\n\");\n",
8553 "extern unsigned char stuff[];\n",
8554 "int read(void) { return stuff[0]; }\n",
8555 ));
8556 assert!(text.contains("global @.Lasm.1 : bytes 4 = { away.4 @stuff }"), "{text}");
8560 }
8561
8562 #[test]
8567 fn a_set_at_file_scope_is_a_second_name_for_what_it_names() {
8568 let text = ir(concat!(
8569 "void base(void) {}\n",
8570 "__asm__(\".weak one\\n.set one, base\");\n",
8571 "__asm__(\".globl two\\n.set two, base\");\n",
8572 "__asm__(\".set three, base\");\n",
8573 "void three(void) {}\n",
8574 ));
8575 assert!(text.contains("alias @one = @base, linkage(weak)"), "{text}");
8576 assert!(text.contains("alias @two = @base"), "{text}");
8577 assert!(!text.contains("alias @three"), "a definition of the name wins: {text}");
8578 assert!(text.contains("func @three"), "{text}");
8579 }
8580
8581 #[test]
8586 fn a_set_of_a_name_this_file_does_not_define_says_so() {
8587 let messages = errors("__asm__(\".set here, elsewhere\");\n");
8588 assert!(
8589 messages
8590 .iter()
8591 .any(|m| m.contains("'here' is aliased to undefined symbol 'elsewhere'")
8592 && m.contains("E0697")),
8593 "{messages:?}"
8594 );
8595 }
8596
8597 #[test]
8600 fn an_incbin_at_file_scope_is_the_bytes_of_the_file_it_names() {
8601 let mut opts = options();
8602 opts.emit = EmitKind::Ir;
8603 let mut fs = MemoryFileSystem::new();
8604 fs.insert(
8605 "/main.c",
8606 b"__asm__(\".data\\n.globl blob\\nblob:\\n.incbin \\\"seed\\\"\\n\");\n".to_vec(),
8607 );
8608 fs.insert("seed", b"hi".to_vec());
8609 let result = compile(&opts, "/main.c", &fs);
8610 assert_eq!(result.messages, Vec::<String>::new());
8611 let text = result.text();
8612 assert!(text.contains("global @blob : bytes 2 = { bytes \"hi\" }"), "{text}");
8613 }
8614
8615 #[test]
8618 fn an_incbin_naming_a_file_that_is_not_there_says_which_file() {
8619 let messages = errors("__asm__(\".data\\nb:\\n.incbin \\\"nowhere\\\"\\n\");\n");
8620 assert!(
8621 messages
8622 .iter()
8623 .any(|m| m.contains("cannot open 'nowhere' for reading") && m.contains("E0702")),
8624 "{messages:?}"
8625 );
8626 }
8627
8628 #[test]
8631 fn an_instruction_in_an_asm_at_file_scope_is_refused_rather_than_ignored() {
8632 for source in [
8633 "__asm__(\".text\\n.globl f\\nf:\\n ret\\n\");\n",
8634 "__asm__(\".data\\n.set alias, 4\\n\");\n",
8635 ] {
8636 let messages = errors(source);
8637 assert!(
8638 messages
8639 .iter()
8640 .any(|m| m.contains("not supported yet")
8641 && m.contains("in an `asm` at file scope")),
8642 "{source}\n{messages:?}"
8643 );
8644 }
8645 }
8646
8647 #[test]
8657 fn a_naked_function_is_its_own_prologue_and_its_own_ending() {
8658 let text = asm(concat!(
8659 "unsigned nlr_push_tail(void *nlr);\n",
8660 "__attribute__((naked)) unsigned nlr_push(void *nlr) {\n",
8661 " __asm volatile(\n",
8662 " \"movq (%rsp), %rax\\n\"\n",
8663 " \"movq %rax, 16(%rdi)\\n\"\n",
8664 " \"movq %rbx, 40(%rdi)\\n\"\n",
8665 " \"jmp nlr_push_tail\\n\");\n",
8666 "}\n",
8667 ));
8668 assert!(text.contains("\tmovq\t(%rsp), %rax\n"), "{text}");
8669 assert!(text.contains("\tjmp\tnlr_push_tail\n"), "{text}");
8670 assert!(text.contains("\tud2\n"), "{text}");
8671 assert!(!text.contains("\tpushq\t"), "nothing is saved in front of it: {text}");
8672 assert!(!text.contains("\tret\n"), "the jump is where it ends: {text}");
8673 }
8674
8675 #[test]
8678 fn what_a_function_without_a_prologue_cannot_be_given_is_refused() {
8679 let mut opts = options();
8680 opts.emit = EmitKind::Asm;
8681 for (source, why) in [
8682 (
8683 "__attribute__((naked)) void f(void) { volatile long a[8]; a[0] = 1; }\n",
8684 "bytes of frame",
8685 ),
8686 (
8687 "__attribute__((naked)) void f(int n) { char a[n]; __asm(\"nop\" ::\"r\"(a)); }\n",
8688 "has no prologue to point a frame pointer at it with",
8689 ),
8690 ("void elsewhere(void); void f(void) { __asm(\"jmp elsewhere\"); }\n", "jumps out of"),
8691 ] {
8692 let result = run(&opts, source);
8693 assert!(result.failed(), "expected this to be refused:\n{source}");
8694 assert!(
8695 result.messages.iter().any(|message| message.contains(why)),
8696 "{:?}",
8697 result.messages
8698 );
8699 }
8700 }
8701
8702 #[test]
8703 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
8704 let mut opts = options();
8705 opts.emit = EmitKind::Ir;
8706 for source in [
8707 "int f(int n) { void *p = &&out; if (n) goto *p; { int a[n]; out: return 1; } }\n",
8708 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
8709 ] {
8710 let result = run(&opts, source);
8711 assert!(result.failed(), "expected this to be reported:\n{source}");
8712 assert!(
8713 result.messages.iter().any(|m| m.contains("not supported yet")),
8714 "{:?}",
8715 result.messages
8716 );
8717 }
8718 }
8719
8720 fn round_trip(source: &str) -> (String, String) {
8722 let printed = ir(source);
8723 let mut opts = options();
8724 opts.emit = EmitKind::Ir;
8725 let mut fs = MemoryFileSystem::new();
8726 fs.insert("/main.ir", printed.clone().into_bytes());
8727 let result = compile_ir(&opts, "/main.ir", &fs);
8728 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
8729 (printed, result.text().to_owned())
8730 }
8731
8732 #[test]
8733 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
8734 let (printed, again) = round_trip(
8738 "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",
8739 );
8740 assert_eq!(printed, again);
8741 }
8742
8743 #[test]
8744 fn ir_that_is_not_ir_says_which_line_stopped_it() {
8745 let mut opts = options();
8746 opts.emit = EmitKind::Ir;
8747 let mut fs = MemoryFileSystem::new();
8748 let text = "\
8749; ModuleID = 'a.c'
8750; format 0
8751target triple = \"x86_64-unknown-linux-gnu\"
8752target datalayout = \"e-p:64:64-i64:64-S128\"
8753
8754func @f(), linkage(external) {
8755block0:
8756 frobnicate
8757}
8758";
8759 fs.insert("/main.ir", text.as_bytes().to_vec());
8760 let result = compile_ir(&opts, "/main.ir", &fs);
8761 assert!(result.failed());
8762 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
8763 }
8764
8765 #[test]
8766 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
8767 let mut opts = options();
8770 opts.emit = EmitKind::Ir;
8771 let mut fs = MemoryFileSystem::new();
8772 let text = "\
8773; ModuleID = 'a.c'
8774; format 0
8775target triple = \"x86_64-unknown-linux-gnu\"
8776target datalayout = \"e-p:64:64-i64:64-S128\"
8777
8778func @f(), linkage(external) {
8779block0:
8780 %0 = iconst.i32 1
8781 return %0
8782}
8783";
8784 fs.insert("/main.ir", text.as_bytes().to_vec());
8785 let result = compile_ir(&opts, "/main.ir", &fs);
8786 assert!(result.failed());
8787 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
8788 }
8789
8790 #[test]
8791 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
8792 let mut fs = MemoryFileSystem::new();
8794 fs.insert("/main.ir", Vec::new());
8795 let result = compile_ir(&options(), "/main.ir", &fs);
8796 assert!(result.failed());
8797 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
8798 }
8799
8800 #[test]
8801 fn the_printed_ir_reads_back_as_the_same_module() {
8802 let text = ir("\
8805struct point { int x, y; };
8806static const char greeting[] = \"hi\";
8807int table[4] = { 1, 2, 3 };
8808int puts(const char *);
8809double half(double x) { return x / 2.0; }
8810int f(int n) {
8811 int total = 0;
8812 for (int i = 0; i < n; i++) {
8813 if (i == 3) continue;
8814 total += table[i];
8815 }
8816 switch (n) {
8817 case 0: total = 1;
8818 case 1: total++; break;
8819 default: total = -total;
8820 }
8821 struct point p = { total, 1 };
8822 int *q = &p.y;
8823 puts(greeting);
8824 return p.x + *q;
8825}
8826int dispatch(int c) {
8827 void *p = c ? &&one : &&two;
8828 goto *p;
8829one:
8830 return 1;
8831two:
8832 return 2;
8833}
8834int assembly(int x, int *p) {
8835 int r;
8836 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
8837 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
8838 return r;
8839away:
8840 return 0;
8841}
8842");
8843 let mut names = Interner::new();
8844 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
8845 assert_eq!(rucc_ir::print(&module, &names), text);
8846 }
8847
8848 #[test]
8849 fn what_save_temps_keeps_is_the_text_that_was_compiled_and_the_assembly_that_was_assembled() {
8850 let mut opts = options();
8854 opts.emit = EmitKind::Object;
8855 opts.save_temps = rucc_session::SaveTemps::Object;
8856 let result = run(&opts, "#define N 2\nint a[N];\n");
8857 assert_eq!(result.messages, Vec::<String>::new());
8858 let text = result.temps.preprocessed.expect("the preprocessed text");
8859 assert!(text.contains("int a[2];"), "{text}");
8860 assert!(text.starts_with("# 1 \"/main.c\""), "{text}");
8861 let asm = result.temps.assembly.expect("the assembly");
8862 assert!(asm.contains("a:"), "{asm}");
8863 assert!(matches!(result.artifact, Artifact::Object { .. }), "{:?}", result.artifact);
8864 }
8865
8866 #[test]
8867 fn nothing_is_kept_unless_the_flag_asked_for_it() {
8868 let mut opts = options();
8871 opts.emit = EmitKind::Object;
8872 assert_eq!(run(&opts, "int a;\n").temps, Temps::default());
8873 }
8874
8875 #[test]
8876 fn a_compilation_that_stops_before_the_back_end_keeps_the_text_and_no_assembly() {
8877 let mut opts = options();
8880 opts.emit = EmitKind::Ir;
8881 opts.save_temps = rucc_session::SaveTemps::Cwd;
8882 let result = run(&opts, "int a;\n");
8883 assert!(result.temps.preprocessed.is_some());
8884 assert_eq!(result.temps.assembly, None);
8885 }
8886
8887 fn span(from: u64, len: u64, held: rucc_debug::Held) -> rucc_debug::Span {
8889 rucc_debug::Span { from, len, held }
8890 }
8891
8892 #[test]
8893 fn two_stretches_that_meet_and_agree_come_out_as_one() {
8894 let one = span(0, 4, rucc_debug::Held::Reg(3));
8895 let two = span(4, 4, rucc_debug::Held::Reg(3));
8896 assert_eq!(settle(vec![two, one]), vec![span(0, 8, rucc_debug::Held::Reg(3))]);
8897 }
8898
8899 #[test]
8900 fn a_stretch_another_starts_inside_and_disagrees_with_ends_where_the_other_starts() {
8901 let one = span(0, 8, rucc_debug::Held::Reg(3));
8902 let two = span(4, 8, rucc_debug::Held::Reg(4));
8903 let settled = settle(vec![one, two]);
8906 assert_eq!(
8907 settled,
8908 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 8, rucc_debug::Held::Reg(4))]
8909 );
8910 }
8911
8912 #[test]
8913 fn a_stretch_cut_by_one_that_ends_first_does_not_come_back_after_it() {
8914 let one = span(0, 16, rucc_debug::Held::Reg(3));
8917 let two = span(4, 4, rucc_debug::Held::Reg(4));
8918 assert_eq!(
8919 settle(vec![one, two]),
8920 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 4, rucc_debug::Held::Reg(4))]
8921 );
8922 }
8923
8924 #[test]
8925 fn a_stretch_inside_another_that_agrees_with_it_cuts_nothing() {
8926 let one = span(0, 16, rucc_debug::Held::Reg(3));
8927 let two = span(4, 4, rucc_debug::Held::Reg(3));
8928 assert_eq!(settle(vec![one, two]), vec![span(0, 16, rucc_debug::Held::Reg(3))]);
8929 }
8930
8931 #[test]
8932 fn a_stretch_two_others_disagree_over_the_whole_of_says_nothing_at_all() {
8933 let one = span(0, 8, rucc_debug::Held::Reg(3));
8934 let two = span(0, 8, rucc_debug::Held::Frame(-16));
8935 assert_eq!(settle(vec![one, two]), Vec::new());
8936 }
8937
8938 #[test]
8939 fn stretches_with_a_gap_between_them_keep_the_gap() {
8940 let one = span(0, 4, rucc_debug::Held::Reg(3));
8941 let two = span(16, 4, rucc_debug::Held::Reg(3));
8942 assert_eq!(settle(vec![one, two]), vec![one, two]);
8943 }
8944
8945 fn extent(len: usize) -> rucc_object::Extent {
8947 rucc_object::Extent {
8948 name: "f".to_owned(),
8949 start: 0,
8950 len,
8951 align: 1,
8952 binding: rucc_object::Binding::Global,
8953 visibility: rucc_object::Visibility::Default,
8954 patch: None,
8955 }
8956 }
8957
8958 fn row(at: usize, lo: u32, hi: u32) -> rucc_asm::Row {
8960 let span = Span::new(lo, hi);
8961 rucc_asm::Row { at, span, inst: None }
8962 }
8963
8964 #[test]
8965 fn a_row_ends_where_the_next_address_begins() {
8966 let rows = [row(0, 0, 1), row(4, 1, 2), row(10, 2, 3)];
8967 assert_eq!(ends(&extent(16), &rows), vec![4, 10, 16]);
8968 }
8969
8970 #[test]
8971 fn rows_sharing_an_address_all_end_where_the_next_address_begins() {
8972 let rows = [row(0, 0, 1), row(4, 1, 2), row(4, 2, 3), row(4, 3, 4)];
8975 assert_eq!(ends(&extent(12), &rows), vec![4, 12, 12, 12]);
8976 }
8977
8978 #[test]
8979 fn the_rows_of_a_scope_that_are_next_to_each_other_come_out_as_one_stretch() {
8980 let rows = [row(0, 0, 4), row(4, 10, 14), row(8, 14, 18), row(12, 40, 44)];
8981 let ends = ends(&extent(16), &rows);
8982 let scope = Span::new(8, 20);
8983 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 8 }]);
8984 }
8985
8986 #[test]
8987 fn a_scope_the_back_end_split_in_two_comes_out_as_two_stretches() {
8988 let rows = [row(0, 10, 14), row(4, 40, 44), row(8, 14, 18)];
8989 let ends = ends(&extent(12), &rows);
8990 let scope = Span::new(8, 20);
8991 let over = spread(scope, &ends, &rows);
8992 assert_eq!(
8993 over,
8994 vec![rucc_debug::Reach { from: 0, len: 4 }, rucc_debug::Reach { from: 8, len: 4 }]
8995 );
8996 }
8997
8998 #[test]
8999 fn a_row_with_no_source_of_its_own_belongs_to_no_scope() {
9000 let rows = [rucc_asm::Row { at: 0, span: Span::DUMMY, inst: None }, row(4, 10, 14)];
9002 let ends = ends(&extent(8), &rows);
9003 let scope = Span::new(0, 20);
9004 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 4 }]);
9005 }
9006
9007 fn scope(parent: Option<usize>, lo: u32, hi: u32) -> crate::shapes::Scope {
9009 let span = Span::new(lo, hi);
9010 crate::shapes::Scope { parent, span }
9011 }
9012
9013 #[test]
9014 fn a_function_gets_the_scopes_its_own_locals_are_in_and_nothing_else() {
9015 let scopes = [scope(None, 0, 10), scope(None, 20, 30), scope(Some(1), 22, 26)];
9017 let rows = [row(0, 22, 24), row(4, 26, 28)];
9018 let (out, at) = nests(&[Some(2)], &scopes, &extent(8), &rows);
9019 assert_eq!(at.get(&1), Some(&0));
9022 assert_eq!(at.get(&2), Some(&1));
9023 assert_eq!(at.get(&0), None);
9024 assert_eq!(out.len(), 2);
9025 assert_eq!(out[0].parent, None);
9026 assert_eq!(out[1].parent, Some(0));
9027 assert_eq!(out[0].over, vec![rucc_debug::Reach { from: 0, len: 8 }]);
9028 assert_eq!(out[1].over, vec![rucc_debug::Reach { from: 0, len: 4 }]);
9029 }
9030
9031 #[test]
9032 fn a_local_written_straight_into_the_body_pulls_no_scope_in() {
9033 let scopes = [scope(None, 20, 30)];
9034 let rows = [row(0, 22, 24)];
9035 let (out, at) = nests(&[None], &scopes, &extent(4), &rows);
9036 assert_eq!(out, Vec::new());
9037 assert!(at.is_empty());
9038 }
9039
9040 #[test]
9041 fn a_scope_whose_code_all_went_away_is_still_one_of_the_functions_scopes() {
9042 let scopes = [scope(None, 20, 30)];
9046 let rows = [row(0, 40, 44)];
9047 let (out, at) = nests(&[Some(0)], &scopes, &extent(4), &rows);
9048 assert_eq!(at.get(&0), Some(&0));
9049 assert_eq!(out.len(), 1);
9050 assert_eq!(out[0].over, Vec::new());
9051 }
9052}