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 align_loops: opts.align_loops.unwrap_or(false),
850 accurate: opts.cycle_accurate_model,
852 verify: opts.verify_each,
855 goal: Goal::for_size(opts.opt_level.is_size()),
860 };
861
862 if opts.safety.instruments() {
871 rucc_opt::heap::annotate(module, names);
881 rucc_safety::handover::arrange(module);
888 rucc_safety::lower(module, names);
889 if let Err(errors) = rucc_ir::verify(module, names) {
890 return Err(errors
891 .iter()
892 .map(|e| internal(&format!("invalid IR after check lowering, {e}")))
893 .collect());
894 }
895 }
896
897 let elsewhere = Elsewhere::of(module, replaceable(target, opts), target.object_format);
906
907 let mut funcs = Vec::new();
908 let mut complaints = Vec::new();
909 for id in module.funcs() {
910 if module[id].is_declaration() {
911 continue;
912 }
913 match pipeline::compile_recording(
914 &mut module[id],
915 names,
916 &machine,
917 &elsewhere,
918 flags,
919 recording,
920 ) {
921 Ok(func) => funcs.push(func),
922 Err(why) => {
923 let name = names.resolve(module[id].name).to_owned();
924 let span = why.inst().map_or(Span::DUMMY, |inst| module[id].span(inst));
927 let said = format!("cannot generate code for '{name}': {why}");
928 complaints.push(unsupported_at(&said, span));
929 }
930 }
931 }
932 if !complaints.is_empty() {
933 return Err(complaints);
934 }
935 let (globals, aliases) = match opts.emit {
941 EmitKind::Asm | EmitKind::Object | EmitKind::Archive | EmitKind::Executable => (
942 rucc_asm::globals(module, names, target.object_format).map_err(refused)?,
943 rucc_asm::aliases(module, names).map_err(refused)?,
944 ),
945 _ => (rucc_asm::Globals::default(), Vec::new()),
946 };
947 let unwind = opts.unwinds();
951 match opts.emit {
952 EmitKind::Asm => {
953 rucc_asm::print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
954 .map(Artifact::Text)
955 .map_err(refused)
956 }
957 EmitKind::Object | EmitKind::Archive | EmitKind::Executable => {
961 if opts.save_temps.wanted() {
962 let listing = rucc_asm::print(
963 &funcs,
964 &globals,
965 &aliases,
966 names,
967 target,
968 unwind,
969 output(opts, target),
970 );
971 *assembly = Some(listing.map_err(refused)?);
972 }
973 if rucc_asm::kept(&funcs, names) {
979 if opts.debug_info {
980 return Err(vec![unsupported(
981 "debug information for a unit with an `asm` template kept as text",
982 )]);
983 }
984 let listing = rucc_asm::print(
985 &funcs,
986 &globals,
987 &aliases,
988 names,
989 target,
990 unwind,
991 output(opts, target),
992 )
993 .map_err(refused)?;
994 let read = rucc_asm::read(&listing).map_err(|trouble| {
995 vec![unsupported(&format!(
996 "an `asm` template kept as text, whose listing the assembler stopped at on \
997 line {}: {}",
998 trouble.line, trouble.why
999 ))]
1000 })?;
1001 let defines = rucc_object::assembled_defines(&read);
1002 let bytes =
1003 rucc_object::assembled(&read, &TargetInfo::new(opts.target)).map_err(wrote)?;
1004 return Ok(Artifact::Object { bytes, defines });
1005 }
1006 let assembled = rucc_asm::assemble(&funcs, names, target, unwind, opts.debug_info)
1007 .map_err(refused)?;
1008 let data = globals.image();
1009 let info = if opts.debug_info {
1013 describe(&assembled, &data, &funcs, origin, opts, target)
1014 .map_err(|why| vec![internal(&why)])?
1015 } else {
1016 rucc_object::Info::default()
1017 };
1018 let text = assembled.text;
1019 let bytes =
1022 rucc_object::write(&text, &data, &aliases, target, output(opts, target), &info)
1023 .map_err(wrote)?;
1024 let defines = rucc_object::defines(&text, &data, &aliases, target).map_err(wrote)?;
1029 Ok(Artifact::Object { bytes, defines })
1030 }
1031 _ => Ok(Artifact::Text(rucc_mir::print(&funcs, names, target.regs))),
1032 }
1033}
1034
1035fn describe(
1054 assembled: &rucc_asm::Assembled,
1055 data: &rucc_object::Data,
1056 machine: &[rucc_mir::Func],
1057 origin: Origin<'_>,
1058 opts: &Options,
1059 target: &TargetInfo,
1060) -> Result<rucc_object::Info, String> {
1061 let rucc_asm::Assembled { text, lines, frames } = assembled;
1062 let rewrite = |path: &str| opts.prefix_map.debug.apply(path).into_owned();
1063 let mut files: Vec<String> = Vec::new();
1068 let mut funcs = Vec::with_capacity(text.funcs.len());
1069 for ((extent, rows), built) in text.funcs.iter().zip(lines).zip(machine) {
1070 let mut out: Vec<rucc_debug::Row> = Vec::with_capacity(rows.len());
1071 for row in rows {
1072 if row.span.is_dummy() {
1073 continue;
1074 }
1075 let Some(at) = origin.map.presumed(row.span.lo) else {
1076 continue;
1077 };
1078 let which = interned(&mut files, rewrite(at.name));
1079 let place = rucc_debug::Row {
1080 at: row.at as u64,
1081 file: which,
1082 line: at.line,
1083 column: at.column,
1084 };
1085 match out.last() {
1095 Some(last) if last.at == place.at => {}
1096 _ => out.push(place),
1097 }
1098 }
1099 if let Some(first) = out.first_mut() {
1106 first.at = 0;
1107 }
1108 let known = origin.meaning.funcs.get(&extent.name);
1114 let decl = known.map(|known| rucc_debug::Place {
1115 file: interned(&mut files, rewrite(&known.file)),
1116 line: known.line,
1117 });
1118 let mut sig = known.and_then(|known| known.sig.clone());
1127 let mut placed: Vec<(u32, i32)> = built.locals.clone();
1128 let mut spots = stretches(extent, rows, built, target);
1129 for &decl in &built.sharing {
1134 if !spots.iter().any(|(at, _)| *at == decl) {
1135 spots.push((decl, Vec::new()));
1136 }
1137 }
1138 if let (Some(sig), Some(known)) = (sig.as_mut(), known) {
1139 for (param, decl) in sig.params.iter_mut().zip(&known.params) {
1140 let Some(decl) = *decl else { continue };
1141 if let Some(which) = placed.iter().position(|&(at, _)| at == decl) {
1142 let at = rucc_debug::Held::Frame(i64::from(placed.remove(which).1));
1143 param.spot = Some(rucc_debug::Spot::Always(at));
1144 continue;
1145 }
1146 let Some(which) = spots.iter().position(|(at, _)| *at == decl) else { continue };
1150 param.spot = Some(rucc_debug::Spot::Over(spots.remove(which).1));
1151 }
1152 }
1153 let mut locals = Vec::with_capacity(placed.len() + spots.len());
1157 let mut wants: Vec<Option<usize>> = Vec::with_capacity(locals.capacity());
1161 for (decl, at) in placed {
1162 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1163 wants.push(named.scope);
1164 locals.push(rucc_debug::Local {
1165 name: named.name.clone(),
1166 ty: named.ty,
1167 decl: Some(rucc_debug::Place {
1168 file: interned(&mut files, rewrite(&named.file)),
1169 line: named.line,
1170 }),
1171 spot: rucc_debug::Spot::Always(rucc_debug::Held::Frame(i64::from(at))),
1172 scope: None,
1173 });
1174 }
1175 spots.sort_by_key(|(decl, _)| *decl);
1179 for (decl, spans) in spots {
1180 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1181 wants.push(named.scope);
1182 locals.push(rucc_debug::Local {
1183 name: named.name.clone(),
1184 ty: named.ty,
1185 decl: Some(rucc_debug::Place {
1186 file: interned(&mut files, rewrite(&named.file)),
1187 line: named.line,
1188 }),
1189 spot: rucc_debug::Spot::Over(spans),
1190 scope: None,
1191 });
1192 }
1193 let (scopes, at) = nests(&wants, &origin.meaning.scopes, extent, rows);
1198 for (local, want) in locals.iter_mut().zip(&wants) {
1199 local.scope = want.and_then(|want| at.get(&want).copied());
1200 }
1201 funcs.push(rucc_debug::Function {
1202 name: extent.name.clone(),
1203 len: extent.len as u64,
1204 rows: out,
1205 decl,
1206 sig,
1207 external: known.is_some_and(|known| known.external),
1208 locals,
1209 scopes,
1210 });
1211 }
1212 let mut globals = Vec::new();
1219 for object in &data.objects {
1220 let Some(held) = origin.meaning.objects.get(&object.name) else { continue };
1221 globals.push(rucc_debug::Global {
1222 name: object.name.clone(),
1223 ty: held.ty,
1224 decl: Some(rucc_debug::Place {
1225 file: interned(&mut files, rewrite(&held.file)),
1226 line: held.line,
1227 }),
1228 external: held.external,
1229 });
1230 }
1231 let unit = rucc_debug::Unit {
1232 name: rewrite(origin.name),
1233 dir: rewrite(opts.working_dir.as_deref().unwrap_or(".")),
1236 producer: format!("rucc {}", crate::VERSION),
1237 files,
1238 types: origin.meaning.types.clone(),
1239 funcs,
1240 globals,
1241 pointer: u8::try_from(target.pointer_width / 8).unwrap_or(8),
1242 frames: opts.unwinds() || frames.is_some(),
1247 };
1248 let mut info = rucc_debug::write(&unit).map_err(|why| why.to_string())?;
1249 info.chunks.extend(frames.clone());
1250 Ok(info)
1251}
1252
1253fn stretches(
1264 extent: &rucc_object::Extent,
1265 rows: &[rucc_asm::Row],
1266 built: &rucc_mir::Func,
1267 target: &TargetInfo,
1268) -> Vec<(u32, Vec<rucc_debug::Span>)> {
1269 let (false, Some(regs)) = (built.kept.is_empty(), target.call_regs) else {
1272 return Vec::new();
1273 };
1274 let ends = ends(extent, rows);
1275 let mut bounds = vec![None; built.inst_count()];
1276 for (which, row) in rows.iter().enumerate() {
1277 let Some(inst) = row.inst else { continue };
1278 bounds[inst.index()] = Some((row.at as u64, ends[which]));
1279 }
1280 let mut spots: Vec<(u32, Vec<rucc_debug::Span>)> = Vec::new();
1281 for kept in &built.kept {
1282 let (Some((from, _)), Some((_, to))) = (bounds[kept.from.index()], bounds[kept.to.index()])
1283 else {
1284 continue;
1285 };
1286 if to <= from {
1287 continue;
1288 }
1289 let held = match kept.at {
1290 rucc_mir::Where::Reg { reg, class } => match regs.dwarf(class, reg) {
1293 Some(number) => rucc_debug::Held::Reg(number),
1294 None => continue,
1295 },
1296 rucc_mir::Where::Frame(at) => rucc_debug::Held::Frame(i64::from(at)),
1297 };
1298 let span = rucc_debug::Span { from, len: to - from, held };
1299 match spots.iter_mut().find(|(decl, _)| *decl == kept.decl) {
1300 Some((_, spans)) => spans.push(span),
1301 None => spots.push((kept.decl, vec![span])),
1302 }
1303 }
1304 for (_, spans) in &mut spots {
1305 *spans = settle(std::mem::take(spans));
1306 }
1307 spots.retain(|(_, spans)| !spans.is_empty());
1308 spots
1309}
1310
1311fn ends(extent: &rucc_object::Extent, rows: &[rucc_asm::Row]) -> Vec<u64> {
1321 let mut out = vec![extent.len as u64; rows.len()];
1322 let mut next = extent.len as u64;
1323 for which in (0..rows.len()).rev() {
1324 let at = rows[which].at as u64;
1325 out[which] = match next > at {
1328 true => next,
1329 false => out.get(which + 1).copied().unwrap_or(extent.len as u64),
1330 };
1331 next = next.min(at);
1332 }
1333 out
1334}
1335
1336fn nests(
1352 wants: &[Option<usize>],
1353 scopes: &[crate::shapes::Scope],
1354 extent: &rucc_object::Extent,
1355 rows: &[rucc_asm::Row],
1356) -> (Vec<rucc_debug::Scope>, HashMap<usize, usize>) {
1357 let mut needed: Vec<usize> = Vec::new();
1358 for &want in wants {
1359 let mut up = want;
1360 while let Some(which) = up {
1361 if needed.contains(&which) {
1362 break;
1363 }
1364 needed.push(which);
1365 up = scopes.get(which).and_then(|scope| scope.parent);
1366 }
1367 }
1368 needed.sort_unstable();
1371 let at: HashMap<usize, usize> =
1372 needed.iter().enumerate().map(|(which, &scope)| (scope, which)).collect();
1373 let ends = ends(extent, rows);
1374 let out = needed
1375 .iter()
1376 .map(|&which| {
1377 let scope = &scopes[which];
1378 rucc_debug::Scope {
1379 parent: scope.parent.and_then(|parent| at.get(&parent).copied()),
1380 over: spread(scope.span, &ends, rows),
1381 }
1382 })
1383 .collect();
1384 (out, at)
1385}
1386
1387fn spread(span: Span, ends: &[u64], rows: &[rucc_asm::Row]) -> Vec<rucc_debug::Reach> {
1395 let mut out: Vec<rucc_debug::Reach> = Vec::new();
1396 for (which, row) in rows.iter().enumerate() {
1397 if row.span.is_dummy() || row.span.lo < span.lo || row.span.hi > span.hi {
1398 continue;
1399 }
1400 let (from, to) = (row.at as u64, ends[which]);
1401 if to <= from {
1402 continue;
1403 }
1404 match out.last_mut() {
1405 Some(last) if last.from + last.len >= from => {
1406 last.len = to.saturating_sub(last.from).max(last.len);
1407 }
1408 _ => out.push(rucc_debug::Reach { from, len: to - from }),
1409 }
1410 }
1411 out
1412}
1413
1414fn settle(mut spans: Vec<rucc_debug::Span>) -> Vec<rucc_debug::Span> {
1431 spans.sort_by_key(|span| (span.from, span.len));
1432 for which in 0..spans.len() {
1433 let (from, end, held) =
1434 (spans[which].from, spans[which].from + spans[which].len, spans[which].held);
1435 let later = spans[which + 1..]
1436 .iter()
1437 .take_while(|later| later.from < end)
1438 .find(|later| later.from > from && later.held != held);
1439 if let Some(later) = later {
1440 spans[which].len = later.from - from;
1441 }
1442 }
1443 let mut edges: Vec<u64> =
1446 spans.iter().flat_map(|span| [span.from, span.from + span.len]).collect();
1447 edges.sort_unstable();
1448 edges.dedup();
1449 let mut out: Vec<rucc_debug::Span> = Vec::new();
1450 let mut first = 0;
1451 for pair in edges.windows(2) {
1452 let (from, to) = (pair[0], pair[1]);
1453 while spans.get(first).is_some_and(|span| span.from + span.len <= from) {
1457 first += 1;
1458 }
1459 let mut held = None;
1460 let mut agreed = true;
1461 for span in &spans[first..] {
1462 if span.from >= to {
1463 break;
1464 }
1465 if span.from > from || span.from + span.len < to {
1466 continue;
1467 }
1468 match held {
1469 None => held = Some(span.held),
1470 Some(seen) => agreed &= seen == span.held,
1471 }
1472 }
1473 let (Some(held), true) = (held, agreed) else { continue };
1474 match out.last_mut() {
1475 Some(last) if last.from + last.len == from && last.held == held => {
1476 last.len += to - from
1477 }
1478 _ => out.push(rucc_debug::Span { from, len: to - from, held }),
1479 }
1480 }
1481 out
1482}
1483
1484fn interned(files: &mut Vec<String>, name: String) -> usize {
1490 match files.iter().position(|have| *have == name) {
1491 Some(which) => which,
1492 None => {
1493 files.push(name);
1494 files.len() - 1
1495 }
1496 }
1497}
1498
1499fn output(opts: &Options, target: &TargetInfo) -> rucc_object::Output {
1511 let mut features = 0;
1512 if target.tuple.arch() == Arch::X86_64 {
1513 if opts.control.branch() {
1514 features |= rucc_object::Property::IBT;
1515 }
1516 if opts.control.ret() {
1517 features |= rucc_object::Property::SHSTK;
1518 }
1519 }
1520 rucc_object::Output {
1521 sections: rucc_object::Sections {
1522 functions: opts.function_sections,
1523 data: opts.data_sections,
1524 },
1525 property: rucc_object::Property { features },
1526 }
1527}
1528
1529fn wrote(why: rucc_object::Error) -> Vec<Diagnostic> {
1535 match why {
1536 rucc_object::Error::Format { .. } => vec![unsupported(&why.to_string())],
1537 rucc_object::Error::Refused { .. } => vec![internal(&why.to_string())],
1538 }
1539}
1540
1541fn refused(why: rucc_asm::Error) -> Vec<Diagnostic> {
1548 match why {
1549 rucc_asm::Error::Thread { .. }
1550 | rucc_asm::Error::IFunc { .. }
1551 | rucc_asm::Error::Frame { .. } => {
1552 vec![unsupported(&why.to_string())]
1553 }
1554 _ => vec![internal(&why.to_string())],
1555 }
1556}
1557
1558fn unsupported(message: &str) -> Diagnostic {
1564 unsupported_at(message, Span::DUMMY)
1565}
1566
1567fn unsupported_at(message: &str, span: Span) -> Diagnostic {
1573 Diagnostic::error(message.to_owned(), span)
1574 .with_code("E0653")
1575 .note("this construct is not lowered yet, see https://github.com/tamnd/rucc/issues", span)
1576}
1577
1578fn invalid(message: &str) -> Diagnostic {
1580 Diagnostic::error(message.to_owned(), Span::DUMMY).with_code("E0661")
1581}
1582
1583fn internal(message: &str) -> Diagnostic {
1585 Diagnostic::error(format!("internal error: {message}"), Span::DUMMY)
1586 .with_code("E0652")
1587 .note("this is a bug in rucc rather than in the program, please report it", Span::DUMMY)
1588}
1589
1590fn failure(message: String) -> Compiled {
1593 Compiled {
1594 artifact: Artifact::Nothing,
1595 messages: vec![format!("rucc: error: {message}")],
1596 errors: 1,
1597 fired: Fired::new(),
1598 pressure: Pressure::new(),
1599 lowerings: Lowerings::new(),
1600 dumps: Vec::new(),
1601 remarks: String::new(),
1602 deps: Vec::new(),
1603 temps: Temps::default(),
1604 }
1605}
1606
1607#[cfg(test)]
1608mod tests {
1609 use rucc_session::{MemoryFileSystem, Std};
1610 use rucc_target::Triple;
1611
1612 use super::*;
1613
1614 fn options() -> Options {
1615 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1616 opts.emit = EmitKind::Tast;
1617 opts
1618 }
1619
1620 fn run(opts: &Options, source: &str) -> Compiled {
1621 let mut fs = MemoryFileSystem::new();
1622 fs.insert("/main.c", source.to_owned().into_bytes());
1623 compile(opts, "/main.c", &fs)
1624 }
1625
1626 fn freestanding() -> Options {
1630 let mut opts = options();
1631 opts.hosted = false;
1632 opts.search.push_system(rucc_session::runtime::DIR);
1633 opts
1634 }
1635
1636 fn shipped(source: &str) -> String {
1638 let result = run(&freestanding(), source);
1639 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1640 result.text().to_owned()
1641 }
1642
1643 fn tast(source: &str) -> String {
1645 let result = run(&options(), source);
1646 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1647 result.text().to_owned()
1648 }
1649
1650 #[test]
1651 fn the_shipped_stdarg_declares_a_list_and_the_four_operators() {
1652 let text = shipped(concat!(
1653 "#include <stdarg.h>\n",
1654 "int sum(int n, ...) {\n",
1655 " va_list ap, copy;\n",
1656 " va_start(ap, n);\n",
1657 " va_copy(copy, ap);\n",
1658 " int total = va_arg(ap, int) + va_arg(copy, int);\n",
1659 " va_end(ap);\n",
1660 " va_end(copy);\n",
1661 " return total;\n",
1662 "}\n",
1663 ));
1664 assert!(text.contains("va-start"), "{text}");
1665 assert!(text.contains("va-copy"), "{text}");
1666 assert!(text.contains("va-arg"), "{text}");
1667 assert!(text.contains("va-end"), "{text}");
1668 }
1669
1670 #[test]
1674 fn stdarg_hands_out_the_type_alone_when_that_is_all_that_was_asked_for() {
1675 let text = shipped(concat!(
1676 "#define __need___va_list\n",
1677 "#include <stdarg.h>\n",
1678 "int vprint(const char *f, __gnuc_va_list ap);\n",
1679 "#ifdef va_start\n",
1680 "#error va_start should not be defined\n",
1681 "#endif\n",
1682 "#ifdef _VA_LIST_DEFINED\n",
1683 "#error va_list should not have been made\n",
1684 "#endif\n",
1685 ));
1686 assert!(text.contains("vprint"), "{text}");
1687 }
1688
1689 #[test]
1692 fn stddef_answers_one_piece_at_a_time_and_the_next_request_still_gets_through() {
1693 let text = shipped(concat!(
1694 "#define __need_size_t\n",
1695 "#include <stddef.h>\n",
1696 "#ifdef offsetof\n",
1697 "#error offsetof should not be defined yet\n",
1698 "#endif\n",
1699 "#define __need_ptrdiff_t\n",
1700 "#include <stddef.h>\n",
1701 "#include <stddef.h>\n",
1702 "size_t a;\n",
1703 "ptrdiff_t b;\n",
1704 "wchar_t c;\n",
1705 "max_align_t d;\n",
1706 "void *e = NULL;\n",
1707 "struct P { int x; long y; };\n",
1708 "size_t f = offsetof(struct P, y);\n",
1709 ));
1710 assert!(text.contains("decl #0 a : unsigned long"), "{text}");
1711 assert!(text.contains("decl #1 b : long"), "{text}");
1712 }
1713
1714 #[test]
1715 fn the_shipped_limits_and_float_are_the_targets_own_answers() {
1716 let text = shipped(concat!(
1717 "#include <limits.h>\n",
1718 "#include <float.h>\n",
1719 "int bits = CHAR_BIT;\n",
1720 "long big = LONG_MAX;\n",
1721 "int low = INT_MIN;\n",
1722 "int radix = FLT_RADIX;\n",
1723 "int digits = DBL_MANT_DIG;\n",
1724 ));
1725 assert!(text.contains("const 8 : int"), "{text}");
1726 assert!(text.contains("const 9223372036854775807 : long"), "{text}");
1727 assert!(text.contains("const 2 : int"), "{text}");
1728 assert!(text.contains("const 53 : int"), "{text}");
1729 }
1730
1731 #[test]
1735 fn the_shipped_stdint_writes_the_whole_set_when_there_is_no_library_to_defer_to() {
1736 let text = shipped(concat!(
1737 "#include <stdint.h>\n",
1738 "int64_t a = INT64_C(1);\n",
1739 "uint_least16_t b;\n",
1740 "intptr_t c;\n",
1741 "uintmax_t d = UINTMAX_MAX;\n",
1742 "int wide = sizeof(int_fast64_t);\n",
1743 ));
1744 assert!(text.contains("decl #0 a : long"), "{text}");
1745 assert!(text.contains("decl #1 b : unsigned short"), "{text}");
1746 assert!(text.contains("decl #2 c : long"), "{text}");
1747 }
1748
1749 #[test]
1760 fn the_shipped_mmintrin_defines_the_mmx_type_and_the_operations_over_it() {
1761 let text = shipped(concat!(
1762 "#include <mmintrin.h>\n",
1763 "__m64 add(__m64 a, __m64 b) { return _mm_add_pi16(a, b); }\n",
1764 "__m64 pack(__m64 a, __m64 b) { return _m_packsswb(a, b); }\n",
1765 "__m64 shift(__m64 a) { return _mm_srai_pi32(a, 3); }\n",
1766 "int low(__m64 a) { return _mm_cvtsi64_si32(a); }\n",
1767 "void done(void) { _mm_empty(); }\n",
1768 ));
1769 assert!(text.contains("add"), "{text}");
1770 assert!(text.contains("pack"), "{text}");
1771 assert!(text.contains("shift"), "{text}");
1772 }
1773
1774 #[test]
1779 fn the_shipped_mm_malloc_asks_for_aligned_memory_and_gives_it_back() {
1780 let text = shipped(concat!(
1781 "#include <mm_malloc.h>\n",
1782 "void *get(void) { return _mm_malloc(64, 16); }\n",
1783 "void put(void *p) { _mm_free(p); }\n",
1784 ));
1785 assert!(text.contains("get"), "{text}");
1786 assert!(text.contains("put"), "{text}");
1787 }
1788
1789 #[test]
1801 fn the_shipped_xmmintrin_defines_the_sse_type_and_the_operations_over_it() {
1802 let text = shipped(concat!(
1803 "#include <xmmintrin.h>\n",
1804 "__m128 add(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1805 "__m128 one(__m128 a, __m128 b) { return _mm_max_ss(a, b); }\n",
1806 "__m128 mask(__m128 a, __m128 b) { return _mm_cmpnle_ps(a, b); }\n",
1807 "__m128 pick(__m128 a, __m128 b) { return _mm_shuffle_ps(a, b, _MM_SHUFFLE(0,1,2,3)); }\n",
1808 "int bits(__m128 a) { return _mm_movemask_ps(a); }\n",
1809 "int near(__m128 a) { return _mm_cvtss_si32(a); }\n",
1810 "__m128 wide(__m64 a) { return _mm_cvtpi16_ps(a); }\n",
1811 "void *room(void) { return _mm_malloc(64, 16); }\n",
1812 "void hint(const float *p) { _mm_prefetch(p, _MM_HINT_T0); _mm_sfence(); }\n",
1813 ));
1814 assert!(text.contains("add"), "{text}");
1815 assert!(text.contains("mask"), "{text}");
1816 assert!(text.contains("pick"), "{text}");
1817 assert!(text.contains("wide"), "{text}");
1818 }
1819
1820 #[test]
1827 fn the_shipped_xmmintrin_leaves_out_the_names_that_need_an_instruction() {
1828 let text = rucc_session::runtime::header("xmmintrin.h").expect("xmmintrin.h is shipped");
1829 for absent in [
1830 "_mm_sqrt_ps",
1831 "_mm_sqrt_ss",
1832 "_mm_rsqrt_ps",
1833 "_mm_rsqrt_ss",
1834 "_mm_getcsr",
1835 "_mm_setcsr",
1836 ] {
1837 let defined = text.contains(&format!("{absent}("));
1838 assert!(!defined, "{absent} is defined and the header says it is not");
1839 assert!(text.contains(absent), "{absent} is absent and unexplained");
1840 }
1841 }
1842
1843 #[test]
1844 fn the_shipped_emmintrin_defines_both_sse2_types_and_the_operations_over_them() {
1845 let text = shipped(concat!(
1846 "#include <emmintrin.h>\n",
1847 "__m128i add(__m128i a, __m128i b) { return _mm_add_epi64(a, b); }\n",
1848 "__m128i wide(__m128i a, __m128i b) { return _mm_mul_epu32(a, b); }\n",
1849 "__m128i pick(__m128i a) { return _mm_shuffle_epi32(a, _MM_SHUFFLE(0,1,2,3)); }\n",
1850 "__m128i up(__m128i a) { return _mm_slli_epi64(a, 13); }\n",
1851 "__m128i down(__m128i a) { return _mm_srli_si128(a, 3); }\n",
1852 "__m128i pack(__m128i a, __m128i b) { return _mm_packus_epi16(a, b); }\n",
1853 "int bits(__m128i a) { return _mm_movemask_epi8(a); }\n",
1854 "__m128d sum(__m128d a, __m128d b) { return _mm_add_sd(a, b); }\n",
1855 "__m128d mask(__m128d a, __m128d b) { return _mm_cmpunord_pd(a, b); }\n",
1856 "__m128i near(__m128d a) { return _mm_cvtpd_epi32(a); }\n",
1857 "__m128d over(__m128 a) { return _mm_cvtps_pd(a); }\n",
1858 "__m128i half(__m64 a) { return _mm_movpi64_epi64(a); }\n",
1859 "__m128i grab(void const *p) { return _mm_loadu_si128(p); }\n",
1860 "void wall(void) { _mm_lfence(); _mm_mfence(); }\n",
1861 ));
1862 assert!(text.contains("wide"), "{text}");
1863 assert!(text.contains("pack"), "{text}");
1864 assert!(text.contains("near"), "{text}");
1865 assert!(text.contains("half"), "{text}");
1866 }
1867
1868 #[test]
1872 fn the_shipped_immintrin_reaches_the_names_the_headers_under_it_define() {
1873 let text = shipped(concat!(
1874 "#include <immintrin.h>\n",
1875 "unsigned long long matching(unsigned char tag, unsigned char const *bucket) {\n",
1876 " __m128i const want = _mm_set1_epi8((char)tag);\n",
1877 " __m128i const chunk = _mm_loadu_si128((__m128i const *)(void const *)bucket);\n",
1878 " __m128i const same = _mm_cmpeq_epi8(chunk, want);\n",
1879 " return (unsigned long long)_mm_movemask_epi8(same);\n",
1880 "}\n",
1881 "__m64 narrow(__m64 a, __m64 b) { return _mm_add_pi32(a, b); }\n",
1882 "__m128 single(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1883 ));
1884 assert!(text.contains("matching"), "{text}");
1885 assert!(text.contains("narrow"), "the MMX header is not reached: {text}");
1886 assert!(text.contains("single"), "the SSE header is not reached: {text}");
1887 }
1888
1889 #[test]
1893 fn the_shipped_x86intrin_reaches_the_fences_windows_headers_ask_it_for() {
1894 let text = shipped(concat!(
1895 "#include <x86intrin.h>\n",
1896 "void barriers(void *p) {\n",
1897 " _mm_lfence();\n",
1898 " _mm_sfence();\n",
1899 " _mm_mfence();\n",
1900 " _mm_pause();\n",
1901 " _mm_clflush(p);\n",
1902 "}\n",
1903 "__m128i wide(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1904 ));
1905 assert!(text.contains("barriers"), "{text}");
1906 assert!(text.contains("wide"), "the SSE2 header is not reached: {text}");
1907 }
1908
1909 #[test]
1913 fn the_umbrella_and_the_header_under_it_can_both_be_included() {
1914 let text = shipped(concat!(
1915 "#include <immintrin.h>\n",
1916 "#include <emmintrin.h>\n",
1917 "#include <immintrin.h>\n",
1918 "#include <x86intrin.h>\n",
1919 "__m128i twice(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1920 ));
1921 assert!(text.contains("twice"), "{text}");
1922 }
1923
1924 #[test]
1928 fn the_shipped_emmintrin_leaves_out_the_two_square_roots() {
1929 let text = rucc_session::runtime::header("emmintrin.h").expect("emmintrin.h is shipped");
1930 for absent in ["_mm_sqrt_pd", "_mm_sqrt_sd"] {
1931 let defined = text.contains(&format!("{absent}("));
1932 assert!(!defined, "{absent} is defined and the header says it is not");
1933 assert!(text.contains(absent), "{absent} is absent and unexplained");
1934 }
1935 }
1936
1937 #[test]
1938 fn the_three_formality_headers_still_have_to_work() {
1939 let text = shipped(concat!(
1940 "#include <stdbool.h>\n",
1941 "#include <stdalign.h>\n",
1942 "#include <iso646.h>\n",
1943 "#include <stdnoreturn.h>\n",
1944 "int t = true and not false;\n",
1945 "_Alignas(16) char buf[16];\n",
1946 "int a = alignof(long);\n",
1947 ));
1948 assert!(text.contains("decl #0 t : int"), "{text}");
1949 assert!(text.contains("const 8 : unsigned long"), "{text}");
1950 }
1951
1952 #[test]
1960 fn every_shipped_header_can_be_included_twice() {
1961 let once: String = rucc_session::runtime::names()
1962 .iter()
1963 .map(|name| format!("#include <{name}>\n"))
1964 .collect();
1965 let twice = once.repeat(2);
1966 assert_eq!(shipped(&format!("{once}int x;\n")), shipped(&format!("{twice}int x;\n")));
1967 }
1968
1969 #[test]
1970 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
1971 let fs = MemoryFileSystem::new();
1972 let result = compile(&options(), "/nope.c", &fs);
1973 assert!(result.failed());
1974 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
1975 assert!(result.text().is_empty());
1976 }
1977
1978 #[test]
1979 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
1980 let text = tast("int x = 1;\n");
1981 let expected = "\
1982decl #0 x : int object external static defined
1983 init
1984 +0
1985 const 1 : int
1986";
1987 assert_eq!(text, expected);
1988 }
1989
1990 #[test]
1991 fn the_macros_are_expanded_before_anything_is_parsed() {
1992 let text = tast("#define N 2\nint a[N];\n");
1996 assert!(text.starts_with("decl #0 a : int[2] object external static tentative"), "{text}");
1997 }
1998
1999 #[test]
2005 fn a_pragma_is_not_a_declaration_and_the_parse_walks_past_the_ones_it_does_not_read() {
2006 let text = tast(concat!(
2007 "#pragma pack(4)\n",
2008 "struct s { int a; };\n",
2009 "#pragma pack()\n",
2010 "int b;\n",
2011 "_Pragma(\"GCC visibility push(default)\") int c;\n",
2012 ));
2013 assert!(text.contains("decl #0 b : int"), "{text}");
2014 assert!(text.contains("decl #1 c : int"), "{text}");
2015 }
2016
2017 #[test]
2025 fn the_layout_attributes_move_the_members_and_the_record_the_way_gcc_lays_them_out() {
2026 tast(concat!(
2027 "struct A { char c; int i; } __attribute__((packed));\n",
2028 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2029 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2030 "struct B { char c; int i; } __attribute__((aligned));\n",
2033 "_Static_assert(sizeof(struct B) == 16 && _Alignof(struct B) == 16, \"B\");\n",
2034 "struct C { char c; int i __attribute__((packed)); };\n",
2035 "_Static_assert(sizeof(struct C) == 5 && _Alignof(struct C) == 1, \"C\");\n",
2036 "_Static_assert(__builtin_offsetof(struct C, i) == 1, \"C.i\");\n",
2037 "struct D { char c; int i; } __attribute__((packed, aligned(4)));\n",
2038 "_Static_assert(sizeof(struct D) == 8 && _Alignof(struct D) == 4, \"D\");\n",
2039 "_Static_assert(__builtin_offsetof(struct D, i) == 1, \"D.i\");\n",
2040 "struct E { char c; _Alignas(8) int i; };\n",
2041 "_Static_assert(sizeof(struct E) == 16 && _Alignof(struct E) == 8, \"E\");\n",
2042 "_Static_assert(__builtin_offsetof(struct E, i) == 8, \"E.i\");\n",
2043 "struct F { char c; int i __attribute__((aligned(8))); };\n",
2044 "_Static_assert(sizeof(struct F) == 16 && _Alignof(struct F) == 8, \"F\");\n",
2045 "struct G { char c; short s; } __attribute__((aligned(2)));\n",
2048 "_Static_assert(sizeof(struct G) == 4 && _Alignof(struct G) == 2, \"G\");\n",
2049 "struct H { char c; int i; } __attribute__((aligned(2)));\n",
2050 "_Static_assert(sizeof(struct H) == 8 && _Alignof(struct H) == 4, \"H\");\n",
2051 "struct I { [[gnu::packed]] char c; int i; };\n",
2054 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2055 "struct J { char c; [[gnu::packed]] int i; };\n",
2056 "_Static_assert(sizeof(struct J) == 5 && _Alignof(struct J) == 1, \"J\");\n",
2057 "struct M { char c; int i : 5; int j : 20; } __attribute__((packed));\n",
2058 "_Static_assert(sizeof(struct M) == 5 && _Alignof(struct M) == 1, \"M\");\n",
2059 "struct N { char c; long long l; } __attribute__((aligned(32)));\n",
2060 "_Static_assert(sizeof(struct N) == 32 && _Alignof(struct N) == 32, \"N\");\n",
2061 "union L { char c; int i; } __attribute__((packed));\n",
2062 "_Static_assert(sizeof(union L) == 4 && _Alignof(union L) == 1, \"L\");\n",
2063 "struct O { char c; int i; } __attribute__((__packed__));\n",
2067 "_Static_assert(sizeof(struct O) == 5 && _Alignof(struct O) == 1, \"O\");\n",
2068 "struct P { char c; int i; } __attribute__((__aligned__(8)));\n",
2069 "_Static_assert(sizeof(struct P) == 8 && _Alignof(struct P) == 8, \"P\");\n",
2070 ));
2071 }
2072
2073 #[test]
2086 fn a_transparent_union_takes_the_member_a_value_fits_and_is_declared_either_way() {
2087 let text = tast(concat!(
2088 "struct one { int x; };\n",
2089 "struct two { long y; };\n",
2090 "typedef union { struct one *a; struct two *b; void *any; }\n",
2091 " __attribute__((__transparent_union__)) arg;\n",
2092 "int takes(arg v);\n",
2093 "int f(struct one *p, struct two *q, char *c) {\n",
2094 " return takes(p) + takes(q) + takes(c) + takes(0);\n",
2095 "}\n",
2096 "int takes(struct one *p);\n",
2098 "int (*as_a_member)(struct one *) = takes;\n",
2099 "int (*as_the_union)(arg) = takes;\n",
2100 ));
2101 assert!(text.contains("compound-literal"), "{text}");
2102 }
2103
2104 #[test]
2110 fn the_attribute_on_the_declarator_of_a_typedef_is_the_one_glibc_writes() {
2111 let text = tast(concat!(
2112 "struct sockaddr { int family; };\n",
2113 "struct sockaddr_in { int family; int addr; };\n",
2114 "typedef union { struct sockaddr *plain; struct sockaddr_in *inet; }\n",
2115 " addr_arg __attribute__((__transparent_union__));\n",
2116 "int bind_to(int fd, addr_arg where);\n",
2117 "int f(struct sockaddr_in *where) { return bind_to(0, where); }\n",
2118 ));
2119 assert!(text.contains("compound-literal"), "{text}");
2120 }
2121
2122 #[test]
2130 fn a_transparent_union_that_cannot_keep_the_promise_is_dropped_with_a_word_about_it() {
2131 let result = run(
2132 &options(),
2133 concat!(
2134 "union wider { int small; double large; } __attribute__((transparent_union));\n",
2135 "struct plain { int x; } __attribute__((transparent_union));\n",
2136 ),
2137 );
2138 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
2139 assert!(!result.failed(), "{:?}", result.messages);
2140 for message in &result.messages {
2141 assert!(message.contains("'transparent_union' attribute ignored"), "{message}");
2142 }
2143 assert!(result.messages[0].contains("first member"), "{:?}", result.messages);
2144 assert!(result.messages[1].contains("only a union"), "{:?}", result.messages);
2145 }
2146
2147 #[test]
2156 fn an_access_to_a_packed_member_says_the_alignment_the_layout_left_it() {
2157 let packed = body(concat!(
2158 "struct P { char c; int v; } __attribute__((packed));\n",
2159 "int f(struct P *p) { return p->v; }\n",
2160 ));
2161 assert!(packed.contains("load.i32 %2, align 1,"), "{packed}");
2162 let plain = body(concat!(
2164 "struct P { char c; int v; };\n",
2165 "int f(struct P *p) { return p->v; }\n",
2166 ));
2167 assert!(plain.contains("load.i32 %2, align 4,"), "{plain}");
2168 }
2169
2170 #[test]
2177 fn what_is_inside_a_packed_member_is_no_more_aligned_than_the_member_is() {
2178 let stepped = body(concat!(
2179 "struct P { char c; int v[4]; } __attribute__((packed));\n",
2180 "int f(struct P *p, int i) { return p->v[i]; }\n",
2181 ));
2182 assert!(stepped.contains(", align 1,"), "{stepped}");
2183 assert!(!stepped.contains(", align 4,"), "{stepped}");
2184 let nested = body(concat!(
2185 "struct Inner { int v; };\n",
2186 "struct P { char c; struct Inner in; } __attribute__((packed));\n",
2187 "int f(struct P *p) { return p->in.v; }\n",
2188 ));
2189 assert!(nested.contains(", align 1,"), "{nested}");
2190 assert!(!nested.contains(", align 4,"), "{nested}");
2191 }
2192
2193 #[test]
2209 fn an_access_through_a_typedef_that_lowered_its_alignment_says_the_one_the_typedef_asked_for() {
2210 let through = body(concat!(
2211 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2212 "unsigned int f(const void *p) { return *(const unalign32 *)p; }\n",
2213 ));
2214 assert!(through.contains("load.i32 %0, align 1,"), "{through}");
2215 let stepped = body(concat!(
2218 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2219 "unsigned int f(unalign32 *p, int i) { return p[i]; }\n",
2220 ));
2221 assert!(stepped.contains(", align 1,"), "{stepped}");
2222 assert!(!stepped.contains(", align 4,"), "{stepped}");
2223 let plain = body(concat!(
2226 "typedef unsigned int word;\n",
2227 "unsigned int f(const void *p) { return *(const word *)p; }\n",
2228 ));
2229 assert!(plain.contains("load.i32 %0, align 4,"), "{plain}");
2230 }
2231
2232 #[test]
2243 fn a_vector_read_through_a_typedef_that_lowered_its_alignment_comes_back_a_piece_at_a_time() {
2244 let prefix = concat!(
2245 "typedef long long v2di __attribute__((__vector_size__(16)));\n",
2246 "typedef long long v2di_u __attribute__((__vector_size__(16), __aligned__(1)));\n",
2247 );
2248 let loaded =
2249 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di_u *)p; }}"));
2250 assert_eq!(loaded.matches("align 1\n").count(), 2, "{loaded}");
2251 assert!(!loaded.contains("align 16"), "{loaded}");
2252 let stored = body(&format!("{prefix}void f(void *p, v2di b) {{ *(v2di_u *)p = b; }}"));
2255 assert!(stored.contains("memcpy %0, %3, size 16, align 1"), "{stored}");
2256 let aligned =
2258 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di *)p; }}"));
2259 assert!(aligned.contains("align 16"), "{aligned}");
2260 }
2261
2262 #[test]
2271 fn the_aligned_attribute_on_a_declaration_raises_what_that_one_object_is_aligned_to() {
2272 tast(concat!(
2273 "int v __attribute__((aligned(64)));\n",
2274 "_Static_assert(__alignof__(v) == 64, \"v\");\n",
2275 "__attribute__((aligned(32))) int w;\n",
2278 "_Static_assert(__alignof__(w) == 32, \"w\");\n",
2279 "[[gnu::aligned(16)]] int x;\n",
2280 "_Static_assert(__alignof__(x) == 16, \"x\");\n",
2281 "int y __attribute__((aligned(2)));\n",
2284 "_Static_assert(__alignof__(y) == 4, \"y\");\n",
2285 "void f(void) { int a __attribute__((aligned(128)));\n",
2287 "_Static_assert(__alignof__(a) == 128, \"a\"); (void)a; }\n",
2288 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2291 "void g(void) __attribute__((aligned(256)));\n",
2294 "void g(void) {}\n",
2295 "_Static_assert(__alignof__(g) == 256, \"g\");\n",
2296 ));
2297 }
2298
2299 #[test]
2303 fn what_a_declaration_asked_to_be_aligned_to_is_what_the_assembler_is_told() {
2304 let text = asm(concat!(
2305 "int v __attribute__((aligned(64)));\n",
2306 "void g(void) __attribute__((aligned(256)));\n",
2307 "void g(void) {}\n",
2308 "void plain(void) {}\n",
2309 ));
2310 assert!(text.contains("\t.p2align\t6\n\t.type\tv, @object\n"), "{text}");
2311 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2312 assert!(text.contains("\t.p2align\t4, 0x90\n\t.globl\tplain\n"), "{text}");
2313 }
2314
2315 #[test]
2321 fn the_alignment_the_command_line_asked_of_every_function_is_a_floor_under_all_of_them() {
2322 let source = concat!(
2323 "void g(void) __attribute__((aligned(256)));\n",
2324 "void g(void) {}\n",
2325 "void small(void) __attribute__((aligned(4)));\n",
2326 "void small(void) {}\n",
2327 "void plain(void) {}\n",
2328 );
2329 let listing = |align: Option<u32>| {
2330 let mut opts = options();
2331 opts.emit = EmitKind::Asm;
2332 opts.align_functions = align;
2333 let result = run(&opts, source);
2334 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
2335 result.text().to_owned()
2336 };
2337
2338 let text = listing(Some(32));
2339 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "the larger one wins: {text}");
2340 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tsmall\n"), "{text}");
2341 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tplain\n"), "{text}");
2342
2343 let text = listing(Some(8));
2346 assert!(text.contains("\t.p2align\t3, 0x90\n\t.globl\tplain\n"), "{text}");
2347 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2348 }
2349
2350 #[test]
2359 fn an_aligned_typedef_says_what_an_object_of_it_is_aligned_to_and_may_lower_it() {
2360 tast(concat!(
2361 "typedef int L __attribute__((aligned(2)));\n",
2362 "_Static_assert(__alignof__(L) == 2, \"L\");\n",
2363 "_Static_assert(_Alignof(L) == 2, \"L alignof\");\n",
2364 "_Static_assert(sizeof(L) == 4, \"L size\");\n",
2366 "struct T { char c; L x; };\n",
2367 "_Static_assert(sizeof(struct T) == 6, \"T\");\n",
2368 "_Static_assert(__builtin_offsetof(struct T, x) == 2, \"T.x\");\n",
2369 "typedef int H __attribute__((aligned(16)));\n",
2371 "_Static_assert(__alignof__(H) == 16, \"H\");\n",
2372 "_Static_assert(sizeof(H) == 4, \"H size\");\n",
2373 "struct U { char c; H x; };\n",
2374 "_Static_assert(sizeof(struct U) == 32, \"U\");\n",
2375 "_Static_assert(__builtin_offsetof(struct U, x) == 16, \"U.x\");\n",
2376 "typedef L M __attribute__((aligned(8)));\n",
2379 "_Static_assert(__alignof__(M) == 8, \"M\");\n",
2380 "typedef L N;\n",
2383 "_Static_assert(__alignof__(N) == 2, \"N\");\n",
2384 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2386 ));
2387 let text = asm(concat!(
2388 "typedef int L __attribute__((aligned(2)));\n",
2389 "typedef int H __attribute__((aligned(16)));\n",
2390 "L low;\n",
2391 "H high;\n",
2392 ));
2393 assert!(text.contains("\t.p2align\t1\n\t.type\tlow, @object\n"), "{text}");
2394 assert!(text.contains("\t.p2align\t4\n\t.type\thigh, @object\n"), "{text}");
2395 }
2396
2397 #[test]
2405 fn the_vector_size_attribute_builds_a_type_of_lanes_and_measures_it_in_bytes() {
2406 tast(concat!(
2407 "typedef int __attribute__((vector_size(16))) v4si;\n",
2408 "_Static_assert(sizeof(v4si) == 16 && _Alignof(v4si) == 16, \"v4si\");\n",
2409 "typedef char __attribute__((vector_size(16))) v16qi;\n",
2410 "_Static_assert(sizeof(v16qi) == 16, \"v16qi\");\n",
2411 "typedef int __attribute__((vector_size(4))) v1si;\n",
2414 "_Static_assert(sizeof(v1si) == 4, \"v1si\");\n",
2415 "typedef float __attribute__((__vector_size__(8))) v2sf;\n",
2417 "_Static_assert(sizeof(v2sf) == 8, \"v2sf\");\n",
2418 "typedef short [[gnu::vector_size(8)]] v4hi;\n",
2419 "_Static_assert(sizeof(v4hi) == 8, \"v4hi\");\n",
2420 "v4si g;\n",
2423 "_Static_assert(sizeof(g[0]) == 4, \"lane\");\n",
2424 "_Static_assert(sizeof(g + g) == 16, \"whole\");\n",
2425 "_Static_assert(sizeof(g + 1) == 16, \"broadcast\");\n",
2428 "_Static_assert(sizeof(v4si[3]) == 48, \"array\");\n",
2430 ));
2431 }
2432
2433 #[test]
2443 fn a_vector_is_written_whole_into_an_array_of_them_and_named_by_a_type_name() {
2444 tast(concat!(
2445 "typedef int __attribute__((vector_size(8))) v2si;\n",
2446 "v2si table[] = { (v2si){ 1, 2 }, (v2si){ 3, 4 } };\n",
2447 "_Static_assert(sizeof(table) == 16, \"two of them and not eight lanes\");\n",
2448 "v2si written = (int __attribute__((vector_size(8)))){ 5, 6 };\n",
2450 "_Static_assert(sizeof((int __attribute__((vector_size(16)))){ 0 }) == 16, \"named\");\n",
2451 "v2si lanes[2] = { 1, 2, 3, 4 };\n",
2454 "_Static_assert(sizeof(lanes) == 16, \"still elided\");\n",
2455 ));
2456 }
2457
2458 #[test]
2466 fn a_lane_is_assignable_and_a_shift_takes_a_count_of_its_own_lane() {
2467 let result = run(
2468 &options(),
2469 concat!(
2470 "typedef int __attribute__((vector_size(16))) v4si;\n",
2471 "typedef unsigned __attribute__((vector_size(16))) v4ui;\n",
2472 "void write(v4si *out, v4ui a, v4si b, int n) {\n",
2473 " v4si v = { 1, 2, 3, 4 };\n",
2474 " v[0] = n;\n",
2475 " v[1] += n;\n",
2476 " v[2]++;\n",
2477 " *&v[3] = n;\n",
2478 " v4ui shifted = a >> b;\n",
2480 " shifted <<= b;\n",
2481 " *out = v + (v4si)shifted + (1 << b);\n",
2484 "}\n",
2485 "void refused(const v4si c) {\n",
2488 " c[0] = 1;\n",
2489 "}\n",
2490 ),
2491 );
2492 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
2493 assert!(result.messages[0].contains("assignment of read-only"), "{:?}", result.messages);
2494 }
2495
2496 #[test]
2508 fn a_record_that_asks_for_the_other_byte_order_swaps_every_scalar_it_holds() {
2509 let read = "int f(struct s *p) { return p->i; }\n";
2510 let big = "struct s { int i; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
2511 assert!(body(&format!("{big}{read}")).contains("bswap"), "{big}");
2512
2513 let armoured =
2514 "struct s { int i; } __attribute__((__scalar_storage_order__(\"big-endian\")));\n";
2515 assert!(body(&format!("{armoured}{read}")).contains("bswap"), "{armoured}");
2516
2517 let front = "struct __attribute__((scalar_storage_order(\"big-endian\"))) s { int i; };\n";
2518 assert!(body(&format!("{front}{read}")).contains("bswap"), "{front}");
2519
2520 let standard = "struct s { int i; } [[gnu::scalar_storage_order(\"big-endian\")]];\n";
2521 assert!(body(&format!("{standard}{read}")).contains("bswap"), "{standard}");
2522
2523 let same =
2524 "struct s { int i; } __attribute__((scalar_storage_order(\"little-endian\")));\n";
2525 assert!(!body(&format!("{same}{read}")).contains("bswap"), "{same}");
2526
2527 let byte = "struct s { char c; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
2529 let source = format!("{byte}int f(struct s *p) {{ return p->c; }}\n");
2530 assert!(!body(&source).contains("bswap"), "{byte}");
2531
2532 tast(concat!(
2533 "struct s { int i; short h; char c; }",
2534 " __attribute__((scalar_storage_order(\"big-endian\")));\n",
2535 "_Static_assert(sizeof(struct s) == 8 && _Alignof(struct s) == 4, \"s\");\n",
2536 "_Static_assert(__builtin_offsetof(struct s, h) == 4, \"s.h\");\n",
2537 "_Static_assert(__builtin_offsetof(struct s, c) == 6, \"s.c\");\n",
2538 ));
2539 }
2540
2541 #[test]
2547 fn a_bit_field_in_one_of_those_records_is_counted_from_the_top_of_its_bytes() {
2548 let members = "short i : 12; char c1 : 1; char c2 : 1; char c3 : 1; char c4 : 1;";
2549 let read = "int f(struct s *p) { return p->i; }\n";
2550 let plain = format!("struct s {{ {members} }};\n{read}");
2551 let reversed = format!(
2552 "struct s {{ {members} }} __attribute__((scalar_storage_order(\"big-endian\")));\n\
2553 {read}"
2554 );
2555 assert!(body(&plain).contains("shl"), "{}", body(&plain));
2556 assert!(!body(&plain).contains("bswap"), "{}", body(&plain));
2557 let built = body(&reversed);
2560 assert!(built.contains("bswap"), "{built}");
2561 assert!(!built.contains("shl"), "{built}");
2562 assert!(built.contains("ashr"), "{built}");
2563 }
2564
2565 #[test]
2572 fn the_address_of_a_scalar_stored_the_other_way_round_is_refused() {
2573 let opts = options();
2574 let record = "struct s { int i; int a[2]; struct in { int n; } w; }\n\
2575 __attribute__((scalar_storage_order(\"big-endian\")));\n";
2576 let taken = format!("{record}int *f(struct s *p) {{ return &p->i; }}\n");
2577 assert_eq!(
2578 run(&opts, &taken).messages,
2579 ["/main.c:3:30: error: cannot take address of scalar with reverse storage order \
2580 [E0712]"]
2581 );
2582 let element = format!("{record}int *f(struct s *p) {{ return &p->a[0]; }}\n");
2583 let messages = run(&opts, &element).messages;
2584 assert!(messages[0].contains("[E0712]"), "{messages:?}");
2585
2586 let whole = format!("{record}int *f(struct s *p) {{ return (int *) &p->w; }}\n");
2587 assert_eq!(run(&opts, &whole).messages, Vec::<String>::new(), "{whole}");
2588 }
2589
2590 #[test]
2594 fn a_storage_order_that_names_neither_end_is_refused_with_the_two_words_that_are_taken() {
2595 let opts = options();
2596 let wrong = "struct s { int i; } __attribute__((scalar_storage_order(\"middle\")));\n";
2597 assert_eq!(
2598 run(&opts, wrong).messages,
2599 ["/main.c:1:36: error: 'scalar_storage_order' argument must be one of \"big-endian\" \
2600 or \"little-endian\" [E0688]"]
2601 );
2602 let bare = "struct s { int i; } __attribute__((scalar_storage_order));\n";
2603 let messages = run(&opts, bare).messages;
2604 assert!(messages[0].contains("[E0688]"), "{messages:?}");
2605 }
2606
2607 #[test]
2617 fn packing_is_what_decides_whether_a_bit_field_may_straddle_its_own_storage() {
2618 assert_eq!(bit_field_byte("struct s { int x : 12; char y : 6; };"), 2);
2620 assert_eq!(
2621 bit_field_byte("struct s { int x : 12; char y : 6; } __attribute__((packed));"),
2622 1
2623 );
2624 assert_eq!(
2625 bit_field_byte("struct s { int x : 12; __attribute__((packed)) char y : 6; };"),
2626 1
2627 );
2628 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { int x : 12; char y : 6; };"), 1);
2629 assert_eq!(bit_field_byte("struct s { char x; int y : 30; };"), 4);
2631 assert_eq!(bit_field_byte("struct s { char x; int y : 30; } __attribute__((packed));"), 1);
2632 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { char x; int y : 30; };"), 1);
2634 assert_eq!(bit_field_byte("#pragma pack(2)\nstruct s { char x; int y : 30; };"), 1);
2635 }
2636
2637 fn bit_field_byte(record: &str) -> u64 {
2639 let source = format!("{record}\nint f(struct s *p) {{ return p->y; }}\n");
2640 let body = body(&source);
2641 let Some((before, _)) = body.split_once("ptr_add") else { return 0 };
2642 let (_, constant) = before.rsplit_once("iconst.i64 ").expect("an offset constant");
2643 constant.lines().next().expect("a line").trim().parse().expect("a byte offset")
2644 }
2645
2646 #[test]
2652 fn an_attribute_among_the_specifiers_is_kept_beside_the_ones_written_in_front() {
2653 tast(concat!(
2654 "struct a { char c; __attribute__((aligned(8))) int i; };\n",
2655 "_Static_assert(sizeof(struct a) == 16 && _Alignof(struct a) == 8, \"a\");\n",
2656 "_Static_assert(__builtin_offsetof(struct a, i) == 8, \"a.i\");\n",
2657 "struct b { char c; __attribute__((packed)) int i; };\n",
2658 "_Static_assert(sizeof(struct b) == 5 && _Alignof(struct b) == 1, \"b\");\n",
2659 "_Static_assert(__builtin_offsetof(struct b, i) == 1, \"b.i\");\n",
2660 "typedef struct { char c; int i; } __attribute__((packed)) c;\n",
2661 "_Static_assert(sizeof(c) == 5 && _Alignof(c) == 1, \"c\");\n",
2662 ));
2663 }
2664
2665 #[test]
2671 fn pragma_pack_caps_every_member_and_is_read_where_the_body_closes() {
2672 tast(concat!(
2673 "#pragma pack(1)\n",
2674 "struct A { char c; int i; };\n",
2675 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2676 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2677 "#pragma pack()\n",
2678 "struct B { char c; int i; };\n",
2679 "_Static_assert(sizeof(struct B) == 8 && _Alignof(struct B) == 4, \"B\");\n",
2680 "#pragma pack(2)\n",
2681 "struct C { char c; int i; double d; };\n",
2682 "_Static_assert(sizeof(struct C) == 14 && _Alignof(struct C) == 2, \"C\");\n",
2683 "_Static_assert(__builtin_offsetof(struct C, d) == 6, \"C.d\");\n",
2684 "struct K { char c; int i __attribute__((aligned(8))); };\n",
2686 "_Static_assert(sizeof(struct K) == 6 && _Alignof(struct K) == 2, \"K\");\n",
2687 "_Static_assert(__builtin_offsetof(struct K, i) == 2, \"K.i\");\n",
2688 "struct J { char c; int i; } __attribute__((aligned(8)));\n",
2690 "_Static_assert(sizeof(struct J) == 8 && _Alignof(struct J) == 8, \"J\");\n",
2691 "#pragma pack()\n",
2692 "#pragma pack(push, 1)\n",
2693 "struct D { char c; short s; };\n",
2694 "_Static_assert(sizeof(struct D) == 3 && _Alignof(struct D) == 1, \"D\");\n",
2695 "#pragma pack(pop)\n",
2696 "struct E { char c; short s; };\n",
2697 "_Static_assert(sizeof(struct E) == 4 && _Alignof(struct E) == 2, \"E\");\n",
2698 "struct H { char c;\n",
2700 "#pragma pack(1)\n",
2701 " int i; };\n",
2702 "_Static_assert(sizeof(struct H) == 5 && _Alignof(struct H) == 1, \"H\");\n",
2703 "#pragma pack(1)\n",
2704 "struct I { char c;\n",
2705 "#pragma pack()\n",
2706 " int i; };\n",
2707 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2708 "#pragma pack()\n",
2709 "#pragma pack(push, 8)\n",
2711 "#pragma pack(push, 1)\n",
2712 "struct P { char c; int i; };\n",
2713 "_Static_assert(sizeof(struct P) == 5 && _Alignof(struct P) == 1, \"P\");\n",
2714 "#pragma pack(pop)\n",
2715 "struct Q { char c; int i; };\n",
2716 "_Static_assert(sizeof(struct Q) == 8 && _Alignof(struct Q) == 4, \"Q\");\n",
2717 "#pragma pack(pop)\n",
2718 "#pragma pack(16)\n",
2720 "struct R { char c; int i; };\n",
2721 "_Static_assert(sizeof(struct R) == 8 && _Alignof(struct R) == 4, \"R\");\n",
2722 "#pragma pack()\n",
2723 "#pragma pack(1)\n",
2724 "struct S { char c; int i : 5; int j : 20; };\n",
2725 "_Static_assert(sizeof(struct S) == 5 && _Alignof(struct S) == 1, \"S\");\n",
2726 "union T { char c; int i; };\n",
2727 "_Static_assert(sizeof(union T) == 4 && _Alignof(union T) == 1, \"T\");\n",
2728 "#pragma pack()\n",
2729 ));
2730 }
2731
2732 #[test]
2736 fn a_pack_line_that_is_not_one_is_reported_in_the_words_gcc_uses() {
2737 let result = run(
2738 &options(),
2739 concat!(
2740 "#pragma pack 4\n",
2741 "#pragma pack(pop)\n",
2742 "#pragma pack(3)\n",
2743 "#pragma pack(1) junk\n",
2744 "#pragma pack(push, 1\n",
2745 "#pragma pack(x)\n",
2746 "#pragma pack(0)\n",
2749 "#pragma pack(push)\n",
2750 "struct s { char c; int i; };\n",
2751 "#pragma pack(pop)\n",
2752 "#pragma pack(pop, foo)\n",
2753 ),
2754 );
2755 let expected = [
2756 "missing `(` after `#pragma pack` - ignored",
2757 "`#pragma pack (pop)` encountered without matching `#pragma pack (push)`",
2758 "alignment must be a small power of two, not 3",
2759 "junk at end of `#pragma pack`",
2760 "malformed `#pragma pack(push[, id][, <n>])` - ignored",
2761 "unknown action `x` for `#pragma pack` - ignored",
2762 "`#pragma pack(pop, foo)` encountered without matching `#pragma pack(push, foo)`",
2763 ];
2764 assert_eq!(result.messages.len(), expected.len(), "{:?}", result.messages);
2765 for (message, want) in result.messages.iter().zip(expected) {
2766 assert!(message.contains(want), "expected {want:?} in {message:?}");
2767 }
2768 }
2769
2770 #[test]
2777 fn a_declaration_behind_an_empty_macro_is_not_eaten_by_the_pragma_above_it() {
2778 let result = run(
2779 &options(),
2780 concat!(
2781 "#pragma pack(push, 1)\n",
2782 "#pragma pack(pop)\n",
2783 "#define API\n",
2784 "API const char version[] = \"3.53.4\";\n",
2785 "const char *get(void) { return version; }\n",
2786 ),
2787 );
2788 assert!(result.messages.is_empty(), "{:?}", result.messages);
2789 }
2790
2791 #[test]
2795 fn the_wide_integer_answers_to_all_three_of_its_names() {
2796 let text = tast("__uint128_t a; __int128_t b; unsigned __int128 c;\n");
2797 assert!(text.contains("decl #0 a : unsigned __int128"), "{text}");
2798 assert!(text.contains("decl #1 b : __int128"), "{text}");
2799 assert!(text.contains("decl #2 c : unsigned __int128"), "{text}");
2800 }
2801
2802 #[test]
2803 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
2804 let text = tast("long f(int a, long b) { return a + b; }\n");
2808 assert!(text.contains("convert arithmetic"), "{text}");
2809 }
2810
2811 #[test]
2812 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
2813 for source in [
2814 "#error stop\n",
2815 "int f(void) { return 1 + ; }\n",
2816 "int f(void) { return undeclared; }\n",
2817 ] {
2818 let result = run(&options(), source);
2819 assert!(result.failed(), "expected this to fail:\n{source}");
2820 assert!(
2821 result.text().is_empty(),
2822 "a file that did not compile wrote a tree:\n{source}"
2823 );
2824 }
2825 }
2826
2827 #[test]
2828 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
2829 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
2833 assert_eq!(result.errors, 1, "{:?}", result.messages);
2834 }
2835
2836 #[test]
2837 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
2838 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
2842 assert_eq!(result.errors, 1, "{:?}", result.messages);
2843 }
2844
2845 #[test]
2846 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
2847 let source = "int f(void) { char c = 300; return c; }\n";
2848 let plain = run(&options(), source);
2849 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
2850 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
2851 assert!(!plain.text().is_empty(), "a warning is not a reason to write nothing");
2852
2853 let mut opts = options();
2854 opts.warnings_are_errors = true;
2855 let strict = run(&opts, source);
2856 assert!(strict.failed());
2857 assert!(strict.text().is_empty(), "and under -Werror it is a reason to write nothing");
2858 for message in &strict.messages {
2859 assert!(!message.contains("warning:"), "{message}");
2860 }
2861 }
2862
2863 #[test]
2864 fn w_drops_the_warning_before_werror_can_promote_it() {
2865 let source = "int f(void) { char c = 300; return c; }\n";
2866 let mut opts = options();
2867 opts.warnings = false;
2868 let quiet = run(&opts, source);
2869 assert_eq!(quiet.messages, Vec::<String>::new());
2870 assert_eq!(quiet.errors, 0);
2871 assert!(!quiet.text().is_empty(), "and the file still compiles");
2872
2873 opts.warnings_are_errors = true;
2876 let both = run(&opts, source);
2877 assert_eq!(both.messages, Vec::<String>::new());
2878 assert!(!both.failed(), "-w -Werror is not an error about a warning nobody saw");
2879 }
2880
2881 #[test]
2882 fn the_dialect_reaches_the_keywords_and_the_checking() {
2883 let source = "typeof(1) x;\n";
2886 let mut opts = options();
2887 opts.std = Std::C23;
2888 opts.gnu_extensions = false;
2889 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
2890
2891 opts.std = Std::C17;
2892 assert!(run(&opts, source).failed());
2893 }
2894
2895 #[test]
2896 fn asking_for_a_kind_that_is_not_written_yet_runs_the_front_end_and_writes_nothing() {
2897 let mut opts = options();
2898 opts.emit = EmitKind::Object;
2899 let result = run(&opts, "int x = 1;\n");
2900 assert!(!result.failed(), "{:?}", result.messages);
2901 assert!(result.text().is_empty());
2902 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
2905 }
2906
2907 fn mir(source: &str) -> String {
2909 let mut opts = options();
2910 opts.emit = EmitKind::MirFinal;
2911 let result = run(&opts, source);
2912 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2913 result.text().to_owned()
2914 }
2915
2916 #[test]
2922 fn a_function_goes_from_c_to_instructions_with_real_registers_in_them() {
2923 let text = mir("int add(int a, int b) { return a + b; }\n");
2924 assert!(text.starts_with("mfunc @add {"), "{text}");
2925 assert!(text.contains("x64.add_rr_32"), "{text}");
2926 assert!(text.contains("x64.ret"), "{text}");
2927 assert!(!text.contains('%'), "{text}");
2930 }
2931
2932 #[test]
2934 fn a_function_with_no_body_produces_no_machine_function() {
2935 let text = mir("int g(int);\nint f(int a) { return g(a); }\n");
2936 assert_eq!(text.matches("mfunc @").count(), 1, "{text}");
2937 assert!(text.contains("mfunc @f {"), "{text}");
2938 assert!(text.contains("x64.call"), "{text}");
2939 }
2940
2941 #[test]
2943 fn every_definition_in_the_file_is_generated_and_they_keep_their_order() {
2944 let text = mir("int a(int x) { return x; }\nint b(int x) { return x; }\n");
2945 let first = text.find("mfunc @a").expect("the first function");
2946 let second = text.find("mfunc @b").expect("the second function");
2947 assert!(first < second, "{text}");
2948 }
2949
2950 #[test]
2952 fn the_target_decides_which_convention_the_generated_code_follows() {
2953 let mut opts = options();
2954 opts.emit = EmitKind::MirFinal;
2955 let linux = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2956 assert!(linux.contains("$rdi"), "{linux}");
2957
2958 opts.target = "x86_64-pc-windows-msvc".parse::<Triple>().unwrap();
2959 let windows = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2960 assert!(windows.contains("$rcx"), "{windows}");
2961 assert!(!windows.contains("$rdi"), "{windows}");
2962 }
2963
2964 #[test]
2972 fn a_tagged_member_with_no_name_is_a_member_on_windows_and_nothing_on_linux() {
2973 let source = concat!(
2974 "struct S { union U { int i; void *p; }; unsigned long tymed; };\n",
2975 "int size(void) { return sizeof(struct S); }\n",
2976 "int f(struct S *s) { s->i = 1; return s->i; }\n",
2977 );
2978
2979 let mut opts = options();
2980 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
2981 let windows = run(&opts, source);
2982 assert!(windows.messages.is_empty(), "{:?}", windows.messages);
2983
2984 let linux = run(&options(), source);
2985 assert_eq!(linux.messages.len(), 3, "{:?}", linux.messages);
2986 assert!(linux.messages[0].contains("does not declare anything"), "{:?}", linux.messages);
2987
2988 let mut opts = options();
2991 opts.ms_extensions = Some(true);
2992 let asked = run(&opts, source);
2993 assert!(asked.messages.is_empty(), "{:?}", asked.messages);
2994 }
2995
2996 #[test]
2998 fn a_target_this_has_no_back_end_for_is_reported_rather_than_generated() {
2999 let mut opts = options();
3000 opts.emit = EmitKind::MirFinal;
3001 opts.target = "riscv64-unknown-linux-gnu".parse::<Triple>().unwrap();
3002 let result = run(&opts, "int f(int a) { return a; }\n");
3003 assert!(result.failed());
3004 assert!(result.messages[0].contains("no back end for riscv64"), "{:?}", result.messages);
3005 assert!(result.text().is_empty());
3006 }
3007
3008 #[test]
3011 fn an_aarch64_target_is_written_as_aarch64_assembly() {
3012 let mut opts = options();
3013 opts.emit = EmitKind::Asm;
3014 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3015 let source = "int g(int);\nint f(int a, int b) { return g(a) + b; }\n";
3016 let result = run(&opts, source);
3017 assert!(!result.failed(), "{:?}", result.messages);
3018 let text = result.text();
3019 for line in ["stp x29, x30, [sp, #-16]!", "mov x29, sp", "bl g", "ldp x29, x30, [sp], #16"]
3020 {
3021 assert!(text.contains(line), "{line} is not in\n{text}");
3022 }
3023 assert!(!text.contains('%'), "{text}");
3024 }
3025
3026 #[test]
3029 fn an_aarch64_result_in_memory_is_reached_through_x8() {
3030 let mut opts = options();
3031 opts.emit = EmitKind::Asm;
3032 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3033 let source = "struct big { long a, b, c; };\nstruct big make(long v);\n\
3034 long f(long v) { return make(v).c; }\n\
3035 struct big g(long v) { struct big b = { v, v, v }; return b; }\n";
3036 let result = run(&opts, source);
3037 assert!(!result.failed(), "{:?}", result.messages);
3038 let text = result.text();
3039 assert!(text.contains("x8"), "{text}");
3040 assert!(text.contains("bl make"), "{text}");
3041 }
3042
3043 #[test]
3046 fn a_variadic_definition_is_refused_on_aarch64_until_its_list_is_written() {
3047 let mut opts = options();
3048 opts.emit = EmitKind::Asm;
3049 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3050 let result = run(&opts, "int f(int n, ...) { return n; }\n");
3051 assert!(result.failed());
3052 assert!(result.messages[0].contains("does not name"), "{:?}", result.messages);
3053 }
3054
3055 #[test]
3067 fn a_construct_the_back_end_cannot_reach_yet_is_reported_against_its_function() {
3068 let mut opts = options();
3069 opts.emit = EmitKind::MirFinal;
3070 let source = "void a(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
3071 s; s.x = 1; v[0] = s.x; }\n\
3072 void b(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
3073 s; s.x = 1; v[0] = s.x; }\n";
3074 let result = run(&opts, source);
3075 assert!(result.failed());
3076 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
3077 assert!(result.messages[0].contains("cannot generate code for 'a'"), "{:?}", result);
3078 assert!(result.messages[0].contains("wants more alignment"), "{:?}", result);
3079 assert!(result.messages[1].contains("cannot generate code for 'b'"), "{:?}", result);
3080 assert!(result.text().is_empty());
3081 }
3082
3083 #[test]
3091 fn a_variable_length_array_walks_its_pages_where_every_page_of_the_frame_is_to_be_touched() {
3092 let mut opts = options();
3093 opts.emit = EmitKind::MirFinal;
3094 let source = "void a(int n) { int v[n]; v[0] = 1; }\n";
3095 let plain = run(&opts, source);
3096 assert!(!plain.failed(), "{:?}", plain.messages);
3097 assert!(!plain.text().contains("cmp_set_a_64"), "{}", plain.text());
3098
3099 opts.stack_clash = true;
3100 let result = run(&opts, source);
3101 assert!(!result.failed(), "{:?}", result.messages);
3102 assert!(result.text().contains("cmp_set_a_64"), "{}", result.text());
3103 assert!(result.text().contains("or_mi_8"), "{}", result.text());
3104 }
3105
3106 #[test]
3116 fn a_function_that_keeps_a_frame_pointer_on_windows_reaches_an_object_file() {
3117 let mut opts = options();
3118 opts.emit = EmitKind::Object;
3119 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3120 let source = concat!(
3121 "void use(void *p);\n",
3122 "void array(int n) { int v[n]; v[0] = 1; use(v); }\n",
3123 "void taken(unsigned long n) { use(__builtin_alloca(n)); }\n",
3124 );
3125 let result = run(&opts, source);
3126 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3127 let bytes = match result.artifact {
3128 Artifact::Object { bytes, .. } => bytes,
3129 other => panic!("expected an object, got {other:?}"),
3130 };
3131 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3132
3133 let mut opts = options();
3136 opts.emit = EmitKind::Object;
3137 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3138 }
3139
3140 #[test]
3151 fn the_address_of_a_function_this_file_only_declares_reaches_a_windows_object() {
3152 let source = concat!(
3153 "void other(void *p);\n",
3154 "void takes(void (*f)(void *));\n",
3155 "void (*held)(void *);\n",
3156 "void pass(void) { takes(other); }\n",
3157 "void keep(void) { held = other; }\n",
3158 "void call(void) { other(0); }\n",
3159 );
3160 let mut opts = options();
3161 opts.emit = EmitKind::Object;
3162 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3163 let result = run(&opts, source);
3164 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3165 let bytes = match result.artifact {
3166 Artifact::Object { bytes, .. } => bytes,
3167 other => panic!("expected an object, got {other:?}"),
3168 };
3169 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3170
3171 let mut opts = options();
3174 opts.emit = EmitKind::Object;
3175 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3176 }
3177
3178 #[test]
3192 fn an_opcode_with_no_name_in_the_rule_language_is_named_by_its_own_spelling() {
3193 let mut opts = options();
3194 opts.emit = EmitKind::MirFinal;
3195 let source =
3196 "long double f(int a) {\n __int128 wide = a;\n return (long double) wide;\n}\n";
3197 let result = run(&opts, source);
3198 assert!(result.failed());
3199 assert!(
3200 result.messages[0].contains("no rule lowers a `sext` producing a `i128`"),
3201 "{result:?}"
3202 );
3203 assert!(result.messages[0].contains(":2:"), "the line the widening is on: {result:?}");
3204 assert!(!result.messages[0].contains("this instruction"), "{result:?}");
3205 }
3206
3207 #[test]
3209 fn the_note_on_unfinished_work_points_at_the_issues_rather_than_at_the_plan() {
3210 let mut opts = options();
3211 opts.emit = EmitKind::MirFinal;
3212 let source = "long double f(int a) { __int128 wide = a; return (long double) wide; }\n";
3213 let result = run(&opts, source);
3214 assert!(result.failed());
3215 let note = result.messages.iter().find(|line| line.contains("note:")).expect("a note");
3216 assert!(note.contains("https://github.com/tamnd/rucc/issues"), "{note}");
3217 assert!(!note.contains("spec/17-milestones.md"), "{note}");
3218 }
3219
3220 #[test]
3222 fn the_frame_flags_on_the_command_line_reach_the_generated_frame() {
3223 let source = "int f(int a) { return a; }\n";
3224 assert!(!mir(source).contains("$rbp"), "a leaf needs no frame pointer by default");
3225
3226 let mut opts = options();
3227 opts.emit = EmitKind::MirFinal;
3228 opts.frame_pointer = true;
3229 let kept = run(&opts, source).text().to_owned();
3230 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
3231 }
3232
3233 fn asm(source: &str) -> String {
3235 let mut opts = options();
3236 opts.emit = EmitKind::Asm;
3237 let result = run(&opts, source);
3238 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3239 result.text().to_owned()
3240 }
3241
3242 #[test]
3249 fn a_function_goes_from_c_to_assembly_an_assembler_would_take() {
3250 let text = asm("int add(int a, int b) { return a + b; }\n");
3251 assert!(text.contains("\t.globl\tadd\n"), "{text}");
3252 assert!(text.contains("\t.type\tadd, @function\n"), "{text}");
3253 assert!(text.contains("\nadd:\n"), "{text}");
3254 assert!(text.contains("\taddl\t"), "{text}");
3255 assert!(text.contains("\tret\n"), "{text}");
3256 assert!(text.contains("\t.size\tadd, .-add\n"), "{text}");
3257 assert!(text.contains(".note.GNU-stack"), "{text}");
3260 }
3261
3262 #[test]
3268 fn a_call_through_a_function_pointer_goes_through_the_register_it_is_in() {
3269 let text = asm("int g(int);\nint f(int (*p)(int), int a) { return p(a) + g(a); }\n");
3270 assert!(text.contains("\tcall\t*%"), "{text}");
3271 assert!(text.contains("\tcall\tg\n"), "{text}");
3272 assert!(text.contains("%rdi"), "{text}");
3276 }
3277
3278 #[test]
3282 fn the_address_of_a_global_is_read_from_the_instruction_pointer() {
3283 let text = asm("extern int counter;\nint f(void) { return counter; }\n");
3284 assert!(text.contains("\tmovl\tcounter(%rip), %eax\n"), "{text}");
3285 }
3286
3287 #[test]
3296 fn a_branch_on_a_comparison_jumps_on_the_opposite_of_what_it_compared() {
3297 let arms = "return 1; return 2;";
3298 let signed = [("==", "jne"), ("!=", "je"), ("<", "jge"), ("<=", "jg"), (">", "jle")];
3299 for (operator, jump) in signed.into_iter().chain([(">=", "jl")]) {
3300 let text = asm(&format!("int f(int a, int b) {{ if (a {operator} b) {arms} }}\n"));
3301 assert!(
3302 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
3303 "{operator}: {text}"
3304 );
3305 assert!(!text.contains("\tset"), "{operator}: {text}");
3306 assert!(!text.contains("\ttest"), "{operator}: {text}");
3307 }
3308 let unsigned = [("<", "jae"), ("<=", "ja"), (">", "jbe"), (">=", "jb")];
3309 for (operator, jump) in unsigned {
3310 let source =
3311 format!("int f(unsigned a, unsigned b) {{ if (a {operator} b) {arms} }}\n");
3312 let text = asm(&source);
3313 assert!(
3314 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
3315 "{operator}: {text}"
3316 );
3317 }
3318
3319 let text = asm("int f(int a) { if (a < 7) return 1; return 2; }\n");
3322 assert!(text.contains("\tcmpl\t$7, %edi\n\tjge\t"), "{text}");
3323 }
3324
3325 #[test]
3331 fn a_comparison_whose_answer_the_program_wanted_still_writes_a_byte() {
3332 let text = asm("int f(int a, int b) { return a < b; }\n");
3333 assert!(text.contains("\tsetl\t"), "{text}");
3334 }
3335
3336 fn optimized(source: &str) -> String {
3338 let mut opts = options();
3339 opts.emit = EmitKind::Asm;
3340 opts.opt_level = rucc_session::OptLevel::O2;
3341 let result = run(&opts, source);
3342 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3343 result.text().to_owned()
3344 }
3345
3346 #[test]
3356 fn a_switch_whose_arms_are_a_function_of_the_label_is_a_range_check_and_arithmetic() {
3357 let arms: String =
3358 (0..16).map(|k| format!("case {k}: return {};", k + 1)).collect::<Vec<_>>().join(" ");
3359 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3360 assert!(text.contains("\tcmpl\t$15, %edi\n\tja\t"), "{text}");
3361 assert!(text.contains("\taddl\t$1, %edi"), "{text}");
3362 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
3363 }
3364
3365 #[test]
3373 fn a_dense_switch_whose_arms_are_not_a_line_is_a_load_from_a_table() {
3374 let arms: String = (0..16)
3375 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
3376 .collect::<Vec<_>>()
3377 .join(" ");
3378 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3379 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
3380 assert!(!text.contains("\tjmp\t*"), "{text}");
3381 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
3382 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
3383 let section = text[..text.find("CSWTCH.0:").unwrap_or(0)].rfind("\t.section\t.rodata");
3384 assert!(section.is_some(), "{text}");
3385 assert_eq!(table.matches("\t.long\t").count(), 16, "{text}");
3386 assert!(table.contains("\t.long\t100\n"), "{text}");
3387 }
3388
3389 #[test]
3395 fn a_table_at_os_has_cells_as_narrow_as_its_answers() {
3396 let arms: String = (0..16)
3397 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
3398 .collect::<Vec<_>>()
3399 .join(" ");
3400 let mut opts = options();
3401 opts.emit = EmitKind::Asm;
3402 opts.opt_level = rucc_session::OptLevel::Os;
3403 let result = run(&opts, &format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3404 assert_eq!(result.messages, Vec::<String>::new());
3405 let text = result.text();
3406 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
3407 assert_eq!(table.matches("\t.byte\t").count(), 16, "{text}");
3408 assert!(text.contains("\tmovsbl\t"), "{text}");
3409 }
3410
3411 #[test]
3418 fn a_table_that_covers_its_operand_has_no_range_check() {
3419 let text = optimized(
3420 "int f(unsigned x) { switch (x & 3) { case 0: return 5; case 1: return 9; \
3421 case 2: return 2; case 3: return 7; } return -1; }\n",
3422 );
3423 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
3424 assert!(!text.contains("\tcmp"), "{text}");
3425 assert!(!text.contains("$-1"), "{text}");
3426 }
3427
3428 #[test]
3433 fn a_store_to_a_local_the_loop_just_read_is_a_conditional_move() {
3434 let text = optimized(
3435 "int f(const int *v, int n, int k) { int best[8] = {0}; \
3436 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; \
3437 return best[k & 7]; }\n",
3438 );
3439 assert!(text.contains("\tcmovgl"), "{text}");
3440 assert!(!text.contains("\tset"), "{text}");
3441 assert!(!text.contains("\ttestb"), "{text}");
3442 }
3443
3444 #[test]
3446 fn a_store_to_a_global_the_loop_just_read_keeps_its_branch() {
3447 let text = optimized(
3448 "int best[8]; void f(const int *v, int n) { \
3449 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; }\n",
3450 );
3451 assert!(!text.contains("\tcmov"), "{text}");
3452 }
3453
3454 #[test]
3462 fn a_conversion_from_a_constant_double_is_the_number_it_converts_to() {
3463 let text = optimized("int f(void) { double d = 2.75; return (int) d; }\n");
3464 assert!(text.contains("movl\t$2, %eax"), "{text}");
3465 assert!(!text.contains("cvttsd2si"), "{text}");
3466 }
3467
3468 #[test]
3476 fn a_slot_of_a_read_only_table_is_the_value_the_table_holds() {
3477 let text =
3478 optimized("static const int t[4] = {10, 20, 30, 40};\nint f(void) { return t[2]; }\n");
3479 assert!(text.contains("movl\t$30, %eax"), "{text}");
3480 assert!(!text.contains("t(%rip)"), "{text}");
3481 }
3482
3483 #[test]
3486 fn a_byte_of_a_read_only_string_is_the_byte_the_string_spells() {
3487 let text = optimized("static const char s[] = \"abc\";\nint f(void) { return s[1]; }\n");
3488 assert!(text.contains("movl\t$98, %eax"), "{text}");
3489 }
3490
3491 #[test]
3495 fn a_table_that_is_not_read_only_keeps_its_load() {
3496 let text = optimized(
3497 "static int t[4] = {10, 20, 30, 40};\nvoid g(int x) { t[2] = x; }\nint f(void) { return t[2]; }\n",
3498 );
3499 assert!(!text.contains("movl\t$30, %eax"), "{text}");
3500 }
3501
3502 #[test]
3509 fn a_call_guarded_by_a_condition_a_read_only_object_settles_is_not_emitted() {
3510 let text = optimized(
3511 "void link_error(void);\nconst double one = 1.0;\nint main(void) { if ((int) one != 1) link_error(); return 0; }\n",
3512 );
3513 assert!(!text.contains("call\tlink_error"), "{text}");
3514 }
3515
3516 #[test]
3518 fn a_cast_between_a_pointer_and_an_integer_leaves_the_value_where_it_is() {
3519 let text = asm("long f(void *p) { return (long)p; }\n");
3520 for line in text.lines().filter(|line| line.starts_with('\t') && !line.contains('.')) {
3525 let mnemonic = line.split_whitespace().next().unwrap_or("");
3526 assert!(matches!(mnemonic, "movq" | "ret"), "{line} in\n{text}");
3527 }
3528 }
3529
3530 #[test]
3534 fn an_argument_past_the_last_register_is_read_out_of_the_caller_s_stack() {
3535 let six = "long a, long b, long c, long d, long e, long f";
3536 let text = asm(&format!("long f({six}, long g, long h) {{ return g + h; }}\n"));
3537
3538 assert!(text.contains("\tmovq\t8(%rsp), "), "{text}");
3545 assert!(text.contains("\taddq\t16(%rsp), "), "{text}");
3546
3547 let narrow = asm(&format!("int f({six}, int g) {{ return g; }}\n"));
3551 assert!(narrow.contains("\tmovl\t8(%rsp), "), "{narrow}");
3552 let eight =
3553 "double a, double b, double c, double d, double e, double f, double g, double h";
3554 let float = asm(&format!("double f({eight}, double i) {{ return i; }}\n"));
3555 assert!(float.contains("\tmovsd\t8(%rsp), "), "{float}");
3556 }
3557
3558 #[test]
3561 fn a_call_writes_the_arguments_with_no_register_left_at_the_stack_pointer() {
3562 let six = "1, 2, 3, 4, 5, 6";
3563 let decl = "long g(long, long, long, long, long, long, long, long);\n";
3564 let text = asm(&format!("{decl}long f(void) {{ return g({six}, 7, 8); }}\n"));
3565
3566 assert!(text.contains("\tmovq\t%"), "{text}");
3567 assert!(text.contains(", (%rsp)\n"), "{text}");
3568 assert!(text.contains(", 8(%rsp)\n"), "{text}");
3569 assert!(text.contains("\tsubq\t$"), "{text}");
3571
3572 let narrow = "int g(int, int, int, int, int, int, int);\n";
3574 let text = asm(&format!("{narrow}int f(void) {{ return g({six}, 7); }}\n"));
3575 assert!(text.contains("\tmovl\t%"), "{text}");
3576 assert!(text.contains(", (%rsp)\n"), "{text}");
3577 }
3578
3579 #[test]
3582 fn a_variadic_call_counts_registers_and_not_arguments() {
3583 let nine = "1., 2., 3., 4., 5., 6., 7., 8., 9.";
3584 let decl = "int g(int, ...);\n";
3585 let text = asm(&format!("{decl}int f(void) {{ return g(0, {nine}); }}\n"));
3586
3587 assert!(text.contains("\tmovl\t$8, "), "eight registers, not nine: {text}");
3588 assert!(text.contains("\tmovsd\t%"), "{text}");
3589 assert!(text.contains(", (%rsp)\n"), "{text}");
3590 }
3591
3592 #[test]
3597 fn a_variadic_function_writes_the_argument_registers_it_was_handed_into_its_frame() {
3598 let body =
3599 "__builtin_va_list ap; __builtin_va_start(ap, n); __builtin_va_end(ap); return n;";
3600 let text = asm(&format!("int f(int n, ...) {{ {body} }}\n"));
3601
3602 let stores = |mnemonic: &str| text.matches(&format!("\t{mnemonic}\t%")).count();
3605 assert!(text.contains(", 8(%r"), "the second slot, not the first: {text}");
3606 assert!(!text.contains(", 0(%r"), "{text}");
3607 assert_eq!(stores("movaps"), 8, "every vector register: {text}");
3610 assert_eq!(stores("movsd"), 0, "and the whole of each one: {text}");
3611
3612 assert!(text.contains("\tsubq\t$"), "{text}");
3614 }
3615
3616 #[test]
3619 fn va_start_writes_the_four_fields_the_psabi_describes() {
3620 let start = "__builtin_va_list ap; __builtin_va_start(ap, d);";
3621 let params = "int a, int b, int c, double d";
3622 let text = asm(&format!("int f({params}, ...) {{ {start} return a; }}\n"));
3623
3624 assert!(text.contains(" movl $24, "), "{text}");
3628 assert!(text.contains(" movl $64, "), "{text}");
3629 assert!(text.contains(", 8(%r"), "{text}");
3633 assert!(text.contains(", 16(%r"), "{text}");
3634 let frame: u32 = text
3635 .lines()
3636 .find_map(|line| line.trim().strip_prefix("subq $")?.split(',').next()?.parse().ok())
3637 .expect("a variadic function takes a frame for the save area");
3638 let above = |line: &str| {
3639 let at: u32 = line.trim().strip_prefix("leaq ")?.split('(').next()?.parse().ok()?;
3640 Some(at > frame)
3641 };
3642 assert!(text.lines().filter_map(above).any(|it| it), "{frame}: {text}");
3643 }
3644
3645 #[test]
3648 fn va_arg_branches_on_whether_the_argument_is_still_in_the_save_area() {
3649 let read = "__builtin_va_list ap; __builtin_va_start(ap, n);";
3650 let ints = format!("int f(int n, ...) {{ {read} return __builtin_va_arg(ap, int); }}\n");
3651 let text = asm(&ints);
3652
3653 assert!(text.contains("$40, "), "{text}");
3656 assert!(text.contains(" cmpl "), "{text}");
3657 assert!(text.contains(" ja "), "{text}");
3661
3662 let arg = "__builtin_va_arg(ap, double)";
3663 let text = asm(&format!("double f(int n, ...) {{ {read} return {arg}; }}\n"));
3664 assert!(text.contains("$160, "), "the last vector slot: {text}");
3665 }
3666
3667 #[test]
3670 fn a_structure_assignment_is_a_move_for_each_word_of_it() {
3671 let decl = "struct pair { long a, b; };\n";
3672 let body = "struct pair p = *q; return p.a + p.b;";
3673 let text = asm(&format!("{decl}long f(struct pair *q) {{ {body} }}\n"));
3674
3675 assert!(!text.contains("memcpy"), "nothing calls the library: {text}");
3676 assert!(!text.contains("\tcall"), "{text}");
3677 assert!(text.matches("\tmovq\t").count() >= 4, "two words each way: {text}");
3679 }
3680
3681 #[test]
3684 fn how_wide_a_word_of_a_copy_is_follows_the_alignment() {
3685 let decl = "struct bytes { char a[8]; };\n";
3686 let body = "struct bytes p = *q; return p.a[0];";
3687 let text = asm(&format!("{decl}int f(struct bytes *q) {{ {body} }}\n"));
3688
3689 assert!(text.matches("\tmovb\t").count() >= 16, "a byte at a time: {text}");
3691 }
3692
3693 #[test]
3696 fn the_part_of_an_initialiser_that_names_nothing_is_stored_as_zero() {
3697 let decl = "struct wide { long a, b, c; };\n";
3698 let text = asm(&format!("{decl}long f(void) {{ struct wide w = {{ 7 }}; return w.c; }}\n"));
3699
3700 assert!(!text.contains("memset"), "nothing calls the library: {text}");
3701 assert!(text.contains("\tmovq\t$0, ") || text.contains("\txorl\t"), "the zero: {text}");
3706 }
3707
3708 #[test]
3711 fn a_copy_too_large_to_unroll_calls_the_runtime() {
3712 let decl = "struct huge { char a[4096]; };\n";
3713 let mut opts = options();
3714 opts.emit = EmitKind::Asm;
3715 let source = format!("{decl}void f(struct huge *p, struct huge *q) {{ *p = *q; }}\n");
3716 let result = run(&opts, &source);
3717 assert!(!result.failed(), "{:?}", result.messages);
3718 let text = result.text();
3719 assert!(text.contains("call") && text.contains("memcpy"), "{text}");
3720 assert!(text.contains("4096"), "the size travels: {text}");
3723 }
3724
3725 #[test]
3732 fn a_structure_too_large_to_unroll_is_copied_into_the_argument_area_by_the_runtime() {
3733 let decl = "struct huge { char a[4096]; };\nint take(struct huge);\n";
3734 let text = asm(&format!("{decl}int f(struct huge *p) {{ return take(*p); }}\n"));
3735
3736 let copy = text.find("call\tmemcpy").expect("the copy");
3737 let call = text.find("call\ttake").expect("the call");
3738 assert!(copy < call, "the copy comes first: {text}");
3739 assert!(text.contains("movq\t%rsp, %rdi"), "the destination: {text}");
3744 assert!(text.contains("$4096, %edx"), "the size: {text}");
3745 }
3746
3747 #[test]
3750 fn a_realigned_frame_reads_them_through_the_frame_pointer() {
3751 let six = "long a, long b, long c, long d, long e, long f";
3752 let body = "_Alignas(32) long wide[4]; wide[0] = g; return wide[0];";
3753 let text = asm(&format!("long f({six}, long g) {{ {body} }}\n"));
3754
3755 assert!(text.contains("\tandq\t$-32, %rsp"), "{text}");
3759 assert!(text.contains("\tmovq\t16(%rbp), "), "{text}");
3760 assert!(!text.contains("\tmovq\t16(%rsp), "), "{text}");
3761 }
3762
3763 #[test]
3765 fn the_target_decides_how_the_assembly_is_spelled() {
3766 let mut opts = options();
3767 opts.emit = EmitKind::Asm;
3768 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
3769 let text = run(&opts, "int f(void) { return 0; }\n").text().to_owned();
3770 assert!(text.contains("__TEXT,__text"), "{text}");
3771 assert!(text.contains("\n_f:\n"), "{text}");
3772 assert!(!text.contains(".note.GNU-stack"), "{text}");
3773 }
3774
3775 fn obj(source: &str) -> Vec<u8> {
3777 let mut opts = options();
3778 opts.emit = EmitKind::Object;
3779 let result = run(&opts, source);
3780 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3781 match result.artifact {
3782 Artifact::Object { bytes, .. } => bytes,
3783 other => panic!("expected an object, got {other:?}"),
3784 }
3785 }
3786
3787 #[test]
3793 fn a_function_goes_from_c_to_an_object_a_linker_would_take() {
3794 let bytes = obj("int add(int a, int b) { return a + b; }\n");
3795 assert_eq!(&bytes[..4], b"\x7fELF", "an object file starts by saying it is one");
3796 let text = asm("int add(int a, int b) { return a + b; }\n");
3797 assert!(
3798 text.contains("\taddl\t"),
3799 "and the listing of it is the same instructions:\n{text}"
3800 );
3801 }
3802
3803 #[test]
3805 fn a_variable_goes_from_c_to_the_section_it_belongs_in() {
3806 let text = asm("int counter = 42;\nstatic int hidden;\nconst int fixed = 7;\n");
3807 assert!(text.contains("\t.data\n\t.globl\tcounter\n"), "{text}");
3808 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
3809 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
3810 assert!(text.contains("\t.bss\n\t.p2align\t2\n"), "{text}");
3813 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
3814 assert!(!text.contains(".globl\thidden"), "{text}");
3815 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
3818 }
3819
3820 #[test]
3827 fn a_bit_field_initializer_writes_every_byte_of_the_value_and_not_only_the_ones_that_are_set() {
3828 let text = asm("struct s { unsigned f : 20; } x = { 0x12300 };\n");
3829 assert!(text.contains("\t.data\n"), "there is something to write: {text}");
3830 assert!(text.contains("\nx:\n\t.ascii\t\"\\000#\\001\"\n"), "and it is the value: {text}");
3831
3832 let text = asm("struct s { unsigned a : 8; unsigned b : 8; } x = { 0, 3 };\n");
3835 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\003\"\n"), "{text}");
3836
3837 let text = asm("struct s { unsigned long long f : 40; } x = { 0x100000 };\n");
3840 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\000\\020\"\n\t.space\t5\n"), "{text}");
3841
3842 let text = asm("struct s { unsigned f : 20; } x = { 0 };\n");
3844 assert!(text.contains("\t.bss\n"), "an object of zeroes is zeroes: {text}");
3845 assert!(text.contains("\nx:\n\t.space\t4\n"), "{text}");
3846 }
3847
3848 #[test]
3850 fn a_string_literal_is_a_variable_with_a_name_no_program_could_write() {
3851 let text = asm("const char *f(void) { return \"hi\"; }\n");
3852 assert!(text.contains("\t.ascii\t\"hi\\000\"\n"), "{text}");
3853 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
3854 let label = text
3855 .lines()
3856 .find(|line| line.starts_with(".Lstr"))
3857 .unwrap_or_else(|| panic!("a label for the literal in\n{text}"));
3858 assert!(!text.contains(&format!(".globl\t{}", label.trim_end_matches(':'))), "{text}");
3859 }
3860
3861 #[test]
3863 fn an_address_in_an_initializer_is_left_to_the_linker() {
3864 let source = "int counter;\nint *p = &counter;\n";
3865 let text = asm(source);
3866 assert!(text.contains("\np:\n\t.quad\tcounter\n"), "{text}");
3867 let bytes = obj(source);
3870 assert!(bytes.windows(8).any(|w| w == b"counter\0"), "the object has to name it");
3871 }
3872
3873 #[test]
3882 fn a_constant_holding_an_address_goes_in_the_section_the_loader_may_write_once() {
3883 let text = asm("static void a(void) {}\nstatic void b(void) {}\n\
3886 struct m { void (*x)(void); void (*y)(void); };\n\
3887 const struct m t = { a, b };\n");
3888 assert!(text.contains("\t.section\t.data.rel.ro.local,\"aw\",@progbits\n"), "{text}");
3889 assert!(text.contains("\nt:\n\t.quad\ta\n\t.quad\tb\n"), "{text}");
3890
3891 let text =
3894 asm("void a(void);\nstruct m { void (*x)(void); };\nconst struct m t = { a };\n");
3895 assert!(text.contains("\t.section\t.data.rel.ro,\"aw\",@progbits\n"), "{text}");
3896
3897 let text = asm("const int fixed = 7;\n");
3899 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
3900 }
3901
3902 #[test]
3909 fn a_thread_local_variable_is_storage_a_thread_gets_a_copy_of_and_an_offset_into_it() {
3910 let text = asm("_Thread_local int x = 1;\nint read(void) { return x; }\n");
3911 assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
3914 assert!(text.contains("\t.type\tx, @tls_object\n"), "{text}");
3915 assert!(text.contains("x@GOTTPOFF(%rip)"), "{text}");
3918 assert!(text.contains("%fs:0"), "{text}");
3919 }
3920
3921 #[test]
3927 fn the_address_of_this_thread_s_own_storage_is_read_out_of_the_segment_register() {
3928 let text = asm("void *here(void) { return __builtin_thread_pointer(); }\n");
3929 assert!(text.contains("movq\t%fs:0, "), "{text}");
3930 assert!(!text.contains("GOTTPOFF"), "{text}");
3932 }
3933
3934 #[test]
3945 fn a_prefetch_is_one_of_four_instructions_and_the_locality_is_what_picks() {
3946 for (locality, wanted) in
3947 [(0, "prefetchnta"), (1, "prefetcht2"), (2, "prefetcht1"), (3, "prefetcht0")]
3948 {
3949 let source =
3950 format!("void warm(void *p) {{ __builtin_prefetch(p, 0, {locality}); }}\n");
3951 let text = asm(&source);
3952 assert!(text.contains(&format!("\t{wanted}\t")), "locality {locality}: {text}");
3953 }
3954 let text = asm("void warm(void *p) { __builtin_prefetch(p); }\n");
3956 assert!(text.contains("\tprefetcht0\t"), "{text}");
3957 let text = asm("void warm(void *p) { __builtin_prefetch(p, 1); }\n");
3960 assert!(text.contains("\tprefetcht0\t"), "{text}");
3961 assert!(!text.contains("prefetchw"), "{text}");
3962 }
3963
3964 #[test]
3975 fn a_trap_is_the_instruction_the_machine_has_no_meaning_for() {
3976 let text = asm("void stop(void) { __builtin_trap(); }\n");
3977 assert!(text.contains("\tud2\n"), "{text}");
3978 assert!(!text.contains("\tcall"), "a stop is not a call to anything: {text}");
3979
3980 let text = asm("int stop(int a) { __builtin_trap(); return a + 1; }\n");
3981 assert!(text.contains("\tud2\n"), "{text}");
3982 assert!(text.contains("\taddl\t"), "the block goes on after a stop: {text}");
3983 }
3984
3985 #[test]
3997 fn assume_aligned_is_its_first_argument_and_keeps_the_rest() {
3998 let text = asm("void *aligned(char *p) { return __builtin_assume_aligned(p, 16); }\n");
3999 assert!(!text.contains("assume_aligned"), "{text}");
4000 assert!(!text.contains("\tcall"), "nothing is called for an alignment fact: {text}");
4001
4002 let source = "unsigned long width(void);\n\
4003 void *aligned(char *p) { return __builtin_assume_aligned(p, width()); }\n";
4004 let text = asm(source);
4005 assert!(!text.contains("assume_aligned"), "{text}");
4006 assert!(text.contains("width"), "the argument that is not the answer still runs: {text}");
4007 }
4008
4009 #[test]
4019 fn the_frame_address_is_the_frame_pointer_after_walking_that_many_links() {
4020 let text = asm("void *here(void) { return __builtin_frame_address(0); }\n");
4021 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
4022 assert!(text.contains("movq\t%rbp, %rax"), "{text}");
4023 assert!(!text.contains("\tcall"), "a frame address is not a call to anything: {text}");
4024
4025 let walk = |depth: u32| {
4026 let source = format!("void *up(void) {{ return __builtin_frame_address({depth}); }}\n");
4027 asm(&source).matches("movq\t(%r").count()
4028 };
4029 assert_eq!(walk(1), 1, "one link is one load");
4030 assert_eq!(walk(3), 3, "three links are three loads");
4031 }
4032
4033 #[test]
4043 fn the_return_address_is_one_word_above_the_frame_the_walk_ended_at() {
4044 let text = asm("void *back(void) { return __builtin_return_address(0); }\n");
4045 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
4046 assert!(text.contains("movq\t8(%rbp), %rax"), "{text}");
4047 assert!(!text.contains("\tcall"), "a return address is not a call to anything: {text}");
4048
4049 let text = asm("void *back(void) { return __builtin_return_address(2); }\n");
4050 assert_eq!(text.matches("movq\t(%r").count(), 2, "two links are two loads: {text}");
4051 assert!(text.contains("movq\t8(%r"), "and the answer is above the last of them: {text}");
4052 }
4053
4054 #[test]
4065 fn a_depth_that_is_not_a_small_constant_is_refused() {
4066 let mut opts = options();
4067 opts.emit = EmitKind::Ir;
4068 for source in [
4069 "void *up(int n) { return __builtin_return_address(n); }\n",
4070 "void *up(void) { return __builtin_frame_address(1000); }\n",
4071 ] {
4072 let messages = run(&opts, source).messages;
4073 let named = messages.iter().any(|m| m.contains("E0705"));
4074 assert!(named, "expected a refusal in {messages:?}");
4075 }
4076 }
4077
4078 #[test]
4090 fn an_alloca_takes_the_bytes_off_the_stack_pointer_and_answers_where_they_are() {
4091 let text =
4092 asm("void use(void *p); void f(unsigned long n) { use(__builtin_alloca(n)); }\n");
4093 assert!(text.contains("andq\t$-16"), "the size is rounded up to sixteen: {text}");
4094 assert!(text.contains("subq\t%rdi, %rsp"), "and taken off the stack pointer: {text}");
4095 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
4096
4097 let plain = concat!(
4100 "extern void *alloca(__SIZE_TYPE__);\n",
4101 "void use(void *p);\n",
4102 "void f(unsigned long n) { use(alloca(n)); }\n",
4103 );
4104 let text = asm(plain);
4105 assert!(text.contains("subq\t%rdi, %rsp"), "the plain name is the same bytes: {text}");
4106 assert_eq!(text.matches("\tcall").count(), 1, "and is not a call either: {text}");
4107
4108 let own = concat!(
4111 "static void *alloca(unsigned long n) { return 0; }\n",
4112 "void *f(unsigned long n) { return alloca(n); }\n",
4113 );
4114 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
4115 }
4116
4117 #[test]
4132 fn a_builtin_the_program_never_declared_is_the_builtin_rather_than_the_one_c89_wrote_down() {
4133 let mut opts = options();
4137 opts.permissive = true;
4138 let undeclared = "void use(void *p);
4139void f(unsigned long n) { use(alloca(n)); }
4140";
4141 assert_eq!(
4142 run(&opts, undeclared).messages,
4143 [
4144 "/main.c:2:31: warning: implicit declaration of function 'alloca' [E0521]",
4145 "/main.c:2:31: warning: incompatible implicit declaration of built-in function \
4146 'alloca' [E0713]",
4147 ]
4148 );
4149
4150 opts.emit = EmitKind::Asm;
4151 let text = run(&opts, undeclared).text().to_owned();
4152 assert!(text.contains("subq\t%rdi, %rsp"), "the bytes come off the stack: {text}");
4153 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
4154
4155 let string = "unsigned long f(void) { return strlen(\"abc\"); }\n";
4158 let text = run(&opts, string).text().to_owned();
4159 assert!(text.contains("call\tstrlen"), "strlen is still a call: {text}");
4160
4161 let own = concat!(
4164 "static void *alloca(unsigned long n) { return 0; }\n",
4165 "void *f(unsigned long n) { return alloca(n); }\n",
4166 );
4167 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
4168 }
4169
4170 #[test]
4180 fn the_bytes_an_alloca_took_are_still_there_at_the_end_of_the_block_that_took_them() {
4181 let inner = "{ use(__builtin_alloca(n)); }";
4182 for body in [inner.to_owned(), format!("int a[n]; {inner} use(a);")] {
4183 let source = format!("void use(void *p);\nvoid f(unsigned long n) {{ {body} }}\n");
4184 let text = asm(&source);
4185 for line in text.lines().filter(|line| line.trim_end().ends_with(", %rsp")) {
4189 let taking = line.contains("subq");
4190 let leaving = line.contains("%rbp");
4191 assert!(taking || leaving, "nothing puts the stack back: {line} in {text}");
4192 }
4193 }
4194 }
4195
4196 #[test]
4198 fn the_object_and_the_listing_are_two_spellings_of_one_compilation() {
4199 let source = "int callee(void); int g(void) { return callee(); }\n";
4203 let bytes = obj(source);
4204 assert!(
4205 bytes.windows(7).any(|w| w == b"callee\0"),
4206 "the object has to name the callee for the linker to find it"
4207 );
4208 let text = asm(source);
4209 assert!(text.contains("\tcall\tcallee\n"), "{text}");
4210 }
4211
4212 #[test]
4218 fn compiling_for_an_executable_produces_an_object_and_not_a_dump() {
4219 let mut opts = options();
4220 opts.emit = EmitKind::Executable;
4222 let result = run(&opts, "int main(void) { return 0; }\n");
4223 assert_eq!(result.messages, Vec::<String>::new());
4224 match result.artifact {
4225 Artifact::Object { bytes, .. } => assert_eq!(&bytes[..4], b"\x7fELF"),
4226 other => panic!("expected an object, got {other:?}"),
4227 }
4228 }
4229
4230 #[test]
4232 fn a_platform_with_no_object_writer_is_said_so_rather_than_written_as_elf() {
4233 let mut opts = options();
4234 opts.emit = EmitKind::Object;
4235 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
4236 let result = run(&opts, "int f(void) { return 0; }\n");
4237 assert!(result.failed(), "an object nobody can read is worse than a message");
4238 assert!(
4239 result.messages.iter().any(|m| m.contains("no object writer")),
4240 "{:?}",
4241 result.messages
4242 );
4243 }
4244
4245 fn ir(source: &str) -> String {
4247 let mut opts = options();
4248 opts.emit = EmitKind::Ir;
4249 let result = run(&opts, source);
4250 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4251 result.text().to_owned()
4252 }
4253
4254 fn errors(source: &str) -> Vec<String> {
4256 let mut opts = options();
4257 opts.emit = EmitKind::Ir;
4258 let result = run(&opts, source);
4259 assert!(result.failed(), "expected this to be refused:\n{source}");
4260 result.messages
4261 }
4262
4263 fn body(source: &str) -> String {
4265 let text = ir(source);
4266 let (_, rest) = text.split_once("{\n").expect("a function definition");
4267 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
4268 body.to_owned()
4269 }
4270
4271 #[test]
4279 fn gnu89_inline_is_what_decides_whether_a_bare_inline_definition_reaches_the_module() {
4280 let source = "inline int f(int x) { return x + 1; }\n";
4281 let with = |flag: bool| {
4282 let mut opts = options();
4283 opts.emit = EmitKind::Ir;
4284 opts.gnu89_inline = flag;
4285 let result = run(&opts, source);
4286 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
4287 result.text().to_owned()
4288 };
4289
4290 assert!(!with(false).contains("block0"), "no body: {}", with(false));
4293
4294 assert!(with(true).contains("block0"), "a body: {}", with(true));
4297 }
4298
4299 #[test]
4306 fn an_access_through_a_type_names_the_type_it_went_through() {
4307 let source = "\
4308struct s { int a; float b; };\n\
4309union u { int i; float f; };\n\
4310int scalar(int *p) { return *p; }\n\
4311float member(struct s *p) { p->a = 1; return p->b; }\n\
4312int element(int *a, long i) { return a[i]; }\n\
4313float through_a_union(union u *p) { p->i = 1; return p->f; }\n";
4314 let text = ir(source);
4315 assert!(text.contains(r#"!0 = tbaa "char""#), "the root: {text}");
4316 assert!(text.contains(r#"tbaa "int", parent !0"#), "int under it: {text}");
4317 assert!(text.contains(r#"tbaa "float", parent !0"#), "float under it: {text}");
4318 let named = text.lines().filter(|line| line.contains(", tbaa !")).count();
4321 assert_eq!(named, 6, "six accesses: {text}");
4322 }
4323
4324 #[test]
4331 fn turning_strict_aliasing_off_leaves_the_type_off_every_access() {
4332 let source = "int punned(float *f, int *i) { *i = 1; *f = 2.0f; return *i; }\n";
4333 let mut opts = options();
4334 opts.emit = EmitKind::Ir;
4335 opts.strict_aliasing = false;
4336 let result = run(&opts, source);
4337 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
4338 let text = result.text().to_owned();
4339 assert!(!text.contains("tbaa"), "not even the root: {text}");
4340 }
4341
4342 #[test]
4350 fn a_bare_return_from_a_function_that_promised_a_value_gives_back_a_zero() {
4351 let mut opts = options();
4352 opts.emit = EmitKind::Ir;
4353 opts.std = Std::C89;
4354 let compiled = |source: &str| {
4355 let result = run(&opts, source);
4356 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
4357 result.text().to_owned()
4358 };
4359
4360 let text = compiled("int f(int x) { if (x) return; return 3; }\n");
4361 assert!(text.contains("iconst.i32 0\n return"), "zero goes back: {text}");
4362 assert!(!text.contains("unreachable"), "the branch that reached it is kept: {text}");
4363
4364 let text = compiled("double f(int x) { if (x) return; return 1.0; }\n");
4366 assert!(text.contains("fconst.f64 0x0\n return"), "a float zero goes back: {text}");
4367 }
4368
4369 #[test]
4377 fn a_call_to_a_name_nothing_declared_declares_it_as_c89_said_to() {
4378 let mut opts = options();
4379 opts.emit = EmitKind::Ir;
4380 opts.std = Std::C89;
4381 let compiled = |source: &str| {
4382 let result = run(&opts, source);
4383 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
4384 result.text().to_owned()
4385 };
4386
4387 let text = compiled("int f(void) { return g(); }\n");
4389 assert!(text.contains("call @g"), "the call is to the name that was written: {text}");
4390 assert!(text.contains("i32"), "and it gives back an int: {text}");
4391
4392 let text = compiled("int f(char c) { return g(c); }\n");
4395 assert!(text.contains("sext.i32"), "the argument is promoted: {text}");
4396
4397 let mut opts = options();
4400 opts.std = Std::C89;
4401 let said = run(&opts, "int f(void) { return h; }\n").messages.join("\n");
4402 assert!(said.contains("'h' undeclared"), "not a call, so not declared: {said}");
4403 }
4404
4405 #[test]
4415 fn a_name_called_before_it_is_defined_still_gets_its_definition() {
4416 let mut opts = options();
4417 opts.emit = EmitKind::Ir;
4418 opts.std = Std::C89;
4419 let text = run(&opts, "int f(void) { return dummy(); }\ndummy () { return 7; }\n")
4420 .text()
4421 .to_owned();
4422 assert!(text.contains("func @f()"), "the caller is there: {text}");
4423 assert!(text.contains("func @dummy"), "and so is what it calls: {text}");
4424 assert!(text.contains("iconst.i32 7"), "with the body it was given: {text}");
4425 }
4426
4427 #[test]
4435 fn an_old_style_parameter_is_converted_from_what_the_call_promoted_it_to() {
4436 let mut opts = options();
4437 opts.emit = EmitKind::Ir;
4438 opts.std = Std::C89;
4439 let compiled = |source: &str| run(&opts, source).text().to_owned();
4440
4441 let text = compiled("f (c) unsigned char c; { return c; }\n");
4442 assert!(text.contains("func @f(i32"), "an int arrives: {text}");
4443 assert!(text.contains("trunc.i8"), "and is cut down to what was declared: {text}");
4444 assert!(text.contains("zext.i32"), "then read back unsigned: {text}");
4445
4446 let text = compiled("f (s) short s; { return s; }\n");
4448 assert!(text.contains("trunc.i16"), "cut down: {text}");
4449 assert!(text.contains("sext.i32"), "and read back signed: {text}");
4450
4451 let text = compiled("f (x) float x; { return x * 2; }\n");
4454 assert!(text.contains("func @f(f64"), "a double arrives: {text}");
4455 assert!(text.contains("fptrunc.f32"), "and is narrowed to the float: {text}");
4456
4457 let text = compiled("int f(unsigned char c) { return c; }\n");
4460 assert!(text.contains("func @f(i8)"), "the declared type arrives: {text}");
4461 assert!(!text.contains("trunc"), "so there is nothing to cut down: {text}");
4462 }
4463
4464 #[test]
4473 fn the_rules_gcc_promoted_are_decided_by_the_dialect_and_by_fpermissive() {
4474 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
4476 let cases = [
4477 ("static counted;\n", ["", "error", "warning", "error"]),
4478 ("int f(void) { return g(); }\n", ["", "error", "warning", "error"]),
4479 ("int f(x) { return x; }\n", ["", "error", "warning", "error"]),
4480 ("int *p;\nvoid h(void) { p = 1; }\n", ["warning", "error", "warning", "error"]),
4481 (
4482 "char *q;\nint *r;\nvoid k(void) { r = q; }\n",
4483 ["warning", "error", "warning", "error"],
4484 ),
4485 ("int f(void) { return; }\n", ["", "error", "warning", "error"]),
4486 ("void g(void) { return 1; }\n", ["warning", "error", "warning", "error"]),
4487 ];
4488
4489 for (source, wanted) in cases {
4490 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
4491 let mut opts = options();
4492 opts.std = std;
4493 opts.permissive = permissive;
4494 let said = run(&opts, source).messages.join("\n");
4495 let severity = if said.contains(": error: ") {
4496 "error"
4497 } else if said.contains(": warning: ") {
4498 "warning"
4499 } else {
4500 ""
4501 };
4502 let how = if permissive { " -fpermissive" } else { "" };
4503 assert_eq!(
4504 severity,
4505 wanted,
4506 "under -std={}{how}, {source} was answered with `{said}`",
4507 std.as_str()
4508 );
4509 if wanted.is_empty() {
4510 assert!(said.is_empty(), "nothing to say, but said `{said}`");
4511 }
4512 }
4513 }
4514 }
4515
4516 #[test]
4525 fn the_three_variadic_builtins_answer_a_bad_list_the_way_a_call_answers_a_bad_argument() {
4526 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
4527 let cases = [
4528 (
4529 "int f(int n, ...) { char *p; return __builtin_va_arg(p, int); }\n",
4530 "first argument to 'va_arg' not of type 'va_list'",
4531 ["error", "error", "error", "error"],
4532 ),
4533 (
4534 "void f(int n, ...) { char *p; __builtin_va_start(p, n); }\n",
4535 "passing argument 1 of '__builtin_va_start' from incompatible pointer type",
4536 ["warning", "error", "warning", "error"],
4537 ),
4538 (
4539 "void f(int n, ...) { int x; __builtin_va_end(x); }\n",
4540 "passing argument 1 of '__builtin_va_end' makes pointer from integer without a \
4541 cast",
4542 ["warning", "error", "warning", "error"],
4543 ),
4544 (
4545 "void f(int n, ...) { __builtin_va_list a; char *p; __builtin_va_copy(a, p); }\n",
4546 "passing argument 2 of '__builtin_va_copy' from incompatible pointer type",
4547 ["warning", "error", "warning", "error"],
4548 ),
4549 ];
4550
4551 for (source, message, wanted) in cases {
4552 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
4553 let mut opts = options();
4554 opts.std = std;
4555 opts.permissive = permissive;
4556 let said = run(&opts, source).messages.join("\n");
4557 let how = if permissive { " -fpermissive" } else { "" };
4558 assert!(
4559 said.contains(&format!(": {wanted}: {message}")),
4560 "under -std={}{how}, {source} was answered with `{said}`",
4561 std.as_str()
4562 );
4563 }
4564 }
4565 }
4566
4567 fn safe_ir(tier: rucc_session::Safety, source: &str) -> String {
4569 let mut opts = options();
4570 opts.emit = EmitKind::Ir;
4571 opts.safety = tier;
4572 let result = run(&opts, source);
4573 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4574 result.text().to_owned()
4575 }
4576
4577 const READS_THROUGH_A_POINTER: &str = "int read(int *p) { return p[1]; }\n";
4578
4579 fn padded_ir(padding: Padding, source: &str) -> String {
4581 let mut opts = options();
4582 opts.emit = EmitKind::Ir;
4583 opts.safety = rucc_session::Safety::Detect;
4584 opts.padding = padding;
4585 let result = run(&opts, source);
4586 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4587 result.text().to_owned()
4588 }
4589
4590 const FILLS_A_RECORD_A_MEMBER_AT_A_TIME: &str = "struct padded { char tag; int value; };\n\
4591 void fill(struct padded *p) { p->tag = 1; p->value = 2; }\n";
4592
4593 #[test]
4594 fn a_record_filled_a_member_at_a_time_comes_out_whole_when_padding_does_not_participate() {
4595 let text = padded_ir(Padding::Ignored, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
4599 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
4600 }
4601
4602 #[test]
4603 fn a_store_says_only_what_it_wrote_when_padding_does_participate() {
4604 let text = padded_ir(Padding::Tracked, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
4607 assert!(!text.contains("owns"), "{text}");
4608 }
4609
4610 #[test]
4611 fn a_member_of_a_union_owns_nothing_after_it() {
4612 let text = padded_ir(
4616 Padding::Ignored,
4617 "union u { char tag; long wide; };\nvoid fill(union u *p) { p->tag = 1; }\n",
4618 );
4619 assert!(!text.contains("owns"), "{text}");
4620 }
4621
4622 #[test]
4623 fn an_inner_records_trailing_padding_reaches_the_outer_records() {
4624 let text = padded_ir(
4629 Padding::Ignored,
4630 "struct inner { char c; };\n\
4631 struct outer { struct inner in; int x; };\n\
4632 void fill(struct outer *p) { p->in.c = 1; p->x = 2; }\n",
4633 );
4634 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
4635 }
4636
4637 #[test]
4638 fn a_build_that_did_not_ask_for_the_monitor_is_compiled_the_way_it_always_was() {
4639 let text = ir(READS_THROUGH_A_POINTER);
4643 assert!(!text.contains("check_"), "{text}");
4644 assert!(!text.contains("cap_of"), "{text}");
4645 }
4646
4647 #[test]
4648 fn asking_for_a_tier_puts_the_checks_in_before_the_optimizer_sees_them() {
4649 let text = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4650 assert!(text.contains("cap_of"), "{text}");
4651 assert!(text.contains("check_bounds"), "{text}");
4652 assert!(text.contains("check_live"), "{text}");
4653 assert!(text.contains("check_deriv"), "{text}");
4655 assert!(text.contains("check_type"), "{text}");
4657 }
4658
4659 #[test]
4660 fn the_three_tiers_that_are_not_off_all_check_the_same_accesses_so_far() {
4661 let detect = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4665 for tier in [rucc_session::Safety::Enforce, rucc_session::Safety::Kernel] {
4666 assert_eq!(safe_ir(tier, READS_THROUGH_A_POINTER), detect, "{tier}");
4667 }
4668 }
4669
4670 fn summary(tier: rucc_session::Safety, source: &str) -> String {
4672 let mut opts = options();
4673 opts.emit = EmitKind::SafetySummary;
4674 opts.safety = tier;
4675 let result = run(&opts, source);
4676 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4677 result.text().to_owned()
4678 }
4679
4680 #[test]
4681 fn the_summary_counts_the_checks_that_went_in_and_the_ones_still_standing() {
4682 let text = summary(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4683 assert!(text.contains("\"tier\": \"detect\""), "{text}");
4684 assert!(
4686 text.contains("\"bounds\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"),
4687 "{text}"
4688 );
4689 assert!(
4690 text.contains(
4691 "\"derivation\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"
4692 ),
4693 "{text}"
4694 );
4695 }
4696
4697 #[test]
4698 fn a_build_without_the_monitor_summarises_as_a_build_with_no_checks_in_it() {
4699 let text = summary(rucc_session::Safety::Off, READS_THROUGH_A_POINTER);
4703 assert!(text.contains("\"tier\": \"off\""), "{text}");
4704 assert!(
4705 text.contains("\"bounds\": { \"emitted\": 0, \"remaining\": 0, \"discharged\": 0 }"),
4706 "{text}"
4707 );
4708 }
4709
4710 #[test]
4711 fn a_call_the_boundary_models_is_counted_apart_from_one_it_does_not() {
4712 let text = summary(
4713 rucc_session::Safety::Detect,
4714 "void *memcpy(void *, const void *, unsigned long);\n\
4715 int puts(const char *);\n\
4716 void f(char *d, char *s) { memcpy(d, s, 4); puts(d); }\n",
4717 );
4718 assert!(text.contains("\"interposed\": 1"), "{text}");
4719 assert!(text.contains("\"puts\""), "{text}");
4720 assert!(!text.contains("__rucc_wrap_memcpy\""), "{text}");
4724 }
4725
4726 #[test]
4727 fn an_address_taken_of_a_library_function_is_counted_the_way_a_call_to_one_is() {
4728 let text = summary(
4733 rucc_session::Safety::Detect,
4734 "void *memcpy(void *, const void *, unsigned long);\n\
4735 int puts(const char *);\n\
4736 void *table[2] = { (void *)memcpy, (void *)puts };\n\
4737 void *f(int i) { return table[i]; }\n",
4738 );
4739 assert!(text.contains("\"interposed\": 1"), "{text}");
4740 assert!(text.contains("\"puts\""), "{text}");
4741 assert!(!text.contains("\"memcpy\""), "{text}");
4742 }
4743
4744 #[test]
4745 fn the_two_directions_a_pointer_crosses_the_boundary_are_counted_apart() {
4746 let text = summary(
4750 rucc_session::Safety::Detect,
4751 "void *notes_open(void);\n\
4752 char *f(char *p) { char *q = notes_open(); return q ? q : p; }\n",
4753 );
4754 assert!(text.contains("\"crossings\": { \"entered\": 1, \"returned\": 1 }"), "{text}");
4755 assert!(text.contains("\"notes_open\""), "{text}");
4756 }
4757
4758 #[test]
4759 fn a_static_function_nobody_takes_the_address_of_is_not_a_crossing() {
4760 let text = summary(
4763 rucc_session::Safety::Detect,
4764 "static int len(const char *p) { return p ? 1 : 0; }\n\
4765 int f(void) { return len(\"x\"); }\n",
4766 );
4767 assert!(text.contains("\"crossings\": { \"entered\": 0, \"returned\": 0 }"), "{text}");
4768 }
4769
4770 fn granules(source: &str) -> String {
4772 let mut opts = options();
4773 opts.emit = EmitKind::TypeGranules;
4774 let result = run(&opts, source);
4775 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4776 result.text().to_owned()
4777 }
4778
4779 #[test]
4780 fn the_granule_report_names_every_record_and_both_keyings() {
4781 let text = granules(
4782 "struct hot { char *p; int a; int b; };\n\
4783 int f(struct hot *h) { return h->a; }\n",
4784 );
4785 assert!(text.contains("struct hot"), "{text}");
4786 assert!(text.contains("every type distinct"), "{text}");
4789 assert!(text.contains("every pointer one type"), "{text}");
4790 assert!(text.contains("budget"), "{text}");
4791 }
4792
4793 #[test]
4794 fn a_record_nothing_uses_is_still_measured() {
4795 let text = granules("struct unused { long a; double b; };\nint f(void) { return 0; }\n");
4798 assert!(text.contains("struct unused"), "{text}");
4799 }
4800
4801 #[test]
4802 fn the_granule_report_stops_before_anything_is_lowered() {
4803 let text = granules(
4807 "struct wide { long double d; };\n\
4808 long double f(long double x) { return x * x; }\n",
4809 );
4810 assert!(text.contains("struct wide"), "{text}");
4811 }
4812
4813 #[test]
4814 fn a_witness_reaches_the_assembler_as_a_call_to_the_runtime() {
4815 let text = safe_asm(rucc_session::Safety::Detect, "char *f(char *p) { return p; }\n");
4818 assert!(text.contains("\tcall\t__rucc_cap_witness\n"), "{text}");
4819 }
4820
4821 #[test]
4822 fn a_pointer_turned_into_an_integer_is_on_the_trust_set() {
4823 let text = summary(
4824 rucc_session::Safety::Detect,
4825 "unsigned long f(int *p) { return (unsigned long) p; }\n",
4826 );
4827 assert!(text.contains("\"exposed\": 1"), "{text}");
4828 }
4829
4830 fn safe_asm(tier: rucc_session::Safety, source: &str) -> String {
4832 let mut opts = options();
4833 opts.emit = EmitKind::Asm;
4834 opts.safety = tier;
4835 let result = run(&opts, source);
4836 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4837 result.text().to_owned()
4838 }
4839
4840 #[test]
4841 fn a_check_reaches_the_assembler_as_a_call_to_the_runtime() {
4842 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4843 assert!(text.contains("\tcall\t__rucc_check_bounds\n"), "{text}");
4844 assert!(text.contains("\tcall\t__rucc_check_live\n"), "{text}");
4845 assert!(text.contains("\tcall\t__rucc_check_deriv\n"), "{text}");
4846 assert!(text.contains("\tcall\t__rucc_check_typed_init\n"), "{text}");
4849 }
4850
4851 #[test]
4852 fn every_check_that_reached_the_assembler_has_a_row_describing_it() {
4853 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4859 let section = format!("\t.section\t{},", rucc_safety::SECTION);
4860 assert_eq!(text.matches(§ion).count(), 4, "{text}");
4861 for index in 0..4 {
4862 let name = format!("__rucc_safety_desc_{index}");
4863 assert!(text.contains(&format!("{name}:\n")), "{text}");
4866 assert!(text.contains(&format!("{name}(%rip)")), "{text}");
4867 }
4868 assert!(!text.contains("__rucc_safety_desc_4"), "{text}");
4869 }
4870
4871 #[test]
4879 fn builtin_constant_p_is_folded_where_it_is_written_rather_than_called() {
4880 let text = ir(concat!(
4881 "int g;\n",
4882 "int a = __builtin_constant_p(1);\n",
4883 "int b = __builtin_constant_p(g);\n",
4884 "int c = __builtin_constant_p(\"abc\");\n",
4885 "int d = __builtin_constant_p(&g);\n",
4886 "int e = __builtin_constant_p(1.5);\n",
4887 "int h = __builtin_choose_expr(__builtin_constant_p(3), 11, 22);\n",
4888 ));
4889 assert!(text.contains("global @a : i32 = 1,"), "{text}");
4890 assert!(text.contains("global @b : i32 = 0,"), "{text}");
4891 assert!(text.contains("global @c : i32 = 1,"), "{text}");
4892 assert!(text.contains("global @d : i32 = 0,"), "{text}");
4893 assert!(text.contains("global @e : i32 = 1,"), "{text}");
4894 assert!(text.contains("global @h : i32 = 11,"), "{text}");
4895 assert!(!text.contains("__builtin_constant_p"), "it is not a call to anything:\n{text}");
4896
4897 let text = body("int f(void) { int i = 0; __builtin_constant_p(i++); return i; }\n");
4901 assert_eq!(text, "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 0\n return %0\n");
4902 }
4903
4904 #[test]
4913 fn a_call_to_a_library_builtin_reaches_the_library_function() {
4914 let text = body("void f(void) { __builtin_abort(); }\n");
4915 assert_eq!(text, "block0:\n call @abort() : ()\n return\n");
4916
4917 let text = ir("int f(const char *s) { return __builtin_puts(s) + __builtin_strlen(s); }\n");
4920 assert!(text.contains("call @puts(%0) : (ptr) -> i32"), "{text}");
4921 assert!(text.contains("call @strlen(%0) : (ptr) -> i64"), "{text}");
4922 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
4923 }
4924
4925 #[test]
4938 fn a_chk_builtin_reaches_the_checking_function_and_keeps_the_size() {
4939 let text = ir(concat!(
4940 "char d[8];\n",
4941 "void f(const char *s, unsigned long n) {\n",
4942 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
4943 " __builtin___strcpy_chk(d, s, __builtin_object_size(d, 1));\n",
4944 " __builtin___memset_chk(d, 0, n, 8);\n",
4945 "}\n",
4946 ));
4947 assert!(text.contains("call @__memcpy_chk("), "{text}");
4948 assert!(text.contains("call @__strcpy_chk("), "{text}");
4949 assert!(text.contains("call @__memset_chk("), "{text}");
4950 assert!(text.contains("iconst.i64 8"), "the object size reaches the call: {text}");
4951 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
4952 }
4953
4954 #[test]
4962 fn a_checking_call_whose_size_says_nothing_is_known_is_the_plain_library_call() {
4963 let text = ir(concat!(
4964 "extern char *p;\n",
4965 "char d[8];\n",
4966 "void f(const char *s, unsigned long n) {\n",
4967 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
4968 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4969 " __builtin___strcpy_chk(p, s, __builtin_object_size(p, 0));\n",
4970 " __builtin___stpncpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4971 " __builtin___sprintf_chk(p, 1, __builtin_object_size(p, 0), s);\n",
4972 "}\n",
4973 ));
4974
4975 assert!(
4977 text.contains("call @__memcpy_chk(%2, %0, %1, %3) : (ptr, ptr, i64, i64)"),
4978 "{text}"
4979 );
4980
4981 assert!(text.contains("call @memcpy(%6, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
4984 assert!(text.contains("call @strcpy(%10, %0) : (ptr, ptr) -> ptr"), "{text}");
4985 assert!(text.contains("call @stpncpy(%14, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
4986
4987 assert!(text.contains("call @__sprintf_chk("), "{text}");
4990
4991 let asm = asm(concat!(
4994 "void f(char *p, const char *s, unsigned long n) {\n",
4995 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
4996 "}\n",
4997 ));
4998 assert!(asm.contains("call\tmemcpy"), "{asm}");
4999 assert!(!asm.contains("$-1"), "the size that went away leaves no instruction:\n{asm}");
5000 }
5001
5002 #[test]
5010 fn the_v_spellings_of_the_chk_family_take_the_list_a_va_list_parameter_holds() {
5011 let text = ir(concat!(
5012 "char d[64];\n",
5013 "int f(const char *fmt, ...) {\n",
5014 " __builtin_va_list ap;\n",
5015 " __builtin_va_start(ap, fmt);\n",
5016 " int n = __builtin___vsprintf_chk(d, 1, __builtin_object_size(d, 0), fmt, ap);\n",
5017 " __builtin_va_end(ap);\n",
5018 " return n;\n",
5019 "}\n",
5020 ));
5021 assert!(text.contains("call @__vsprintf_chk("), "{text}");
5022 assert!(text.contains("iconst.i64 64"), "the object size reaches the call: {text}");
5023 }
5024
5025 #[test]
5036 fn the_absolute_value_family_is_the_magnitude_and_not_a_call() {
5037 let text = body(concat!(
5038 "long long llabs(long long);\n",
5039 "long long f(long long x) { return llabs(x); }\n",
5040 ));
5041 assert!(text.contains("%1 = iconst.i64 63"), "{text}");
5042 assert!(text.contains("%2 = ashr %0, %1"), "{text}");
5043 assert!(text.contains("%3 = xor %0, %2"), "{text}");
5044 assert!(text.contains("%4 = sub %3, %2"), "{text}");
5045 assert!(!text.contains("call"), "the call does not happen:\n{text}");
5046
5047 let text = body("int abs(int);\nint f(int x) { return abs(x); }\n");
5050 assert!(text.contains("iconst.i32 31"), "{text}");
5051 let text = body("long labs(long);\nlong f(long x) { return labs(x); }\n");
5052 assert!(text.contains("iconst.i64 63"), "{text}");
5053
5054 let text = body("long long f(long long x) { return __builtin_llabs(x); }\n");
5057 assert!(!text.contains("call"), "{text}");
5058
5059 let text = ir(concat!(
5061 "long long llabs(long long b);\n",
5062 "long long g(long long x) { return llabs(x); }\n",
5063 "long long llabs(long long b) { return 7; }\n",
5064 ));
5065 assert!(!text.contains("call @llabs"), "{text}");
5066 }
5067
5068 #[test]
5075 fn a_byte_swap_is_arithmetic_and_not_a_call() {
5076 let text = body("unsigned f(unsigned x) { return __builtin_bswap32(x); }\n");
5077 assert_eq!(text, "block0(%0: i32):\n %1 = bswap %0\n return %1\n");
5078
5079 let text = body("unsigned f(unsigned char c) { return __builtin_bswap32(c); }\n");
5082 assert!(text.contains("zext.i32 %0"), "widened first: {text}");
5083 assert!(text.contains("bswap %1"), "and swapped at four bytes: {text}");
5084 }
5085
5086 #[test]
5092 fn the_byte_swaps_reverse_at_the_width_their_name_says() {
5093 for (name, ty, width) in [
5094 ("__builtin_bswap16", "unsigned short", "i16"),
5095 ("__builtin_bswap32", "unsigned", "i32"),
5096 ("__builtin_bswap64", "unsigned long long", "i64"),
5097 ] {
5098 let source = format!("{ty} f({ty} x) {{ return {name}(x); }}\n");
5099 let text = body(&source);
5100 assert_eq!(
5101 text,
5102 format!("block0(%0: {width}):\n %1 = bswap %0\n return %1\n"),
5103 "{name}"
5104 );
5105 }
5106 }
5107
5108 #[test]
5115 fn the_bit_counts_are_instructions_and_not_calls() {
5116 let text = body("int f(unsigned x) { return __builtin_clz(x); }\n");
5117 assert_eq!(text, "block0(%0: i32):\n %1 = ctlz %0\n return %1\n");
5118
5119 let text = body("int f(unsigned x) { return __builtin_ctz(x); }\n");
5120 assert_eq!(text, "block0(%0: i32):\n %1 = cttz %0\n return %1\n");
5121
5122 let text = body("int f(unsigned x) { return __builtin_popcount(x); }\n");
5123 assert_eq!(text, "block0(%0: i32):\n %1 = ctpop %0\n return %1\n");
5124 }
5125
5126 #[test]
5135 fn the_bit_counts_ask_about_the_width_their_name_says() {
5136 let text = body("int f(unsigned long long x) { return __builtin_clzll(x); }\n");
5137 assert!(text.starts_with("block0(%0: i64):"), "counted at eight bytes: {text}");
5138 assert!(text.contains("%1 = ctlz %0"), "{text}");
5139 assert!(text.contains("trunc.i32 %1"), "and answered in an int: {text}");
5140
5141 let text = body("int f(unsigned long long x) { return __builtin_clz(x); }\n");
5144 assert!(text.contains("trunc.i32 %0"), "narrowed to what was asked about: {text}");
5145 assert!(text.contains("ctlz %1"), "and counted there: {text}");
5146
5147 let text = body("int f(unsigned long x) { return __builtin_popcountl(x); }\n");
5148 assert!(text.contains("%1 = ctpop %0"), "{text}");
5149 assert!(!text.contains("call"), "{text}");
5150 }
5151
5152 #[test]
5157 fn a_parity_is_the_low_bit_of_the_set_bit_count() {
5158 let text = body("int f(unsigned x) { return __builtin_parity(x); }\n");
5159 assert!(text.contains("%1 = ctpop %0"), "{text}");
5160 assert!(text.contains("iconst.i32 1"), "{text}");
5161 assert!(text.contains("and %1, %2"), "the low bit of it: {text}");
5162 }
5163
5164 #[test]
5170 fn the_first_set_bit_is_one_based_and_zero_for_a_zero() {
5171 let text = body("int f(int x) { return __builtin_ffs(x); }\n");
5172 assert!(text.contains("%1 = cttz %0"), "{text}");
5173 assert!(text.contains("%4 = add %1, %2"), "one more than the count: {text}");
5174 assert!(text.contains("%5 = icmp ne %0, %3"), "whether there was a bit at all: {text}");
5175 assert!(text.contains("%7 = sub %3, %6"), "spread to a mask: {text}");
5176 assert!(text.contains("%8 = and %4, %7"), "and kept only then: {text}");
5177 assert!(!text.contains("br_if"), "no branch: {text}");
5178 }
5179
5180 #[test]
5190 fn the_redundant_sign_bit_count_is_instructions_and_not_a_call() {
5191 let text = body("int f(int x) { return __builtin_clrsb(x); }\n");
5192 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
5193 assert!(text.contains("%2 = ashr %0, %1"), "the sign over every bit: {text}");
5194 assert!(text.contains("%3 = xor %0, %2"), "folded onto it: {text}");
5195 assert!(text.contains("%5 = shl %3, %4"), "one less than the count: {text}");
5196 assert!(text.contains("%6 = or %5, %4"), "with something to count at zero: {text}");
5197 assert!(text.contains("%7 = ctlz %6"), "{text}");
5198 assert!(!text.contains("call"), "{text}");
5199 assert!(!text.contains("br_if"), "no branch: {text}");
5200 }
5201
5202 #[test]
5208 fn the_unsigned_absolute_value_family_answers_in_the_unsigned_type() {
5209 let text = body("unsigned f(int x) { return __builtin_uabs(x); }\n");
5210 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
5211 assert!(text.contains("%4 = sub %3, %2"), "{text}");
5212 assert!(!text.contains("call"), "nothing declares uabs, so a call would not link: {text}");
5213
5214 let text = body("unsigned long long f(long long x) { return __builtin_ullabs(x); }\n");
5215 assert!(text.contains("iconst.i64 63"), "at the width the name says: {text}");
5216
5217 let text = body("int f(int x) { return __builtin_uabs(x) > 2147483647u; }\n");
5220 assert!(text.contains("icmp ugt"), "compared unsigned: {text}");
5221 }
5222
5223 #[test]
5231 fn the_widest_absolute_value_is_whichever_type_the_target_makes_intmax_t() {
5232 let text = body("long f(long x) { return __builtin_imaxabs(x); }\n");
5233 assert!(text.contains("iconst.i64 63"), "{text}");
5234 assert!(text.contains("%4 = sub %3, %2"), "{text}");
5235 assert!(!text.contains("call"), "{text}");
5236
5237 let text = body("unsigned long f(long x) { return __builtin_umaxabs(x); }\n");
5238 assert!(text.contains("iconst.i64 63"), "{text}");
5239 assert!(!text.contains("call"), "{text}");
5240 }
5241
5242 #[test]
5250 fn an_overflow_predicate_writes_nothing_and_answers_the_bit_the_check_would() {
5251 let text =
5252 body("int f(int a, int b) { return __builtin_add_overflow_p(a, b, (int) 0); }\n");
5253 assert!(text.contains("%2, %3 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
5254 assert!(!text.contains("store"), "nothing is written: {text}");
5255 assert!(!text.contains("call"), "{text}");
5256
5257 let text =
5260 body("int f(int a, int b) { return __builtin_mul_overflow_p(a, b, (long long) 0); }\n");
5261 assert!(text.contains("smul_overflow.(i64, i1)"), "{text}");
5262 assert!(!text.contains("store"), "{text}");
5263
5264 let text = body(concat!(
5267 "int g(void);\n",
5268 "int f(int a, int b) { return __builtin_sub_overflow_p(a, b, g()); }\n",
5269 ));
5270 assert!(!text.contains("call @g"), "the third argument is not evaluated: {text}");
5271 }
5272
5273 #[test]
5283 fn an_overflow_check_is_arithmetic_and_not_a_call() {
5284 let text =
5285 body("int f(int a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
5286 assert!(text.contains("%3, %4 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
5287 assert!(text.contains("store %3 -> %2"), "{text}");
5288 assert!(!text.contains("call"), "{text}");
5289
5290 let text =
5291 body("int f(int a, int b, int *r) { return __builtin_sub_overflow(a, b, r); }\n");
5292 assert!(text.contains("ssub_overflow.(i32, i1) %0, %1"), "{text}");
5293
5294 let text =
5295 body("int f(int a, int b, int *r) { return __builtin_mul_overflow(a, b, r); }\n");
5296 assert!(text.contains("smul_overflow.(i32, i1) %0, %1"), "{text}");
5297
5298 let text = body(
5301 "int f(unsigned a, unsigned b, unsigned *r) { return __builtin_add_overflow(a, b, r); }\n",
5302 );
5303 assert!(text.contains("uadd_overflow.(i32, i1) %0, %1"), "{text}");
5304 }
5305
5306 #[test]
5314 fn an_overflow_check_is_done_at_a_type_that_holds_every_operand() {
5315 let text = body(
5316 "int f(unsigned a, int b, long long *r) { return __builtin_add_overflow(a, b, r); }\n",
5317 );
5318 assert!(text.contains("%3 = zext.i64 %0"), "the unsigned operand keeps its value: {text}");
5319 assert!(text.contains("%4 = sext.i64 %1"), "and so does the signed one: {text}");
5320 assert!(text.contains("sadd_overflow.(i64, i1) %3, %4"), "{text}");
5321
5322 let text = body(
5325 "int f(long long a, long long b, long long *r) { return __builtin_mul_overflow(a, b, r); }\n",
5326 );
5327 assert!(text.contains("smul_overflow.(i64, i1) %0, %1"), "{text}");
5328 assert!(!text.contains("sext."), "{text}");
5329 assert!(!text.contains("zext.i64"), "{text}");
5331 }
5332
5333 #[test]
5341 fn an_overflow_check_writes_the_wrapped_answer_whether_or_not_it_fit() {
5342 let text =
5343 body("int f(int a, int b, char *r) { return __builtin_sub_overflow(a, b, r); }\n");
5344 assert!(text.contains("%3, %4 = ssub_overflow.(i32, i1) %0, %1"), "{text}");
5345 assert!(text.contains("%5 = trunc.i8 %3"), "narrowed to where it goes: {text}");
5346 assert!(text.contains("%6 = sext.i32 %5"), "and back: {text}");
5347 assert!(text.contains("%7 = icmp ne %6, %3"), "which is whether it fit: {text}");
5348 assert!(text.contains("store %5 -> %2"), "the narrowed value is stored either way: {text}");
5349 assert!(text.contains("%8 = or %4, %7"), "and either bit is an overflow: {text}");
5350 }
5351
5352 #[test]
5359 fn a_call_needing_more_than_the_widest_type_still_compiles() {
5360 for name in ["add", "sub", "mul"] {
5361 let source = format!(
5362 "int f(unsigned __int128 a, long long b, __int128 *r) {{\n \
5363 return __builtin_{name}_overflow(a, b, r);\n}}\n"
5364 );
5365 let mut opts = options();
5366 opts.emit = EmitKind::MirFinal;
5367 assert!(!run(&opts, &source).failed(), "{name} was refused or stopped the back end");
5368 }
5369 }
5370
5371 #[test]
5374 fn an_overflow_check_over_something_that_is_not_an_integer_says_so() {
5375 let messages =
5376 errors("int f(double a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
5377 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
5378
5379 let messages =
5380 errors("int f(int a, int b, double *r) { return __builtin_add_overflow(a, b, r); }\n");
5381 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
5382 }
5383
5384 #[test]
5395 fn an_ordered_access_is_ordered_in_the_ir() {
5396 let text = body("int f(int *p) { return __atomic_load_n(p, 0); }\n");
5397 assert!(text.contains("atomic_load.i32 %0, align 4, relaxed"), "{text}");
5398
5399 let text = body("long f(long *p) { return __atomic_load_n(p, 2); }\n");
5400 assert!(text.contains("atomic_load.i64 %0, align 8, acquire"), "{text}");
5401
5402 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
5403 assert!(text.contains("atomic_store %1 -> %0, align 4, release"), "{text}");
5404
5405 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
5406 assert!(text.contains("atomic_store %1 -> %0, align 4, seq_cst"), "{text}");
5407
5408 let text = body("void f(char *p, int v) { __atomic_store_n(p, v, 0); }\n");
5411 assert!(text.contains("trunc.i8 %1"), "{text}");
5412 assert!(text.contains("atomic_store %2 -> %0, align 1, relaxed"), "{text}");
5413 }
5414
5415 #[test]
5424 fn an_ordered_access_is_the_plain_instruction_on_this_machine() {
5425 let text = asm("int f(int *p) { return __atomic_load_n(p, 5); }\n");
5426 assert!(text.contains("movl\t(%rdi), %eax"), "{text}");
5427 assert!(!text.contains("mfence"), "a load needs no barrier here: {text}");
5428
5429 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
5430 assert!(text.contains("movl\t%esi, (%rdi)"), "{text}");
5431 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
5432
5433 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
5434 let (before, after) = text.split_once("mfence").expect("a barrier: {text}");
5435 assert!(before.contains("movl\t%esi, (%rdi)"), "the store comes first: {text}");
5436 assert!(!after.contains("movl"), "and nothing else is between them: {text}");
5437 }
5438
5439 #[test]
5449 fn a_barrier_is_one_instruction_at_the_strongest_ordering_and_none_below_it() {
5450 assert!(asm("void f(void) { __atomic_thread_fence(5); }\n").contains("mfence"));
5451 assert!(asm("void f(void) { __sync_synchronize(); }\n").contains("mfence"));
5452
5453 for weaker in ["1", "2", "3", "4"] {
5454 let source = format!("void f(void) {{ __atomic_thread_fence({weaker}); }}\n");
5455 assert!(!asm(&source).contains("mfence"), "{weaker} costs nothing here");
5456 }
5457 }
5458
5459 #[test]
5469 fn the_three_x86_fences_are_the_barrier_the_strongest_ordering_gives() {
5470 for name in ["__builtin_ia32_sfence", "__builtin_ia32_lfence", "__builtin_ia32_mfence"] {
5471 let source = format!("void f(void) {{ {name}(); }}\n");
5472 assert!(asm(&source).contains("mfence"), "{name} is a barrier");
5473 let text = body(&source);
5474 assert!(text.contains("fence seq_cst"), "{name}: {text}");
5475 }
5476
5477 let result = run(&options(), "void f(void) { __builtin_ia32_sfence(1); }\n");
5478 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
5479 assert!(result.messages[0].contains("too many arguments"), "{:?}", result.messages);
5480 }
5481
5482 #[test]
5488 fn a_compare_and_exchange_is_one_instruction_answering_two_things() {
5489 let text =
5492 body("int f(int *p, int e, int d) { return __sync_val_compare_and_swap(p, e, d); }\n");
5493 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
5494 assert!(text.contains("return %3"), "the value it found: {text}");
5495
5496 let text =
5497 body("int f(int *p, int e, int d) { return __sync_bool_compare_and_swap(p, e, d); }\n");
5498 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
5499 assert!(text.contains("zext.i32 %4"), "whether it happened: {text}");
5500
5501 let text = body(
5504 "int f(int *p, int *e, int d) { return __atomic_compare_exchange_n(p, e, d, 0, 4, 2); }\n",
5505 );
5506 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
5507 assert!(text.contains("%4, %5 = cmpxchg.(i32, i1) %0, %3, %2, align 4, acq_rel"), "{text}");
5508 assert!(text.contains("br_if %5, block2, block1"), "{text}");
5509 assert!(text.contains("store %4 -> %1, align 4"), "{text}");
5510
5511 let text = body(
5514 "int f(int *p, int *e, int *d) { return __atomic_compare_exchange(p, e, d, 0, 5, 5); }\n",
5515 );
5516 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
5517 assert!(text.contains("%4 = load.i32 %2, align 4"), "{text}");
5518 assert!(text.contains("%5, %6 = cmpxchg.(i32, i1) %0, %3, %4, align 4, seq_cst"), "{text}");
5519 }
5520
5521 #[test]
5528 fn a_compare_and_exchange_is_a_locked_instruction_at_the_width_of_the_object() {
5529 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
5530 for (ty, suffix, reg) in widths {
5531 let source = format!(
5532 "int f({ty} *p, {ty} e, {ty} d) {{ return __sync_bool_compare_and_swap(p, e, d); }}\n"
5533 );
5534 let text = asm(&source);
5535 assert!(text.contains("\tlock\n"), "{ty}: {text}");
5536 assert!(text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
5537 assert!(text.contains("sete\t"), "{ty}: {text}");
5538 }
5539 let source =
5540 "int f(long *p, long e, long d) { return __sync_bool_compare_and_swap(p, e, d); }\n";
5541 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
5542
5543 for order in ["0", "2", "3", "4", "5"] {
5547 let call = format!("__atomic_compare_exchange_n(p, e, d, 0, {order}, 0)");
5548 let source = format!("int f(int *p, int *e, int d) {{ return {call}; }}\n");
5549 let text = asm(&source);
5550 assert!(text.contains("cmpxchgl\t"), "{order}: {text}");
5551 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
5552 }
5553 }
5554
5555 #[test]
5567 fn a_read_modify_write_is_one_instruction_and_the_arithmetic_a_name_asks_for() {
5568 let text = body("int f(int *p, int v) { return __atomic_fetch_add(p, v, 5); }\n");
5569 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
5570 assert!(text.contains("return %2"), "the value that was there: {text}");
5571
5572 let text = body("int f(int *p, int v) { return __atomic_add_fetch(p, v, 5); }\n");
5573 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
5574 assert!(text.contains("%3 = add %2, %1"), "and the value afterwards: {text}");
5575
5576 let text = body("int f(int *p, int v) { return __atomic_sub_fetch(p, v, 5); }\n");
5577 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
5578 assert!(text.contains("%3 = sub %2, %1"), "{text}");
5579
5580 let text = body("int f(int *p, int v) { return __sync_fetch_and_sub(p, v); }\n");
5582 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
5583
5584 let text = body("int f(int *p, int v) { return __atomic_exchange_n(p, v, 5); }\n");
5587 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, seq_cst"), "{text}");
5588
5589 let text = body("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
5590 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, acquire"), "{text}");
5591
5592 let text = body("void f(int *p) { __sync_lock_release(p); }\n");
5595 assert!(text.contains("release"), "{text}");
5596 assert!(text.contains("%1 = iconst.i32 0"), "{text}");
5597
5598 let text = body("void f(int *p, int guard) { __sync_lock_release(p, guard); }\n");
5602 assert!(text.contains("%2 = iconst.i32 0"), "{text}");
5603 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
5604
5605 let text = body("int f(int *p, int v) { return __atomic_fetch_and(p, v, 5); }\n");
5608 assert!(text.contains("%2 = atomic_rmw.i32 and %0, %1, align 4, seq_cst"), "{text}");
5609
5610 let text = body("int f(int *p, int v) { return __sync_or_and_fetch(p, v); }\n");
5611 assert!(text.contains("%2 = atomic_rmw.i32 or %0, %1, align 4, seq_cst"), "{text}");
5612 assert!(text.contains("%3 = or %2, %1"), "and the value afterwards: {text}");
5613
5614 let text = body("int f(int *p, int v) { return __atomic_nand_fetch(p, v, 5); }\n");
5617 assert!(text.contains("%2 = atomic_rmw.i32 nand %0, %1, align 4, seq_cst"), "{text}");
5618 assert!(text.contains("%3 = and %2, %1"), "{text}");
5619 assert!(text.contains("%4 = iconst.i32 -1"), "{text}");
5620 assert!(text.contains("%5 = xor %3, %4"), "{text}");
5621 }
5622
5623 #[test]
5634 fn a_bitwise_read_modify_write_is_a_loop_around_the_compare_and_exchange() {
5635 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
5636 for (ty, suffix, reg) in widths {
5637 for (name, call, insn) in [
5638 ("and", "__atomic_fetch_and(p, v, 5)", "and"),
5639 ("or", "__sync_fetch_and_or(p, v)", "or"),
5640 ("xor", "__atomic_xor_fetch(p, v, 5)", "xor"),
5641 ] {
5642 let source = format!("{ty} f({ty} *p, {ty} v) {{ return {call}; }}\n");
5643 let text = asm(&source);
5644 assert!(text.contains("\tlock\n"), "{ty} {name}: {text}");
5645 assert!(
5646 text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")),
5647 "{ty} {name}: {text}"
5648 );
5649 assert!(text.contains(&format!("{insn}{suffix}\t")), "{ty} {name}: {text}");
5650 assert!(!text.contains("\txadd"), "{ty} {name} is not an add: {text}");
5652 assert!(!text.contains("\txchg"), "{ty} {name} is not an exchange: {text}");
5653 }
5654 }
5655 let source = "long f(long *p, long v) { return __atomic_fetch_or(p, v, 5); }\n";
5656 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
5657
5658 let text = asm("int f(int *p, int v) { return __sync_fetch_and_nand(p, v); }\n");
5662 assert!(text.contains("cmpxchgl\t"), "{text}");
5663 assert!(text.contains("andl\t"), "{text}");
5664 assert!(text.contains("notl\t"), "{text}");
5665 }
5666
5667 #[test]
5676 fn an_access_through_a_second_pointer_is_the_same_access_and_one_more() {
5677 let text = body("void f(int *p, int *r) { __atomic_load(p, r, 5); }\n");
5678 assert!(text.contains("%2 = atomic_load.i32 %0, align 4, seq_cst"), "{text}");
5679 assert!(text.contains("store %2 -> %1, align 4"), "and out through the place: {text}");
5680
5681 let text = body("void f(int *p, int *v) { __atomic_store(p, v, 3); }\n");
5682 assert!(text.contains("%2 = load.i32 %1, align 4"), "in through the place: {text}");
5683 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
5684
5685 let text = body("void f(int *p, int *v, int *r) { __atomic_exchange(p, v, r, 5); }\n");
5688 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
5689 assert!(text.contains("%4 = atomic_rmw.i32 xchg %0, %3, align 4, seq_cst"), "{text}");
5690 assert!(text.contains("store %4 -> %2, align 4"), "{text}");
5691 }
5692
5693 #[test]
5704 fn a_flag_is_an_exchange_of_one_byte_and_a_store_of_a_zero_over_the_same_byte() {
5705 for pointer in ["char", "int", "void"] {
5706 let source = format!("int f({pointer} *p) {{ return __atomic_test_and_set(p, 5); }}\n");
5707 let text = body(&source);
5708 assert!(text.contains("%1 = iconst.i8 1"), "{pointer}: {text}");
5709 assert!(
5710 text.contains("%2 = atomic_rmw.i8 xchg %0, %1, align 1, seq_cst"),
5711 "{pointer}: {text}"
5712 );
5713 assert!(text.contains("%4 = icmp ne %2, %3"), "{pointer}: {text}");
5714
5715 let source = format!("void f({pointer} *p) {{ __atomic_clear(p, 3); }}\n");
5716 let text = body(&source);
5717 assert!(text.contains("atomic_store %2 -> %0, align 1, release"), "{pointer}: {text}");
5718 }
5719
5720 let text = asm("int f(int *p) { return __atomic_test_and_set(p, 5); }\n");
5723 assert!(text.contains("xchgb\t%al, (%rdi)"), "{text}");
5724 assert!(text.contains("setne\t"), "{text}");
5725 }
5726
5727 #[test]
5735 fn a_read_modify_write_is_an_exchange_or_a_locked_add_at_the_width_of_the_object() {
5736 let widths = [("char", "b", "%sil"), ("short", "w", "%si"), ("int", "l", "%esi")];
5737 for (ty, suffix, reg) in widths {
5738 let source =
5739 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_fetch_add(p, v, 5); }}\n");
5740 let text = asm(&source);
5741 assert!(text.contains("\tlock\n"), "{ty}: {text}");
5742 assert!(text.contains(&format!("xadd{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
5743
5744 let source =
5745 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_exchange_n(p, v, 5); }}\n");
5746 let text = asm(&source);
5747 assert!(text.contains(&format!("xchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
5748 assert!(!text.contains("\tlock\n"), "an exchange is locked already: {ty}: {text}");
5749 }
5750 let source = "long f(long *p, long v) { return __atomic_fetch_add(p, v, 5); }\n";
5751 assert!(asm(source).contains("xaddq\t%rsi, (%rdi)"), "{}", asm(source));
5752
5753 let source = "int f(int *p, int v) { return __atomic_fetch_sub(p, v, 5); }\n";
5756 let text = asm(source);
5757 assert!(text.contains("negl\t"), "{text}");
5758 assert!(text.contains("xaddl\t"), "{text}");
5759
5760 for order in ["0", "2", "3", "4", "5"] {
5763 let source =
5764 format!("int f(int *p, int v) {{ return __atomic_fetch_add(p, v, {order}); }}\n");
5765 let text = asm(&source);
5766 assert!(text.contains("xaddl\t"), "{order}: {text}");
5767 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
5768 }
5769
5770 let text = asm("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
5774 assert!(text.contains("xchgl\t%esi, (%rdi)"), "{text}");
5775 let text = asm("void f(int *p) { __sync_lock_release(p); }\n");
5782 assert!(text.contains("xorl\t%eax, %eax"), "{text}");
5783 assert!(text.contains("movl\t%eax, (%rdi)"), "{text}");
5784 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
5785 }
5786
5787 #[test]
5799 fn the_lock_free_questions_are_answered_as_constants() {
5800 for size in ["1", "2", "4", "8"] {
5801 let source =
5802 format!("int f(void) {{ return __atomic_always_lock_free({size}, 0); }}\n");
5803 let text = asm(&source);
5804 assert!(text.contains("movb\t$1, %al"), "{size} bytes is lock free: {text}");
5805 assert!(!text.contains("call"), "and is not a call: {text}");
5806 }
5807 for size in ["3", "16", "sizeof(long double)"] {
5808 let source = format!("int f(void) {{ return __atomic_is_lock_free({size}, 0); }}\n");
5809 let text = asm(&source);
5810 assert!(text.contains("movb\t$0, %al"), "{size} bytes is not: {text}");
5811 assert!(!text.contains("call"), "and is not a call either: {text}");
5812 }
5813
5814 let text = asm("int f(int n) { return __atomic_is_lock_free(n, 0); }\n");
5818 assert!(text.contains("movb\t$0, %al"), "a size nobody knows is not lock free: {text}");
5819 let text = asm("int f(int *p) { return __atomic_always_lock_free(8, p); }\n");
5820 assert!(text.contains("movb\t$0, %al"), "eight bytes at four is not: {text}");
5821 let text = asm("int f(long *p) { return __atomic_always_lock_free(8, p); }\n");
5822 assert!(text.contains("movb\t$1, %al"), "and at eight it is: {text}");
5823 }
5824
5825 #[test]
5837 fn a_memory_order_an_operation_cannot_carry_is_read_as_the_strongest() {
5838 let mut opts = options();
5839 opts.emit = EmitKind::Ir;
5840
5841 let acquire_store = run(&opts, "void f(int *p, int v) { __atomic_store_n(p, v, 2); }\n");
5842 assert!(acquire_store.text().contains("seq_cst"), "{:?}", acquire_store.text());
5843 assert!(acquire_store.messages[0].contains("[W0333]"), "{:?}", acquire_store.messages);
5844
5845 let nonsense = run(&opts, "int f(int *p) { return __atomic_load_n(p, 99); }\n");
5846 assert!(nonsense.text().contains("seq_cst"), "{:?}", nonsense.text());
5847 assert!(nonsense.messages[0].contains("[W0333]"), "{:?}", nonsense.messages);
5848
5849 let computed = run(&opts, "int f(int *p, int n) { return __atomic_load_n(p, n); }\n");
5850 assert!(computed.text().contains("seq_cst"), "{:?}", computed.text());
5851 assert_eq!(computed.messages, Vec::<String>::new(), "a computed order is not a mistake");
5852 }
5853
5854 #[test]
5866 fn a_conversion_between_a_float_and_the_widest_unsigned_integer_is_written_without_a_branch() {
5867 let text = asm("double f(unsigned long long x) { return (double)x; }\n");
5868 assert!(text.contains("cvtsi2sdq"), "the signed conversion is what runs: {text}");
5869 assert!(text.contains("shrq"), "with the value halved first: {text}");
5870 assert!(text.contains("addsd"), "and doubled after: {text}");
5871 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
5872
5873 let text = asm("unsigned long long f(double d) { return (unsigned long long)d; }\n");
5874 assert!(text.contains("cvttsd2siq"), "the signed conversion is what runs: {text}");
5875 assert!(text.contains("subsd"), "with half the range taken off first: {text}");
5876 assert!(text.contains("shlq\t$63"), "and the top bit put back: {text}");
5877 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
5878 }
5879
5880 #[test]
5891 fn a_plain_name_the_program_took_is_the_programs_own_function() {
5892 let taken = concat!(
5893 "static long long llabs(long long b) { return 7; }\n",
5894 "long long f(long long x) { return llabs(x); }\n",
5895 );
5896 assert!(ir(taken).contains("call @llabs"), "a static definition is the program's own");
5897
5898 let retyped = concat!("int llabs(int b);\n", "int f(int x) { return llabs(x); }\n",);
5899 assert!(ir(retyped).contains("call @llabs"), "another type is another function");
5900
5901 let plain = concat!(
5902 "long long llabs(long long b);\n",
5903 "long long f(long long x) { return llabs(x); }\n",
5904 );
5905 let mut opts = options();
5906 opts.emit = EmitKind::Ir;
5907 assert!(!run(&opts, plain).text().contains("call @llabs"), "the library's by default");
5908
5909 opts.builtins = false;
5910 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin");
5911
5912 opts.builtins = true;
5913 opts.no_builtin = vec!["llabs".to_owned()];
5914 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin-llabs");
5915 let one = "long labs(long b);\nlong f(long x) { return labs(x); }\n";
5916 assert!(!run(&opts, one).text().contains("call @labs"), "one name and not the family");
5917
5918 opts.no_builtin = Vec::new();
5921 opts.builtins = false;
5922 let prefixed = "long long f(long long x) { return __builtin_llabs(x); }\n";
5923 assert!(!run(&opts, prefixed).text().contains("call @llabs"), "the prefix is a promise");
5924 }
5925
5926 #[test]
5939 fn the_hint_builtins_are_their_first_argument_and_the_hint_leaves_no_trace() {
5940 let text = ir(concat!(
5941 "long a = __builtin_expect(7, 1);\n",
5942 "long b = __builtin_expect_with_probability(9, 1, 0.9);\n",
5943 "unsigned long c = sizeof(__builtin_expect((char)1, 1));\n",
5944 ));
5945 assert!(text.contains("global @a : i64 = 7,"), "{text}");
5946 assert!(text.contains("global @b : i64 = 9,"), "{text}");
5947 assert!(text.contains("global @c : i64 = 8,"), "{text}");
5948 assert!(!text.contains("__builtin_expect"), "it is not a call to anything:\n{text}");
5949
5950 let text = body("long f(char c) { return __builtin_expect(c, 1); }\n");
5953 assert!(text.contains("sext"), "{text}");
5954
5955 let one = "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 1\n %2 = sext.i64 %1\n return %0\n";
5959 assert_eq!(body("int f(void) { int i = 0; __builtin_expect(1, i++); return i; }\n"), one);
5960 let source = "int g(void) { int i = 0; __builtin_expect_with_probability(1, i++, 0.5); return i; }\n";
5961 assert_eq!(body(source), one);
5962
5963 let kept = body("int f(int n) { int i = 0; __builtin_expect(n, i++); return i; }\n");
5968 assert!(kept.contains("add.nsw"), "the hint still runs: {kept}");
5969 assert!(kept.ends_with("return %3\n"), "and the answer is what it left behind: {kept}");
5970 let both = "int g(int n) { int i = 0; __builtin_expect_with_probability(n, i++, 0.5); return i; }\n";
5971 assert!(body(both).contains("add.nsw"), "and so does the one with three arguments");
5972 }
5973
5974 #[test]
5986 fn a_promise_that_control_does_not_arrive_writes_no_instruction() {
5987 let promised = "int f(int x) { if (x) return 1; __builtin_unreachable(); }\n";
5988 let text = ir(promised);
5989 assert!(text.contains(" unreachable_hint\n"), "{text}");
5990 assert!(!text.contains("call"), "it is not a call to anything:\n{text}");
5991
5992 let after = body("int g(int x) { __builtin_unreachable(); return x; }\n");
5996 assert!(after.contains("return"), "{after}");
5997
5998 let text = asm(promised);
6001 let mine = text.split_once("\nf:\n").expect("a definition").1;
6002 let mine = mine.split_once("\t.size").expect("a definition").0;
6003 let plain = asm("int f(int x) { if (x) return 1; }\n");
6004 let plain = plain.split_once("\nf:\n").expect("a definition").1;
6005 let plain = plain.split_once("\t.size").expect("a definition").0;
6006 assert_eq!(mine, plain);
6007 let last = mine.lines().rfind(|line| !line.trim_start().starts_with('.'));
6010 assert_eq!(last.map(str::trim), Some("ret"), "{mine}");
6011 assert!(!mine.contains("ud2"), "{mine}");
6012 }
6013
6014 #[test]
6021 fn a_library_builtin_is_diagnosed_under_the_name_the_program_wrote() {
6022 let mut opts = options();
6023 opts.emit = EmitKind::Ir;
6024 let messages = run(&opts, "void f(void) { __builtin_abort(1); }\n").messages;
6025 assert!(
6026 messages.iter().any(|m| m.contains("__builtin_abort")),
6027 "expected the written name in {messages:?}"
6028 );
6029 }
6030
6031 #[test]
6039 fn a_builtin_nothing_lowers_is_refused_by_name() {
6040 let mut opts = options();
6041 opts.emit = EmitKind::Ir;
6042 let builtin = "__atomic_signal_fence";
6043 let source = format!("int counter;\nint f(void) {{ return ({builtin}(5), 0); }}\n");
6044 let messages = run(&opts, &source).messages;
6045 let named = messages.iter().any(|m| m.contains(builtin) && m.contains("E0686"));
6046 assert!(named, "expected {builtin} to be refused by name in {messages:?}");
6047 }
6048
6049 #[test]
6058 fn what_is_refused_is_the_call_and_not_the_name() {
6059 let text = ir(concat!(
6060 "void __atomic_signal_fence(int order) { (void)order; }\n",
6061 "void f(void) { __atomic_signal_fence(5); }\n",
6062 ));
6063 assert!(text.contains("call @__atomic_signal_fence"), "{text}");
6064 }
6065
6066 #[test]
6075 fn the_object_size_of_an_address_is_what_the_layout_leaves_in_front_of_it() {
6076 let text = ir(concat!(
6077 "struct S { char a[8]; int n; char b[12]; };\n",
6078 "char g[32];\n",
6079 "struct S gs;\n",
6080 "unsigned long whole = __builtin_object_size(g, 0);\n",
6081 "unsigned long moved = __builtin_object_size(g + 4, 0);\n",
6082 "unsigned long back = __builtin_object_size(g + 30 - 2, 0);\n",
6083 "unsigned long outer = __builtin_object_size(gs.a, 0);\n",
6084 "unsigned long inner = __builtin_object_size(gs.a, 1);\n",
6085 "unsigned long scalar = __builtin_object_size(&gs.n, 1);\n",
6086 "unsigned long after = __builtin_object_size(&gs.n, 0);\n",
6087 "unsigned long into = __builtin_object_size(&gs.b[2], 1);\n",
6088 "unsigned long text = __builtin_object_size(\"hello\", 0);\n",
6089 "unsigned long dyn = __builtin_dynamic_object_size(gs.b, 1);\n",
6090 ));
6091 for (name, size) in [
6092 ("whole", 32),
6093 ("moved", 28),
6094 ("back", 4),
6095 ("outer", 24),
6096 ("inner", 8),
6097 ("scalar", 4),
6098 ("after", 16),
6099 ("into", 10),
6100 ("text", 6),
6101 ("dyn", 12),
6102 ] {
6103 let said = format!("global @{name} : i64 = {size},");
6104 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
6105 }
6106 }
6107
6108 #[test]
6116 fn the_object_behind_an_address_can_be_one_with_automatic_storage() {
6117 let text = body(concat!(
6118 "struct S { char a[8]; int n; char b[12]; };\n",
6119 "unsigned long f(void) {\n",
6120 " char loc[20];\n",
6121 " struct S ls;\n",
6122 " return __builtin_object_size(loc + 3, 0) + __builtin_object_size(ls.b + 2, 1);\n",
6123 "}\n",
6124 ));
6125 assert!(text.contains("iconst.i64 17"), "twenty bytes with three used: {text}");
6126 assert!(text.contains("iconst.i64 10"), "twelve bytes with two used: {text}");
6127 }
6128
6129 #[test]
6139 fn an_address_with_no_object_in_sight_answers_at_the_end_of_the_range_its_kind_asks_for() {
6140 let text = ir(concat!(
6141 "struct T { int n; char f[]; };\n",
6142 "extern char *p;\n",
6143 "extern struct T *t;\n",
6144 "unsigned long largest = __builtin_object_size(p, 0);\n",
6145 "unsigned long nearest = __builtin_object_size(p, 1);\n",
6146 "unsigned long least = __builtin_object_size(p, 2);\n",
6147 "unsigned long tight = __builtin_object_size(p, 3);\n",
6148 "unsigned long flex = __builtin_object_size(t->f, 1);\n",
6149 "int says = __builtin_object_size(p, 0) == (unsigned long)-1;\n",
6150 ));
6151 for name in ["largest", "nearest", "flex"] {
6152 let said = format!("global @{name} : i64 = -1,");
6156 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
6157 }
6158 for name in ["least", "tight"] {
6159 let said = format!("global @{name} : i64 = 0,");
6160 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
6161 }
6162 assert!(text.contains("global @says : i32 = 1,"), "{text}");
6163 }
6164
6165 #[test]
6172 fn the_address_an_object_size_is_asked_about_is_not_evaluated() {
6173 let text = body(concat!(
6174 "extern char *side(void);\n",
6175 "unsigned long f(void) { return __builtin_object_size(side(), 0); }\n",
6176 ));
6177 assert!(!text.contains("call"), "nothing is called: {text}");
6178 }
6179
6180 #[test]
6185 fn a_kind_that_is_not_one_of_the_four_is_refused() {
6186 for source in [
6187 "extern char *p;\nextern int k;\nunsigned long f(void) ".to_owned()
6188 + "{ return __builtin_object_size(p, k); }\n",
6189 "extern char *p;\nunsigned long f(void) { return __builtin_object_size(p, 4); }\n"
6190 .to_owned(),
6191 "extern char *p;\nunsigned long f(void) ".to_owned()
6192 + "{ return __builtin_dynamic_object_size(p, -1); }\n",
6193 ] {
6194 let messages = errors(&source);
6195 let named = messages.iter().any(|m| m.contains("E0709") && m.contains("0 to 3"));
6196 assert!(named, "expected a complaint about the kind in {messages:?}");
6197 }
6198 }
6199
6200 #[test]
6206 fn the_pair_that_saves_a_place_lowers_to_the_two_markers() {
6207 let text = ir(concat!(
6208 "void *buf[5];\n",
6209 "int f(void) {\n",
6210 " if (__builtin_setjmp(buf)) return 2;\n",
6211 " return 1;\n",
6212 "}\n",
6213 "void g(void) { __builtin_longjmp(buf, 1); }\n",
6214 ));
6215 assert!(text.contains("= setjmp_marker.i32 %0\n"), "the save answers a value: {text}");
6216 assert!(text.contains(" longjmp_marker %0\n"), "the restore answers nothing: {text}");
6217 assert!(!text.contains("call @"), "neither of them is a call: {text}");
6218 }
6219
6220 #[test]
6228 fn a_local_of_a_function_that_saves_a_place_gets_a_slot() {
6229 let text = ir(concat!(
6230 "void *buf[5];\n",
6231 "int f(int x) { int a = 0; if (__builtin_setjmp(buf)) return a; a = 1; return x; }\n",
6232 "int g(int x) { int a = 0; if (x) return a; a = 1; return x; }\n",
6233 ));
6234 let (saves, plain) = text.split_once("func @g").expect("both functions");
6235 assert_eq!(saves.matches("= alloca").count(), 2, "the parameter and the local: {text}");
6236 assert!(saves.contains("store %9 -> %2"), "the local is written through: {text}");
6237 assert!(!plain.contains("alloca"), "nothing in the plain one needs a slot: {text}");
6238 }
6239
6240 #[test]
6249 fn the_save_writes_four_words_and_carries_on_in_a_new_block() {
6250 let text =
6251 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
6252 let body = text.split_once("\nf:\n").expect("the function").1;
6253 assert!(body.contains("\tmovq\t%rsp, %rbp\n"), "a frame pointer whatever: {text}");
6254 assert!(body.contains("\tsubq\t$8, %rsp\n"), "no red zone: {text}");
6255 assert!(body.contains("\tmovq\t%rbp, (%rax)\n"), "the frame pointer: {text}");
6256 assert!(body.contains("\tmovq\t%rsp, 16(%rax)\n"), "the stack pointer: {text}");
6257 assert!(body.contains("\tleaq\t.Lf_1(%rip), %rcx\n"), "where to come back to: {text}");
6258 assert!(body.contains("\tmovq\t%rcx, 8(%rax)\n"), "and that goes in the buffer: {text}");
6259 let back = body.split_once(".Lf_1:\n").expect("the block control comes back to").1;
6260 assert!(back.starts_with("\tmovq\t(%rsp), %rax\n"), "the answer is read back: {text}");
6261 }
6262
6263 #[test]
6271 fn a_save_destroys_every_register_the_allocator_hands_out() {
6272 let text =
6273 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
6274 for reg in ["%rbx", "%r12", "%r13", "%r14", "%r15"] {
6275 assert!(text.contains(&format!("\tpushq\t{reg}\n")), "{reg} is saved: {text}");
6276 assert!(text.contains(&format!("\tpopq\t{reg}\n")), "{reg} is restored: {text}");
6277 }
6278 }
6279
6280 #[test]
6287 fn the_restore_puts_the_frame_back_before_it_jumps() {
6288 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
6289 let mut opts = options();
6290 opts.emit = EmitKind::Asm;
6291 opts.opt_level = level;
6292 let source = "void *buf[5];\nvoid g(void) { __builtin_longjmp(buf, 1); }\n";
6293 let result = run(&opts, source);
6294 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
6295 let text = result.text().to_owned();
6296 let jump = text.find("\tjmp\t*%").unwrap_or_else(|| panic!("an indirect jump: {text}"));
6297 let stack = text.find(", %rsp\n").unwrap_or_else(|| panic!("the stack back: {text}"));
6298 let frame = text.find(", %rbp\n").unwrap_or_else(|| panic!("the frame back: {text}"));
6299 assert!(stack < jump, "the stack goes back first at {level:?}: {text}");
6300 assert!(frame < jump, "and so does the frame at {level:?}: {text}");
6301 }
6302 }
6303
6304 #[test]
6310 fn a_longjmp_whose_second_argument_is_not_one_is_turned_down() {
6311 for source in [
6312 "void *buf[5];\nvoid f(void) { __builtin_longjmp(buf, 0); }\n",
6313 "void *buf[5];\nextern int v;\nvoid f(void) { __builtin_longjmp(buf, v); }\n",
6314 ] {
6315 let messages = errors(source);
6316 let named = messages.iter().any(|m| m.contains("E0710"));
6317 assert!(named, "expected a complaint about the value in {messages:?}");
6318 }
6319 }
6320
6321 #[test]
6326 fn a_static_function_nothing_refers_to_is_not_emitted() {
6327 let text = ir("static int dropped(void) { return 1; }\n\
6328 static int kept(void) { return 2; }\n\
6329 int main(void) { return kept(); }\n");
6330 assert!(text.contains("func @kept"), "{text}");
6331 assert!(!text.contains("dropped"), "{text}");
6332 }
6333
6334 #[test]
6340 fn two_static_functions_that_only_call_each_other_are_both_dropped() {
6341 let text = ir("static int ping(void);\n\
6342 static int pong(void) { return ping(); }\n\
6343 static int ping(void) { return pong(); }\n\
6344 int main(void) { return 0; }\n");
6345 assert!(!text.contains("ping"), "{text}");
6346 assert!(!text.contains("pong"), "{text}");
6347 }
6348
6349 #[test]
6355 fn naming_a_static_function_anywhere_keeps_it() {
6356 let text = ir("static int by_address(void) { return 1; }\n\
6357 static int in_an_image(void) { return 2; }\n\
6358 static int deeper(void) { return 3; }\n\
6359 static int reaches_deeper(void) { return deeper(); }\n\
6360 static int (*table[1])(void) = {in_an_image};\n\
6361 int main(void) {\n\
6362 int (*p)(void) = by_address;\n\
6363 return p() + table[0]() + reaches_deeper();\n\
6364 }\n");
6365 for kept in ["by_address", "in_an_image", "deeper", "reaches_deeper"] {
6366 assert!(text.contains(&format!("func @{kept}")), "expected {kept} in:\n{text}");
6367 }
6368 }
6369
6370 #[test]
6376 fn an_attribute_keeps_a_static_function_nothing_refers_to() {
6377 for attribute in ["used", "retain", "constructor", "destructor", "__used__"] {
6378 let source = format!(
6379 "__attribute__(({attribute})) static int kept(void) {{ return 1; }}\n\
6380 int main(void) {{ return 0; }}\n"
6381 );
6382 let text = ir(&source);
6383 assert!(text.contains("func @kept"), "for {attribute}:\n{text}");
6384 }
6385 }
6386
6387 #[test]
6390 fn a_function_anything_could_call_is_emitted_without_being_called() {
6391 let text =
6392 ir("int nobody_here_calls_it(void) { return 1; }\nint main(void) { return 0; }\n");
6393 assert!(text.contains("func @nobody_here_calls_it"), "{text}");
6394 }
6395
6396 #[test]
6403 fn a_classification_c_has_an_operator_for_is_that_operator() {
6404 for (builtin, operator) in [
6405 ("__builtin_isgreater", "binary >"),
6406 ("__builtin_isgreaterequal", "binary >="),
6407 ("__builtin_isless", "binary <"),
6408 ("__builtin_islessequal", "binary <="),
6409 ] {
6410 let source = format!("int f(double x, double y) {{ return {builtin}(x, y); }}\n");
6411 let text = tast(&source);
6412 assert!(text.contains(&format!("{operator} : int")), "for {builtin}:\n{text}");
6413 }
6414 }
6415
6416 #[test]
6425 fn the_classification_builtins_are_comparisons_and_not_calls() {
6426 let text = body("int f(double x, double y) { return __builtin_isunordered(x, y); }\n");
6427 assert_eq!(
6428 text,
6429 "block0(%0: f64, %1: f64):\n %2 = fcmp uno %0, %1\n %3 = zext.i32 \
6430 %2\n return %3\n"
6431 );
6432
6433 let text = body("int f(double x, double y) { return __builtin_islessgreater(x, y); }\n");
6435 assert!(text.contains("fcmp one %0, %1"), "{text}");
6436
6437 let text = body("int f(double x) { return __builtin_isnan(x); }\n");
6438 assert!(text.contains("fcmp uno %0, %0"), "{text}");
6439
6440 let text = body("int f(double x) { return __builtin_isinf(x); }\n");
6441 assert!(text.contains("fconst.f64 0x7ff0000000000000"), "{text}");
6442 assert!(text.contains("fconst.f64 0xfff0000000000000"), "{text}");
6443 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
6444 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
6445 assert!(text.contains("%5 = or %3, %4"), "{text}");
6446
6447 let text = body("int f(double x) { return __builtin_isfinite(x); }\n");
6450 assert!(text.contains("%3 = fcmp olt %2, %0"), "{text}");
6451 assert!(text.contains("%4 = fcmp olt %0, %1"), "{text}");
6452 assert!(text.contains("%5 = and %3, %4"), "{text}");
6453
6454 let text = body("int f(double x) { return __builtin_signbit(x); }\n");
6455 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
6456 assert!(text.contains("icmp slt %1, %2"), "{text}");
6457
6458 let text = body("int f(long double x) { return __builtin_signbitl(x); }\n");
6461 assert!(text.contains("%1 = bitcast.i80 %0"), "{text}");
6462
6463 let text = body("double g(void);\nint f(void) { return __builtin_isnan(g()); }\n");
6466 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
6467 }
6468
6469 #[test]
6476 fn a_classification_spelling_that_names_a_width_converts_before_it_asks() {
6477 let text = ir(concat!(
6478 "int a = __builtin_isinff(1e300);\n",
6479 "int b = __builtin_isinf(1e300);\n",
6480 "int c = __builtin_isnan(0.0);\n",
6484 "int d = __builtin_signbit(-0.0);\n",
6485 "int e = __builtin_islessgreater(1.0, 2.0);\n",
6486 ));
6487 assert!(text.contains("global @a : i32 = 1,"), "{text}");
6488 assert!(text.contains("global @b : i32 = 0,"), "{text}");
6489 assert!(text.contains("global @c : i32 = 0,"), "{text}");
6490 assert!(text.contains("global @d : i32 = 1,"), "{text}");
6491 assert!(text.contains("global @e : i32 = 1,"), "{text}");
6492 }
6493
6494 #[test]
6496 fn a_classification_builtin_refuses_an_argument_that_is_not_floating_point() {
6497 let mut opts = options();
6498 opts.emit = EmitKind::Ir;
6499 let source = concat!(
6500 "int a(int x) { return __builtin_isnan(x); }\n",
6501 "int b(int x, int y) { return __builtin_isunordered(x, y); }\n",
6502 "int c(double x) { return __builtin_isnan(x, x); }\n",
6503 );
6504 let messages = run(&opts, source).messages;
6505 assert_eq!(
6506 messages,
6507 [
6508 "/main.c:1:23: error: non-floating-point argument in call to function \
6509 '__builtin_isnan' [E0685]",
6510 "/main.c:2:30: error: non-floating-point arguments in call to function \
6511 '__builtin_isunordered' [E0685]",
6512 "/main.c:3:26: error: too many arguments to function '__builtin_isnan' [E0511]",
6513 ]
6514 );
6515 }
6516
6517 #[test]
6526 fn the_last_three_classification_builtins_are_comparisons_and_not_calls() {
6527 let text = body("int f(double x) { return __builtin_isnormal(x); }\n");
6528 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
6532 assert!(text.contains("%2 = iconst.i64 9223372036854775807"), "{text}");
6533 assert!(text.contains("%3 = and %1, %2"), "{text}");
6534 assert!(text.contains("%4 = iconst.i64 4503599627370496"), "{text}");
6535 assert!(text.contains("%5 = iconst.i64 9218868437227405312"), "{text}");
6536 assert!(text.contains("%6 = icmp uge %3, %4"), "{text}");
6537 assert!(text.contains("%7 = icmp ult %3, %5"), "{text}");
6538 assert!(text.contains("%8 = and %6, %7"), "{text}");
6539
6540 let text = body("int f(long double x) { return __builtin_isnormal(x); }\n");
6544 assert!(text.contains("%4 = iconst.i80 27670116110564327424"), "{text}");
6545 assert!(text.contains("%5 = iconst.i80 604453686435277732577280"), "{text}");
6546
6547 let text = body("int f(double x) { return __builtin_isinf_sign(x); }\n");
6548 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
6549 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
6550 assert!(text.contains("%7 = sub %5, %6"), "{text}");
6551
6552 let text = body("int f(double x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n");
6553 assert!(text.contains("fcmp uno %0, %0"), "{text}");
6554 assert!(text.contains("fcmp oeq %0, %6"), "{text}");
6555 assert_eq!(text.matches(" = zext.i32 ").count(), 4, "{text}");
6559 assert_eq!(text.matches(" = xor ").count(), 4, "{text}");
6560 assert!(!text.contains("call"), "{text}");
6561
6562 let text = body(concat!(
6565 "double g(void);\n",
6566 "int f(void) { return __builtin_fpclassify(0, 1, 2, 3, 4, g()); }\n",
6567 ));
6568 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
6569 }
6570
6571 #[test]
6578 fn the_last_three_classification_builtins_fold_where_their_operand_is_a_constant() {
6579 let text = ir(concat!(
6580 "int a = __builtin_isnormal(1.0);\n",
6581 "int b = __builtin_isnormal(0.0);\n",
6582 "int c = __builtin_isnormal(1.0 / 0.0);\n",
6583 "int d = __builtin_isinf_sign(-1.0 / 0.0);\n",
6584 "int e = __builtin_isinf_sign(1.0);\n",
6585 "int g = __builtin_fpclassify(0, 1, 2, 3, 4, 0.0);\n",
6586 "int h = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0);\n",
6587 "int i = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0 / 0.0);\n",
6588 ));
6589 assert!(text.contains("global @a : i32 = 1,"), "{text}");
6590 assert!(text.contains("global @b : i32 = 0,"), "{text}");
6591 assert!(text.contains("global @c : i32 = 0,"), "{text}");
6592 assert!(text.contains("global @d : i32 = -1,"), "{text}");
6593 assert!(text.contains("global @e : i32 = 0,"), "{text}");
6594 assert!(text.contains("global @g : i32 = 4,"), "{text}");
6595 assert!(text.contains("global @h : i32 = 2,"), "{text}");
6596 assert!(text.contains("global @i : i32 = 1,"), "{text}");
6597 }
6598
6599 #[test]
6605 fn fpclassify_refuses_an_answer_that_is_not_an_integer_constant() {
6606 let mut opts = options();
6607 opts.emit = EmitKind::Ir;
6608 let source = concat!(
6609 "int a(double x, int n) { return __builtin_fpclassify(0, 1, n, 3, 4, x); }\n",
6610 "int b(double x) { return __builtin_fpclassify(0, 1, 2, 3, x); }\n",
6611 "int c(int x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n",
6612 );
6613 let messages = run(&opts, source).messages;
6614 assert_eq!(
6615 messages,
6616 [
6617 "/main.c:1:60: error: non-const integer argument 3 in call to function \
6618 '__builtin_fpclassify' [E0687]",
6619 "/main.c:2:26: error: too few arguments to function '__builtin_fpclassify' \
6620 [E0511]",
6621 "/main.c:3:23: error: non-floating-point argument in call to function \
6622 '__builtin_fpclassify' [E0685]",
6623 ]
6624 );
6625 }
6626
6627 #[test]
6635 fn a_builtin_whose_answer_is_a_constant_is_one_and_not_a_call() {
6636 let text = ir(concat!(
6637 "double a = __builtin_inf();\n",
6638 "float b = __builtin_huge_valf();\n",
6639 "long double c = __builtin_infl();\n",
6640 "double d = __builtin_huge_val();\n",
6641 ));
6642 assert!(text.contains("global @a : f64 = 0x7ff0000000000000,"), "{text}");
6643 assert!(text.contains("global @b : f32 = 0x7f800000,"), "{text}");
6644 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
6645 assert!(text.contains("global @d : f64 = 0x7ff0000000000000,"), "{text}");
6646 assert!(!text.contains("call"), "{text}");
6647 }
6648
6649 #[test]
6658 fn a_nan_is_written_with_the_payload_the_program_asked_for() {
6659 let text = ir(concat!(
6660 "double a = __builtin_nan(\"\");\n",
6661 "double b = __builtin_nan(\"0x1\");\n",
6662 "double c = __builtin_nan(\"010\");\n",
6664 "double d = __builtin_nans(\"\");\n",
6665 "double e = __builtin_nans(\"0x1\");\n",
6666 "float f = __builtin_nanf(\"0x1\");\n",
6667 "float g = __builtin_nansf(\"\");\n",
6668 "long double h = __builtin_nansl(\"\");\n",
6669 ));
6670 assert!(text.contains("global @a : f64 = 0x7ff8000000000000,"), "{text}");
6671 assert!(text.contains("global @b : f64 = 0x7ff8000000000001,"), "{text}");
6672 assert!(text.contains("global @c : f64 = 0x7ff8000000000008,"), "{text}");
6673 assert!(text.contains("global @d : f64 = 0x7ff4000000000000,"), "{text}");
6674 assert!(text.contains("global @e : f64 = 0x7ff0000000000001,"), "{text}");
6675 assert!(text.contains("global @f : f32 = 0x7fc00001,"), "{text}");
6676 assert!(text.contains("global @g : f32 = 0x7fa00000,"), "{text}");
6677 assert!(text.contains("f80 0x7fffa000000000000000"), "{text}");
6678
6679 let text = ir(concat!(
6682 "double f(const char *p) { return __builtin_nan(p); }\n",
6683 "double g(void) { return __builtin_nans(\"1x\"); }\n",
6684 ));
6685 assert_eq!(text.matches("call @nan(").count(), 1, "{text}");
6686 assert_eq!(text.matches("call @nans(").count(), 1, "{text}");
6687 }
6688
6689 #[test]
6697 fn the_length_and_the_order_of_a_string_literal_are_known_here() {
6698 let text = ir(concat!(
6699 "unsigned long a = __builtin_strlen(\"hello\");\n",
6700 "unsigned long b = __builtin_strlen(\"a\\0bc\");\n",
6701 "int c = __builtin_strcmp(\"X\", \"X\\376\") < 0;\n",
6702 "int d = __builtin_strcmp(\"abc\", \"abc\");\n",
6703 "int e = __builtin_strcmp(\"abc\", \"ab\") > 0;\n",
6704 ));
6705 assert!(text.contains("global @a : i64 = 5,"), "{text}");
6706 assert!(text.contains("global @b : i64 = 1,"), "{text}");
6707 assert!(text.contains("global @c : i32 = 1,"), "{text}");
6708 assert!(text.contains("global @d : i32 = 0,"), "{text}");
6709 assert!(text.contains("global @e : i32 = 1,"), "{text}");
6710 assert!(!text.contains("call"), "{text}");
6711
6712 let text = ir("unsigned long f(const char *p) { return __builtin_strlen(p); }\n");
6714 assert!(text.contains("call @strlen("), "{text}");
6715 }
6716
6717 #[test]
6724 fn a_sign_builtin_is_a_mask_over_the_bits_and_not_a_call() {
6725 let text = body("double f(double x) { return __builtin_fabs(x); }\n");
6726 assert!(text.contains("bitcast.i64 %0"), "{text}");
6727 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
6728 assert!(text.contains("and %1, %2"), "{text}");
6729 assert!(text.contains("bitcast.f64 %3"), "{text}");
6730 assert!(!text.contains("call"), "{text}");
6731
6732 let text = body("double f(double x, double y) { return __builtin_copysign(x, y); }\n");
6733 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
6734 assert!(text.contains("%8 = or %4, %7"), "{text}");
6735 assert!(!text.contains("call"), "{text}");
6736
6737 let text = body("long double f(long double x) { return __builtin_fabsl(x); }\n");
6740 assert!(text.contains("bitcast.i80 %0"), "{text}");
6741 assert!(text.contains("bitcast.f80"), "{text}");
6742
6743 let text = body("double f(float x) { return __builtin_fabs(x); }\n");
6746 assert!(text.contains("fpext.f64 %0"), "{text}");
6747 assert!(text.contains("bitcast.i64 %1"), "{text}");
6748 }
6749
6750 #[test]
6759 fn the_plain_math_names_are_the_same_mask_and_not_a_call() {
6760 let text =
6761 body(concat!("double fabs(double x);\n", "double f(double x) { return fabs(x); }\n",));
6762 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
6763 assert!(!text.contains("call"), "{text}");
6764
6765 let text =
6766 body(concat!("float fabsf(float x);\n", "float f(float x) { return fabsf(x); }\n",));
6767 assert!(text.contains("bitcast.i32 %0"), "{text}");
6768 assert!(!text.contains("call"), "{text}");
6769
6770 let text = body(concat!(
6771 "double copysign(double x, double y);\n",
6772 "double f(double x, double y) { return copysign(x, y); }\n",
6773 ));
6774 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
6775 assert!(!text.contains("call"), "{text}");
6776
6777 let text = body(concat!(
6778 "float copysignf(float x, float y);\n",
6779 "float f(float x, float y) { return copysignf(x, y); }\n",
6780 ));
6781 assert!(!text.contains("call"), "{text}");
6782
6783 let text = ir(concat!(
6787 "long double fabsl(long double x);\n",
6788 "long double f(long double x) { return fabsl(x); }\n",
6789 ));
6790 assert!(text.contains("call @fabsl"), "{text}");
6791 }
6792
6793 #[test]
6801 fn a_plain_math_name_the_program_took_is_the_programs_own_function() {
6802 let taken = concat!(
6803 "static double fabs(double b) { return 7; }\n",
6804 "double f(double x) { return fabs(x); }\n",
6805 );
6806 assert!(ir(taken).contains("call @fabs"), "a static definition is the program's own");
6807
6808 let retyped = concat!("int fabs(int b);\n", "int f(int x) { return fabs(x); }\n");
6809 assert!(ir(retyped).contains("call @fabs"), "another type is another function");
6810
6811 let plain = concat!("double fabs(double b);\n", "double f(double x) { return fabs(x); }\n");
6812 let mut opts = options();
6813 opts.emit = EmitKind::Ir;
6814 assert!(!run(&opts, plain).text().contains("call @fabs"), "the library's by default");
6815
6816 opts.builtins = false;
6817 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin");
6818
6819 opts.builtins = true;
6820 opts.no_builtin = vec!["fabs".to_owned()];
6821 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin-fabs");
6822 let one = concat!(
6823 "double copysign(double a, double b);\n",
6824 "double f(double x) { return copysign(x, 1.0); }\n",
6825 );
6826 assert!(!run(&opts, one).text().contains("call @copysign"), "one name and not the family");
6827
6828 opts.no_builtin = Vec::new();
6830 opts.builtins = false;
6831 let prefixed = "double f(double x) { return __builtin_fabs(x); }\n";
6832 assert!(!run(&opts, prefixed).text().contains("call @fabs"), "the prefix is not a library");
6833 }
6834
6835 #[test]
6844 fn the_sign_builtins_answer_a_zero_and_a_nan_the_way_the_bits_say() {
6845 let text = ir(concat!(
6846 "double a = __builtin_fabs(-3.5);\n",
6847 "double b = __builtin_copysign(1.0, -0.0);\n",
6848 "double c = __builtin_copysign(0.0, -2.0);\n",
6849 "double d = __builtin_copysign(-__builtin_nan(\"\"), 1.0);\n",
6851 "double e = __builtin_fabs(-__builtin_nan(\"0x1\"));\n",
6852 "float g = __builtin_copysignf(-0.0f, 2.0f);\n",
6853 "long double h = __builtin_copysignl(1.0L, -1.0L);\n",
6854 "long double i = __builtin_fabsl(-__builtin_infl());\n",
6855 ));
6856 assert!(text.contains("global @a : f64 = 0x400c000000000000,"), "{text}");
6857 assert!(text.contains("global @b : f64 = 0xbff0000000000000,"), "{text}");
6858 assert!(text.contains("global @c : f64 = 0x8000000000000000,"), "{text}");
6859 assert!(text.contains("global @d : f64 = 0x7ff8000000000000,"), "{text}");
6860 assert!(text.contains("global @e : f64 = 0x7ff8000000000001,"), "{text}");
6861 assert!(text.contains("global @g : f32 = 0x0,"), "{text}");
6862 assert!(text.contains("f80 0xbfff8000000000000000"), "{text}");
6863 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
6864 }
6865
6866 #[test]
6874 fn the_complex_builtins_are_the_halves_of_the_value_and_not_a_call() {
6875 let text = body("double f(_Complex double z) { return __builtin_creal(z); }\n");
6876 assert!(!text.contains("call"), "{text}");
6877 let text = body("double f(_Complex double z) { return __builtin_cimag(z); }\n");
6878 assert!(!text.contains("call"), "{text}");
6879
6880 let text = body("_Complex double f(_Complex double z) { return __builtin_conj(z); }\n");
6883 assert_eq!(text.matches("fneg").count(), 1, "{text}");
6884 assert!(!text.contains("call"), "{text}");
6885 let negated = body("_Complex double f(_Complex double z) { return -z; }\n");
6886 assert_eq!(negated.matches("fneg").count(), 2, "{negated}");
6887
6888 let written = body("_Complex double f(_Complex double z) { return ~z; }\n");
6891 assert_eq!(written, text, "the name and the operator are the same thing");
6892
6893 let text = body(concat!(
6895 "double creal(_Complex double z);\n",
6896 "double f(_Complex double z) { return creal(z); }\n",
6897 ));
6898 assert!(!text.contains("call"), "{text}");
6899 let text = body(concat!(
6900 "_Complex float conjf(_Complex float z);\n",
6901 "_Complex float f(_Complex float z) { return conjf(z); }\n",
6902 ));
6903 assert_eq!(text.matches("fneg").count(), 1, "{text}");
6904 assert!(!text.contains("call"), "{text}");
6905
6906 let taken = concat!(
6909 "static double creal(_Complex double z) { return 7; }\n",
6910 "double f(_Complex double z) { return creal(z); }\n",
6911 );
6912 assert!(ir(taken).contains("call @creal"), "a static definition is the program's own");
6913 let retyped = concat!("int cimag(int z);\n", "int f(int z) { return cimag(z); }\n");
6914 assert!(ir(retyped).contains("call @cimag"), "another type is another function");
6915 let plain = concat!(
6916 "double cimag(_Complex double z);\n",
6917 "double f(_Complex double z) { return cimag(z); }\n",
6918 );
6919 let mut opts = options();
6920 opts.emit = EmitKind::Ir;
6921 opts.builtins = false;
6922 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin");
6923 opts.builtins = true;
6924 opts.no_builtin = vec!["cimag".to_owned()];
6925 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin-cimag");
6926
6927 let text = ir(concat!(
6929 "double a = __builtin_creal(1.5 + 2.5i);\n",
6930 "double b = __builtin_cimag(1.5 + 2.5i);\n",
6931 "_Complex double c = __builtin_conj(1.5 + 2.5i);\n",
6932 ));
6933 assert!(text.contains("global @a : f64 = 0x3ff8000000000000,"), "{text}");
6934 assert!(text.contains("global @b : f64 = 0x4004000000000000,"), "{text}");
6935 assert!(
6936 text.contains("{ f64 0x3ff8000000000000, f64 0xc004000000000000 }"),
6937 "the conjugate of a constant is the constant with the second half negated: {text}"
6938 );
6939 assert!(!text.contains("call"), "{text}");
6940 }
6941
6942 #[test]
6950 fn a_math_library_builtin_of_a_constant_is_the_answer_and_not_a_call() {
6951 let text = ir(concat!(
6952 "double a = __builtin_ceil(1.5);\n",
6953 "double b = __builtin_floor(1.5);\n",
6954 "double c = __builtin_trunc(-1.5);\n",
6955 "double d = __builtin_round(2.5);\n",
6958 "double e = __builtin_ceil(-0.5);\n",
6960 "double f = __builtin_fmax(1.0, 2.0);\n",
6961 "double g = __builtin_fmin(1.0, 2.0);\n",
6962 "float h = __builtin_ceilf(1.25f);\n",
6963 "double ceil(double x);\n",
6966 "double i = ceil(2.25);\n",
6967 ));
6968 assert!(text.contains("global @a : f64 = 0x4000000000000000,"), "{text}");
6969 assert!(text.contains("global @b : f64 = 0x3ff0000000000000,"), "{text}");
6970 assert!(text.contains("global @c : f64 = 0xbff0000000000000,"), "{text}");
6971 assert!(text.contains("global @d : f64 = 0x4008000000000000,"), "{text}");
6972 assert!(text.contains("global @e : f64 = 0x8000000000000000,"), "{text}");
6973 assert!(text.contains("global @f : f64 = 0x4000000000000000,"), "{text}");
6974 assert!(text.contains("global @g : f64 = 0x3ff0000000000000,"), "{text}");
6975 assert!(text.contains("global @h : f32 = 0x40000000,"), "{text}");
6976 assert!(text.contains("global @i : f64 = 0x4008000000000000,"), "{text}");
6977 assert!(!text.contains("call"), "{text}");
6978 }
6979
6980 #[test]
6988 fn a_math_library_builtin_of_anything_else_is_a_call_to_the_library() {
6989 let text = ir(concat!(
6990 "double f(double x) { return __builtin_ceil(x); }\n",
6991 "float g(float x) { return __builtin_floorf(x); }\n",
6992 "double h(double x, double y) { return __builtin_fmax(x, y); }\n",
6993 ));
6994 assert!(text.contains("call @ceil("), "{text}");
6995 assert!(text.contains("call @floorf("), "{text}");
6996 assert!(text.contains("call @fmax("), "{text}");
6997
6998 let text = ir(concat!(
7002 "double f(void) { return __builtin_rint(2.5); }\n",
7003 "double g(void) { return __builtin_nearbyint(2.5); }\n",
7004 ));
7005 assert!(text.contains("call @rint("), "{text}");
7006 assert!(text.contains("call @nearbyint("), "{text}");
7007
7008 let text = ir("double f(void) { return __builtin_fmin(__builtin_nan(\"\"), 1.0); }\n");
7011 assert!(text.contains("call @fmin("), "{text}");
7012
7013 let plain = concat!("double ceil(double x);\n", "double f(void) { return ceil(2.25); }\n");
7016 let mut opts = options();
7017 opts.emit = EmitKind::Ir;
7018 opts.no_builtin = vec!["ceil".to_owned()];
7019 assert!(run(&opts, plain).text().contains("call @ceil("), "-fno-builtin-ceil");
7020 }
7021
7022 #[test]
7029 fn a_constexpr_object_is_a_constant_wherever_one_is_required() {
7030 let text = ir(concat!(
7031 "constexpr int side = 4;\n",
7032 "constexpr int wider = side + 1;\n",
7033 "constexpr double half = 1.5;\n",
7034 "struct point { int x; int y; };\n",
7035 "constexpr struct point origin = { 5, 6 };\n",
7036 "int square[side * side];\n",
7037 "int rectangle[wider];\n",
7038 "int rounded[(int)half * 2];\n",
7039 "int across[origin.y];\n",
7040 "enum named { four = side };\n",
7041 "int e = four;\n",
7042 ));
7043 assert!(text.contains("global @square : bytes 64 ="), "{text}");
7044 assert!(text.contains("global @rectangle : bytes 20 ="), "{text}");
7045 assert!(text.contains("global @rounded : bytes 8 ="), "{text}");
7046 assert!(text.contains("global @across : bytes 24 ="), "{text}");
7047 assert!(text.contains("global @e : i32 = 4,"), "{text}");
7048
7049 let mut opts = options();
7052 opts.emit = EmitKind::Ir;
7053 let konst = "const int n = 1;\nint a[n];\n";
7054 let message = "/main.c:2:5: error: variably modified 'a' at file scope [E0538]";
7055 assert_eq!(run(&opts, konst).messages, [message]);
7056
7057 let subscript = "constexpr int t[3] = { 1, 2, 3 };\nint a[t[1]];\n";
7059 assert_eq!(run(&opts, subscript).messages, [message]);
7060
7061 let address = "constexpr int c = 3;\nint *p = &c;\n";
7063 let warning = "/main.c:2:6: warning: initialization discards 'const' qualifier from \
7064 pointer target type [E0514]";
7065 assert_eq!(run(&opts, address).messages, [warning]);
7066 }
7067
7068 #[test]
7077 fn a_member_whose_size_was_refused_is_not_a_flexible_array_member() {
7078 let mut opts = options();
7079 opts.emit = EmitKind::Ir;
7080
7081 let alone = "int k;\nextern struct D { int a[k]; } ed;\n";
7082 let message = "/main.c:2:23: error: variably modified 'a' at file scope [E0538]";
7083 assert_eq!(run(&opts, alone).messages, [message]);
7084
7085 let first = "int k;\nextern struct E { int a[k]; int b; } ee;\n";
7087 assert_eq!(run(&opts, first).messages, [message]);
7088
7089 let negative = "struct F { int a[-1]; };\n";
7092 let refused = "/main.c:1:18: error: size of array 'a' is negative [E0536]";
7093 assert_eq!(run(&opts, negative).messages, [refused]);
7094
7095 let flexible = "struct G { int a[]; };\n";
7098 let named = "/main.c:1:16: error: flexible array member in a struct with no named \
7099 members [E0554]";
7100 assert_eq!(run(&opts, flexible).messages, [named]);
7101 }
7102
7103 #[test]
7118 fn a_pointer_to_an_array_gains_a_qualifier_the_same_way_a_pointer_to_anything_else_does() {
7119 let mut opts = options();
7120 opts.emit = EmitKind::Ir;
7121 let prefix = "typedef unsigned int B[4];\nstruct H { B category[2]; };\n";
7122
7123 let adding = format!("{prefix}const B *f(struct H *h) {{ return &h->category[0]; }}\n");
7125 assert_eq!(run(&opts, &adding).messages, [] as [String; 0]);
7126
7127 let plain = concat!(
7130 "const unsigned int (*f(unsigned int (*p)[4]))[4] { return p; }\n",
7131 "const unsigned int (*g(unsigned int (*p)[2][3]))[2][3] { return p; }\n",
7132 );
7133 assert_eq!(run(&opts, plain).messages, [] as [String; 0]);
7134
7135 let dropping = format!("{prefix}B *f(const B *p) {{ return p; }}\n");
7138 let warning = "/main.c:3:27: warning: return discards 'const' qualifier from pointer target type \
7139 [E0514]";
7140 assert_eq!(run(&opts, &dropping).messages, [warning]);
7141
7142 let wrong = "const unsigned int (*f(unsigned short (*p)[4]))[4] { return p; }\n";
7145 let error = "/main.c:1:61: error: returning 'unsigned short (*)[4]' from a function with \
7146 incompatible return type 'const unsigned int (*)[4]' [E0512]";
7147 assert_eq!(run(&opts, wrong).messages, [error]);
7148 }
7149
7150 #[test]
7159 fn an_old_style_definition_takes_its_types_from_the_declarations_under_its_list() {
7160 let mut opts = options();
7163 opts.std = Std::C17;
7164 let source = concat!(
7165 "int add(a, b)\n",
7166 "int a;\n",
7167 "int b;\n",
7168 "{ return a + b; }\n",
7169 "int promoted(c)\n",
7170 "char c;\n",
7171 "{ return c; }\n",
7172 "int narrow(char);\n",
7173 "int narrow(c)\n",
7174 "char c;\n",
7175 "{ return c; }\n",
7176 "int first(a)\n",
7177 "int a[4];\n",
7178 "{ return a[0]; }\n",
7179 );
7180 let result = run(&opts, source);
7181 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
7182 let text = result.text();
7183 assert!(text.contains("add : int(int, int) function external defined"), "{text}");
7184 assert!(text.contains("promoted : int(int) function external defined"), "{text}");
7185 assert!(text.contains("c : char object automatic defined"), "{text}");
7187 assert!(text.contains("narrow : int(char) function external defined"), "{text}");
7188 assert!(text.contains("first : int(int *) function external defined"), "{text}");
7190 }
7191
7192 #[test]
7199 fn the_two_halves_of_an_old_style_parameter_list_have_to_agree() {
7200 let mut opts = options();
7201 opts.std = Std::C17;
7202 for (source, message) in [
7203 ("int f(a, a)\nint a;\n{ return a; }\n", "1:10: error: multiple parameters named 'a'"),
7204 (
7205 "int f(a)\nint a;\nint b;\n{ return a; }\n",
7206 "3:5: error: declaration for parameter 'b' but no such parameter",
7207 ),
7208 ("int f(a)\nint a;\nint a;\n{ return a; }\n", "3:5: error: redefinition of parameter"),
7209 ("int f(a)\nint a = 1;\n{ return a; }\n", "2:5: error: parameter 'a' is initialized"),
7210 (
7211 "int f(a)\nstatic int a;\n{ return a; }\n",
7212 "2:12: error: storage class specified for parameter 'a'",
7213 ),
7214 (
7215 "int f(char);\nint f(a)\nshort a;\n{ return a; }\n",
7216 "2:7: error: argument 'a' doesn't match prototype",
7217 ),
7218 ] {
7219 let result = run(&opts, source);
7220 assert!(result.failed(), "expected this to fail:\n{source}");
7221 assert!(result.messages[0].contains(message), "{:?}", result.messages);
7222 }
7223
7224 let implicit = "int f(a, b)\nint a;\n{ return a + b; }\n";
7227 let mut older = options();
7228 older.std = Std::C89;
7229 assert!(!run(&older, implicit).failed(), "{:?}", run(&older, implicit).messages);
7230 let result = run(&opts, implicit);
7231 assert!(
7232 result.messages[0].contains("1:10: error: type of 'b' defaults to 'int'"),
7233 "{:?}",
7234 result.messages
7235 );
7236
7237 let mut newer = options();
7241 newer.std = Std::C23;
7242 let plain = "int f(a)\nint a;\n{ return a; }\n";
7243 let result = run(&newer, plain);
7244 assert!(!result.failed(), "{:?}", result.messages);
7245 assert_eq!(
7246 result.messages,
7247 ["/main.c:1:5: warning: old-style function definition [E0412]"]
7248 );
7249 assert!(run(&opts, plain).messages.is_empty(), "and nothing to say in the dialects before");
7250 }
7251
7252 #[test]
7259 fn the_obsolete_designators_are_taken_and_are_pedantic_warnings() {
7260 let array = "int a[8] = { [3] 7 };\n";
7261 let member = "struct s { int x; } v = { x: 7 };\n";
7262 for source in [array, member] {
7263 let result = run(&options(), source);
7264 assert!(!result.failed(), "{:?}", result.messages);
7265 assert!(result.messages.is_empty(), "nothing to say: {:?}", result.messages);
7266 }
7267
7268 let mut asked = options();
7269 asked.pedantic = true;
7270 assert_eq!(
7271 run(&asked, array).messages,
7272 ["/main.c:1:18: warning: obsolete designator, write `[i] =` instead [E0415]"]
7273 );
7274 assert_eq!(
7275 run(&asked, member).messages,
7276 ["/main.c:1:27: warning: obsolete designator, write `.field =` instead [E0413]"]
7277 );
7278 }
7279
7280 #[test]
7287 fn a_type_is_refused_when_it_passes_the_largest_object_and_not_before() {
7288 let text = ir(concat!(
7289 "struct huge_struct { short buf[(1L << 62) - 256]; int a, b, c, d; };\n",
7290 "struct brim { char buf[9223372036854775807L]; };\n",
7291 "struct bitty { char buf[9223372036854775800L]; int x : 1; };\n",
7292 "unsigned long h = sizeof(struct huge_struct);\n",
7293 "unsigned long b = sizeof(struct brim);\n",
7294 "unsigned long y = sizeof(struct bitty);\n",
7295 ));
7296 assert!(text.contains("global @h : i64 = 9223372036854775312,"), "{text}");
7297 assert!(text.contains("global @b : i64 = 9223372036854775807,"), "{text}");
7298 assert!(text.contains("global @y : i64 = 9223372036854775804,"), "{text}");
7299
7300 let mut opts = options();
7301 opts.emit = EmitKind::Ir;
7302 let over = "struct over { char buf[9223372036854775800L]; char x[8]; };\n";
7303 let message = "/main.c:1:1: error: type 'struct over' is too large [E0560]";
7304 assert_eq!(run(&opts, over).messages, [message]);
7305 let array = "struct wide { short buf[1L << 62]; };\n";
7306 let message = "/main.c:1:25: error: size of array 'buf' exceeds \
7307 maximum object size '9223372036854775807' [E0537]";
7308 assert_eq!(run(&opts, array).messages[0], message);
7309 }
7310
7311 fn compile_bytes(source: &[u8]) -> Compiled {
7316 let mut opts = options();
7317 opts.emit = EmitKind::Ir;
7318 let mut fs = MemoryFileSystem::new();
7319 fs.insert("/main.c", source.to_vec());
7320 compile(&opts, "/main.c", &fs)
7321 }
7322
7323 #[test]
7330 fn a_byte_that_is_not_a_character_is_kept_in_a_literal_and_refused_outside_one() {
7331 let mut source = b"char s[] = \"a".to_vec();
7332 source.push(0xff);
7333 source.extend_from_slice(b"b\";\nchar c = '");
7334 source.push(0xff);
7335 source.extend_from_slice(b"';\n");
7336 let result = compile_bytes(&source);
7337 assert_eq!(result.messages, Vec::<String>::new(), "a raw byte in a literal is that byte");
7338 assert!(result.text().contains(r#"bytes "a\ffb\00""#), "{}", result.text());
7339 assert!(result.text().contains("global @c : i8 = -1,"), "{}", result.text());
7341
7342 let mut stray = b"int a".to_vec();
7343 stray.push(0xff);
7344 stray.extend_from_slice(b" = 1;\n");
7345 let result = compile_bytes(&stray);
7346 assert!(
7347 result.messages.iter().any(|m| m.contains("source is not valid UTF-8 here")),
7348 "{:?}",
7349 result.messages
7350 );
7351 }
7352
7353 #[test]
7354 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
7355 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
7356 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
7357 let expected = "\
7358func @add(i32, i32) -> i32, linkage(external) {
7359block0(%0: i32, %1: i32):
7360 %2 = add.nsw %0, %1
7361 return %2
7362}
7363";
7364 assert!(text.contains(expected), "{text}");
7365 }
7366
7367 #[test]
7368 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
7369 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
7370 assert!(!text.contains("alloca"), "{text}");
7371 assert!(!text.contains("load"), "{text}");
7372 assert!(!text.contains("store"), "{text}");
7373 }
7374
7375 #[test]
7376 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
7377 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
7378 let expected = "\
7379block0:
7380 %0 = alloca, size 4, align 4
7381 %1 = iconst.i32 1
7382 store %1 -> %0, align 4, tbaa !1
7383 %2 = call @g(%0) : (ptr) -> i32
7384 return %2
7385";
7386 assert_eq!(text, expected);
7387 }
7388
7389 #[test]
7390 fn a_loop_carries_what_it_changes_as_block_parameters() {
7391 let text = body(
7394 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
7395 return total;\n}\n",
7396 );
7397 assert!(!text.contains("alloca"), "{text}");
7398 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
7399 assert!(text.contains("jump block1("), "{text}");
7400 }
7401
7402 #[test]
7403 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
7404 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
7405 assert!(text.contains("icmp slt %0, %1"), "{text}");
7406 assert!(!text.contains("zext"), "{text}");
7407 }
7408
7409 #[test]
7410 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
7411 let text = body("int f(int a, int b) { return a && b; }\n");
7412 let expected = "\
7413block0(%0: i32, %1: i32):
7414 %2 = iconst.i32 0
7415 %3 = icmp ne %0, %2
7416 %4 = iconst.i1 0
7417 br_if %3, block1, block2(%4)
7418
7419block1:
7420 %5 = iconst.i32 0
7421 %6 = icmp ne %1, %5
7422 jump block2(%6)
7423
7424block2(%7: i1):
7425 %8 = zext.i32 %7
7426 return %8
7427";
7428 assert_eq!(text, expected);
7429 }
7430
7431 #[test]
7432 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
7433 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
7434 assert!(!text.contains("block3"), "{text}");
7437 assert!(!text.contains("iconst.i32 3"), "{text}");
7438 }
7439
7440 #[test]
7441 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
7442 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
7443 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
7444 assert!(body("int f(void) { }\n").contains("unreachable"));
7445 }
7446
7447 #[test]
7448 fn a_structure_is_copied_rather_than_held_in_a_value() {
7449 let text = body(
7450 "struct point { int x, y; };\n\
7451 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
7452 );
7453 assert!(text.contains("memcpy"), "{text}");
7454 }
7455
7456 #[test]
7457 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
7458 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
7459 assert!(text.contains("memset"), "{text}");
7460 }
7461
7462 #[test]
7463 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
7464 let text = body(
7465 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
7466 default: r = 4; } return r; }\n",
7467 );
7468 let expected = "\
7469block0(%0: i32):
7470 %1 = iconst.i32 0
7471 switch %0, block1, [1 => block2, 2 => block3(%1)]
7472
7473block1:
7474 %2 = iconst.i32 4
7475 jump block4(%2)
7476
7477block2:
7478 %3 = iconst.i32 1
7479 jump block3(%3)
7480
7481block3(%4: i32):
7482 %5 = iconst.i32 2
7483 %6 = add.nsw %4, %5
7484 jump block4(%6)
7485
7486block4(%7: i32):
7487 return %7
7488";
7489 assert_eq!(text, expected);
7490 }
7491
7492 #[test]
7493 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
7494 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
7497 assert!(text.contains("%2 = sub %0, %1"), "{text}");
7498 assert!(text.contains("icmp ule"), "{text}");
7499 assert!(!text.contains("switch"), "{text}");
7500 }
7501
7502 #[test]
7503 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
7504 let text = body(
7505 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
7506 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
7507 );
7508 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
7511 assert!(text.contains("block5:\n jump block7("), "{text}");
7512 assert!(text.contains("block6:\n jump block8("), "{text}");
7513 }
7514
7515 #[test]
7516 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
7517 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
7518 }
7519
7520 #[test]
7521 fn a_label_a_loop_is_only_entered_through_builds_the_loop_around_it() {
7522 let text = body(
7527 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
7528 return n; }\n",
7529 );
7530 assert!(text.contains("switch %0, block1(%1), [1 => block2, 2 => block3(%1)]"), "{text}");
7533 assert!(text.contains("block3(%3: i32):\n %4 = iconst.i32 1"), "{text}");
7534 assert!(text.contains("block4:\n jump block3("), "{text}");
7535 }
7536
7537 #[test]
7538 fn a_goto_into_a_loop_body_enters_it_without_the_test() {
7539 let text = body("int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n");
7542 assert!(text.starts_with("block0(%0: i32, %1: i32):\n jump block1(%1)"), "{text}");
7543 assert!(text.contains("block1(%2: i32):\n %3 = iconst.i32 1"), "{text}");
7544 assert!(text.contains("br_if %6, block2, block3"), "{text}");
7545 }
7546
7547 #[test]
7548 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
7549 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
7550 assert!(!text.contains("alloca"), "{text}");
7554 assert!(text.contains("block2(%4: i32):\n return %4"), "{text}");
7555 assert_eq!(text.matches("jump block2(").count(), 2, "{text}");
7556 }
7557
7558 #[test]
7559 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
7560 let text =
7561 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
7562 assert!(!text.contains("alloca"), "{text}");
7563 assert!(text.contains("block1(%2: i32):"), "{text}");
7564 assert!(text.contains("jump block1(%5)"), "{text}");
7565 }
7566
7567 #[test]
7568 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
7569 assert_eq!(
7572 body("int f(int x) { return x; spare: return 0; }\n"),
7573 "block0(%0: i32):\n return %0\n"
7574 );
7575 }
7576
7577 #[test]
7578 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
7579 let text = body(
7580 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
7581 );
7582 assert_eq!(
7585 text,
7586 "\
7587block0(%0: ptr):
7588 %1 = load.i8 %0, align 1
7589 %2 = iconst.i8 3
7590 %3 = ashr %1, %2
7591 %4 = sext.i32 %3
7592 return %4
7593"
7594 );
7595 }
7596
7597 #[test]
7598 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
7599 let text =
7603 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
7604 assert_eq!(
7605 text,
7606 "\
7607block0(%0: ptr, %1: i32):
7608 %2 = iconst.i32 16777215
7609 %3 = and %1, %2
7610 %4 = trunc.i16 %3
7611 store %4 -> %0, align 2
7612 %5 = iconst.i32 16
7613 %6 = lshr %3, %5
7614 %7 = trunc.i8 %6
7615 %8 = iconst.i64 2
7616 %9 = ptr_add %0, %8
7617 store %7 -> %9, align 1
7618 return
7619"
7620 );
7621 }
7622
7623 #[test]
7624 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
7625 let text =
7626 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
7627 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
7630 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
7631 }
7632
7633 #[test]
7634 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
7635 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
7638 assert_eq!(text.matches("ashr").count(), 0, "{text}");
7639 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
7640 }
7641
7642 #[test]
7643 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
7644 let text = body(
7648 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
7649 );
7650 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
7651 }
7652
7653 #[test]
7654 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
7655 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
7658 assert!(
7659 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
7660 "{text}"
7661 );
7662 }
7663
7664 #[test]
7665 fn an_initialized_flexible_array_member_makes_the_object_larger_than_its_type() {
7666 let text = ir(concat!(
7671 "struct a { int i; int j[]; } x = { 1, { 2, 0, 2, 3 } };\n",
7672 "struct b { char c; char p[]; } y = { 'o', \"wx\" };\n",
7673 "struct c { char c; char p[]; } z = { '9', { 'e', 'b' } };\n",
7674 "char s[2] = \"hi\";\n",
7675 ));
7676 assert!(
7677 text.contains("global @x : bytes 20 = { i32 1, i32 2, i32 0, i32 2, i32 3 }"),
7678 "{text}"
7679 );
7680 assert!(text.contains("global @y : bytes 4 = { i8 111, bytes \"wx\\00\" }"), "{text}");
7681 assert!(text.contains("global @z : bytes 3 = { i8 57, i8 101, i8 98 }"), "{text}");
7682 assert!(text.contains("global @s : bytes 2 = { bytes \"hi\" }"), "{text}");
7685 }
7686
7687 #[test]
7688 fn a_definition_takes_a_parameter_it_left_unnamed() {
7689 let text = ir("int f(int a, int) { return a; }\n");
7693 assert!(text.contains("func @f(i32, i32) -> i32"), "{text}");
7694 assert!(text.contains("block0(%0: i32, %1: i32):"), "{text}");
7695
7696 let text = ir("int g(int, int n) { return n; }\n");
7699 assert!(text.contains("block0(%0: i32, %1: i32):\n return %1\n"), "{text}");
7700 }
7701
7702 #[test]
7703 fn an_assignment_of_a_structure_is_the_object_it_wrote() {
7704 let text = body(concat!(
7709 "struct s { int f; int g; };\n",
7710 "void h(struct s *a, struct s *c, struct s *d, struct s *e)\n",
7711 "{ *d = *e = a[0] = *c; }\n",
7712 ));
7713 assert_eq!(text.matches("memcpy").count(), 3, "{text}");
7714 assert!(text.contains("memcpy %8, %1, size 8, align 4\n"), "{text}");
7715 assert!(text.contains("memcpy %3, %8, size 8, align 4\n"), "{text}");
7716 assert!(text.contains("memcpy %2, %3, size 8, align 4\n"), "{text}");
7717 }
7718
7719 #[test]
7720 fn a_string_literal_stops_at_the_end_of_the_array_it_is_filling() {
7721 let mut opts = options();
7726 opts.emit = EmitKind::Ir;
7727 let result = run(
7728 &opts,
7729 concat!(
7730 "const char a[2][3] = { \"1234\", \"xyz\" };\n",
7731 "static const char b[3][5] = { \"12345\", \"678\", \"9\" };\n",
7732 "union u { struct { char x[4]; char y[4]; }; struct { char z[8]; }; };\n",
7733 "const union u c = { { \"1234\", \"567\" } };\n",
7734 ),
7735 );
7736 let text = result.text();
7737 assert_eq!(
7738 result.messages,
7739 ["/main.c:1:24: warning: initializer-string for array of 'const char' is too long \
7740 (5 chars into 3 available) [E0637]"]
7741 );
7742 assert!(text.contains("global @a : bytes 6 = { bytes \"123\", bytes \"xyz\" }"), "{text}");
7743 assert!(
7744 text.contains(
7745 "global @b : bytes 15 = { bytes \"12345\", bytes \"678\\00\", zero 1, \
7746 bytes \"9\\00\", zero 3 }"
7747 ),
7748 "{text}"
7749 );
7750 assert!(
7753 text.contains("global @c : bytes 8 = { bytes \"1234\", bytes \"567\\00\" }"),
7754 "{text}"
7755 );
7756 }
7757
7758 #[test]
7759 fn a_cast_of_a_record_to_its_own_type_is_the_object_that_was_cast() {
7760 let text = body(concat!(
7764 "struct s { int a, b; };\nstruct v { struct s s; int t; };\n",
7765 "void g(struct v *);\n",
7766 "void f(struct s *p) { struct v w = { (struct s)*p, 5 }; g(&w); }\n",
7767 ));
7768 assert_eq!(text.matches("memcpy").count(), 1, "{text}");
7769 }
7770
7771 #[test]
7772 fn a_compound_literal_read_in_a_static_initializer_lays_its_bytes_into_the_image() {
7773 let text = ir(concat!(
7778 "struct s { int x; };\n",
7779 "struct t { struct s s; int o; } a = { (struct s){ 2 }, 3 };\n",
7780 "int n = (int){ 7 };\n",
7781 "struct u { struct s p; struct s q; } b = { (struct s){ 1 }, (struct s){ } };\n",
7782 ));
7783 assert!(text.contains("global @a : bytes 8 = { i32 2, i32 3 }"), "{text}");
7784 assert!(text.contains("global @n : i32 = 7,"), "{text}");
7785 assert!(text.contains("global @b : bytes 8 = { i32 1, zero 4 }"), "{text}");
7788 }
7789
7790 #[test]
7791 fn the_address_of_a_compound_literal_asks_for_the_object_it_points_at() {
7792 let text = ir("struct s { int x; };\nstruct s *q = &(struct s){ 9 };\n");
7796 assert!(text.contains("global @.Lanon.0 : i32 = 9, align 4, linkage(internal)"), "{text}");
7797 assert!(text.contains("global @q : bytes 8 = { addr.8 @.Lanon.0 }"), "{text}");
7798 }
7799
7800 #[test]
7801 fn an_object_of_no_size_at_all_has_an_image_with_nothing_in_it() {
7802 let text = ir("unsigned char foo[1][0];\n");
7806 assert!(text.contains("global @foo : bytes 0 = {}, align 1"), "{text}");
7807 }
7808
7809 #[test]
7810 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
7811 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
7814 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
7815 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
7816 }
7817
7818 #[test]
7819 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
7820 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
7824 assert!(
7825 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
7826 "{text}"
7827 );
7828 }
7829
7830 #[test]
7831 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
7832 let text = body(
7837 "\
7838struct s { int a, b; };
7839struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
7840",
7841 );
7842 assert!(text.contains("block3(%7: ptr)"), "{text}");
7844 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
7845 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
7846 }
7847
7848 #[test]
7856 fn the_left_side_of_a_conditional_with_no_middle_is_evaluated_once() {
7857 let text = body("int f(int i) { return ++i ?: 10; }\n");
7858 assert!(text.contains("jump block3(%2)"), "the arm is the value that was tested: {text}");
7859 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
7860
7861 let text = body("long f(int i) { return ++i ?: 10L; }\n");
7864 assert!(text.contains("%5 = sext.i64 %2"), "the arm widens what was tested: {text}");
7865 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
7866
7867 let text = body("int g(void);\nint f(void) { return g() ?: 10; }\n");
7869 assert_eq!(text.matches("call @g").count(), 1, "called once: {text}");
7870
7871 let text = body("int f(int i) { return ++i ? ++i : 10; }\n");
7874 assert_eq!(text.matches("add.nsw").count(), 2, "incremented twice: {text}");
7875 }
7876
7877 #[test]
7878 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
7879 let text = ir("\
7883struct pair { int a, b; };
7884struct pair make(int a, int b);
7885struct pair twice(struct pair p) { return make(p.a, p.b); }
7886");
7887 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
7888 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
7889 }
7890
7891 #[test]
7892 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
7893 let text = ir("\
7897struct big { double v[8]; };
7898struct big grow(struct big b);
7899struct big twice(struct big b) { return grow(grow(b)); }
7900");
7901 assert!(
7902 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
7903 "{text}"
7904 );
7905 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
7906 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
7909 }
7910
7911 #[test]
7912 fn a_structure_passed_to_a_variadic_function_says_so_at_the_call() {
7913 let text = ir("\
7918struct big { double v[8]; };
7919struct pair { int a, b; };
7920int p(const char *, ...);
7921int f(struct big b, struct pair q) { return p(\"\", 1, b, q); }
7922");
7923 assert!(
7924 text.contains("call @p(%4, %5, %2 byval(64, align 8), %6) : (ptr, ...) -> i32"),
7925 "{text}"
7926 );
7927 }
7928
7929 #[test]
7930 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
7931 let body = body(
7934 "\
7935struct pair { int a, b; };
7936struct pair make(int a, int b);
7937int second(void) { return make(1, 2).b; }
7938",
7939 );
7940 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
7941 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
7942 }
7943
7944 #[test]
7945 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
7946 let source = "\
7950struct hfa { float x, y, z; };
7951int take(struct hfa h);
7952int give(struct hfa h) { return take(h); }
7953";
7954 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
7955 let mut opts = options();
7956 opts.emit = EmitKind::Ir;
7957 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
7958 let result = run(&opts, source);
7959 assert_eq!(result.messages, Vec::<String>::new());
7960 assert!(result.text().contains("func @take(f32, f32, f32) -> i32"), "{}", result.text());
7961 }
7962
7963 #[test]
7964 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
7965 let source = "\
7968int use(int *);
7969void f(int n) {
7970 {
7971 int a[n];
7972 use(a);
7973 }
7974 use(0);
7975}
7976";
7977 let body = body(source);
7978 assert!(body.contains("mul.nsw"), "{body}");
7979 assert!(body.contains("stacksave"), "{body}");
7980 assert!(body.contains("alloca %"), "{body}");
7981 assert!(body.contains("stackrestore"), "{body}");
7982 }
7983
7984 #[test]
7985 fn a_goto_out_of_the_scope_of_one_gives_its_stack_back_on_the_way() {
7986 let source = "\
7991int use(int *);
7992int f(int n) {
7993 {
7994 int a[n];
7995 if (use(a)) goto out;
7996 use(0);
7997 }
7998out:
7999 return 0;
8000}
8001";
8002 let body = body(source);
8003 assert_eq!(body.matches("stackrestore").count(), 2, "{body}");
8005 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
8006 assert!(after.starts_with(" %4\n jump block"), "{body}");
8007 }
8008
8009 #[test]
8010 fn a_goto_to_a_label_the_array_is_still_alive_at_leaves_the_stack_alone() {
8011 let source = "\
8015int use(int *);
8016int f(int n) {
8017 int a[n];
8018again:
8019 if (use(a)) goto again;
8020 return 0;
8021}
8022";
8023 let body = body(source);
8024 assert!(body.contains("stacksave"), "{body}");
8025 assert!(!body.contains("stackrestore"), "{body}");
8026 }
8027
8028 #[test]
8029 fn a_goto_back_to_a_label_in_front_of_one_gives_it_back_every_time_round() {
8030 let source = "\
8035int use(int *);
8036int f(int n) {
8037again:
8038 {
8039 int a[n];
8040 if (use(a)) goto again;
8041 }
8042 return 0;
8043}
8044";
8045 let body = body(source);
8046 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
8047 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
8048 assert!(after.starts_with(" %4\n jump block1\n"), "{body}");
8049 }
8050
8051 #[test]
8052 fn the_head_of_a_for_loop_is_a_scope_that_closes_where_the_loop_is_left() {
8053 let source = "\
8059int f(void);
8060void t(void) {
8061 int count = 10;
8062 for (; count--;) {
8063 int b[f()];
8064 int i;
8065 for (i = 0; i < f(); i++) {
8066 b[i] = count;
8067 }
8068 }
8069}
8070";
8071 let body = body(source);
8072 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
8076 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
8077 let next = after.split("\n\n").next().expect("the block the restore is in");
8080 assert!(next.contains("jump block1("), "{body}");
8081 }
8082
8083 #[test]
8084 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
8085 let source = "\
8088unsigned long f(int n) {
8089 int a[n];
8090 n = 0;
8091 return sizeof a;
8092}
8093";
8094 let body = body(source);
8095 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
8097 }
8098
8099 #[test]
8100 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
8101 let source = "\
8104int use(int);
8105int f(int x) {
8106 return ({
8107 int t = use(x);
8108 t * t;
8109 });
8110}
8111";
8112 let expected = "\
8113block0(%0: i32):
8114 %1 = call @use(%0) : (i32) -> i32
8115 %2 = mul.nsw %1, %1
8116 return %2
8117";
8118 assert_eq!(body(source), expected);
8119 }
8120
8121 #[test]
8122 fn a_comma_whose_value_is_an_object_names_the_object_the_right_side_named() {
8123 let source = "\
8127struct pair { int a, b; };
8128void bail(void);
8129int f(struct pair p) {
8130 return (bail(), p).b;
8131}
8132";
8133 let expected = "\
8134block0(%0: i64):
8135 %1 = alloca, size 8, align 4
8136 store %0 -> %1, align 4
8137 call @bail() : ()
8138 %2 = iconst.i64 4
8139 %3 = ptr_add %1, %2
8140 %4 = load.i32 %3, align 4, tbaa !1
8141 return %4
8142";
8143 assert_eq!(body(source), expected);
8144 }
8145
8146 #[test]
8147 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
8148 let source = "int f(int x) { return ({ return x; 0; }); }\n";
8152 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
8153 }
8154
8155 #[test]
8156 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
8157 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
8161 let expected = "\
8162block0(%0: ptr):
8163 %1 = va_arg.f64 %0
8164 %2 = va_arg.f64 %0
8165 %3 = fadd %1, %2
8166 return %3
8167";
8168 assert_eq!(body(source), expected);
8169 }
8170
8171 #[test]
8172 fn one_that_reads_a_structure_answers_where_the_object_is() {
8173 let source = "\
8187struct s { int a; long b; };
8188long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.b; }
8189";
8190 let expected = "\
8191block0(%0: ptr):
8192 %1 = alloca, size 16, align 16
8193 %2 = va_object %0, size 16, align 8, in(int 8 at 0, int 8 at 8)
8194 memcpy %1, %2, size 16, align 8
8195 %3 = iconst.i64 8
8196 %4 = ptr_add %1, %3
8197 %5 = load.i64 %4, align 8, tbaa !1
8198 return %5
8199";
8200 assert_eq!(body(source), expected);
8201 }
8202
8203 #[test]
8207 fn the_classification_says_which_registers_the_object_arrived_in() {
8208 let source = "\
8209struct s { double a; double b; };
8210double f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a; }
8211";
8212 assert!(
8213 body(source)
8214 .contains("va_object %0, size 16, align 8, in(float f64 at 0, float f64 at 8)"),
8215 "{}",
8216 body(source)
8217 );
8218
8219 let big = "\
8220struct s { long a[4]; };
8221long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a[0]; }
8222";
8223 assert!(body(big).contains("va_object %0, size 32, align 8\n"), "{}", body(big));
8224 }
8225
8226 #[test]
8227 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
8228 let source = "\
8232int f(int c) {
8233 void *p = c ? &&one : &&two;
8234 goto *p;
8235one:
8236 return 1;
8237two:
8238 return 2;
8239}
8240";
8241 let expected = "\
8242block0(%0: i32):
8243 %1 = iconst.i32 0
8244 %2 = icmp ne %0, %1
8245 br_if %2, block1, block2
8246
8247block1:
8248 %3 = block_addr block3
8249 jump block4(%3)
8250
8251block2:
8252 %4 = block_addr block5
8253 jump block4(%4)
8254
8255block3:
8256 %5 = iconst.i32 1
8257 return %5
8258
8259block4(%6: ptr):
8260 indirect_br %6, block3, block5
8261
8262block5:
8263 %7 = iconst.i32 2
8264 return %7
8265";
8266 assert_eq!(body(source), expected);
8267 }
8268
8269 fn dispatch(labels: usize) -> String {
8272 let mask = labels - 1;
8273 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
8274 for index in 0..labels {
8275 source.push_str(&format!(" &&a{index},"));
8276 }
8277 source.push_str(" };\n\tint w = n, x = n + 1, y = n + 2, z = n + 3;\n");
8278 source.push_str(&format!("\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
8279 for index in 0..labels {
8280 let step = match index % 4 {
8281 0 => "w += x;",
8282 1 => "x += y;",
8283 2 => "y += z;",
8284 _ => "z += w;",
8285 };
8286 source.push_str(&format!("a{index}:\n\t{step}\n"));
8287 source.push_str("\tif (--n <= 0) return w + x + y + z;\n");
8288 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
8289 }
8290 source.push_str("}\n");
8291 source
8292 }
8293
8294 fn in_front_of_the_jump(text: &str) -> usize {
8296 let (before, _) = text.split_once("\tjmp\t*%").expect("a jump through a register");
8297 before.lines().rev().take_while(|line| line.starts_with("\tmov")).count()
8298 }
8299
8300 #[test]
8310 fn a_jump_through_a_register_writes_what_it_carries_and_not_the_whole_table() {
8311 let small = in_front_of_the_jump(&asm(&dispatch(4)));
8312 let large = in_front_of_the_jump(&asm(&dispatch(32)));
8313 assert_eq!(small, large, "eight times the labels and the same values in hand");
8314 assert!(large <= 8, "the values the loop keeps, and not a set of them per label: {large}");
8315 }
8316
8317 fn crowded(labels: usize) -> String {
8320 const VALUES: usize = 24;
8321 let mask = labels - 1;
8322 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
8323 for index in 0..labels {
8324 source.push_str(&format!(" &&a{index},"));
8325 }
8326 source.push_str(" };\n\t");
8327 for value in 0..VALUES {
8328 source.push_str(&format!("int v{value} = n + {value}; "));
8329 }
8330 let sum: Vec<String> = (0..VALUES).map(|value| format!("v{value}")).collect();
8331 source.push_str(&format!("\n\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
8332 for index in 0..labels {
8333 let (to, from) = (index % VALUES, (index + 1) % VALUES);
8334 source.push_str(&format!("a{index}:\n\tv{to} += v{from};\n"));
8335 source.push_str(&format!("\tif (--n <= 0) return {};\n", sum.join(" + ")));
8336 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
8337 }
8338 source.push_str("}\n");
8339 source
8340 }
8341
8342 fn the_frame(text: &str) -> u64 {
8344 text.lines()
8345 .find_map(|line| {
8346 let (size, _) = line.strip_prefix("\tsubq\t$")?.split_once(", %rsp")?;
8347 size.parse().ok()
8348 })
8349 .expect("a function that opens a frame")
8350 }
8351
8352 #[test]
8361 fn a_frame_holds_what_is_wanted_at_once_and_not_a_slot_for_every_label() {
8362 let small = the_frame(&asm(&crowded(16)));
8363 let large = the_frame(&asm(&crowded(64)));
8364 assert_eq!(small, large, "four times the labels and the same values: {small}, {large}");
8365 }
8366
8367 #[test]
8376 fn a_template_that_names_its_own_registers_gets_the_ones_it_named() {
8377 let source = "void save(void *nlr) {
8378 __asm volatile (
8379 \"movq %%rsp, 32(%%rdi) \\n\"
8380 \"movq %%rbx, 40(%%rdi) \\n\"
8381 \"movq %%r12, 48(%%rdi) \\n\"
8382 : : \"D\" (nlr) : \"memory\");
8383}
8384";
8385 let text = asm(source);
8386 assert!(text.contains("\tmovq\t%rsp, 32(%rdi)\n"), "{text}");
8387 assert!(text.contains("\tmovq\t%rbx, 40(%rdi)\n"), "{text}");
8388 assert!(text.contains("\tmovq\t%r12, 48(%rdi)\n"), "{text}");
8389 }
8390
8391 #[test]
8392 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
8393 let source = "void **next(void);
8396void f(void) { goto *next(); }
8397";
8398 let expected = "\
8399block0:
8400 %0 = call @next() : () -> ptr
8401 unreachable
8402";
8403 assert_eq!(body(source), expected);
8404 }
8405
8406 #[test]
8407 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
8408 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
8411 let expected = "\
8412block0:
8413 inline_asm.volatile \"mfence\", \"\", \"memory\"()
8414 return
8415";
8416 assert_eq!(body(source), expected);
8417 }
8418
8419 #[test]
8420 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
8421 let source = "\
8424int f(int x, int y) {
8425 int r;
8426 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
8427 return r + y;
8428}
8429";
8430 let expected = "\
8431block0(%0: i32, %1: i32):
8432 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
8433 %4 = add.nsw %2, %3
8434 return %4
8435";
8436 assert_eq!(body(source), expected);
8437 }
8438
8439 #[test]
8440 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
8441 let source = "\
8446struct pair { int a, b; };
8447int f(int x) {
8448 int slot = x;
8449 struct pair p = { x, x };
8450 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
8451 return slot + p.a;
8452}
8453";
8454 let text = body(source);
8455 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
8456 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
8457 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
8458 }
8459
8460 #[test]
8461 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
8462 let source = "\
8467int f(int x) {
8468 int r = 7;
8469 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
8470 return r;
8471away:
8472 return r;
8473}
8474";
8475 let expected = "\
8476block0(%0: i32):
8477 %1 = iconst.i32 7
8478 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
8479
8480block1:
8481 return %2
8482
8483block2:
8484 return %1
8485";
8486 assert_eq!(body(source), expected);
8487 }
8488
8489 #[test]
8490 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
8491 let mut opts = options();
8495 opts.emit = EmitKind::Ir;
8496 for (source, expected) in [
8497 (
8498 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
8499 "output operand constraint lacks '='",
8500 ),
8501 (
8502 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
8503 "lvalue required in 'asm' statement",
8504 ),
8505 (
8506 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
8507 "read-only variable 'g' used as 'asm' output",
8508 ),
8509 (
8510 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
8511 "input operand constraint contains '='",
8512 ),
8513 (
8514 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
8515 "memory input 0 is not directly addressable",
8516 ),
8517 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
8518 (
8519 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
8520 "duplicate asm operand name 'a'",
8521 ),
8522 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
8523 ] {
8524 let result = run(&opts, source);
8525 assert!(result.failed(), "expected this to be reported:\n{source}");
8526 assert!(
8527 result.messages.iter().any(|m| m.contains(expected)),
8528 "{expected}\n{:?}",
8529 result.messages
8530 );
8531 }
8532 }
8533
8534 #[test]
8539 fn an_asm_at_file_scope_that_is_directives_becomes_the_objects_it_defines() {
8540 let text = ir(concat!(
8541 "__asm__(\n",
8542 " \".section .rodata\\n\"\n",
8543 " \".globl first\\n\"\n",
8544 " \".balign 8\\n\"\n",
8545 " \"first:\\n\"\n",
8546 " \".long 1\\n\"\n",
8547 " \".long 2\\n\"\n",
8548 " \".globl last\\n\"\n",
8549 " \"last:\\n\"\n",
8550 " \".quad last - first\\n\");\n",
8551 "extern const int first[];\n",
8552 "extern const long last;\n",
8553 ));
8554 assert!(text.contains("global @first : bytes 8 = { i32 1, i32 2 }, align 8"), "{text}");
8555 assert!(text.contains("global @last : i64 = 8"), "{text}");
8556 }
8557
8558 #[test]
8562 fn a_name_an_asm_at_file_scope_defined_is_not_undone_by_a_declaration_of_it() {
8563 let text = ir(concat!(
8564 "__asm__(\".data\\n.globl counter\\ncounter:\\n.long 7\\n\");\n",
8565 "extern int counter;\n",
8566 "int read(void) { return counter; }\n",
8567 ));
8568 assert!(text.contains("global @counter : i32 = 7"), "{text}");
8569 }
8570
8571 #[test]
8576 fn bytes_under_no_label_at_file_scope_are_a_global_in_front_of_the_label() {
8577 let text = ir(concat!(
8578 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n662:\\n",
8579 ".pushsection .data.ignore\\n.byte 7\\n.popsection\\n.byte 662b - 661b\\n\");\n",
8580 "extern unsigned char stuff[];\n",
8581 "int read(void) { return stuff[0]; }\n",
8582 ));
8583 let under = text.find("global @.Lasm.0 : i8 = 41").expect(&text);
8584 let named = text.find("global @stuff : i8 = 42").expect(&text);
8585 assert!(under < named, "the bytes under no label come first: {text}");
8586 assert!(text.contains("global @.Lasm.1 : i8 = 7, align 1, linkage(internal), section"));
8587 let after = text.find("global @.Lasm.2 : i8 = 1").expect(&text);
8592 assert!(named < after, "{text}");
8593 }
8594
8595 #[test]
8600 fn a_distance_from_here_at_file_scope_is_a_hole_naming_the_global_it_measures_to() {
8601 let text = ir(concat!(
8602 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n",
8603 ".pushsection .data.ignore\\n.long 661b - .\\n.popsection\\n\");\n",
8604 "extern unsigned char stuff[];\n",
8605 "int read(void) { return stuff[0]; }\n",
8606 ));
8607 assert!(text.contains("global @.Lasm.1 : bytes 4 = { away.4 @stuff }"), "{text}");
8611 }
8612
8613 #[test]
8618 fn a_set_at_file_scope_is_a_second_name_for_what_it_names() {
8619 let text = ir(concat!(
8620 "void base(void) {}\n",
8621 "__asm__(\".weak one\\n.set one, base\");\n",
8622 "__asm__(\".globl two\\n.set two, base\");\n",
8623 "__asm__(\".set three, base\");\n",
8624 "void three(void) {}\n",
8625 ));
8626 assert!(text.contains("alias @one = @base, linkage(weak)"), "{text}");
8627 assert!(text.contains("alias @two = @base"), "{text}");
8628 assert!(!text.contains("alias @three"), "a definition of the name wins: {text}");
8629 assert!(text.contains("func @three"), "{text}");
8630 }
8631
8632 #[test]
8637 fn a_set_of_a_name_this_file_does_not_define_says_so() {
8638 let messages = errors("__asm__(\".set here, elsewhere\");\n");
8639 assert!(
8640 messages
8641 .iter()
8642 .any(|m| m.contains("'here' is aliased to undefined symbol 'elsewhere'")
8643 && m.contains("E0697")),
8644 "{messages:?}"
8645 );
8646 }
8647
8648 #[test]
8651 fn an_incbin_at_file_scope_is_the_bytes_of_the_file_it_names() {
8652 let mut opts = options();
8653 opts.emit = EmitKind::Ir;
8654 let mut fs = MemoryFileSystem::new();
8655 fs.insert(
8656 "/main.c",
8657 b"__asm__(\".data\\n.globl blob\\nblob:\\n.incbin \\\"seed\\\"\\n\");\n".to_vec(),
8658 );
8659 fs.insert("seed", b"hi".to_vec());
8660 let result = compile(&opts, "/main.c", &fs);
8661 assert_eq!(result.messages, Vec::<String>::new());
8662 let text = result.text();
8663 assert!(text.contains("global @blob : bytes 2 = { bytes \"hi\" }"), "{text}");
8664 }
8665
8666 #[test]
8669 fn an_incbin_naming_a_file_that_is_not_there_says_which_file() {
8670 let messages = errors("__asm__(\".data\\nb:\\n.incbin \\\"nowhere\\\"\\n\");\n");
8671 assert!(
8672 messages
8673 .iter()
8674 .any(|m| m.contains("cannot open 'nowhere' for reading") && m.contains("E0702")),
8675 "{messages:?}"
8676 );
8677 }
8678
8679 #[test]
8682 fn an_instruction_in_an_asm_at_file_scope_is_refused_rather_than_ignored() {
8683 for source in [
8684 "__asm__(\".text\\n.globl f\\nf:\\n ret\\n\");\n",
8685 "__asm__(\".data\\n.set alias, 4\\n\");\n",
8686 ] {
8687 let messages = errors(source);
8688 assert!(
8689 messages
8690 .iter()
8691 .any(|m| m.contains("not supported yet")
8692 && m.contains("in an `asm` at file scope")),
8693 "{source}\n{messages:?}"
8694 );
8695 }
8696 }
8697
8698 #[test]
8708 fn a_naked_function_is_its_own_prologue_and_its_own_ending() {
8709 let text = asm(concat!(
8710 "unsigned nlr_push_tail(void *nlr);\n",
8711 "__attribute__((naked)) unsigned nlr_push(void *nlr) {\n",
8712 " __asm volatile(\n",
8713 " \"movq (%rsp), %rax\\n\"\n",
8714 " \"movq %rax, 16(%rdi)\\n\"\n",
8715 " \"movq %rbx, 40(%rdi)\\n\"\n",
8716 " \"jmp nlr_push_tail\\n\");\n",
8717 "}\n",
8718 ));
8719 assert!(text.contains("\tmovq\t(%rsp), %rax\n"), "{text}");
8720 assert!(text.contains("\tjmp\tnlr_push_tail\n"), "{text}");
8721 assert!(text.contains("\tud2\n"), "{text}");
8722 assert!(!text.contains("\tpushq\t"), "nothing is saved in front of it: {text}");
8723 assert!(!text.contains("\tret\n"), "the jump is where it ends: {text}");
8724 }
8725
8726 #[test]
8729 fn what_a_function_without_a_prologue_cannot_be_given_is_refused() {
8730 let mut opts = options();
8731 opts.emit = EmitKind::Asm;
8732 for (source, why) in [
8733 (
8734 "__attribute__((naked)) void f(void) { volatile long a[8]; a[0] = 1; }\n",
8735 "bytes of frame",
8736 ),
8737 (
8738 "__attribute__((naked)) void f(int n) { char a[n]; __asm(\"nop\" ::\"r\"(a)); }\n",
8739 "has no prologue to point a frame pointer at it with",
8740 ),
8741 ("void elsewhere(void); void f(void) { __asm(\"jmp elsewhere\"); }\n", "jumps out of"),
8742 ] {
8743 let result = run(&opts, source);
8744 assert!(result.failed(), "expected this to be refused:\n{source}");
8745 assert!(
8746 result.messages.iter().any(|message| message.contains(why)),
8747 "{:?}",
8748 result.messages
8749 );
8750 }
8751 }
8752
8753 #[test]
8754 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
8755 let mut opts = options();
8756 opts.emit = EmitKind::Ir;
8757 for source in [
8758 "int f(int n) { void *p = &&out; if (n) goto *p; { int a[n]; out: return 1; } }\n",
8759 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
8760 ] {
8761 let result = run(&opts, source);
8762 assert!(result.failed(), "expected this to be reported:\n{source}");
8763 assert!(
8764 result.messages.iter().any(|m| m.contains("not supported yet")),
8765 "{:?}",
8766 result.messages
8767 );
8768 }
8769 }
8770
8771 fn round_trip(source: &str) -> (String, String) {
8773 let printed = ir(source);
8774 let mut opts = options();
8775 opts.emit = EmitKind::Ir;
8776 let mut fs = MemoryFileSystem::new();
8777 fs.insert("/main.ir", printed.clone().into_bytes());
8778 let result = compile_ir(&opts, "/main.ir", &fs);
8779 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
8780 (printed, result.text().to_owned())
8781 }
8782
8783 #[test]
8784 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
8785 let (printed, again) = round_trip(
8789 "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",
8790 );
8791 assert_eq!(printed, again);
8792 }
8793
8794 #[test]
8795 fn ir_that_is_not_ir_says_which_line_stopped_it() {
8796 let mut opts = options();
8797 opts.emit = EmitKind::Ir;
8798 let mut fs = MemoryFileSystem::new();
8799 let text = "\
8800; ModuleID = 'a.c'
8801; format 0
8802target triple = \"x86_64-unknown-linux-gnu\"
8803target datalayout = \"e-p:64:64-i64:64-S128\"
8804
8805func @f(), linkage(external) {
8806block0:
8807 frobnicate
8808}
8809";
8810 fs.insert("/main.ir", text.as_bytes().to_vec());
8811 let result = compile_ir(&opts, "/main.ir", &fs);
8812 assert!(result.failed());
8813 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
8814 }
8815
8816 #[test]
8817 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
8818 let mut opts = options();
8821 opts.emit = EmitKind::Ir;
8822 let mut fs = MemoryFileSystem::new();
8823 let text = "\
8824; ModuleID = 'a.c'
8825; format 0
8826target triple = \"x86_64-unknown-linux-gnu\"
8827target datalayout = \"e-p:64:64-i64:64-S128\"
8828
8829func @f(), linkage(external) {
8830block0:
8831 %0 = iconst.i32 1
8832 return %0
8833}
8834";
8835 fs.insert("/main.ir", text.as_bytes().to_vec());
8836 let result = compile_ir(&opts, "/main.ir", &fs);
8837 assert!(result.failed());
8838 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
8839 }
8840
8841 #[test]
8842 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
8843 let mut fs = MemoryFileSystem::new();
8845 fs.insert("/main.ir", Vec::new());
8846 let result = compile_ir(&options(), "/main.ir", &fs);
8847 assert!(result.failed());
8848 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
8849 }
8850
8851 #[test]
8852 fn the_printed_ir_reads_back_as_the_same_module() {
8853 let text = ir("\
8856struct point { int x, y; };
8857static const char greeting[] = \"hi\";
8858int table[4] = { 1, 2, 3 };
8859int puts(const char *);
8860double half(double x) { return x / 2.0; }
8861int f(int n) {
8862 int total = 0;
8863 for (int i = 0; i < n; i++) {
8864 if (i == 3) continue;
8865 total += table[i];
8866 }
8867 switch (n) {
8868 case 0: total = 1;
8869 case 1: total++; break;
8870 default: total = -total;
8871 }
8872 struct point p = { total, 1 };
8873 int *q = &p.y;
8874 puts(greeting);
8875 return p.x + *q;
8876}
8877int dispatch(int c) {
8878 void *p = c ? &&one : &&two;
8879 goto *p;
8880one:
8881 return 1;
8882two:
8883 return 2;
8884}
8885int assembly(int x, int *p) {
8886 int r;
8887 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
8888 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
8889 return r;
8890away:
8891 return 0;
8892}
8893");
8894 let mut names = Interner::new();
8895 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
8896 assert_eq!(rucc_ir::print(&module, &names), text);
8897 }
8898
8899 #[test]
8900 fn what_save_temps_keeps_is_the_text_that_was_compiled_and_the_assembly_that_was_assembled() {
8901 let mut opts = options();
8905 opts.emit = EmitKind::Object;
8906 opts.save_temps = rucc_session::SaveTemps::Object;
8907 let result = run(&opts, "#define N 2\nint a[N];\n");
8908 assert_eq!(result.messages, Vec::<String>::new());
8909 let text = result.temps.preprocessed.expect("the preprocessed text");
8910 assert!(text.contains("int a[2];"), "{text}");
8911 assert!(text.starts_with("# 1 \"/main.c\""), "{text}");
8912 let asm = result.temps.assembly.expect("the assembly");
8913 assert!(asm.contains("a:"), "{asm}");
8914 assert!(matches!(result.artifact, Artifact::Object { .. }), "{:?}", result.artifact);
8915 }
8916
8917 #[test]
8918 fn nothing_is_kept_unless_the_flag_asked_for_it() {
8919 let mut opts = options();
8922 opts.emit = EmitKind::Object;
8923 assert_eq!(run(&opts, "int a;\n").temps, Temps::default());
8924 }
8925
8926 #[test]
8927 fn a_compilation_that_stops_before_the_back_end_keeps_the_text_and_no_assembly() {
8928 let mut opts = options();
8931 opts.emit = EmitKind::Ir;
8932 opts.save_temps = rucc_session::SaveTemps::Cwd;
8933 let result = run(&opts, "int a;\n");
8934 assert!(result.temps.preprocessed.is_some());
8935 assert_eq!(result.temps.assembly, None);
8936 }
8937
8938 fn span(from: u64, len: u64, held: rucc_debug::Held) -> rucc_debug::Span {
8940 rucc_debug::Span { from, len, held }
8941 }
8942
8943 #[test]
8944 fn two_stretches_that_meet_and_agree_come_out_as_one() {
8945 let one = span(0, 4, rucc_debug::Held::Reg(3));
8946 let two = span(4, 4, rucc_debug::Held::Reg(3));
8947 assert_eq!(settle(vec![two, one]), vec![span(0, 8, rucc_debug::Held::Reg(3))]);
8948 }
8949
8950 #[test]
8951 fn a_stretch_another_starts_inside_and_disagrees_with_ends_where_the_other_starts() {
8952 let one = span(0, 8, rucc_debug::Held::Reg(3));
8953 let two = span(4, 8, rucc_debug::Held::Reg(4));
8954 let settled = settle(vec![one, two]);
8957 assert_eq!(
8958 settled,
8959 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 8, rucc_debug::Held::Reg(4))]
8960 );
8961 }
8962
8963 #[test]
8964 fn a_stretch_cut_by_one_that_ends_first_does_not_come_back_after_it() {
8965 let one = span(0, 16, rucc_debug::Held::Reg(3));
8968 let two = span(4, 4, rucc_debug::Held::Reg(4));
8969 assert_eq!(
8970 settle(vec![one, two]),
8971 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 4, rucc_debug::Held::Reg(4))]
8972 );
8973 }
8974
8975 #[test]
8976 fn a_stretch_inside_another_that_agrees_with_it_cuts_nothing() {
8977 let one = span(0, 16, rucc_debug::Held::Reg(3));
8978 let two = span(4, 4, rucc_debug::Held::Reg(3));
8979 assert_eq!(settle(vec![one, two]), vec![span(0, 16, rucc_debug::Held::Reg(3))]);
8980 }
8981
8982 #[test]
8983 fn a_stretch_two_others_disagree_over_the_whole_of_says_nothing_at_all() {
8984 let one = span(0, 8, rucc_debug::Held::Reg(3));
8985 let two = span(0, 8, rucc_debug::Held::Frame(-16));
8986 assert_eq!(settle(vec![one, two]), Vec::new());
8987 }
8988
8989 #[test]
8990 fn stretches_with_a_gap_between_them_keep_the_gap() {
8991 let one = span(0, 4, rucc_debug::Held::Reg(3));
8992 let two = span(16, 4, rucc_debug::Held::Reg(3));
8993 assert_eq!(settle(vec![one, two]), vec![one, two]);
8994 }
8995
8996 fn extent(len: usize) -> rucc_object::Extent {
8998 rucc_object::Extent {
8999 name: "f".to_owned(),
9000 start: 0,
9001 len,
9002 align: 1,
9003 binding: rucc_object::Binding::Global,
9004 visibility: rucc_object::Visibility::Default,
9005 patch: None,
9006 }
9007 }
9008
9009 fn row(at: usize, lo: u32, hi: u32) -> rucc_asm::Row {
9011 let span = Span::new(lo, hi);
9012 rucc_asm::Row { at, span, inst: None }
9013 }
9014
9015 #[test]
9016 fn a_row_ends_where_the_next_address_begins() {
9017 let rows = [row(0, 0, 1), row(4, 1, 2), row(10, 2, 3)];
9018 assert_eq!(ends(&extent(16), &rows), vec![4, 10, 16]);
9019 }
9020
9021 #[test]
9022 fn rows_sharing_an_address_all_end_where_the_next_address_begins() {
9023 let rows = [row(0, 0, 1), row(4, 1, 2), row(4, 2, 3), row(4, 3, 4)];
9026 assert_eq!(ends(&extent(12), &rows), vec![4, 12, 12, 12]);
9027 }
9028
9029 #[test]
9030 fn the_rows_of_a_scope_that_are_next_to_each_other_come_out_as_one_stretch() {
9031 let rows = [row(0, 0, 4), row(4, 10, 14), row(8, 14, 18), row(12, 40, 44)];
9032 let ends = ends(&extent(16), &rows);
9033 let scope = Span::new(8, 20);
9034 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 8 }]);
9035 }
9036
9037 #[test]
9038 fn a_scope_the_back_end_split_in_two_comes_out_as_two_stretches() {
9039 let rows = [row(0, 10, 14), row(4, 40, 44), row(8, 14, 18)];
9040 let ends = ends(&extent(12), &rows);
9041 let scope = Span::new(8, 20);
9042 let over = spread(scope, &ends, &rows);
9043 assert_eq!(
9044 over,
9045 vec![rucc_debug::Reach { from: 0, len: 4 }, rucc_debug::Reach { from: 8, len: 4 }]
9046 );
9047 }
9048
9049 #[test]
9050 fn a_row_with_no_source_of_its_own_belongs_to_no_scope() {
9051 let rows = [rucc_asm::Row { at: 0, span: Span::DUMMY, inst: None }, row(4, 10, 14)];
9053 let ends = ends(&extent(8), &rows);
9054 let scope = Span::new(0, 20);
9055 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 4 }]);
9056 }
9057
9058 fn scope(parent: Option<usize>, lo: u32, hi: u32) -> crate::shapes::Scope {
9060 let span = Span::new(lo, hi);
9061 crate::shapes::Scope { parent, span }
9062 }
9063
9064 #[test]
9065 fn a_function_gets_the_scopes_its_own_locals_are_in_and_nothing_else() {
9066 let scopes = [scope(None, 0, 10), scope(None, 20, 30), scope(Some(1), 22, 26)];
9068 let rows = [row(0, 22, 24), row(4, 26, 28)];
9069 let (out, at) = nests(&[Some(2)], &scopes, &extent(8), &rows);
9070 assert_eq!(at.get(&1), Some(&0));
9073 assert_eq!(at.get(&2), Some(&1));
9074 assert_eq!(at.get(&0), None);
9075 assert_eq!(out.len(), 2);
9076 assert_eq!(out[0].parent, None);
9077 assert_eq!(out[1].parent, Some(0));
9078 assert_eq!(out[0].over, vec![rucc_debug::Reach { from: 0, len: 8 }]);
9079 assert_eq!(out[1].over, vec![rucc_debug::Reach { from: 0, len: 4 }]);
9080 }
9081
9082 #[test]
9083 fn a_local_written_straight_into_the_body_pulls_no_scope_in() {
9084 let scopes = [scope(None, 20, 30)];
9085 let rows = [row(0, 22, 24)];
9086 let (out, at) = nests(&[None], &scopes, &extent(4), &rows);
9087 assert_eq!(out, Vec::new());
9088 assert!(at.is_empty());
9089 }
9090
9091 #[test]
9092 fn a_scope_whose_code_all_went_away_is_still_one_of_the_functions_scopes() {
9093 let scopes = [scope(None, 20, 30)];
9097 let rows = [row(0, 40, 44)];
9098 let (out, at) = nests(&[Some(0)], &scopes, &extent(4), &rows);
9099 assert_eq!(at.get(&0), Some(&0));
9100 assert_eq!(out.len(), 1);
9101 assert_eq!(out[0].over, Vec::new());
9102 }
9103}