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 instrument: opts.instrument_functions,
381 read: &mut read,
382 },
383 );
384 let failed = lowered.diagnostics.iter().any(|d| d.severity.is_fatal());
388 if !failed {
389 if let Err(errors) = rucc_ir::verify(&lowered.module, &sess.interner) {
394 for error in errors {
395 diagnostics.push(internal(&format!("invalid IR, {error}")));
396 }
397 } else if let Err(complaints) =
398 instrument(&mut lowered.module, &mut sess.interner, opts)
399 .map(|done| instrumented = done)
400 {
401 diagnostics.extend(complaints);
402 } else if let Err(complaints) = optimize(
403 &mut lowered.module,
404 &mut sess.interner,
405 &sess.target,
406 opts,
407 name,
408 &mut dumps,
409 &mut remarks,
410 ) {
411 diagnostics.extend(complaints);
412 } else if opts.emit == EmitKind::SafetySummary {
413 artifact = Artifact::Text(
418 rucc_safety::summarize(
419 &lowered.module,
420 &sess.interner,
421 name,
422 opts.safety.as_str(),
423 instrumented.checks,
424 instrumented.interposed,
425 instrumented.crossings,
426 )
427 .render(),
428 );
429 } else if opts.emit == EmitKind::Ir {
430 artifact =
435 Artifact::Text(rucc_ir::print(&lowered.module, &sess.interner));
436 } else {
437 match generate(
440 &mut lowered.module,
441 &mut sess.interner,
442 &sess.target,
443 opts,
444 &mut Recording {
445 fired: &mut fired,
446 pressure: &mut pressure,
447 lowerings: &mut lowerings,
448 },
449 &mut temps.assembly,
450 Origin { map: &sess.sources, name, meaning: &meaning },
451 ) {
452 Ok(made) => artifact = made,
453 Err(complaints) => diagnostics.extend(complaints),
454 }
455 }
456 }
457 diagnostics.extend(lowered.diagnostics);
458 }
459 _ => {}
460 }
461 }
462 diagnostics.extend(checked.diagnostics);
463 }
464
465 let mut messages = Vec::with_capacity(diagnostics.len());
466 let mut errors = 0;
467 for diag in &diagnostics {
468 if rucc_diag::dropped(diag, &sess.sources, opts.warnings, opts.system_header_warnings) {
474 continue;
475 }
476 if diag.severity.is_fatal()
477 || (diag.severity == Severity::Warning && opts.warnings_are_errors)
478 {
479 errors += 1;
480 }
481 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
482 }
483 if errors > 0 {
484 artifact = Artifact::Nothing;
486 }
487 Compiled { artifact, messages, errors, fired, pressure, lowerings, dumps, remarks, deps, temps }
490}
491
492#[must_use]
502pub fn compile_ir(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
503 let mut sess = Session::new(opts.clone());
504 if opts.emit != EmitKind::Ir {
505 return failure(format!(
506 "{name}: an input of IR can only be emitted as IR, and `--emit={}` asks for what \
507 the C in front of it became",
508 opts.emit.as_str()
509 ));
510 }
511 let bytes = match fs.read(Path::new(name)) {
512 Ok(bytes) => bytes,
513 Err(e) => return failure(format!("{name}: {e}")),
514 };
515 let Ok(text) = std::str::from_utf8(bytes.as_slice()) else {
516 return failure(format!("{name}: this is not text, so it is not IR"));
517 };
518
519 let module = match rucc_ir::parse(text, &mut sess.interner) {
520 Ok(module) => module,
521 Err(error) => {
522 return failure(format!("{name}:{}: {}", error.line, error.message));
523 }
524 };
525 let mut diagnostics: Vec<Diagnostic> = Vec::new();
526 if let Err(errors) = rucc_ir::verify(&module, &sess.interner) {
527 for error in errors {
528 diagnostics.push(invalid(&format!("invalid IR, {error}")));
529 }
530 }
531 let mut messages = Vec::with_capacity(diagnostics.len());
532 for diag in &diagnostics {
533 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
534 }
535 let errors = u32::try_from(messages.len()).unwrap_or(u32::MAX);
536 let artifact = if errors > 0 {
537 Artifact::Nothing
538 } else {
539 Artifact::Text(rucc_ir::print(&module, &sess.interner))
540 };
541 Compiled {
543 artifact,
544 messages,
545 errors,
546 fired: Fired::new(),
547 pressure: Pressure::new(),
548 lowerings: Lowerings::new(),
549 dumps: Vec::new(),
550 remarks: String::new(),
551 deps: Vec::new(),
552 temps: Temps::default(),
553 }
554}
555
556fn instrument(
579 module: &mut rucc_ir::Module,
580 names: &mut Interner,
581 opts: &Options,
582) -> Result<Instrumented, Vec<Diagnostic>> {
583 if !opts.safety.instruments() {
584 return Ok(Instrumented::default());
585 }
586 let mut checks = rucc_safety::run(module, opts.subobject, opts.promise, opts.races);
587 checks.freed = rucc_safety::ending::checks(module, names);
595 let interposed = rucc_safety::redirect(module, names);
600 let crossings = rucc_safety::witness(module, names);
603 match rucc_ir::verify(module, names) {
604 Ok(()) => Ok(Instrumented { checks, interposed, crossings }),
605 Err(errors) => Err(errors
606 .iter()
607 .map(|e| internal(&format!("invalid IR after check insertion, {e}")))
608 .collect()),
609 }
610}
611
612#[derive(Clone, Copy, Debug, Default)]
618struct Instrumented {
619 checks: rucc_safety::Counts,
621 interposed: usize,
623 crossings: rucc_safety::Sites,
625}
626
627fn optimize(
639 module: &mut rucc_ir::Module,
640 names: &mut Interner,
641 target: &TargetInfo,
642 opts: &Options,
643 file: &str,
644 dumps: &mut Vec<rucc_opt::Dump>,
645 remarks: &mut String,
646) -> Result<(), Vec<Diagnostic>> {
647 let mut settings = rucc_opt::Options::for_level(opts.opt_level);
648 settings.interposition = match opts.interposition {
654 true => replaceable(target, opts),
655 false => IrPic::Executable,
656 };
657 settings.toggles.clone_from(&opts.passes);
658 settings.builtins = opts.builtins && opts.hosted;
663 settings.no_builtin.clone_from(&opts.no_builtin);
664 settings.fuel = opts.pass_fuel.iter().cloned().collect();
665 settings.global_fuel = opts.pass_fuel_global;
666 settings.verify |= opts.verify_each;
667 for (on, spec) in &opts.pass_gates {
668 if let Err(why) = settings.gates.add(*on, spec) {
671 return Err(vec![internal(&why)]);
672 }
673 }
674 for spec in &opts.dump_ir {
675 if let Err(why) = settings.dumps.add(spec) {
678 return Err(vec![internal(&why)]);
679 }
680 }
681 let mut wants = rucc_opt::Wants::none();
682 for spec in &opts.opt_info {
683 if let Err(why) = wants.add(spec) {
686 return Err(vec![internal(&why)]);
687 }
688 }
689 let report = rucc_opt::run(module, names, &settings);
690 remarks.push_str(&rucc_opt::optinfo::render(file, &report, names, wants));
691 dumps.extend(report.dumps);
692 match report.broke.is_empty() {
693 true => Ok(()),
694 false => Err(report.broke.iter().map(|why| internal(why)).collect()),
695 }
696}
697
698fn replaceable(target: &TargetInfo, opts: &Options) -> IrPic {
732 match (target.tuple.os().object_format(), opts.pic) {
733 (Some(ObjectFormat::Elf), Pic::Library) => IrPic::Library,
734 _ => IrPic::Executable,
735 }
736}
737
738#[derive(Clone, Copy)]
745struct Origin<'a> {
746 map: &'a SourceMap,
748 name: &'a str,
750 meaning: &'a crate::shapes::Meaning,
752}
753
754fn generate(
755 module: &mut rucc_ir::Module,
756 names: &mut Interner,
757 target: &TargetInfo,
758 opts: &Options,
759 recording: &mut Recording<'_>,
760 assembly: &mut Option<String>,
761 origin: Origin<'_>,
762) -> Result<Artifact, Vec<Diagnostic>> {
763 let Some(machine) = Machine::for_target(target) else {
764 return Err(vec![unsupported(&format!(
765 "there is no back end for {} in this compiler yet, so there is nothing to generate",
766 target.tuple
767 ))]);
768 };
769 if opts.protector != Protector::None && machine.conv.guard.is_none() {
774 return Err(vec![unsupported(&format!(
775 "{} is not supported for {} yet, because the stack protector on that target is not \
776 the one this compiler writes",
777 opts.protector, target.tuple
778 ))]);
779 }
780 if opts.control.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
786 return Err(vec![unsupported(&format!(
787 "-fcf-protection={} is not supported for {} yet, because what says a file was built \
788 for it there is not the note this compiler writes",
789 opts.control, target.tuple
790 ))]);
791 }
792 let profile = match machine.conv.trace {
798 Some(trace) => opts.profile.then(|| opts.hook.early(trace.fentry)),
799 None if opts.profile => {
800 return Err(vec![unsupported(&format!(
801 "-pg is not supported for {} yet, because the profiler's hook on that target is \
802 not the one this compiler calls",
803 target.tuple
804 ))]);
805 }
806 None => None,
807 };
808 if opts.patchable.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
813 return Err(vec![unsupported(&format!(
814 "-fpatchable-function-entry= is not supported for {} yet, because what records where \
815 the room is there is not the section this compiler writes",
816 target.tuple
817 ))]);
818 }
819 let flags = pipeline::Flags {
820 frame_pointer: opts.frame_pointer,
821 red_zone: opts.red_zone,
822 stack_clash: opts.stack_clash,
823 landing: opts.control.branch(),
824 profile: match profile {
825 None => pipeline::Profile::No,
826 Some(true) => pipeline::Profile::Early,
827 Some(false) => pipeline::Profile::Late,
828 },
829 patch: pipeline::Room { after: opts.patchable.after(), before: opts.patchable.before },
830 reorder: opts.reorder_blocks.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
837 reuse: opts.stack_reuse.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
842 schedule: opts.schedule_insns.unwrap_or_else(|| opts.opt_level.schedules()),
847 align_loops: opts.align_loops.unwrap_or(false),
853 accurate: opts.cycle_accurate_model,
855 verify: opts.verify_each,
858 goal: Goal::for_size(opts.opt_level.is_size()),
863 };
864
865 if opts.safety.instruments() {
874 rucc_opt::heap::annotate(module, names);
884 rucc_safety::handover::arrange(module);
891 rucc_safety::lower(module, names);
892 if let Err(errors) = rucc_ir::verify(module, names) {
893 return Err(errors
894 .iter()
895 .map(|e| internal(&format!("invalid IR after check lowering, {e}")))
896 .collect());
897 }
898 }
899
900 let elsewhere = Elsewhere::of(module, replaceable(target, opts), target.object_format);
909
910 let mut funcs = Vec::new();
911 let mut complaints = Vec::new();
912 for id in module.funcs() {
913 if module[id].is_declaration() {
914 continue;
915 }
916 match pipeline::compile_recording(
917 &mut module[id],
918 names,
919 &machine,
920 &elsewhere,
921 flags,
922 recording,
923 ) {
924 Ok(func) => funcs.push(func),
925 Err(why) => {
926 let name = names.resolve(module[id].name).to_owned();
927 let span = why.inst().map_or(Span::DUMMY, |inst| module[id].span(inst));
930 let said = format!("cannot generate code for '{name}': {why}");
931 complaints.push(unsupported_at(&said, span));
932 }
933 }
934 }
935 if !complaints.is_empty() {
936 return Err(complaints);
937 }
938 let (globals, aliases) = match opts.emit {
944 EmitKind::Asm | EmitKind::Object | EmitKind::Archive | EmitKind::Executable => (
945 rucc_asm::globals(module, names, target.object_format).map_err(refused)?,
946 rucc_asm::aliases(module, names).map_err(refused)?,
947 ),
948 _ => (rucc_asm::Globals::default(), Vec::new()),
949 };
950 let unwind = opts.unwinds();
954 match opts.emit {
955 EmitKind::Asm => {
956 rucc_asm::print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
957 .map(Artifact::Text)
958 .map_err(refused)
959 }
960 EmitKind::Object | EmitKind::Archive | EmitKind::Executable => {
964 if opts.save_temps.wanted() {
965 let listing = rucc_asm::print(
966 &funcs,
967 &globals,
968 &aliases,
969 names,
970 target,
971 unwind,
972 output(opts, target),
973 );
974 *assembly = Some(listing.map_err(refused)?);
975 }
976 if rucc_asm::kept(&funcs, names, target) {
983 if opts.debug_info {
984 return Err(vec![unsupported(
985 "debug information for a unit with an `asm` template kept as text",
986 )]);
987 }
988 let listing = rucc_asm::print(
989 &funcs,
990 &globals,
991 &aliases,
992 names,
993 target,
994 unwind,
995 output(opts, target),
996 )
997 .map_err(refused)?;
998 let read = rucc_asm::read(&listing).map_err(|trouble| {
999 vec![unsupported(&format!(
1000 "an `asm` template kept as text, whose listing the assembler stopped at on \
1001 line {}: {}",
1002 trouble.line, trouble.why
1003 ))]
1004 })?;
1005 let defines = rucc_object::assembled_defines(&read);
1006 let bytes =
1007 rucc_object::assembled(&read, &TargetInfo::new(opts.target)).map_err(wrote)?;
1008 return Ok(Artifact::Object { bytes, defines });
1009 }
1010 let assembled = rucc_asm::assemble(&funcs, names, target, unwind, opts.debug_info)
1011 .map_err(refused)?;
1012 let data = globals.image();
1013 let info = if opts.debug_info {
1017 describe(&assembled, &data, &funcs, origin, opts, target)
1018 .map_err(|why| vec![internal(&why)])?
1019 } else {
1020 rucc_object::Info::default()
1021 };
1022 let text = assembled.text;
1023 let bytes =
1026 rucc_object::write(&text, &data, &aliases, target, output(opts, target), &info)
1027 .map_err(wrote)?;
1028 let defines = rucc_object::defines(&text, &data, &aliases, target).map_err(wrote)?;
1033 Ok(Artifact::Object { bytes, defines })
1034 }
1035 _ => Ok(Artifact::Text(rucc_mir::print(&funcs, names, target.regs))),
1036 }
1037}
1038
1039fn describe(
1058 assembled: &rucc_asm::Assembled,
1059 data: &rucc_object::Data,
1060 machine: &[rucc_mir::Func],
1061 origin: Origin<'_>,
1062 opts: &Options,
1063 target: &TargetInfo,
1064) -> Result<rucc_object::Info, String> {
1065 let rucc_asm::Assembled { text, lines, frames } = assembled;
1066 let rewrite = |path: &str| opts.prefix_map.debug.apply(path).into_owned();
1067 let mut files: Vec<String> = Vec::new();
1072 let mut funcs = Vec::with_capacity(text.funcs.len());
1073 for ((extent, rows), built) in text.funcs.iter().zip(lines).zip(machine) {
1074 let mut out: Vec<rucc_debug::Row> = Vec::with_capacity(rows.len());
1075 for row in rows {
1076 if row.span.is_dummy() {
1077 continue;
1078 }
1079 let Some(at) = origin.map.presumed(row.span.lo) else {
1080 continue;
1081 };
1082 let which = interned(&mut files, rewrite(at.name));
1083 let place = rucc_debug::Row {
1084 at: row.at as u64,
1085 file: which,
1086 line: at.line,
1087 column: at.column,
1088 };
1089 match out.last() {
1099 Some(last) if last.at == place.at => {}
1100 _ => out.push(place),
1101 }
1102 }
1103 if let Some(first) = out.first_mut() {
1110 first.at = 0;
1111 }
1112 let known = origin.meaning.funcs.get(&extent.name);
1118 let decl = known.map(|known| rucc_debug::Place {
1119 file: interned(&mut files, rewrite(&known.file)),
1120 line: known.line,
1121 });
1122 let mut sig = known.and_then(|known| known.sig.clone());
1131 let mut placed: Vec<(u32, i32)> = built.locals.clone();
1132 let mut spots = stretches(extent, rows, built, target);
1133 for &decl in &built.sharing {
1138 if !spots.iter().any(|(at, _)| *at == decl) {
1139 spots.push((decl, Vec::new()));
1140 }
1141 }
1142 if let (Some(sig), Some(known)) = (sig.as_mut(), known) {
1143 for (param, decl) in sig.params.iter_mut().zip(&known.params) {
1144 let Some(decl) = *decl else { continue };
1145 if let Some(which) = placed.iter().position(|&(at, _)| at == decl) {
1146 let at = rucc_debug::Held::Frame(i64::from(placed.remove(which).1));
1147 param.spot = Some(rucc_debug::Spot::Always(at));
1148 continue;
1149 }
1150 let Some(which) = spots.iter().position(|(at, _)| *at == decl) else { continue };
1154 param.spot = Some(rucc_debug::Spot::Over(spots.remove(which).1));
1155 }
1156 }
1157 let mut locals = Vec::with_capacity(placed.len() + spots.len());
1161 let mut wants: Vec<Option<usize>> = Vec::with_capacity(locals.capacity());
1165 for (decl, at) in placed {
1166 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1167 wants.push(named.scope);
1168 locals.push(rucc_debug::Local {
1169 name: named.name.clone(),
1170 ty: named.ty,
1171 decl: Some(rucc_debug::Place {
1172 file: interned(&mut files, rewrite(&named.file)),
1173 line: named.line,
1174 }),
1175 spot: rucc_debug::Spot::Always(rucc_debug::Held::Frame(i64::from(at))),
1176 scope: None,
1177 });
1178 }
1179 spots.sort_by_key(|(decl, _)| *decl);
1183 for (decl, spans) in spots {
1184 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1185 wants.push(named.scope);
1186 locals.push(rucc_debug::Local {
1187 name: named.name.clone(),
1188 ty: named.ty,
1189 decl: Some(rucc_debug::Place {
1190 file: interned(&mut files, rewrite(&named.file)),
1191 line: named.line,
1192 }),
1193 spot: rucc_debug::Spot::Over(spans),
1194 scope: None,
1195 });
1196 }
1197 let (scopes, at) = nests(&wants, &origin.meaning.scopes, extent, rows);
1202 for (local, want) in locals.iter_mut().zip(&wants) {
1203 local.scope = want.and_then(|want| at.get(&want).copied());
1204 }
1205 funcs.push(rucc_debug::Function {
1206 name: extent.name.clone(),
1207 len: extent.len as u64,
1208 rows: out,
1209 decl,
1210 sig,
1211 external: known.is_some_and(|known| known.external),
1212 locals,
1213 scopes,
1214 });
1215 }
1216 let mut globals = Vec::new();
1223 for object in &data.objects {
1224 let Some(held) = origin.meaning.objects.get(&object.name) else { continue };
1225 globals.push(rucc_debug::Global {
1226 name: object.name.clone(),
1227 ty: held.ty,
1228 decl: Some(rucc_debug::Place {
1229 file: interned(&mut files, rewrite(&held.file)),
1230 line: held.line,
1231 }),
1232 external: held.external,
1233 });
1234 }
1235 let unit = rucc_debug::Unit {
1236 name: rewrite(origin.name),
1237 dir: rewrite(opts.working_dir.as_deref().unwrap_or(".")),
1240 producer: format!("rucc {}", crate::VERSION),
1241 files,
1242 types: origin.meaning.types.clone(),
1243 funcs,
1244 globals,
1245 pointer: u8::try_from(target.pointer_width / 8).unwrap_or(8),
1246 frames: opts.unwinds() || frames.is_some(),
1251 };
1252 let mut info = rucc_debug::write(&unit).map_err(|why| why.to_string())?;
1253 info.chunks.extend(frames.clone());
1254 Ok(info)
1255}
1256
1257fn stretches(
1268 extent: &rucc_object::Extent,
1269 rows: &[rucc_asm::Row],
1270 built: &rucc_mir::Func,
1271 target: &TargetInfo,
1272) -> Vec<(u32, Vec<rucc_debug::Span>)> {
1273 let (false, Some(regs)) = (built.kept.is_empty(), target.call_regs) else {
1276 return Vec::new();
1277 };
1278 let ends = ends(extent, rows);
1279 let mut bounds = vec![None; built.inst_count()];
1280 for (which, row) in rows.iter().enumerate() {
1281 let Some(inst) = row.inst else { continue };
1282 bounds[inst.index()] = Some((row.at as u64, ends[which]));
1283 }
1284 let mut spots: Vec<(u32, Vec<rucc_debug::Span>)> = Vec::new();
1285 for kept in &built.kept {
1286 let (Some((from, _)), Some((_, to))) = (bounds[kept.from.index()], bounds[kept.to.index()])
1287 else {
1288 continue;
1289 };
1290 if to <= from {
1291 continue;
1292 }
1293 let held = match kept.at {
1294 rucc_mir::Where::Reg { reg, class } => match regs.dwarf(class, reg) {
1297 Some(number) => rucc_debug::Held::Reg(number),
1298 None => continue,
1299 },
1300 rucc_mir::Where::Frame(at) => rucc_debug::Held::Frame(i64::from(at)),
1301 };
1302 let span = rucc_debug::Span { from, len: to - from, held };
1303 match spots.iter_mut().find(|(decl, _)| *decl == kept.decl) {
1304 Some((_, spans)) => spans.push(span),
1305 None => spots.push((kept.decl, vec![span])),
1306 }
1307 }
1308 for (_, spans) in &mut spots {
1309 *spans = settle(std::mem::take(spans));
1310 }
1311 spots.retain(|(_, spans)| !spans.is_empty());
1312 spots
1313}
1314
1315fn ends(extent: &rucc_object::Extent, rows: &[rucc_asm::Row]) -> Vec<u64> {
1325 let mut out = vec![extent.len as u64; rows.len()];
1326 let mut next = extent.len as u64;
1327 for which in (0..rows.len()).rev() {
1328 let at = rows[which].at as u64;
1329 out[which] = match next > at {
1332 true => next,
1333 false => out.get(which + 1).copied().unwrap_or(extent.len as u64),
1334 };
1335 next = next.min(at);
1336 }
1337 out
1338}
1339
1340fn nests(
1356 wants: &[Option<usize>],
1357 scopes: &[crate::shapes::Scope],
1358 extent: &rucc_object::Extent,
1359 rows: &[rucc_asm::Row],
1360) -> (Vec<rucc_debug::Scope>, HashMap<usize, usize>) {
1361 let mut needed: Vec<usize> = Vec::new();
1362 for &want in wants {
1363 let mut up = want;
1364 while let Some(which) = up {
1365 if needed.contains(&which) {
1366 break;
1367 }
1368 needed.push(which);
1369 up = scopes.get(which).and_then(|scope| scope.parent);
1370 }
1371 }
1372 needed.sort_unstable();
1375 let at: HashMap<usize, usize> =
1376 needed.iter().enumerate().map(|(which, &scope)| (scope, which)).collect();
1377 let ends = ends(extent, rows);
1378 let out = needed
1379 .iter()
1380 .map(|&which| {
1381 let scope = &scopes[which];
1382 rucc_debug::Scope {
1383 parent: scope.parent.and_then(|parent| at.get(&parent).copied()),
1384 over: spread(scope.span, &ends, rows),
1385 }
1386 })
1387 .collect();
1388 (out, at)
1389}
1390
1391fn spread(span: Span, ends: &[u64], rows: &[rucc_asm::Row]) -> Vec<rucc_debug::Reach> {
1399 let mut out: Vec<rucc_debug::Reach> = Vec::new();
1400 for (which, row) in rows.iter().enumerate() {
1401 if row.span.is_dummy() || row.span.lo < span.lo || row.span.hi > span.hi {
1402 continue;
1403 }
1404 let (from, to) = (row.at as u64, ends[which]);
1405 if to <= from {
1406 continue;
1407 }
1408 match out.last_mut() {
1409 Some(last) if last.from + last.len >= from => {
1410 last.len = to.saturating_sub(last.from).max(last.len);
1411 }
1412 _ => out.push(rucc_debug::Reach { from, len: to - from }),
1413 }
1414 }
1415 out
1416}
1417
1418fn settle(mut spans: Vec<rucc_debug::Span>) -> Vec<rucc_debug::Span> {
1435 spans.sort_by_key(|span| (span.from, span.len));
1436 for which in 0..spans.len() {
1437 let (from, end, held) =
1438 (spans[which].from, spans[which].from + spans[which].len, spans[which].held);
1439 let later = spans[which + 1..]
1440 .iter()
1441 .take_while(|later| later.from < end)
1442 .find(|later| later.from > from && later.held != held);
1443 if let Some(later) = later {
1444 spans[which].len = later.from - from;
1445 }
1446 }
1447 let mut edges: Vec<u64> =
1450 spans.iter().flat_map(|span| [span.from, span.from + span.len]).collect();
1451 edges.sort_unstable();
1452 edges.dedup();
1453 let mut out: Vec<rucc_debug::Span> = Vec::new();
1454 let mut first = 0;
1455 for pair in edges.windows(2) {
1456 let (from, to) = (pair[0], pair[1]);
1457 while spans.get(first).is_some_and(|span| span.from + span.len <= from) {
1461 first += 1;
1462 }
1463 let mut held = None;
1464 let mut agreed = true;
1465 for span in &spans[first..] {
1466 if span.from >= to {
1467 break;
1468 }
1469 if span.from > from || span.from + span.len < to {
1470 continue;
1471 }
1472 match held {
1473 None => held = Some(span.held),
1474 Some(seen) => agreed &= seen == span.held,
1475 }
1476 }
1477 let (Some(held), true) = (held, agreed) else { continue };
1478 match out.last_mut() {
1479 Some(last) if last.from + last.len == from && last.held == held => {
1480 last.len += to - from
1481 }
1482 _ => out.push(rucc_debug::Span { from, len: to - from, held }),
1483 }
1484 }
1485 out
1486}
1487
1488fn interned(files: &mut Vec<String>, name: String) -> usize {
1494 match files.iter().position(|have| *have == name) {
1495 Some(which) => which,
1496 None => {
1497 files.push(name);
1498 files.len() - 1
1499 }
1500 }
1501}
1502
1503fn output(opts: &Options, target: &TargetInfo) -> rucc_object::Output {
1515 let mut features = 0;
1516 if target.tuple.arch() == Arch::X86_64 {
1517 if opts.control.branch() {
1518 features |= rucc_object::Property::IBT;
1519 }
1520 if opts.control.ret() {
1521 features |= rucc_object::Property::SHSTK;
1522 }
1523 }
1524 rucc_object::Output {
1525 sections: rucc_object::Sections {
1526 functions: opts.function_sections,
1527 data: opts.data_sections,
1528 },
1529 property: rucc_object::Property { features },
1530 }
1531}
1532
1533fn wrote(why: rucc_object::Error) -> Vec<Diagnostic> {
1539 match why {
1540 rucc_object::Error::Format { .. } => vec![unsupported(&why.to_string())],
1541 rucc_object::Error::Refused { .. } => vec![internal(&why.to_string())],
1542 }
1543}
1544
1545fn refused(why: rucc_asm::Error) -> Vec<Diagnostic> {
1552 match why {
1553 rucc_asm::Error::Thread { .. }
1554 | rucc_asm::Error::IFunc { .. }
1555 | rucc_asm::Error::Frame { .. } => {
1556 vec![unsupported(&why.to_string())]
1557 }
1558 _ => vec![internal(&why.to_string())],
1559 }
1560}
1561
1562fn unsupported(message: &str) -> Diagnostic {
1568 unsupported_at(message, Span::DUMMY)
1569}
1570
1571fn unsupported_at(message: &str, span: Span) -> Diagnostic {
1577 Diagnostic::error(message.to_owned(), span)
1578 .with_code("E0653")
1579 .note("this construct is not lowered yet, see https://github.com/tamnd/rucc/issues", span)
1580}
1581
1582fn invalid(message: &str) -> Diagnostic {
1584 Diagnostic::error(message.to_owned(), Span::DUMMY).with_code("E0661")
1585}
1586
1587fn internal(message: &str) -> Diagnostic {
1589 Diagnostic::error(format!("internal error: {message}"), Span::DUMMY)
1590 .with_code("E0652")
1591 .note("this is a bug in rucc rather than in the program, please report it", Span::DUMMY)
1592}
1593
1594fn failure(message: String) -> Compiled {
1597 Compiled {
1598 artifact: Artifact::Nothing,
1599 messages: vec![format!("rucc: error: {message}")],
1600 errors: 1,
1601 fired: Fired::new(),
1602 pressure: Pressure::new(),
1603 lowerings: Lowerings::new(),
1604 dumps: Vec::new(),
1605 remarks: String::new(),
1606 deps: Vec::new(),
1607 temps: Temps::default(),
1608 }
1609}
1610
1611#[cfg(test)]
1612mod tests {
1613 use rucc_session::{MemoryFileSystem, Std};
1614 use rucc_target::Triple;
1615
1616 use super::*;
1617
1618 fn options() -> Options {
1619 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1620 opts.emit = EmitKind::Tast;
1621 opts
1622 }
1623
1624 fn run(opts: &Options, source: &str) -> Compiled {
1625 let mut fs = MemoryFileSystem::new();
1626 fs.insert("/main.c", source.to_owned().into_bytes());
1627 compile(opts, "/main.c", &fs)
1628 }
1629
1630 fn freestanding() -> Options {
1634 let mut opts = options();
1635 opts.hosted = false;
1636 opts.search.push_system(rucc_session::runtime::DIR);
1637 opts
1638 }
1639
1640 fn shipped(source: &str) -> String {
1642 let result = run(&freestanding(), source);
1643 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1644 result.text().to_owned()
1645 }
1646
1647 fn tast(source: &str) -> String {
1649 let result = run(&options(), source);
1650 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1651 result.text().to_owned()
1652 }
1653
1654 #[test]
1655 fn the_shipped_stdarg_declares_a_list_and_the_four_operators() {
1656 let text = shipped(concat!(
1657 "#include <stdarg.h>\n",
1658 "int sum(int n, ...) {\n",
1659 " va_list ap, copy;\n",
1660 " va_start(ap, n);\n",
1661 " va_copy(copy, ap);\n",
1662 " int total = va_arg(ap, int) + va_arg(copy, int);\n",
1663 " va_end(ap);\n",
1664 " va_end(copy);\n",
1665 " return total;\n",
1666 "}\n",
1667 ));
1668 assert!(text.contains("va-start"), "{text}");
1669 assert!(text.contains("va-copy"), "{text}");
1670 assert!(text.contains("va-arg"), "{text}");
1671 assert!(text.contains("va-end"), "{text}");
1672 }
1673
1674 #[test]
1678 fn stdarg_hands_out_the_type_alone_when_that_is_all_that_was_asked_for() {
1679 let text = shipped(concat!(
1680 "#define __need___va_list\n",
1681 "#include <stdarg.h>\n",
1682 "int vprint(const char *f, __gnuc_va_list ap);\n",
1683 "#ifdef va_start\n",
1684 "#error va_start should not be defined\n",
1685 "#endif\n",
1686 "#ifdef _VA_LIST_DEFINED\n",
1687 "#error va_list should not have been made\n",
1688 "#endif\n",
1689 ));
1690 assert!(text.contains("vprint"), "{text}");
1691 }
1692
1693 #[test]
1696 fn stddef_answers_one_piece_at_a_time_and_the_next_request_still_gets_through() {
1697 let text = shipped(concat!(
1698 "#define __need_size_t\n",
1699 "#include <stddef.h>\n",
1700 "#ifdef offsetof\n",
1701 "#error offsetof should not be defined yet\n",
1702 "#endif\n",
1703 "#define __need_ptrdiff_t\n",
1704 "#include <stddef.h>\n",
1705 "#include <stddef.h>\n",
1706 "size_t a;\n",
1707 "ptrdiff_t b;\n",
1708 "wchar_t c;\n",
1709 "max_align_t d;\n",
1710 "void *e = NULL;\n",
1711 "struct P { int x; long y; };\n",
1712 "size_t f = offsetof(struct P, y);\n",
1713 ));
1714 assert!(text.contains("decl #0 a : unsigned long"), "{text}");
1715 assert!(text.contains("decl #1 b : long"), "{text}");
1716 }
1717
1718 #[test]
1719 fn the_shipped_limits_and_float_are_the_targets_own_answers() {
1720 let text = shipped(concat!(
1721 "#include <limits.h>\n",
1722 "#include <float.h>\n",
1723 "int bits = CHAR_BIT;\n",
1724 "long big = LONG_MAX;\n",
1725 "int low = INT_MIN;\n",
1726 "int radix = FLT_RADIX;\n",
1727 "int digits = DBL_MANT_DIG;\n",
1728 ));
1729 assert!(text.contains("const 8 : int"), "{text}");
1730 assert!(text.contains("const 9223372036854775807 : long"), "{text}");
1731 assert!(text.contains("const 2 : int"), "{text}");
1732 assert!(text.contains("const 53 : int"), "{text}");
1733 }
1734
1735 #[test]
1739 fn the_shipped_stdint_writes_the_whole_set_when_there_is_no_library_to_defer_to() {
1740 let text = shipped(concat!(
1741 "#include <stdint.h>\n",
1742 "int64_t a = INT64_C(1);\n",
1743 "uint_least16_t b;\n",
1744 "intptr_t c;\n",
1745 "uintmax_t d = UINTMAX_MAX;\n",
1746 "int wide = sizeof(int_fast64_t);\n",
1747 ));
1748 assert!(text.contains("decl #0 a : long"), "{text}");
1749 assert!(text.contains("decl #1 b : unsigned short"), "{text}");
1750 assert!(text.contains("decl #2 c : long"), "{text}");
1751 }
1752
1753 #[test]
1764 fn the_shipped_mmintrin_defines_the_mmx_type_and_the_operations_over_it() {
1765 let text = shipped(concat!(
1766 "#include <mmintrin.h>\n",
1767 "__m64 add(__m64 a, __m64 b) { return _mm_add_pi16(a, b); }\n",
1768 "__m64 pack(__m64 a, __m64 b) { return _m_packsswb(a, b); }\n",
1769 "__m64 shift(__m64 a) { return _mm_srai_pi32(a, 3); }\n",
1770 "int low(__m64 a) { return _mm_cvtsi64_si32(a); }\n",
1771 "void done(void) { _mm_empty(); }\n",
1772 ));
1773 assert!(text.contains("add"), "{text}");
1774 assert!(text.contains("pack"), "{text}");
1775 assert!(text.contains("shift"), "{text}");
1776 }
1777
1778 #[test]
1783 fn the_shipped_mm_malloc_asks_for_aligned_memory_and_gives_it_back() {
1784 let text = shipped(concat!(
1785 "#include <mm_malloc.h>\n",
1786 "void *get(void) { return _mm_malloc(64, 16); }\n",
1787 "void put(void *p) { _mm_free(p); }\n",
1788 ));
1789 assert!(text.contains("get"), "{text}");
1790 assert!(text.contains("put"), "{text}");
1791 }
1792
1793 #[test]
1805 fn the_shipped_xmmintrin_defines_the_sse_type_and_the_operations_over_it() {
1806 let text = shipped(concat!(
1807 "#include <xmmintrin.h>\n",
1808 "__m128 add(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1809 "__m128 one(__m128 a, __m128 b) { return _mm_max_ss(a, b); }\n",
1810 "__m128 mask(__m128 a, __m128 b) { return _mm_cmpnle_ps(a, b); }\n",
1811 "__m128 pick(__m128 a, __m128 b) { return _mm_shuffle_ps(a, b, _MM_SHUFFLE(0,1,2,3)); }\n",
1812 "int bits(__m128 a) { return _mm_movemask_ps(a); }\n",
1813 "int near(__m128 a) { return _mm_cvtss_si32(a); }\n",
1814 "__m128 wide(__m64 a) { return _mm_cvtpi16_ps(a); }\n",
1815 "void *room(void) { return _mm_malloc(64, 16); }\n",
1816 "void hint(const float *p) { _mm_prefetch(p, _MM_HINT_T0); _mm_sfence(); }\n",
1817 ));
1818 assert!(text.contains("add"), "{text}");
1819 assert!(text.contains("mask"), "{text}");
1820 assert!(text.contains("pick"), "{text}");
1821 assert!(text.contains("wide"), "{text}");
1822 }
1823
1824 #[test]
1831 fn the_shipped_xmmintrin_leaves_out_the_names_that_need_an_instruction() {
1832 let text = rucc_session::runtime::header("xmmintrin.h").expect("xmmintrin.h is shipped");
1833 for absent in [
1834 "_mm_sqrt_ps",
1835 "_mm_sqrt_ss",
1836 "_mm_rsqrt_ps",
1837 "_mm_rsqrt_ss",
1838 "_mm_getcsr",
1839 "_mm_setcsr",
1840 ] {
1841 let defined = text.contains(&format!("{absent}("));
1842 assert!(!defined, "{absent} is defined and the header says it is not");
1843 assert!(text.contains(absent), "{absent} is absent and unexplained");
1844 }
1845 }
1846
1847 #[test]
1848 fn the_shipped_emmintrin_defines_both_sse2_types_and_the_operations_over_them() {
1849 let text = shipped(concat!(
1850 "#include <emmintrin.h>\n",
1851 "__m128i add(__m128i a, __m128i b) { return _mm_add_epi64(a, b); }\n",
1852 "__m128i wide(__m128i a, __m128i b) { return _mm_mul_epu32(a, b); }\n",
1853 "__m128i pick(__m128i a) { return _mm_shuffle_epi32(a, _MM_SHUFFLE(0,1,2,3)); }\n",
1854 "__m128i up(__m128i a) { return _mm_slli_epi64(a, 13); }\n",
1855 "__m128i down(__m128i a) { return _mm_srli_si128(a, 3); }\n",
1856 "__m128i pack(__m128i a, __m128i b) { return _mm_packus_epi16(a, b); }\n",
1857 "int bits(__m128i a) { return _mm_movemask_epi8(a); }\n",
1858 "__m128d sum(__m128d a, __m128d b) { return _mm_add_sd(a, b); }\n",
1859 "__m128d mask(__m128d a, __m128d b) { return _mm_cmpunord_pd(a, b); }\n",
1860 "__m128i near(__m128d a) { return _mm_cvtpd_epi32(a); }\n",
1861 "__m128d over(__m128 a) { return _mm_cvtps_pd(a); }\n",
1862 "__m128i half(__m64 a) { return _mm_movpi64_epi64(a); }\n",
1863 "__m128i grab(void const *p) { return _mm_loadu_si128(p); }\n",
1864 "void wall(void) { _mm_lfence(); _mm_mfence(); }\n",
1865 ));
1866 assert!(text.contains("wide"), "{text}");
1867 assert!(text.contains("pack"), "{text}");
1868 assert!(text.contains("near"), "{text}");
1869 assert!(text.contains("half"), "{text}");
1870 }
1871
1872 #[test]
1876 fn the_shipped_immintrin_reaches_the_names_the_headers_under_it_define() {
1877 let text = shipped(concat!(
1878 "#include <immintrin.h>\n",
1879 "unsigned long long matching(unsigned char tag, unsigned char const *bucket) {\n",
1880 " __m128i const want = _mm_set1_epi8((char)tag);\n",
1881 " __m128i const chunk = _mm_loadu_si128((__m128i const *)(void const *)bucket);\n",
1882 " __m128i const same = _mm_cmpeq_epi8(chunk, want);\n",
1883 " return (unsigned long long)_mm_movemask_epi8(same);\n",
1884 "}\n",
1885 "__m64 narrow(__m64 a, __m64 b) { return _mm_add_pi32(a, b); }\n",
1886 "__m128 single(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1887 ));
1888 assert!(text.contains("matching"), "{text}");
1889 assert!(text.contains("narrow"), "the MMX header is not reached: {text}");
1890 assert!(text.contains("single"), "the SSE header is not reached: {text}");
1891 }
1892
1893 #[test]
1897 fn the_shipped_x86intrin_reaches_the_fences_windows_headers_ask_it_for() {
1898 let text = shipped(concat!(
1899 "#include <x86intrin.h>\n",
1900 "void barriers(void *p) {\n",
1901 " _mm_lfence();\n",
1902 " _mm_sfence();\n",
1903 " _mm_mfence();\n",
1904 " _mm_pause();\n",
1905 " _mm_clflush(p);\n",
1906 "}\n",
1907 "__m128i wide(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1908 ));
1909 assert!(text.contains("barriers"), "{text}");
1910 assert!(text.contains("wide"), "the SSE2 header is not reached: {text}");
1911 }
1912
1913 #[test]
1917 fn the_umbrella_and_the_header_under_it_can_both_be_included() {
1918 let text = shipped(concat!(
1919 "#include <immintrin.h>\n",
1920 "#include <emmintrin.h>\n",
1921 "#include <immintrin.h>\n",
1922 "#include <x86intrin.h>\n",
1923 "__m128i twice(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1924 ));
1925 assert!(text.contains("twice"), "{text}");
1926 }
1927
1928 #[test]
1932 fn the_shipped_emmintrin_leaves_out_the_two_square_roots() {
1933 let text = rucc_session::runtime::header("emmintrin.h").expect("emmintrin.h is shipped");
1934 for absent in ["_mm_sqrt_pd", "_mm_sqrt_sd"] {
1935 let defined = text.contains(&format!("{absent}("));
1936 assert!(!defined, "{absent} is defined and the header says it is not");
1937 assert!(text.contains(absent), "{absent} is absent and unexplained");
1938 }
1939 }
1940
1941 #[test]
1942 fn the_three_formality_headers_still_have_to_work() {
1943 let text = shipped(concat!(
1944 "#include <stdbool.h>\n",
1945 "#include <stdalign.h>\n",
1946 "#include <iso646.h>\n",
1947 "#include <stdnoreturn.h>\n",
1948 "int t = true and not false;\n",
1949 "_Alignas(16) char buf[16];\n",
1950 "int a = alignof(long);\n",
1951 ));
1952 assert!(text.contains("decl #0 t : int"), "{text}");
1953 assert!(text.contains("const 8 : unsigned long"), "{text}");
1954 }
1955
1956 #[test]
1964 fn every_shipped_header_can_be_included_twice() {
1965 let once: String = rucc_session::runtime::names()
1966 .iter()
1967 .map(|name| format!("#include <{name}>\n"))
1968 .collect();
1969 let twice = once.repeat(2);
1970 assert_eq!(shipped(&format!("{once}int x;\n")), shipped(&format!("{twice}int x;\n")));
1971 }
1972
1973 #[test]
1974 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
1975 let fs = MemoryFileSystem::new();
1976 let result = compile(&options(), "/nope.c", &fs);
1977 assert!(result.failed());
1978 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
1979 assert!(result.text().is_empty());
1980 }
1981
1982 #[test]
1983 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
1984 let text = tast("int x = 1;\n");
1985 let expected = "\
1986decl #0 x : int object external static defined
1987 init
1988 +0
1989 const 1 : int
1990";
1991 assert_eq!(text, expected);
1992 }
1993
1994 #[test]
1995 fn the_macros_are_expanded_before_anything_is_parsed() {
1996 let text = tast("#define N 2\nint a[N];\n");
2000 assert!(text.starts_with("decl #0 a : int[2] object external static tentative"), "{text}");
2001 }
2002
2003 #[test]
2009 fn a_pragma_is_not_a_declaration_and_the_parse_walks_past_the_ones_it_does_not_read() {
2010 let text = tast(concat!(
2011 "#pragma pack(4)\n",
2012 "struct s { int a; };\n",
2013 "#pragma pack()\n",
2014 "int b;\n",
2015 "_Pragma(\"GCC visibility push(default)\") int c;\n",
2016 ));
2017 assert!(text.contains("decl #0 b : int"), "{text}");
2018 assert!(text.contains("decl #1 c : int"), "{text}");
2019 }
2020
2021 #[test]
2029 fn the_layout_attributes_move_the_members_and_the_record_the_way_gcc_lays_them_out() {
2030 tast(concat!(
2031 "struct A { char c; int i; } __attribute__((packed));\n",
2032 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2033 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2034 "struct B { char c; int i; } __attribute__((aligned));\n",
2037 "_Static_assert(sizeof(struct B) == 16 && _Alignof(struct B) == 16, \"B\");\n",
2038 "struct C { char c; int i __attribute__((packed)); };\n",
2039 "_Static_assert(sizeof(struct C) == 5 && _Alignof(struct C) == 1, \"C\");\n",
2040 "_Static_assert(__builtin_offsetof(struct C, i) == 1, \"C.i\");\n",
2041 "struct D { char c; int i; } __attribute__((packed, aligned(4)));\n",
2042 "_Static_assert(sizeof(struct D) == 8 && _Alignof(struct D) == 4, \"D\");\n",
2043 "_Static_assert(__builtin_offsetof(struct D, i) == 1, \"D.i\");\n",
2044 "struct E { char c; _Alignas(8) int i; };\n",
2045 "_Static_assert(sizeof(struct E) == 16 && _Alignof(struct E) == 8, \"E\");\n",
2046 "_Static_assert(__builtin_offsetof(struct E, i) == 8, \"E.i\");\n",
2047 "struct F { char c; int i __attribute__((aligned(8))); };\n",
2048 "_Static_assert(sizeof(struct F) == 16 && _Alignof(struct F) == 8, \"F\");\n",
2049 "struct G { char c; short s; } __attribute__((aligned(2)));\n",
2052 "_Static_assert(sizeof(struct G) == 4 && _Alignof(struct G) == 2, \"G\");\n",
2053 "struct H { char c; int i; } __attribute__((aligned(2)));\n",
2054 "_Static_assert(sizeof(struct H) == 8 && _Alignof(struct H) == 4, \"H\");\n",
2055 "struct I { [[gnu::packed]] char c; int i; };\n",
2058 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2059 "struct J { char c; [[gnu::packed]] int i; };\n",
2060 "_Static_assert(sizeof(struct J) == 5 && _Alignof(struct J) == 1, \"J\");\n",
2061 "struct M { char c; int i : 5; int j : 20; } __attribute__((packed));\n",
2062 "_Static_assert(sizeof(struct M) == 5 && _Alignof(struct M) == 1, \"M\");\n",
2063 "struct N { char c; long long l; } __attribute__((aligned(32)));\n",
2064 "_Static_assert(sizeof(struct N) == 32 && _Alignof(struct N) == 32, \"N\");\n",
2065 "union L { char c; int i; } __attribute__((packed));\n",
2066 "_Static_assert(sizeof(union L) == 4 && _Alignof(union L) == 1, \"L\");\n",
2067 "struct O { char c; int i; } __attribute__((__packed__));\n",
2071 "_Static_assert(sizeof(struct O) == 5 && _Alignof(struct O) == 1, \"O\");\n",
2072 "struct P { char c; int i; } __attribute__((__aligned__(8)));\n",
2073 "_Static_assert(sizeof(struct P) == 8 && _Alignof(struct P) == 8, \"P\");\n",
2074 ));
2075 }
2076
2077 #[test]
2090 fn a_transparent_union_takes_the_member_a_value_fits_and_is_declared_either_way() {
2091 let text = tast(concat!(
2092 "struct one { int x; };\n",
2093 "struct two { long y; };\n",
2094 "typedef union { struct one *a; struct two *b; void *any; }\n",
2095 " __attribute__((__transparent_union__)) arg;\n",
2096 "int takes(arg v);\n",
2097 "int f(struct one *p, struct two *q, char *c) {\n",
2098 " return takes(p) + takes(q) + takes(c) + takes(0);\n",
2099 "}\n",
2100 "int takes(struct one *p);\n",
2102 "int (*as_a_member)(struct one *) = takes;\n",
2103 "int (*as_the_union)(arg) = takes;\n",
2104 ));
2105 assert!(text.contains("compound-literal"), "{text}");
2106 }
2107
2108 #[test]
2114 fn the_attribute_on_the_declarator_of_a_typedef_is_the_one_glibc_writes() {
2115 let text = tast(concat!(
2116 "struct sockaddr { int family; };\n",
2117 "struct sockaddr_in { int family; int addr; };\n",
2118 "typedef union { struct sockaddr *plain; struct sockaddr_in *inet; }\n",
2119 " addr_arg __attribute__((__transparent_union__));\n",
2120 "int bind_to(int fd, addr_arg where);\n",
2121 "int f(struct sockaddr_in *where) { return bind_to(0, where); }\n",
2122 ));
2123 assert!(text.contains("compound-literal"), "{text}");
2124 }
2125
2126 #[test]
2134 fn a_transparent_union_that_cannot_keep_the_promise_is_dropped_with_a_word_about_it() {
2135 let result = run(
2136 &options(),
2137 concat!(
2138 "union wider { int small; double large; } __attribute__((transparent_union));\n",
2139 "struct plain { int x; } __attribute__((transparent_union));\n",
2140 ),
2141 );
2142 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
2143 assert!(!result.failed(), "{:?}", result.messages);
2144 for message in &result.messages {
2145 assert!(message.contains("'transparent_union' attribute ignored"), "{message}");
2146 }
2147 assert!(result.messages[0].contains("first member"), "{:?}", result.messages);
2148 assert!(result.messages[1].contains("only a union"), "{:?}", result.messages);
2149 }
2150
2151 #[test]
2160 fn an_access_to_a_packed_member_says_the_alignment_the_layout_left_it() {
2161 let packed = body(concat!(
2162 "struct P { char c; int v; } __attribute__((packed));\n",
2163 "int f(struct P *p) { return p->v; }\n",
2164 ));
2165 assert!(packed.contains("load.i32 %2, align 1,"), "{packed}");
2166 let plain = body(concat!(
2168 "struct P { char c; int v; };\n",
2169 "int f(struct P *p) { return p->v; }\n",
2170 ));
2171 assert!(plain.contains("load.i32 %2, align 4,"), "{plain}");
2172 }
2173
2174 #[test]
2181 fn what_is_inside_a_packed_member_is_no_more_aligned_than_the_member_is() {
2182 let stepped = body(concat!(
2183 "struct P { char c; int v[4]; } __attribute__((packed));\n",
2184 "int f(struct P *p, int i) { return p->v[i]; }\n",
2185 ));
2186 assert!(stepped.contains(", align 1,"), "{stepped}");
2187 assert!(!stepped.contains(", align 4,"), "{stepped}");
2188 let nested = body(concat!(
2189 "struct Inner { int v; };\n",
2190 "struct P { char c; struct Inner in; } __attribute__((packed));\n",
2191 "int f(struct P *p) { return p->in.v; }\n",
2192 ));
2193 assert!(nested.contains(", align 1,"), "{nested}");
2194 assert!(!nested.contains(", align 4,"), "{nested}");
2195 }
2196
2197 #[test]
2213 fn an_access_through_a_typedef_that_lowered_its_alignment_says_the_one_the_typedef_asked_for() {
2214 let through = body(concat!(
2215 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2216 "unsigned int f(const void *p) { return *(const unalign32 *)p; }\n",
2217 ));
2218 assert!(through.contains("load.i32 %0, align 1,"), "{through}");
2219 let stepped = body(concat!(
2222 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2223 "unsigned int f(unalign32 *p, int i) { return p[i]; }\n",
2224 ));
2225 assert!(stepped.contains(", align 1,"), "{stepped}");
2226 assert!(!stepped.contains(", align 4,"), "{stepped}");
2227 let plain = body(concat!(
2230 "typedef unsigned int word;\n",
2231 "unsigned int f(const void *p) { return *(const word *)p; }\n",
2232 ));
2233 assert!(plain.contains("load.i32 %0, align 4,"), "{plain}");
2234 }
2235
2236 #[test]
2247 fn a_vector_read_through_a_typedef_that_lowered_its_alignment_comes_back_a_piece_at_a_time() {
2248 let prefix = concat!(
2249 "typedef long long v2di __attribute__((__vector_size__(16)));\n",
2250 "typedef long long v2di_u __attribute__((__vector_size__(16), __aligned__(1)));\n",
2251 );
2252 let loaded =
2253 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di_u *)p; }}"));
2254 assert_eq!(loaded.matches("align 1\n").count(), 2, "{loaded}");
2255 assert!(!loaded.contains("align 16"), "{loaded}");
2256 let stored = body(&format!("{prefix}void f(void *p, v2di b) {{ *(v2di_u *)p = b; }}"));
2259 assert!(stored.contains("memcpy %0, %3, size 16, align 1"), "{stored}");
2260 let aligned =
2262 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di *)p; }}"));
2263 assert!(aligned.contains("align 16"), "{aligned}");
2264 }
2265
2266 #[test]
2275 fn the_aligned_attribute_on_a_declaration_raises_what_that_one_object_is_aligned_to() {
2276 tast(concat!(
2277 "int v __attribute__((aligned(64)));\n",
2278 "_Static_assert(__alignof__(v) == 64, \"v\");\n",
2279 "__attribute__((aligned(32))) int w;\n",
2282 "_Static_assert(__alignof__(w) == 32, \"w\");\n",
2283 "[[gnu::aligned(16)]] int x;\n",
2284 "_Static_assert(__alignof__(x) == 16, \"x\");\n",
2285 "int y __attribute__((aligned(2)));\n",
2288 "_Static_assert(__alignof__(y) == 4, \"y\");\n",
2289 "void f(void) { int a __attribute__((aligned(128)));\n",
2291 "_Static_assert(__alignof__(a) == 128, \"a\"); (void)a; }\n",
2292 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2295 "void g(void) __attribute__((aligned(256)));\n",
2298 "void g(void) {}\n",
2299 "_Static_assert(__alignof__(g) == 256, \"g\");\n",
2300 ));
2301 }
2302
2303 #[test]
2307 fn what_a_declaration_asked_to_be_aligned_to_is_what_the_assembler_is_told() {
2308 let text = asm(concat!(
2309 "int v __attribute__((aligned(64)));\n",
2310 "void g(void) __attribute__((aligned(256)));\n",
2311 "void g(void) {}\n",
2312 "void plain(void) {}\n",
2313 ));
2314 assert!(text.contains("\t.p2align\t6\n\t.type\tv, @object\n"), "{text}");
2315 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2316 assert!(text.contains("\t.p2align\t4, 0x90\n\t.globl\tplain\n"), "{text}");
2317 }
2318
2319 #[test]
2325 fn the_alignment_the_command_line_asked_of_every_function_is_a_floor_under_all_of_them() {
2326 let source = concat!(
2327 "void g(void) __attribute__((aligned(256)));\n",
2328 "void g(void) {}\n",
2329 "void small(void) __attribute__((aligned(4)));\n",
2330 "void small(void) {}\n",
2331 "void plain(void) {}\n",
2332 );
2333 let listing = |align: Option<u32>| {
2334 let mut opts = options();
2335 opts.emit = EmitKind::Asm;
2336 opts.align_functions = align;
2337 let result = run(&opts, source);
2338 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
2339 result.text().to_owned()
2340 };
2341
2342 let text = listing(Some(32));
2343 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "the larger one wins: {text}");
2344 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tsmall\n"), "{text}");
2345 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tplain\n"), "{text}");
2346
2347 let text = listing(Some(8));
2350 assert!(text.contains("\t.p2align\t3, 0x90\n\t.globl\tplain\n"), "{text}");
2351 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2352 }
2353
2354 #[test]
2363 fn an_aligned_typedef_says_what_an_object_of_it_is_aligned_to_and_may_lower_it() {
2364 tast(concat!(
2365 "typedef int L __attribute__((aligned(2)));\n",
2366 "_Static_assert(__alignof__(L) == 2, \"L\");\n",
2367 "_Static_assert(_Alignof(L) == 2, \"L alignof\");\n",
2368 "_Static_assert(sizeof(L) == 4, \"L size\");\n",
2370 "struct T { char c; L x; };\n",
2371 "_Static_assert(sizeof(struct T) == 6, \"T\");\n",
2372 "_Static_assert(__builtin_offsetof(struct T, x) == 2, \"T.x\");\n",
2373 "typedef int H __attribute__((aligned(16)));\n",
2375 "_Static_assert(__alignof__(H) == 16, \"H\");\n",
2376 "_Static_assert(sizeof(H) == 4, \"H size\");\n",
2377 "struct U { char c; H x; };\n",
2378 "_Static_assert(sizeof(struct U) == 32, \"U\");\n",
2379 "_Static_assert(__builtin_offsetof(struct U, x) == 16, \"U.x\");\n",
2380 "typedef L M __attribute__((aligned(8)));\n",
2383 "_Static_assert(__alignof__(M) == 8, \"M\");\n",
2384 "typedef L N;\n",
2387 "_Static_assert(__alignof__(N) == 2, \"N\");\n",
2388 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2390 ));
2391 let text = asm(concat!(
2392 "typedef int L __attribute__((aligned(2)));\n",
2393 "typedef int H __attribute__((aligned(16)));\n",
2394 "L low;\n",
2395 "H high;\n",
2396 ));
2397 assert!(text.contains("\t.p2align\t1\n\t.type\tlow, @object\n"), "{text}");
2398 assert!(text.contains("\t.p2align\t4\n\t.type\thigh, @object\n"), "{text}");
2399 }
2400
2401 #[test]
2409 fn the_vector_size_attribute_builds_a_type_of_lanes_and_measures_it_in_bytes() {
2410 tast(concat!(
2411 "typedef int __attribute__((vector_size(16))) v4si;\n",
2412 "_Static_assert(sizeof(v4si) == 16 && _Alignof(v4si) == 16, \"v4si\");\n",
2413 "typedef char __attribute__((vector_size(16))) v16qi;\n",
2414 "_Static_assert(sizeof(v16qi) == 16, \"v16qi\");\n",
2415 "typedef int __attribute__((vector_size(4))) v1si;\n",
2418 "_Static_assert(sizeof(v1si) == 4, \"v1si\");\n",
2419 "typedef float __attribute__((__vector_size__(8))) v2sf;\n",
2421 "_Static_assert(sizeof(v2sf) == 8, \"v2sf\");\n",
2422 "typedef short [[gnu::vector_size(8)]] v4hi;\n",
2423 "_Static_assert(sizeof(v4hi) == 8, \"v4hi\");\n",
2424 "v4si g;\n",
2427 "_Static_assert(sizeof(g[0]) == 4, \"lane\");\n",
2428 "_Static_assert(sizeof(g + g) == 16, \"whole\");\n",
2429 "_Static_assert(sizeof(g + 1) == 16, \"broadcast\");\n",
2432 "_Static_assert(sizeof(v4si[3]) == 48, \"array\");\n",
2434 ));
2435 }
2436
2437 #[test]
2447 fn a_vector_is_written_whole_into_an_array_of_them_and_named_by_a_type_name() {
2448 tast(concat!(
2449 "typedef int __attribute__((vector_size(8))) v2si;\n",
2450 "v2si table[] = { (v2si){ 1, 2 }, (v2si){ 3, 4 } };\n",
2451 "_Static_assert(sizeof(table) == 16, \"two of them and not eight lanes\");\n",
2452 "v2si written = (int __attribute__((vector_size(8)))){ 5, 6 };\n",
2454 "_Static_assert(sizeof((int __attribute__((vector_size(16)))){ 0 }) == 16, \"named\");\n",
2455 "v2si lanes[2] = { 1, 2, 3, 4 };\n",
2458 "_Static_assert(sizeof(lanes) == 16, \"still elided\");\n",
2459 ));
2460 }
2461
2462 #[test]
2470 fn a_lane_is_assignable_and_a_shift_takes_a_count_of_its_own_lane() {
2471 let result = run(
2472 &options(),
2473 concat!(
2474 "typedef int __attribute__((vector_size(16))) v4si;\n",
2475 "typedef unsigned __attribute__((vector_size(16))) v4ui;\n",
2476 "void write(v4si *out, v4ui a, v4si b, int n) {\n",
2477 " v4si v = { 1, 2, 3, 4 };\n",
2478 " v[0] = n;\n",
2479 " v[1] += n;\n",
2480 " v[2]++;\n",
2481 " *&v[3] = n;\n",
2482 " v4ui shifted = a >> b;\n",
2484 " shifted <<= b;\n",
2485 " *out = v + (v4si)shifted + (1 << b);\n",
2488 "}\n",
2489 "void refused(const v4si c) {\n",
2492 " c[0] = 1;\n",
2493 "}\n",
2494 ),
2495 );
2496 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
2497 assert!(result.messages[0].contains("assignment of read-only"), "{:?}", result.messages);
2498 }
2499
2500 #[test]
2512 fn a_record_that_asks_for_the_other_byte_order_swaps_every_scalar_it_holds() {
2513 let read = "int f(struct s *p) { return p->i; }\n";
2514 let big = "struct s { int i; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
2515 assert!(body(&format!("{big}{read}")).contains("bswap"), "{big}");
2516
2517 let armoured =
2518 "struct s { int i; } __attribute__((__scalar_storage_order__(\"big-endian\")));\n";
2519 assert!(body(&format!("{armoured}{read}")).contains("bswap"), "{armoured}");
2520
2521 let front = "struct __attribute__((scalar_storage_order(\"big-endian\"))) s { int i; };\n";
2522 assert!(body(&format!("{front}{read}")).contains("bswap"), "{front}");
2523
2524 let standard = "struct s { int i; } [[gnu::scalar_storage_order(\"big-endian\")]];\n";
2525 assert!(body(&format!("{standard}{read}")).contains("bswap"), "{standard}");
2526
2527 let same =
2528 "struct s { int i; } __attribute__((scalar_storage_order(\"little-endian\")));\n";
2529 assert!(!body(&format!("{same}{read}")).contains("bswap"), "{same}");
2530
2531 let byte = "struct s { char c; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
2533 let source = format!("{byte}int f(struct s *p) {{ return p->c; }}\n");
2534 assert!(!body(&source).contains("bswap"), "{byte}");
2535
2536 tast(concat!(
2537 "struct s { int i; short h; char c; }",
2538 " __attribute__((scalar_storage_order(\"big-endian\")));\n",
2539 "_Static_assert(sizeof(struct s) == 8 && _Alignof(struct s) == 4, \"s\");\n",
2540 "_Static_assert(__builtin_offsetof(struct s, h) == 4, \"s.h\");\n",
2541 "_Static_assert(__builtin_offsetof(struct s, c) == 6, \"s.c\");\n",
2542 ));
2543 }
2544
2545 #[test]
2551 fn a_bit_field_in_one_of_those_records_is_counted_from_the_top_of_its_bytes() {
2552 let members = "short i : 12; char c1 : 1; char c2 : 1; char c3 : 1; char c4 : 1;";
2553 let read = "int f(struct s *p) { return p->i; }\n";
2554 let plain = format!("struct s {{ {members} }};\n{read}");
2555 let reversed = format!(
2556 "struct s {{ {members} }} __attribute__((scalar_storage_order(\"big-endian\")));\n\
2557 {read}"
2558 );
2559 assert!(body(&plain).contains("shl"), "{}", body(&plain));
2560 assert!(!body(&plain).contains("bswap"), "{}", body(&plain));
2561 let built = body(&reversed);
2564 assert!(built.contains("bswap"), "{built}");
2565 assert!(!built.contains("shl"), "{built}");
2566 assert!(built.contains("ashr"), "{built}");
2567 }
2568
2569 #[test]
2576 fn the_address_of_a_scalar_stored_the_other_way_round_is_refused() {
2577 let opts = options();
2578 let record = "struct s { int i; int a[2]; struct in { int n; } w; }\n\
2579 __attribute__((scalar_storage_order(\"big-endian\")));\n";
2580 let taken = format!("{record}int *f(struct s *p) {{ return &p->i; }}\n");
2581 assert_eq!(
2582 run(&opts, &taken).messages,
2583 ["/main.c:3:30: error: cannot take address of scalar with reverse storage order \
2584 [E0712]"]
2585 );
2586 let element = format!("{record}int *f(struct s *p) {{ return &p->a[0]; }}\n");
2587 let messages = run(&opts, &element).messages;
2588 assert!(messages[0].contains("[E0712]"), "{messages:?}");
2589
2590 let whole = format!("{record}int *f(struct s *p) {{ return (int *) &p->w; }}\n");
2591 assert_eq!(run(&opts, &whole).messages, Vec::<String>::new(), "{whole}");
2592 }
2593
2594 #[test]
2598 fn a_storage_order_that_names_neither_end_is_refused_with_the_two_words_that_are_taken() {
2599 let opts = options();
2600 let wrong = "struct s { int i; } __attribute__((scalar_storage_order(\"middle\")));\n";
2601 assert_eq!(
2602 run(&opts, wrong).messages,
2603 ["/main.c:1:36: error: 'scalar_storage_order' argument must be one of \"big-endian\" \
2604 or \"little-endian\" [E0688]"]
2605 );
2606 let bare = "struct s { int i; } __attribute__((scalar_storage_order));\n";
2607 let messages = run(&opts, bare).messages;
2608 assert!(messages[0].contains("[E0688]"), "{messages:?}");
2609 }
2610
2611 #[test]
2621 fn packing_is_what_decides_whether_a_bit_field_may_straddle_its_own_storage() {
2622 assert_eq!(bit_field_byte("struct s { int x : 12; char y : 6; };"), 2);
2624 assert_eq!(
2625 bit_field_byte("struct s { int x : 12; char y : 6; } __attribute__((packed));"),
2626 1
2627 );
2628 assert_eq!(
2629 bit_field_byte("struct s { int x : 12; __attribute__((packed)) char y : 6; };"),
2630 1
2631 );
2632 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { int x : 12; char y : 6; };"), 1);
2633 assert_eq!(bit_field_byte("struct s { char x; int y : 30; };"), 4);
2635 assert_eq!(bit_field_byte("struct s { char x; int y : 30; } __attribute__((packed));"), 1);
2636 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { char x; int y : 30; };"), 1);
2638 assert_eq!(bit_field_byte("#pragma pack(2)\nstruct s { char x; int y : 30; };"), 1);
2639 }
2640
2641 fn bit_field_byte(record: &str) -> u64 {
2643 let source = format!("{record}\nint f(struct s *p) {{ return p->y; }}\n");
2644 let body = body(&source);
2645 let Some((before, _)) = body.split_once("ptr_add") else { return 0 };
2646 let (_, constant) = before.rsplit_once("iconst.i64 ").expect("an offset constant");
2647 constant.lines().next().expect("a line").trim().parse().expect("a byte offset")
2648 }
2649
2650 #[test]
2656 fn an_attribute_among_the_specifiers_is_kept_beside_the_ones_written_in_front() {
2657 tast(concat!(
2658 "struct a { char c; __attribute__((aligned(8))) int i; };\n",
2659 "_Static_assert(sizeof(struct a) == 16 && _Alignof(struct a) == 8, \"a\");\n",
2660 "_Static_assert(__builtin_offsetof(struct a, i) == 8, \"a.i\");\n",
2661 "struct b { char c; __attribute__((packed)) int i; };\n",
2662 "_Static_assert(sizeof(struct b) == 5 && _Alignof(struct b) == 1, \"b\");\n",
2663 "_Static_assert(__builtin_offsetof(struct b, i) == 1, \"b.i\");\n",
2664 "typedef struct { char c; int i; } __attribute__((packed)) c;\n",
2665 "_Static_assert(sizeof(c) == 5 && _Alignof(c) == 1, \"c\");\n",
2666 ));
2667 }
2668
2669 #[test]
2675 fn pragma_pack_caps_every_member_and_is_read_where_the_body_closes() {
2676 tast(concat!(
2677 "#pragma pack(1)\n",
2678 "struct A { char c; int i; };\n",
2679 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2680 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2681 "#pragma pack()\n",
2682 "struct B { char c; int i; };\n",
2683 "_Static_assert(sizeof(struct B) == 8 && _Alignof(struct B) == 4, \"B\");\n",
2684 "#pragma pack(2)\n",
2685 "struct C { char c; int i; double d; };\n",
2686 "_Static_assert(sizeof(struct C) == 14 && _Alignof(struct C) == 2, \"C\");\n",
2687 "_Static_assert(__builtin_offsetof(struct C, d) == 6, \"C.d\");\n",
2688 "struct K { char c; int i __attribute__((aligned(8))); };\n",
2690 "_Static_assert(sizeof(struct K) == 6 && _Alignof(struct K) == 2, \"K\");\n",
2691 "_Static_assert(__builtin_offsetof(struct K, i) == 2, \"K.i\");\n",
2692 "struct J { char c; int i; } __attribute__((aligned(8)));\n",
2694 "_Static_assert(sizeof(struct J) == 8 && _Alignof(struct J) == 8, \"J\");\n",
2695 "#pragma pack()\n",
2696 "#pragma pack(push, 1)\n",
2697 "struct D { char c; short s; };\n",
2698 "_Static_assert(sizeof(struct D) == 3 && _Alignof(struct D) == 1, \"D\");\n",
2699 "#pragma pack(pop)\n",
2700 "struct E { char c; short s; };\n",
2701 "_Static_assert(sizeof(struct E) == 4 && _Alignof(struct E) == 2, \"E\");\n",
2702 "struct H { char c;\n",
2704 "#pragma pack(1)\n",
2705 " int i; };\n",
2706 "_Static_assert(sizeof(struct H) == 5 && _Alignof(struct H) == 1, \"H\");\n",
2707 "#pragma pack(1)\n",
2708 "struct I { char c;\n",
2709 "#pragma pack()\n",
2710 " int i; };\n",
2711 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2712 "#pragma pack()\n",
2713 "#pragma pack(push, 8)\n",
2715 "#pragma pack(push, 1)\n",
2716 "struct P { char c; int i; };\n",
2717 "_Static_assert(sizeof(struct P) == 5 && _Alignof(struct P) == 1, \"P\");\n",
2718 "#pragma pack(pop)\n",
2719 "struct Q { char c; int i; };\n",
2720 "_Static_assert(sizeof(struct Q) == 8 && _Alignof(struct Q) == 4, \"Q\");\n",
2721 "#pragma pack(pop)\n",
2722 "#pragma pack(16)\n",
2724 "struct R { char c; int i; };\n",
2725 "_Static_assert(sizeof(struct R) == 8 && _Alignof(struct R) == 4, \"R\");\n",
2726 "#pragma pack()\n",
2727 "#pragma pack(1)\n",
2728 "struct S { char c; int i : 5; int j : 20; };\n",
2729 "_Static_assert(sizeof(struct S) == 5 && _Alignof(struct S) == 1, \"S\");\n",
2730 "union T { char c; int i; };\n",
2731 "_Static_assert(sizeof(union T) == 4 && _Alignof(union T) == 1, \"T\");\n",
2732 "#pragma pack()\n",
2733 ));
2734 }
2735
2736 #[test]
2740 fn a_pack_line_that_is_not_one_is_reported_in_the_words_gcc_uses() {
2741 let result = run(
2742 &options(),
2743 concat!(
2744 "#pragma pack 4\n",
2745 "#pragma pack(pop)\n",
2746 "#pragma pack(3)\n",
2747 "#pragma pack(1) junk\n",
2748 "#pragma pack(push, 1\n",
2749 "#pragma pack(x)\n",
2750 "#pragma pack(0)\n",
2753 "#pragma pack(push)\n",
2754 "struct s { char c; int i; };\n",
2755 "#pragma pack(pop)\n",
2756 "#pragma pack(pop, foo)\n",
2757 ),
2758 );
2759 let expected = [
2760 "missing `(` after `#pragma pack` - ignored",
2761 "`#pragma pack (pop)` encountered without matching `#pragma pack (push)`",
2762 "alignment must be a small power of two, not 3",
2763 "junk at end of `#pragma pack`",
2764 "malformed `#pragma pack(push[, id][, <n>])` - ignored",
2765 "unknown action `x` for `#pragma pack` - ignored",
2766 "`#pragma pack(pop, foo)` encountered without matching `#pragma pack(push, foo)`",
2767 ];
2768 assert_eq!(result.messages.len(), expected.len(), "{:?}", result.messages);
2769 for (message, want) in result.messages.iter().zip(expected) {
2770 assert!(message.contains(want), "expected {want:?} in {message:?}");
2771 }
2772 }
2773
2774 #[test]
2781 fn a_declaration_behind_an_empty_macro_is_not_eaten_by_the_pragma_above_it() {
2782 let result = run(
2783 &options(),
2784 concat!(
2785 "#pragma pack(push, 1)\n",
2786 "#pragma pack(pop)\n",
2787 "#define API\n",
2788 "API const char version[] = \"3.53.4\";\n",
2789 "const char *get(void) { return version; }\n",
2790 ),
2791 );
2792 assert!(result.messages.is_empty(), "{:?}", result.messages);
2793 }
2794
2795 #[test]
2799 fn the_wide_integer_answers_to_all_three_of_its_names() {
2800 let text = tast("__uint128_t a; __int128_t b; unsigned __int128 c;\n");
2801 assert!(text.contains("decl #0 a : unsigned __int128"), "{text}");
2802 assert!(text.contains("decl #1 b : __int128"), "{text}");
2803 assert!(text.contains("decl #2 c : unsigned __int128"), "{text}");
2804 }
2805
2806 #[test]
2807 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
2808 let text = tast("long f(int a, long b) { return a + b; }\n");
2812 assert!(text.contains("convert arithmetic"), "{text}");
2813 }
2814
2815 #[test]
2816 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
2817 for source in [
2818 "#error stop\n",
2819 "int f(void) { return 1 + ; }\n",
2820 "int f(void) { return undeclared; }\n",
2821 ] {
2822 let result = run(&options(), source);
2823 assert!(result.failed(), "expected this to fail:\n{source}");
2824 assert!(
2825 result.text().is_empty(),
2826 "a file that did not compile wrote a tree:\n{source}"
2827 );
2828 }
2829 }
2830
2831 #[test]
2832 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
2833 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
2837 assert_eq!(result.errors, 1, "{:?}", result.messages);
2838 }
2839
2840 #[test]
2841 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
2842 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
2846 assert_eq!(result.errors, 1, "{:?}", result.messages);
2847 }
2848
2849 #[test]
2850 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
2851 let source = "int f(void) { char c = 300; return c; }\n";
2852 let plain = run(&options(), source);
2853 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
2854 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
2855 assert!(!plain.text().is_empty(), "a warning is not a reason to write nothing");
2856
2857 let mut opts = options();
2858 opts.warnings_are_errors = true;
2859 let strict = run(&opts, source);
2860 assert!(strict.failed());
2861 assert!(strict.text().is_empty(), "and under -Werror it is a reason to write nothing");
2862 for message in &strict.messages {
2863 assert!(!message.contains("warning:"), "{message}");
2864 }
2865 }
2866
2867 #[test]
2868 fn w_drops_the_warning_before_werror_can_promote_it() {
2869 let source = "int f(void) { char c = 300; return c; }\n";
2870 let mut opts = options();
2871 opts.warnings = false;
2872 let quiet = run(&opts, source);
2873 assert_eq!(quiet.messages, Vec::<String>::new());
2874 assert_eq!(quiet.errors, 0);
2875 assert!(!quiet.text().is_empty(), "and the file still compiles");
2876
2877 opts.warnings_are_errors = true;
2880 let both = run(&opts, source);
2881 assert_eq!(both.messages, Vec::<String>::new());
2882 assert!(!both.failed(), "-w -Werror is not an error about a warning nobody saw");
2883 }
2884
2885 #[test]
2886 fn the_dialect_reaches_the_keywords_and_the_checking() {
2887 let source = "typeof(1) x;\n";
2890 let mut opts = options();
2891 opts.std = Std::C23;
2892 opts.gnu_extensions = false;
2893 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
2894
2895 opts.std = Std::C17;
2896 assert!(run(&opts, source).failed());
2897 }
2898
2899 #[test]
2900 fn asking_for_a_kind_that_is_not_written_yet_runs_the_front_end_and_writes_nothing() {
2901 let mut opts = options();
2902 opts.emit = EmitKind::Object;
2903 let result = run(&opts, "int x = 1;\n");
2904 assert!(!result.failed(), "{:?}", result.messages);
2905 assert!(result.text().is_empty());
2906 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
2909 }
2910
2911 fn mir(source: &str) -> String {
2913 let mut opts = options();
2914 opts.emit = EmitKind::MirFinal;
2915 let result = run(&opts, source);
2916 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2917 result.text().to_owned()
2918 }
2919
2920 #[test]
2926 fn a_function_goes_from_c_to_instructions_with_real_registers_in_them() {
2927 let text = mir("int add(int a, int b) { return a + b; }\n");
2928 assert!(text.starts_with("mfunc @add {"), "{text}");
2929 assert!(text.contains("x64.add_rr_32"), "{text}");
2930 assert!(text.contains("x64.ret"), "{text}");
2931 assert!(!text.contains('%'), "{text}");
2934 }
2935
2936 #[test]
2938 fn a_function_with_no_body_produces_no_machine_function() {
2939 let text = mir("int g(int);\nint f(int a) { return g(a); }\n");
2940 assert_eq!(text.matches("mfunc @").count(), 1, "{text}");
2941 assert!(text.contains("mfunc @f {"), "{text}");
2942 assert!(text.contains("x64.call"), "{text}");
2943 }
2944
2945 #[test]
2947 fn every_definition_in_the_file_is_generated_and_they_keep_their_order() {
2948 let text = mir("int a(int x) { return x; }\nint b(int x) { return x; }\n");
2949 let first = text.find("mfunc @a").expect("the first function");
2950 let second = text.find("mfunc @b").expect("the second function");
2951 assert!(first < second, "{text}");
2952 }
2953
2954 #[test]
2956 fn the_target_decides_which_convention_the_generated_code_follows() {
2957 let mut opts = options();
2958 opts.emit = EmitKind::MirFinal;
2959 let linux = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2960 assert!(linux.contains("$rdi"), "{linux}");
2961
2962 opts.target = "x86_64-pc-windows-msvc".parse::<Triple>().unwrap();
2963 let windows = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
2964 assert!(windows.contains("$rcx"), "{windows}");
2965 assert!(!windows.contains("$rdi"), "{windows}");
2966 }
2967
2968 #[test]
2976 fn a_tagged_member_with_no_name_is_a_member_on_windows_and_nothing_on_linux() {
2977 let source = concat!(
2978 "struct S { union U { int i; void *p; }; unsigned long tymed; };\n",
2979 "int size(void) { return sizeof(struct S); }\n",
2980 "int f(struct S *s) { s->i = 1; return s->i; }\n",
2981 );
2982
2983 let mut opts = options();
2984 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
2985 let windows = run(&opts, source);
2986 assert!(windows.messages.is_empty(), "{:?}", windows.messages);
2987
2988 let linux = run(&options(), source);
2989 assert_eq!(linux.messages.len(), 3, "{:?}", linux.messages);
2990 assert!(linux.messages[0].contains("does not declare anything"), "{:?}", linux.messages);
2991
2992 let mut opts = options();
2995 opts.ms_extensions = Some(true);
2996 let asked = run(&opts, source);
2997 assert!(asked.messages.is_empty(), "{:?}", asked.messages);
2998 }
2999
3000 #[test]
3002 fn a_target_this_has_no_back_end_for_is_reported_rather_than_generated() {
3003 let mut opts = options();
3004 opts.emit = EmitKind::MirFinal;
3005 opts.target = "riscv64-unknown-linux-gnu".parse::<Triple>().unwrap();
3006 let result = run(&opts, "int f(int a) { return a; }\n");
3007 assert!(result.failed());
3008 assert!(result.messages[0].contains("no back end for riscv64"), "{:?}", result.messages);
3009 assert!(result.text().is_empty());
3010 }
3011
3012 #[test]
3015 fn an_aarch64_target_is_written_as_aarch64_assembly() {
3016 let mut opts = options();
3017 opts.emit = EmitKind::Asm;
3018 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3019 let source = "int g(int);\nint f(int a, int b) { return g(a) + b; }\n";
3020 let result = run(&opts, source);
3021 assert!(!result.failed(), "{:?}", result.messages);
3022 let text = result.text();
3023 for line in ["stp x29, x30, [sp, #-16]!", "mov x29, sp", "bl g", "ldp x29, x30, [sp], #16"]
3024 {
3025 assert!(text.contains(line), "{line} is not in\n{text}");
3026 }
3027 assert!(!text.contains('%'), "{text}");
3028 }
3029
3030 #[test]
3033 fn an_aarch64_result_in_memory_is_reached_through_x8() {
3034 let mut opts = options();
3035 opts.emit = EmitKind::Asm;
3036 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3037 let source = "struct big { long a, b, c; };\nstruct big make(long v);\n\
3038 long f(long v) { return make(v).c; }\n\
3039 struct big g(long v) { struct big b = { v, v, v }; return b; }\n";
3040 let result = run(&opts, source);
3041 assert!(!result.failed(), "{:?}", result.messages);
3042 let text = result.text();
3043 assert!(text.contains("x8"), "{text}");
3044 assert!(text.contains("bl make"), "{text}");
3045 }
3046
3047 #[test]
3050 fn an_aarch64_remainder_is_a_division_and_a_multiply_subtract() {
3051 let mut opts = options();
3052 opts.emit = EmitKind::Asm;
3053 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3054 let source = "int s(int a, int b) { return a % b; }\n\
3055 unsigned long u(unsigned long a, unsigned long b) { return a % b; }\n";
3056 let result = run(&opts, source);
3057 assert!(!result.failed(), "{:?}", result.messages);
3058 let text = result.text();
3059 let at = |what: &str| text.find(what).unwrap_or_else(|| panic!("{what} is not in\n{text}"));
3060 assert!(at("sdiv w") < at("msub w"), "{text}");
3061 assert!(at("udiv x") < at("msub x"), "{text}");
3062 }
3063
3064 #[test]
3067 fn an_aarch64_jump_table_is_reached_with_adr() {
3068 let mut opts = options();
3069 opts.emit = EmitKind::Asm;
3070 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3071 let source = "int f(int x) { switch (x) { case 0: return 10; case 1: return 21; \
3072 case 2: return 32; case 3: return 43; case 4: return 54; case 5: return 65; \
3073 case 6: return 76; case 7: return 87; case 8: return 98; case 9: return 9; \
3074 default: return 0; } }\n";
3075 let result = run(&opts, source);
3076 assert!(!result.failed(), "{:?}", result.messages);
3077 let text = result.text();
3078 let at = |what: &str| text.find(what).unwrap_or_else(|| panic!("{what} is not in\n{text}"));
3079 assert!(at("adr x") < at("ldrsw x"), "{text}");
3080 assert!(at("ldrsw x") < at("br x"), "{text}");
3081 assert!(text.contains("_j0:"), "{text}");
3082 assert!(text.contains(".long"), "{text}");
3083 }
3084
3085 #[test]
3089 fn an_aarch64_va_start_writes_the_five_fields_of_its_list() {
3090 let mut opts = options();
3091 opts.emit = EmitKind::Asm;
3092 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3093 let source = "typedef __builtin_va_list va_list;\n\
3094 int f(int n, ...) { va_list ap; __builtin_va_start(ap, n); \
3095 int x = __builtin_va_arg(ap, int); double d = __builtin_va_arg(ap, double); \
3096 __builtin_va_end(ap); return x + (int)d; }\n";
3097 let result = run(&opts, source);
3098 assert!(!result.failed(), "{:?}", result.messages);
3099 let text = result.text();
3100 assert!(text.contains("#-56"), "{text}");
3101 assert!(text.contains("#-128"), "{text}");
3102 assert!(text.contains("#24]"), "{text}");
3103 assert!(text.contains("#28]"), "{text}");
3104 assert!(text.contains("str q"), "{text}");
3105 }
3106
3107 #[test]
3110 fn an_aarch64_long_double_is_a_quad_in_a_vector_register() {
3111 let mut opts = options();
3112 opts.emit = EmitKind::Asm;
3113 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3114 let source = "void f(long double *p, long double x) { *p = *p + x; }\n";
3115 let result = run(&opts, source);
3116 assert!(!result.failed(), "{:?}", result.messages);
3117 let text = result.text();
3118 assert!(text.contains("ldr q"), "{text}");
3119 assert!(text.contains("str q"), "{text}");
3120 assert!(text.contains("__addtf3"), "{text}");
3121 }
3122
3123 #[test]
3126 fn an_aarch64_thread_local_is_reached_through_tpidr_el0() {
3127 let mut opts = options();
3128 opts.emit = EmitKind::Asm;
3129 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3130 let source = "__thread int n;\nint *f(void) { return &n; }\n\
3131 void *g(void) { return __builtin_thread_pointer(); }\n";
3132 let result = run(&opts, source);
3133 assert!(!result.failed(), "{:?}", result.messages);
3134 let text = result.text();
3135 assert!(text.contains(":gottprel:n"), "{text}");
3136 assert!(text.contains(":gottprel_lo12:n]"), "{text}");
3137 assert_eq!(text.matches("mrs x").count(), 2, "{text}");
3138 assert!(text.contains("tpidr_el0"), "{text}");
3139 }
3140
3141 #[test]
3144 fn a_darwin_thread_local_is_reached_through_its_descriptor() {
3145 let source = "__thread int n = 5;\nint *f(void) { return &n; }\n";
3146 for (triple, wanted) in [
3147 ("aarch64-apple-darwin", &["_n@TLVPPAGE\n", "_n@TLVPPAGEOFF]\n", "\tblr x"][..]),
3148 ("x86_64-apple-darwin", &["_n@TLVP(%rip), %rdi\n", "\tcall\t*%"][..]),
3149 ] {
3150 let mut opts = options();
3151 opts.emit = EmitKind::Asm;
3152 opts.target = triple.parse::<Triple>().unwrap();
3153 let result = run(&opts, source);
3154 assert!(!result.failed(), "{triple}: {:?}", result.messages);
3155 let text = result.text();
3156 for want in wanted {
3157 assert!(text.contains(want), "{triple} wanted {want:?}:\n{text}");
3158 }
3159 assert!(text.contains("\n_n:\n\t.quad\t__tlv_bootstrap\n"), "{text}");
3160 assert!(!text.contains("tpidr_el0") && !text.contains("%fs"), "{text}");
3161 }
3162 }
3163
3164 #[test]
3166 fn the_thread_pointer_is_refused_on_darwin() {
3167 let mut opts = options();
3168 opts.emit = EmitKind::Asm;
3169 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3170 let result = run(&opts, "void *f(void) { return __builtin_thread_pointer(); }\n");
3171 assert!(result.failed());
3172 assert!(result.messages[0].contains("thread pointer"), "{:?}", result.messages);
3173 }
3174
3175 #[test]
3178 fn a_darwin_variadic_definition_saves_nothing_and_walks_the_stack() {
3179 let mut opts = options();
3180 opts.emit = EmitKind::Asm;
3181 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3182 let source = "int f(int n, ...) { __builtin_va_list ap; __builtin_va_start(ap, n);\n\
3183 int r = __builtin_va_arg(ap, int); __builtin_va_end(ap); return r; }\n";
3184 let result = run(&opts, source);
3185 assert!(!result.failed(), "{:?}", result.messages);
3186 let text = result.text();
3187 assert!(!text.contains("str q"), "{text}");
3188 assert!(!text.contains("x7"), "{text}");
3189 }
3190
3191 #[test]
3194 fn a_darwin_call_puts_its_variadic_arguments_in_memory() {
3195 let mut opts = options();
3196 opts.emit = EmitKind::Asm;
3197 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3198 let source = "int printf(const char *, ...);\n\
3199 int g(double x) { return printf(\"%d %f\", 7, x); }\n";
3200 let result = run(&opts, source);
3201 assert!(!result.failed(), "{:?}", result.messages);
3202 let text = result.text();
3203 assert!(text.contains("str d0, [sp, #8]"), "{text}");
3204 }
3205
3206 #[test]
3210 fn a_darwin_listing_is_one_apples_assembler_reads() {
3211 let mut opts = options();
3212 opts.emit = EmitKind::Asm;
3213 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3214 let source = "extern int ext;\n\
3215 int g[4];\n\
3216 int f(int i) { return g[i] + ext; }\n";
3217 let result = run(&opts, source);
3218 assert!(!result.failed(), "{:?}", result.messages);
3219 let text = result.text();
3220 assert!(text.contains(", _g@PAGE\n"), "{text}");
3221 assert!(text.contains(", _g@PAGEOFF\n"), "{text}");
3222 assert!(text.contains(", _ext@GOTPAGE\n"), "{text}");
3223 assert!(text.contains(", _ext@GOTPAGEOFF]\n"), "{text}");
3224 assert!(!text.contains(":lo12:"), "{text}");
3225 assert!(text.contains("\t.globl\t_g\n\t.zerofill\t__DATA,__bss,_g,16,2\n"), "{text}");
3226 }
3227
3228 #[test]
3234 fn a_signed_char_on_aarch64_is_widened_with_its_sign_before_it_is_added_to() {
3235 for target in ["aarch64-linux-gnu", "aarch64-apple-darwin"] {
3236 let mut opts = options();
3237 opts.emit = EmitKind::Asm;
3238 opts.target = target.parse::<Triple>().unwrap();
3239 let source = "int f(signed char *p) { return *p + 1; }\n\
3240 unsigned g(unsigned short *p) { return *p + 1u; }\n";
3241 let result = run(&opts, source);
3242 assert!(!result.failed(), "{:?}", result.messages);
3243 let text = result.text();
3244 let signed = text.contains("\tsxtb w") || text.contains("\tldrsb w");
3245 assert!(signed, "{target}: {text}");
3246 }
3247 }
3248
3249 #[test]
3261 fn a_construct_the_back_end_cannot_reach_yet_is_reported_against_its_function() {
3262 let mut opts = options();
3263 opts.emit = EmitKind::MirFinal;
3264 let source = "void a(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
3265 s; s.x = 1; v[0] = s.x; }\n\
3266 void b(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
3267 s; s.x = 1; v[0] = s.x; }\n";
3268 let result = run(&opts, source);
3269 assert!(result.failed());
3270 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
3271 assert!(result.messages[0].contains("cannot generate code for 'a'"), "{:?}", result);
3272 assert!(result.messages[0].contains("wants more alignment"), "{:?}", result);
3273 assert!(result.messages[1].contains("cannot generate code for 'b'"), "{:?}", result);
3274 assert!(result.text().is_empty());
3275 }
3276
3277 #[test]
3285 fn a_variable_length_array_walks_its_pages_where_every_page_of_the_frame_is_to_be_touched() {
3286 let mut opts = options();
3287 opts.emit = EmitKind::MirFinal;
3288 let source = "void a(int n) { int v[n]; v[0] = 1; }\n";
3289 let plain = run(&opts, source);
3290 assert!(!plain.failed(), "{:?}", plain.messages);
3291 assert!(!plain.text().contains("cmp_set_a_64"), "{}", plain.text());
3292
3293 opts.stack_clash = true;
3294 let result = run(&opts, source);
3295 assert!(!result.failed(), "{:?}", result.messages);
3296 assert!(result.text().contains("cmp_set_a_64"), "{}", result.text());
3297 assert!(result.text().contains("or_mi_8"), "{}", result.text());
3298 }
3299
3300 #[test]
3310 fn a_function_that_keeps_a_frame_pointer_on_windows_reaches_an_object_file() {
3311 let mut opts = options();
3312 opts.emit = EmitKind::Object;
3313 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3314 let source = concat!(
3315 "void use(void *p);\n",
3316 "void array(int n) { int v[n]; v[0] = 1; use(v); }\n",
3317 "void taken(unsigned long n) { use(__builtin_alloca(n)); }\n",
3318 );
3319 let result = run(&opts, source);
3320 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3321 let bytes = match result.artifact {
3322 Artifact::Object { bytes, .. } => bytes,
3323 other => panic!("expected an object, got {other:?}"),
3324 };
3325 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3326
3327 let mut opts = options();
3330 opts.emit = EmitKind::Object;
3331 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3332 }
3333
3334 #[test]
3345 fn the_address_of_a_function_this_file_only_declares_reaches_a_windows_object() {
3346 let source = concat!(
3347 "void other(void *p);\n",
3348 "void takes(void (*f)(void *));\n",
3349 "void (*held)(void *);\n",
3350 "void pass(void) { takes(other); }\n",
3351 "void keep(void) { held = other; }\n",
3352 "void call(void) { other(0); }\n",
3353 );
3354 let mut opts = options();
3355 opts.emit = EmitKind::Object;
3356 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3357 let result = run(&opts, source);
3358 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3359 let bytes = match result.artifact {
3360 Artifact::Object { bytes, .. } => bytes,
3361 other => panic!("expected an object, got {other:?}"),
3362 };
3363 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3364
3365 let mut opts = options();
3368 opts.emit = EmitKind::Object;
3369 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3370 }
3371
3372 #[test]
3386 fn an_opcode_with_no_name_in_the_rule_language_is_named_by_its_own_spelling() {
3387 let mut opts = options();
3388 opts.emit = EmitKind::MirFinal;
3389 let source =
3390 "long double f(int a) {\n __int128 wide = a;\n return (long double) wide;\n}\n";
3391 let result = run(&opts, source);
3392 assert!(result.failed());
3393 assert!(
3394 result.messages[0].contains("no rule lowers a `sext` producing a `i128`"),
3395 "{result:?}"
3396 );
3397 assert!(result.messages[0].contains(":2:"), "the line the widening is on: {result:?}");
3398 assert!(!result.messages[0].contains("this instruction"), "{result:?}");
3399 }
3400
3401 #[test]
3403 fn the_note_on_unfinished_work_points_at_the_issues_rather_than_at_the_plan() {
3404 let mut opts = options();
3405 opts.emit = EmitKind::MirFinal;
3406 let source = "long double f(int a) { __int128 wide = a; return (long double) wide; }\n";
3407 let result = run(&opts, source);
3408 assert!(result.failed());
3409 let note = result.messages.iter().find(|line| line.contains("note:")).expect("a note");
3410 assert!(note.contains("https://github.com/tamnd/rucc/issues"), "{note}");
3411 assert!(!note.contains("spec/17-milestones.md"), "{note}");
3412 }
3413
3414 #[test]
3416 fn the_frame_flags_on_the_command_line_reach_the_generated_frame() {
3417 let source = "int f(int a) { return a; }\n";
3418 assert!(!mir(source).contains("$rbp"), "a leaf needs no frame pointer by default");
3419
3420 let mut opts = options();
3421 opts.emit = EmitKind::MirFinal;
3422 opts.frame_pointer = true;
3423 let kept = run(&opts, source).text().to_owned();
3424 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
3425 }
3426
3427 fn asm(source: &str) -> String {
3429 let mut opts = options();
3430 opts.emit = EmitKind::Asm;
3431 let result = run(&opts, source);
3432 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3433 result.text().to_owned()
3434 }
3435
3436 #[test]
3443 fn a_function_goes_from_c_to_assembly_an_assembler_would_take() {
3444 let text = asm("int add(int a, int b) { return a + b; }\n");
3445 assert!(text.contains("\t.globl\tadd\n"), "{text}");
3446 assert!(text.contains("\t.type\tadd, @function\n"), "{text}");
3447 assert!(text.contains("\nadd:\n"), "{text}");
3448 assert!(text.contains("\taddl\t"), "{text}");
3449 assert!(text.contains("\tret\n"), "{text}");
3450 assert!(text.contains("\t.size\tadd, .-add\n"), "{text}");
3451 assert!(text.contains(".note.GNU-stack"), "{text}");
3454 }
3455
3456 #[test]
3462 fn a_call_through_a_function_pointer_goes_through_the_register_it_is_in() {
3463 let text = asm("int g(int);\nint f(int (*p)(int), int a) { return p(a) + g(a); }\n");
3464 assert!(text.contains("\tcall\t*%"), "{text}");
3465 assert!(text.contains("\tcall\tg\n"), "{text}");
3466 assert!(text.contains("%rdi"), "{text}");
3470 }
3471
3472 #[test]
3476 fn the_address_of_a_global_is_read_from_the_instruction_pointer() {
3477 let text = asm("extern int counter;\nint f(void) { return counter; }\n");
3478 assert!(text.contains("\tmovl\tcounter(%rip), %eax\n"), "{text}");
3479 }
3480
3481 #[test]
3490 fn a_branch_on_a_comparison_jumps_on_the_opposite_of_what_it_compared() {
3491 let arms = "return 1; return 2;";
3492 let signed = [("==", "jne"), ("!=", "je"), ("<", "jge"), ("<=", "jg"), (">", "jle")];
3493 for (operator, jump) in signed.into_iter().chain([(">=", "jl")]) {
3494 let text = asm(&format!("int f(int a, int b) {{ if (a {operator} b) {arms} }}\n"));
3495 assert!(
3496 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
3497 "{operator}: {text}"
3498 );
3499 assert!(!text.contains("\tset"), "{operator}: {text}");
3500 assert!(!text.contains("\ttest"), "{operator}: {text}");
3501 }
3502 let unsigned = [("<", "jae"), ("<=", "ja"), (">", "jbe"), (">=", "jb")];
3503 for (operator, jump) in unsigned {
3504 let source =
3505 format!("int f(unsigned a, unsigned b) {{ if (a {operator} b) {arms} }}\n");
3506 let text = asm(&source);
3507 assert!(
3508 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
3509 "{operator}: {text}"
3510 );
3511 }
3512
3513 let text = asm("int f(int a) { if (a < 7) return 1; return 2; }\n");
3516 assert!(text.contains("\tcmpl\t$7, %edi\n\tjge\t"), "{text}");
3517 }
3518
3519 #[test]
3525 fn a_comparison_whose_answer_the_program_wanted_still_writes_a_byte() {
3526 let text = asm("int f(int a, int b) { return a < b; }\n");
3527 assert!(text.contains("\tsetl\t"), "{text}");
3528 }
3529
3530 fn optimized(source: &str) -> String {
3532 let mut opts = options();
3533 opts.emit = EmitKind::Asm;
3534 opts.opt_level = rucc_session::OptLevel::O2;
3535 let result = run(&opts, source);
3536 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3537 result.text().to_owned()
3538 }
3539
3540 #[test]
3550 fn a_switch_whose_arms_are_a_function_of_the_label_is_a_range_check_and_arithmetic() {
3551 let arms: String =
3552 (0..16).map(|k| format!("case {k}: return {};", k + 1)).collect::<Vec<_>>().join(" ");
3553 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3554 assert!(text.contains("\tcmpl\t$15, %edi\n\tja\t"), "{text}");
3555 assert!(text.contains("\taddl\t$1, %edi"), "{text}");
3556 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
3557 }
3558
3559 #[test]
3567 fn a_dense_switch_whose_arms_are_not_a_line_is_a_load_from_a_table() {
3568 let arms: String = (0..16)
3569 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
3570 .collect::<Vec<_>>()
3571 .join(" ");
3572 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3573 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
3574 assert!(!text.contains("\tjmp\t*"), "{text}");
3575 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
3576 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
3577 let section = text[..text.find("CSWTCH.0:").unwrap_or(0)].rfind("\t.section\t.rodata");
3578 assert!(section.is_some(), "{text}");
3579 assert_eq!(table.matches("\t.long\t").count(), 16, "{text}");
3580 assert!(table.contains("\t.long\t100\n"), "{text}");
3581 }
3582
3583 #[test]
3589 fn a_table_at_os_has_cells_as_narrow_as_its_answers() {
3590 let arms: String = (0..16)
3591 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
3592 .collect::<Vec<_>>()
3593 .join(" ");
3594 let mut opts = options();
3595 opts.emit = EmitKind::Asm;
3596 opts.opt_level = rucc_session::OptLevel::Os;
3597 let result = run(&opts, &format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3598 assert_eq!(result.messages, Vec::<String>::new());
3599 let text = result.text();
3600 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
3601 assert_eq!(table.matches("\t.byte\t").count(), 16, "{text}");
3602 assert!(text.contains("\tmovsbl\t"), "{text}");
3603 }
3604
3605 #[test]
3612 fn a_table_that_covers_its_operand_has_no_range_check() {
3613 let text = optimized(
3614 "int f(unsigned x) { switch (x & 3) { case 0: return 5; case 1: return 9; \
3615 case 2: return 2; case 3: return 7; } return -1; }\n",
3616 );
3617 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
3618 assert!(!text.contains("\tcmp"), "{text}");
3619 assert!(!text.contains("$-1"), "{text}");
3620 }
3621
3622 #[test]
3627 fn a_store_to_a_local_the_loop_just_read_is_a_conditional_move() {
3628 let text = optimized(
3629 "int f(const int *v, int n, int k) { int best[8] = {0}; \
3630 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; \
3631 return best[k & 7]; }\n",
3632 );
3633 assert!(text.contains("\tcmovgl"), "{text}");
3634 assert!(!text.contains("\tset"), "{text}");
3635 assert!(!text.contains("\ttestb"), "{text}");
3636 }
3637
3638 #[test]
3640 fn a_store_to_a_global_the_loop_just_read_keeps_its_branch() {
3641 let text = optimized(
3642 "int best[8]; void f(const int *v, int n) { \
3643 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; }\n",
3644 );
3645 assert!(!text.contains("\tcmov"), "{text}");
3646 }
3647
3648 #[test]
3656 fn a_conversion_from_a_constant_double_is_the_number_it_converts_to() {
3657 let text = optimized("int f(void) { double d = 2.75; return (int) d; }\n");
3658 assert!(text.contains("movl\t$2, %eax"), "{text}");
3659 assert!(!text.contains("cvttsd2si"), "{text}");
3660 }
3661
3662 #[test]
3670 fn a_slot_of_a_read_only_table_is_the_value_the_table_holds() {
3671 let text =
3672 optimized("static const int t[4] = {10, 20, 30, 40};\nint f(void) { return t[2]; }\n");
3673 assert!(text.contains("movl\t$30, %eax"), "{text}");
3674 assert!(!text.contains("t(%rip)"), "{text}");
3675 }
3676
3677 #[test]
3680 fn a_byte_of_a_read_only_string_is_the_byte_the_string_spells() {
3681 let text = optimized("static const char s[] = \"abc\";\nint f(void) { return s[1]; }\n");
3682 assert!(text.contains("movl\t$98, %eax"), "{text}");
3683 }
3684
3685 #[test]
3689 fn a_table_that_is_not_read_only_keeps_its_load() {
3690 let text = optimized(
3691 "static int t[4] = {10, 20, 30, 40};\nvoid g(int x) { t[2] = x; }\nint f(void) { return t[2]; }\n",
3692 );
3693 assert!(!text.contains("movl\t$30, %eax"), "{text}");
3694 }
3695
3696 #[test]
3703 fn a_call_guarded_by_a_condition_a_read_only_object_settles_is_not_emitted() {
3704 let text = optimized(
3705 "void link_error(void);\nconst double one = 1.0;\nint main(void) { if ((int) one != 1) link_error(); return 0; }\n",
3706 );
3707 assert!(!text.contains("call\tlink_error"), "{text}");
3708 }
3709
3710 #[test]
3712 fn a_cast_between_a_pointer_and_an_integer_leaves_the_value_where_it_is() {
3713 let text = asm("long f(void *p) { return (long)p; }\n");
3714 for line in text.lines().filter(|line| line.starts_with('\t') && !line.contains('.')) {
3719 let mnemonic = line.split_whitespace().next().unwrap_or("");
3720 assert!(matches!(mnemonic, "movq" | "ret"), "{line} in\n{text}");
3721 }
3722 }
3723
3724 #[test]
3728 fn an_argument_past_the_last_register_is_read_out_of_the_caller_s_stack() {
3729 let six = "long a, long b, long c, long d, long e, long f";
3730 let text = asm(&format!("long f({six}, long g, long h) {{ return g + h; }}\n"));
3731
3732 assert!(text.contains("\tmovq\t8(%rsp), "), "{text}");
3739 assert!(text.contains("\taddq\t16(%rsp), "), "{text}");
3740
3741 let narrow = asm(&format!("int f({six}, int g) {{ return g; }}\n"));
3745 assert!(narrow.contains("\tmovl\t8(%rsp), "), "{narrow}");
3746 let eight =
3747 "double a, double b, double c, double d, double e, double f, double g, double h";
3748 let float = asm(&format!("double f({eight}, double i) {{ return i; }}\n"));
3749 assert!(float.contains("\tmovsd\t8(%rsp), "), "{float}");
3750 }
3751
3752 #[test]
3755 fn a_call_writes_the_arguments_with_no_register_left_at_the_stack_pointer() {
3756 let six = "1, 2, 3, 4, 5, 6";
3757 let decl = "long g(long, long, long, long, long, long, long, long);\n";
3758 let text = asm(&format!("{decl}long f(void) {{ return g({six}, 7, 8); }}\n"));
3759
3760 assert!(text.contains("\tmovq\t%"), "{text}");
3761 assert!(text.contains(", (%rsp)\n"), "{text}");
3762 assert!(text.contains(", 8(%rsp)\n"), "{text}");
3763 assert!(text.contains("\tsubq\t$"), "{text}");
3765
3766 let narrow = "int g(int, int, int, int, int, int, int);\n";
3768 let text = asm(&format!("{narrow}int f(void) {{ return g({six}, 7); }}\n"));
3769 assert!(text.contains("\tmovl\t%"), "{text}");
3770 assert!(text.contains(", (%rsp)\n"), "{text}");
3771 }
3772
3773 #[test]
3776 fn a_variadic_call_counts_registers_and_not_arguments() {
3777 let nine = "1., 2., 3., 4., 5., 6., 7., 8., 9.";
3778 let decl = "int g(int, ...);\n";
3779 let text = asm(&format!("{decl}int f(void) {{ return g(0, {nine}); }}\n"));
3780
3781 assert!(text.contains("\tmovl\t$8, "), "eight registers, not nine: {text}");
3782 assert!(text.contains("\tmovsd\t%"), "{text}");
3783 assert!(text.contains(", (%rsp)\n"), "{text}");
3784 }
3785
3786 #[test]
3791 fn a_variadic_function_writes_the_argument_registers_it_was_handed_into_its_frame() {
3792 let body =
3793 "__builtin_va_list ap; __builtin_va_start(ap, n); __builtin_va_end(ap); return n;";
3794 let text = asm(&format!("int f(int n, ...) {{ {body} }}\n"));
3795
3796 let stores = |mnemonic: &str| text.matches(&format!("\t{mnemonic}\t%")).count();
3799 assert!(text.contains(", 8(%r"), "the second slot, not the first: {text}");
3800 assert!(!text.contains(", 0(%r"), "{text}");
3801 assert_eq!(stores("movaps"), 8, "every vector register: {text}");
3804 assert_eq!(stores("movsd"), 0, "and the whole of each one: {text}");
3805
3806 assert!(text.contains("\tsubq\t$"), "{text}");
3808 }
3809
3810 #[test]
3813 fn va_start_writes_the_four_fields_the_psabi_describes() {
3814 let start = "__builtin_va_list ap; __builtin_va_start(ap, d);";
3815 let params = "int a, int b, int c, double d";
3816 let text = asm(&format!("int f({params}, ...) {{ {start} return a; }}\n"));
3817
3818 assert!(text.contains(" movl $24, "), "{text}");
3822 assert!(text.contains(" movl $64, "), "{text}");
3823 assert!(text.contains(", 8(%r"), "{text}");
3827 assert!(text.contains(", 16(%r"), "{text}");
3828 let frame: u32 = text
3829 .lines()
3830 .find_map(|line| line.trim().strip_prefix("subq $")?.split(',').next()?.parse().ok())
3831 .expect("a variadic function takes a frame for the save area");
3832 let above = |line: &str| {
3833 let at: u32 = line.trim().strip_prefix("leaq ")?.split('(').next()?.parse().ok()?;
3834 Some(at > frame)
3835 };
3836 assert!(text.lines().filter_map(above).any(|it| it), "{frame}: {text}");
3837 }
3838
3839 #[test]
3842 fn va_arg_branches_on_whether_the_argument_is_still_in_the_save_area() {
3843 let read = "__builtin_va_list ap; __builtin_va_start(ap, n);";
3844 let ints = format!("int f(int n, ...) {{ {read} return __builtin_va_arg(ap, int); }}\n");
3845 let text = asm(&ints);
3846
3847 assert!(text.contains("$40, "), "{text}");
3850 assert!(text.contains(" cmpl "), "{text}");
3851 assert!(text.contains(" ja "), "{text}");
3855
3856 let arg = "__builtin_va_arg(ap, double)";
3857 let text = asm(&format!("double f(int n, ...) {{ {read} return {arg}; }}\n"));
3858 assert!(text.contains("$160, "), "the last vector slot: {text}");
3859 }
3860
3861 #[test]
3864 fn a_structure_assignment_is_a_move_for_each_word_of_it() {
3865 let decl = "struct pair { long a, b; };\n";
3866 let body = "struct pair p = *q; return p.a + p.b;";
3867 let text = asm(&format!("{decl}long f(struct pair *q) {{ {body} }}\n"));
3868
3869 assert!(!text.contains("memcpy"), "nothing calls the library: {text}");
3870 assert!(!text.contains("\tcall"), "{text}");
3871 assert!(text.matches("\tmovq\t").count() >= 4, "two words each way: {text}");
3873 }
3874
3875 #[test]
3878 fn how_wide_a_word_of_a_copy_is_follows_the_alignment() {
3879 let decl = "struct bytes { char a[8]; };\n";
3880 let body = "struct bytes p = *q; return p.a[0];";
3881 let text = asm(&format!("{decl}int f(struct bytes *q) {{ {body} }}\n"));
3882
3883 assert!(text.matches("\tmovb\t").count() >= 16, "a byte at a time: {text}");
3885 }
3886
3887 #[test]
3890 fn the_part_of_an_initialiser_that_names_nothing_is_stored_as_zero() {
3891 let decl = "struct wide { long a, b, c; };\n";
3892 let text = asm(&format!("{decl}long f(void) {{ struct wide w = {{ 7 }}; return w.c; }}\n"));
3893
3894 assert!(!text.contains("memset"), "nothing calls the library: {text}");
3895 assert!(text.contains("\tmovq\t$0, ") || text.contains("\txorl\t"), "the zero: {text}");
3900 }
3901
3902 #[test]
3905 fn a_copy_too_large_to_unroll_calls_the_runtime() {
3906 let decl = "struct huge { char a[4096]; };\n";
3907 let mut opts = options();
3908 opts.emit = EmitKind::Asm;
3909 let source = format!("{decl}void f(struct huge *p, struct huge *q) {{ *p = *q; }}\n");
3910 let result = run(&opts, &source);
3911 assert!(!result.failed(), "{:?}", result.messages);
3912 let text = result.text();
3913 assert!(text.contains("call") && text.contains("memcpy"), "{text}");
3914 assert!(text.contains("4096"), "the size travels: {text}");
3917 }
3918
3919 #[test]
3926 fn a_structure_too_large_to_unroll_is_copied_into_the_argument_area_by_the_runtime() {
3927 let decl = "struct huge { char a[4096]; };\nint take(struct huge);\n";
3928 let text = asm(&format!("{decl}int f(struct huge *p) {{ return take(*p); }}\n"));
3929
3930 let copy = text.find("call\tmemcpy").expect("the copy");
3931 let call = text.find("call\ttake").expect("the call");
3932 assert!(copy < call, "the copy comes first: {text}");
3933 assert!(text.contains("movq\t%rsp, %rdi"), "the destination: {text}");
3938 assert!(text.contains("$4096, %edx"), "the size: {text}");
3939 }
3940
3941 #[test]
3944 fn a_realigned_frame_reads_them_through_the_frame_pointer() {
3945 let six = "long a, long b, long c, long d, long e, long f";
3946 let body = "_Alignas(32) long wide[4]; wide[0] = g; return wide[0];";
3947 let text = asm(&format!("long f({six}, long g) {{ {body} }}\n"));
3948
3949 assert!(text.contains("\tandq\t$-32, %rsp"), "{text}");
3953 assert!(text.contains("\tmovq\t16(%rbp), "), "{text}");
3954 assert!(!text.contains("\tmovq\t16(%rsp), "), "{text}");
3955 }
3956
3957 #[test]
3959 fn the_target_decides_how_the_assembly_is_spelled() {
3960 let mut opts = options();
3961 opts.emit = EmitKind::Asm;
3962 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
3963 let text = run(&opts, "int f(void) { return 0; }\n").text().to_owned();
3964 assert!(text.contains("__TEXT,__text"), "{text}");
3965 assert!(text.contains("\n_f:\n"), "{text}");
3966 assert!(!text.contains(".note.GNU-stack"), "{text}");
3967 }
3968
3969 fn obj(source: &str) -> Vec<u8> {
3971 let mut opts = options();
3972 opts.emit = EmitKind::Object;
3973 let result = run(&opts, source);
3974 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3975 match result.artifact {
3976 Artifact::Object { bytes, .. } => bytes,
3977 other => panic!("expected an object, got {other:?}"),
3978 }
3979 }
3980
3981 #[test]
3987 fn a_function_goes_from_c_to_an_object_a_linker_would_take() {
3988 let bytes = obj("int add(int a, int b) { return a + b; }\n");
3989 assert_eq!(&bytes[..4], b"\x7fELF", "an object file starts by saying it is one");
3990 let text = asm("int add(int a, int b) { return a + b; }\n");
3991 assert!(
3992 text.contains("\taddl\t"),
3993 "and the listing of it is the same instructions:\n{text}"
3994 );
3995 }
3996
3997 #[test]
3999 fn a_variable_goes_from_c_to_the_section_it_belongs_in() {
4000 let text = asm("int counter = 42;\nstatic int hidden;\nconst int fixed = 7;\n");
4001 assert!(text.contains("\t.data\n\t.globl\tcounter\n"), "{text}");
4002 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
4003 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
4004 assert!(text.contains("\t.bss\n\t.p2align\t2\n"), "{text}");
4007 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
4008 assert!(!text.contains(".globl\thidden"), "{text}");
4009 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4012 }
4013
4014 #[test]
4021 fn a_bit_field_initializer_writes_every_byte_of_the_value_and_not_only_the_ones_that_are_set() {
4022 let text = asm("struct s { unsigned f : 20; } x = { 0x12300 };\n");
4023 assert!(text.contains("\t.data\n"), "there is something to write: {text}");
4024 assert!(text.contains("\nx:\n\t.ascii\t\"\\000#\\001\"\n"), "and it is the value: {text}");
4025
4026 let text = asm("struct s { unsigned a : 8; unsigned b : 8; } x = { 0, 3 };\n");
4029 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\003\"\n"), "{text}");
4030
4031 let text = asm("struct s { unsigned long long f : 40; } x = { 0x100000 };\n");
4034 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\000\\020\"\n\t.space\t5\n"), "{text}");
4035
4036 let text = asm("struct s { unsigned f : 20; } x = { 0 };\n");
4038 assert!(text.contains("\t.bss\n"), "an object of zeroes is zeroes: {text}");
4039 assert!(text.contains("\nx:\n\t.space\t4\n"), "{text}");
4040 }
4041
4042 #[test]
4044 fn a_string_literal_is_a_variable_with_a_name_no_program_could_write() {
4045 let text = asm("const char *f(void) { return \"hi\"; }\n");
4046 assert!(text.contains("\t.ascii\t\"hi\\000\"\n"), "{text}");
4047 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4048 let label = text
4049 .lines()
4050 .find(|line| line.starts_with(".Lstr"))
4051 .unwrap_or_else(|| panic!("a label for the literal in\n{text}"));
4052 assert!(!text.contains(&format!(".globl\t{}", label.trim_end_matches(':'))), "{text}");
4053 }
4054
4055 #[test]
4057 fn an_address_in_an_initializer_is_left_to_the_linker() {
4058 let source = "int counter;\nint *p = &counter;\n";
4059 let text = asm(source);
4060 assert!(text.contains("\np:\n\t.quad\tcounter\n"), "{text}");
4061 let bytes = obj(source);
4064 assert!(bytes.windows(8).any(|w| w == b"counter\0"), "the object has to name it");
4065 }
4066
4067 #[test]
4076 fn a_constant_holding_an_address_goes_in_the_section_the_loader_may_write_once() {
4077 let text = asm("static void a(void) {}\nstatic void b(void) {}\n\
4080 struct m { void (*x)(void); void (*y)(void); };\n\
4081 const struct m t = { a, b };\n");
4082 assert!(text.contains("\t.section\t.data.rel.ro.local,\"aw\",@progbits\n"), "{text}");
4083 assert!(text.contains("\nt:\n\t.quad\ta\n\t.quad\tb\n"), "{text}");
4084
4085 let text =
4088 asm("void a(void);\nstruct m { void (*x)(void); };\nconst struct m t = { a };\n");
4089 assert!(text.contains("\t.section\t.data.rel.ro,\"aw\",@progbits\n"), "{text}");
4090
4091 let text = asm("const int fixed = 7;\n");
4093 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4094 }
4095
4096 #[test]
4103 fn a_thread_local_variable_is_storage_a_thread_gets_a_copy_of_and_an_offset_into_it() {
4104 let text = asm("_Thread_local int x = 1;\nint read(void) { return x; }\n");
4105 assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
4108 assert!(text.contains("\t.type\tx, @tls_object\n"), "{text}");
4109 assert!(text.contains("x@GOTTPOFF(%rip)"), "{text}");
4112 assert!(text.contains("%fs:0"), "{text}");
4113 }
4114
4115 #[test]
4121 fn the_address_of_this_thread_s_own_storage_is_read_out_of_the_segment_register() {
4122 let text = asm("void *here(void) { return __builtin_thread_pointer(); }\n");
4123 assert!(text.contains("movq\t%fs:0, "), "{text}");
4124 assert!(!text.contains("GOTTPOFF"), "{text}");
4126 }
4127
4128 #[test]
4139 fn a_prefetch_is_one_of_four_instructions_and_the_locality_is_what_picks() {
4140 for (locality, wanted) in
4141 [(0, "prefetchnta"), (1, "prefetcht2"), (2, "prefetcht1"), (3, "prefetcht0")]
4142 {
4143 let source =
4144 format!("void warm(void *p) {{ __builtin_prefetch(p, 0, {locality}); }}\n");
4145 let text = asm(&source);
4146 assert!(text.contains(&format!("\t{wanted}\t")), "locality {locality}: {text}");
4147 }
4148 let text = asm("void warm(void *p) { __builtin_prefetch(p); }\n");
4150 assert!(text.contains("\tprefetcht0\t"), "{text}");
4151 let text = asm("void warm(void *p) { __builtin_prefetch(p, 1); }\n");
4154 assert!(text.contains("\tprefetcht0\t"), "{text}");
4155 assert!(!text.contains("prefetchw"), "{text}");
4156 }
4157
4158 #[test]
4169 fn a_trap_is_the_instruction_the_machine_has_no_meaning_for() {
4170 let text = asm("void stop(void) { __builtin_trap(); }\n");
4171 assert!(text.contains("\tud2\n"), "{text}");
4172 assert!(!text.contains("\tcall"), "a stop is not a call to anything: {text}");
4173
4174 let text = asm("int stop(int a) { __builtin_trap(); return a + 1; }\n");
4175 assert!(text.contains("\tud2\n"), "{text}");
4176 assert!(text.contains("\taddl\t"), "the block goes on after a stop: {text}");
4177 }
4178
4179 #[test]
4191 fn assume_aligned_is_its_first_argument_and_keeps_the_rest() {
4192 let text = asm("void *aligned(char *p) { return __builtin_assume_aligned(p, 16); }\n");
4193 assert!(!text.contains("assume_aligned"), "{text}");
4194 assert!(!text.contains("\tcall"), "nothing is called for an alignment fact: {text}");
4195
4196 let source = "unsigned long width(void);\n\
4197 void *aligned(char *p) { return __builtin_assume_aligned(p, width()); }\n";
4198 let text = asm(source);
4199 assert!(!text.contains("assume_aligned"), "{text}");
4200 assert!(text.contains("width"), "the argument that is not the answer still runs: {text}");
4201 }
4202
4203 #[test]
4213 fn the_frame_address_is_the_frame_pointer_after_walking_that_many_links() {
4214 let text = asm("void *here(void) { return __builtin_frame_address(0); }\n");
4215 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
4216 assert!(text.contains("movq\t%rbp, %rax"), "{text}");
4217 assert!(!text.contains("\tcall"), "a frame address is not a call to anything: {text}");
4218
4219 let walk = |depth: u32| {
4220 let source = format!("void *up(void) {{ return __builtin_frame_address({depth}); }}\n");
4221 asm(&source).matches("movq\t(%r").count()
4222 };
4223 assert_eq!(walk(1), 1, "one link is one load");
4224 assert_eq!(walk(3), 3, "three links are three loads");
4225 }
4226
4227 #[test]
4237 fn the_return_address_is_one_word_above_the_frame_the_walk_ended_at() {
4238 let text = asm("void *back(void) { return __builtin_return_address(0); }\n");
4239 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
4240 assert!(text.contains("movq\t8(%rbp), %rax"), "{text}");
4241 assert!(!text.contains("\tcall"), "a return address is not a call to anything: {text}");
4242
4243 let text = asm("void *back(void) { return __builtin_return_address(2); }\n");
4244 assert_eq!(text.matches("movq\t(%r").count(), 2, "two links are two loads: {text}");
4245 assert!(text.contains("movq\t8(%r"), "and the answer is above the last of them: {text}");
4246 }
4247
4248 #[test]
4259 fn a_depth_that_is_not_a_small_constant_is_refused() {
4260 let mut opts = options();
4261 opts.emit = EmitKind::Ir;
4262 for source in [
4263 "void *up(int n) { return __builtin_return_address(n); }\n",
4264 "void *up(void) { return __builtin_frame_address(1000); }\n",
4265 ] {
4266 let messages = run(&opts, source).messages;
4267 let named = messages.iter().any(|m| m.contains("E0705"));
4268 assert!(named, "expected a refusal in {messages:?}");
4269 }
4270 }
4271
4272 #[test]
4284 fn an_alloca_takes_the_bytes_off_the_stack_pointer_and_answers_where_they_are() {
4285 let text =
4286 asm("void use(void *p); void f(unsigned long n) { use(__builtin_alloca(n)); }\n");
4287 assert!(text.contains("andq\t$-16"), "the size is rounded up to sixteen: {text}");
4288 assert!(text.contains("subq\t%rdi, %rsp"), "and taken off the stack pointer: {text}");
4289 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
4290
4291 let plain = concat!(
4294 "extern void *alloca(__SIZE_TYPE__);\n",
4295 "void use(void *p);\n",
4296 "void f(unsigned long n) { use(alloca(n)); }\n",
4297 );
4298 let text = asm(plain);
4299 assert!(text.contains("subq\t%rdi, %rsp"), "the plain name is the same bytes: {text}");
4300 assert_eq!(text.matches("\tcall").count(), 1, "and is not a call either: {text}");
4301
4302 let own = concat!(
4305 "static void *alloca(unsigned long n) { return 0; }\n",
4306 "void *f(unsigned long n) { return alloca(n); }\n",
4307 );
4308 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
4309 }
4310
4311 #[test]
4326 fn a_builtin_the_program_never_declared_is_the_builtin_rather_than_the_one_c89_wrote_down() {
4327 let mut opts = options();
4331 opts.permissive = true;
4332 let undeclared = "void use(void *p);
4333void f(unsigned long n) { use(alloca(n)); }
4334";
4335 assert_eq!(
4336 run(&opts, undeclared).messages,
4337 [
4338 "/main.c:2:31: warning: implicit declaration of function 'alloca' [E0521]",
4339 "/main.c:2:31: warning: incompatible implicit declaration of built-in function \
4340 'alloca' [E0713]",
4341 ]
4342 );
4343
4344 opts.emit = EmitKind::Asm;
4345 let text = run(&opts, undeclared).text().to_owned();
4346 assert!(text.contains("subq\t%rdi, %rsp"), "the bytes come off the stack: {text}");
4347 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
4348
4349 let string = "unsigned long f(void) { return strlen(\"abc\"); }\n";
4352 let text = run(&opts, string).text().to_owned();
4353 assert!(text.contains("call\tstrlen"), "strlen is still a call: {text}");
4354
4355 let own = concat!(
4358 "static void *alloca(unsigned long n) { return 0; }\n",
4359 "void *f(unsigned long n) { return alloca(n); }\n",
4360 );
4361 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
4362 }
4363
4364 #[test]
4374 fn the_bytes_an_alloca_took_are_still_there_at_the_end_of_the_block_that_took_them() {
4375 let inner = "{ use(__builtin_alloca(n)); }";
4376 for body in [inner.to_owned(), format!("int a[n]; {inner} use(a);")] {
4377 let source = format!("void use(void *p);\nvoid f(unsigned long n) {{ {body} }}\n");
4378 let text = asm(&source);
4379 for line in text.lines().filter(|line| line.trim_end().ends_with(", %rsp")) {
4383 let taking = line.contains("subq");
4384 let leaving = line.contains("%rbp");
4385 assert!(taking || leaving, "nothing puts the stack back: {line} in {text}");
4386 }
4387 }
4388 }
4389
4390 #[test]
4392 fn the_object_and_the_listing_are_two_spellings_of_one_compilation() {
4393 let source = "int callee(void); int g(void) { return callee(); }\n";
4397 let bytes = obj(source);
4398 assert!(
4399 bytes.windows(7).any(|w| w == b"callee\0"),
4400 "the object has to name the callee for the linker to find it"
4401 );
4402 let text = asm(source);
4403 assert!(text.contains("\tcall\tcallee\n"), "{text}");
4404 }
4405
4406 #[test]
4412 fn compiling_for_an_executable_produces_an_object_and_not_a_dump() {
4413 let mut opts = options();
4414 opts.emit = EmitKind::Executable;
4416 let result = run(&opts, "int main(void) { return 0; }\n");
4417 assert_eq!(result.messages, Vec::<String>::new());
4418 match result.artifact {
4419 Artifact::Object { bytes, .. } => assert_eq!(&bytes[..4], b"\x7fELF"),
4420 other => panic!("expected an object, got {other:?}"),
4421 }
4422 }
4423
4424 #[test]
4426 fn a_platform_with_no_object_writer_is_said_so_rather_than_written_as_elf() {
4427 let mut opts = options();
4428 opts.emit = EmitKind::Object;
4429 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
4430 let result = run(&opts, "int f(void) { return 0; }\n");
4431 assert!(result.failed(), "an object nobody can read is worse than a message");
4432 assert!(
4433 result.messages.iter().any(|m| m.contains("no object writer")),
4434 "{:?}",
4435 result.messages
4436 );
4437 }
4438
4439 fn ir(source: &str) -> String {
4441 let mut opts = options();
4442 opts.emit = EmitKind::Ir;
4443 let result = run(&opts, source);
4444 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4445 result.text().to_owned()
4446 }
4447
4448 fn errors(source: &str) -> Vec<String> {
4450 let mut opts = options();
4451 opts.emit = EmitKind::Ir;
4452 let result = run(&opts, source);
4453 assert!(result.failed(), "expected this to be refused:\n{source}");
4454 result.messages
4455 }
4456
4457 fn body(source: &str) -> String {
4459 let text = ir(source);
4460 let (_, rest) = text.split_once("{\n").expect("a function definition");
4461 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
4462 body.to_owned()
4463 }
4464
4465 #[test]
4473 fn gnu89_inline_is_what_decides_whether_a_bare_inline_definition_reaches_the_module() {
4474 let source = "inline int f(int x) { return x + 1; }\n";
4475 let with = |flag: bool| {
4476 let mut opts = options();
4477 opts.emit = EmitKind::Ir;
4478 opts.gnu89_inline = flag;
4479 let result = run(&opts, source);
4480 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
4481 result.text().to_owned()
4482 };
4483
4484 assert!(!with(false).contains("block0"), "no body: {}", with(false));
4487
4488 assert!(with(true).contains("block0"), "a body: {}", with(true));
4491 }
4492
4493 #[test]
4500 fn an_access_through_a_type_names_the_type_it_went_through() {
4501 let source = "\
4502struct s { int a; float b; };\n\
4503union u { int i; float f; };\n\
4504int scalar(int *p) { return *p; }\n\
4505float member(struct s *p) { p->a = 1; return p->b; }\n\
4506int element(int *a, long i) { return a[i]; }\n\
4507float through_a_union(union u *p) { p->i = 1; return p->f; }\n";
4508 let text = ir(source);
4509 assert!(text.contains(r#"!0 = tbaa "char""#), "the root: {text}");
4510 assert!(text.contains(r#"tbaa "int", parent !0"#), "int under it: {text}");
4511 assert!(text.contains(r#"tbaa "float", parent !0"#), "float under it: {text}");
4512 let named = text.lines().filter(|line| line.contains(", tbaa !")).count();
4515 assert_eq!(named, 6, "six accesses: {text}");
4516 }
4517
4518 #[test]
4525 fn turning_strict_aliasing_off_leaves_the_type_off_every_access() {
4526 let source = "int punned(float *f, int *i) { *i = 1; *f = 2.0f; return *i; }\n";
4527 let mut opts = options();
4528 opts.emit = EmitKind::Ir;
4529 opts.strict_aliasing = false;
4530 let result = run(&opts, source);
4531 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
4532 let text = result.text().to_owned();
4533 assert!(!text.contains("tbaa"), "not even the root: {text}");
4534 }
4535
4536 #[test]
4542 fn instrumenting_functions_calls_the_hooks_around_every_body_but_the_hooks() {
4543 let source = concat!(
4544 "#define NOCHK __attribute__((no_instrument_function))\n",
4545 "void __cyg_profile_func_enter(void *, void *) NOCHK;\n",
4546 "void __cyg_profile_func_exit(void *, void *) NOCHK;\n",
4547 "int calls;\n",
4548 "int pick(int x) { if (x) return 1; return 2; }\n",
4549 "void quiet(void) NOCHK;\n",
4550 "void quiet(void) { calls++; }\n",
4551 "void __cyg_profile_func_enter(void *fn, void *site) { calls++; }\n",
4552 "void __cyg_profile_func_exit(void *fn, void *site) { calls--; }\n",
4553 );
4554 let mut opts = options();
4555 opts.emit = EmitKind::Ir;
4556 opts.instrument_functions = true;
4557 let result = run(&opts, source);
4558 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
4559 let text = result.text().to_owned();
4560 let body = |name: &str| -> String {
4561 let open = format!("func @{name}(");
4562 let start = text.find(&open).unwrap_or_else(|| panic!("no {name}: {text}"));
4563 let rest = &text[start..];
4564 rest[..rest.find("\n}").unwrap_or(rest.len())].to_owned()
4565 };
4566 let pick = body("pick");
4567 assert_eq!(pick.matches("call @__cyg_profile_func_enter(").count(), 1, "{pick}");
4568 assert_eq!(pick.matches("call @__cyg_profile_func_exit(").count(), 2, "{pick}");
4569 assert!(pick.contains("return_address"), "{pick}");
4570 assert!(pick.contains("global_addr @pick"), "{pick}");
4571 for quiet in ["quiet", "__cyg_profile_func_enter", "__cyg_profile_func_exit"] {
4572 assert!(!body(quiet).contains("call "), "{quiet} is left alone: {text}");
4573 }
4574
4575 opts.instrument_functions = false;
4576 let result = run(&opts, source);
4577 assert!(!result.text().contains("call @__cyg_profile"), "off unless asked for");
4578 }
4579
4580 #[test]
4588 fn a_bare_return_from_a_function_that_promised_a_value_gives_back_a_zero() {
4589 let mut opts = options();
4590 opts.emit = EmitKind::Ir;
4591 opts.std = Std::C89;
4592 let compiled = |source: &str| {
4593 let result = run(&opts, source);
4594 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
4595 result.text().to_owned()
4596 };
4597
4598 let text = compiled("int f(int x) { if (x) return; return 3; }\n");
4599 assert!(text.contains("iconst.i32 0\n return"), "zero goes back: {text}");
4600 assert!(!text.contains("unreachable"), "the branch that reached it is kept: {text}");
4601
4602 let text = compiled("double f(int x) { if (x) return; return 1.0; }\n");
4604 assert!(text.contains("fconst.f64 0x0\n return"), "a float zero goes back: {text}");
4605 }
4606
4607 #[test]
4615 fn a_call_to_a_name_nothing_declared_declares_it_as_c89_said_to() {
4616 let mut opts = options();
4617 opts.emit = EmitKind::Ir;
4618 opts.std = Std::C89;
4619 let compiled = |source: &str| {
4620 let result = run(&opts, source);
4621 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
4622 result.text().to_owned()
4623 };
4624
4625 let text = compiled("int f(void) { return g(); }\n");
4627 assert!(text.contains("call @g"), "the call is to the name that was written: {text}");
4628 assert!(text.contains("i32"), "and it gives back an int: {text}");
4629
4630 let text = compiled("int f(char c) { return g(c); }\n");
4633 assert!(text.contains("sext.i32"), "the argument is promoted: {text}");
4634
4635 let mut opts = options();
4638 opts.std = Std::C89;
4639 let said = run(&opts, "int f(void) { return h; }\n").messages.join("\n");
4640 assert!(said.contains("'h' undeclared"), "not a call, so not declared: {said}");
4641 }
4642
4643 #[test]
4653 fn a_name_called_before_it_is_defined_still_gets_its_definition() {
4654 let mut opts = options();
4655 opts.emit = EmitKind::Ir;
4656 opts.std = Std::C89;
4657 let text = run(&opts, "int f(void) { return dummy(); }\ndummy () { return 7; }\n")
4658 .text()
4659 .to_owned();
4660 assert!(text.contains("func @f()"), "the caller is there: {text}");
4661 assert!(text.contains("func @dummy"), "and so is what it calls: {text}");
4662 assert!(text.contains("iconst.i32 7"), "with the body it was given: {text}");
4663 }
4664
4665 #[test]
4673 fn an_old_style_parameter_is_converted_from_what_the_call_promoted_it_to() {
4674 let mut opts = options();
4675 opts.emit = EmitKind::Ir;
4676 opts.std = Std::C89;
4677 let compiled = |source: &str| run(&opts, source).text().to_owned();
4678
4679 let text = compiled("f (c) unsigned char c; { return c; }\n");
4680 assert!(text.contains("func @f(i32"), "an int arrives: {text}");
4681 assert!(text.contains("trunc.i8"), "and is cut down to what was declared: {text}");
4682 assert!(text.contains("zext.i32"), "then read back unsigned: {text}");
4683
4684 let text = compiled("f (s) short s; { return s; }\n");
4686 assert!(text.contains("trunc.i16"), "cut down: {text}");
4687 assert!(text.contains("sext.i32"), "and read back signed: {text}");
4688
4689 let text = compiled("f (x) float x; { return x * 2; }\n");
4692 assert!(text.contains("func @f(f64"), "a double arrives: {text}");
4693 assert!(text.contains("fptrunc.f32"), "and is narrowed to the float: {text}");
4694
4695 let text = compiled("int f(unsigned char c) { return c; }\n");
4698 assert!(text.contains("func @f(i8)"), "the declared type arrives: {text}");
4699 assert!(!text.contains("trunc"), "so there is nothing to cut down: {text}");
4700 }
4701
4702 #[test]
4711 fn the_rules_gcc_promoted_are_decided_by_the_dialect_and_by_fpermissive() {
4712 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
4714 let cases = [
4715 ("static counted;\n", ["", "error", "warning", "error"]),
4716 ("int f(void) { return g(); }\n", ["", "error", "warning", "error"]),
4717 ("int f(x) { return x; }\n", ["", "error", "warning", "error"]),
4718 ("int *p;\nvoid h(void) { p = 1; }\n", ["warning", "error", "warning", "error"]),
4719 (
4720 "char *q;\nint *r;\nvoid k(void) { r = q; }\n",
4721 ["warning", "error", "warning", "error"],
4722 ),
4723 ("int f(void) { return; }\n", ["", "error", "warning", "error"]),
4724 ("void g(void) { return 1; }\n", ["warning", "error", "warning", "error"]),
4725 ];
4726
4727 for (source, wanted) in cases {
4728 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
4729 let mut opts = options();
4730 opts.std = std;
4731 opts.permissive = permissive;
4732 let said = run(&opts, source).messages.join("\n");
4733 let severity = if said.contains(": error: ") {
4734 "error"
4735 } else if said.contains(": warning: ") {
4736 "warning"
4737 } else {
4738 ""
4739 };
4740 let how = if permissive { " -fpermissive" } else { "" };
4741 assert_eq!(
4742 severity,
4743 wanted,
4744 "under -std={}{how}, {source} was answered with `{said}`",
4745 std.as_str()
4746 );
4747 if wanted.is_empty() {
4748 assert!(said.is_empty(), "nothing to say, but said `{said}`");
4749 }
4750 }
4751 }
4752 }
4753
4754 #[test]
4763 fn the_three_variadic_builtins_answer_a_bad_list_the_way_a_call_answers_a_bad_argument() {
4764 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
4765 let cases = [
4766 (
4767 "int f(int n, ...) { char *p; return __builtin_va_arg(p, int); }\n",
4768 "first argument to 'va_arg' not of type 'va_list'",
4769 ["error", "error", "error", "error"],
4770 ),
4771 (
4772 "void f(int n, ...) { char *p; __builtin_va_start(p, n); }\n",
4773 "passing argument 1 of '__builtin_va_start' from incompatible pointer type",
4774 ["warning", "error", "warning", "error"],
4775 ),
4776 (
4777 "void f(int n, ...) { int x; __builtin_va_end(x); }\n",
4778 "passing argument 1 of '__builtin_va_end' makes pointer from integer without a \
4779 cast",
4780 ["warning", "error", "warning", "error"],
4781 ),
4782 (
4783 "void f(int n, ...) { __builtin_va_list a; char *p; __builtin_va_copy(a, p); }\n",
4784 "passing argument 2 of '__builtin_va_copy' from incompatible pointer type",
4785 ["warning", "error", "warning", "error"],
4786 ),
4787 ];
4788
4789 for (source, message, wanted) in cases {
4790 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
4791 let mut opts = options();
4792 opts.std = std;
4793 opts.permissive = permissive;
4794 let said = run(&opts, source).messages.join("\n");
4795 let how = if permissive { " -fpermissive" } else { "" };
4796 assert!(
4797 said.contains(&format!(": {wanted}: {message}")),
4798 "under -std={}{how}, {source} was answered with `{said}`",
4799 std.as_str()
4800 );
4801 }
4802 }
4803 }
4804
4805 fn safe_ir(tier: rucc_session::Safety, source: &str) -> String {
4807 let mut opts = options();
4808 opts.emit = EmitKind::Ir;
4809 opts.safety = tier;
4810 let result = run(&opts, source);
4811 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4812 result.text().to_owned()
4813 }
4814
4815 const READS_THROUGH_A_POINTER: &str = "int read(int *p) { return p[1]; }\n";
4816
4817 fn padded_ir(padding: Padding, source: &str) -> String {
4819 let mut opts = options();
4820 opts.emit = EmitKind::Ir;
4821 opts.safety = rucc_session::Safety::Detect;
4822 opts.padding = padding;
4823 let result = run(&opts, source);
4824 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4825 result.text().to_owned()
4826 }
4827
4828 const FILLS_A_RECORD_A_MEMBER_AT_A_TIME: &str = "struct padded { char tag; int value; };\n\
4829 void fill(struct padded *p) { p->tag = 1; p->value = 2; }\n";
4830
4831 #[test]
4832 fn a_record_filled_a_member_at_a_time_comes_out_whole_when_padding_does_not_participate() {
4833 let text = padded_ir(Padding::Ignored, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
4837 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
4838 }
4839
4840 #[test]
4841 fn a_store_says_only_what_it_wrote_when_padding_does_participate() {
4842 let text = padded_ir(Padding::Tracked, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
4845 assert!(!text.contains("owns"), "{text}");
4846 }
4847
4848 #[test]
4849 fn a_member_of_a_union_owns_nothing_after_it() {
4850 let text = padded_ir(
4854 Padding::Ignored,
4855 "union u { char tag; long wide; };\nvoid fill(union u *p) { p->tag = 1; }\n",
4856 );
4857 assert!(!text.contains("owns"), "{text}");
4858 }
4859
4860 #[test]
4861 fn an_inner_records_trailing_padding_reaches_the_outer_records() {
4862 let text = padded_ir(
4867 Padding::Ignored,
4868 "struct inner { char c; };\n\
4869 struct outer { struct inner in; int x; };\n\
4870 void fill(struct outer *p) { p->in.c = 1; p->x = 2; }\n",
4871 );
4872 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
4873 }
4874
4875 #[test]
4876 fn a_build_that_did_not_ask_for_the_monitor_is_compiled_the_way_it_always_was() {
4877 let text = ir(READS_THROUGH_A_POINTER);
4881 assert!(!text.contains("check_"), "{text}");
4882 assert!(!text.contains("cap_of"), "{text}");
4883 }
4884
4885 #[test]
4886 fn asking_for_a_tier_puts_the_checks_in_before_the_optimizer_sees_them() {
4887 let text = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4888 assert!(text.contains("cap_of"), "{text}");
4889 assert!(text.contains("check_bounds"), "{text}");
4890 assert!(text.contains("check_live"), "{text}");
4891 assert!(text.contains("check_deriv"), "{text}");
4893 assert!(text.contains("check_type"), "{text}");
4895 }
4896
4897 #[test]
4898 fn the_three_tiers_that_are_not_off_all_check_the_same_accesses_so_far() {
4899 let detect = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4903 for tier in [rucc_session::Safety::Enforce, rucc_session::Safety::Kernel] {
4904 assert_eq!(safe_ir(tier, READS_THROUGH_A_POINTER), detect, "{tier}");
4905 }
4906 }
4907
4908 fn summary(tier: rucc_session::Safety, source: &str) -> String {
4910 let mut opts = options();
4911 opts.emit = EmitKind::SafetySummary;
4912 opts.safety = tier;
4913 let result = run(&opts, source);
4914 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4915 result.text().to_owned()
4916 }
4917
4918 #[test]
4919 fn the_summary_counts_the_checks_that_went_in_and_the_ones_still_standing() {
4920 let text = summary(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
4921 assert!(text.contains("\"tier\": \"detect\""), "{text}");
4922 assert!(
4924 text.contains("\"bounds\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"),
4925 "{text}"
4926 );
4927 assert!(
4928 text.contains(
4929 "\"derivation\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"
4930 ),
4931 "{text}"
4932 );
4933 }
4934
4935 #[test]
4936 fn a_build_without_the_monitor_summarises_as_a_build_with_no_checks_in_it() {
4937 let text = summary(rucc_session::Safety::Off, READS_THROUGH_A_POINTER);
4941 assert!(text.contains("\"tier\": \"off\""), "{text}");
4942 assert!(
4943 text.contains("\"bounds\": { \"emitted\": 0, \"remaining\": 0, \"discharged\": 0 }"),
4944 "{text}"
4945 );
4946 }
4947
4948 #[test]
4949 fn a_call_the_boundary_models_is_counted_apart_from_one_it_does_not() {
4950 let text = summary(
4951 rucc_session::Safety::Detect,
4952 "void *memcpy(void *, const void *, unsigned long);\n\
4953 int puts(const char *);\n\
4954 void f(char *d, char *s) { memcpy(d, s, 4); puts(d); }\n",
4955 );
4956 assert!(text.contains("\"interposed\": 1"), "{text}");
4957 assert!(text.contains("\"puts\""), "{text}");
4958 assert!(!text.contains("__rucc_wrap_memcpy\""), "{text}");
4962 }
4963
4964 #[test]
4965 fn an_address_taken_of_a_library_function_is_counted_the_way_a_call_to_one_is() {
4966 let text = summary(
4971 rucc_session::Safety::Detect,
4972 "void *memcpy(void *, const void *, unsigned long);\n\
4973 int puts(const char *);\n\
4974 void *table[2] = { (void *)memcpy, (void *)puts };\n\
4975 void *f(int i) { return table[i]; }\n",
4976 );
4977 assert!(text.contains("\"interposed\": 1"), "{text}");
4978 assert!(text.contains("\"puts\""), "{text}");
4979 assert!(!text.contains("\"memcpy\""), "{text}");
4980 }
4981
4982 #[test]
4983 fn the_two_directions_a_pointer_crosses_the_boundary_are_counted_apart() {
4984 let text = summary(
4988 rucc_session::Safety::Detect,
4989 "void *notes_open(void);\n\
4990 char *f(char *p) { char *q = notes_open(); return q ? q : p; }\n",
4991 );
4992 assert!(text.contains("\"crossings\": { \"entered\": 1, \"returned\": 1 }"), "{text}");
4993 assert!(text.contains("\"notes_open\""), "{text}");
4994 }
4995
4996 #[test]
4997 fn a_static_function_nobody_takes_the_address_of_is_not_a_crossing() {
4998 let text = summary(
5001 rucc_session::Safety::Detect,
5002 "static int len(const char *p) { return p ? 1 : 0; }\n\
5003 int f(void) { return len(\"x\"); }\n",
5004 );
5005 assert!(text.contains("\"crossings\": { \"entered\": 0, \"returned\": 0 }"), "{text}");
5006 }
5007
5008 fn granules(source: &str) -> String {
5010 let mut opts = options();
5011 opts.emit = EmitKind::TypeGranules;
5012 let result = run(&opts, source);
5013 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5014 result.text().to_owned()
5015 }
5016
5017 #[test]
5018 fn the_granule_report_names_every_record_and_both_keyings() {
5019 let text = granules(
5020 "struct hot { char *p; int a; int b; };\n\
5021 int f(struct hot *h) { return h->a; }\n",
5022 );
5023 assert!(text.contains("struct hot"), "{text}");
5024 assert!(text.contains("every type distinct"), "{text}");
5027 assert!(text.contains("every pointer one type"), "{text}");
5028 assert!(text.contains("budget"), "{text}");
5029 }
5030
5031 #[test]
5032 fn a_record_nothing_uses_is_still_measured() {
5033 let text = granules("struct unused { long a; double b; };\nint f(void) { return 0; }\n");
5036 assert!(text.contains("struct unused"), "{text}");
5037 }
5038
5039 #[test]
5040 fn the_granule_report_stops_before_anything_is_lowered() {
5041 let text = granules(
5045 "struct wide { long double d; };\n\
5046 long double f(long double x) { return x * x; }\n",
5047 );
5048 assert!(text.contains("struct wide"), "{text}");
5049 }
5050
5051 #[test]
5052 fn a_witness_reaches_the_assembler_as_a_call_to_the_runtime() {
5053 let text = safe_asm(rucc_session::Safety::Detect, "char *f(char *p) { return p; }\n");
5056 assert!(text.contains("\tcall\t__rucc_cap_witness\n"), "{text}");
5057 }
5058
5059 #[test]
5060 fn a_pointer_turned_into_an_integer_is_on_the_trust_set() {
5061 let text = summary(
5062 rucc_session::Safety::Detect,
5063 "unsigned long f(int *p) { return (unsigned long) p; }\n",
5064 );
5065 assert!(text.contains("\"exposed\": 1"), "{text}");
5066 }
5067
5068 fn safe_asm(tier: rucc_session::Safety, source: &str) -> String {
5070 let mut opts = options();
5071 opts.emit = EmitKind::Asm;
5072 opts.safety = tier;
5073 let result = run(&opts, source);
5074 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5075 result.text().to_owned()
5076 }
5077
5078 #[test]
5079 fn a_check_reaches_the_assembler_as_a_call_to_the_runtime() {
5080 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5081 assert!(text.contains("\tcall\t__rucc_check_bounds\n"), "{text}");
5082 assert!(text.contains("\tcall\t__rucc_check_live\n"), "{text}");
5083 assert!(text.contains("\tcall\t__rucc_check_deriv\n"), "{text}");
5084 assert!(text.contains("\tcall\t__rucc_check_typed_init\n"), "{text}");
5087 }
5088
5089 #[test]
5090 fn every_check_that_reached_the_assembler_has_a_row_describing_it() {
5091 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5097 let section = format!("\t.section\t{},", rucc_safety::SECTION);
5098 assert_eq!(text.matches(§ion).count(), 4, "{text}");
5099 for index in 0..4 {
5100 let name = format!("__rucc_safety_desc_{index}");
5101 assert!(text.contains(&format!("{name}:\n")), "{text}");
5104 assert!(text.contains(&format!("{name}(%rip)")), "{text}");
5105 }
5106 assert!(!text.contains("__rucc_safety_desc_4"), "{text}");
5107 }
5108
5109 #[test]
5117 fn builtin_constant_p_is_folded_where_it_is_written_rather_than_called() {
5118 let text = ir(concat!(
5119 "int g;\n",
5120 "int a = __builtin_constant_p(1);\n",
5121 "int b = __builtin_constant_p(g);\n",
5122 "int c = __builtin_constant_p(\"abc\");\n",
5123 "int d = __builtin_constant_p(&g);\n",
5124 "int e = __builtin_constant_p(1.5);\n",
5125 "int h = __builtin_choose_expr(__builtin_constant_p(3), 11, 22);\n",
5126 ));
5127 assert!(text.contains("global @a : i32 = 1,"), "{text}");
5128 assert!(text.contains("global @b : i32 = 0,"), "{text}");
5129 assert!(text.contains("global @c : i32 = 1,"), "{text}");
5130 assert!(text.contains("global @d : i32 = 0,"), "{text}");
5131 assert!(text.contains("global @e : i32 = 1,"), "{text}");
5132 assert!(text.contains("global @h : i32 = 11,"), "{text}");
5133 assert!(!text.contains("__builtin_constant_p"), "it is not a call to anything:\n{text}");
5134
5135 let text = body("int f(void) { int i = 0; __builtin_constant_p(i++); return i; }\n");
5139 assert_eq!(text, "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 0\n return %0\n");
5140 }
5141
5142 #[test]
5151 fn a_call_to_a_library_builtin_reaches_the_library_function() {
5152 let text = body("void f(void) { __builtin_abort(); }\n");
5153 assert_eq!(text, "block0:\n call @abort() : ()\n return\n");
5154
5155 let text = ir("int f(const char *s) { return __builtin_puts(s) + __builtin_strlen(s); }\n");
5158 assert!(text.contains("call @puts(%0) : (ptr) -> i32"), "{text}");
5159 assert!(text.contains("call @strlen(%0) : (ptr) -> i64"), "{text}");
5160 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
5161 }
5162
5163 #[test]
5176 fn a_chk_builtin_reaches_the_checking_function_and_keeps_the_size() {
5177 let text = ir(concat!(
5178 "char d[8];\n",
5179 "void f(const char *s, unsigned long n) {\n",
5180 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
5181 " __builtin___strcpy_chk(d, s, __builtin_object_size(d, 1));\n",
5182 " __builtin___memset_chk(d, 0, n, 8);\n",
5183 "}\n",
5184 ));
5185 assert!(text.contains("call @__memcpy_chk("), "{text}");
5186 assert!(text.contains("call @__strcpy_chk("), "{text}");
5187 assert!(text.contains("call @__memset_chk("), "{text}");
5188 assert!(text.contains("iconst.i64 8"), "the object size reaches the call: {text}");
5189 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
5190 }
5191
5192 #[test]
5200 fn a_checking_call_whose_size_says_nothing_is_known_is_the_plain_library_call() {
5201 let text = ir(concat!(
5202 "extern char *p;\n",
5203 "char d[8];\n",
5204 "void f(const char *s, unsigned long n) {\n",
5205 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
5206 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
5207 " __builtin___strcpy_chk(p, s, __builtin_object_size(p, 0));\n",
5208 " __builtin___stpncpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
5209 " __builtin___sprintf_chk(p, 1, __builtin_object_size(p, 0), s);\n",
5210 "}\n",
5211 ));
5212
5213 assert!(
5215 text.contains("call @__memcpy_chk(%2, %0, %1, %3) : (ptr, ptr, i64, i64)"),
5216 "{text}"
5217 );
5218
5219 assert!(text.contains("call @memcpy(%6, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
5222 assert!(text.contains("call @strcpy(%10, %0) : (ptr, ptr) -> ptr"), "{text}");
5223 assert!(text.contains("call @stpncpy(%14, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
5224
5225 assert!(text.contains("call @__sprintf_chk("), "{text}");
5228
5229 let asm = asm(concat!(
5232 "void f(char *p, const char *s, unsigned long n) {\n",
5233 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
5234 "}\n",
5235 ));
5236 assert!(asm.contains("call\tmemcpy"), "{asm}");
5237 assert!(!asm.contains("$-1"), "the size that went away leaves no instruction:\n{asm}");
5238 }
5239
5240 #[test]
5248 fn the_v_spellings_of_the_chk_family_take_the_list_a_va_list_parameter_holds() {
5249 let text = ir(concat!(
5250 "char d[64];\n",
5251 "int f(const char *fmt, ...) {\n",
5252 " __builtin_va_list ap;\n",
5253 " __builtin_va_start(ap, fmt);\n",
5254 " int n = __builtin___vsprintf_chk(d, 1, __builtin_object_size(d, 0), fmt, ap);\n",
5255 " __builtin_va_end(ap);\n",
5256 " return n;\n",
5257 "}\n",
5258 ));
5259 assert!(text.contains("call @__vsprintf_chk("), "{text}");
5260 assert!(text.contains("iconst.i64 64"), "the object size reaches the call: {text}");
5261 }
5262
5263 #[test]
5274 fn the_absolute_value_family_is_the_magnitude_and_not_a_call() {
5275 let text = body(concat!(
5276 "long long llabs(long long);\n",
5277 "long long f(long long x) { return llabs(x); }\n",
5278 ));
5279 assert!(text.contains("%1 = iconst.i64 63"), "{text}");
5280 assert!(text.contains("%2 = ashr %0, %1"), "{text}");
5281 assert!(text.contains("%3 = xor %0, %2"), "{text}");
5282 assert!(text.contains("%4 = sub %3, %2"), "{text}");
5283 assert!(!text.contains("call"), "the call does not happen:\n{text}");
5284
5285 let text = body("int abs(int);\nint f(int x) { return abs(x); }\n");
5288 assert!(text.contains("iconst.i32 31"), "{text}");
5289 let text = body("long labs(long);\nlong f(long x) { return labs(x); }\n");
5290 assert!(text.contains("iconst.i64 63"), "{text}");
5291
5292 let text = body("long long f(long long x) { return __builtin_llabs(x); }\n");
5295 assert!(!text.contains("call"), "{text}");
5296
5297 let text = ir(concat!(
5299 "long long llabs(long long b);\n",
5300 "long long g(long long x) { return llabs(x); }\n",
5301 "long long llabs(long long b) { return 7; }\n",
5302 ));
5303 assert!(!text.contains("call @llabs"), "{text}");
5304 }
5305
5306 #[test]
5313 fn a_byte_swap_is_arithmetic_and_not_a_call() {
5314 let text = body("unsigned f(unsigned x) { return __builtin_bswap32(x); }\n");
5315 assert_eq!(text, "block0(%0: i32):\n %1 = bswap %0\n return %1\n");
5316
5317 let text = body("unsigned f(unsigned char c) { return __builtin_bswap32(c); }\n");
5320 assert!(text.contains("zext.i32 %0"), "widened first: {text}");
5321 assert!(text.contains("bswap %1"), "and swapped at four bytes: {text}");
5322 }
5323
5324 #[test]
5330 fn the_byte_swaps_reverse_at_the_width_their_name_says() {
5331 for (name, ty, width) in [
5332 ("__builtin_bswap16", "unsigned short", "i16"),
5333 ("__builtin_bswap32", "unsigned", "i32"),
5334 ("__builtin_bswap64", "unsigned long long", "i64"),
5335 ] {
5336 let source = format!("{ty} f({ty} x) {{ return {name}(x); }}\n");
5337 let text = body(&source);
5338 assert_eq!(
5339 text,
5340 format!("block0(%0: {width}):\n %1 = bswap %0\n return %1\n"),
5341 "{name}"
5342 );
5343 }
5344 }
5345
5346 #[test]
5353 fn the_bit_counts_are_instructions_and_not_calls() {
5354 let text = body("int f(unsigned x) { return __builtin_clz(x); }\n");
5355 assert_eq!(text, "block0(%0: i32):\n %1 = ctlz %0\n return %1\n");
5356
5357 let text = body("int f(unsigned x) { return __builtin_ctz(x); }\n");
5358 assert_eq!(text, "block0(%0: i32):\n %1 = cttz %0\n return %1\n");
5359
5360 let text = body("int f(unsigned x) { return __builtin_popcount(x); }\n");
5361 assert_eq!(text, "block0(%0: i32):\n %1 = ctpop %0\n return %1\n");
5362 }
5363
5364 #[test]
5373 fn the_bit_counts_ask_about_the_width_their_name_says() {
5374 let text = body("int f(unsigned long long x) { return __builtin_clzll(x); }\n");
5375 assert!(text.starts_with("block0(%0: i64):"), "counted at eight bytes: {text}");
5376 assert!(text.contains("%1 = ctlz %0"), "{text}");
5377 assert!(text.contains("trunc.i32 %1"), "and answered in an int: {text}");
5378
5379 let text = body("int f(unsigned long long x) { return __builtin_clz(x); }\n");
5382 assert!(text.contains("trunc.i32 %0"), "narrowed to what was asked about: {text}");
5383 assert!(text.contains("ctlz %1"), "and counted there: {text}");
5384
5385 let text = body("int f(unsigned long x) { return __builtin_popcountl(x); }\n");
5386 assert!(text.contains("%1 = ctpop %0"), "{text}");
5387 assert!(!text.contains("call"), "{text}");
5388 }
5389
5390 #[test]
5395 fn a_parity_is_the_low_bit_of_the_set_bit_count() {
5396 let text = body("int f(unsigned x) { return __builtin_parity(x); }\n");
5397 assert!(text.contains("%1 = ctpop %0"), "{text}");
5398 assert!(text.contains("iconst.i32 1"), "{text}");
5399 assert!(text.contains("and %1, %2"), "the low bit of it: {text}");
5400 }
5401
5402 #[test]
5408 fn the_first_set_bit_is_one_based_and_zero_for_a_zero() {
5409 let text = body("int f(int x) { return __builtin_ffs(x); }\n");
5410 assert!(text.contains("%1 = cttz %0"), "{text}");
5411 assert!(text.contains("%4 = add %1, %2"), "one more than the count: {text}");
5412 assert!(text.contains("%5 = icmp ne %0, %3"), "whether there was a bit at all: {text}");
5413 assert!(text.contains("%7 = sub %3, %6"), "spread to a mask: {text}");
5414 assert!(text.contains("%8 = and %4, %7"), "and kept only then: {text}");
5415 assert!(!text.contains("br_if"), "no branch: {text}");
5416 }
5417
5418 #[test]
5428 fn the_redundant_sign_bit_count_is_instructions_and_not_a_call() {
5429 let text = body("int f(int x) { return __builtin_clrsb(x); }\n");
5430 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
5431 assert!(text.contains("%2 = ashr %0, %1"), "the sign over every bit: {text}");
5432 assert!(text.contains("%3 = xor %0, %2"), "folded onto it: {text}");
5433 assert!(text.contains("%5 = shl %3, %4"), "one less than the count: {text}");
5434 assert!(text.contains("%6 = or %5, %4"), "with something to count at zero: {text}");
5435 assert!(text.contains("%7 = ctlz %6"), "{text}");
5436 assert!(!text.contains("call"), "{text}");
5437 assert!(!text.contains("br_if"), "no branch: {text}");
5438 }
5439
5440 #[test]
5446 fn the_unsigned_absolute_value_family_answers_in_the_unsigned_type() {
5447 let text = body("unsigned f(int x) { return __builtin_uabs(x); }\n");
5448 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
5449 assert!(text.contains("%4 = sub %3, %2"), "{text}");
5450 assert!(!text.contains("call"), "nothing declares uabs, so a call would not link: {text}");
5451
5452 let text = body("unsigned long long f(long long x) { return __builtin_ullabs(x); }\n");
5453 assert!(text.contains("iconst.i64 63"), "at the width the name says: {text}");
5454
5455 let text = body("int f(int x) { return __builtin_uabs(x) > 2147483647u; }\n");
5458 assert!(text.contains("icmp ugt"), "compared unsigned: {text}");
5459 }
5460
5461 #[test]
5469 fn the_widest_absolute_value_is_whichever_type_the_target_makes_intmax_t() {
5470 let text = body("long f(long x) { return __builtin_imaxabs(x); }\n");
5471 assert!(text.contains("iconst.i64 63"), "{text}");
5472 assert!(text.contains("%4 = sub %3, %2"), "{text}");
5473 assert!(!text.contains("call"), "{text}");
5474
5475 let text = body("unsigned long f(long x) { return __builtin_umaxabs(x); }\n");
5476 assert!(text.contains("iconst.i64 63"), "{text}");
5477 assert!(!text.contains("call"), "{text}");
5478 }
5479
5480 #[test]
5488 fn an_overflow_predicate_writes_nothing_and_answers_the_bit_the_check_would() {
5489 let text =
5490 body("int f(int a, int b) { return __builtin_add_overflow_p(a, b, (int) 0); }\n");
5491 assert!(text.contains("%2, %3 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
5492 assert!(!text.contains("store"), "nothing is written: {text}");
5493 assert!(!text.contains("call"), "{text}");
5494
5495 let text =
5498 body("int f(int a, int b) { return __builtin_mul_overflow_p(a, b, (long long) 0); }\n");
5499 assert!(text.contains("smul_overflow.(i64, i1)"), "{text}");
5500 assert!(!text.contains("store"), "{text}");
5501
5502 let text = body(concat!(
5505 "int g(void);\n",
5506 "int f(int a, int b) { return __builtin_sub_overflow_p(a, b, g()); }\n",
5507 ));
5508 assert!(!text.contains("call @g"), "the third argument is not evaluated: {text}");
5509 }
5510
5511 #[test]
5521 fn an_overflow_check_is_arithmetic_and_not_a_call() {
5522 let text =
5523 body("int f(int a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
5524 assert!(text.contains("%3, %4 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
5525 assert!(text.contains("store %3 -> %2"), "{text}");
5526 assert!(!text.contains("call"), "{text}");
5527
5528 let text =
5529 body("int f(int a, int b, int *r) { return __builtin_sub_overflow(a, b, r); }\n");
5530 assert!(text.contains("ssub_overflow.(i32, i1) %0, %1"), "{text}");
5531
5532 let text =
5533 body("int f(int a, int b, int *r) { return __builtin_mul_overflow(a, b, r); }\n");
5534 assert!(text.contains("smul_overflow.(i32, i1) %0, %1"), "{text}");
5535
5536 let text = body(
5539 "int f(unsigned a, unsigned b, unsigned *r) { return __builtin_add_overflow(a, b, r); }\n",
5540 );
5541 assert!(text.contains("uadd_overflow.(i32, i1) %0, %1"), "{text}");
5542 }
5543
5544 #[test]
5552 fn an_overflow_check_is_done_at_a_type_that_holds_every_operand() {
5553 let text = body(
5554 "int f(unsigned a, int b, long long *r) { return __builtin_add_overflow(a, b, r); }\n",
5555 );
5556 assert!(text.contains("%3 = zext.i64 %0"), "the unsigned operand keeps its value: {text}");
5557 assert!(text.contains("%4 = sext.i64 %1"), "and so does the signed one: {text}");
5558 assert!(text.contains("sadd_overflow.(i64, i1) %3, %4"), "{text}");
5559
5560 let text = body(
5563 "int f(long long a, long long b, long long *r) { return __builtin_mul_overflow(a, b, r); }\n",
5564 );
5565 assert!(text.contains("smul_overflow.(i64, i1) %0, %1"), "{text}");
5566 assert!(!text.contains("sext."), "{text}");
5567 assert!(!text.contains("zext.i64"), "{text}");
5569 }
5570
5571 #[test]
5579 fn an_overflow_check_writes_the_wrapped_answer_whether_or_not_it_fit() {
5580 let text =
5581 body("int f(int a, int b, char *r) { return __builtin_sub_overflow(a, b, r); }\n");
5582 assert!(text.contains("%3, %4 = ssub_overflow.(i32, i1) %0, %1"), "{text}");
5583 assert!(text.contains("%5 = trunc.i8 %3"), "narrowed to where it goes: {text}");
5584 assert!(text.contains("%6 = sext.i32 %5"), "and back: {text}");
5585 assert!(text.contains("%7 = icmp ne %6, %3"), "which is whether it fit: {text}");
5586 assert!(text.contains("store %5 -> %2"), "the narrowed value is stored either way: {text}");
5587 assert!(text.contains("%8 = or %4, %7"), "and either bit is an overflow: {text}");
5588 }
5589
5590 #[test]
5597 fn a_call_needing_more_than_the_widest_type_still_compiles() {
5598 for name in ["add", "sub", "mul"] {
5599 let source = format!(
5600 "int f(unsigned __int128 a, long long b, __int128 *r) {{\n \
5601 return __builtin_{name}_overflow(a, b, r);\n}}\n"
5602 );
5603 let mut opts = options();
5604 opts.emit = EmitKind::MirFinal;
5605 assert!(!run(&opts, &source).failed(), "{name} was refused or stopped the back end");
5606 }
5607 }
5608
5609 #[test]
5612 fn an_overflow_check_over_something_that_is_not_an_integer_says_so() {
5613 let messages =
5614 errors("int f(double a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
5615 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
5616
5617 let messages =
5618 errors("int f(int a, int b, double *r) { return __builtin_add_overflow(a, b, r); }\n");
5619 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
5620 }
5621
5622 #[test]
5633 fn an_ordered_access_is_ordered_in_the_ir() {
5634 let text = body("int f(int *p) { return __atomic_load_n(p, 0); }\n");
5635 assert!(text.contains("atomic_load.i32 %0, align 4, relaxed"), "{text}");
5636
5637 let text = body("long f(long *p) { return __atomic_load_n(p, 2); }\n");
5638 assert!(text.contains("atomic_load.i64 %0, align 8, acquire"), "{text}");
5639
5640 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
5641 assert!(text.contains("atomic_store %1 -> %0, align 4, release"), "{text}");
5642
5643 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
5644 assert!(text.contains("atomic_store %1 -> %0, align 4, seq_cst"), "{text}");
5645
5646 let text = body("void f(char *p, int v) { __atomic_store_n(p, v, 0); }\n");
5649 assert!(text.contains("trunc.i8 %1"), "{text}");
5650 assert!(text.contains("atomic_store %2 -> %0, align 1, relaxed"), "{text}");
5651 }
5652
5653 #[test]
5662 fn an_ordered_access_is_the_plain_instruction_on_this_machine() {
5663 let text = asm("int f(int *p) { return __atomic_load_n(p, 5); }\n");
5664 assert!(text.contains("movl\t(%rdi), %eax"), "{text}");
5665 assert!(!text.contains("mfence"), "a load needs no barrier here: {text}");
5666
5667 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
5668 assert!(text.contains("movl\t%esi, (%rdi)"), "{text}");
5669 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
5670
5671 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
5672 let (before, after) = text.split_once("mfence").expect("a barrier: {text}");
5673 assert!(before.contains("movl\t%esi, (%rdi)"), "the store comes first: {text}");
5674 assert!(!after.contains("movl"), "and nothing else is between them: {text}");
5675 }
5676
5677 #[test]
5687 fn a_barrier_is_one_instruction_at_the_strongest_ordering_and_none_below_it() {
5688 assert!(asm("void f(void) { __atomic_thread_fence(5); }\n").contains("mfence"));
5689 assert!(asm("void f(void) { __sync_synchronize(); }\n").contains("mfence"));
5690
5691 for weaker in ["1", "2", "3", "4"] {
5692 let source = format!("void f(void) {{ __atomic_thread_fence({weaker}); }}\n");
5693 assert!(!asm(&source).contains("mfence"), "{weaker} costs nothing here");
5694 }
5695 }
5696
5697 #[test]
5707 fn the_three_x86_fences_are_the_barrier_the_strongest_ordering_gives() {
5708 for name in ["__builtin_ia32_sfence", "__builtin_ia32_lfence", "__builtin_ia32_mfence"] {
5709 let source = format!("void f(void) {{ {name}(); }}\n");
5710 assert!(asm(&source).contains("mfence"), "{name} is a barrier");
5711 let text = body(&source);
5712 assert!(text.contains("fence seq_cst"), "{name}: {text}");
5713 }
5714
5715 let result = run(&options(), "void f(void) { __builtin_ia32_sfence(1); }\n");
5716 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
5717 assert!(result.messages[0].contains("too many arguments"), "{:?}", result.messages);
5718 }
5719
5720 #[test]
5726 fn a_compare_and_exchange_is_one_instruction_answering_two_things() {
5727 let text =
5730 body("int f(int *p, int e, int d) { return __sync_val_compare_and_swap(p, e, d); }\n");
5731 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
5732 assert!(text.contains("return %3"), "the value it found: {text}");
5733
5734 let text =
5735 body("int f(int *p, int e, int d) { return __sync_bool_compare_and_swap(p, e, d); }\n");
5736 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
5737 assert!(text.contains("zext.i32 %4"), "whether it happened: {text}");
5738
5739 let text = body(
5742 "int f(int *p, int *e, int d) { return __atomic_compare_exchange_n(p, e, d, 0, 4, 2); }\n",
5743 );
5744 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
5745 assert!(text.contains("%4, %5 = cmpxchg.(i32, i1) %0, %3, %2, align 4, acq_rel"), "{text}");
5746 assert!(text.contains("br_if %5, block2, block1"), "{text}");
5747 assert!(text.contains("store %4 -> %1, align 4"), "{text}");
5748
5749 let text = body(
5752 "int f(int *p, int *e, int *d) { return __atomic_compare_exchange(p, e, d, 0, 5, 5); }\n",
5753 );
5754 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
5755 assert!(text.contains("%4 = load.i32 %2, align 4"), "{text}");
5756 assert!(text.contains("%5, %6 = cmpxchg.(i32, i1) %0, %3, %4, align 4, seq_cst"), "{text}");
5757 }
5758
5759 #[test]
5766 fn a_compare_and_exchange_is_a_locked_instruction_at_the_width_of_the_object() {
5767 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
5768 for (ty, suffix, reg) in widths {
5769 let source = format!(
5770 "int f({ty} *p, {ty} e, {ty} d) {{ return __sync_bool_compare_and_swap(p, e, d); }}\n"
5771 );
5772 let text = asm(&source);
5773 assert!(text.contains("\tlock\n"), "{ty}: {text}");
5774 assert!(text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
5775 assert!(text.contains("sete\t"), "{ty}: {text}");
5776 }
5777 let source =
5778 "int f(long *p, long e, long d) { return __sync_bool_compare_and_swap(p, e, d); }\n";
5779 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
5780
5781 for order in ["0", "2", "3", "4", "5"] {
5785 let call = format!("__atomic_compare_exchange_n(p, e, d, 0, {order}, 0)");
5786 let source = format!("int f(int *p, int *e, int d) {{ return {call}; }}\n");
5787 let text = asm(&source);
5788 assert!(text.contains("cmpxchgl\t"), "{order}: {text}");
5789 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
5790 }
5791 }
5792
5793 #[test]
5805 fn a_read_modify_write_is_one_instruction_and_the_arithmetic_a_name_asks_for() {
5806 let text = body("int f(int *p, int v) { return __atomic_fetch_add(p, v, 5); }\n");
5807 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
5808 assert!(text.contains("return %2"), "the value that was there: {text}");
5809
5810 let text = body("int f(int *p, int v) { return __atomic_add_fetch(p, v, 5); }\n");
5811 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
5812 assert!(text.contains("%3 = add %2, %1"), "and the value afterwards: {text}");
5813
5814 let text = body("int f(int *p, int v) { return __atomic_sub_fetch(p, v, 5); }\n");
5815 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
5816 assert!(text.contains("%3 = sub %2, %1"), "{text}");
5817
5818 let text = body("int f(int *p, int v) { return __sync_fetch_and_sub(p, v); }\n");
5820 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
5821
5822 let text = body("int f(int *p, int v) { return __atomic_exchange_n(p, v, 5); }\n");
5825 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, seq_cst"), "{text}");
5826
5827 let text = body("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
5828 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, acquire"), "{text}");
5829
5830 let text = body("void f(int *p) { __sync_lock_release(p); }\n");
5833 assert!(text.contains("release"), "{text}");
5834 assert!(text.contains("%1 = iconst.i32 0"), "{text}");
5835
5836 let text = body("void f(int *p, int guard) { __sync_lock_release(p, guard); }\n");
5840 assert!(text.contains("%2 = iconst.i32 0"), "{text}");
5841 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
5842
5843 let text = body("int f(int *p, int v) { return __atomic_fetch_and(p, v, 5); }\n");
5846 assert!(text.contains("%2 = atomic_rmw.i32 and %0, %1, align 4, seq_cst"), "{text}");
5847
5848 let text = body("int f(int *p, int v) { return __sync_or_and_fetch(p, v); }\n");
5849 assert!(text.contains("%2 = atomic_rmw.i32 or %0, %1, align 4, seq_cst"), "{text}");
5850 assert!(text.contains("%3 = or %2, %1"), "and the value afterwards: {text}");
5851
5852 let text = body("int f(int *p, int v) { return __atomic_nand_fetch(p, v, 5); }\n");
5855 assert!(text.contains("%2 = atomic_rmw.i32 nand %0, %1, align 4, seq_cst"), "{text}");
5856 assert!(text.contains("%3 = and %2, %1"), "{text}");
5857 assert!(text.contains("%4 = iconst.i32 -1"), "{text}");
5858 assert!(text.contains("%5 = xor %3, %4"), "{text}");
5859 }
5860
5861 #[test]
5872 fn a_bitwise_read_modify_write_is_a_loop_around_the_compare_and_exchange() {
5873 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
5874 for (ty, suffix, reg) in widths {
5875 for (name, call, insn) in [
5876 ("and", "__atomic_fetch_and(p, v, 5)", "and"),
5877 ("or", "__sync_fetch_and_or(p, v)", "or"),
5878 ("xor", "__atomic_xor_fetch(p, v, 5)", "xor"),
5879 ] {
5880 let source = format!("{ty} f({ty} *p, {ty} v) {{ return {call}; }}\n");
5881 let text = asm(&source);
5882 assert!(text.contains("\tlock\n"), "{ty} {name}: {text}");
5883 assert!(
5884 text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")),
5885 "{ty} {name}: {text}"
5886 );
5887 assert!(text.contains(&format!("{insn}{suffix}\t")), "{ty} {name}: {text}");
5888 assert!(!text.contains("\txadd"), "{ty} {name} is not an add: {text}");
5890 assert!(!text.contains("\txchg"), "{ty} {name} is not an exchange: {text}");
5891 }
5892 }
5893 let source = "long f(long *p, long v) { return __atomic_fetch_or(p, v, 5); }\n";
5894 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
5895
5896 let text = asm("int f(int *p, int v) { return __sync_fetch_and_nand(p, v); }\n");
5900 assert!(text.contains("cmpxchgl\t"), "{text}");
5901 assert!(text.contains("andl\t"), "{text}");
5902 assert!(text.contains("notl\t"), "{text}");
5903 }
5904
5905 #[test]
5914 fn an_access_through_a_second_pointer_is_the_same_access_and_one_more() {
5915 let text = body("void f(int *p, int *r) { __atomic_load(p, r, 5); }\n");
5916 assert!(text.contains("%2 = atomic_load.i32 %0, align 4, seq_cst"), "{text}");
5917 assert!(text.contains("store %2 -> %1, align 4"), "and out through the place: {text}");
5918
5919 let text = body("void f(int *p, int *v) { __atomic_store(p, v, 3); }\n");
5920 assert!(text.contains("%2 = load.i32 %1, align 4"), "in through the place: {text}");
5921 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
5922
5923 let text = body("void f(int *p, int *v, int *r) { __atomic_exchange(p, v, r, 5); }\n");
5926 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
5927 assert!(text.contains("%4 = atomic_rmw.i32 xchg %0, %3, align 4, seq_cst"), "{text}");
5928 assert!(text.contains("store %4 -> %2, align 4"), "{text}");
5929 }
5930
5931 #[test]
5942 fn a_flag_is_an_exchange_of_one_byte_and_a_store_of_a_zero_over_the_same_byte() {
5943 for pointer in ["char", "int", "void"] {
5944 let source = format!("int f({pointer} *p) {{ return __atomic_test_and_set(p, 5); }}\n");
5945 let text = body(&source);
5946 assert!(text.contains("%1 = iconst.i8 1"), "{pointer}: {text}");
5947 assert!(
5948 text.contains("%2 = atomic_rmw.i8 xchg %0, %1, align 1, seq_cst"),
5949 "{pointer}: {text}"
5950 );
5951 assert!(text.contains("%4 = icmp ne %2, %3"), "{pointer}: {text}");
5952
5953 let source = format!("void f({pointer} *p) {{ __atomic_clear(p, 3); }}\n");
5954 let text = body(&source);
5955 assert!(text.contains("atomic_store %2 -> %0, align 1, release"), "{pointer}: {text}");
5956 }
5957
5958 let text = asm("int f(int *p) { return __atomic_test_and_set(p, 5); }\n");
5961 assert!(text.contains("xchgb\t%al, (%rdi)"), "{text}");
5962 assert!(text.contains("setne\t"), "{text}");
5963 }
5964
5965 #[test]
5973 fn a_read_modify_write_is_an_exchange_or_a_locked_add_at_the_width_of_the_object() {
5974 let widths = [("char", "b", "%sil"), ("short", "w", "%si"), ("int", "l", "%esi")];
5975 for (ty, suffix, reg) in widths {
5976 let source =
5977 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_fetch_add(p, v, 5); }}\n");
5978 let text = asm(&source);
5979 assert!(text.contains("\tlock\n"), "{ty}: {text}");
5980 assert!(text.contains(&format!("xadd{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
5981
5982 let source =
5983 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_exchange_n(p, v, 5); }}\n");
5984 let text = asm(&source);
5985 assert!(text.contains(&format!("xchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
5986 assert!(!text.contains("\tlock\n"), "an exchange is locked already: {ty}: {text}");
5987 }
5988 let source = "long f(long *p, long v) { return __atomic_fetch_add(p, v, 5); }\n";
5989 assert!(asm(source).contains("xaddq\t%rsi, (%rdi)"), "{}", asm(source));
5990
5991 let source = "int f(int *p, int v) { return __atomic_fetch_sub(p, v, 5); }\n";
5994 let text = asm(source);
5995 assert!(text.contains("negl\t"), "{text}");
5996 assert!(text.contains("xaddl\t"), "{text}");
5997
5998 for order in ["0", "2", "3", "4", "5"] {
6001 let source =
6002 format!("int f(int *p, int v) {{ return __atomic_fetch_add(p, v, {order}); }}\n");
6003 let text = asm(&source);
6004 assert!(text.contains("xaddl\t"), "{order}: {text}");
6005 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
6006 }
6007
6008 let text = asm("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
6012 assert!(text.contains("xchgl\t%esi, (%rdi)"), "{text}");
6013 let text = asm("void f(int *p) { __sync_lock_release(p); }\n");
6020 assert!(text.contains("xorl\t%eax, %eax"), "{text}");
6021 assert!(text.contains("movl\t%eax, (%rdi)"), "{text}");
6022 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
6023 }
6024
6025 #[test]
6037 fn the_lock_free_questions_are_answered_as_constants() {
6038 for size in ["1", "2", "4", "8"] {
6039 let source =
6040 format!("int f(void) {{ return __atomic_always_lock_free({size}, 0); }}\n");
6041 let text = asm(&source);
6042 assert!(text.contains("movb\t$1, %al"), "{size} bytes is lock free: {text}");
6043 assert!(!text.contains("call"), "and is not a call: {text}");
6044 }
6045 for size in ["3", "16", "sizeof(long double)"] {
6046 let source = format!("int f(void) {{ return __atomic_is_lock_free({size}, 0); }}\n");
6047 let text = asm(&source);
6048 assert!(text.contains("movb\t$0, %al"), "{size} bytes is not: {text}");
6049 assert!(!text.contains("call"), "and is not a call either: {text}");
6050 }
6051
6052 let text = asm("int f(int n) { return __atomic_is_lock_free(n, 0); }\n");
6056 assert!(text.contains("movb\t$0, %al"), "a size nobody knows is not lock free: {text}");
6057 let text = asm("int f(int *p) { return __atomic_always_lock_free(8, p); }\n");
6058 assert!(text.contains("movb\t$0, %al"), "eight bytes at four is not: {text}");
6059 let text = asm("int f(long *p) { return __atomic_always_lock_free(8, p); }\n");
6060 assert!(text.contains("movb\t$1, %al"), "and at eight it is: {text}");
6061 }
6062
6063 #[test]
6075 fn a_memory_order_an_operation_cannot_carry_is_read_as_the_strongest() {
6076 let mut opts = options();
6077 opts.emit = EmitKind::Ir;
6078
6079 let acquire_store = run(&opts, "void f(int *p, int v) { __atomic_store_n(p, v, 2); }\n");
6080 assert!(acquire_store.text().contains("seq_cst"), "{:?}", acquire_store.text());
6081 assert!(acquire_store.messages[0].contains("[W0333]"), "{:?}", acquire_store.messages);
6082
6083 let nonsense = run(&opts, "int f(int *p) { return __atomic_load_n(p, 99); }\n");
6084 assert!(nonsense.text().contains("seq_cst"), "{:?}", nonsense.text());
6085 assert!(nonsense.messages[0].contains("[W0333]"), "{:?}", nonsense.messages);
6086
6087 let computed = run(&opts, "int f(int *p, int n) { return __atomic_load_n(p, n); }\n");
6088 assert!(computed.text().contains("seq_cst"), "{:?}", computed.text());
6089 assert_eq!(computed.messages, Vec::<String>::new(), "a computed order is not a mistake");
6090 }
6091
6092 #[test]
6104 fn a_conversion_between_a_float_and_the_widest_unsigned_integer_is_written_without_a_branch() {
6105 let text = asm("double f(unsigned long long x) { return (double)x; }\n");
6106 assert!(text.contains("cvtsi2sdq"), "the signed conversion is what runs: {text}");
6107 assert!(text.contains("shrq"), "with the value halved first: {text}");
6108 assert!(text.contains("addsd"), "and doubled after: {text}");
6109 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
6110
6111 let text = asm("unsigned long long f(double d) { return (unsigned long long)d; }\n");
6112 assert!(text.contains("cvttsd2siq"), "the signed conversion is what runs: {text}");
6113 assert!(text.contains("subsd"), "with half the range taken off first: {text}");
6114 assert!(text.contains("shlq\t$63"), "and the top bit put back: {text}");
6115 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
6116 }
6117
6118 #[test]
6129 fn a_plain_name_the_program_took_is_the_programs_own_function() {
6130 let taken = concat!(
6131 "static long long llabs(long long b) { return 7; }\n",
6132 "long long f(long long x) { return llabs(x); }\n",
6133 );
6134 assert!(ir(taken).contains("call @llabs"), "a static definition is the program's own");
6135
6136 let retyped = concat!("int llabs(int b);\n", "int f(int x) { return llabs(x); }\n",);
6137 assert!(ir(retyped).contains("call @llabs"), "another type is another function");
6138
6139 let plain = concat!(
6140 "long long llabs(long long b);\n",
6141 "long long f(long long x) { return llabs(x); }\n",
6142 );
6143 let mut opts = options();
6144 opts.emit = EmitKind::Ir;
6145 assert!(!run(&opts, plain).text().contains("call @llabs"), "the library's by default");
6146
6147 opts.builtins = false;
6148 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin");
6149
6150 opts.builtins = true;
6151 opts.no_builtin = vec!["llabs".to_owned()];
6152 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin-llabs");
6153 let one = "long labs(long b);\nlong f(long x) { return labs(x); }\n";
6154 assert!(!run(&opts, one).text().contains("call @labs"), "one name and not the family");
6155
6156 opts.no_builtin = Vec::new();
6159 opts.builtins = false;
6160 let prefixed = "long long f(long long x) { return __builtin_llabs(x); }\n";
6161 assert!(!run(&opts, prefixed).text().contains("call @llabs"), "the prefix is a promise");
6162 }
6163
6164 #[test]
6177 fn the_hint_builtins_are_their_first_argument_and_the_hint_leaves_no_trace() {
6178 let text = ir(concat!(
6179 "long a = __builtin_expect(7, 1);\n",
6180 "long b = __builtin_expect_with_probability(9, 1, 0.9);\n",
6181 "unsigned long c = sizeof(__builtin_expect((char)1, 1));\n",
6182 ));
6183 assert!(text.contains("global @a : i64 = 7,"), "{text}");
6184 assert!(text.contains("global @b : i64 = 9,"), "{text}");
6185 assert!(text.contains("global @c : i64 = 8,"), "{text}");
6186 assert!(!text.contains("__builtin_expect"), "it is not a call to anything:\n{text}");
6187
6188 let text = body("long f(char c) { return __builtin_expect(c, 1); }\n");
6191 assert!(text.contains("sext"), "{text}");
6192
6193 let one = "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 1\n %2 = sext.i64 %1\n return %0\n";
6197 assert_eq!(body("int f(void) { int i = 0; __builtin_expect(1, i++); return i; }\n"), one);
6198 let source = "int g(void) { int i = 0; __builtin_expect_with_probability(1, i++, 0.5); return i; }\n";
6199 assert_eq!(body(source), one);
6200
6201 let kept = body("int f(int n) { int i = 0; __builtin_expect(n, i++); return i; }\n");
6206 assert!(kept.contains("add.nsw"), "the hint still runs: {kept}");
6207 assert!(kept.ends_with("return %3\n"), "and the answer is what it left behind: {kept}");
6208 let both = "int g(int n) { int i = 0; __builtin_expect_with_probability(n, i++, 0.5); return i; }\n";
6209 assert!(body(both).contains("add.nsw"), "and so does the one with three arguments");
6210 }
6211
6212 #[test]
6224 fn a_promise_that_control_does_not_arrive_writes_no_instruction() {
6225 let promised = "int f(int x) { if (x) return 1; __builtin_unreachable(); }\n";
6226 let text = ir(promised);
6227 assert!(text.contains(" unreachable_hint\n"), "{text}");
6228 assert!(!text.contains("call"), "it is not a call to anything:\n{text}");
6229
6230 let after = body("int g(int x) { __builtin_unreachable(); return x; }\n");
6234 assert!(after.contains("return"), "{after}");
6235
6236 let text = asm(promised);
6239 let mine = text.split_once("\nf:\n").expect("a definition").1;
6240 let mine = mine.split_once("\t.size").expect("a definition").0;
6241 let plain = asm("int f(int x) { if (x) return 1; }\n");
6242 let plain = plain.split_once("\nf:\n").expect("a definition").1;
6243 let plain = plain.split_once("\t.size").expect("a definition").0;
6244 assert_eq!(mine, plain);
6245 let last = mine.lines().rfind(|line| !line.trim_start().starts_with('.'));
6248 assert_eq!(last.map(str::trim), Some("ret"), "{mine}");
6249 assert!(!mine.contains("ud2"), "{mine}");
6250 }
6251
6252 #[test]
6259 fn a_library_builtin_is_diagnosed_under_the_name_the_program_wrote() {
6260 let mut opts = options();
6261 opts.emit = EmitKind::Ir;
6262 let messages = run(&opts, "void f(void) { __builtin_abort(1); }\n").messages;
6263 assert!(
6264 messages.iter().any(|m| m.contains("__builtin_abort")),
6265 "expected the written name in {messages:?}"
6266 );
6267 }
6268
6269 #[test]
6277 fn a_builtin_nothing_lowers_is_refused_by_name() {
6278 let mut opts = options();
6279 opts.emit = EmitKind::Ir;
6280 let builtin = "__atomic_signal_fence";
6281 let source = format!("int counter;\nint f(void) {{ return ({builtin}(5), 0); }}\n");
6282 let messages = run(&opts, &source).messages;
6283 let named = messages.iter().any(|m| m.contains(builtin) && m.contains("E0686"));
6284 assert!(named, "expected {builtin} to be refused by name in {messages:?}");
6285 }
6286
6287 #[test]
6296 fn what_is_refused_is_the_call_and_not_the_name() {
6297 let text = ir(concat!(
6298 "void __atomic_signal_fence(int order) { (void)order; }\n",
6299 "void f(void) { __atomic_signal_fence(5); }\n",
6300 ));
6301 assert!(text.contains("call @__atomic_signal_fence"), "{text}");
6302 }
6303
6304 #[test]
6313 fn the_object_size_of_an_address_is_what_the_layout_leaves_in_front_of_it() {
6314 let text = ir(concat!(
6315 "struct S { char a[8]; int n; char b[12]; };\n",
6316 "char g[32];\n",
6317 "struct S gs;\n",
6318 "unsigned long whole = __builtin_object_size(g, 0);\n",
6319 "unsigned long moved = __builtin_object_size(g + 4, 0);\n",
6320 "unsigned long back = __builtin_object_size(g + 30 - 2, 0);\n",
6321 "unsigned long outer = __builtin_object_size(gs.a, 0);\n",
6322 "unsigned long inner = __builtin_object_size(gs.a, 1);\n",
6323 "unsigned long scalar = __builtin_object_size(&gs.n, 1);\n",
6324 "unsigned long after = __builtin_object_size(&gs.n, 0);\n",
6325 "unsigned long into = __builtin_object_size(&gs.b[2], 1);\n",
6326 "unsigned long text = __builtin_object_size(\"hello\", 0);\n",
6327 "unsigned long dyn = __builtin_dynamic_object_size(gs.b, 1);\n",
6328 ));
6329 for (name, size) in [
6330 ("whole", 32),
6331 ("moved", 28),
6332 ("back", 4),
6333 ("outer", 24),
6334 ("inner", 8),
6335 ("scalar", 4),
6336 ("after", 16),
6337 ("into", 10),
6338 ("text", 6),
6339 ("dyn", 12),
6340 ] {
6341 let said = format!("global @{name} : i64 = {size},");
6342 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
6343 }
6344 }
6345
6346 #[test]
6354 fn the_object_behind_an_address_can_be_one_with_automatic_storage() {
6355 let text = body(concat!(
6356 "struct S { char a[8]; int n; char b[12]; };\n",
6357 "unsigned long f(void) {\n",
6358 " char loc[20];\n",
6359 " struct S ls;\n",
6360 " return __builtin_object_size(loc + 3, 0) + __builtin_object_size(ls.b + 2, 1);\n",
6361 "}\n",
6362 ));
6363 assert!(text.contains("iconst.i64 17"), "twenty bytes with three used: {text}");
6364 assert!(text.contains("iconst.i64 10"), "twelve bytes with two used: {text}");
6365 }
6366
6367 #[test]
6377 fn an_address_with_no_object_in_sight_answers_at_the_end_of_the_range_its_kind_asks_for() {
6378 let text = ir(concat!(
6379 "struct T { int n; char f[]; };\n",
6380 "extern char *p;\n",
6381 "extern struct T *t;\n",
6382 "unsigned long largest = __builtin_object_size(p, 0);\n",
6383 "unsigned long nearest = __builtin_object_size(p, 1);\n",
6384 "unsigned long least = __builtin_object_size(p, 2);\n",
6385 "unsigned long tight = __builtin_object_size(p, 3);\n",
6386 "unsigned long flex = __builtin_object_size(t->f, 1);\n",
6387 "int says = __builtin_object_size(p, 0) == (unsigned long)-1;\n",
6388 ));
6389 for name in ["largest", "nearest", "flex"] {
6390 let said = format!("global @{name} : i64 = -1,");
6394 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
6395 }
6396 for name in ["least", "tight"] {
6397 let said = format!("global @{name} : i64 = 0,");
6398 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
6399 }
6400 assert!(text.contains("global @says : i32 = 1,"), "{text}");
6401 }
6402
6403 #[test]
6410 fn the_address_an_object_size_is_asked_about_is_not_evaluated() {
6411 let text = body(concat!(
6412 "extern char *side(void);\n",
6413 "unsigned long f(void) { return __builtin_object_size(side(), 0); }\n",
6414 ));
6415 assert!(!text.contains("call"), "nothing is called: {text}");
6416 }
6417
6418 #[test]
6423 fn a_kind_that_is_not_one_of_the_four_is_refused() {
6424 for source in [
6425 "extern char *p;\nextern int k;\nunsigned long f(void) ".to_owned()
6426 + "{ return __builtin_object_size(p, k); }\n",
6427 "extern char *p;\nunsigned long f(void) { return __builtin_object_size(p, 4); }\n"
6428 .to_owned(),
6429 "extern char *p;\nunsigned long f(void) ".to_owned()
6430 + "{ return __builtin_dynamic_object_size(p, -1); }\n",
6431 ] {
6432 let messages = errors(&source);
6433 let named = messages.iter().any(|m| m.contains("E0709") && m.contains("0 to 3"));
6434 assert!(named, "expected a complaint about the kind in {messages:?}");
6435 }
6436 }
6437
6438 #[test]
6444 fn the_pair_that_saves_a_place_lowers_to_the_two_markers() {
6445 let text = ir(concat!(
6446 "void *buf[5];\n",
6447 "int f(void) {\n",
6448 " if (__builtin_setjmp(buf)) return 2;\n",
6449 " return 1;\n",
6450 "}\n",
6451 "void g(void) { __builtin_longjmp(buf, 1); }\n",
6452 ));
6453 assert!(text.contains("= setjmp_marker.i32 %0\n"), "the save answers a value: {text}");
6454 assert!(text.contains(" longjmp_marker %0\n"), "the restore answers nothing: {text}");
6455 assert!(!text.contains("call @"), "neither of them is a call: {text}");
6456 }
6457
6458 #[test]
6466 fn a_local_of_a_function_that_saves_a_place_gets_a_slot() {
6467 let text = ir(concat!(
6468 "void *buf[5];\n",
6469 "int f(int x) { int a = 0; if (__builtin_setjmp(buf)) return a; a = 1; return x; }\n",
6470 "int g(int x) { int a = 0; if (x) return a; a = 1; return x; }\n",
6471 ));
6472 let (saves, plain) = text.split_once("func @g").expect("both functions");
6473 assert_eq!(saves.matches("= alloca").count(), 2, "the parameter and the local: {text}");
6474 assert!(saves.contains("store %9 -> %2"), "the local is written through: {text}");
6475 assert!(!plain.contains("alloca"), "nothing in the plain one needs a slot: {text}");
6476 }
6477
6478 #[test]
6487 fn the_save_writes_four_words_and_carries_on_in_a_new_block() {
6488 let text =
6489 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
6490 let body = text.split_once("\nf:\n").expect("the function").1;
6491 assert!(body.contains("\tmovq\t%rsp, %rbp\n"), "a frame pointer whatever: {text}");
6492 assert!(body.contains("\tsubq\t$8, %rsp\n"), "no red zone: {text}");
6493 assert!(body.contains("\tmovq\t%rbp, (%rax)\n"), "the frame pointer: {text}");
6494 assert!(body.contains("\tmovq\t%rsp, 16(%rax)\n"), "the stack pointer: {text}");
6495 assert!(body.contains("\tleaq\t.Lf_1(%rip), %rcx\n"), "where to come back to: {text}");
6496 assert!(body.contains("\tmovq\t%rcx, 8(%rax)\n"), "and that goes in the buffer: {text}");
6497 let back = body.split_once(".Lf_1:\n").expect("the block control comes back to").1;
6498 assert!(back.starts_with("\tmovq\t(%rsp), %rax\n"), "the answer is read back: {text}");
6499 }
6500
6501 #[test]
6509 fn a_save_destroys_every_register_the_allocator_hands_out() {
6510 let text =
6511 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
6512 for reg in ["%rbx", "%r12", "%r13", "%r14", "%r15"] {
6513 assert!(text.contains(&format!("\tpushq\t{reg}\n")), "{reg} is saved: {text}");
6514 assert!(text.contains(&format!("\tpopq\t{reg}\n")), "{reg} is restored: {text}");
6515 }
6516 }
6517
6518 #[test]
6525 fn the_restore_puts_the_frame_back_before_it_jumps() {
6526 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
6527 let mut opts = options();
6528 opts.emit = EmitKind::Asm;
6529 opts.opt_level = level;
6530 let source = "void *buf[5];\nvoid g(void) { __builtin_longjmp(buf, 1); }\n";
6531 let result = run(&opts, source);
6532 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
6533 let text = result.text().to_owned();
6534 let jump = text.find("\tjmp\t*%").unwrap_or_else(|| panic!("an indirect jump: {text}"));
6535 let stack = text.find(", %rsp\n").unwrap_or_else(|| panic!("the stack back: {text}"));
6536 let frame = text.find(", %rbp\n").unwrap_or_else(|| panic!("the frame back: {text}"));
6537 assert!(stack < jump, "the stack goes back first at {level:?}: {text}");
6538 assert!(frame < jump, "and so does the frame at {level:?}: {text}");
6539 }
6540 }
6541
6542 #[test]
6548 fn a_longjmp_whose_second_argument_is_not_one_is_turned_down() {
6549 for source in [
6550 "void *buf[5];\nvoid f(void) { __builtin_longjmp(buf, 0); }\n",
6551 "void *buf[5];\nextern int v;\nvoid f(void) { __builtin_longjmp(buf, v); }\n",
6552 ] {
6553 let messages = errors(source);
6554 let named = messages.iter().any(|m| m.contains("E0710"));
6555 assert!(named, "expected a complaint about the value in {messages:?}");
6556 }
6557 }
6558
6559 #[test]
6564 fn a_static_function_nothing_refers_to_is_not_emitted() {
6565 let text = ir("static int dropped(void) { return 1; }\n\
6566 static int kept(void) { return 2; }\n\
6567 int main(void) { return kept(); }\n");
6568 assert!(text.contains("func @kept"), "{text}");
6569 assert!(!text.contains("dropped"), "{text}");
6570 }
6571
6572 #[test]
6578 fn two_static_functions_that_only_call_each_other_are_both_dropped() {
6579 let text = ir("static int ping(void);\n\
6580 static int pong(void) { return ping(); }\n\
6581 static int ping(void) { return pong(); }\n\
6582 int main(void) { return 0; }\n");
6583 assert!(!text.contains("ping"), "{text}");
6584 assert!(!text.contains("pong"), "{text}");
6585 }
6586
6587 #[test]
6593 fn naming_a_static_function_anywhere_keeps_it() {
6594 let text = ir("static int by_address(void) { return 1; }\n\
6595 static int in_an_image(void) { return 2; }\n\
6596 static int deeper(void) { return 3; }\n\
6597 static int reaches_deeper(void) { return deeper(); }\n\
6598 static int (*table[1])(void) = {in_an_image};\n\
6599 int main(void) {\n\
6600 int (*p)(void) = by_address;\n\
6601 return p() + table[0]() + reaches_deeper();\n\
6602 }\n");
6603 for kept in ["by_address", "in_an_image", "deeper", "reaches_deeper"] {
6604 assert!(text.contains(&format!("func @{kept}")), "expected {kept} in:\n{text}");
6605 }
6606 }
6607
6608 #[test]
6614 fn an_attribute_keeps_a_static_function_nothing_refers_to() {
6615 for attribute in ["used", "retain", "constructor", "destructor", "__used__"] {
6616 let source = format!(
6617 "__attribute__(({attribute})) static int kept(void) {{ return 1; }}\n\
6618 int main(void) {{ return 0; }}\n"
6619 );
6620 let text = ir(&source);
6621 assert!(text.contains("func @kept"), "for {attribute}:\n{text}");
6622 }
6623 }
6624
6625 #[test]
6628 fn a_function_anything_could_call_is_emitted_without_being_called() {
6629 let text =
6630 ir("int nobody_here_calls_it(void) { return 1; }\nint main(void) { return 0; }\n");
6631 assert!(text.contains("func @nobody_here_calls_it"), "{text}");
6632 }
6633
6634 #[test]
6641 fn a_classification_c_has_an_operator_for_is_that_operator() {
6642 for (builtin, operator) in [
6643 ("__builtin_isgreater", "binary >"),
6644 ("__builtin_isgreaterequal", "binary >="),
6645 ("__builtin_isless", "binary <"),
6646 ("__builtin_islessequal", "binary <="),
6647 ] {
6648 let source = format!("int f(double x, double y) {{ return {builtin}(x, y); }}\n");
6649 let text = tast(&source);
6650 assert!(text.contains(&format!("{operator} : int")), "for {builtin}:\n{text}");
6651 }
6652 }
6653
6654 #[test]
6663 fn the_classification_builtins_are_comparisons_and_not_calls() {
6664 let text = body("int f(double x, double y) { return __builtin_isunordered(x, y); }\n");
6665 assert_eq!(
6666 text,
6667 "block0(%0: f64, %1: f64):\n %2 = fcmp uno %0, %1\n %3 = zext.i32 \
6668 %2\n return %3\n"
6669 );
6670
6671 let text = body("int f(double x, double y) { return __builtin_islessgreater(x, y); }\n");
6673 assert!(text.contains("fcmp one %0, %1"), "{text}");
6674
6675 let text = body("int f(double x) { return __builtin_isnan(x); }\n");
6676 assert!(text.contains("fcmp uno %0, %0"), "{text}");
6677
6678 let text = body("int f(double x) { return __builtin_isinf(x); }\n");
6679 assert!(text.contains("fconst.f64 0x7ff0000000000000"), "{text}");
6680 assert!(text.contains("fconst.f64 0xfff0000000000000"), "{text}");
6681 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
6682 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
6683 assert!(text.contains("%5 = or %3, %4"), "{text}");
6684
6685 let text = body("int f(double x) { return __builtin_isfinite(x); }\n");
6688 assert!(text.contains("%3 = fcmp olt %2, %0"), "{text}");
6689 assert!(text.contains("%4 = fcmp olt %0, %1"), "{text}");
6690 assert!(text.contains("%5 = and %3, %4"), "{text}");
6691
6692 let text = body("int f(double x) { return __builtin_signbit(x); }\n");
6693 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
6694 assert!(text.contains("icmp slt %1, %2"), "{text}");
6695
6696 let text = body("int f(long double x) { return __builtin_signbitl(x); }\n");
6700 assert!(text.contains("load.i16"), "{text}");
6701 assert!(text.contains("icmp slt"), "{text}");
6702 assert!(!text.contains("i80"), "{text}");
6703
6704 let text = body("double g(void);\nint f(void) { return __builtin_isnan(g()); }\n");
6707 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
6708 }
6709
6710 #[test]
6717 fn a_classification_spelling_that_names_a_width_converts_before_it_asks() {
6718 let text = ir(concat!(
6719 "int a = __builtin_isinff(1e300);\n",
6720 "int b = __builtin_isinf(1e300);\n",
6721 "int c = __builtin_isnan(0.0);\n",
6725 "int d = __builtin_signbit(-0.0);\n",
6726 "int e = __builtin_islessgreater(1.0, 2.0);\n",
6727 ));
6728 assert!(text.contains("global @a : i32 = 1,"), "{text}");
6729 assert!(text.contains("global @b : i32 = 0,"), "{text}");
6730 assert!(text.contains("global @c : i32 = 0,"), "{text}");
6731 assert!(text.contains("global @d : i32 = 1,"), "{text}");
6732 assert!(text.contains("global @e : i32 = 1,"), "{text}");
6733 }
6734
6735 #[test]
6737 fn a_classification_builtin_refuses_an_argument_that_is_not_floating_point() {
6738 let mut opts = options();
6739 opts.emit = EmitKind::Ir;
6740 let source = concat!(
6741 "int a(int x) { return __builtin_isnan(x); }\n",
6742 "int b(int x, int y) { return __builtin_isunordered(x, y); }\n",
6743 "int c(double x) { return __builtin_isnan(x, x); }\n",
6744 );
6745 let messages = run(&opts, source).messages;
6746 assert_eq!(
6747 messages,
6748 [
6749 "/main.c:1:23: error: non-floating-point argument in call to function \
6750 '__builtin_isnan' [E0685]",
6751 "/main.c:2:30: error: non-floating-point arguments in call to function \
6752 '__builtin_isunordered' [E0685]",
6753 "/main.c:3:26: error: too many arguments to function '__builtin_isnan' [E0511]",
6754 ]
6755 );
6756 }
6757
6758 #[test]
6767 fn the_last_three_classification_builtins_are_comparisons_and_not_calls() {
6768 let text = body("int f(double x) { return __builtin_isnormal(x); }\n");
6769 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
6773 assert!(text.contains("%2 = iconst.i64 9223372036854775807"), "{text}");
6774 assert!(text.contains("%3 = and %1, %2"), "{text}");
6775 assert!(text.contains("%4 = iconst.i64 4503599627370496"), "{text}");
6776 assert!(text.contains("%5 = iconst.i64 9218868437227405312"), "{text}");
6777 assert!(text.contains("%6 = icmp uge %3, %4"), "{text}");
6778 assert!(text.contains("%7 = icmp ult %3, %5"), "{text}");
6779 assert!(text.contains("%8 = and %6, %7"), "{text}");
6780
6781 let text = body("int f(long double x) { return __builtin_isnormal(x); }\n");
6786 assert!(text.contains("fconst.f80 0x18000000000000000"), "{text}");
6787 assert!(text.contains("fconst.f80 0x7fff8000000000000000"), "{text}");
6788 assert!(text.contains("fcmp oge"), "{text}");
6789 assert!(text.contains("fcmp olt"), "{text}");
6790
6791 let text = body("int f(double x) { return __builtin_isinf_sign(x); }\n");
6792 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
6793 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
6794 assert!(text.contains("%7 = sub %5, %6"), "{text}");
6795
6796 let text = body("int f(double x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n");
6797 assert!(text.contains("fcmp uno %0, %0"), "{text}");
6798 assert!(text.contains("fcmp oeq %0, %6"), "{text}");
6799 assert_eq!(text.matches(" = zext.i32 ").count(), 4, "{text}");
6803 assert_eq!(text.matches(" = xor ").count(), 4, "{text}");
6804 assert!(!text.contains("call"), "{text}");
6805
6806 let text = body(concat!(
6809 "double g(void);\n",
6810 "int f(void) { return __builtin_fpclassify(0, 1, 2, 3, 4, g()); }\n",
6811 ));
6812 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
6813 }
6814
6815 #[test]
6822 fn the_last_three_classification_builtins_fold_where_their_operand_is_a_constant() {
6823 let text = ir(concat!(
6824 "int a = __builtin_isnormal(1.0);\n",
6825 "int b = __builtin_isnormal(0.0);\n",
6826 "int c = __builtin_isnormal(1.0 / 0.0);\n",
6827 "int d = __builtin_isinf_sign(-1.0 / 0.0);\n",
6828 "int e = __builtin_isinf_sign(1.0);\n",
6829 "int g = __builtin_fpclassify(0, 1, 2, 3, 4, 0.0);\n",
6830 "int h = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0);\n",
6831 "int i = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0 / 0.0);\n",
6832 ));
6833 assert!(text.contains("global @a : i32 = 1,"), "{text}");
6834 assert!(text.contains("global @b : i32 = 0,"), "{text}");
6835 assert!(text.contains("global @c : i32 = 0,"), "{text}");
6836 assert!(text.contains("global @d : i32 = -1,"), "{text}");
6837 assert!(text.contains("global @e : i32 = 0,"), "{text}");
6838 assert!(text.contains("global @g : i32 = 4,"), "{text}");
6839 assert!(text.contains("global @h : i32 = 2,"), "{text}");
6840 assert!(text.contains("global @i : i32 = 1,"), "{text}");
6841 }
6842
6843 #[test]
6849 fn fpclassify_refuses_an_answer_that_is_not_an_integer_constant() {
6850 let mut opts = options();
6851 opts.emit = EmitKind::Ir;
6852 let source = concat!(
6853 "int a(double x, int n) { return __builtin_fpclassify(0, 1, n, 3, 4, x); }\n",
6854 "int b(double x) { return __builtin_fpclassify(0, 1, 2, 3, x); }\n",
6855 "int c(int x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n",
6856 );
6857 let messages = run(&opts, source).messages;
6858 assert_eq!(
6859 messages,
6860 [
6861 "/main.c:1:60: error: non-const integer argument 3 in call to function \
6862 '__builtin_fpclassify' [E0687]",
6863 "/main.c:2:26: error: too few arguments to function '__builtin_fpclassify' \
6864 [E0511]",
6865 "/main.c:3:23: error: non-floating-point argument in call to function \
6866 '__builtin_fpclassify' [E0685]",
6867 ]
6868 );
6869 }
6870
6871 #[test]
6879 fn a_builtin_whose_answer_is_a_constant_is_one_and_not_a_call() {
6880 let text = ir(concat!(
6881 "double a = __builtin_inf();\n",
6882 "float b = __builtin_huge_valf();\n",
6883 "long double c = __builtin_infl();\n",
6884 "double d = __builtin_huge_val();\n",
6885 ));
6886 assert!(text.contains("global @a : f64 = 0x7ff0000000000000,"), "{text}");
6887 assert!(text.contains("global @b : f32 = 0x7f800000,"), "{text}");
6888 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
6889 assert!(text.contains("global @d : f64 = 0x7ff0000000000000,"), "{text}");
6890 assert!(!text.contains("call"), "{text}");
6891 }
6892
6893 #[test]
6902 fn a_nan_is_written_with_the_payload_the_program_asked_for() {
6903 let text = ir(concat!(
6904 "double a = __builtin_nan(\"\");\n",
6905 "double b = __builtin_nan(\"0x1\");\n",
6906 "double c = __builtin_nan(\"010\");\n",
6908 "double d = __builtin_nans(\"\");\n",
6909 "double e = __builtin_nans(\"0x1\");\n",
6910 "float f = __builtin_nanf(\"0x1\");\n",
6911 "float g = __builtin_nansf(\"\");\n",
6912 "long double h = __builtin_nansl(\"\");\n",
6913 ));
6914 assert!(text.contains("global @a : f64 = 0x7ff8000000000000,"), "{text}");
6915 assert!(text.contains("global @b : f64 = 0x7ff8000000000001,"), "{text}");
6916 assert!(text.contains("global @c : f64 = 0x7ff8000000000008,"), "{text}");
6917 assert!(text.contains("global @d : f64 = 0x7ff4000000000000,"), "{text}");
6918 assert!(text.contains("global @e : f64 = 0x7ff0000000000001,"), "{text}");
6919 assert!(text.contains("global @f : f32 = 0x7fc00001,"), "{text}");
6920 assert!(text.contains("global @g : f32 = 0x7fa00000,"), "{text}");
6921 assert!(text.contains("f80 0x7fffa000000000000000"), "{text}");
6922
6923 let text = ir(concat!(
6926 "double f(const char *p) { return __builtin_nan(p); }\n",
6927 "double g(void) { return __builtin_nans(\"1x\"); }\n",
6928 ));
6929 assert_eq!(text.matches("call @nan(").count(), 1, "{text}");
6930 assert_eq!(text.matches("call @nans(").count(), 1, "{text}");
6931 }
6932
6933 #[test]
6941 fn the_length_and_the_order_of_a_string_literal_are_known_here() {
6942 let text = ir(concat!(
6943 "unsigned long a = __builtin_strlen(\"hello\");\n",
6944 "unsigned long b = __builtin_strlen(\"a\\0bc\");\n",
6945 "int c = __builtin_strcmp(\"X\", \"X\\376\") < 0;\n",
6946 "int d = __builtin_strcmp(\"abc\", \"abc\");\n",
6947 "int e = __builtin_strcmp(\"abc\", \"ab\") > 0;\n",
6948 ));
6949 assert!(text.contains("global @a : i64 = 5,"), "{text}");
6950 assert!(text.contains("global @b : i64 = 1,"), "{text}");
6951 assert!(text.contains("global @c : i32 = 1,"), "{text}");
6952 assert!(text.contains("global @d : i32 = 0,"), "{text}");
6953 assert!(text.contains("global @e : i32 = 1,"), "{text}");
6954 assert!(!text.contains("call"), "{text}");
6955
6956 let text = ir("unsigned long f(const char *p) { return __builtin_strlen(p); }\n");
6958 assert!(text.contains("call @strlen("), "{text}");
6959 }
6960
6961 #[test]
6968 fn a_sign_builtin_is_a_mask_over_the_bits_and_not_a_call() {
6969 let text = body("double f(double x) { return __builtin_fabs(x); }\n");
6970 assert!(text.contains("bitcast.i64 %0"), "{text}");
6971 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
6972 assert!(text.contains("and %1, %2"), "{text}");
6973 assert!(text.contains("bitcast.f64 %3"), "{text}");
6974 assert!(!text.contains("call"), "{text}");
6975
6976 let text = body("double f(double x, double y) { return __builtin_copysign(x, y); }\n");
6977 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
6978 assert!(text.contains("%8 = or %4, %7"), "{text}");
6979 assert!(!text.contains("call"), "{text}");
6980
6981 let text = body("long double f(long double x) { return __builtin_fabsl(x); }\n");
6984 assert!(text.contains("iconst.i16 32767"), "{text}");
6985 assert!(text.contains("load.f80"), "{text}");
6986 assert!(!text.contains("call"), "{text}");
6987
6988 let text = body("double f(float x) { return __builtin_fabs(x); }\n");
6991 assert!(text.contains("fpext.f64 %0"), "{text}");
6992 assert!(text.contains("bitcast.i64 %1"), "{text}");
6993 }
6994
6995 #[test]
7004 fn the_plain_math_names_are_the_same_mask_and_not_a_call() {
7005 let text =
7006 body(concat!("double fabs(double x);\n", "double f(double x) { return fabs(x); }\n",));
7007 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
7008 assert!(!text.contains("call"), "{text}");
7009
7010 let text =
7011 body(concat!("float fabsf(float x);\n", "float f(float x) { return fabsf(x); }\n",));
7012 assert!(text.contains("bitcast.i32 %0"), "{text}");
7013 assert!(!text.contains("call"), "{text}");
7014
7015 let text = body(concat!(
7016 "double copysign(double x, double y);\n",
7017 "double f(double x, double y) { return copysign(x, y); }\n",
7018 ));
7019 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
7020 assert!(!text.contains("call"), "{text}");
7021
7022 let text = body(concat!(
7023 "float copysignf(float x, float y);\n",
7024 "float f(float x, float y) { return copysignf(x, y); }\n",
7025 ));
7026 assert!(!text.contains("call"), "{text}");
7027
7028 let text = ir(concat!(
7032 "long double fabsl(long double x);\n",
7033 "long double f(long double x) { return fabsl(x); }\n",
7034 ));
7035 assert!(text.contains("call @fabsl"), "{text}");
7036 }
7037
7038 #[test]
7046 fn a_plain_math_name_the_program_took_is_the_programs_own_function() {
7047 let taken = concat!(
7048 "static double fabs(double b) { return 7; }\n",
7049 "double f(double x) { return fabs(x); }\n",
7050 );
7051 assert!(ir(taken).contains("call @fabs"), "a static definition is the program's own");
7052
7053 let retyped = concat!("int fabs(int b);\n", "int f(int x) { return fabs(x); }\n");
7054 assert!(ir(retyped).contains("call @fabs"), "another type is another function");
7055
7056 let plain = concat!("double fabs(double b);\n", "double f(double x) { return fabs(x); }\n");
7057 let mut opts = options();
7058 opts.emit = EmitKind::Ir;
7059 assert!(!run(&opts, plain).text().contains("call @fabs"), "the library's by default");
7060
7061 opts.builtins = false;
7062 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin");
7063
7064 opts.builtins = true;
7065 opts.no_builtin = vec!["fabs".to_owned()];
7066 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin-fabs");
7067 let one = concat!(
7068 "double copysign(double a, double b);\n",
7069 "double f(double x) { return copysign(x, 1.0); }\n",
7070 );
7071 assert!(!run(&opts, one).text().contains("call @copysign"), "one name and not the family");
7072
7073 opts.no_builtin = Vec::new();
7075 opts.builtins = false;
7076 let prefixed = "double f(double x) { return __builtin_fabs(x); }\n";
7077 assert!(!run(&opts, prefixed).text().contains("call @fabs"), "the prefix is not a library");
7078 }
7079
7080 #[test]
7089 fn the_sign_builtins_answer_a_zero_and_a_nan_the_way_the_bits_say() {
7090 let text = ir(concat!(
7091 "double a = __builtin_fabs(-3.5);\n",
7092 "double b = __builtin_copysign(1.0, -0.0);\n",
7093 "double c = __builtin_copysign(0.0, -2.0);\n",
7094 "double d = __builtin_copysign(-__builtin_nan(\"\"), 1.0);\n",
7096 "double e = __builtin_fabs(-__builtin_nan(\"0x1\"));\n",
7097 "float g = __builtin_copysignf(-0.0f, 2.0f);\n",
7098 "long double h = __builtin_copysignl(1.0L, -1.0L);\n",
7099 "long double i = __builtin_fabsl(-__builtin_infl());\n",
7100 ));
7101 assert!(text.contains("global @a : f64 = 0x400c000000000000,"), "{text}");
7102 assert!(text.contains("global @b : f64 = 0xbff0000000000000,"), "{text}");
7103 assert!(text.contains("global @c : f64 = 0x8000000000000000,"), "{text}");
7104 assert!(text.contains("global @d : f64 = 0x7ff8000000000000,"), "{text}");
7105 assert!(text.contains("global @e : f64 = 0x7ff8000000000001,"), "{text}");
7106 assert!(text.contains("global @g : f32 = 0x0,"), "{text}");
7107 assert!(text.contains("f80 0xbfff8000000000000000"), "{text}");
7108 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
7109 }
7110
7111 #[test]
7118 fn the_sign_of_a_long_double_is_in_the_word_at_the_top_of_it() {
7119 for source in [
7120 "int f(long double x) { return __builtin_signbit(x); }\n",
7121 "long double f(long double x) { return __builtin_fabsl(x); }\n",
7122 "long double f(long double x, long double y) { return __builtin_copysignl(x, y); }\n",
7123 "int f(long double x) { return __builtin_isnormal(x); }\n",
7124 ] {
7125 let text = body(source);
7126 assert!(!text.contains("i80"), "{text}");
7127 assert!(text.contains("i16"), "{text}");
7128 }
7129 }
7130
7131 #[test]
7139 fn the_complex_builtins_are_the_halves_of_the_value_and_not_a_call() {
7140 let text = body("double f(_Complex double z) { return __builtin_creal(z); }\n");
7141 assert!(!text.contains("call"), "{text}");
7142 let text = body("double f(_Complex double z) { return __builtin_cimag(z); }\n");
7143 assert!(!text.contains("call"), "{text}");
7144
7145 let text = body("_Complex double f(_Complex double z) { return __builtin_conj(z); }\n");
7148 assert_eq!(text.matches("fneg").count(), 1, "{text}");
7149 assert!(!text.contains("call"), "{text}");
7150 let negated = body("_Complex double f(_Complex double z) { return -z; }\n");
7151 assert_eq!(negated.matches("fneg").count(), 2, "{negated}");
7152
7153 let written = body("_Complex double f(_Complex double z) { return ~z; }\n");
7156 assert_eq!(written, text, "the name and the operator are the same thing");
7157
7158 let text = body(concat!(
7160 "double creal(_Complex double z);\n",
7161 "double f(_Complex double z) { return creal(z); }\n",
7162 ));
7163 assert!(!text.contains("call"), "{text}");
7164 let text = body(concat!(
7165 "_Complex float conjf(_Complex float z);\n",
7166 "_Complex float f(_Complex float z) { return conjf(z); }\n",
7167 ));
7168 assert_eq!(text.matches("fneg").count(), 1, "{text}");
7169 assert!(!text.contains("call"), "{text}");
7170
7171 let taken = concat!(
7174 "static double creal(_Complex double z) { return 7; }\n",
7175 "double f(_Complex double z) { return creal(z); }\n",
7176 );
7177 assert!(ir(taken).contains("call @creal"), "a static definition is the program's own");
7178 let retyped = concat!("int cimag(int z);\n", "int f(int z) { return cimag(z); }\n");
7179 assert!(ir(retyped).contains("call @cimag"), "another type is another function");
7180 let plain = concat!(
7181 "double cimag(_Complex double z);\n",
7182 "double f(_Complex double z) { return cimag(z); }\n",
7183 );
7184 let mut opts = options();
7185 opts.emit = EmitKind::Ir;
7186 opts.builtins = false;
7187 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin");
7188 opts.builtins = true;
7189 opts.no_builtin = vec!["cimag".to_owned()];
7190 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin-cimag");
7191
7192 let text = ir(concat!(
7194 "double a = __builtin_creal(1.5 + 2.5i);\n",
7195 "double b = __builtin_cimag(1.5 + 2.5i);\n",
7196 "_Complex double c = __builtin_conj(1.5 + 2.5i);\n",
7197 ));
7198 assert!(text.contains("global @a : f64 = 0x3ff8000000000000,"), "{text}");
7199 assert!(text.contains("global @b : f64 = 0x4004000000000000,"), "{text}");
7200 assert!(
7201 text.contains("{ f64 0x3ff8000000000000, f64 0xc004000000000000 }"),
7202 "the conjugate of a constant is the constant with the second half negated: {text}"
7203 );
7204 assert!(!text.contains("call"), "{text}");
7205 }
7206
7207 #[test]
7215 fn a_math_library_builtin_of_a_constant_is_the_answer_and_not_a_call() {
7216 let text = ir(concat!(
7217 "double a = __builtin_ceil(1.5);\n",
7218 "double b = __builtin_floor(1.5);\n",
7219 "double c = __builtin_trunc(-1.5);\n",
7220 "double d = __builtin_round(2.5);\n",
7223 "double e = __builtin_ceil(-0.5);\n",
7225 "double f = __builtin_fmax(1.0, 2.0);\n",
7226 "double g = __builtin_fmin(1.0, 2.0);\n",
7227 "float h = __builtin_ceilf(1.25f);\n",
7228 "double ceil(double x);\n",
7231 "double i = ceil(2.25);\n",
7232 ));
7233 assert!(text.contains("global @a : f64 = 0x4000000000000000,"), "{text}");
7234 assert!(text.contains("global @b : f64 = 0x3ff0000000000000,"), "{text}");
7235 assert!(text.contains("global @c : f64 = 0xbff0000000000000,"), "{text}");
7236 assert!(text.contains("global @d : f64 = 0x4008000000000000,"), "{text}");
7237 assert!(text.contains("global @e : f64 = 0x8000000000000000,"), "{text}");
7238 assert!(text.contains("global @f : f64 = 0x4000000000000000,"), "{text}");
7239 assert!(text.contains("global @g : f64 = 0x3ff0000000000000,"), "{text}");
7240 assert!(text.contains("global @h : f32 = 0x40000000,"), "{text}");
7241 assert!(text.contains("global @i : f64 = 0x4008000000000000,"), "{text}");
7242 assert!(!text.contains("call"), "{text}");
7243 }
7244
7245 #[test]
7253 fn a_math_library_builtin_of_anything_else_is_a_call_to_the_library() {
7254 let text = ir(concat!(
7255 "double f(double x) { return __builtin_ceil(x); }\n",
7256 "float g(float x) { return __builtin_floorf(x); }\n",
7257 "double h(double x, double y) { return __builtin_fmax(x, y); }\n",
7258 ));
7259 assert!(text.contains("call @ceil("), "{text}");
7260 assert!(text.contains("call @floorf("), "{text}");
7261 assert!(text.contains("call @fmax("), "{text}");
7262
7263 let text = ir(concat!(
7267 "double f(void) { return __builtin_rint(2.5); }\n",
7268 "double g(void) { return __builtin_nearbyint(2.5); }\n",
7269 ));
7270 assert!(text.contains("call @rint("), "{text}");
7271 assert!(text.contains("call @nearbyint("), "{text}");
7272
7273 let text = ir("double f(void) { return __builtin_fmin(__builtin_nan(\"\"), 1.0); }\n");
7276 assert!(text.contains("call @fmin("), "{text}");
7277
7278 let plain = concat!("double ceil(double x);\n", "double f(void) { return ceil(2.25); }\n");
7281 let mut opts = options();
7282 opts.emit = EmitKind::Ir;
7283 opts.no_builtin = vec!["ceil".to_owned()];
7284 assert!(run(&opts, plain).text().contains("call @ceil("), "-fno-builtin-ceil");
7285 }
7286
7287 #[test]
7294 fn a_constexpr_object_is_a_constant_wherever_one_is_required() {
7295 let text = ir(concat!(
7296 "constexpr int side = 4;\n",
7297 "constexpr int wider = side + 1;\n",
7298 "constexpr double half = 1.5;\n",
7299 "struct point { int x; int y; };\n",
7300 "constexpr struct point origin = { 5, 6 };\n",
7301 "int square[side * side];\n",
7302 "int rectangle[wider];\n",
7303 "int rounded[(int)half * 2];\n",
7304 "int across[origin.y];\n",
7305 "enum named { four = side };\n",
7306 "int e = four;\n",
7307 ));
7308 assert!(text.contains("global @square : bytes 64 ="), "{text}");
7309 assert!(text.contains("global @rectangle : bytes 20 ="), "{text}");
7310 assert!(text.contains("global @rounded : bytes 8 ="), "{text}");
7311 assert!(text.contains("global @across : bytes 24 ="), "{text}");
7312 assert!(text.contains("global @e : i32 = 4,"), "{text}");
7313
7314 let mut opts = options();
7317 opts.emit = EmitKind::Ir;
7318 let konst = "const int n = 1;\nint a[n];\n";
7319 let message = "/main.c:2:5: error: variably modified 'a' at file scope [E0538]";
7320 assert_eq!(run(&opts, konst).messages, [message]);
7321
7322 let subscript = "constexpr int t[3] = { 1, 2, 3 };\nint a[t[1]];\n";
7324 assert_eq!(run(&opts, subscript).messages, [message]);
7325
7326 let address = "constexpr int c = 3;\nint *p = &c;\n";
7328 let warning = "/main.c:2:6: warning: initialization discards 'const' qualifier from \
7329 pointer target type [E0514]";
7330 assert_eq!(run(&opts, address).messages, [warning]);
7331 }
7332
7333 #[test]
7342 fn a_member_whose_size_was_refused_is_not_a_flexible_array_member() {
7343 let mut opts = options();
7344 opts.emit = EmitKind::Ir;
7345
7346 let alone = "int k;\nextern struct D { int a[k]; } ed;\n";
7347 let message = "/main.c:2:23: error: variably modified 'a' at file scope [E0538]";
7348 assert_eq!(run(&opts, alone).messages, [message]);
7349
7350 let first = "int k;\nextern struct E { int a[k]; int b; } ee;\n";
7352 assert_eq!(run(&opts, first).messages, [message]);
7353
7354 let negative = "struct F { int a[-1]; };\n";
7357 let refused = "/main.c:1:18: error: size of array 'a' is negative [E0536]";
7358 assert_eq!(run(&opts, negative).messages, [refused]);
7359
7360 let flexible = "struct G { int a[]; };\n";
7363 let named = "/main.c:1:16: error: flexible array member in a struct with no named \
7364 members [E0554]";
7365 assert_eq!(run(&opts, flexible).messages, [named]);
7366 }
7367
7368 #[test]
7383 fn a_pointer_to_an_array_gains_a_qualifier_the_same_way_a_pointer_to_anything_else_does() {
7384 let mut opts = options();
7385 opts.emit = EmitKind::Ir;
7386 let prefix = "typedef unsigned int B[4];\nstruct H { B category[2]; };\n";
7387
7388 let adding = format!("{prefix}const B *f(struct H *h) {{ return &h->category[0]; }}\n");
7390 assert_eq!(run(&opts, &adding).messages, [] as [String; 0]);
7391
7392 let plain = concat!(
7395 "const unsigned int (*f(unsigned int (*p)[4]))[4] { return p; }\n",
7396 "const unsigned int (*g(unsigned int (*p)[2][3]))[2][3] { return p; }\n",
7397 );
7398 assert_eq!(run(&opts, plain).messages, [] as [String; 0]);
7399
7400 let dropping = format!("{prefix}B *f(const B *p) {{ return p; }}\n");
7403 let warning = "/main.c:3:27: warning: return discards 'const' qualifier from pointer target type \
7404 [E0514]";
7405 assert_eq!(run(&opts, &dropping).messages, [warning]);
7406
7407 let wrong = "const unsigned int (*f(unsigned short (*p)[4]))[4] { return p; }\n";
7410 let error = "/main.c:1:61: error: returning 'unsigned short (*)[4]' from a function with \
7411 incompatible return type 'const unsigned int (*)[4]' [E0512]";
7412 assert_eq!(run(&opts, wrong).messages, [error]);
7413 }
7414
7415 #[test]
7424 fn an_old_style_definition_takes_its_types_from_the_declarations_under_its_list() {
7425 let mut opts = options();
7428 opts.std = Std::C17;
7429 let source = concat!(
7430 "int add(a, b)\n",
7431 "int a;\n",
7432 "int b;\n",
7433 "{ return a + b; }\n",
7434 "int promoted(c)\n",
7435 "char c;\n",
7436 "{ return c; }\n",
7437 "int narrow(char);\n",
7438 "int narrow(c)\n",
7439 "char c;\n",
7440 "{ return c; }\n",
7441 "int first(a)\n",
7442 "int a[4];\n",
7443 "{ return a[0]; }\n",
7444 );
7445 let result = run(&opts, source);
7446 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
7447 let text = result.text();
7448 assert!(text.contains("add : int(int, int) function external defined"), "{text}");
7449 assert!(text.contains("promoted : int(int) function external defined"), "{text}");
7450 assert!(text.contains("c : char object automatic defined"), "{text}");
7452 assert!(text.contains("narrow : int(char) function external defined"), "{text}");
7453 assert!(text.contains("first : int(int *) function external defined"), "{text}");
7455 }
7456
7457 #[test]
7464 fn the_two_halves_of_an_old_style_parameter_list_have_to_agree() {
7465 let mut opts = options();
7466 opts.std = Std::C17;
7467 for (source, message) in [
7468 ("int f(a, a)\nint a;\n{ return a; }\n", "1:10: error: multiple parameters named 'a'"),
7469 (
7470 "int f(a)\nint a;\nint b;\n{ return a; }\n",
7471 "3:5: error: declaration for parameter 'b' but no such parameter",
7472 ),
7473 ("int f(a)\nint a;\nint a;\n{ return a; }\n", "3:5: error: redefinition of parameter"),
7474 ("int f(a)\nint a = 1;\n{ return a; }\n", "2:5: error: parameter 'a' is initialized"),
7475 (
7476 "int f(a)\nstatic int a;\n{ return a; }\n",
7477 "2:12: error: storage class specified for parameter 'a'",
7478 ),
7479 (
7480 "int f(char);\nint f(a)\nshort a;\n{ return a; }\n",
7481 "2:7: error: argument 'a' doesn't match prototype",
7482 ),
7483 ] {
7484 let result = run(&opts, source);
7485 assert!(result.failed(), "expected this to fail:\n{source}");
7486 assert!(result.messages[0].contains(message), "{:?}", result.messages);
7487 }
7488
7489 let implicit = "int f(a, b)\nint a;\n{ return a + b; }\n";
7492 let mut older = options();
7493 older.std = Std::C89;
7494 assert!(!run(&older, implicit).failed(), "{:?}", run(&older, implicit).messages);
7495 let result = run(&opts, implicit);
7496 assert!(
7497 result.messages[0].contains("1:10: error: type of 'b' defaults to 'int'"),
7498 "{:?}",
7499 result.messages
7500 );
7501
7502 let mut newer = options();
7506 newer.std = Std::C23;
7507 let plain = "int f(a)\nint a;\n{ return a; }\n";
7508 let result = run(&newer, plain);
7509 assert!(!result.failed(), "{:?}", result.messages);
7510 assert_eq!(
7511 result.messages,
7512 ["/main.c:1:5: warning: old-style function definition [E0412]"]
7513 );
7514 assert!(run(&opts, plain).messages.is_empty(), "and nothing to say in the dialects before");
7515 }
7516
7517 #[test]
7524 fn the_obsolete_designators_are_taken_and_are_pedantic_warnings() {
7525 let array = "int a[8] = { [3] 7 };\n";
7526 let member = "struct s { int x; } v = { x: 7 };\n";
7527 for source in [array, member] {
7528 let result = run(&options(), source);
7529 assert!(!result.failed(), "{:?}", result.messages);
7530 assert!(result.messages.is_empty(), "nothing to say: {:?}", result.messages);
7531 }
7532
7533 let mut asked = options();
7534 asked.pedantic = true;
7535 assert_eq!(
7536 run(&asked, array).messages,
7537 ["/main.c:1:18: warning: obsolete designator, write `[i] =` instead [E0415]"]
7538 );
7539 assert_eq!(
7540 run(&asked, member).messages,
7541 ["/main.c:1:27: warning: obsolete designator, write `.field =` instead [E0413]"]
7542 );
7543 }
7544
7545 #[test]
7552 fn a_type_is_refused_when_it_passes_the_largest_object_and_not_before() {
7553 let text = ir(concat!(
7554 "struct huge_struct { short buf[(1L << 62) - 256]; int a, b, c, d; };\n",
7555 "struct brim { char buf[9223372036854775807L]; };\n",
7556 "struct bitty { char buf[9223372036854775800L]; int x : 1; };\n",
7557 "unsigned long h = sizeof(struct huge_struct);\n",
7558 "unsigned long b = sizeof(struct brim);\n",
7559 "unsigned long y = sizeof(struct bitty);\n",
7560 ));
7561 assert!(text.contains("global @h : i64 = 9223372036854775312,"), "{text}");
7562 assert!(text.contains("global @b : i64 = 9223372036854775807,"), "{text}");
7563 assert!(text.contains("global @y : i64 = 9223372036854775804,"), "{text}");
7564
7565 let mut opts = options();
7566 opts.emit = EmitKind::Ir;
7567 let over = "struct over { char buf[9223372036854775800L]; char x[8]; };\n";
7568 let message = "/main.c:1:1: error: type 'struct over' is too large [E0560]";
7569 assert_eq!(run(&opts, over).messages, [message]);
7570 let array = "struct wide { short buf[1L << 62]; };\n";
7571 let message = "/main.c:1:25: error: size of array 'buf' exceeds \
7572 maximum object size '9223372036854775807' [E0537]";
7573 assert_eq!(run(&opts, array).messages[0], message);
7574 }
7575
7576 fn compile_bytes(source: &[u8]) -> Compiled {
7581 let mut opts = options();
7582 opts.emit = EmitKind::Ir;
7583 let mut fs = MemoryFileSystem::new();
7584 fs.insert("/main.c", source.to_vec());
7585 compile(&opts, "/main.c", &fs)
7586 }
7587
7588 #[test]
7595 fn a_byte_that_is_not_a_character_is_kept_in_a_literal_and_refused_outside_one() {
7596 let mut source = b"char s[] = \"a".to_vec();
7597 source.push(0xff);
7598 source.extend_from_slice(b"b\";\nchar c = '");
7599 source.push(0xff);
7600 source.extend_from_slice(b"';\n");
7601 let result = compile_bytes(&source);
7602 assert_eq!(result.messages, Vec::<String>::new(), "a raw byte in a literal is that byte");
7603 assert!(result.text().contains(r#"bytes "a\ffb\00""#), "{}", result.text());
7604 assert!(result.text().contains("global @c : i8 = -1,"), "{}", result.text());
7606
7607 let mut stray = b"int a".to_vec();
7608 stray.push(0xff);
7609 stray.extend_from_slice(b" = 1;\n");
7610 let result = compile_bytes(&stray);
7611 assert!(
7612 result.messages.iter().any(|m| m.contains("source is not valid UTF-8 here")),
7613 "{:?}",
7614 result.messages
7615 );
7616 }
7617
7618 #[test]
7619 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
7620 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
7621 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
7622 let expected = "\
7623func @add(i32, i32) -> i32, linkage(external) {
7624block0(%0: i32, %1: i32):
7625 %2 = add.nsw %0, %1
7626 return %2
7627}
7628";
7629 assert!(text.contains(expected), "{text}");
7630 }
7631
7632 #[test]
7633 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
7634 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
7635 assert!(!text.contains("alloca"), "{text}");
7636 assert!(!text.contains("load"), "{text}");
7637 assert!(!text.contains("store"), "{text}");
7638 }
7639
7640 #[test]
7641 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
7642 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
7643 let expected = "\
7644block0:
7645 %0 = alloca, size 4, align 4
7646 %1 = iconst.i32 1
7647 store %1 -> %0, align 4, tbaa !1
7648 %2 = call @g(%0) : (ptr) -> i32
7649 return %2
7650";
7651 assert_eq!(text, expected);
7652 }
7653
7654 #[test]
7655 fn a_loop_carries_what_it_changes_as_block_parameters() {
7656 let text = body(
7659 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
7660 return total;\n}\n",
7661 );
7662 assert!(!text.contains("alloca"), "{text}");
7663 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
7664 assert!(text.contains("jump block1("), "{text}");
7665 }
7666
7667 #[test]
7668 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
7669 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
7670 assert!(text.contains("icmp slt %0, %1"), "{text}");
7671 assert!(!text.contains("zext"), "{text}");
7672 }
7673
7674 #[test]
7675 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
7676 let text = body("int f(int a, int b) { return a && b; }\n");
7677 let expected = "\
7678block0(%0: i32, %1: i32):
7679 %2 = iconst.i32 0
7680 %3 = icmp ne %0, %2
7681 %4 = iconst.i1 0
7682 br_if %3, block1, block2(%4)
7683
7684block1:
7685 %5 = iconst.i32 0
7686 %6 = icmp ne %1, %5
7687 jump block2(%6)
7688
7689block2(%7: i1):
7690 %8 = zext.i32 %7
7691 return %8
7692";
7693 assert_eq!(text, expected);
7694 }
7695
7696 #[test]
7697 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
7698 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
7699 assert!(!text.contains("block3"), "{text}");
7702 assert!(!text.contains("iconst.i32 3"), "{text}");
7703 }
7704
7705 #[test]
7706 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
7707 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
7708 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
7709 assert!(body("int f(void) { }\n").contains("unreachable"));
7710 }
7711
7712 #[test]
7713 fn a_structure_is_copied_rather_than_held_in_a_value() {
7714 let text = body(
7715 "struct point { int x, y; };\n\
7716 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
7717 );
7718 assert!(text.contains("memcpy"), "{text}");
7719 }
7720
7721 #[test]
7722 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
7723 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
7724 assert!(text.contains("memset"), "{text}");
7725 }
7726
7727 #[test]
7728 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
7729 let text = body(
7730 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
7731 default: r = 4; } return r; }\n",
7732 );
7733 let expected = "\
7734block0(%0: i32):
7735 %1 = iconst.i32 0
7736 switch %0, block1, [1 => block2, 2 => block3(%1)]
7737
7738block1:
7739 %2 = iconst.i32 4
7740 jump block4(%2)
7741
7742block2:
7743 %3 = iconst.i32 1
7744 jump block3(%3)
7745
7746block3(%4: i32):
7747 %5 = iconst.i32 2
7748 %6 = add.nsw %4, %5
7749 jump block4(%6)
7750
7751block4(%7: i32):
7752 return %7
7753";
7754 assert_eq!(text, expected);
7755 }
7756
7757 #[test]
7758 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
7759 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
7762 assert!(text.contains("%2 = sub %0, %1"), "{text}");
7763 assert!(text.contains("icmp ule"), "{text}");
7764 assert!(!text.contains("switch"), "{text}");
7765 }
7766
7767 #[test]
7768 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
7769 let text = body(
7770 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
7771 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
7772 );
7773 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
7776 assert!(text.contains("block5:\n jump block7("), "{text}");
7777 assert!(text.contains("block6:\n jump block8("), "{text}");
7778 }
7779
7780 #[test]
7781 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
7782 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
7783 }
7784
7785 #[test]
7786 fn a_label_a_loop_is_only_entered_through_builds_the_loop_around_it() {
7787 let text = body(
7792 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
7793 return n; }\n",
7794 );
7795 assert!(text.contains("switch %0, block1(%1), [1 => block2, 2 => block3(%1)]"), "{text}");
7798 assert!(text.contains("block3(%3: i32):\n %4 = iconst.i32 1"), "{text}");
7799 assert!(text.contains("block4:\n jump block3("), "{text}");
7800 }
7801
7802 #[test]
7803 fn a_goto_into_a_loop_body_enters_it_without_the_test() {
7804 let text = body("int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n");
7807 assert!(text.starts_with("block0(%0: i32, %1: i32):\n jump block1(%1)"), "{text}");
7808 assert!(text.contains("block1(%2: i32):\n %3 = iconst.i32 1"), "{text}");
7809 assert!(text.contains("br_if %6, block2, block3"), "{text}");
7810 }
7811
7812 #[test]
7813 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
7814 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
7815 assert!(!text.contains("alloca"), "{text}");
7819 assert!(text.contains("block2(%4: i32):\n return %4"), "{text}");
7820 assert_eq!(text.matches("jump block2(").count(), 2, "{text}");
7821 }
7822
7823 #[test]
7824 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
7825 let text =
7826 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
7827 assert!(!text.contains("alloca"), "{text}");
7828 assert!(text.contains("block1(%2: i32):"), "{text}");
7829 assert!(text.contains("jump block1(%5)"), "{text}");
7830 }
7831
7832 #[test]
7833 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
7834 assert_eq!(
7837 body("int f(int x) { return x; spare: return 0; }\n"),
7838 "block0(%0: i32):\n return %0\n"
7839 );
7840 }
7841
7842 #[test]
7843 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
7844 let text = body(
7845 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
7846 );
7847 assert_eq!(
7850 text,
7851 "\
7852block0(%0: ptr):
7853 %1 = load.i8 %0, align 1
7854 %2 = iconst.i8 3
7855 %3 = ashr %1, %2
7856 %4 = sext.i32 %3
7857 return %4
7858"
7859 );
7860 }
7861
7862 #[test]
7863 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
7864 let text =
7868 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
7869 assert_eq!(
7870 text,
7871 "\
7872block0(%0: ptr, %1: i32):
7873 %2 = iconst.i32 16777215
7874 %3 = and %1, %2
7875 %4 = trunc.i16 %3
7876 store %4 -> %0, align 2
7877 %5 = iconst.i32 16
7878 %6 = lshr %3, %5
7879 %7 = trunc.i8 %6
7880 %8 = iconst.i64 2
7881 %9 = ptr_add %0, %8
7882 store %7 -> %9, align 1
7883 return
7884"
7885 );
7886 }
7887
7888 #[test]
7889 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
7890 let text =
7891 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
7892 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
7895 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
7896 }
7897
7898 #[test]
7899 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
7900 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
7903 assert_eq!(text.matches("ashr").count(), 0, "{text}");
7904 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
7905 }
7906
7907 #[test]
7908 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
7909 let text = body(
7913 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
7914 );
7915 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
7916 }
7917
7918 #[test]
7919 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
7920 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
7923 assert!(
7924 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
7925 "{text}"
7926 );
7927 }
7928
7929 #[test]
7930 fn an_initialized_flexible_array_member_makes_the_object_larger_than_its_type() {
7931 let text = ir(concat!(
7936 "struct a { int i; int j[]; } x = { 1, { 2, 0, 2, 3 } };\n",
7937 "struct b { char c; char p[]; } y = { 'o', \"wx\" };\n",
7938 "struct c { char c; char p[]; } z = { '9', { 'e', 'b' } };\n",
7939 "char s[2] = \"hi\";\n",
7940 ));
7941 assert!(
7942 text.contains("global @x : bytes 20 = { i32 1, i32 2, i32 0, i32 2, i32 3 }"),
7943 "{text}"
7944 );
7945 assert!(text.contains("global @y : bytes 4 = { i8 111, bytes \"wx\\00\" }"), "{text}");
7946 assert!(text.contains("global @z : bytes 3 = { i8 57, i8 101, i8 98 }"), "{text}");
7947 assert!(text.contains("global @s : bytes 2 = { bytes \"hi\" }"), "{text}");
7950 }
7951
7952 #[test]
7953 fn a_definition_takes_a_parameter_it_left_unnamed() {
7954 let text = ir("int f(int a, int) { return a; }\n");
7958 assert!(text.contains("func @f(i32, i32) -> i32"), "{text}");
7959 assert!(text.contains("block0(%0: i32, %1: i32):"), "{text}");
7960
7961 let text = ir("int g(int, int n) { return n; }\n");
7964 assert!(text.contains("block0(%0: i32, %1: i32):\n return %1\n"), "{text}");
7965 }
7966
7967 #[test]
7968 fn an_assignment_of_a_structure_is_the_object_it_wrote() {
7969 let text = body(concat!(
7974 "struct s { int f; int g; };\n",
7975 "void h(struct s *a, struct s *c, struct s *d, struct s *e)\n",
7976 "{ *d = *e = a[0] = *c; }\n",
7977 ));
7978 assert_eq!(text.matches("memcpy").count(), 3, "{text}");
7979 assert!(text.contains("memcpy %8, %1, size 8, align 4\n"), "{text}");
7980 assert!(text.contains("memcpy %3, %8, size 8, align 4\n"), "{text}");
7981 assert!(text.contains("memcpy %2, %3, size 8, align 4\n"), "{text}");
7982 }
7983
7984 #[test]
7985 fn a_string_literal_stops_at_the_end_of_the_array_it_is_filling() {
7986 let mut opts = options();
7991 opts.emit = EmitKind::Ir;
7992 let result = run(
7993 &opts,
7994 concat!(
7995 "const char a[2][3] = { \"1234\", \"xyz\" };\n",
7996 "static const char b[3][5] = { \"12345\", \"678\", \"9\" };\n",
7997 "union u { struct { char x[4]; char y[4]; }; struct { char z[8]; }; };\n",
7998 "const union u c = { { \"1234\", \"567\" } };\n",
7999 ),
8000 );
8001 let text = result.text();
8002 assert_eq!(
8003 result.messages,
8004 ["/main.c:1:24: warning: initializer-string for array of 'const char' is too long \
8005 (5 chars into 3 available) [E0637]"]
8006 );
8007 assert!(text.contains("global @a : bytes 6 = { bytes \"123\", bytes \"xyz\" }"), "{text}");
8008 assert!(
8009 text.contains(
8010 "global @b : bytes 15 = { bytes \"12345\", bytes \"678\\00\", zero 1, \
8011 bytes \"9\\00\", zero 3 }"
8012 ),
8013 "{text}"
8014 );
8015 assert!(
8018 text.contains("global @c : bytes 8 = { bytes \"1234\", bytes \"567\\00\" }"),
8019 "{text}"
8020 );
8021 }
8022
8023 #[test]
8024 fn a_cast_of_a_record_to_its_own_type_is_the_object_that_was_cast() {
8025 let text = body(concat!(
8029 "struct s { int a, b; };\nstruct v { struct s s; int t; };\n",
8030 "void g(struct v *);\n",
8031 "void f(struct s *p) { struct v w = { (struct s)*p, 5 }; g(&w); }\n",
8032 ));
8033 assert_eq!(text.matches("memcpy").count(), 1, "{text}");
8034 }
8035
8036 #[test]
8037 fn a_compound_literal_read_in_a_static_initializer_lays_its_bytes_into_the_image() {
8038 let text = ir(concat!(
8043 "struct s { int x; };\n",
8044 "struct t { struct s s; int o; } a = { (struct s){ 2 }, 3 };\n",
8045 "int n = (int){ 7 };\n",
8046 "struct u { struct s p; struct s q; } b = { (struct s){ 1 }, (struct s){ } };\n",
8047 ));
8048 assert!(text.contains("global @a : bytes 8 = { i32 2, i32 3 }"), "{text}");
8049 assert!(text.contains("global @n : i32 = 7,"), "{text}");
8050 assert!(text.contains("global @b : bytes 8 = { i32 1, zero 4 }"), "{text}");
8053 }
8054
8055 #[test]
8056 fn the_address_of_a_compound_literal_asks_for_the_object_it_points_at() {
8057 let text = ir("struct s { int x; };\nstruct s *q = &(struct s){ 9 };\n");
8061 assert!(text.contains("global @.Lanon.0 : i32 = 9, align 4, linkage(internal)"), "{text}");
8062 assert!(text.contains("global @q : bytes 8 = { addr.8 @.Lanon.0 }"), "{text}");
8063 }
8064
8065 #[test]
8066 fn an_object_of_no_size_at_all_has_an_image_with_nothing_in_it() {
8067 let text = ir("unsigned char foo[1][0];\n");
8071 assert!(text.contains("global @foo : bytes 0 = {}, align 1"), "{text}");
8072 }
8073
8074 #[test]
8075 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
8076 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
8079 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
8080 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
8081 }
8082
8083 #[test]
8084 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
8085 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
8089 assert!(
8090 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
8091 "{text}"
8092 );
8093 }
8094
8095 #[test]
8096 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
8097 let text = body(
8102 "\
8103struct s { int a, b; };
8104struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
8105",
8106 );
8107 assert!(text.contains("block3(%7: ptr)"), "{text}");
8109 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
8110 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
8111 }
8112
8113 #[test]
8121 fn the_left_side_of_a_conditional_with_no_middle_is_evaluated_once() {
8122 let text = body("int f(int i) { return ++i ?: 10; }\n");
8123 assert!(text.contains("jump block3(%2)"), "the arm is the value that was tested: {text}");
8124 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
8125
8126 let text = body("long f(int i) { return ++i ?: 10L; }\n");
8129 assert!(text.contains("%5 = sext.i64 %2"), "the arm widens what was tested: {text}");
8130 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
8131
8132 let text = body("int g(void);\nint f(void) { return g() ?: 10; }\n");
8134 assert_eq!(text.matches("call @g").count(), 1, "called once: {text}");
8135
8136 let text = body("int f(int i) { return ++i ? ++i : 10; }\n");
8139 assert_eq!(text.matches("add.nsw").count(), 2, "incremented twice: {text}");
8140 }
8141
8142 #[test]
8143 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
8144 let text = ir("\
8148struct pair { int a, b; };
8149struct pair make(int a, int b);
8150struct pair twice(struct pair p) { return make(p.a, p.b); }
8151");
8152 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
8153 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
8154 }
8155
8156 #[test]
8157 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
8158 let text = ir("\
8162struct big { double v[8]; };
8163struct big grow(struct big b);
8164struct big twice(struct big b) { return grow(grow(b)); }
8165");
8166 assert!(
8167 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
8168 "{text}"
8169 );
8170 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
8171 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
8174 }
8175
8176 #[test]
8177 fn a_structure_passed_to_a_variadic_function_says_so_at_the_call() {
8178 let text = ir("\
8183struct big { double v[8]; };
8184struct pair { int a, b; };
8185int p(const char *, ...);
8186int f(struct big b, struct pair q) { return p(\"\", 1, b, q); }
8187");
8188 assert!(
8189 text.contains("call @p(%4, %5, %2 byval(64, align 8), %6) : (ptr, ...) -> i32"),
8190 "{text}"
8191 );
8192 }
8193
8194 #[test]
8195 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
8196 let body = body(
8199 "\
8200struct pair { int a, b; };
8201struct pair make(int a, int b);
8202int second(void) { return make(1, 2).b; }
8203",
8204 );
8205 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
8206 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
8207 }
8208
8209 #[test]
8210 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
8211 let source = "\
8215struct hfa { float x, y, z; };
8216int take(struct hfa h);
8217int give(struct hfa h) { return take(h); }
8218";
8219 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
8220 let mut opts = options();
8221 opts.emit = EmitKind::Ir;
8222 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
8223 let result = run(&opts, source);
8224 assert_eq!(result.messages, Vec::<String>::new());
8225 assert!(result.text().contains("func @take(f32, f32, f32) -> i32"), "{}", result.text());
8226 }
8227
8228 #[test]
8229 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
8230 let source = "\
8233int use(int *);
8234void f(int n) {
8235 {
8236 int a[n];
8237 use(a);
8238 }
8239 use(0);
8240}
8241";
8242 let body = body(source);
8243 assert!(body.contains("mul.nsw"), "{body}");
8244 assert!(body.contains("stacksave"), "{body}");
8245 assert!(body.contains("alloca %"), "{body}");
8246 assert!(body.contains("stackrestore"), "{body}");
8247 }
8248
8249 #[test]
8250 fn a_goto_out_of_the_scope_of_one_gives_its_stack_back_on_the_way() {
8251 let source = "\
8256int use(int *);
8257int f(int n) {
8258 {
8259 int a[n];
8260 if (use(a)) goto out;
8261 use(0);
8262 }
8263out:
8264 return 0;
8265}
8266";
8267 let body = body(source);
8268 assert_eq!(body.matches("stackrestore").count(), 2, "{body}");
8270 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
8271 assert!(after.starts_with(" %4\n jump block"), "{body}");
8272 }
8273
8274 #[test]
8275 fn a_goto_to_a_label_the_array_is_still_alive_at_leaves_the_stack_alone() {
8276 let source = "\
8280int use(int *);
8281int f(int n) {
8282 int a[n];
8283again:
8284 if (use(a)) goto again;
8285 return 0;
8286}
8287";
8288 let body = body(source);
8289 assert!(body.contains("stacksave"), "{body}");
8290 assert!(!body.contains("stackrestore"), "{body}");
8291 }
8292
8293 #[test]
8294 fn a_goto_back_to_a_label_in_front_of_one_gives_it_back_every_time_round() {
8295 let source = "\
8300int use(int *);
8301int f(int n) {
8302again:
8303 {
8304 int a[n];
8305 if (use(a)) goto again;
8306 }
8307 return 0;
8308}
8309";
8310 let body = body(source);
8311 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
8312 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
8313 assert!(after.starts_with(" %4\n jump block1\n"), "{body}");
8314 }
8315
8316 #[test]
8317 fn the_head_of_a_for_loop_is_a_scope_that_closes_where_the_loop_is_left() {
8318 let source = "\
8324int f(void);
8325void t(void) {
8326 int count = 10;
8327 for (; count--;) {
8328 int b[f()];
8329 int i;
8330 for (i = 0; i < f(); i++) {
8331 b[i] = count;
8332 }
8333 }
8334}
8335";
8336 let body = body(source);
8337 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
8341 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
8342 let next = after.split("\n\n").next().expect("the block the restore is in");
8345 assert!(next.contains("jump block1("), "{body}");
8346 }
8347
8348 #[test]
8349 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
8350 let source = "\
8353unsigned long f(int n) {
8354 int a[n];
8355 n = 0;
8356 return sizeof a;
8357}
8358";
8359 let body = body(source);
8360 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
8362 }
8363
8364 #[test]
8365 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
8366 let source = "\
8369int use(int);
8370int f(int x) {
8371 return ({
8372 int t = use(x);
8373 t * t;
8374 });
8375}
8376";
8377 let expected = "\
8378block0(%0: i32):
8379 %1 = call @use(%0) : (i32) -> i32
8380 %2 = mul.nsw %1, %1
8381 return %2
8382";
8383 assert_eq!(body(source), expected);
8384 }
8385
8386 #[test]
8387 fn a_comma_whose_value_is_an_object_names_the_object_the_right_side_named() {
8388 let source = "\
8392struct pair { int a, b; };
8393void bail(void);
8394int f(struct pair p) {
8395 return (bail(), p).b;
8396}
8397";
8398 let expected = "\
8399block0(%0: i64):
8400 %1 = alloca, size 8, align 4
8401 store %0 -> %1, align 4
8402 call @bail() : ()
8403 %2 = iconst.i64 4
8404 %3 = ptr_add %1, %2
8405 %4 = load.i32 %3, align 4, tbaa !1
8406 return %4
8407";
8408 assert_eq!(body(source), expected);
8409 }
8410
8411 #[test]
8412 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
8413 let source = "int f(int x) { return ({ return x; 0; }); }\n";
8417 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
8418 }
8419
8420 #[test]
8421 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
8422 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
8426 let expected = "\
8427block0(%0: ptr):
8428 %1 = va_arg.f64 %0
8429 %2 = va_arg.f64 %0
8430 %3 = fadd %1, %2
8431 return %3
8432";
8433 assert_eq!(body(source), expected);
8434 }
8435
8436 #[test]
8437 fn one_that_reads_a_structure_answers_where_the_object_is() {
8438 let source = "\
8452struct s { int a; long b; };
8453long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.b; }
8454";
8455 let expected = "\
8456block0(%0: ptr):
8457 %1 = alloca, size 16, align 16
8458 %2 = va_object %0, size 16, align 8, in(int 8 at 0, int 8 at 8)
8459 memcpy %1, %2, size 16, align 8
8460 %3 = iconst.i64 8
8461 %4 = ptr_add %1, %3
8462 %5 = load.i64 %4, align 8, tbaa !1
8463 return %5
8464";
8465 assert_eq!(body(source), expected);
8466 }
8467
8468 #[test]
8472 fn the_classification_says_which_registers_the_object_arrived_in() {
8473 let source = "\
8474struct s { double a; double b; };
8475double f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a; }
8476";
8477 assert!(
8478 body(source)
8479 .contains("va_object %0, size 16, align 8, in(float f64 at 0, float f64 at 8)"),
8480 "{}",
8481 body(source)
8482 );
8483
8484 let big = "\
8485struct s { long a[4]; };
8486long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a[0]; }
8487";
8488 assert!(body(big).contains("va_object %0, size 32, align 8\n"), "{}", body(big));
8489 }
8490
8491 #[test]
8492 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
8493 let source = "\
8497int f(int c) {
8498 void *p = c ? &&one : &&two;
8499 goto *p;
8500one:
8501 return 1;
8502two:
8503 return 2;
8504}
8505";
8506 let expected = "\
8507block0(%0: i32):
8508 %1 = iconst.i32 0
8509 %2 = icmp ne %0, %1
8510 br_if %2, block1, block2
8511
8512block1:
8513 %3 = block_addr block3
8514 jump block4(%3)
8515
8516block2:
8517 %4 = block_addr block5
8518 jump block4(%4)
8519
8520block3:
8521 %5 = iconst.i32 1
8522 return %5
8523
8524block4(%6: ptr):
8525 indirect_br %6, block3, block5
8526
8527block5:
8528 %7 = iconst.i32 2
8529 return %7
8530";
8531 assert_eq!(body(source), expected);
8532 }
8533
8534 fn dispatch(labels: usize) -> String {
8537 let mask = labels - 1;
8538 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
8539 for index in 0..labels {
8540 source.push_str(&format!(" &&a{index},"));
8541 }
8542 source.push_str(" };\n\tint w = n, x = n + 1, y = n + 2, z = n + 3;\n");
8543 source.push_str(&format!("\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
8544 for index in 0..labels {
8545 let step = match index % 4 {
8546 0 => "w += x;",
8547 1 => "x += y;",
8548 2 => "y += z;",
8549 _ => "z += w;",
8550 };
8551 source.push_str(&format!("a{index}:\n\t{step}\n"));
8552 source.push_str("\tif (--n <= 0) return w + x + y + z;\n");
8553 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
8554 }
8555 source.push_str("}\n");
8556 source
8557 }
8558
8559 fn in_front_of_the_jump(text: &str) -> usize {
8561 let (before, _) = text.split_once("\tjmp\t*%").expect("a jump through a register");
8562 before.lines().rev().take_while(|line| line.starts_with("\tmov")).count()
8563 }
8564
8565 #[test]
8575 fn a_jump_through_a_register_writes_what_it_carries_and_not_the_whole_table() {
8576 let small = in_front_of_the_jump(&asm(&dispatch(4)));
8577 let large = in_front_of_the_jump(&asm(&dispatch(32)));
8578 assert_eq!(small, large, "eight times the labels and the same values in hand");
8579 assert!(large <= 8, "the values the loop keeps, and not a set of them per label: {large}");
8580 }
8581
8582 fn crowded(labels: usize) -> String {
8585 const VALUES: usize = 24;
8586 let mask = labels - 1;
8587 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
8588 for index in 0..labels {
8589 source.push_str(&format!(" &&a{index},"));
8590 }
8591 source.push_str(" };\n\t");
8592 for value in 0..VALUES {
8593 source.push_str(&format!("int v{value} = n + {value}; "));
8594 }
8595 let sum: Vec<String> = (0..VALUES).map(|value| format!("v{value}")).collect();
8596 source.push_str(&format!("\n\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
8597 for index in 0..labels {
8598 let (to, from) = (index % VALUES, (index + 1) % VALUES);
8599 source.push_str(&format!("a{index}:\n\tv{to} += v{from};\n"));
8600 source.push_str(&format!("\tif (--n <= 0) return {};\n", sum.join(" + ")));
8601 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
8602 }
8603 source.push_str("}\n");
8604 source
8605 }
8606
8607 fn the_frame(text: &str) -> u64 {
8609 text.lines()
8610 .find_map(|line| {
8611 let (size, _) = line.strip_prefix("\tsubq\t$")?.split_once(", %rsp")?;
8612 size.parse().ok()
8613 })
8614 .expect("a function that opens a frame")
8615 }
8616
8617 #[test]
8626 fn a_frame_holds_what_is_wanted_at_once_and_not_a_slot_for_every_label() {
8627 let small = the_frame(&asm(&crowded(16)));
8628 let large = the_frame(&asm(&crowded(64)));
8629 assert_eq!(small, large, "four times the labels and the same values: {small}, {large}");
8630 }
8631
8632 #[test]
8641 fn a_template_that_names_its_own_registers_gets_the_ones_it_named() {
8642 let source = "void save(void *nlr) {
8643 __asm volatile (
8644 \"movq %%rsp, 32(%%rdi) \\n\"
8645 \"movq %%rbx, 40(%%rdi) \\n\"
8646 \"movq %%r12, 48(%%rdi) \\n\"
8647 : : \"D\" (nlr) : \"memory\");
8648}
8649";
8650 let text = asm(source);
8651 assert!(text.contains("\tmovq\t%rsp, 32(%rdi)\n"), "{text}");
8652 assert!(text.contains("\tmovq\t%rbx, 40(%rdi)\n"), "{text}");
8653 assert!(text.contains("\tmovq\t%r12, 48(%rdi)\n"), "{text}");
8654 }
8655
8656 #[test]
8657 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
8658 let source = "void **next(void);
8661void f(void) { goto *next(); }
8662";
8663 let expected = "\
8664block0:
8665 %0 = call @next() : () -> ptr
8666 unreachable
8667";
8668 assert_eq!(body(source), expected);
8669 }
8670
8671 #[test]
8672 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
8673 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
8676 let expected = "\
8677block0:
8678 inline_asm.volatile \"mfence\", \"\", \"memory\"()
8679 return
8680";
8681 assert_eq!(body(source), expected);
8682 }
8683
8684 #[test]
8685 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
8686 let source = "\
8689int f(int x, int y) {
8690 int r;
8691 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
8692 return r + y;
8693}
8694";
8695 let expected = "\
8696block0(%0: i32, %1: i32):
8697 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
8698 %4 = add.nsw %2, %3
8699 return %4
8700";
8701 assert_eq!(body(source), expected);
8702 }
8703
8704 #[test]
8705 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
8706 let source = "\
8711struct pair { int a, b; };
8712int f(int x) {
8713 int slot = x;
8714 struct pair p = { x, x };
8715 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
8716 return slot + p.a;
8717}
8718";
8719 let text = body(source);
8720 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
8721 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
8722 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
8723 }
8724
8725 #[test]
8726 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
8727 let source = "\
8732int f(int x) {
8733 int r = 7;
8734 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
8735 return r;
8736away:
8737 return r;
8738}
8739";
8740 let expected = "\
8741block0(%0: i32):
8742 %1 = iconst.i32 7
8743 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
8744
8745block1:
8746 return %2
8747
8748block2:
8749 return %1
8750";
8751 assert_eq!(body(source), expected);
8752 }
8753
8754 #[test]
8755 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
8756 let mut opts = options();
8760 opts.emit = EmitKind::Ir;
8761 for (source, expected) in [
8762 (
8763 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
8764 "output operand constraint lacks '='",
8765 ),
8766 (
8767 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
8768 "lvalue required in 'asm' statement",
8769 ),
8770 (
8771 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
8772 "read-only variable 'g' used as 'asm' output",
8773 ),
8774 (
8775 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
8776 "input operand constraint contains '='",
8777 ),
8778 (
8779 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
8780 "memory input 0 is not directly addressable",
8781 ),
8782 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
8783 (
8784 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
8785 "duplicate asm operand name 'a'",
8786 ),
8787 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
8788 ] {
8789 let result = run(&opts, source);
8790 assert!(result.failed(), "expected this to be reported:\n{source}");
8791 assert!(
8792 result.messages.iter().any(|m| m.contains(expected)),
8793 "{expected}\n{:?}",
8794 result.messages
8795 );
8796 }
8797 }
8798
8799 #[test]
8804 fn an_asm_at_file_scope_that_is_directives_becomes_the_objects_it_defines() {
8805 let text = ir(concat!(
8806 "__asm__(\n",
8807 " \".section .rodata\\n\"\n",
8808 " \".globl first\\n\"\n",
8809 " \".balign 8\\n\"\n",
8810 " \"first:\\n\"\n",
8811 " \".long 1\\n\"\n",
8812 " \".long 2\\n\"\n",
8813 " \".globl last\\n\"\n",
8814 " \"last:\\n\"\n",
8815 " \".quad last - first\\n\");\n",
8816 "extern const int first[];\n",
8817 "extern const long last;\n",
8818 ));
8819 assert!(text.contains("global @first : bytes 8 = { i32 1, i32 2 }, align 8"), "{text}");
8820 assert!(text.contains("global @last : i64 = 8"), "{text}");
8821 }
8822
8823 #[test]
8827 fn a_name_an_asm_at_file_scope_defined_is_not_undone_by_a_declaration_of_it() {
8828 let text = ir(concat!(
8829 "__asm__(\".data\\n.globl counter\\ncounter:\\n.long 7\\n\");\n",
8830 "extern int counter;\n",
8831 "int read(void) { return counter; }\n",
8832 ));
8833 assert!(text.contains("global @counter : i32 = 7"), "{text}");
8834 }
8835
8836 #[test]
8841 fn bytes_under_no_label_at_file_scope_are_a_global_in_front_of_the_label() {
8842 let text = ir(concat!(
8843 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n662:\\n",
8844 ".pushsection .data.ignore\\n.byte 7\\n.popsection\\n.byte 662b - 661b\\n\");\n",
8845 "extern unsigned char stuff[];\n",
8846 "int read(void) { return stuff[0]; }\n",
8847 ));
8848 let under = text.find("global @.Lasm.0 : i8 = 41").expect(&text);
8849 let named = text.find("global @stuff : i8 = 42").expect(&text);
8850 assert!(under < named, "the bytes under no label come first: {text}");
8851 assert!(text.contains("global @.Lasm.1 : i8 = 7, align 1, linkage(internal), section"));
8852 let after = text.find("global @.Lasm.2 : i8 = 1").expect(&text);
8857 assert!(named < after, "{text}");
8858 }
8859
8860 #[test]
8865 fn a_distance_from_here_at_file_scope_is_a_hole_naming_the_global_it_measures_to() {
8866 let text = ir(concat!(
8867 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n",
8868 ".pushsection .data.ignore\\n.long 661b - .\\n.popsection\\n\");\n",
8869 "extern unsigned char stuff[];\n",
8870 "int read(void) { return stuff[0]; }\n",
8871 ));
8872 assert!(text.contains("global @.Lasm.1 : bytes 4 = { away.4 @stuff }"), "{text}");
8876 }
8877
8878 #[test]
8883 fn a_set_at_file_scope_is_a_second_name_for_what_it_names() {
8884 let text = ir(concat!(
8885 "void base(void) {}\n",
8886 "__asm__(\".weak one\\n.set one, base\");\n",
8887 "__asm__(\".globl two\\n.set two, base\");\n",
8888 "__asm__(\".set three, base\");\n",
8889 "void three(void) {}\n",
8890 ));
8891 assert!(text.contains("alias @one = @base, linkage(weak)"), "{text}");
8892 assert!(text.contains("alias @two = @base"), "{text}");
8893 assert!(!text.contains("alias @three"), "a definition of the name wins: {text}");
8894 assert!(text.contains("func @three"), "{text}");
8895 }
8896
8897 #[test]
8902 fn a_set_of_a_name_this_file_does_not_define_says_so() {
8903 let messages = errors("__asm__(\".set here, elsewhere\");\n");
8904 assert!(
8905 messages
8906 .iter()
8907 .any(|m| m.contains("'here' is aliased to undefined symbol 'elsewhere'")
8908 && m.contains("E0697")),
8909 "{messages:?}"
8910 );
8911 }
8912
8913 #[test]
8916 fn an_incbin_at_file_scope_is_the_bytes_of_the_file_it_names() {
8917 let mut opts = options();
8918 opts.emit = EmitKind::Ir;
8919 let mut fs = MemoryFileSystem::new();
8920 fs.insert(
8921 "/main.c",
8922 b"__asm__(\".data\\n.globl blob\\nblob:\\n.incbin \\\"seed\\\"\\n\");\n".to_vec(),
8923 );
8924 fs.insert("seed", b"hi".to_vec());
8925 let result = compile(&opts, "/main.c", &fs);
8926 assert_eq!(result.messages, Vec::<String>::new());
8927 let text = result.text();
8928 assert!(text.contains("global @blob : bytes 2 = { bytes \"hi\" }"), "{text}");
8929 }
8930
8931 #[test]
8934 fn an_incbin_naming_a_file_that_is_not_there_says_which_file() {
8935 let messages = errors("__asm__(\".data\\nb:\\n.incbin \\\"nowhere\\\"\\n\");\n");
8936 assert!(
8937 messages
8938 .iter()
8939 .any(|m| m.contains("cannot open 'nowhere' for reading") && m.contains("E0702")),
8940 "{messages:?}"
8941 );
8942 }
8943
8944 #[test]
8947 fn an_instruction_in_an_asm_at_file_scope_is_refused_rather_than_ignored() {
8948 for source in [
8949 "__asm__(\".text\\n.globl f\\nf:\\n ret\\n\");\n",
8950 "__asm__(\".data\\n.set alias, 4\\n\");\n",
8951 ] {
8952 let messages = errors(source);
8953 assert!(
8954 messages
8955 .iter()
8956 .any(|m| m.contains("not supported yet")
8957 && m.contains("in an `asm` at file scope")),
8958 "{source}\n{messages:?}"
8959 );
8960 }
8961 }
8962
8963 #[test]
8973 fn a_naked_function_is_its_own_prologue_and_its_own_ending() {
8974 let text = asm(concat!(
8975 "unsigned nlr_push_tail(void *nlr);\n",
8976 "__attribute__((naked)) unsigned nlr_push(void *nlr) {\n",
8977 " __asm volatile(\n",
8978 " \"movq (%rsp), %rax\\n\"\n",
8979 " \"movq %rax, 16(%rdi)\\n\"\n",
8980 " \"movq %rbx, 40(%rdi)\\n\"\n",
8981 " \"jmp nlr_push_tail\\n\");\n",
8982 "}\n",
8983 ));
8984 assert!(text.contains("\tmovq\t(%rsp), %rax\n"), "{text}");
8985 assert!(text.contains("\tjmp\tnlr_push_tail\n"), "{text}");
8986 assert!(text.contains("\tud2\n"), "{text}");
8987 assert!(!text.contains("\tpushq\t"), "nothing is saved in front of it: {text}");
8988 assert!(!text.contains("\tret\n"), "the jump is where it ends: {text}");
8989 }
8990
8991 #[test]
8994 fn what_a_function_without_a_prologue_cannot_be_given_is_refused() {
8995 let mut opts = options();
8996 opts.emit = EmitKind::Asm;
8997 for (source, why) in [
8998 (
8999 "__attribute__((naked)) void f(void) { volatile long a[8]; a[0] = 1; }\n",
9000 "bytes of frame",
9001 ),
9002 (
9003 "__attribute__((naked)) void f(int n) { char a[n]; __asm(\"nop\" ::\"r\"(a)); }\n",
9004 "has no prologue to point a frame pointer at it with",
9005 ),
9006 ("void elsewhere(void); void f(void) { __asm(\"jmp elsewhere\"); }\n", "jumps out of"),
9007 ] {
9008 let result = run(&opts, source);
9009 assert!(result.failed(), "expected this to be refused:\n{source}");
9010 assert!(
9011 result.messages.iter().any(|message| message.contains(why)),
9012 "{:?}",
9013 result.messages
9014 );
9015 }
9016 }
9017
9018 #[test]
9019 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
9020 let mut opts = options();
9021 opts.emit = EmitKind::Ir;
9022 for source in [
9023 "int f(int n) { void *p = &&out; if (n) goto *p; { int a[n]; out: return 1; } }\n",
9024 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
9025 ] {
9026 let result = run(&opts, source);
9027 assert!(result.failed(), "expected this to be reported:\n{source}");
9028 assert!(
9029 result.messages.iter().any(|m| m.contains("not supported yet")),
9030 "{:?}",
9031 result.messages
9032 );
9033 }
9034 }
9035
9036 fn round_trip(source: &str) -> (String, String) {
9038 let printed = ir(source);
9039 let mut opts = options();
9040 opts.emit = EmitKind::Ir;
9041 let mut fs = MemoryFileSystem::new();
9042 fs.insert("/main.ir", printed.clone().into_bytes());
9043 let result = compile_ir(&opts, "/main.ir", &fs);
9044 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
9045 (printed, result.text().to_owned())
9046 }
9047
9048 #[test]
9049 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
9050 let (printed, again) = round_trip(
9054 "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",
9055 );
9056 assert_eq!(printed, again);
9057 }
9058
9059 #[test]
9060 fn ir_that_is_not_ir_says_which_line_stopped_it() {
9061 let mut opts = options();
9062 opts.emit = EmitKind::Ir;
9063 let mut fs = MemoryFileSystem::new();
9064 let text = "\
9065; ModuleID = 'a.c'
9066; format 0
9067target triple = \"x86_64-unknown-linux-gnu\"
9068target datalayout = \"e-p:64:64-i64:64-S128\"
9069
9070func @f(), linkage(external) {
9071block0:
9072 frobnicate
9073}
9074";
9075 fs.insert("/main.ir", text.as_bytes().to_vec());
9076 let result = compile_ir(&opts, "/main.ir", &fs);
9077 assert!(result.failed());
9078 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
9079 }
9080
9081 #[test]
9082 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
9083 let mut opts = options();
9086 opts.emit = EmitKind::Ir;
9087 let mut fs = MemoryFileSystem::new();
9088 let text = "\
9089; ModuleID = 'a.c'
9090; format 0
9091target triple = \"x86_64-unknown-linux-gnu\"
9092target datalayout = \"e-p:64:64-i64:64-S128\"
9093
9094func @f(), linkage(external) {
9095block0:
9096 %0 = iconst.i32 1
9097 return %0
9098}
9099";
9100 fs.insert("/main.ir", text.as_bytes().to_vec());
9101 let result = compile_ir(&opts, "/main.ir", &fs);
9102 assert!(result.failed());
9103 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
9104 }
9105
9106 #[test]
9107 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
9108 let mut fs = MemoryFileSystem::new();
9110 fs.insert("/main.ir", Vec::new());
9111 let result = compile_ir(&options(), "/main.ir", &fs);
9112 assert!(result.failed());
9113 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
9114 }
9115
9116 #[test]
9117 fn the_printed_ir_reads_back_as_the_same_module() {
9118 let text = ir("\
9121struct point { int x, y; };
9122static const char greeting[] = \"hi\";
9123int table[4] = { 1, 2, 3 };
9124int puts(const char *);
9125double half(double x) { return x / 2.0; }
9126int f(int n) {
9127 int total = 0;
9128 for (int i = 0; i < n; i++) {
9129 if (i == 3) continue;
9130 total += table[i];
9131 }
9132 switch (n) {
9133 case 0: total = 1;
9134 case 1: total++; break;
9135 default: total = -total;
9136 }
9137 struct point p = { total, 1 };
9138 int *q = &p.y;
9139 puts(greeting);
9140 return p.x + *q;
9141}
9142int dispatch(int c) {
9143 void *p = c ? &&one : &&two;
9144 goto *p;
9145one:
9146 return 1;
9147two:
9148 return 2;
9149}
9150int assembly(int x, int *p) {
9151 int r;
9152 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
9153 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
9154 return r;
9155away:
9156 return 0;
9157}
9158");
9159 let mut names = Interner::new();
9160 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
9161 assert_eq!(rucc_ir::print(&module, &names), text);
9162 }
9163
9164 #[test]
9165 fn what_save_temps_keeps_is_the_text_that_was_compiled_and_the_assembly_that_was_assembled() {
9166 let mut opts = options();
9170 opts.emit = EmitKind::Object;
9171 opts.save_temps = rucc_session::SaveTemps::Object;
9172 let result = run(&opts, "#define N 2\nint a[N];\n");
9173 assert_eq!(result.messages, Vec::<String>::new());
9174 let text = result.temps.preprocessed.expect("the preprocessed text");
9175 assert!(text.contains("int a[2];"), "{text}");
9176 assert!(text.starts_with("# 1 \"/main.c\""), "{text}");
9177 let asm = result.temps.assembly.expect("the assembly");
9178 assert!(asm.contains("a:"), "{asm}");
9179 assert!(matches!(result.artifact, Artifact::Object { .. }), "{:?}", result.artifact);
9180 }
9181
9182 #[test]
9183 fn nothing_is_kept_unless_the_flag_asked_for_it() {
9184 let mut opts = options();
9187 opts.emit = EmitKind::Object;
9188 assert_eq!(run(&opts, "int a;\n").temps, Temps::default());
9189 }
9190
9191 #[test]
9192 fn a_compilation_that_stops_before_the_back_end_keeps_the_text_and_no_assembly() {
9193 let mut opts = options();
9196 opts.emit = EmitKind::Ir;
9197 opts.save_temps = rucc_session::SaveTemps::Cwd;
9198 let result = run(&opts, "int a;\n");
9199 assert!(result.temps.preprocessed.is_some());
9200 assert_eq!(result.temps.assembly, None);
9201 }
9202
9203 fn span(from: u64, len: u64, held: rucc_debug::Held) -> rucc_debug::Span {
9205 rucc_debug::Span { from, len, held }
9206 }
9207
9208 #[test]
9209 fn two_stretches_that_meet_and_agree_come_out_as_one() {
9210 let one = span(0, 4, rucc_debug::Held::Reg(3));
9211 let two = span(4, 4, rucc_debug::Held::Reg(3));
9212 assert_eq!(settle(vec![two, one]), vec![span(0, 8, rucc_debug::Held::Reg(3))]);
9213 }
9214
9215 #[test]
9216 fn a_stretch_another_starts_inside_and_disagrees_with_ends_where_the_other_starts() {
9217 let one = span(0, 8, rucc_debug::Held::Reg(3));
9218 let two = span(4, 8, rucc_debug::Held::Reg(4));
9219 let settled = settle(vec![one, two]);
9222 assert_eq!(
9223 settled,
9224 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 8, rucc_debug::Held::Reg(4))]
9225 );
9226 }
9227
9228 #[test]
9229 fn a_stretch_cut_by_one_that_ends_first_does_not_come_back_after_it() {
9230 let one = span(0, 16, rucc_debug::Held::Reg(3));
9233 let two = span(4, 4, rucc_debug::Held::Reg(4));
9234 assert_eq!(
9235 settle(vec![one, two]),
9236 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 4, rucc_debug::Held::Reg(4))]
9237 );
9238 }
9239
9240 #[test]
9241 fn a_stretch_inside_another_that_agrees_with_it_cuts_nothing() {
9242 let one = span(0, 16, rucc_debug::Held::Reg(3));
9243 let two = span(4, 4, rucc_debug::Held::Reg(3));
9244 assert_eq!(settle(vec![one, two]), vec![span(0, 16, rucc_debug::Held::Reg(3))]);
9245 }
9246
9247 #[test]
9248 fn a_stretch_two_others_disagree_over_the_whole_of_says_nothing_at_all() {
9249 let one = span(0, 8, rucc_debug::Held::Reg(3));
9250 let two = span(0, 8, rucc_debug::Held::Frame(-16));
9251 assert_eq!(settle(vec![one, two]), Vec::new());
9252 }
9253
9254 #[test]
9255 fn stretches_with_a_gap_between_them_keep_the_gap() {
9256 let one = span(0, 4, rucc_debug::Held::Reg(3));
9257 let two = span(16, 4, rucc_debug::Held::Reg(3));
9258 assert_eq!(settle(vec![one, two]), vec![one, two]);
9259 }
9260
9261 fn extent(len: usize) -> rucc_object::Extent {
9263 rucc_object::Extent {
9264 name: "f".to_owned(),
9265 start: 0,
9266 len,
9267 align: 1,
9268 binding: rucc_object::Binding::Global,
9269 visibility: rucc_object::Visibility::Default,
9270 patch: None,
9271 }
9272 }
9273
9274 fn row(at: usize, lo: u32, hi: u32) -> rucc_asm::Row {
9276 let span = Span::new(lo, hi);
9277 rucc_asm::Row { at, span, inst: None }
9278 }
9279
9280 #[test]
9281 fn a_row_ends_where_the_next_address_begins() {
9282 let rows = [row(0, 0, 1), row(4, 1, 2), row(10, 2, 3)];
9283 assert_eq!(ends(&extent(16), &rows), vec![4, 10, 16]);
9284 }
9285
9286 #[test]
9287 fn rows_sharing_an_address_all_end_where_the_next_address_begins() {
9288 let rows = [row(0, 0, 1), row(4, 1, 2), row(4, 2, 3), row(4, 3, 4)];
9291 assert_eq!(ends(&extent(12), &rows), vec![4, 12, 12, 12]);
9292 }
9293
9294 #[test]
9295 fn the_rows_of_a_scope_that_are_next_to_each_other_come_out_as_one_stretch() {
9296 let rows = [row(0, 0, 4), row(4, 10, 14), row(8, 14, 18), row(12, 40, 44)];
9297 let ends = ends(&extent(16), &rows);
9298 let scope = Span::new(8, 20);
9299 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 8 }]);
9300 }
9301
9302 #[test]
9303 fn a_scope_the_back_end_split_in_two_comes_out_as_two_stretches() {
9304 let rows = [row(0, 10, 14), row(4, 40, 44), row(8, 14, 18)];
9305 let ends = ends(&extent(12), &rows);
9306 let scope = Span::new(8, 20);
9307 let over = spread(scope, &ends, &rows);
9308 assert_eq!(
9309 over,
9310 vec![rucc_debug::Reach { from: 0, len: 4 }, rucc_debug::Reach { from: 8, len: 4 }]
9311 );
9312 }
9313
9314 #[test]
9315 fn a_row_with_no_source_of_its_own_belongs_to_no_scope() {
9316 let rows = [rucc_asm::Row { at: 0, span: Span::DUMMY, inst: None }, row(4, 10, 14)];
9318 let ends = ends(&extent(8), &rows);
9319 let scope = Span::new(0, 20);
9320 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 4 }]);
9321 }
9322
9323 fn scope(parent: Option<usize>, lo: u32, hi: u32) -> crate::shapes::Scope {
9325 let span = Span::new(lo, hi);
9326 crate::shapes::Scope { parent, span }
9327 }
9328
9329 #[test]
9330 fn a_function_gets_the_scopes_its_own_locals_are_in_and_nothing_else() {
9331 let scopes = [scope(None, 0, 10), scope(None, 20, 30), scope(Some(1), 22, 26)];
9333 let rows = [row(0, 22, 24), row(4, 26, 28)];
9334 let (out, at) = nests(&[Some(2)], &scopes, &extent(8), &rows);
9335 assert_eq!(at.get(&1), Some(&0));
9338 assert_eq!(at.get(&2), Some(&1));
9339 assert_eq!(at.get(&0), None);
9340 assert_eq!(out.len(), 2);
9341 assert_eq!(out[0].parent, None);
9342 assert_eq!(out[1].parent, Some(0));
9343 assert_eq!(out[0].over, vec![rucc_debug::Reach { from: 0, len: 8 }]);
9344 assert_eq!(out[1].over, vec![rucc_debug::Reach { from: 0, len: 4 }]);
9345 }
9346
9347 #[test]
9348 fn a_local_written_straight_into_the_body_pulls_no_scope_in() {
9349 let scopes = [scope(None, 20, 30)];
9350 let rows = [row(0, 22, 24)];
9351 let (out, at) = nests(&[None], &scopes, &extent(4), &rows);
9352 assert_eq!(out, Vec::new());
9353 assert!(at.is_empty());
9354 }
9355
9356 #[test]
9357 fn a_scope_whose_code_all_went_away_is_still_one_of_the_functions_scopes() {
9358 let scopes = [scope(None, 20, 30)];
9362 let rows = [row(0, 40, 44)];
9363 let (out, at) = nests(&[Some(0)], &scopes, &extent(4), &rows);
9364 assert_eq!(at.get(&0), Some(&0));
9365 assert_eq!(out.len(), 1);
9366 assert_eq!(out[0].over, Vec::new());
9367 }
9368}