1use std::collections::HashMap;
14use std::path::Path;
15
16use rucc_base::{Interner, Symbol};
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 type_names: Vec<Symbol> =
260 sess.target.type_names().iter().filter_map(|&(name, _)| sess.interner.find(name)).collect();
261 let parsed = rucc_parse::parse(
262 &tokens,
263 rucc_parse::Context {
264 interner: &sess.interner,
265 std: opts.std,
266 gnu: opts.gnu_extensions,
267 pedantic: opts.pedantic,
268 error_limit: opts.error_limit as usize,
269 type_names: &type_names,
270 },
271 );
272 let parse_failed = parsed.diagnostics.iter().any(|d| d.severity.is_fatal());
273 diagnostics.extend(parsed.diagnostics);
274
275 let mut artifact = Artifact::Nothing;
276 let mut instrumented = Instrumented::default();
279 if !parse_failed {
280 let mut checker = Checker::new(
281 &parsed.ast,
282 CheckContext {
283 names: &sess.interner,
284 target: &sess.target,
285 std: opts.std,
286 gnu: opts.gnu_extensions,
287 pedantic: opts.pedantic,
288 permissive: opts.permissive,
289 gnu89_inline: opts.gnu89_inline,
290 error_limit: opts.error_limit as usize,
291 builtins: opts.builtins && opts.hosted,
294 no_builtin: &opts.no_builtin,
295 short_enums: opts.short_enums,
296 ms_extensions: sess.ms_extensions(),
297 trapping_math: opts.trapping_math,
298 },
299 );
300 checker.check_unit();
301 let checked = checker.finish();
302 if !checked.failed() {
303 match opts.emit {
304 EmitKind::Tast => {
305 artifact = Artifact::Text(rucc_sema::print(
306 &checked.tast,
307 &checked.types,
308 &sess.interner,
309 ));
310 }
311 EmitKind::TypeGranules => {
315 artifact = Artifact::Text(rucc_types::granule_report(
316 &checked.types,
317 &sess.interner,
318 &sess.target,
319 ));
320 }
321 EmitKind::Ir
322 | EmitKind::MirFinal
323 | EmitKind::Asm
324 | EmitKind::Object
325 | EmitKind::Archive
326 | EmitKind::Executable
327 | EmitKind::SafetySummary => {
328 let mut read = |named: &str| {
333 fs.read(Path::new(named))
334 .map(|bytes| bytes.as_slice().to_vec())
335 .map_err(|why| why.to_string())
336 };
337 let meaning = if opts.debug_info {
343 crate::shapes::collect(
344 &checked.tast,
345 &checked.types,
346 &sess.target,
347 &sess.interner,
348 &sess.sources,
349 )
350 } else {
351 crate::shapes::Meaning::default()
352 };
353 let mut lowered = rucc_lower::lower(
354 crate::phase::source_name(name),
355 rucc_lower::Context {
356 tast: &checked.tast,
357 types: &checked.types,
358 target: &sess.target,
359 names: &mut sess.interner,
360 visibility: match opts.visibility {
361 Visibility::Default => IrVisibility::Default,
362 Visibility::Hidden => IrVisibility::Hidden,
363 Visibility::Protected => IrVisibility::Protected,
364 },
365 protector: match opts.protector {
366 Protector::None => LowerProtector::None,
367 Protector::Buffers => LowerProtector::Buffers,
368 Protector::Strong => LowerProtector::Strong,
369 Protector::All => LowerProtector::All,
370 },
371 wrapping: rucc_lower::Wrapping {
372 signed: opts.wrapping.signed,
373 pointer: opts.wrapping.pointer,
374 trap: opts.wrapping.trap,
375 },
376 aliasing: opts.strict_aliasing,
377 padding: opts.padding == Padding::Ignored,
378 contract: match opts.fp_contract {
379 Contract::Off => FpContract::Off,
380 Contract::On => FpContract::On,
381 Contract::Fast => FpContract::Fast,
382 },
383 align: opts.align_functions,
384 instrument: opts.instrument_functions,
385 read: &mut read,
386 },
387 );
388 let failed = lowered.diagnostics.iter().any(|d| d.severity.is_fatal());
392 if !failed {
393 if let Err(errors) = rucc_ir::verify(&lowered.module, &sess.interner) {
398 for error in errors {
399 diagnostics.push(internal(&format!("invalid IR, {error}")));
400 }
401 } else if let Err(complaints) =
402 instrument(&mut lowered.module, &mut sess.interner, opts)
403 .map(|done| instrumented = done)
404 {
405 diagnostics.extend(complaints);
406 } else if let Err(complaints) = optimize(
407 &mut lowered.module,
408 &mut sess.interner,
409 &sess.target,
410 opts,
411 name,
412 &mut dumps,
413 &mut remarks,
414 ) {
415 diagnostics.extend(complaints);
416 } else if opts.emit == EmitKind::SafetySummary {
417 artifact = Artifact::Text(
422 rucc_safety::summarize(
423 &lowered.module,
424 &sess.interner,
425 name,
426 opts.safety.as_str(),
427 instrumented.checks,
428 instrumented.interposed,
429 instrumented.crossings,
430 )
431 .render(),
432 );
433 } else if opts.emit == EmitKind::Ir {
434 artifact =
439 Artifact::Text(rucc_ir::print(&lowered.module, &sess.interner));
440 } else {
441 match generate(
444 &mut lowered.module,
445 &mut sess.interner,
446 &sess.target,
447 opts,
448 &mut Recording {
449 fired: &mut fired,
450 pressure: &mut pressure,
451 lowerings: &mut lowerings,
452 },
453 &mut temps.assembly,
454 Origin { map: &sess.sources, name, meaning: &meaning },
455 ) {
456 Ok(made) => artifact = made,
457 Err(complaints) => diagnostics.extend(complaints),
458 }
459 }
460 }
461 diagnostics.extend(lowered.diagnostics);
462 }
463 _ => {}
464 }
465 }
466 diagnostics.extend(checked.diagnostics);
467 }
468 let mut wants = rucc_opt::Wants::none();
471 for spec in &opts.opt_info {
472 let _ = wants.add(spec);
474 }
475 if wants.wants(rucc_opt::stats::Kind::Optimized) {
476 remarks.push_str(&lowerings.remarks(name));
477 }
478
479 let mut messages = Vec::with_capacity(diagnostics.len());
480 let mut errors = 0;
481 for diag in &diagnostics {
482 if rucc_diag::dropped(diag, &sess.sources, opts.warnings, opts.system_header_warnings) {
488 continue;
489 }
490 if diag.severity.is_fatal()
491 || (diag.severity == Severity::Warning && opts.warnings_are_errors)
492 {
493 errors += 1;
494 }
495 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
496 }
497 if errors > 0 {
498 artifact = Artifact::Nothing;
500 }
501 Compiled { artifact, messages, errors, fired, pressure, lowerings, dumps, remarks, deps, temps }
504}
505
506#[must_use]
516pub fn compile_ir(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
517 let mut sess = Session::new(opts.clone());
518 if opts.emit != EmitKind::Ir {
519 return failure(format!(
520 "{name}: an input of IR can only be emitted as IR, and `--emit={}` asks for what \
521 the C in front of it became",
522 opts.emit.as_str()
523 ));
524 }
525 let bytes = match fs.read(Path::new(name)) {
526 Ok(bytes) => bytes,
527 Err(e) => return failure(format!("{name}: {e}")),
528 };
529 let Ok(text) = std::str::from_utf8(bytes.as_slice()) else {
530 return failure(format!("{name}: this is not text, so it is not IR"));
531 };
532
533 let module = match rucc_ir::parse(text, &mut sess.interner) {
534 Ok(module) => module,
535 Err(error) => {
536 return failure(format!("{name}:{}: {}", error.line, error.message));
537 }
538 };
539 let mut diagnostics: Vec<Diagnostic> = Vec::new();
540 if let Err(errors) = rucc_ir::verify(&module, &sess.interner) {
541 for error in errors {
542 diagnostics.push(invalid(&format!("invalid IR, {error}")));
543 }
544 }
545 let mut messages = Vec::with_capacity(diagnostics.len());
546 for diag in &diagnostics {
547 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
548 }
549 let errors = u32::try_from(messages.len()).unwrap_or(u32::MAX);
550 let artifact = if errors > 0 {
551 Artifact::Nothing
552 } else {
553 Artifact::Text(rucc_ir::print(&module, &sess.interner))
554 };
555 Compiled {
557 artifact,
558 messages,
559 errors,
560 fired: Fired::new(),
561 pressure: Pressure::new(),
562 lowerings: Lowerings::new(),
563 dumps: Vec::new(),
564 remarks: String::new(),
565 deps: Vec::new(),
566 temps: Temps::default(),
567 }
568}
569
570fn instrument(
593 module: &mut rucc_ir::Module,
594 names: &mut Interner,
595 opts: &Options,
596) -> Result<Instrumented, Vec<Diagnostic>> {
597 if !opts.safety.instruments() {
598 return Ok(Instrumented::default());
599 }
600 let mut checks = rucc_safety::run(module, opts.subobject, opts.promise, opts.races);
601 checks.freed = rucc_safety::ending::checks(module, names);
609 let interposed = rucc_safety::redirect(module, names);
614 let crossings = rucc_safety::witness(module, names);
617 match rucc_ir::verify(module, names) {
618 Ok(()) => Ok(Instrumented { checks, interposed, crossings }),
619 Err(errors) => Err(errors
620 .iter()
621 .map(|e| internal(&format!("invalid IR after check insertion, {e}")))
622 .collect()),
623 }
624}
625
626#[derive(Clone, Copy, Debug, Default)]
632struct Instrumented {
633 checks: rucc_safety::Counts,
635 interposed: usize,
637 crossings: rucc_safety::Sites,
639}
640
641fn optimize(
653 module: &mut rucc_ir::Module,
654 names: &mut Interner,
655 target: &TargetInfo,
656 opts: &Options,
657 file: &str,
658 dumps: &mut Vec<rucc_opt::Dump>,
659 remarks: &mut String,
660) -> Result<(), Vec<Diagnostic>> {
661 let mut settings = rucc_opt::Options::for_level(opts.opt_level);
662 settings.interposition = match opts.interposition {
668 true => replaceable(target, opts),
669 false => IrPic::Executable,
670 };
671 settings.toggles.clone_from(&opts.passes);
672 settings.builtins = opts.builtins && opts.hosted;
677 settings.no_builtin.clone_from(&opts.no_builtin);
678 settings.fuel = opts.pass_fuel.iter().cloned().collect();
679 settings.global_fuel = opts.pass_fuel_global;
680 settings.verify |= opts.verify_each;
681 for (on, spec) in &opts.pass_gates {
682 if let Err(why) = settings.gates.add(*on, spec) {
685 return Err(vec![internal(&why)]);
686 }
687 }
688 for spec in &opts.dump_ir {
689 if let Err(why) = settings.dumps.add(spec) {
692 return Err(vec![internal(&why)]);
693 }
694 }
695 let mut wants = rucc_opt::Wants::none();
696 for spec in &opts.opt_info {
697 if let Err(why) = wants.add(spec) {
700 return Err(vec![internal(&why)]);
701 }
702 }
703 let report = rucc_opt::run(module, names, &settings);
704 remarks.push_str(&rucc_opt::optinfo::render(file, &report, names, wants));
705 dumps.extend(report.dumps);
706 match report.broke.is_empty() {
707 true => Ok(()),
708 false => Err(report.broke.iter().map(|why| internal(why)).collect()),
709 }
710}
711
712fn replaceable(target: &TargetInfo, opts: &Options) -> IrPic {
746 match (target.tuple.os().object_format(), opts.pic) {
747 (Some(ObjectFormat::Elf), Pic::Library) => IrPic::Library,
748 _ => IrPic::Executable,
749 }
750}
751
752#[derive(Clone, Copy)]
759struct Origin<'a> {
760 map: &'a SourceMap,
762 name: &'a str,
764 meaning: &'a crate::shapes::Meaning,
766}
767
768fn generate(
769 module: &mut rucc_ir::Module,
770 names: &mut Interner,
771 target: &TargetInfo,
772 opts: &Options,
773 recording: &mut Recording<'_>,
774 assembly: &mut Option<String>,
775 origin: Origin<'_>,
776) -> Result<Artifact, Vec<Diagnostic>> {
777 let Some(machine) = Machine::for_target(target) else {
778 return Err(vec![unsupported(&format!(
779 "there is no back end for {} in this compiler yet, so there is nothing to generate",
780 target.tuple
781 ))]);
782 };
783 if opts.protector != Protector::None && machine.conv.guard.is_none() {
788 return Err(vec![unsupported(&format!(
789 "{} is not supported for {} yet, because the stack protector on that target is not \
790 the one this compiler writes",
791 opts.protector, target.tuple
792 ))]);
793 }
794 if opts.control.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
800 return Err(vec![unsupported(&format!(
801 "-fcf-protection={} is not supported for {} yet, because what says a file was built \
802 for it there is not the note this compiler writes",
803 opts.control, target.tuple
804 ))]);
805 }
806 let profile = match machine.conv.trace {
812 Some(trace) => opts.profile.then(|| opts.hook.early(trace.fentry)),
813 None if opts.profile => {
814 return Err(vec![unsupported(&format!(
815 "-pg is not supported for {} yet, because the profiler's hook on that target is \
816 not the one this compiler calls",
817 target.tuple
818 ))]);
819 }
820 None => None,
821 };
822 if opts.patchable.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
827 return Err(vec![unsupported(&format!(
828 "-fpatchable-function-entry= is not supported for {} yet, because what records where \
829 the room is there is not the section this compiler writes",
830 target.tuple
831 ))]);
832 }
833 let flags = pipeline::Flags {
834 frame_pointer: opts.frame_pointer,
835 red_zone: opts.red_zone,
836 stack_clash: opts.stack_clash,
837 landing: opts.control.branch(),
838 profile: match profile {
839 None => pipeline::Profile::No,
840 Some(true) => pipeline::Profile::Early,
841 Some(false) => pipeline::Profile::Late,
842 },
843 patch: pipeline::Room { after: opts.patchable.after(), before: opts.patchable.before },
844 reorder: opts.reorder_blocks.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
851 reuse: opts.stack_reuse.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
856 schedule: opts.schedule_insns.unwrap_or_else(|| opts.opt_level.schedules()),
861 align_loops: opts.align_loops.unwrap_or(false),
867 accurate: opts.cycle_accurate_model,
869 verify: opts.verify_each,
872 goal: Goal::for_size(opts.opt_level.is_size()),
877 switch: opts.switch_shape.as_deref().and_then(rucc_codegen::switch::Force::named),
879 };
880
881 if opts.safety.instruments() {
890 rucc_opt::heap::annotate(module, names);
900 rucc_safety::handover::arrange(module);
907 rucc_safety::lower(module, names);
908 if let Err(errors) = rucc_ir::verify(module, names) {
909 return Err(errors
910 .iter()
911 .map(|e| internal(&format!("invalid IR after check lowering, {e}")))
912 .collect());
913 }
914 }
915
916 let copies = target.tuple.arch() == Arch::X86_64;
927 let elsewhere = Elsewhere::of(module, replaceable(target, opts), target.object_format, copies);
928
929 let mut funcs = Vec::new();
930 let mut complaints = Vec::new();
931 for id in module.funcs() {
932 if module[id].is_declaration() {
933 continue;
934 }
935 match pipeline::compile_recording(
936 &mut module[id],
937 names,
938 &machine,
939 &elsewhere,
940 flags,
941 recording,
942 ) {
943 Ok(func) => funcs.push(func),
944 Err(why) => {
945 let name = names.resolve(module[id].name).to_owned();
946 let span = why.inst().map_or(Span::DUMMY, |inst| module[id].span(inst));
949 let said = format!("cannot generate code for '{name}': {why}");
950 complaints.push(unsupported_at(&said, span));
951 }
952 }
953 }
954 if !complaints.is_empty() {
955 return Err(complaints);
956 }
957 let (globals, aliases) = match opts.emit {
963 EmitKind::Asm | EmitKind::Object | EmitKind::Archive | EmitKind::Executable => (
964 rucc_asm::globals(module, names, target.object_format).map_err(refused)?,
965 rucc_asm::aliases(module, names).map_err(refused)?,
966 ),
967 _ => (rucc_asm::Globals::default(), Vec::new()),
968 };
969 let unwind = opts.unwinds();
973 match opts.emit {
974 EmitKind::Asm => {
975 rucc_asm::print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
976 .map(Artifact::Text)
977 .map_err(refused)
978 }
979 EmitKind::Object | EmitKind::Archive | EmitKind::Executable => {
983 if opts.save_temps.wanted() {
984 let listing = rucc_asm::print(
985 &funcs,
986 &globals,
987 &aliases,
988 names,
989 target,
990 unwind,
991 output(opts, target),
992 );
993 *assembly = Some(listing.map_err(refused)?);
994 }
995 let aarch64 = target.tuple.arch() == Arch::Aarch64;
1006 if aarch64 || rucc_asm::kept(&funcs, names, target) {
1007 let print = if opts.debug_info { rucc_asm::print_marked } else { rucc_asm::print };
1008 let listing =
1009 print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
1010 .map_err(refused)?;
1011 let read = rucc_asm::read(&listing, target.tuple.arch()).map_err(|trouble| {
1012 let what = if aarch64 {
1013 "a unit for aarch64"
1014 } else {
1015 "an `asm` template kept as text"
1016 };
1017 vec![unsupported(&format!(
1018 "{what}, whose listing the assembler stopped at on line {}: {}",
1019 trouble.line, trouble.why
1020 ))]
1021 })?;
1022 let info = if opts.debug_info {
1023 let assembled =
1024 placed(&read, &funcs, names, target).map_err(|why| vec![internal(&why)])?;
1025 describe(&assembled, &globals.image(), &funcs, origin, opts, target)
1026 .map_err(|why| vec![internal(&why)])?
1027 } else {
1028 rucc_object::Info::default()
1029 };
1030 let defines = rucc_object::assembled_defines(&read);
1031 let bytes =
1032 rucc_object::assembled_described(&read, &TargetInfo::new(opts.target), &info)
1033 .map_err(wrote)?;
1034 return Ok(Artifact::Object { bytes, defines });
1035 }
1036 let assembled = rucc_asm::assemble(&funcs, names, target, unwind, opts.debug_info)
1037 .map_err(refused)?;
1038 let data = globals.image();
1039 let info = if opts.debug_info {
1043 describe(&assembled, &data, &funcs, origin, opts, target)
1044 .map_err(|why| vec![internal(&why)])?
1045 } else {
1046 rucc_object::Info::default()
1047 };
1048 let text = assembled.text;
1049 let bytes =
1052 rucc_object::write(&text, &data, &aliases, target, output(opts, target), &info)
1053 .map_err(wrote)?;
1054 let defines = rucc_object::defines(&text, &data, &aliases, target).map_err(wrote)?;
1059 Ok(Artifact::Object { bytes, defines })
1060 }
1061 _ => Ok(Artifact::Text(rucc_mir::print(&funcs, names, target.regs))),
1062 }
1063}
1064
1065fn placed(
1077 read: &rucc_object::Assembled,
1078 funcs: &[rucc_mir::Func],
1079 names: &Interner,
1080 target: &TargetInfo,
1081) -> Result<rucc_asm::Assembled, String> {
1082 let at: HashMap<&str, &rucc_object::Name> =
1083 read.names.iter().map(|name| (name.name.as_str(), name)).collect();
1084 let offset = |name: &str| match at.get(name).map(|name| name.at) {
1085 Some(rucc_object::Held::In { part, offset }) => Some((part, offset)),
1086 _ => None,
1087 };
1088 let mut text = rucc_object::Text::default();
1089 let mut lines = Vec::with_capacity(funcs.len());
1090 for (which, func) in funcs.iter().enumerate() {
1091 let name = names.resolve(func.name);
1092 let Some((part, start)) = offset(name) else {
1093 return Err(format!("the listing has no label for the function '{name}'"));
1094 };
1095 let mut rows = Vec::with_capacity(func.inst_count() + 1);
1096 if !func.declared.is_dummy() {
1097 rows.push(rucc_asm::Row { at: 0, span: func.declared, inst: None });
1098 }
1099 for block in func.blocks() {
1100 for inst in func.insts(block) {
1101 let label = rucc_asm::mark(target, which, inst);
1102 let Some((held, here)) = offset(&label) else {
1103 return Err(format!("the listing has no label '{label}'"));
1104 };
1105 if held != part || here < start {
1106 return Err(format!("the label '{label}' is not inside '{name}'"));
1107 }
1108 let at = usize::try_from(here - start).map_err(|why| why.to_string())?;
1109 rows.push(rucc_asm::Row { at, span: func.span(inst), inst: Some(inst) });
1110 }
1111 }
1112 let len = at.get(name).map_or(0, |name| name.size);
1113 text.funcs.push(rucc_object::Extent {
1114 name: name.to_owned(),
1115 start: usize::try_from(start).map_err(|why| why.to_string())?,
1116 len: usize::try_from(len).map_err(|why| why.to_string())?,
1117 align: func.align.unwrap_or(rucc_object::FUNC_ALIGN),
1118 binding: rucc_object::Binding::Global,
1119 visibility: rucc_object::Visibility::Default,
1120 patch: None,
1121 });
1122 lines.push(rows);
1123 }
1124 Ok(rucc_asm::Assembled { text, lines, frames: None })
1125}
1126
1127fn describe(
1146 assembled: &rucc_asm::Assembled,
1147 data: &rucc_object::Data,
1148 machine: &[rucc_mir::Func],
1149 origin: Origin<'_>,
1150 opts: &Options,
1151 target: &TargetInfo,
1152) -> Result<rucc_object::Info, String> {
1153 let rucc_asm::Assembled { text, lines, frames } = assembled;
1154 let rewrite = |path: &str| opts.prefix_map.debug.apply(path).into_owned();
1155 let mut files: Vec<String> = Vec::new();
1160 let mut funcs = Vec::with_capacity(text.funcs.len());
1161 for ((extent, rows), built) in text.funcs.iter().zip(lines).zip(machine) {
1162 let mut out: Vec<rucc_debug::Row> = Vec::with_capacity(rows.len());
1163 for row in rows {
1164 if row.span.is_dummy() {
1165 continue;
1166 }
1167 let Some(at) = origin.map.presumed(row.span.lo) else {
1168 continue;
1169 };
1170 let which = interned(&mut files, rewrite(at.name));
1171 let place = rucc_debug::Row {
1172 at: row.at as u64,
1173 file: which,
1174 line: at.line,
1175 column: at.column,
1176 };
1177 match out.last() {
1187 Some(last) if last.at == place.at => {}
1188 _ => out.push(place),
1189 }
1190 }
1191 if let Some(first) = out.first_mut() {
1198 first.at = 0;
1199 }
1200 let known = origin.meaning.funcs.get(&extent.name);
1206 let decl = known.map(|known| rucc_debug::Place {
1207 file: interned(&mut files, rewrite(&known.file)),
1208 line: known.line,
1209 });
1210 let mut sig = known.and_then(|known| known.sig.clone());
1219 let mut placed: Vec<(u32, i32)> = built.locals.clone();
1220 let mut spots = stretches(extent, rows, built, target);
1221 for &decl in &built.sharing {
1226 if !spots.iter().any(|(at, _)| *at == decl) {
1227 spots.push((decl, Vec::new()));
1228 }
1229 }
1230 if let (Some(sig), Some(known)) = (sig.as_mut(), known) {
1231 for (param, decl) in sig.params.iter_mut().zip(&known.params) {
1232 let Some(decl) = *decl else { continue };
1233 if let Some(which) = placed.iter().position(|&(at, _)| at == decl) {
1234 let at = rucc_debug::Held::Frame(i64::from(placed.remove(which).1));
1235 param.spot = Some(rucc_debug::Spot::Always(at));
1236 continue;
1237 }
1238 let Some(which) = spots.iter().position(|(at, _)| *at == decl) else { continue };
1242 param.spot = Some(rucc_debug::Spot::Over(spots.remove(which).1));
1243 }
1244 }
1245 let mut locals = Vec::with_capacity(placed.len() + spots.len());
1249 let mut wants: Vec<Option<usize>> = Vec::with_capacity(locals.capacity());
1253 for (decl, at) in placed {
1254 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1255 wants.push(named.scope);
1256 locals.push(rucc_debug::Local {
1257 name: named.name.clone(),
1258 ty: named.ty,
1259 decl: Some(rucc_debug::Place {
1260 file: interned(&mut files, rewrite(&named.file)),
1261 line: named.line,
1262 }),
1263 spot: rucc_debug::Spot::Always(rucc_debug::Held::Frame(i64::from(at))),
1264 scope: None,
1265 });
1266 }
1267 spots.sort_by_key(|(decl, _)| *decl);
1271 for (decl, spans) in spots {
1272 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1273 wants.push(named.scope);
1274 locals.push(rucc_debug::Local {
1275 name: named.name.clone(),
1276 ty: named.ty,
1277 decl: Some(rucc_debug::Place {
1278 file: interned(&mut files, rewrite(&named.file)),
1279 line: named.line,
1280 }),
1281 spot: rucc_debug::Spot::Over(spans),
1282 scope: None,
1283 });
1284 }
1285 let (scopes, at) = nests(&wants, &origin.meaning.scopes, extent, rows);
1290 for (local, want) in locals.iter_mut().zip(&wants) {
1291 local.scope = want.and_then(|want| at.get(&want).copied());
1292 }
1293 funcs.push(rucc_debug::Function {
1294 name: extent.name.clone(),
1295 len: extent.len as u64,
1296 rows: out,
1297 decl,
1298 sig,
1299 external: known.is_some_and(|known| known.external),
1300 locals,
1301 scopes,
1302 });
1303 }
1304 let mut globals = Vec::new();
1311 for object in &data.objects {
1312 let Some(held) = origin.meaning.objects.get(&object.name) else { continue };
1313 globals.push(rucc_debug::Global {
1314 name: object.name.clone(),
1315 ty: held.ty,
1316 decl: Some(rucc_debug::Place {
1317 file: interned(&mut files, rewrite(&held.file)),
1318 line: held.line,
1319 }),
1320 external: held.external,
1321 });
1322 }
1323 let unit = rucc_debug::Unit {
1324 name: rewrite(origin.name),
1325 dir: rewrite(opts.working_dir.as_deref().unwrap_or(".")),
1328 producer: format!("rucc {}", crate::VERSION),
1329 files,
1330 types: origin.meaning.types.clone(),
1331 funcs,
1332 globals,
1333 pointer: u8::try_from(target.pointer_width / 8).unwrap_or(8),
1334 frames: opts.unwinds() || frames.is_some(),
1339 };
1340 let mut info = rucc_debug::write(&unit).map_err(|why| why.to_string())?;
1341 info.chunks.extend(frames.clone());
1342 Ok(info)
1343}
1344
1345fn stretches(
1356 extent: &rucc_object::Extent,
1357 rows: &[rucc_asm::Row],
1358 built: &rucc_mir::Func,
1359 target: &TargetInfo,
1360) -> Vec<(u32, Vec<rucc_debug::Span>)> {
1361 let (false, Some(regs)) = (built.kept.is_empty(), target.call_regs) else {
1364 return Vec::new();
1365 };
1366 let ends = ends(extent, rows);
1367 let mut bounds = vec![None; built.inst_count()];
1368 for (which, row) in rows.iter().enumerate() {
1369 let Some(inst) = row.inst else { continue };
1370 bounds[inst.index()] = Some((row.at as u64, ends[which]));
1371 }
1372 let mut spots: Vec<(u32, Vec<rucc_debug::Span>)> = Vec::new();
1373 for kept in &built.kept {
1374 let (Some((from, _)), Some((_, to))) = (bounds[kept.from.index()], bounds[kept.to.index()])
1375 else {
1376 continue;
1377 };
1378 if to <= from {
1379 continue;
1380 }
1381 let held = match kept.at {
1382 rucc_mir::Where::Reg { reg, class } => match regs.dwarf(class, reg) {
1385 Some(number) => rucc_debug::Held::Reg(number),
1386 None => continue,
1387 },
1388 rucc_mir::Where::Frame(at) => rucc_debug::Held::Frame(i64::from(at)),
1389 };
1390 let span = rucc_debug::Span { from, len: to - from, held };
1391 match spots.iter_mut().find(|(decl, _)| *decl == kept.decl) {
1392 Some((_, spans)) => spans.push(span),
1393 None => spots.push((kept.decl, vec![span])),
1394 }
1395 }
1396 for (_, spans) in &mut spots {
1397 *spans = settle(std::mem::take(spans));
1398 }
1399 spots.retain(|(_, spans)| !spans.is_empty());
1400 spots
1401}
1402
1403fn ends(extent: &rucc_object::Extent, rows: &[rucc_asm::Row]) -> Vec<u64> {
1413 let mut out = vec![extent.len as u64; rows.len()];
1414 let mut next = extent.len as u64;
1415 for which in (0..rows.len()).rev() {
1416 let at = rows[which].at as u64;
1417 out[which] = match next > at {
1420 true => next,
1421 false => out.get(which + 1).copied().unwrap_or(extent.len as u64),
1422 };
1423 next = next.min(at);
1424 }
1425 out
1426}
1427
1428fn nests(
1444 wants: &[Option<usize>],
1445 scopes: &[crate::shapes::Scope],
1446 extent: &rucc_object::Extent,
1447 rows: &[rucc_asm::Row],
1448) -> (Vec<rucc_debug::Scope>, HashMap<usize, usize>) {
1449 let mut needed: Vec<usize> = Vec::new();
1450 for &want in wants {
1451 let mut up = want;
1452 while let Some(which) = up {
1453 if needed.contains(&which) {
1454 break;
1455 }
1456 needed.push(which);
1457 up = scopes.get(which).and_then(|scope| scope.parent);
1458 }
1459 }
1460 needed.sort_unstable();
1463 let at: HashMap<usize, usize> =
1464 needed.iter().enumerate().map(|(which, &scope)| (scope, which)).collect();
1465 let ends = ends(extent, rows);
1466 let out = needed
1467 .iter()
1468 .map(|&which| {
1469 let scope = &scopes[which];
1470 rucc_debug::Scope {
1471 parent: scope.parent.and_then(|parent| at.get(&parent).copied()),
1472 over: spread(scope.span, &ends, rows),
1473 }
1474 })
1475 .collect();
1476 (out, at)
1477}
1478
1479fn spread(span: Span, ends: &[u64], rows: &[rucc_asm::Row]) -> Vec<rucc_debug::Reach> {
1487 let mut out: Vec<rucc_debug::Reach> = Vec::new();
1488 for (which, row) in rows.iter().enumerate() {
1489 if row.span.is_dummy() || row.span.lo < span.lo || row.span.hi > span.hi {
1490 continue;
1491 }
1492 let (from, to) = (row.at as u64, ends[which]);
1493 if to <= from {
1494 continue;
1495 }
1496 match out.last_mut() {
1497 Some(last) if last.from + last.len >= from => {
1498 last.len = to.saturating_sub(last.from).max(last.len);
1499 }
1500 _ => out.push(rucc_debug::Reach { from, len: to - from }),
1501 }
1502 }
1503 out
1504}
1505
1506fn settle(mut spans: Vec<rucc_debug::Span>) -> Vec<rucc_debug::Span> {
1523 spans.sort_by_key(|span| (span.from, span.len));
1524 for which in 0..spans.len() {
1525 let (from, end, held) =
1526 (spans[which].from, spans[which].from + spans[which].len, spans[which].held);
1527 let later = spans[which + 1..]
1528 .iter()
1529 .take_while(|later| later.from < end)
1530 .find(|later| later.from > from && later.held != held);
1531 if let Some(later) = later {
1532 spans[which].len = later.from - from;
1533 }
1534 }
1535 let mut edges: Vec<u64> =
1538 spans.iter().flat_map(|span| [span.from, span.from + span.len]).collect();
1539 edges.sort_unstable();
1540 edges.dedup();
1541 let mut out: Vec<rucc_debug::Span> = Vec::new();
1542 let mut first = 0;
1543 for pair in edges.windows(2) {
1544 let (from, to) = (pair[0], pair[1]);
1545 while spans.get(first).is_some_and(|span| span.from + span.len <= from) {
1549 first += 1;
1550 }
1551 let mut held = None;
1552 let mut agreed = true;
1553 for span in &spans[first..] {
1554 if span.from >= to {
1555 break;
1556 }
1557 if span.from > from || span.from + span.len < to {
1558 continue;
1559 }
1560 match held {
1561 None => held = Some(span.held),
1562 Some(seen) => agreed &= seen == span.held,
1563 }
1564 }
1565 let (Some(held), true) = (held, agreed) else { continue };
1566 match out.last_mut() {
1567 Some(last) if last.from + last.len == from && last.held == held => {
1568 last.len += to - from
1569 }
1570 _ => out.push(rucc_debug::Span { from, len: to - from, held }),
1571 }
1572 }
1573 out
1574}
1575
1576fn interned(files: &mut Vec<String>, name: String) -> usize {
1582 match files.iter().position(|have| *have == name) {
1583 Some(which) => which,
1584 None => {
1585 files.push(name);
1586 files.len() - 1
1587 }
1588 }
1589}
1590
1591fn output(opts: &Options, target: &TargetInfo) -> rucc_object::Output {
1603 let mut features = 0;
1604 if target.tuple.arch() == Arch::X86_64 {
1605 if opts.control.branch() {
1606 features |= rucc_object::Property::IBT;
1607 }
1608 if opts.control.ret() {
1609 features |= rucc_object::Property::SHSTK;
1610 }
1611 }
1612 rucc_object::Output {
1613 sections: rucc_object::Sections {
1614 functions: opts.function_sections,
1615 data: opts.data_sections,
1616 },
1617 property: rucc_object::Property { features },
1618 }
1619}
1620
1621fn wrote(why: rucc_object::Error) -> Vec<Diagnostic> {
1627 match why {
1628 rucc_object::Error::Format { .. } => vec![unsupported(&why.to_string())],
1629 rucc_object::Error::Refused { .. } => vec![internal(&why.to_string())],
1630 }
1631}
1632
1633fn refused(why: rucc_asm::Error) -> Vec<Diagnostic> {
1640 match why {
1641 rucc_asm::Error::Thread { .. }
1642 | rucc_asm::Error::IFunc { .. }
1643 | rucc_asm::Error::Frame { .. } => {
1644 vec![unsupported(&why.to_string())]
1645 }
1646 _ => vec![internal(&why.to_string())],
1647 }
1648}
1649
1650fn unsupported(message: &str) -> Diagnostic {
1656 unsupported_at(message, Span::DUMMY)
1657}
1658
1659fn unsupported_at(message: &str, span: Span) -> Diagnostic {
1665 Diagnostic::error(message.to_owned(), span)
1666 .with_code("E0653")
1667 .note("this construct is not lowered yet, see https://github.com/tamnd/rucc/issues", span)
1668}
1669
1670fn invalid(message: &str) -> Diagnostic {
1672 Diagnostic::error(message.to_owned(), Span::DUMMY).with_code("E0661")
1673}
1674
1675fn internal(message: &str) -> Diagnostic {
1677 Diagnostic::error(format!("internal error: {message}"), Span::DUMMY)
1678 .with_code("E0652")
1679 .note("this is a bug in rucc rather than in the program, please report it", Span::DUMMY)
1680}
1681
1682fn failure(message: String) -> Compiled {
1685 Compiled {
1686 artifact: Artifact::Nothing,
1687 messages: vec![format!("rucc: error: {message}")],
1688 errors: 1,
1689 fired: Fired::new(),
1690 pressure: Pressure::new(),
1691 lowerings: Lowerings::new(),
1692 dumps: Vec::new(),
1693 remarks: String::new(),
1694 deps: Vec::new(),
1695 temps: Temps::default(),
1696 }
1697}
1698
1699#[cfg(test)]
1700mod tests {
1701 use rucc_session::{MemoryFileSystem, Std};
1702 use rucc_target::Triple;
1703
1704 use super::*;
1705
1706 fn options() -> Options {
1707 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1708 opts.emit = EmitKind::Tast;
1709 opts
1710 }
1711
1712 fn run(opts: &Options, source: &str) -> Compiled {
1713 let mut fs = MemoryFileSystem::new();
1714 fs.insert("/main.c", source.to_owned().into_bytes());
1715 compile(opts, "/main.c", &fs)
1716 }
1717
1718 fn freestanding() -> Options {
1722 let mut opts = options();
1723 opts.hosted = false;
1724 opts.search.push_system(rucc_session::runtime::DIR);
1725 opts
1726 }
1727
1728 fn shipped(source: &str) -> String {
1730 let result = run(&freestanding(), source);
1731 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1732 result.text().to_owned()
1733 }
1734
1735 fn tast(source: &str) -> String {
1737 let result = run(&options(), source);
1738 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1739 result.text().to_owned()
1740 }
1741
1742 #[test]
1743 fn the_shipped_stdarg_declares_a_list_and_the_four_operators() {
1744 let text = shipped(concat!(
1745 "#include <stdarg.h>\n",
1746 "int sum(int n, ...) {\n",
1747 " va_list ap, copy;\n",
1748 " va_start(ap, n);\n",
1749 " va_copy(copy, ap);\n",
1750 " int total = va_arg(ap, int) + va_arg(copy, int);\n",
1751 " va_end(ap);\n",
1752 " va_end(copy);\n",
1753 " return total;\n",
1754 "}\n",
1755 ));
1756 assert!(text.contains("va-start"), "{text}");
1757 assert!(text.contains("va-copy"), "{text}");
1758 assert!(text.contains("va-arg"), "{text}");
1759 assert!(text.contains("va-end"), "{text}");
1760 }
1761
1762 #[test]
1766 fn stdarg_hands_out_the_type_alone_when_that_is_all_that_was_asked_for() {
1767 let text = shipped(concat!(
1768 "#define __need___va_list\n",
1769 "#include <stdarg.h>\n",
1770 "int vprint(const char *f, __gnuc_va_list ap);\n",
1771 "#ifdef va_start\n",
1772 "#error va_start should not be defined\n",
1773 "#endif\n",
1774 "#ifdef _VA_LIST_DEFINED\n",
1775 "#error va_list should not have been made\n",
1776 "#endif\n",
1777 ));
1778 assert!(text.contains("vprint"), "{text}");
1779 }
1780
1781 #[test]
1784 fn stddef_answers_one_piece_at_a_time_and_the_next_request_still_gets_through() {
1785 let text = shipped(concat!(
1786 "#define __need_size_t\n",
1787 "#include <stddef.h>\n",
1788 "#ifdef offsetof\n",
1789 "#error offsetof should not be defined yet\n",
1790 "#endif\n",
1791 "#define __need_ptrdiff_t\n",
1792 "#include <stddef.h>\n",
1793 "#include <stddef.h>\n",
1794 "size_t a;\n",
1795 "ptrdiff_t b;\n",
1796 "wchar_t c;\n",
1797 "max_align_t d;\n",
1798 "void *e = NULL;\n",
1799 "struct P { int x; long y; };\n",
1800 "size_t f = offsetof(struct P, y);\n",
1801 ));
1802 assert!(text.contains("decl #0 a : unsigned long"), "{text}");
1803 assert!(text.contains("decl #1 b : long"), "{text}");
1804 }
1805
1806 #[test]
1807 fn the_shipped_limits_and_float_are_the_targets_own_answers() {
1808 let text = shipped(concat!(
1809 "#include <limits.h>\n",
1810 "#include <float.h>\n",
1811 "int bits = CHAR_BIT;\n",
1812 "long big = LONG_MAX;\n",
1813 "int low = INT_MIN;\n",
1814 "int radix = FLT_RADIX;\n",
1815 "int digits = DBL_MANT_DIG;\n",
1816 ));
1817 assert!(text.contains("const 8 : int"), "{text}");
1818 assert!(text.contains("const 9223372036854775807 : long"), "{text}");
1819 assert!(text.contains("const 2 : int"), "{text}");
1820 assert!(text.contains("const 53 : int"), "{text}");
1821 }
1822
1823 #[test]
1827 fn the_shipped_stdint_writes_the_whole_set_when_there_is_no_library_to_defer_to() {
1828 let text = shipped(concat!(
1829 "#include <stdint.h>\n",
1830 "int64_t a = INT64_C(1);\n",
1831 "uint_least16_t b;\n",
1832 "intptr_t c;\n",
1833 "uintmax_t d = UINTMAX_MAX;\n",
1834 "int wide = sizeof(int_fast64_t);\n",
1835 ));
1836 assert!(text.contains("decl #0 a : long"), "{text}");
1837 assert!(text.contains("decl #1 b : unsigned short"), "{text}");
1838 assert!(text.contains("decl #2 c : long"), "{text}");
1839 }
1840
1841 #[test]
1852 fn the_shipped_mmintrin_defines_the_mmx_type_and_the_operations_over_it() {
1853 let text = shipped(concat!(
1854 "#include <mmintrin.h>\n",
1855 "__m64 add(__m64 a, __m64 b) { return _mm_add_pi16(a, b); }\n",
1856 "__m64 pack(__m64 a, __m64 b) { return _m_packsswb(a, b); }\n",
1857 "__m64 shift(__m64 a) { return _mm_srai_pi32(a, 3); }\n",
1858 "int low(__m64 a) { return _mm_cvtsi64_si32(a); }\n",
1859 "void done(void) { _mm_empty(); }\n",
1860 ));
1861 assert!(text.contains("add"), "{text}");
1862 assert!(text.contains("pack"), "{text}");
1863 assert!(text.contains("shift"), "{text}");
1864 }
1865
1866 #[test]
1871 fn the_shipped_mm_malloc_asks_for_aligned_memory_and_gives_it_back() {
1872 let text = shipped(concat!(
1873 "#include <mm_malloc.h>\n",
1874 "void *get(void) { return _mm_malloc(64, 16); }\n",
1875 "void put(void *p) { _mm_free(p); }\n",
1876 ));
1877 assert!(text.contains("get"), "{text}");
1878 assert!(text.contains("put"), "{text}");
1879 }
1880
1881 #[test]
1893 fn the_shipped_xmmintrin_defines_the_sse_type_and_the_operations_over_it() {
1894 let text = shipped(concat!(
1895 "#include <xmmintrin.h>\n",
1896 "__m128 add(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1897 "__m128 one(__m128 a, __m128 b) { return _mm_max_ss(a, b); }\n",
1898 "__m128 mask(__m128 a, __m128 b) { return _mm_cmpnle_ps(a, b); }\n",
1899 "__m128 pick(__m128 a, __m128 b) { return _mm_shuffle_ps(a, b, _MM_SHUFFLE(0,1,2,3)); }\n",
1900 "int bits(__m128 a) { return _mm_movemask_ps(a); }\n",
1901 "int near(__m128 a) { return _mm_cvtss_si32(a); }\n",
1902 "__m128 wide(__m64 a) { return _mm_cvtpi16_ps(a); }\n",
1903 "void *room(void) { return _mm_malloc(64, 16); }\n",
1904 "void hint(const float *p) { _mm_prefetch(p, _MM_HINT_T0); _mm_sfence(); }\n",
1905 ));
1906 assert!(text.contains("add"), "{text}");
1907 assert!(text.contains("mask"), "{text}");
1908 assert!(text.contains("pick"), "{text}");
1909 assert!(text.contains("wide"), "{text}");
1910 }
1911
1912 #[test]
1919 fn the_shipped_xmmintrin_leaves_out_the_names_that_need_an_instruction() {
1920 let text = rucc_session::runtime::header("xmmintrin.h").expect("xmmintrin.h is shipped");
1921 for absent in [
1922 "_mm_sqrt_ps",
1923 "_mm_sqrt_ss",
1924 "_mm_rsqrt_ps",
1925 "_mm_rsqrt_ss",
1926 "_mm_getcsr",
1927 "_mm_setcsr",
1928 ] {
1929 let defined = text.contains(&format!("{absent}("));
1930 assert!(!defined, "{absent} is defined and the header says it is not");
1931 assert!(text.contains(absent), "{absent} is absent and unexplained");
1932 }
1933 }
1934
1935 #[test]
1936 fn the_shipped_emmintrin_defines_both_sse2_types_and_the_operations_over_them() {
1937 let text = shipped(concat!(
1938 "#include <emmintrin.h>\n",
1939 "__m128i add(__m128i a, __m128i b) { return _mm_add_epi64(a, b); }\n",
1940 "__m128i wide(__m128i a, __m128i b) { return _mm_mul_epu32(a, b); }\n",
1941 "__m128i pick(__m128i a) { return _mm_shuffle_epi32(a, _MM_SHUFFLE(0,1,2,3)); }\n",
1942 "__m128i up(__m128i a) { return _mm_slli_epi64(a, 13); }\n",
1943 "__m128i down(__m128i a) { return _mm_srli_si128(a, 3); }\n",
1944 "__m128i pack(__m128i a, __m128i b) { return _mm_packus_epi16(a, b); }\n",
1945 "int bits(__m128i a) { return _mm_movemask_epi8(a); }\n",
1946 "__m128d sum(__m128d a, __m128d b) { return _mm_add_sd(a, b); }\n",
1947 "__m128d mask(__m128d a, __m128d b) { return _mm_cmpunord_pd(a, b); }\n",
1948 "__m128i near(__m128d a) { return _mm_cvtpd_epi32(a); }\n",
1949 "__m128d over(__m128 a) { return _mm_cvtps_pd(a); }\n",
1950 "__m128i half(__m64 a) { return _mm_movpi64_epi64(a); }\n",
1951 "__m128i grab(void const *p) { return _mm_loadu_si128(p); }\n",
1952 "void wall(void) { _mm_lfence(); _mm_mfence(); }\n",
1953 ));
1954 assert!(text.contains("wide"), "{text}");
1955 assert!(text.contains("pack"), "{text}");
1956 assert!(text.contains("near"), "{text}");
1957 assert!(text.contains("half"), "{text}");
1958 }
1959
1960 #[test]
1964 fn the_shipped_immintrin_reaches_the_names_the_headers_under_it_define() {
1965 let text = shipped(concat!(
1966 "#include <immintrin.h>\n",
1967 "unsigned long long matching(unsigned char tag, unsigned char const *bucket) {\n",
1968 " __m128i const want = _mm_set1_epi8((char)tag);\n",
1969 " __m128i const chunk = _mm_loadu_si128((__m128i const *)(void const *)bucket);\n",
1970 " __m128i const same = _mm_cmpeq_epi8(chunk, want);\n",
1971 " return (unsigned long long)_mm_movemask_epi8(same);\n",
1972 "}\n",
1973 "__m64 narrow(__m64 a, __m64 b) { return _mm_add_pi32(a, b); }\n",
1974 "__m128 single(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1975 ));
1976 assert!(text.contains("matching"), "{text}");
1977 assert!(text.contains("narrow"), "the MMX header is not reached: {text}");
1978 assert!(text.contains("single"), "the SSE header is not reached: {text}");
1979 }
1980
1981 #[test]
1985 fn the_shipped_x86intrin_reaches_the_fences_windows_headers_ask_it_for() {
1986 let text = shipped(concat!(
1987 "#include <x86intrin.h>\n",
1988 "void barriers(void *p) {\n",
1989 " _mm_lfence();\n",
1990 " _mm_sfence();\n",
1991 " _mm_mfence();\n",
1992 " _mm_pause();\n",
1993 " _mm_clflush(p);\n",
1994 "}\n",
1995 "__m128i wide(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
1996 ));
1997 assert!(text.contains("barriers"), "{text}");
1998 assert!(text.contains("wide"), "the SSE2 header is not reached: {text}");
1999 }
2000
2001 #[test]
2005 fn the_umbrella_and_the_header_under_it_can_both_be_included() {
2006 let text = shipped(concat!(
2007 "#include <immintrin.h>\n",
2008 "#include <emmintrin.h>\n",
2009 "#include <immintrin.h>\n",
2010 "#include <x86intrin.h>\n",
2011 "__m128i twice(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
2012 ));
2013 assert!(text.contains("twice"), "{text}");
2014 }
2015
2016 #[test]
2019 fn the_shipped_arm_neon_has_what_xxhash_asks_it_for() {
2020 let mut opts = freestanding();
2021 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2022 let source = concat!(
2023 "#include <arm_neon.h>\n",
2024 "uint64x2_t acc(uint64x2_t sum, const void *in, const void *key) {\n",
2025 " uint8x16_t data = vld1q_u8((const uint8_t *)in);\n",
2026 " uint8x16_t k = vld1q_u8((const uint8_t *)key);\n",
2027 " uint64x2_t mixed = vreinterpretq_u64_u8(veorq_u8(data, k));\n",
2028 " uint32x2_t lo = vmovn_u64(mixed);\n",
2029 " uint32x2_t hi = vshrn_n_u64(mixed, 32);\n",
2030 " return vmlal_u32(sum, lo, hi);\n",
2031 "}\n",
2032 "uint32x4x2_t pair(uint32x4_t a, uint32x4_t b) { return vzipq_u32(a, b); }\n",
2033 "uint32_t total(uint32x4_t a) { return vaddvq_u32(a); }\n",
2034 );
2035 let result = run(&opts, source);
2036 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2037 assert!(result.text().contains("pair"), "{}", result.text());
2038 assert!(result.text().contains("total"), "{}", result.text());
2039 }
2040
2041 #[test]
2043 fn the_shipped_arm_neon_refuses_another_target() {
2044 let result = run(&freestanding(), "#include <arm_neon.h>\n");
2045 let said = result.messages.join("\n");
2046 assert!(said.contains("arm_neon.h is for AArch64"), "{said}");
2047 }
2048
2049 #[test]
2053 fn the_shipped_emmintrin_leaves_out_the_two_square_roots() {
2054 let text = rucc_session::runtime::header("emmintrin.h").expect("emmintrin.h is shipped");
2055 for absent in ["_mm_sqrt_pd", "_mm_sqrt_sd"] {
2056 let defined = text.contains(&format!("{absent}("));
2057 assert!(!defined, "{absent} is defined and the header says it is not");
2058 assert!(text.contains(absent), "{absent} is absent and unexplained");
2059 }
2060 }
2061
2062 #[test]
2063 fn the_three_formality_headers_still_have_to_work() {
2064 let text = shipped(concat!(
2065 "#include <stdbool.h>\n",
2066 "#include <stdalign.h>\n",
2067 "#include <iso646.h>\n",
2068 "#include <stdnoreturn.h>\n",
2069 "int t = true and not false;\n",
2070 "_Alignas(16) char buf[16];\n",
2071 "int a = alignof(long);\n",
2072 ));
2073 assert!(text.contains("decl #0 t : int"), "{text}");
2074 assert!(text.contains("const 8 : unsigned long"), "{text}");
2075 }
2076
2077 #[test]
2085 fn every_shipped_header_can_be_included_twice() {
2086 let once: String = rucc_session::runtime::names()
2089 .iter()
2090 .filter(|name| **name != "arm_neon.h")
2091 .map(|name| format!("#include <{name}>\n"))
2092 .collect();
2093 let twice = once.repeat(2);
2094 assert_eq!(shipped(&format!("{once}int x;\n")), shipped(&format!("{twice}int x;\n")));
2095
2096 let mut opts = freestanding();
2097 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2098 let tree = |source: &str| {
2099 let result = run(&opts, source);
2100 assert_eq!(
2101 result.messages,
2102 Vec::<String>::new(),
2103 "expected this to compile:\n{source}"
2104 );
2105 result.text().to_owned()
2106 };
2107 let neon = "#include <arm_neon.h>\n";
2108 assert_eq!(tree(&format!("{neon}int x;\n")), tree(&format!("{neon}{neon}int x;\n")));
2109 }
2110
2111 #[test]
2112 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
2113 let fs = MemoryFileSystem::new();
2114 let result = compile(&options(), "/nope.c", &fs);
2115 assert!(result.failed());
2116 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
2117 assert!(result.text().is_empty());
2118 }
2119
2120 #[test]
2121 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
2122 let text = tast("int x = 1;\n");
2123 let expected = "\
2124decl #0 x : int object external static defined
2125 init
2126 +0
2127 const 1 : int
2128";
2129 assert_eq!(text, expected);
2130 }
2131
2132 #[test]
2133 fn the_macros_are_expanded_before_anything_is_parsed() {
2134 let text = tast("#define N 2\nint a[N];\n");
2138 assert!(text.starts_with("decl #0 a : int[2] object external static tentative"), "{text}");
2139 }
2140
2141 #[test]
2147 fn a_pragma_is_not_a_declaration_and_the_parse_walks_past_the_ones_it_does_not_read() {
2148 let text = tast(concat!(
2149 "#pragma pack(4)\n",
2150 "struct s { int a; };\n",
2151 "#pragma pack()\n",
2152 "int b;\n",
2153 "_Pragma(\"GCC visibility push(default)\") int c;\n",
2154 ));
2155 assert!(text.contains("decl #0 b : int"), "{text}");
2156 assert!(text.contains("decl #1 c : int"), "{text}");
2157 }
2158
2159 #[test]
2167 fn the_layout_attributes_move_the_members_and_the_record_the_way_gcc_lays_them_out() {
2168 tast(concat!(
2169 "struct A { char c; int i; } __attribute__((packed));\n",
2170 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2171 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2172 "struct B { char c; int i; } __attribute__((aligned));\n",
2175 "_Static_assert(sizeof(struct B) == 16 && _Alignof(struct B) == 16, \"B\");\n",
2176 "struct C { char c; int i __attribute__((packed)); };\n",
2177 "_Static_assert(sizeof(struct C) == 5 && _Alignof(struct C) == 1, \"C\");\n",
2178 "_Static_assert(__builtin_offsetof(struct C, i) == 1, \"C.i\");\n",
2179 "struct D { char c; int i; } __attribute__((packed, aligned(4)));\n",
2180 "_Static_assert(sizeof(struct D) == 8 && _Alignof(struct D) == 4, \"D\");\n",
2181 "_Static_assert(__builtin_offsetof(struct D, i) == 1, \"D.i\");\n",
2182 "struct E { char c; _Alignas(8) int i; };\n",
2183 "_Static_assert(sizeof(struct E) == 16 && _Alignof(struct E) == 8, \"E\");\n",
2184 "_Static_assert(__builtin_offsetof(struct E, i) == 8, \"E.i\");\n",
2185 "struct F { char c; int i __attribute__((aligned(8))); };\n",
2186 "_Static_assert(sizeof(struct F) == 16 && _Alignof(struct F) == 8, \"F\");\n",
2187 "struct G { char c; short s; } __attribute__((aligned(2)));\n",
2190 "_Static_assert(sizeof(struct G) == 4 && _Alignof(struct G) == 2, \"G\");\n",
2191 "struct H { char c; int i; } __attribute__((aligned(2)));\n",
2192 "_Static_assert(sizeof(struct H) == 8 && _Alignof(struct H) == 4, \"H\");\n",
2193 "struct I { [[gnu::packed]] char c; int i; };\n",
2196 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2197 "struct J { char c; [[gnu::packed]] int i; };\n",
2198 "_Static_assert(sizeof(struct J) == 5 && _Alignof(struct J) == 1, \"J\");\n",
2199 "struct M { char c; int i : 5; int j : 20; } __attribute__((packed));\n",
2200 "_Static_assert(sizeof(struct M) == 5 && _Alignof(struct M) == 1, \"M\");\n",
2201 "struct N { char c; long long l; } __attribute__((aligned(32)));\n",
2202 "_Static_assert(sizeof(struct N) == 32 && _Alignof(struct N) == 32, \"N\");\n",
2203 "union L { char c; int i; } __attribute__((packed));\n",
2204 "_Static_assert(sizeof(union L) == 4 && _Alignof(union L) == 1, \"L\");\n",
2205 "struct O { char c; int i; } __attribute__((__packed__));\n",
2209 "_Static_assert(sizeof(struct O) == 5 && _Alignof(struct O) == 1, \"O\");\n",
2210 "struct P { char c; int i; } __attribute__((__aligned__(8)));\n",
2211 "_Static_assert(sizeof(struct P) == 8 && _Alignof(struct P) == 8, \"P\");\n",
2212 ));
2213 }
2214
2215 #[test]
2228 fn a_transparent_union_takes_the_member_a_value_fits_and_is_declared_either_way() {
2229 let text = tast(concat!(
2230 "struct one { int x; };\n",
2231 "struct two { long y; };\n",
2232 "typedef union { struct one *a; struct two *b; void *any; }\n",
2233 " __attribute__((__transparent_union__)) arg;\n",
2234 "int takes(arg v);\n",
2235 "int f(struct one *p, struct two *q, char *c) {\n",
2236 " return takes(p) + takes(q) + takes(c) + takes(0);\n",
2237 "}\n",
2238 "int takes(struct one *p);\n",
2240 "int (*as_a_member)(struct one *) = takes;\n",
2241 "int (*as_the_union)(arg) = takes;\n",
2242 ));
2243 assert!(text.contains("compound-literal"), "{text}");
2244 }
2245
2246 #[test]
2252 fn the_attribute_on_the_declarator_of_a_typedef_is_the_one_glibc_writes() {
2253 let text = tast(concat!(
2254 "struct sockaddr { int family; };\n",
2255 "struct sockaddr_in { int family; int addr; };\n",
2256 "typedef union { struct sockaddr *plain; struct sockaddr_in *inet; }\n",
2257 " addr_arg __attribute__((__transparent_union__));\n",
2258 "int bind_to(int fd, addr_arg where);\n",
2259 "int f(struct sockaddr_in *where) { return bind_to(0, where); }\n",
2260 ));
2261 assert!(text.contains("compound-literal"), "{text}");
2262 }
2263
2264 #[test]
2272 fn a_transparent_union_that_cannot_keep_the_promise_is_dropped_with_a_word_about_it() {
2273 let result = run(
2274 &options(),
2275 concat!(
2276 "union wider { int small; double large; } __attribute__((transparent_union));\n",
2277 "struct plain { int x; } __attribute__((transparent_union));\n",
2278 ),
2279 );
2280 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
2281 assert!(!result.failed(), "{:?}", result.messages);
2282 for message in &result.messages {
2283 assert!(message.contains("'transparent_union' attribute ignored"), "{message}");
2284 }
2285 assert!(result.messages[0].contains("first member"), "{:?}", result.messages);
2286 assert!(result.messages[1].contains("only a union"), "{:?}", result.messages);
2287 }
2288
2289 #[test]
2298 fn an_access_to_a_packed_member_says_the_alignment_the_layout_left_it() {
2299 let packed = body(concat!(
2300 "struct P { char c; int v; } __attribute__((packed));\n",
2301 "int f(struct P *p) { return p->v; }\n",
2302 ));
2303 assert!(packed.contains("load.i32 %2, align 1,"), "{packed}");
2304 let plain = body(concat!(
2306 "struct P { char c; int v; };\n",
2307 "int f(struct P *p) { return p->v; }\n",
2308 ));
2309 assert!(plain.contains("load.i32 %2, align 4,"), "{plain}");
2310 }
2311
2312 #[test]
2319 fn what_is_inside_a_packed_member_is_no_more_aligned_than_the_member_is() {
2320 let stepped = body(concat!(
2321 "struct P { char c; int v[4]; } __attribute__((packed));\n",
2322 "int f(struct P *p, int i) { return p->v[i]; }\n",
2323 ));
2324 assert!(stepped.contains(", align 1,"), "{stepped}");
2325 assert!(!stepped.contains(", align 4,"), "{stepped}");
2326 let nested = body(concat!(
2327 "struct Inner { int v; };\n",
2328 "struct P { char c; struct Inner in; } __attribute__((packed));\n",
2329 "int f(struct P *p) { return p->in.v; }\n",
2330 ));
2331 assert!(nested.contains(", align 1,"), "{nested}");
2332 assert!(!nested.contains(", align 4,"), "{nested}");
2333 }
2334
2335 #[test]
2351 fn an_access_through_a_typedef_that_lowered_its_alignment_says_the_one_the_typedef_asked_for() {
2352 let through = body(concat!(
2353 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2354 "unsigned int f(const void *p) { return *(const unalign32 *)p; }\n",
2355 ));
2356 assert!(through.contains("load.i32 %0, align 1,"), "{through}");
2357 let stepped = body(concat!(
2360 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2361 "unsigned int f(unalign32 *p, int i) { return p[i]; }\n",
2362 ));
2363 assert!(stepped.contains(", align 1,"), "{stepped}");
2364 assert!(!stepped.contains(", align 4,"), "{stepped}");
2365 let plain = body(concat!(
2368 "typedef unsigned int word;\n",
2369 "unsigned int f(const void *p) { return *(const word *)p; }\n",
2370 ));
2371 assert!(plain.contains("load.i32 %0, align 4,"), "{plain}");
2372 }
2373
2374 #[test]
2385 fn a_vector_read_through_a_typedef_that_lowered_its_alignment_comes_back_a_piece_at_a_time() {
2386 let prefix = concat!(
2387 "typedef long long v2di __attribute__((__vector_size__(16)));\n",
2388 "typedef long long v2di_u __attribute__((__vector_size__(16), __aligned__(1)));\n",
2389 );
2390 let loaded =
2391 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di_u *)p; }}"));
2392 assert_eq!(loaded.matches("align 1\n").count(), 2, "{loaded}");
2393 assert!(!loaded.contains("align 16"), "{loaded}");
2394 let stored = body(&format!("{prefix}void f(void *p, v2di b) {{ *(v2di_u *)p = b; }}"));
2397 assert!(stored.contains("memcpy %0, %3, size 16, align 1"), "{stored}");
2398 let aligned =
2400 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di *)p; }}"));
2401 assert!(aligned.contains("align 16"), "{aligned}");
2402 }
2403
2404 #[test]
2413 fn the_aligned_attribute_on_a_declaration_raises_what_that_one_object_is_aligned_to() {
2414 tast(concat!(
2415 "int v __attribute__((aligned(64)));\n",
2416 "_Static_assert(__alignof__(v) == 64, \"v\");\n",
2417 "__attribute__((aligned(32))) int w;\n",
2420 "_Static_assert(__alignof__(w) == 32, \"w\");\n",
2421 "[[gnu::aligned(16)]] int x;\n",
2422 "_Static_assert(__alignof__(x) == 16, \"x\");\n",
2423 "int y __attribute__((aligned(2)));\n",
2426 "_Static_assert(__alignof__(y) == 4, \"y\");\n",
2427 "void f(void) { int a __attribute__((aligned(128)));\n",
2429 "_Static_assert(__alignof__(a) == 128, \"a\"); (void)a; }\n",
2430 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2433 "void g(void) __attribute__((aligned(256)));\n",
2436 "void g(void) {}\n",
2437 "_Static_assert(__alignof__(g) == 256, \"g\");\n",
2438 ));
2439 }
2440
2441 #[test]
2445 fn what_a_declaration_asked_to_be_aligned_to_is_what_the_assembler_is_told() {
2446 let text = asm(concat!(
2447 "int v __attribute__((aligned(64)));\n",
2448 "void g(void) __attribute__((aligned(256)));\n",
2449 "void g(void) {}\n",
2450 "void plain(void) {}\n",
2451 ));
2452 assert!(text.contains("\t.p2align\t6\n\t.type\tv, @object\n"), "{text}");
2453 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2454 assert!(text.contains("\t.p2align\t4, 0x90\n\t.globl\tplain\n"), "{text}");
2455 }
2456
2457 #[test]
2463 fn the_alignment_the_command_line_asked_of_every_function_is_a_floor_under_all_of_them() {
2464 let source = concat!(
2465 "void g(void) __attribute__((aligned(256)));\n",
2466 "void g(void) {}\n",
2467 "void small(void) __attribute__((aligned(4)));\n",
2468 "void small(void) {}\n",
2469 "void plain(void) {}\n",
2470 );
2471 let listing = |align: Option<u32>| {
2472 let mut opts = options();
2473 opts.emit = EmitKind::Asm;
2474 opts.align_functions = align;
2475 let result = run(&opts, source);
2476 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
2477 result.text().to_owned()
2478 };
2479
2480 let text = listing(Some(32));
2481 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "the larger one wins: {text}");
2482 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tsmall\n"), "{text}");
2483 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tplain\n"), "{text}");
2484
2485 let text = listing(Some(8));
2488 assert!(text.contains("\t.p2align\t3, 0x90\n\t.globl\tplain\n"), "{text}");
2489 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2490 }
2491
2492 #[test]
2501 fn an_aligned_typedef_says_what_an_object_of_it_is_aligned_to_and_may_lower_it() {
2502 tast(concat!(
2503 "typedef int L __attribute__((aligned(2)));\n",
2504 "_Static_assert(__alignof__(L) == 2, \"L\");\n",
2505 "_Static_assert(_Alignof(L) == 2, \"L alignof\");\n",
2506 "_Static_assert(sizeof(L) == 4, \"L size\");\n",
2508 "struct T { char c; L x; };\n",
2509 "_Static_assert(sizeof(struct T) == 6, \"T\");\n",
2510 "_Static_assert(__builtin_offsetof(struct T, x) == 2, \"T.x\");\n",
2511 "typedef int H __attribute__((aligned(16)));\n",
2513 "_Static_assert(__alignof__(H) == 16, \"H\");\n",
2514 "_Static_assert(sizeof(H) == 4, \"H size\");\n",
2515 "struct U { char c; H x; };\n",
2516 "_Static_assert(sizeof(struct U) == 32, \"U\");\n",
2517 "_Static_assert(__builtin_offsetof(struct U, x) == 16, \"U.x\");\n",
2518 "typedef L M __attribute__((aligned(8)));\n",
2521 "_Static_assert(__alignof__(M) == 8, \"M\");\n",
2522 "typedef L N;\n",
2525 "_Static_assert(__alignof__(N) == 2, \"N\");\n",
2526 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2528 ));
2529 let text = asm(concat!(
2530 "typedef int L __attribute__((aligned(2)));\n",
2531 "typedef int H __attribute__((aligned(16)));\n",
2532 "L low;\n",
2533 "H high;\n",
2534 ));
2535 assert!(text.contains("\t.p2align\t1\n\t.type\tlow, @object\n"), "{text}");
2536 assert!(text.contains("\t.p2align\t4\n\t.type\thigh, @object\n"), "{text}");
2537 }
2538
2539 #[test]
2547 fn the_vector_size_attribute_builds_a_type_of_lanes_and_measures_it_in_bytes() {
2548 tast(concat!(
2549 "typedef int __attribute__((vector_size(16))) v4si;\n",
2550 "_Static_assert(sizeof(v4si) == 16 && _Alignof(v4si) == 16, \"v4si\");\n",
2551 "typedef char __attribute__((vector_size(16))) v16qi;\n",
2552 "_Static_assert(sizeof(v16qi) == 16, \"v16qi\");\n",
2553 "typedef int __attribute__((vector_size(4))) v1si;\n",
2556 "_Static_assert(sizeof(v1si) == 4, \"v1si\");\n",
2557 "typedef float __attribute__((__vector_size__(8))) v2sf;\n",
2559 "_Static_assert(sizeof(v2sf) == 8, \"v2sf\");\n",
2560 "typedef short [[gnu::vector_size(8)]] v4hi;\n",
2561 "_Static_assert(sizeof(v4hi) == 8, \"v4hi\");\n",
2562 "v4si g;\n",
2565 "_Static_assert(sizeof(g[0]) == 4, \"lane\");\n",
2566 "_Static_assert(sizeof(g + g) == 16, \"whole\");\n",
2567 "_Static_assert(sizeof(g + 1) == 16, \"broadcast\");\n",
2570 "_Static_assert(sizeof(v4si[3]) == 48, \"array\");\n",
2572 ));
2573 }
2574
2575 #[test]
2585 fn a_vector_is_written_whole_into_an_array_of_them_and_named_by_a_type_name() {
2586 tast(concat!(
2587 "typedef int __attribute__((vector_size(8))) v2si;\n",
2588 "v2si table[] = { (v2si){ 1, 2 }, (v2si){ 3, 4 } };\n",
2589 "_Static_assert(sizeof(table) == 16, \"two of them and not eight lanes\");\n",
2590 "v2si written = (int __attribute__((vector_size(8)))){ 5, 6 };\n",
2592 "_Static_assert(sizeof((int __attribute__((vector_size(16)))){ 0 }) == 16, \"named\");\n",
2593 "v2si lanes[2] = { 1, 2, 3, 4 };\n",
2596 "_Static_assert(sizeof(lanes) == 16, \"still elided\");\n",
2597 ));
2598 }
2599
2600 #[test]
2608 fn a_lane_is_assignable_and_a_shift_takes_a_count_of_its_own_lane() {
2609 let result = run(
2610 &options(),
2611 concat!(
2612 "typedef int __attribute__((vector_size(16))) v4si;\n",
2613 "typedef unsigned __attribute__((vector_size(16))) v4ui;\n",
2614 "void write(v4si *out, v4ui a, v4si b, int n) {\n",
2615 " v4si v = { 1, 2, 3, 4 };\n",
2616 " v[0] = n;\n",
2617 " v[1] += n;\n",
2618 " v[2]++;\n",
2619 " *&v[3] = n;\n",
2620 " v4ui shifted = a >> b;\n",
2622 " shifted <<= b;\n",
2623 " *out = v + (v4si)shifted + (1 << b);\n",
2626 "}\n",
2627 "void refused(const v4si c) {\n",
2630 " c[0] = 1;\n",
2631 "}\n",
2632 ),
2633 );
2634 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
2635 assert!(result.messages[0].contains("assignment of read-only"), "{:?}", result.messages);
2636 }
2637
2638 #[test]
2650 fn a_record_that_asks_for_the_other_byte_order_swaps_every_scalar_it_holds() {
2651 let read = "int f(struct s *p) { return p->i; }\n";
2652 let big = "struct s { int i; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
2653 assert!(body(&format!("{big}{read}")).contains("bswap"), "{big}");
2654
2655 let armoured =
2656 "struct s { int i; } __attribute__((__scalar_storage_order__(\"big-endian\")));\n";
2657 assert!(body(&format!("{armoured}{read}")).contains("bswap"), "{armoured}");
2658
2659 let front = "struct __attribute__((scalar_storage_order(\"big-endian\"))) s { int i; };\n";
2660 assert!(body(&format!("{front}{read}")).contains("bswap"), "{front}");
2661
2662 let standard = "struct s { int i; } [[gnu::scalar_storage_order(\"big-endian\")]];\n";
2663 assert!(body(&format!("{standard}{read}")).contains("bswap"), "{standard}");
2664
2665 let same =
2666 "struct s { int i; } __attribute__((scalar_storage_order(\"little-endian\")));\n";
2667 assert!(!body(&format!("{same}{read}")).contains("bswap"), "{same}");
2668
2669 let byte = "struct s { char c; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
2671 let source = format!("{byte}int f(struct s *p) {{ return p->c; }}\n");
2672 assert!(!body(&source).contains("bswap"), "{byte}");
2673
2674 tast(concat!(
2675 "struct s { int i; short h; char c; }",
2676 " __attribute__((scalar_storage_order(\"big-endian\")));\n",
2677 "_Static_assert(sizeof(struct s) == 8 && _Alignof(struct s) == 4, \"s\");\n",
2678 "_Static_assert(__builtin_offsetof(struct s, h) == 4, \"s.h\");\n",
2679 "_Static_assert(__builtin_offsetof(struct s, c) == 6, \"s.c\");\n",
2680 ));
2681 }
2682
2683 #[test]
2689 fn a_bit_field_in_one_of_those_records_is_counted_from_the_top_of_its_bytes() {
2690 let members = "short i : 12; char c1 : 1; char c2 : 1; char c3 : 1; char c4 : 1;";
2691 let read = "int f(struct s *p) { return p->i; }\n";
2692 let plain = format!("struct s {{ {members} }};\n{read}");
2693 let reversed = format!(
2694 "struct s {{ {members} }} __attribute__((scalar_storage_order(\"big-endian\")));\n\
2695 {read}"
2696 );
2697 assert!(body(&plain).contains("shl"), "{}", body(&plain));
2698 assert!(!body(&plain).contains("bswap"), "{}", body(&plain));
2699 let built = body(&reversed);
2702 assert!(built.contains("bswap"), "{built}");
2703 assert!(!built.contains("shl"), "{built}");
2704 assert!(built.contains("ashr"), "{built}");
2705 }
2706
2707 #[test]
2714 fn the_address_of_a_scalar_stored_the_other_way_round_is_refused() {
2715 let opts = options();
2716 let record = "struct s { int i; int a[2]; struct in { int n; } w; }\n\
2717 __attribute__((scalar_storage_order(\"big-endian\")));\n";
2718 let taken = format!("{record}int *f(struct s *p) {{ return &p->i; }}\n");
2719 assert_eq!(
2720 run(&opts, &taken).messages,
2721 ["/main.c:3:30: error: cannot take address of scalar with reverse storage order \
2722 [E0712]"]
2723 );
2724 let element = format!("{record}int *f(struct s *p) {{ return &p->a[0]; }}\n");
2725 let messages = run(&opts, &element).messages;
2726 assert!(messages[0].contains("[E0712]"), "{messages:?}");
2727
2728 let whole = format!("{record}int *f(struct s *p) {{ return (int *) &p->w; }}\n");
2729 assert_eq!(run(&opts, &whole).messages, Vec::<String>::new(), "{whole}");
2730 }
2731
2732 #[test]
2736 fn a_storage_order_that_names_neither_end_is_refused_with_the_two_words_that_are_taken() {
2737 let opts = options();
2738 let wrong = "struct s { int i; } __attribute__((scalar_storage_order(\"middle\")));\n";
2739 assert_eq!(
2740 run(&opts, wrong).messages,
2741 ["/main.c:1:36: error: 'scalar_storage_order' argument must be one of \"big-endian\" \
2742 or \"little-endian\" [E0688]"]
2743 );
2744 let bare = "struct s { int i; } __attribute__((scalar_storage_order));\n";
2745 let messages = run(&opts, bare).messages;
2746 assert!(messages[0].contains("[E0688]"), "{messages:?}");
2747 }
2748
2749 #[test]
2759 fn packing_is_what_decides_whether_a_bit_field_may_straddle_its_own_storage() {
2760 assert_eq!(bit_field_byte("struct s { int x : 12; char y : 6; };"), 2);
2762 assert_eq!(
2763 bit_field_byte("struct s { int x : 12; char y : 6; } __attribute__((packed));"),
2764 1
2765 );
2766 assert_eq!(
2767 bit_field_byte("struct s { int x : 12; __attribute__((packed)) char y : 6; };"),
2768 1
2769 );
2770 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { int x : 12; char y : 6; };"), 1);
2771 assert_eq!(bit_field_byte("struct s { char x; int y : 30; };"), 4);
2773 assert_eq!(bit_field_byte("struct s { char x; int y : 30; } __attribute__((packed));"), 1);
2774 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { char x; int y : 30; };"), 1);
2776 assert_eq!(bit_field_byte("#pragma pack(2)\nstruct s { char x; int y : 30; };"), 1);
2777 }
2778
2779 fn bit_field_byte(record: &str) -> u64 {
2781 let source = format!("{record}\nint f(struct s *p) {{ return p->y; }}\n");
2782 let body = body(&source);
2783 let Some((before, _)) = body.split_once("ptr_add") else { return 0 };
2784 let (_, constant) = before.rsplit_once("iconst.i64 ").expect("an offset constant");
2785 constant.lines().next().expect("a line").trim().parse().expect("a byte offset")
2786 }
2787
2788 #[test]
2794 fn an_attribute_among_the_specifiers_is_kept_beside_the_ones_written_in_front() {
2795 tast(concat!(
2796 "struct a { char c; __attribute__((aligned(8))) int i; };\n",
2797 "_Static_assert(sizeof(struct a) == 16 && _Alignof(struct a) == 8, \"a\");\n",
2798 "_Static_assert(__builtin_offsetof(struct a, i) == 8, \"a.i\");\n",
2799 "struct b { char c; __attribute__((packed)) int i; };\n",
2800 "_Static_assert(sizeof(struct b) == 5 && _Alignof(struct b) == 1, \"b\");\n",
2801 "_Static_assert(__builtin_offsetof(struct b, i) == 1, \"b.i\");\n",
2802 "typedef struct { char c; int i; } __attribute__((packed)) c;\n",
2803 "_Static_assert(sizeof(c) == 5 && _Alignof(c) == 1, \"c\");\n",
2804 ));
2805 }
2806
2807 #[test]
2813 fn pragma_pack_caps_every_member_and_is_read_where_the_body_closes() {
2814 tast(concat!(
2815 "#pragma pack(1)\n",
2816 "struct A { char c; int i; };\n",
2817 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2818 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2819 "#pragma pack()\n",
2820 "struct B { char c; int i; };\n",
2821 "_Static_assert(sizeof(struct B) == 8 && _Alignof(struct B) == 4, \"B\");\n",
2822 "#pragma pack(2)\n",
2823 "struct C { char c; int i; double d; };\n",
2824 "_Static_assert(sizeof(struct C) == 14 && _Alignof(struct C) == 2, \"C\");\n",
2825 "_Static_assert(__builtin_offsetof(struct C, d) == 6, \"C.d\");\n",
2826 "struct K { char c; int i __attribute__((aligned(8))); };\n",
2828 "_Static_assert(sizeof(struct K) == 6 && _Alignof(struct K) == 2, \"K\");\n",
2829 "_Static_assert(__builtin_offsetof(struct K, i) == 2, \"K.i\");\n",
2830 "struct J { char c; int i; } __attribute__((aligned(8)));\n",
2832 "_Static_assert(sizeof(struct J) == 8 && _Alignof(struct J) == 8, \"J\");\n",
2833 "#pragma pack()\n",
2834 "#pragma pack(push, 1)\n",
2835 "struct D { char c; short s; };\n",
2836 "_Static_assert(sizeof(struct D) == 3 && _Alignof(struct D) == 1, \"D\");\n",
2837 "#pragma pack(pop)\n",
2838 "struct E { char c; short s; };\n",
2839 "_Static_assert(sizeof(struct E) == 4 && _Alignof(struct E) == 2, \"E\");\n",
2840 "struct H { char c;\n",
2842 "#pragma pack(1)\n",
2843 " int i; };\n",
2844 "_Static_assert(sizeof(struct H) == 5 && _Alignof(struct H) == 1, \"H\");\n",
2845 "#pragma pack(1)\n",
2846 "struct I { char c;\n",
2847 "#pragma pack()\n",
2848 " int i; };\n",
2849 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2850 "#pragma pack()\n",
2851 "#pragma pack(push, 8)\n",
2853 "#pragma pack(push, 1)\n",
2854 "struct P { char c; int i; };\n",
2855 "_Static_assert(sizeof(struct P) == 5 && _Alignof(struct P) == 1, \"P\");\n",
2856 "#pragma pack(pop)\n",
2857 "struct Q { char c; int i; };\n",
2858 "_Static_assert(sizeof(struct Q) == 8 && _Alignof(struct Q) == 4, \"Q\");\n",
2859 "#pragma pack(pop)\n",
2860 "#pragma pack(16)\n",
2862 "struct R { char c; int i; };\n",
2863 "_Static_assert(sizeof(struct R) == 8 && _Alignof(struct R) == 4, \"R\");\n",
2864 "#pragma pack()\n",
2865 "#pragma pack(1)\n",
2866 "struct S { char c; int i : 5; int j : 20; };\n",
2867 "_Static_assert(sizeof(struct S) == 5 && _Alignof(struct S) == 1, \"S\");\n",
2868 "union T { char c; int i; };\n",
2869 "_Static_assert(sizeof(union T) == 4 && _Alignof(union T) == 1, \"T\");\n",
2870 "#pragma pack()\n",
2871 ));
2872 }
2873
2874 #[test]
2878 fn a_pack_line_that_is_not_one_is_reported_in_the_words_gcc_uses() {
2879 let result = run(
2880 &options(),
2881 concat!(
2882 "#pragma pack 4\n",
2883 "#pragma pack(pop)\n",
2884 "#pragma pack(3)\n",
2885 "#pragma pack(1) junk\n",
2886 "#pragma pack(push, 1\n",
2887 "#pragma pack(x)\n",
2888 "#pragma pack(0)\n",
2891 "#pragma pack(push)\n",
2892 "struct s { char c; int i; };\n",
2893 "#pragma pack(pop)\n",
2894 "#pragma pack(pop, foo)\n",
2895 ),
2896 );
2897 let expected = [
2898 "missing `(` after `#pragma pack` - ignored",
2899 "`#pragma pack (pop)` encountered without matching `#pragma pack (push)`",
2900 "alignment must be a small power of two, not 3",
2901 "junk at end of `#pragma pack`",
2902 "malformed `#pragma pack(push[, id][, <n>])` - ignored",
2903 "unknown action `x` for `#pragma pack` - ignored",
2904 "`#pragma pack(pop, foo)` encountered without matching `#pragma pack(push, foo)`",
2905 ];
2906 assert_eq!(result.messages.len(), expected.len(), "{:?}", result.messages);
2907 for (message, want) in result.messages.iter().zip(expected) {
2908 assert!(message.contains(want), "expected {want:?} in {message:?}");
2909 }
2910 }
2911
2912 #[test]
2919 fn a_declaration_behind_an_empty_macro_is_not_eaten_by_the_pragma_above_it() {
2920 let result = run(
2921 &options(),
2922 concat!(
2923 "#pragma pack(push, 1)\n",
2924 "#pragma pack(pop)\n",
2925 "#define API\n",
2926 "API const char version[] = \"3.53.4\";\n",
2927 "const char *get(void) { return version; }\n",
2928 ),
2929 );
2930 assert!(result.messages.is_empty(), "{:?}", result.messages);
2931 }
2932
2933 #[test]
2937 fn the_wide_integer_answers_to_all_three_of_its_names() {
2938 let text = tast("__uint128_t a; __int128_t b; unsigned __int128 c;\n");
2939 assert!(text.contains("decl #0 a : unsigned __int128"), "{text}");
2940 assert!(text.contains("decl #1 b : __int128"), "{text}");
2941 assert!(text.contains("decl #2 c : unsigned __int128"), "{text}");
2942 }
2943
2944 #[test]
2945 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
2946 let text = tast("long f(int a, long b) { return a + b; }\n");
2950 assert!(text.contains("convert arithmetic"), "{text}");
2951 }
2952
2953 #[test]
2954 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
2955 for source in [
2956 "#error stop\n",
2957 "int f(void) { return 1 + ; }\n",
2958 "int f(void) { return undeclared; }\n",
2959 ] {
2960 let result = run(&options(), source);
2961 assert!(result.failed(), "expected this to fail:\n{source}");
2962 assert!(
2963 result.text().is_empty(),
2964 "a file that did not compile wrote a tree:\n{source}"
2965 );
2966 }
2967 }
2968
2969 #[test]
2970 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
2971 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
2975 assert_eq!(result.errors, 1, "{:?}", result.messages);
2976 }
2977
2978 #[test]
2979 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
2980 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
2984 assert_eq!(result.errors, 1, "{:?}", result.messages);
2985 }
2986
2987 #[test]
2988 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
2989 let source = "int f(void) { char c = 300; return c; }\n";
2990 let plain = run(&options(), source);
2991 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
2992 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
2993 assert!(!plain.text().is_empty(), "a warning is not a reason to write nothing");
2994
2995 let mut opts = options();
2996 opts.warnings_are_errors = true;
2997 let strict = run(&opts, source);
2998 assert!(strict.failed());
2999 assert!(strict.text().is_empty(), "and under -Werror it is a reason to write nothing");
3000 for message in &strict.messages {
3001 assert!(!message.contains("warning:"), "{message}");
3002 }
3003 }
3004
3005 #[test]
3006 fn w_drops_the_warning_before_werror_can_promote_it() {
3007 let source = "int f(void) { char c = 300; return c; }\n";
3008 let mut opts = options();
3009 opts.warnings = false;
3010 let quiet = run(&opts, source);
3011 assert_eq!(quiet.messages, Vec::<String>::new());
3012 assert_eq!(quiet.errors, 0);
3013 assert!(!quiet.text().is_empty(), "and the file still compiles");
3014
3015 opts.warnings_are_errors = true;
3018 let both = run(&opts, source);
3019 assert_eq!(both.messages, Vec::<String>::new());
3020 assert!(!both.failed(), "-w -Werror is not an error about a warning nobody saw");
3021 }
3022
3023 #[test]
3024 fn the_dialect_reaches_the_keywords_and_the_checking() {
3025 let source = "typeof(1) x;\n";
3028 let mut opts = options();
3029 opts.std = Std::C23;
3030 opts.gnu_extensions = false;
3031 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
3032
3033 opts.std = Std::C17;
3034 assert!(run(&opts, source).failed());
3035 }
3036
3037 #[test]
3038 fn asking_for_a_kind_that_is_not_written_yet_runs_the_front_end_and_writes_nothing() {
3039 let mut opts = options();
3040 opts.emit = EmitKind::Object;
3041 let result = run(&opts, "int x = 1;\n");
3042 assert!(!result.failed(), "{:?}", result.messages);
3043 assert!(result.text().is_empty());
3044 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
3047 }
3048
3049 fn mir(source: &str) -> String {
3051 let mut opts = options();
3052 opts.emit = EmitKind::MirFinal;
3053 let result = run(&opts, source);
3054 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3055 result.text().to_owned()
3056 }
3057
3058 #[test]
3064 fn a_function_goes_from_c_to_instructions_with_real_registers_in_them() {
3065 let text = mir("int add(int a, int b) { return a + b; }\n");
3066 assert!(text.starts_with("mfunc @add {"), "{text}");
3067 assert!(text.contains("x64.add_rr_32"), "{text}");
3068 assert!(text.contains("x64.ret"), "{text}");
3069 assert!(!text.contains('%'), "{text}");
3072 }
3073
3074 #[test]
3076 fn a_function_with_no_body_produces_no_machine_function() {
3077 let text = mir("int g(int);\nint f(int a) { return g(a); }\n");
3078 assert_eq!(text.matches("mfunc @").count(), 1, "{text}");
3079 assert!(text.contains("mfunc @f {"), "{text}");
3080 assert!(text.contains("x64.call"), "{text}");
3081 }
3082
3083 #[test]
3085 fn every_definition_in_the_file_is_generated_and_they_keep_their_order() {
3086 let text = mir("int a(int x) { return x; }\nint b(int x) { return x; }\n");
3087 let first = text.find("mfunc @a").expect("the first function");
3088 let second = text.find("mfunc @b").expect("the second function");
3089 assert!(first < second, "{text}");
3090 }
3091
3092 #[test]
3094 fn the_target_decides_which_convention_the_generated_code_follows() {
3095 let mut opts = options();
3096 opts.emit = EmitKind::MirFinal;
3097 let linux = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
3098 assert!(linux.contains("$rdi"), "{linux}");
3099
3100 opts.target = "x86_64-pc-windows-msvc".parse::<Triple>().unwrap();
3101 let windows = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
3102 assert!(windows.contains("$rcx"), "{windows}");
3103 assert!(!windows.contains("$rdi"), "{windows}");
3104 }
3105
3106 #[test]
3114 fn a_tagged_member_with_no_name_is_a_member_on_windows_and_nothing_on_linux() {
3115 let source = concat!(
3116 "struct S { union U { int i; void *p; }; unsigned long tymed; };\n",
3117 "int size(void) { return sizeof(struct S); }\n",
3118 "int f(struct S *s) { s->i = 1; return s->i; }\n",
3119 );
3120
3121 let mut opts = options();
3122 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3123 let windows = run(&opts, source);
3124 assert!(windows.messages.is_empty(), "{:?}", windows.messages);
3125
3126 let linux = run(&options(), source);
3127 assert_eq!(linux.messages.len(), 3, "{:?}", linux.messages);
3128 assert!(linux.messages[0].contains("does not declare anything"), "{:?}", linux.messages);
3129
3130 let mut opts = options();
3133 opts.ms_extensions = Some(true);
3134 let asked = run(&opts, source);
3135 assert!(asked.messages.is_empty(), "{:?}", asked.messages);
3136 }
3137
3138 #[test]
3140 fn a_target_this_has_no_back_end_for_is_reported_rather_than_generated() {
3141 let mut opts = options();
3142 opts.emit = EmitKind::MirFinal;
3143 opts.target = "riscv64-unknown-linux-gnu".parse::<Triple>().unwrap();
3144 let result = run(&opts, "int f(int a) { return a; }\n");
3145 assert!(result.failed());
3146 assert!(result.messages[0].contains("no back end for riscv64"), "{:?}", result.messages);
3147 assert!(result.text().is_empty());
3148 }
3149
3150 #[test]
3153 fn an_aarch64_target_is_written_as_aarch64_assembly() {
3154 let mut opts = options();
3155 opts.emit = EmitKind::Asm;
3156 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3157 let source = "int g(int);\nint f(int a, int b) { return g(a) + b; }\n";
3158 let result = run(&opts, source);
3159 assert!(!result.failed(), "{:?}", result.messages);
3160 let text = result.text();
3161 for line in ["stp x29, x30, [sp, #-16]!", "mov x29, sp", "bl g", "ldp x29, x30, [sp], #16"]
3162 {
3163 assert!(text.contains(line), "{line} is not in\n{text}");
3164 }
3165 assert!(!text.contains('%'), "{text}");
3166 }
3167
3168 #[test]
3171 fn an_aarch64_target_reaches_an_object_file() {
3172 let mut opts = options();
3173 opts.emit = EmitKind::Object;
3174 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3175 let source = concat!(
3176 "int g(int);\n",
3177 "int table[4] = {1, 2, 3, 4};\n",
3178 "int f(int a, int b) { return g(a) + table[b & 3]; }\n",
3179 );
3180 let result = run(&opts, source);
3181 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3182 let bytes = match result.artifact {
3183 Artifact::Object { bytes, defines } => {
3184 assert_eq!(defines, ["f", "table"]);
3185 bytes
3186 }
3187 other => panic!("expected an object, got {other:?}"),
3188 };
3189 assert_eq!(&bytes[..4], b"\x7fELF");
3190 assert_eq!(&bytes[18..20], &183u16.to_le_bytes(), "EM_AARCH64");
3191
3192 opts.debug_info = true;
3195 let result = run(&opts, source);
3196 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3197 let bytes = match result.artifact {
3198 Artifact::Object { bytes, .. } => bytes,
3199 other => panic!("expected an object, got {other:?}"),
3200 };
3201 let has = |name: &[u8]| bytes.windows(name.len()).any(|at| at == name);
3202 assert!(has(b".debug_line\0") && has(b".debug_info\0"));
3203 assert!(!has(b"rucc_row"), "a row label reached the symbol table");
3204 }
3205
3206 #[test]
3209 fn the_aarch64_vector_type_names_are_declared_on_that_target_and_nowhere_else() {
3210 let mut opts = options();
3211 opts.emit = EmitKind::Asm;
3212 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3213 let source = "typedef __Float32x4_t f4;\n__SVFloat32_t sv(__SVFloat32_t, __SVBool_t);\n\
3214 int n = sizeof(f4) + sizeof(__Int8x8_t);\n\
3215 int f(f4 v) { int __Uint8x16_t = 3; return v[1] + __Uint8x16_t; }\n";
3216 let result = run(&opts, source);
3217 assert!(!result.failed(), "{:?}", result.messages);
3218 assert!(result.text().contains(".long\t24"), "{}", result.text());
3219 opts.target = "x86_64-unknown-linux-gnu".parse::<Triple>().unwrap();
3220 let result = run(&opts, "typedef __Float32x4_t f4;\n");
3221 assert!(result.failed());
3222 let result = run(&opts, "int __Float32x4_t = 1;\n");
3223 assert!(!result.failed(), "{:?}", result.messages);
3224 }
3225
3226 #[test]
3229 fn an_aarch64_result_in_memory_is_reached_through_x8() {
3230 let mut opts = options();
3231 opts.emit = EmitKind::Asm;
3232 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3233 let source = "struct big { long a, b, c; };\nstruct big make(long v);\n\
3234 long f(long v) { return make(v).c; }\n\
3235 struct big g(long v) { struct big b = { v, v, v }; return b; }\n";
3236 let result = run(&opts, source);
3237 assert!(!result.failed(), "{:?}", result.messages);
3238 let text = result.text();
3239 assert!(text.contains("x8"), "{text}");
3240 assert!(text.contains("bl make"), "{text}");
3241 }
3242
3243 #[test]
3246 fn an_aarch64_remainder_is_a_division_and_a_multiply_subtract() {
3247 let mut opts = options();
3248 opts.emit = EmitKind::Asm;
3249 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3250 let source = "int s(int a, int b) { return a % b; }\n\
3251 unsigned long u(unsigned long a, unsigned long b) { return a % b; }\n";
3252 let result = run(&opts, source);
3253 assert!(!result.failed(), "{:?}", result.messages);
3254 let text = result.text();
3255 let at = |what: &str| text.find(what).unwrap_or_else(|| panic!("{what} is not in\n{text}"));
3256 assert!(at("sdiv w") < at("msub w"), "{text}");
3257 assert!(at("udiv x") < at("msub x"), "{text}");
3258 }
3259
3260 #[test]
3263 fn an_aarch64_jump_table_is_reached_with_adr() {
3264 let mut opts = options();
3265 opts.emit = EmitKind::Asm;
3266 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3267 let source = "int f(int x) { switch (x) { case 0: return 10; case 1: return 21; \
3268 case 2: return 32; case 3: return 43; case 4: return 54; case 5: return 65; \
3269 case 6: return 76; case 7: return 87; case 8: return 98; case 9: return 9; \
3270 case 10: return 19; case 11: return 29; default: return 0; } }\n";
3271 let result = run(&opts, source);
3272 assert!(!result.failed(), "{:?}", result.messages);
3273 let text = result.text();
3274 let at = |what: &str| text.find(what).unwrap_or_else(|| panic!("{what} is not in\n{text}"));
3275 assert!(at("adr x") < at("ldrsw x"), "{text}");
3276 assert!(at("ldrsw x") < at("br x"), "{text}");
3277 assert!(text.contains("_j0:"), "{text}");
3278 assert!(text.contains(".long"), "{text}");
3279 }
3280
3281 #[test]
3285 fn an_aarch64_va_start_writes_the_five_fields_of_its_list() {
3286 let mut opts = options();
3287 opts.emit = EmitKind::Asm;
3288 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3289 let source = "typedef __builtin_va_list va_list;\n\
3290 int f(int n, ...) { va_list ap; __builtin_va_start(ap, n); \
3291 int x = __builtin_va_arg(ap, int); double d = __builtin_va_arg(ap, double); \
3292 __builtin_va_end(ap); return x + (int)d; }\n";
3293 let result = run(&opts, source);
3294 assert!(!result.failed(), "{:?}", result.messages);
3295 let text = result.text();
3296 assert!(text.contains("#-56"), "{text}");
3297 assert!(text.contains("#-128"), "{text}");
3298 assert!(text.contains("#24]"), "{text}");
3299 assert!(text.contains("#28]"), "{text}");
3300 assert!(text.contains("str q"), "{text}");
3301 }
3302
3303 #[test]
3306 fn an_aarch64_long_double_is_a_quad_in_a_vector_register() {
3307 let mut opts = options();
3308 opts.emit = EmitKind::Asm;
3309 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3310 let source = "void f(long double *p, long double x) { *p = *p + x; }\n";
3311 let result = run(&opts, source);
3312 assert!(!result.failed(), "{:?}", result.messages);
3313 let text = result.text();
3314 assert!(text.contains("ldr q"), "{text}");
3315 assert!(text.contains("str q"), "{text}");
3316 assert!(text.contains("__addtf3"), "{text}");
3317 }
3318
3319 #[test]
3322 fn an_aarch64_thread_local_is_reached_through_tpidr_el0() {
3323 let mut opts = options();
3324 opts.emit = EmitKind::Asm;
3325 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3326 let source = "__thread int n;\nint *f(void) { return &n; }\n\
3327 void *g(void) { return __builtin_thread_pointer(); }\n";
3328 let result = run(&opts, source);
3329 assert!(!result.failed(), "{:?}", result.messages);
3330 let text = result.text();
3331 assert!(text.contains(":gottprel:n"), "{text}");
3332 assert!(text.contains(":gottprel_lo12:n]"), "{text}");
3333 assert_eq!(text.matches("mrs x").count(), 2, "{text}");
3334 assert!(text.contains("tpidr_el0"), "{text}");
3335 }
3336
3337 #[test]
3340 fn a_darwin_thread_local_is_reached_through_its_descriptor() {
3341 let source = "__thread int n = 5;\nint *f(void) { return &n; }\n";
3342 for (triple, wanted) in [
3343 ("aarch64-apple-darwin", &["_n@TLVPPAGE\n", "_n@TLVPPAGEOFF]\n", "\tblr x"][..]),
3344 ("x86_64-apple-darwin", &["_n@TLVP(%rip), %rdi\n", "\tcall\t*%"][..]),
3345 ] {
3346 let mut opts = options();
3347 opts.emit = EmitKind::Asm;
3348 opts.target = triple.parse::<Triple>().unwrap();
3349 let result = run(&opts, source);
3350 assert!(!result.failed(), "{triple}: {:?}", result.messages);
3351 let text = result.text();
3352 for want in wanted {
3353 assert!(text.contains(want), "{triple} wanted {want:?}:\n{text}");
3354 }
3355 assert!(text.contains("\n_n:\n\t.quad\t__tlv_bootstrap\n"), "{text}");
3356 assert!(!text.contains("tpidr_el0") && !text.contains("%fs"), "{text}");
3357 }
3358 }
3359
3360 #[test]
3362 fn the_thread_pointer_is_refused_on_darwin() {
3363 let mut opts = options();
3364 opts.emit = EmitKind::Asm;
3365 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3366 let result = run(&opts, "void *f(void) { return __builtin_thread_pointer(); }\n");
3367 assert!(result.failed());
3368 assert!(result.messages[0].contains("thread pointer"), "{:?}", result.messages);
3369 }
3370
3371 #[test]
3374 fn a_darwin_variadic_definition_saves_nothing_and_walks_the_stack() {
3375 let mut opts = options();
3376 opts.emit = EmitKind::Asm;
3377 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3378 let source = "int f(int n, ...) { __builtin_va_list ap; __builtin_va_start(ap, n);\n\
3379 int r = __builtin_va_arg(ap, int); __builtin_va_end(ap); return r; }\n";
3380 let result = run(&opts, source);
3381 assert!(!result.failed(), "{:?}", result.messages);
3382 let text = result.text();
3383 assert!(!text.contains("str q"), "{text}");
3384 assert!(!text.contains("x7"), "{text}");
3385 }
3386
3387 #[test]
3390 fn a_darwin_call_puts_its_variadic_arguments_in_memory() {
3391 let mut opts = options();
3392 opts.emit = EmitKind::Asm;
3393 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3394 let source = "int printf(const char *, ...);\n\
3395 int g(double x) { return printf(\"%d %f\", 7, x); }\n";
3396 let result = run(&opts, source);
3397 assert!(!result.failed(), "{:?}", result.messages);
3398 let text = result.text();
3399 assert!(text.contains("str d0, [sp, #8]"), "{text}");
3400 }
3401
3402 #[test]
3406 fn a_darwin_listing_is_one_apples_assembler_reads() {
3407 let mut opts = options();
3408 opts.emit = EmitKind::Asm;
3409 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3410 let source = "extern int ext;\n\
3411 int g[4];\n\
3412 int f(int i) { return g[i] + ext; }\n";
3413 let result = run(&opts, source);
3414 assert!(!result.failed(), "{:?}", result.messages);
3415 let text = result.text();
3416 assert!(text.contains(", _g@PAGE\n"), "{text}");
3417 assert!(text.contains(", _g@PAGEOFF\n"), "{text}");
3418 assert!(text.contains(", _ext@GOTPAGE\n"), "{text}");
3419 assert!(text.contains(", _ext@GOTPAGEOFF]\n"), "{text}");
3420 assert!(!text.contains(":lo12:"), "{text}");
3421 assert!(text.contains("\t.globl\t_g\n\t.zerofill\t__DATA,__bss,_g,16,2\n"), "{text}");
3422 }
3423
3424 #[test]
3430 fn a_signed_char_on_aarch64_is_widened_with_its_sign_before_it_is_added_to() {
3431 for target in ["aarch64-linux-gnu", "aarch64-apple-darwin"] {
3432 let mut opts = options();
3433 opts.emit = EmitKind::Asm;
3434 opts.target = target.parse::<Triple>().unwrap();
3435 let source = "int f(signed char *p) { return *p + 1; }\n\
3436 unsigned g(unsigned short *p) { return *p + 1u; }\n";
3437 let result = run(&opts, source);
3438 assert!(!result.failed(), "{:?}", result.messages);
3439 let text = result.text();
3440 let signed = text.contains("\tsxtb w") || text.contains("\tldrsb w");
3441 assert!(signed, "{target}: {text}");
3442 }
3443 }
3444
3445 #[test]
3457 fn a_construct_the_back_end_cannot_reach_yet_is_reported_against_its_function() {
3458 let mut opts = options();
3459 opts.emit = EmitKind::MirFinal;
3460 let source = "void a(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
3461 s; s.x = 1; v[0] = s.x; }\n\
3462 void b(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
3463 s; s.x = 1; v[0] = s.x; }\n";
3464 let result = run(&opts, source);
3465 assert!(result.failed());
3466 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
3467 assert!(result.messages[0].contains("cannot generate code for 'a'"), "{:?}", result);
3468 assert!(result.messages[0].contains("wants more alignment"), "{:?}", result);
3469 assert!(result.messages[1].contains("cannot generate code for 'b'"), "{:?}", result);
3470 assert!(result.text().is_empty());
3471 }
3472
3473 #[test]
3481 fn a_variable_length_array_walks_its_pages_where_every_page_of_the_frame_is_to_be_touched() {
3482 let mut opts = options();
3483 opts.emit = EmitKind::MirFinal;
3484 let source = "void a(int n) { int v[n]; v[0] = 1; }\n";
3485 let plain = run(&opts, source);
3486 assert!(!plain.failed(), "{:?}", plain.messages);
3487 assert!(!plain.text().contains("cmp_set_a_64"), "{}", plain.text());
3488
3489 opts.stack_clash = true;
3490 let result = run(&opts, source);
3491 assert!(!result.failed(), "{:?}", result.messages);
3492 assert!(result.text().contains("cmp_set_a_64"), "{}", result.text());
3493 assert!(result.text().contains("or_mi_8"), "{}", result.text());
3494 }
3495
3496 #[test]
3506 fn a_function_that_keeps_a_frame_pointer_on_windows_reaches_an_object_file() {
3507 let mut opts = options();
3508 opts.emit = EmitKind::Object;
3509 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3510 let source = concat!(
3511 "void use(void *p);\n",
3512 "void array(int n) { int v[n]; v[0] = 1; use(v); }\n",
3513 "void taken(unsigned long n) { use(__builtin_alloca(n)); }\n",
3514 );
3515 let result = run(&opts, source);
3516 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3517 let bytes = match result.artifact {
3518 Artifact::Object { bytes, .. } => bytes,
3519 other => panic!("expected an object, got {other:?}"),
3520 };
3521 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3522
3523 let mut opts = options();
3526 opts.emit = EmitKind::Object;
3527 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3528 }
3529
3530 #[test]
3541 fn the_address_of_a_function_this_file_only_declares_reaches_a_windows_object() {
3542 let source = concat!(
3543 "void other(void *p);\n",
3544 "void takes(void (*f)(void *));\n",
3545 "void (*held)(void *);\n",
3546 "void pass(void) { takes(other); }\n",
3547 "void keep(void) { held = other; }\n",
3548 "void call(void) { other(0); }\n",
3549 );
3550 let mut opts = options();
3551 opts.emit = EmitKind::Object;
3552 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3553 let result = run(&opts, source);
3554 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3555 let bytes = match result.artifact {
3556 Artifact::Object { bytes, .. } => bytes,
3557 other => panic!("expected an object, got {other:?}"),
3558 };
3559 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3560
3561 let mut opts = options();
3564 opts.emit = EmitKind::Object;
3565 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3566 }
3567
3568 #[test]
3582 fn an_opcode_with_no_name_in_the_rule_language_is_named_by_its_own_spelling() {
3583 let mut opts = options();
3584 opts.emit = EmitKind::MirFinal;
3585 let source =
3586 "long double f(int a) {\n __int128 wide = a;\n return (long double) wide;\n}\n";
3587 let result = run(&opts, source);
3588 assert!(result.failed());
3589 assert!(
3590 result.messages[0].contains("no rule lowers a `sext` producing a `i128`"),
3591 "{result:?}"
3592 );
3593 assert!(result.messages[0].contains(":2:"), "the line the widening is on: {result:?}");
3594 assert!(!result.messages[0].contains("this instruction"), "{result:?}");
3595 }
3596
3597 #[test]
3599 fn the_note_on_unfinished_work_points_at_the_issues_rather_than_at_the_plan() {
3600 let mut opts = options();
3601 opts.emit = EmitKind::MirFinal;
3602 let source = "long double f(int a) { __int128 wide = a; return (long double) wide; }\n";
3603 let result = run(&opts, source);
3604 assert!(result.failed());
3605 let note = result.messages.iter().find(|line| line.contains("note:")).expect("a note");
3606 assert!(note.contains("https://github.com/tamnd/rucc/issues"), "{note}");
3607 assert!(!note.contains("spec/17-milestones.md"), "{note}");
3608 }
3609
3610 #[test]
3612 fn the_frame_flags_on_the_command_line_reach_the_generated_frame() {
3613 let source = "int f(int a) { return a; }\n";
3614 assert!(!mir(source).contains("$rbp"), "a leaf needs no frame pointer by default");
3615
3616 let mut opts = options();
3617 opts.emit = EmitKind::MirFinal;
3618 opts.frame_pointer = true;
3619 let kept = run(&opts, source).text().to_owned();
3620 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
3621 }
3622
3623 fn asm(source: &str) -> String {
3625 let mut opts = options();
3626 opts.emit = EmitKind::Asm;
3627 let result = run(&opts, source);
3628 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3629 result.text().to_owned()
3630 }
3631
3632 #[test]
3639 fn a_function_goes_from_c_to_assembly_an_assembler_would_take() {
3640 let text = asm("int add(int a, int b) { return a + b; }\n");
3641 assert!(text.contains("\t.globl\tadd\n"), "{text}");
3642 assert!(text.contains("\t.type\tadd, @function\n"), "{text}");
3643 assert!(text.contains("\nadd:\n"), "{text}");
3644 assert!(text.contains("\taddl\t"), "{text}");
3645 assert!(text.contains("\tret\n"), "{text}");
3646 assert!(text.contains("\t.size\tadd, .-add\n"), "{text}");
3647 assert!(text.contains(".note.GNU-stack"), "{text}");
3650 }
3651
3652 #[test]
3658 fn a_call_through_a_function_pointer_goes_through_the_register_it_is_in() {
3659 let text = asm("int g(int);\nint f(int (*p)(int), int a) { return p(a) + g(a); }\n");
3660 assert!(text.contains("\tcall\t*%"), "{text}");
3661 assert!(text.contains("\tcall\tg\n"), "{text}");
3662 assert!(text.contains("%rdi"), "{text}");
3666 }
3667
3668 #[test]
3672 fn the_address_of_a_global_is_read_from_the_instruction_pointer() {
3673 let text = asm("extern int counter;\nint f(void) { return counter; }\n");
3674 assert!(text.contains("\tmovl\tcounter(%rip), %eax\n"), "{text}");
3675 }
3676
3677 #[test]
3686 fn a_branch_on_a_comparison_jumps_on_the_opposite_of_what_it_compared() {
3687 let arms = "return 1; return 2;";
3688 let signed = [("==", "jne"), ("!=", "je"), ("<", "jge"), ("<=", "jg"), (">", "jle")];
3689 for (operator, jump) in signed.into_iter().chain([(">=", "jl")]) {
3690 let text = asm(&format!("int f(int a, int b) {{ if (a {operator} b) {arms} }}\n"));
3691 assert!(
3692 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
3693 "{operator}: {text}"
3694 );
3695 assert!(!text.contains("\tset"), "{operator}: {text}");
3696 assert!(!text.contains("\ttest"), "{operator}: {text}");
3697 }
3698 let unsigned = [("<", "jae"), ("<=", "ja"), (">", "jbe"), (">=", "jb")];
3699 for (operator, jump) in unsigned {
3700 let source =
3701 format!("int f(unsigned a, unsigned b) {{ if (a {operator} b) {arms} }}\n");
3702 let text = asm(&source);
3703 assert!(
3704 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
3705 "{operator}: {text}"
3706 );
3707 }
3708
3709 let text = asm("int f(int a) { if (a < 7) return 1; return 2; }\n");
3712 assert!(text.contains("\tcmpl\t$7, %edi\n\tjge\t"), "{text}");
3713 }
3714
3715 #[test]
3721 fn a_comparison_whose_answer_the_program_wanted_still_writes_a_byte() {
3722 let text = asm("int f(int a, int b) { return a < b; }\n");
3723 assert!(text.contains("\tsetl\t"), "{text}");
3724 }
3725
3726 fn optimized(source: &str) -> String {
3728 let mut opts = options();
3729 opts.emit = EmitKind::Asm;
3730 opts.opt_level = rucc_session::OptLevel::O2;
3731 let result = run(&opts, source);
3732 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3733 result.text().to_owned()
3734 }
3735
3736 #[test]
3738 fn opt_info_says_what_each_switch_became_and_a_forced_shape_is_what_it_says() {
3739 let arms: String = (0..40)
3740 .map(|k| format!("case {}: return g({k});", k * 17))
3741 .collect::<Vec<_>>()
3742 .join(" ");
3743 let source = format!("int g(int);\nint f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n");
3744 let said = |shape: Option<&str>| {
3745 let mut opts = options();
3746 opts.emit = EmitKind::Asm;
3747 opts.opt_level = rucc_session::OptLevel::O2;
3748 opts.opt_info = vec![String::new()];
3749 opts.switch_shape = shape.map(str::to_owned);
3750 let result = run(&opts, &source);
3751 assert_eq!(result.messages, Vec::<String>::new());
3752 let lines: Vec<String> = result
3753 .remarks
3754 .lines()
3755 .filter(|line| line.contains("[switch-lowering]"))
3756 .map(str::to_owned)
3757 .collect();
3758 assert_eq!(lines.len(), 1, "{}", result.remarks);
3759 lines[0].clone()
3760 };
3761 assert!(said(None).contains(": f: optimized: switch of 40 cases lowered as a tree;"));
3762 assert!(said(Some("table")).contains("lowered as a table;"));
3763 assert!(said(Some("walk")).contains("lowered as a walk;"));
3764 }
3765
3766 #[test]
3776 fn a_switch_whose_arms_are_a_function_of_the_label_is_a_range_check_and_arithmetic() {
3777 let arms: String =
3778 (0..16).map(|k| format!("case {k}: return {};", k + 1)).collect::<Vec<_>>().join(" ");
3779 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3780 assert!(text.contains("\tcmpl\t$15, %edi\n\tja\t"), "{text}");
3781 assert!(text.contains("\taddl\t$1, %edi"), "{text}");
3782 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
3783 }
3784
3785 #[test]
3793 fn a_dense_switch_whose_arms_are_not_a_line_is_a_load_from_a_table() {
3794 let arms: String = (0..16)
3795 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
3796 .collect::<Vec<_>>()
3797 .join(" ");
3798 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3799 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
3800 assert!(!text.contains("\tjmp\t*"), "{text}");
3801 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
3802 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
3803 let section = text[..text.find("CSWTCH.0:").unwrap_or(0)].rfind("\t.section\t.rodata");
3804 assert!(section.is_some(), "{text}");
3805 assert_eq!(table.matches("\t.long\t").count(), 16, "{text}");
3806 assert!(table.contains("\t.long\t100\n"), "{text}");
3807 }
3808
3809 #[test]
3815 fn a_switch_whose_arms_give_strings_is_a_table_of_how_far_away_they_are() {
3816 let text = optimized(
3817 "const char *f(int k) { switch (k) { case 0: return \"zero\"; \
3818 case 1: return \"one\"; case 2: return \"two\"; case 3: return \"three\"; } \
3819 return \"many\"; }\n",
3820 );
3821 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
3822 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
3823 assert!(!text.contains(".data.rel.ro"), "{text}");
3824 let at = text.find("CSWTCH.0:").expect("the table is in the output");
3825 assert!(text[..at].rfind("\t.section\t.rodata").is_some(), "{text}");
3826 let table = &text[at..];
3827 assert_eq!(table.matches(" - .\n").count(), 4, "{text}");
3828 assert!(table.contains("\t.long\t.Lstr.1+4 - .\n"), "{text}");
3829 }
3830
3831 #[test]
3837 fn a_table_at_os_has_cells_as_narrow_as_its_answers() {
3838 let arms: String = (0..16)
3839 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
3840 .collect::<Vec<_>>()
3841 .join(" ");
3842 let mut opts = options();
3843 opts.emit = EmitKind::Asm;
3844 opts.opt_level = rucc_session::OptLevel::Os;
3845 let result = run(&opts, &format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3846 assert_eq!(result.messages, Vec::<String>::new());
3847 let text = result.text();
3848 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
3849 assert_eq!(table.matches("\t.byte\t").count(), 16, "{text}");
3850 assert!(text.contains("\tmovsbl\t"), "{text}");
3851 }
3852
3853 #[test]
3860 fn a_table_that_covers_its_operand_has_no_range_check() {
3861 let text = optimized(
3862 "int f(unsigned x) { switch (x & 3) { case 0: return 5; case 1: return 9; \
3863 case 2: return 2; case 3: return 7; } return -1; }\n",
3864 );
3865 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
3866 assert!(!text.contains("\tcmp"), "{text}");
3867 assert!(!text.contains("$-1"), "{text}");
3868 }
3869
3870 #[test]
3875 fn a_store_to_a_local_the_loop_just_read_is_a_conditional_move() {
3876 let text = optimized(
3877 "int f(const int *v, int n, int k) { int best[8] = {0}; \
3878 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; \
3879 return best[k & 7]; }\n",
3880 );
3881 assert!(text.contains("\tcmovgl"), "{text}");
3882 assert!(!text.contains("\tset"), "{text}");
3883 assert!(!text.contains("\ttestb"), "{text}");
3884 }
3885
3886 #[test]
3888 fn a_store_to_a_global_the_loop_just_read_keeps_its_branch() {
3889 let text = optimized(
3890 "int best[8]; void f(const int *v, int n) { \
3891 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; }\n",
3892 );
3893 assert!(!text.contains("\tcmov"), "{text}");
3894 }
3895
3896 #[test]
3904 fn a_conversion_from_a_constant_double_is_the_number_it_converts_to() {
3905 let text = optimized("int f(void) { double d = 2.75; return (int) d; }\n");
3906 assert!(text.contains("movl\t$2, %eax"), "{text}");
3907 assert!(!text.contains("cvttsd2si"), "{text}");
3908 }
3909
3910 #[test]
3918 fn a_slot_of_a_read_only_table_is_the_value_the_table_holds() {
3919 let text =
3920 optimized("static const int t[4] = {10, 20, 30, 40};\nint f(void) { return t[2]; }\n");
3921 assert!(text.contains("movl\t$30, %eax"), "{text}");
3922 assert!(!text.contains("t(%rip)"), "{text}");
3923 }
3924
3925 #[test]
3928 fn a_byte_of_a_read_only_string_is_the_byte_the_string_spells() {
3929 let text = optimized("static const char s[] = \"abc\";\nint f(void) { return s[1]; }\n");
3930 assert!(text.contains("movl\t$98, %eax"), "{text}");
3931 }
3932
3933 #[test]
3937 fn a_table_that_is_not_read_only_keeps_its_load() {
3938 let text = optimized(
3939 "static int t[4] = {10, 20, 30, 40};\nvoid g(int x) { t[2] = x; }\nint f(void) { return t[2]; }\n",
3940 );
3941 assert!(!text.contains("movl\t$30, %eax"), "{text}");
3942 }
3943
3944 #[test]
3951 fn a_call_guarded_by_a_condition_a_read_only_object_settles_is_not_emitted() {
3952 let text = optimized(
3953 "void link_error(void);\nconst double one = 1.0;\nint main(void) { if ((int) one != 1) link_error(); return 0; }\n",
3954 );
3955 assert!(!text.contains("call\tlink_error"), "{text}");
3956 }
3957
3958 #[test]
3960 fn a_cast_between_a_pointer_and_an_integer_leaves_the_value_where_it_is() {
3961 let text = asm("long f(void *p) { return (long)p; }\n");
3962 for line in text.lines().filter(|line| line.starts_with('\t') && !line.contains('.')) {
3967 let mnemonic = line.split_whitespace().next().unwrap_or("");
3968 assert!(matches!(mnemonic, "movq" | "ret"), "{line} in\n{text}");
3969 }
3970 }
3971
3972 #[test]
3976 fn an_argument_past_the_last_register_is_read_out_of_the_caller_s_stack() {
3977 let six = "long a, long b, long c, long d, long e, long f";
3978 let text = asm(&format!("long f({six}, long g, long h) {{ return g + h; }}\n"));
3979
3980 assert!(text.contains("\tmovq\t8(%rsp), "), "{text}");
3987 assert!(text.contains("\taddq\t16(%rsp), "), "{text}");
3988
3989 let narrow = asm(&format!("int f({six}, int g) {{ return g; }}\n"));
3993 assert!(narrow.contains("\tmovl\t8(%rsp), "), "{narrow}");
3994 let eight =
3995 "double a, double b, double c, double d, double e, double f, double g, double h";
3996 let float = asm(&format!("double f({eight}, double i) {{ return i; }}\n"));
3997 assert!(float.contains("\tmovsd\t8(%rsp), "), "{float}");
3998 }
3999
4000 #[test]
4003 fn a_call_writes_the_arguments_with_no_register_left_at_the_stack_pointer() {
4004 let six = "1, 2, 3, 4, 5, 6";
4005 let decl = "long g(long, long, long, long, long, long, long, long);\n";
4006 let text = asm(&format!("{decl}long f(void) {{ return g({six}, 7, 8); }}\n"));
4007
4008 assert!(text.contains("\tmovq\t%"), "{text}");
4009 assert!(text.contains(", (%rsp)\n"), "{text}");
4010 assert!(text.contains(", 8(%rsp)\n"), "{text}");
4011 assert!(text.contains("\tsubq\t$"), "{text}");
4013
4014 let narrow = "int g(int, int, int, int, int, int, int);\n";
4016 let text = asm(&format!("{narrow}int f(void) {{ return g({six}, 7); }}\n"));
4017 assert!(text.contains("\tmovl\t%"), "{text}");
4018 assert!(text.contains(", (%rsp)\n"), "{text}");
4019 }
4020
4021 #[test]
4024 fn a_variadic_call_counts_registers_and_not_arguments() {
4025 let nine = "1., 2., 3., 4., 5., 6., 7., 8., 9.";
4026 let decl = "int g(int, ...);\n";
4027 let text = asm(&format!("{decl}int f(void) {{ return g(0, {nine}); }}\n"));
4028
4029 assert!(text.contains("\tmovl\t$8, "), "eight registers, not nine: {text}");
4030 assert!(text.contains("\tmovsd\t%"), "{text}");
4031 assert!(text.contains(", (%rsp)\n"), "{text}");
4032 }
4033
4034 #[test]
4039 fn a_variadic_function_writes_the_argument_registers_it_was_handed_into_its_frame() {
4040 let body =
4041 "__builtin_va_list ap; __builtin_va_start(ap, n); __builtin_va_end(ap); return n;";
4042 let text = asm(&format!("int f(int n, ...) {{ {body} }}\n"));
4043
4044 let stores = |mnemonic: &str| text.matches(&format!("\t{mnemonic}\t%")).count();
4047 assert!(text.contains(", 8(%r"), "the second slot, not the first: {text}");
4048 assert!(!text.contains(", 0(%r"), "{text}");
4049 assert_eq!(stores("movaps"), 8, "every vector register: {text}");
4052 assert_eq!(stores("movsd"), 0, "and the whole of each one: {text}");
4053
4054 assert!(text.contains("\tsubq\t$"), "{text}");
4056 }
4057
4058 #[test]
4061 fn va_start_writes_the_four_fields_the_psabi_describes() {
4062 let start = "__builtin_va_list ap; __builtin_va_start(ap, d);";
4063 let params = "int a, int b, int c, double d";
4064 let text = asm(&format!("int f({params}, ...) {{ {start} return a; }}\n"));
4065
4066 assert!(text.contains(" movl $24, "), "{text}");
4070 assert!(text.contains(" movl $64, "), "{text}");
4071 assert!(text.contains(", 8(%r"), "{text}");
4075 assert!(text.contains(", 16(%r"), "{text}");
4076 let frame: u32 = text
4077 .lines()
4078 .find_map(|line| line.trim().strip_prefix("subq $")?.split(',').next()?.parse().ok())
4079 .expect("a variadic function takes a frame for the save area");
4080 let above = |line: &str| {
4081 let at: u32 = line.trim().strip_prefix("leaq ")?.split('(').next()?.parse().ok()?;
4082 Some(at > frame)
4083 };
4084 assert!(text.lines().filter_map(above).any(|it| it), "{frame}: {text}");
4085 }
4086
4087 #[test]
4090 fn va_arg_branches_on_whether_the_argument_is_still_in_the_save_area() {
4091 let read = "__builtin_va_list ap; __builtin_va_start(ap, n);";
4092 let ints = format!("int f(int n, ...) {{ {read} return __builtin_va_arg(ap, int); }}\n");
4093 let text = asm(&ints);
4094
4095 assert!(text.contains("$40, "), "{text}");
4098 assert!(text.contains(" cmpl "), "{text}");
4099 assert!(text.contains(" ja "), "{text}");
4103
4104 let arg = "__builtin_va_arg(ap, double)";
4105 let text = asm(&format!("double f(int n, ...) {{ {read} return {arg}; }}\n"));
4106 assert!(text.contains("$160, "), "the last vector slot: {text}");
4107 }
4108
4109 #[test]
4112 fn a_structure_assignment_is_a_move_for_each_word_of_it() {
4113 let decl = "struct pair { long a, b; };\n";
4114 let body = "struct pair p = *q; return p.a + p.b;";
4115 let text = asm(&format!("{decl}long f(struct pair *q) {{ {body} }}\n"));
4116
4117 assert!(!text.contains("memcpy"), "nothing calls the library: {text}");
4118 assert!(!text.contains("\tcall"), "{text}");
4119 assert!(text.matches("\tmovq\t").count() >= 4, "two words each way: {text}");
4121 }
4122
4123 #[test]
4126 fn how_wide_a_word_of_a_copy_is_follows_the_alignment() {
4127 let decl = "struct bytes { char a[8]; };\n";
4128 let body = "struct bytes p = *q; return p.a[0];";
4129 let text = asm(&format!("{decl}int f(struct bytes *q) {{ {body} }}\n"));
4130
4131 assert!(text.matches("\tmovb\t").count() >= 16, "a byte at a time: {text}");
4133 }
4134
4135 #[test]
4138 fn the_part_of_an_initialiser_that_names_nothing_is_stored_as_zero() {
4139 let decl = "struct wide { long a, b, c; };\n";
4140 let text = asm(&format!("{decl}long f(void) {{ struct wide w = {{ 7 }}; return w.c; }}\n"));
4141
4142 assert!(!text.contains("memset"), "nothing calls the library: {text}");
4143 assert!(text.contains("\tmovq\t$0, ") || text.contains("\txorl\t"), "the zero: {text}");
4148 }
4149
4150 #[test]
4153 fn a_copy_too_large_to_unroll_calls_the_runtime() {
4154 let decl = "struct huge { char a[4096]; };\n";
4155 let mut opts = options();
4156 opts.emit = EmitKind::Asm;
4157 let source = format!("{decl}void f(struct huge *p, struct huge *q) {{ *p = *q; }}\n");
4158 let result = run(&opts, &source);
4159 assert!(!result.failed(), "{:?}", result.messages);
4160 let text = result.text();
4161 assert!(text.contains("call") && text.contains("memcpy"), "{text}");
4162 assert!(text.contains("4096"), "the size travels: {text}");
4165 }
4166
4167 #[test]
4174 fn a_structure_too_large_to_unroll_is_copied_into_the_argument_area_by_the_runtime() {
4175 let decl = "struct huge { char a[4096]; };\nint take(struct huge);\n";
4176 let text = asm(&format!("{decl}int f(struct huge *p) {{ return take(*p); }}\n"));
4177
4178 let copy = text.find("call\tmemcpy").expect("the copy");
4179 let call = text.find("call\ttake").expect("the call");
4180 assert!(copy < call, "the copy comes first: {text}");
4181 assert!(text.contains("movq\t%rsp, %rdi"), "the destination: {text}");
4186 assert!(text.contains("$4096, %edx"), "the size: {text}");
4187 }
4188
4189 #[test]
4192 fn a_realigned_frame_reads_them_through_the_frame_pointer() {
4193 let six = "long a, long b, long c, long d, long e, long f";
4194 let body = "_Alignas(32) long wide[4]; wide[0] = g; return wide[0];";
4195 let text = asm(&format!("long f({six}, long g) {{ {body} }}\n"));
4196
4197 assert!(text.contains("\tandq\t$-32, %rsp"), "{text}");
4201 assert!(text.contains("\tmovq\t16(%rbp), "), "{text}");
4202 assert!(!text.contains("\tmovq\t16(%rsp), "), "{text}");
4203 }
4204
4205 #[test]
4207 fn the_target_decides_how_the_assembly_is_spelled() {
4208 let mut opts = options();
4209 opts.emit = EmitKind::Asm;
4210 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
4211 let text = run(&opts, "int f(void) { return 0; }\n").text().to_owned();
4212 assert!(text.contains("__TEXT,__text"), "{text}");
4213 assert!(text.contains("\n_f:\n"), "{text}");
4214 assert!(!text.contains(".note.GNU-stack"), "{text}");
4215 }
4216
4217 fn obj(source: &str) -> Vec<u8> {
4219 let mut opts = options();
4220 opts.emit = EmitKind::Object;
4221 let result = run(&opts, source);
4222 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4223 match result.artifact {
4224 Artifact::Object { bytes, .. } => bytes,
4225 other => panic!("expected an object, got {other:?}"),
4226 }
4227 }
4228
4229 #[test]
4235 fn a_function_goes_from_c_to_an_object_a_linker_would_take() {
4236 let bytes = obj("int add(int a, int b) { return a + b; }\n");
4237 assert_eq!(&bytes[..4], b"\x7fELF", "an object file starts by saying it is one");
4238 let text = asm("int add(int a, int b) { return a + b; }\n");
4239 assert!(
4240 text.contains("\taddl\t"),
4241 "and the listing of it is the same instructions:\n{text}"
4242 );
4243 }
4244
4245 #[test]
4247 fn a_variable_goes_from_c_to_the_section_it_belongs_in() {
4248 let text = asm("int counter = 42;\nstatic int hidden;\nconst int fixed = 7;\n");
4249 assert!(text.contains("\t.data\n\t.globl\tcounter\n"), "{text}");
4250 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
4251 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
4252 assert!(text.contains("\t.bss\n\t.p2align\t2\n"), "{text}");
4255 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
4256 assert!(!text.contains(".globl\thidden"), "{text}");
4257 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4260 }
4261
4262 #[test]
4269 fn a_bit_field_initializer_writes_every_byte_of_the_value_and_not_only_the_ones_that_are_set() {
4270 let text = asm("struct s { unsigned f : 20; } x = { 0x12300 };\n");
4271 assert!(text.contains("\t.data\n"), "there is something to write: {text}");
4272 assert!(text.contains("\nx:\n\t.ascii\t\"\\000#\\001\"\n"), "and it is the value: {text}");
4273
4274 let text = asm("struct s { unsigned a : 8; unsigned b : 8; } x = { 0, 3 };\n");
4277 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\003\"\n"), "{text}");
4278
4279 let text = asm("struct s { unsigned long long f : 40; } x = { 0x100000 };\n");
4282 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\000\\020\"\n\t.space\t5\n"), "{text}");
4283
4284 let text = asm("struct s { unsigned f : 20; } x = { 0 };\n");
4286 assert!(text.contains("\t.bss\n"), "an object of zeroes is zeroes: {text}");
4287 assert!(text.contains("\nx:\n\t.space\t4\n"), "{text}");
4288 }
4289
4290 #[test]
4292 fn a_string_literal_is_a_variable_with_a_name_no_program_could_write() {
4293 let text = asm("const char *f(void) { return \"hi\"; }\n");
4294 assert!(text.contains("\t.ascii\t\"hi\\000\"\n"), "{text}");
4295 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4296 let label = text
4297 .lines()
4298 .find(|line| line.starts_with(".Lstr"))
4299 .unwrap_or_else(|| panic!("a label for the literal in\n{text}"));
4300 assert!(!text.contains(&format!(".globl\t{}", label.trim_end_matches(':'))), "{text}");
4301 }
4302
4303 #[test]
4305 fn an_address_in_an_initializer_is_left_to_the_linker() {
4306 let source = "int counter;\nint *p = &counter;\n";
4307 let text = asm(source);
4308 assert!(text.contains("\np:\n\t.quad\tcounter\n"), "{text}");
4309 let bytes = obj(source);
4312 assert!(bytes.windows(8).any(|w| w == b"counter\0"), "the object has to name it");
4313 }
4314
4315 #[test]
4324 fn a_constant_holding_an_address_goes_in_the_section_the_loader_may_write_once() {
4325 let text = asm("static void a(void) {}\nstatic void b(void) {}\n\
4328 struct m { void (*x)(void); void (*y)(void); };\n\
4329 const struct m t = { a, b };\n");
4330 assert!(text.contains("\t.section\t.data.rel.ro.local,\"aw\",@progbits\n"), "{text}");
4331 assert!(text.contains("\nt:\n\t.quad\ta\n\t.quad\tb\n"), "{text}");
4332
4333 let text =
4336 asm("void a(void);\nstruct m { void (*x)(void); };\nconst struct m t = { a };\n");
4337 assert!(text.contains("\t.section\t.data.rel.ro,\"aw\",@progbits\n"), "{text}");
4338
4339 let text = asm("const int fixed = 7;\n");
4341 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4342 }
4343
4344 #[test]
4351 fn a_thread_local_variable_is_storage_a_thread_gets_a_copy_of_and_an_offset_into_it() {
4352 let text = asm("_Thread_local int x = 1;\nint read(void) { return x; }\n");
4353 assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
4356 assert!(text.contains("\t.type\tx, @tls_object\n"), "{text}");
4357 assert!(text.contains("x@GOTTPOFF(%rip)"), "{text}");
4360 assert!(text.contains("%fs:0"), "{text}");
4361 }
4362
4363 #[test]
4369 fn the_address_of_this_thread_s_own_storage_is_read_out_of_the_segment_register() {
4370 let text = asm("void *here(void) { return __builtin_thread_pointer(); }\n");
4371 assert!(text.contains("movq\t%fs:0, "), "{text}");
4372 assert!(!text.contains("GOTTPOFF"), "{text}");
4374 }
4375
4376 #[test]
4387 fn a_prefetch_is_one_of_four_instructions_and_the_locality_is_what_picks() {
4388 for (locality, wanted) in
4389 [(0, "prefetchnta"), (1, "prefetcht2"), (2, "prefetcht1"), (3, "prefetcht0")]
4390 {
4391 let source =
4392 format!("void warm(void *p) {{ __builtin_prefetch(p, 0, {locality}); }}\n");
4393 let text = asm(&source);
4394 assert!(text.contains(&format!("\t{wanted}\t")), "locality {locality}: {text}");
4395 }
4396 let text = asm("void warm(void *p) { __builtin_prefetch(p); }\n");
4398 assert!(text.contains("\tprefetcht0\t"), "{text}");
4399 let text = asm("void warm(void *p) { __builtin_prefetch(p, 1); }\n");
4402 assert!(text.contains("\tprefetcht0\t"), "{text}");
4403 assert!(!text.contains("prefetchw"), "{text}");
4404 }
4405
4406 #[test]
4409 fn an_aarch64_prefetch_is_a_prfm_that_says_the_locality_and_whether_it_writes() {
4410 let mut opts = options();
4411 opts.emit = EmitKind::Asm;
4412 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
4413 for (write, kind) in [(0, "pld"), (1, "pst")] {
4414 for (locality, wanted) in [(0, "l1strm"), (1, "l3keep"), (2, "l2keep"), (3, "l1keep")] {
4415 let source = format!(
4416 "void warm(void *p) {{ __builtin_prefetch(p, {write}, {locality}); }}\n"
4417 );
4418 let result = run(&opts, &source);
4419 assert_eq!(result.messages, Vec::<String>::new(), "{source}");
4420 let text = result.text();
4421 assert!(text.contains("prfm"), "{source}{text}");
4422 assert!(text.contains(&format!("{kind}{wanted}, [x0]")), "{source}{text}");
4423 }
4424 }
4425 }
4426
4427 #[test]
4438 fn a_trap_is_the_instruction_the_machine_has_no_meaning_for() {
4439 let text = asm("void stop(void) { __builtin_trap(); }\n");
4440 assert!(text.contains("\tud2\n"), "{text}");
4441 assert!(!text.contains("\tcall"), "a stop is not a call to anything: {text}");
4442
4443 let text = asm("int stop(int a) { __builtin_trap(); return a + 1; }\n");
4444 assert!(text.contains("\tud2\n"), "{text}");
4445 assert!(text.contains("\taddl\t"), "the block goes on after a stop: {text}");
4446 }
4447
4448 #[test]
4460 fn assume_aligned_is_its_first_argument_and_keeps_the_rest() {
4461 let text = asm("void *aligned(char *p) { return __builtin_assume_aligned(p, 16); }\n");
4462 assert!(!text.contains("assume_aligned"), "{text}");
4463 assert!(!text.contains("\tcall"), "nothing is called for an alignment fact: {text}");
4464
4465 let source = "unsigned long width(void);\n\
4466 void *aligned(char *p) { return __builtin_assume_aligned(p, width()); }\n";
4467 let text = asm(source);
4468 assert!(!text.contains("assume_aligned"), "{text}");
4469 assert!(text.contains("width"), "the argument that is not the answer still runs: {text}");
4470 }
4471
4472 #[test]
4482 fn the_frame_address_is_the_frame_pointer_after_walking_that_many_links() {
4483 let text = asm("void *here(void) { return __builtin_frame_address(0); }\n");
4484 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
4485 assert!(text.contains("movq\t%rbp, %rax"), "{text}");
4486 assert!(!text.contains("\tcall"), "a frame address is not a call to anything: {text}");
4487
4488 let walk = |depth: u32| {
4489 let source = format!("void *up(void) {{ return __builtin_frame_address({depth}); }}\n");
4490 asm(&source).matches("movq\t(%r").count()
4491 };
4492 assert_eq!(walk(1), 1, "one link is one load");
4493 assert_eq!(walk(3), 3, "three links are three loads");
4494 }
4495
4496 #[test]
4506 fn the_return_address_is_one_word_above_the_frame_the_walk_ended_at() {
4507 let text = asm("void *back(void) { return __builtin_return_address(0); }\n");
4508 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
4509 assert!(text.contains("movq\t8(%rbp), %rax"), "{text}");
4510 assert!(!text.contains("\tcall"), "a return address is not a call to anything: {text}");
4511
4512 let text = asm("void *back(void) { return __builtin_return_address(2); }\n");
4513 assert_eq!(text.matches("movq\t(%r").count(), 2, "two links are two loads: {text}");
4514 assert!(text.contains("movq\t8(%r"), "and the answer is above the last of them: {text}");
4515 }
4516
4517 #[test]
4528 fn a_depth_that_is_not_a_small_constant_is_refused() {
4529 let mut opts = options();
4530 opts.emit = EmitKind::Ir;
4531 for source in [
4532 "void *up(int n) { return __builtin_return_address(n); }\n",
4533 "void *up(void) { return __builtin_frame_address(1000); }\n",
4534 ] {
4535 let messages = run(&opts, source).messages;
4536 let named = messages.iter().any(|m| m.contains("E0705"));
4537 assert!(named, "expected a refusal in {messages:?}");
4538 }
4539 }
4540
4541 #[test]
4553 fn an_alloca_takes_the_bytes_off_the_stack_pointer_and_answers_where_they_are() {
4554 let text =
4555 asm("void use(void *p); void f(unsigned long n) { use(__builtin_alloca(n)); }\n");
4556 assert!(text.contains("andq\t$-16"), "the size is rounded up to sixteen: {text}");
4557 assert!(text.contains("subq\t%rdi, %rsp"), "and taken off the stack pointer: {text}");
4558 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
4559
4560 let plain = concat!(
4563 "extern void *alloca(__SIZE_TYPE__);\n",
4564 "void use(void *p);\n",
4565 "void f(unsigned long n) { use(alloca(n)); }\n",
4566 );
4567 let text = asm(plain);
4568 assert!(text.contains("subq\t%rdi, %rsp"), "the plain name is the same bytes: {text}");
4569 assert_eq!(text.matches("\tcall").count(), 1, "and is not a call either: {text}");
4570
4571 let own = concat!(
4574 "static void *alloca(unsigned long n) { return 0; }\n",
4575 "void *f(unsigned long n) { return alloca(n); }\n",
4576 );
4577 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
4578 }
4579
4580 #[test]
4595 fn a_builtin_the_program_never_declared_is_the_builtin_rather_than_the_one_c89_wrote_down() {
4596 let mut opts = options();
4600 opts.permissive = true;
4601 let undeclared = "void use(void *p);
4602void f(unsigned long n) { use(alloca(n)); }
4603";
4604 assert_eq!(
4605 run(&opts, undeclared).messages,
4606 [
4607 "/main.c:2:31: warning: implicit declaration of function 'alloca' [E0521]",
4608 "/main.c:2:31: warning: incompatible implicit declaration of built-in function \
4609 'alloca' [E0713]",
4610 ]
4611 );
4612
4613 opts.emit = EmitKind::Asm;
4614 let text = run(&opts, undeclared).text().to_owned();
4615 assert!(text.contains("subq\t%rdi, %rsp"), "the bytes come off the stack: {text}");
4616 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
4617
4618 let string = "unsigned long f(void) { return strlen(\"abc\"); }\n";
4621 let text = run(&opts, string).text().to_owned();
4622 assert!(text.contains("call\tstrlen"), "strlen is still a call: {text}");
4623
4624 let own = concat!(
4627 "static void *alloca(unsigned long n) { return 0; }\n",
4628 "void *f(unsigned long n) { return alloca(n); }\n",
4629 );
4630 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
4631 }
4632
4633 #[test]
4643 fn the_bytes_an_alloca_took_are_still_there_at_the_end_of_the_block_that_took_them() {
4644 let inner = "{ use(__builtin_alloca(n)); }";
4645 for body in [inner.to_owned(), format!("int a[n]; {inner} use(a);")] {
4646 let source = format!("void use(void *p);\nvoid f(unsigned long n) {{ {body} }}\n");
4647 let text = asm(&source);
4648 for line in text.lines().filter(|line| line.trim_end().ends_with(", %rsp")) {
4652 let taking = line.contains("subq");
4653 let leaving = line.contains("%rbp");
4654 assert!(taking || leaving, "nothing puts the stack back: {line} in {text}");
4655 }
4656 }
4657 }
4658
4659 #[test]
4661 fn the_object_and_the_listing_are_two_spellings_of_one_compilation() {
4662 let source = "int callee(void); int g(void) { return callee(); }\n";
4666 let bytes = obj(source);
4667 assert!(
4668 bytes.windows(7).any(|w| w == b"callee\0"),
4669 "the object has to name the callee for the linker to find it"
4670 );
4671 let text = asm(source);
4672 assert!(text.contains("\tcall\tcallee\n"), "{text}");
4673 }
4674
4675 #[test]
4681 fn compiling_for_an_executable_produces_an_object_and_not_a_dump() {
4682 let mut opts = options();
4683 opts.emit = EmitKind::Executable;
4685 let result = run(&opts, "int main(void) { return 0; }\n");
4686 assert_eq!(result.messages, Vec::<String>::new());
4687 match result.artifact {
4688 Artifact::Object { bytes, .. } => assert_eq!(&bytes[..4], b"\x7fELF"),
4689 other => panic!("expected an object, got {other:?}"),
4690 }
4691 }
4692
4693 #[test]
4695 fn a_platform_with_no_object_writer_is_said_so_rather_than_written_as_elf() {
4696 let mut opts = options();
4697 opts.emit = EmitKind::Object;
4698 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
4699 let result = run(&opts, "int f(void) { return 0; }\n");
4700 assert!(result.failed(), "an object nobody can read is worse than a message");
4701 assert!(
4702 result.messages.iter().any(|m| m.contains("no object writer")),
4703 "{:?}",
4704 result.messages
4705 );
4706 }
4707
4708 fn ir(source: &str) -> String {
4710 let mut opts = options();
4711 opts.emit = EmitKind::Ir;
4712 let result = run(&opts, source);
4713 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4714 result.text().to_owned()
4715 }
4716
4717 fn errors(source: &str) -> Vec<String> {
4719 let mut opts = options();
4720 opts.emit = EmitKind::Ir;
4721 let result = run(&opts, source);
4722 assert!(result.failed(), "expected this to be refused:\n{source}");
4723 result.messages
4724 }
4725
4726 fn body(source: &str) -> String {
4728 let text = ir(source);
4729 let (_, rest) = text.split_once("{\n").expect("a function definition");
4730 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
4731 body.to_owned()
4732 }
4733
4734 #[test]
4742 fn gnu89_inline_is_what_decides_whether_a_bare_inline_definition_reaches_the_module() {
4743 let source = "inline int f(int x) { return x + 1; }\n";
4744 let with = |flag: bool| {
4745 let mut opts = options();
4746 opts.emit = EmitKind::Ir;
4747 opts.gnu89_inline = flag;
4748 let result = run(&opts, source);
4749 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
4750 result.text().to_owned()
4751 };
4752
4753 assert!(!with(false).contains("block0"), "no body: {}", with(false));
4756
4757 assert!(with(true).contains("block0"), "a body: {}", with(true));
4760 }
4761
4762 #[test]
4769 fn an_access_through_a_type_names_the_type_it_went_through() {
4770 let source = "\
4771struct s { int a; float b; };\n\
4772union u { int i; float f; };\n\
4773int scalar(int *p) { return *p; }\n\
4774float member(struct s *p) { p->a = 1; return p->b; }\n\
4775int element(int *a, long i) { return a[i]; }\n\
4776float through_a_union(union u *p) { p->i = 1; return p->f; }\n";
4777 let text = ir(source);
4778 assert!(text.contains(r#"!0 = tbaa "char""#), "the root: {text}");
4779 assert!(text.contains(r#"tbaa "int", parent !0"#), "int under it: {text}");
4780 assert!(text.contains(r#"tbaa "float", parent !0"#), "float under it: {text}");
4781 let named = text.lines().filter(|line| line.contains(", tbaa !")).count();
4784 assert_eq!(named, 6, "six accesses: {text}");
4785 }
4786
4787 #[test]
4794 fn turning_strict_aliasing_off_leaves_the_type_off_every_access() {
4795 let source = "int punned(float *f, int *i) { *i = 1; *f = 2.0f; return *i; }\n";
4796 let mut opts = options();
4797 opts.emit = EmitKind::Ir;
4798 opts.strict_aliasing = false;
4799 let result = run(&opts, source);
4800 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
4801 let text = result.text().to_owned();
4802 assert!(!text.contains("tbaa"), "not even the root: {text}");
4803 }
4804
4805 #[test]
4811 fn instrumenting_functions_calls_the_hooks_around_every_body_but_the_hooks() {
4812 let source = concat!(
4813 "#define NOCHK __attribute__((no_instrument_function))\n",
4814 "void __cyg_profile_func_enter(void *, void *) NOCHK;\n",
4815 "void __cyg_profile_func_exit(void *, void *) NOCHK;\n",
4816 "int calls;\n",
4817 "int pick(int x) { if (x) return 1; return 2; }\n",
4818 "void quiet(void) NOCHK;\n",
4819 "void quiet(void) { calls++; }\n",
4820 "void __cyg_profile_func_enter(void *fn, void *site) { calls++; }\n",
4821 "void __cyg_profile_func_exit(void *fn, void *site) { calls--; }\n",
4822 );
4823 let mut opts = options();
4824 opts.emit = EmitKind::Ir;
4825 opts.instrument_functions = true;
4826 let result = run(&opts, source);
4827 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
4828 let text = result.text().to_owned();
4829 let body = |name: &str| -> String {
4830 let open = format!("func @{name}(");
4831 let start = text.find(&open).unwrap_or_else(|| panic!("no {name}: {text}"));
4832 let rest = &text[start..];
4833 rest[..rest.find("\n}").unwrap_or(rest.len())].to_owned()
4834 };
4835 let pick = body("pick");
4836 assert_eq!(pick.matches("call @__cyg_profile_func_enter(").count(), 1, "{pick}");
4837 assert_eq!(pick.matches("call @__cyg_profile_func_exit(").count(), 2, "{pick}");
4838 assert!(pick.contains("return_address"), "{pick}");
4839 assert!(pick.contains("global_addr @pick"), "{pick}");
4840 for quiet in ["quiet", "__cyg_profile_func_enter", "__cyg_profile_func_exit"] {
4841 assert!(!body(quiet).contains("call "), "{quiet} is left alone: {text}");
4842 }
4843
4844 opts.instrument_functions = false;
4845 let result = run(&opts, source);
4846 assert!(!result.text().contains("call @__cyg_profile"), "off unless asked for");
4847 }
4848
4849 #[test]
4857 fn a_bare_return_from_a_function_that_promised_a_value_gives_back_a_zero() {
4858 let mut opts = options();
4859 opts.emit = EmitKind::Ir;
4860 opts.std = Std::C89;
4861 let compiled = |source: &str| {
4862 let result = run(&opts, source);
4863 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
4864 result.text().to_owned()
4865 };
4866
4867 let text = compiled("int f(int x) { if (x) return; return 3; }\n");
4868 assert!(text.contains("iconst.i32 0\n return"), "zero goes back: {text}");
4869 assert!(!text.contains("unreachable"), "the branch that reached it is kept: {text}");
4870
4871 let text = compiled("double f(int x) { if (x) return; return 1.0; }\n");
4873 assert!(text.contains("fconst.f64 0x0\n return"), "a float zero goes back: {text}");
4874 }
4875
4876 #[test]
4884 fn a_call_to_a_name_nothing_declared_declares_it_as_c89_said_to() {
4885 let mut opts = options();
4886 opts.emit = EmitKind::Ir;
4887 opts.std = Std::C89;
4888 let compiled = |source: &str| {
4889 let result = run(&opts, source);
4890 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
4891 result.text().to_owned()
4892 };
4893
4894 let text = compiled("int f(void) { return g(); }\n");
4896 assert!(text.contains("call @g"), "the call is to the name that was written: {text}");
4897 assert!(text.contains("i32"), "and it gives back an int: {text}");
4898
4899 let text = compiled("int f(char c) { return g(c); }\n");
4902 assert!(text.contains("sext.i32"), "the argument is promoted: {text}");
4903
4904 let mut opts = options();
4907 opts.std = Std::C89;
4908 let said = run(&opts, "int f(void) { return h; }\n").messages.join("\n");
4909 assert!(said.contains("'h' undeclared"), "not a call, so not declared: {said}");
4910 }
4911
4912 #[test]
4922 fn a_name_called_before_it_is_defined_still_gets_its_definition() {
4923 let mut opts = options();
4924 opts.emit = EmitKind::Ir;
4925 opts.std = Std::C89;
4926 let text = run(&opts, "int f(void) { return dummy(); }\ndummy () { return 7; }\n")
4927 .text()
4928 .to_owned();
4929 assert!(text.contains("func @f()"), "the caller is there: {text}");
4930 assert!(text.contains("func @dummy"), "and so is what it calls: {text}");
4931 assert!(text.contains("iconst.i32 7"), "with the body it was given: {text}");
4932 }
4933
4934 #[test]
4942 fn an_old_style_parameter_is_converted_from_what_the_call_promoted_it_to() {
4943 let mut opts = options();
4944 opts.emit = EmitKind::Ir;
4945 opts.std = Std::C89;
4946 let compiled = |source: &str| run(&opts, source).text().to_owned();
4947
4948 let text = compiled("f (c) unsigned char c; { return c; }\n");
4949 assert!(text.contains("func @f(i32"), "an int arrives: {text}");
4950 assert!(text.contains("trunc.i8"), "and is cut down to what was declared: {text}");
4951 assert!(text.contains("zext.i32"), "then read back unsigned: {text}");
4952
4953 let text = compiled("f (s) short s; { return s; }\n");
4955 assert!(text.contains("trunc.i16"), "cut down: {text}");
4956 assert!(text.contains("sext.i32"), "and read back signed: {text}");
4957
4958 let text = compiled("f (x) float x; { return x * 2; }\n");
4961 assert!(text.contains("func @f(f64"), "a double arrives: {text}");
4962 assert!(text.contains("fptrunc.f32"), "and is narrowed to the float: {text}");
4963
4964 let text = compiled("int f(unsigned char c) { return c; }\n");
4967 assert!(text.contains("func @f(i8)"), "the declared type arrives: {text}");
4968 assert!(!text.contains("trunc"), "so there is nothing to cut down: {text}");
4969 }
4970
4971 #[test]
4980 fn the_rules_gcc_promoted_are_decided_by_the_dialect_and_by_fpermissive() {
4981 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
4983 let cases = [
4984 ("static counted;\n", ["", "error", "warning", "error"]),
4985 ("int f(void) { return g(); }\n", ["", "error", "warning", "error"]),
4986 ("int f(x) { return x; }\n", ["", "error", "warning", "error"]),
4987 ("int *p;\nvoid h(void) { p = 1; }\n", ["warning", "error", "warning", "error"]),
4988 (
4989 "char *q;\nint *r;\nvoid k(void) { r = q; }\n",
4990 ["warning", "error", "warning", "error"],
4991 ),
4992 ("int f(void) { return; }\n", ["", "error", "warning", "error"]),
4993 ("void g(void) { return 1; }\n", ["warning", "error", "warning", "error"]),
4994 ];
4995
4996 for (source, wanted) in cases {
4997 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
4998 let mut opts = options();
4999 opts.std = std;
5000 opts.permissive = permissive;
5001 let said = run(&opts, source).messages.join("\n");
5002 let severity = if said.contains(": error: ") {
5003 "error"
5004 } else if said.contains(": warning: ") {
5005 "warning"
5006 } else {
5007 ""
5008 };
5009 let how = if permissive { " -fpermissive" } else { "" };
5010 assert_eq!(
5011 severity,
5012 wanted,
5013 "under -std={}{how}, {source} was answered with `{said}`",
5014 std.as_str()
5015 );
5016 if wanted.is_empty() {
5017 assert!(said.is_empty(), "nothing to say, but said `{said}`");
5018 }
5019 }
5020 }
5021 }
5022
5023 #[test]
5032 fn the_three_variadic_builtins_answer_a_bad_list_the_way_a_call_answers_a_bad_argument() {
5033 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
5034 let cases = [
5035 (
5036 "int f(int n, ...) { char *p; return __builtin_va_arg(p, int); }\n",
5037 "first argument to 'va_arg' not of type 'va_list'",
5038 ["error", "error", "error", "error"],
5039 ),
5040 (
5041 "void f(int n, ...) { char *p; __builtin_va_start(p, n); }\n",
5042 "passing argument 1 of '__builtin_va_start' from incompatible pointer type",
5043 ["warning", "error", "warning", "error"],
5044 ),
5045 (
5046 "void f(int n, ...) { int x; __builtin_va_end(x); }\n",
5047 "passing argument 1 of '__builtin_va_end' makes pointer from integer without a \
5048 cast",
5049 ["warning", "error", "warning", "error"],
5050 ),
5051 (
5052 "void f(int n, ...) { __builtin_va_list a; char *p; __builtin_va_copy(a, p); }\n",
5053 "passing argument 2 of '__builtin_va_copy' from incompatible pointer type",
5054 ["warning", "error", "warning", "error"],
5055 ),
5056 ];
5057
5058 for (source, message, wanted) in cases {
5059 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
5060 let mut opts = options();
5061 opts.std = std;
5062 opts.permissive = permissive;
5063 let said = run(&opts, source).messages.join("\n");
5064 let how = if permissive { " -fpermissive" } else { "" };
5065 assert!(
5066 said.contains(&format!(": {wanted}: {message}")),
5067 "under -std={}{how}, {source} was answered with `{said}`",
5068 std.as_str()
5069 );
5070 }
5071 }
5072 }
5073
5074 fn safe_ir(tier: rucc_session::Safety, source: &str) -> String {
5076 let mut opts = options();
5077 opts.emit = EmitKind::Ir;
5078 opts.safety = tier;
5079 let result = run(&opts, source);
5080 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5081 result.text().to_owned()
5082 }
5083
5084 const READS_THROUGH_A_POINTER: &str = "int read(int *p) { return p[1]; }\n";
5085
5086 fn padded_ir(padding: Padding, source: &str) -> String {
5088 let mut opts = options();
5089 opts.emit = EmitKind::Ir;
5090 opts.safety = rucc_session::Safety::Detect;
5091 opts.padding = padding;
5092 let result = run(&opts, source);
5093 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5094 result.text().to_owned()
5095 }
5096
5097 const FILLS_A_RECORD_A_MEMBER_AT_A_TIME: &str = "struct padded { char tag; int value; };\n\
5098 void fill(struct padded *p) { p->tag = 1; p->value = 2; }\n";
5099
5100 #[test]
5101 fn a_record_filled_a_member_at_a_time_comes_out_whole_when_padding_does_not_participate() {
5102 let text = padded_ir(Padding::Ignored, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
5106 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
5107 }
5108
5109 #[test]
5110 fn a_store_says_only_what_it_wrote_when_padding_does_participate() {
5111 let text = padded_ir(Padding::Tracked, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
5114 assert!(!text.contains("owns"), "{text}");
5115 }
5116
5117 #[test]
5118 fn a_member_of_a_union_owns_nothing_after_it() {
5119 let text = padded_ir(
5123 Padding::Ignored,
5124 "union u { char tag; long wide; };\nvoid fill(union u *p) { p->tag = 1; }\n",
5125 );
5126 assert!(!text.contains("owns"), "{text}");
5127 }
5128
5129 #[test]
5130 fn an_inner_records_trailing_padding_reaches_the_outer_records() {
5131 let text = padded_ir(
5136 Padding::Ignored,
5137 "struct inner { char c; };\n\
5138 struct outer { struct inner in; int x; };\n\
5139 void fill(struct outer *p) { p->in.c = 1; p->x = 2; }\n",
5140 );
5141 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
5142 }
5143
5144 #[test]
5145 fn a_build_that_did_not_ask_for_the_monitor_is_compiled_the_way_it_always_was() {
5146 let text = ir(READS_THROUGH_A_POINTER);
5150 assert!(!text.contains("check_"), "{text}");
5151 assert!(!text.contains("cap_of"), "{text}");
5152 }
5153
5154 #[test]
5155 fn asking_for_a_tier_puts_the_checks_in_before_the_optimizer_sees_them() {
5156 let text = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5157 assert!(text.contains("cap_of"), "{text}");
5158 assert!(text.contains("check_bounds"), "{text}");
5159 assert!(text.contains("check_live"), "{text}");
5160 assert!(text.contains("check_deriv"), "{text}");
5162 assert!(text.contains("check_type"), "{text}");
5164 }
5165
5166 #[test]
5167 fn the_three_tiers_that_are_not_off_all_check_the_same_accesses_so_far() {
5168 let detect = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5172 for tier in [rucc_session::Safety::Enforce, rucc_session::Safety::Kernel] {
5173 assert_eq!(safe_ir(tier, READS_THROUGH_A_POINTER), detect, "{tier}");
5174 }
5175 }
5176
5177 fn summary(tier: rucc_session::Safety, source: &str) -> String {
5179 let mut opts = options();
5180 opts.emit = EmitKind::SafetySummary;
5181 opts.safety = tier;
5182 let result = run(&opts, source);
5183 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5184 result.text().to_owned()
5185 }
5186
5187 #[test]
5188 fn the_summary_counts_the_checks_that_went_in_and_the_ones_still_standing() {
5189 let text = summary(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5190 assert!(text.contains("\"tier\": \"detect\""), "{text}");
5191 assert!(
5193 text.contains("\"bounds\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"),
5194 "{text}"
5195 );
5196 assert!(
5197 text.contains(
5198 "\"derivation\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"
5199 ),
5200 "{text}"
5201 );
5202 }
5203
5204 #[test]
5205 fn a_build_without_the_monitor_summarises_as_a_build_with_no_checks_in_it() {
5206 let text = summary(rucc_session::Safety::Off, READS_THROUGH_A_POINTER);
5210 assert!(text.contains("\"tier\": \"off\""), "{text}");
5211 assert!(
5212 text.contains("\"bounds\": { \"emitted\": 0, \"remaining\": 0, \"discharged\": 0 }"),
5213 "{text}"
5214 );
5215 }
5216
5217 #[test]
5218 fn a_call_the_boundary_models_is_counted_apart_from_one_it_does_not() {
5219 let text = summary(
5220 rucc_session::Safety::Detect,
5221 "void *memcpy(void *, const void *, unsigned long);\n\
5222 int puts(const char *);\n\
5223 void f(char *d, char *s) { memcpy(d, s, 4); puts(d); }\n",
5224 );
5225 assert!(text.contains("\"interposed\": 1"), "{text}");
5226 assert!(text.contains("\"puts\""), "{text}");
5227 assert!(!text.contains("__rucc_wrap_memcpy\""), "{text}");
5231 }
5232
5233 #[test]
5234 fn an_address_taken_of_a_library_function_is_counted_the_way_a_call_to_one_is() {
5235 let text = summary(
5240 rucc_session::Safety::Detect,
5241 "void *memcpy(void *, const void *, unsigned long);\n\
5242 int puts(const char *);\n\
5243 void *table[2] = { (void *)memcpy, (void *)puts };\n\
5244 void *f(int i) { return table[i]; }\n",
5245 );
5246 assert!(text.contains("\"interposed\": 1"), "{text}");
5247 assert!(text.contains("\"puts\""), "{text}");
5248 assert!(!text.contains("\"memcpy\""), "{text}");
5249 }
5250
5251 #[test]
5252 fn the_two_directions_a_pointer_crosses_the_boundary_are_counted_apart() {
5253 let text = summary(
5257 rucc_session::Safety::Detect,
5258 "void *notes_open(void);\n\
5259 char *f(char *p) { char *q = notes_open(); return q ? q : p; }\n",
5260 );
5261 assert!(text.contains("\"crossings\": { \"entered\": 1, \"returned\": 1 }"), "{text}");
5262 assert!(text.contains("\"notes_open\""), "{text}");
5263 }
5264
5265 #[test]
5266 fn a_static_function_nobody_takes_the_address_of_is_not_a_crossing() {
5267 let text = summary(
5270 rucc_session::Safety::Detect,
5271 "static int len(const char *p) { return p ? 1 : 0; }\n\
5272 int f(void) { return len(\"x\"); }\n",
5273 );
5274 assert!(text.contains("\"crossings\": { \"entered\": 0, \"returned\": 0 }"), "{text}");
5275 }
5276
5277 fn granules(source: &str) -> String {
5279 let mut opts = options();
5280 opts.emit = EmitKind::TypeGranules;
5281 let result = run(&opts, source);
5282 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5283 result.text().to_owned()
5284 }
5285
5286 #[test]
5287 fn the_granule_report_names_every_record_and_both_keyings() {
5288 let text = granules(
5289 "struct hot { char *p; int a; int b; };\n\
5290 int f(struct hot *h) { return h->a; }\n",
5291 );
5292 assert!(text.contains("struct hot"), "{text}");
5293 assert!(text.contains("every type distinct"), "{text}");
5296 assert!(text.contains("every pointer one type"), "{text}");
5297 assert!(text.contains("budget"), "{text}");
5298 }
5299
5300 #[test]
5301 fn a_record_nothing_uses_is_still_measured() {
5302 let text = granules("struct unused { long a; double b; };\nint f(void) { return 0; }\n");
5305 assert!(text.contains("struct unused"), "{text}");
5306 }
5307
5308 #[test]
5309 fn the_granule_report_stops_before_anything_is_lowered() {
5310 let text = granules(
5314 "struct wide { long double d; };\n\
5315 long double f(long double x) { return x * x; }\n",
5316 );
5317 assert!(text.contains("struct wide"), "{text}");
5318 }
5319
5320 #[test]
5321 fn a_witness_reaches_the_assembler_as_a_call_to_the_runtime() {
5322 let text = safe_asm(rucc_session::Safety::Detect, "char *f(char *p) { return p; }\n");
5325 assert!(text.contains("\tcall\t__rucc_cap_witness\n"), "{text}");
5326 }
5327
5328 #[test]
5329 fn a_pointer_turned_into_an_integer_is_on_the_trust_set() {
5330 let text = summary(
5331 rucc_session::Safety::Detect,
5332 "unsigned long f(int *p) { return (unsigned long) p; }\n",
5333 );
5334 assert!(text.contains("\"exposed\": 1"), "{text}");
5335 }
5336
5337 fn safe_asm(tier: rucc_session::Safety, source: &str) -> String {
5339 let mut opts = options();
5340 opts.emit = EmitKind::Asm;
5341 opts.safety = tier;
5342 let result = run(&opts, source);
5343 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5344 result.text().to_owned()
5345 }
5346
5347 #[test]
5348 fn a_check_reaches_the_assembler_as_a_call_to_the_runtime() {
5349 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5350 assert!(text.contains("\tcall\t__rucc_check_bounds\n"), "{text}");
5351 assert!(text.contains("\tcall\t__rucc_check_live\n"), "{text}");
5352 assert!(text.contains("\tcall\t__rucc_check_deriv\n"), "{text}");
5353 assert!(text.contains("\tcall\t__rucc_check_typed_init\n"), "{text}");
5356 }
5357
5358 #[test]
5359 fn every_check_that_reached_the_assembler_has_a_row_describing_it() {
5360 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5366 let section = format!("\t.section\t{},", rucc_safety::SECTION);
5367 assert_eq!(text.matches(§ion).count(), 4, "{text}");
5368 for index in 0..4 {
5369 let name = format!("__rucc_safety_desc_{index}");
5370 assert!(text.contains(&format!("{name}:\n")), "{text}");
5373 assert!(text.contains(&format!("{name}(%rip)")), "{text}");
5374 }
5375 assert!(!text.contains("__rucc_safety_desc_4"), "{text}");
5376 }
5377
5378 #[test]
5386 fn builtin_constant_p_is_folded_where_it_is_written_rather_than_called() {
5387 let text = ir(concat!(
5388 "int g;\n",
5389 "int a = __builtin_constant_p(1);\n",
5390 "int b = __builtin_constant_p(g);\n",
5391 "int c = __builtin_constant_p(\"abc\");\n",
5392 "int d = __builtin_constant_p(&g);\n",
5393 "int e = __builtin_constant_p(1.5);\n",
5394 "int h = __builtin_choose_expr(__builtin_constant_p(3), 11, 22);\n",
5395 ));
5396 assert!(text.contains("global @a : i32 = 1,"), "{text}");
5397 assert!(text.contains("global @b : i32 = 0,"), "{text}");
5398 assert!(text.contains("global @c : i32 = 1,"), "{text}");
5399 assert!(text.contains("global @d : i32 = 0,"), "{text}");
5400 assert!(text.contains("global @e : i32 = 1,"), "{text}");
5401 assert!(text.contains("global @h : i32 = 11,"), "{text}");
5402 assert!(!text.contains("__builtin_constant_p"), "it is not a call to anything:\n{text}");
5403
5404 let text = body("int f(void) { int i = 0; __builtin_constant_p(i++); return i; }\n");
5408 assert_eq!(text, "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 0\n return %0\n");
5409 }
5410
5411 #[test]
5420 fn a_call_to_a_library_builtin_reaches_the_library_function() {
5421 let text = body("void f(void) { __builtin_abort(); }\n");
5422 assert_eq!(text, "block0:\n call @abort() : ()\n return\n");
5423
5424 let text = ir("int f(const char *s) { return __builtin_puts(s) + __builtin_strlen(s); }\n");
5427 assert!(text.contains("call @puts(%0) : (ptr) -> i32"), "{text}");
5428 assert!(text.contains("call @strlen(%0) : (ptr) -> i64"), "{text}");
5429 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
5430 }
5431
5432 #[test]
5445 fn a_chk_builtin_reaches_the_checking_function_and_keeps_the_size() {
5446 let text = ir(concat!(
5447 "char d[8];\n",
5448 "void f(const char *s, unsigned long n) {\n",
5449 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
5450 " __builtin___strcpy_chk(d, s, __builtin_object_size(d, 1));\n",
5451 " __builtin___memset_chk(d, 0, n, 8);\n",
5452 "}\n",
5453 ));
5454 assert!(text.contains("call @__memcpy_chk("), "{text}");
5455 assert!(text.contains("call @__strcpy_chk("), "{text}");
5456 assert!(text.contains("call @__memset_chk("), "{text}");
5457 assert!(text.contains("iconst.i64 8"), "the object size reaches the call: {text}");
5458 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
5459 }
5460
5461 #[test]
5469 fn a_checking_call_whose_size_says_nothing_is_known_is_the_plain_library_call() {
5470 let text = ir(concat!(
5471 "extern char *p;\n",
5472 "char d[8];\n",
5473 "void f(const char *s, unsigned long n) {\n",
5474 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
5475 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
5476 " __builtin___strcpy_chk(p, s, __builtin_object_size(p, 0));\n",
5477 " __builtin___stpncpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
5478 " __builtin___sprintf_chk(p, 1, __builtin_object_size(p, 0), s);\n",
5479 "}\n",
5480 ));
5481
5482 assert!(
5484 text.contains("call @__memcpy_chk(%2, %0, %1, %3) : (ptr, ptr, i64, i64)"),
5485 "{text}"
5486 );
5487
5488 assert!(text.contains("call @memcpy(%6, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
5491 assert!(text.contains("call @strcpy(%10, %0) : (ptr, ptr) -> ptr"), "{text}");
5492 assert!(text.contains("call @stpncpy(%14, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
5493
5494 assert!(text.contains("call @__sprintf_chk("), "{text}");
5497
5498 let asm = asm(concat!(
5501 "void f(char *p, const char *s, unsigned long n) {\n",
5502 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
5503 "}\n",
5504 ));
5505 assert!(asm.contains("call\tmemcpy"), "{asm}");
5506 assert!(!asm.contains("$-1"), "the size that went away leaves no instruction:\n{asm}");
5507 }
5508
5509 #[test]
5517 fn the_v_spellings_of_the_chk_family_take_the_list_a_va_list_parameter_holds() {
5518 let text = ir(concat!(
5519 "char d[64];\n",
5520 "int f(const char *fmt, ...) {\n",
5521 " __builtin_va_list ap;\n",
5522 " __builtin_va_start(ap, fmt);\n",
5523 " int n = __builtin___vsprintf_chk(d, 1, __builtin_object_size(d, 0), fmt, ap);\n",
5524 " __builtin_va_end(ap);\n",
5525 " return n;\n",
5526 "}\n",
5527 ));
5528 assert!(text.contains("call @__vsprintf_chk("), "{text}");
5529 assert!(text.contains("iconst.i64 64"), "the object size reaches the call: {text}");
5530 }
5531
5532 #[test]
5543 fn the_absolute_value_family_is_the_magnitude_and_not_a_call() {
5544 let text = body(concat!(
5545 "long long llabs(long long);\n",
5546 "long long f(long long x) { return llabs(x); }\n",
5547 ));
5548 assert!(text.contains("%1 = iconst.i64 63"), "{text}");
5549 assert!(text.contains("%2 = ashr %0, %1"), "{text}");
5550 assert!(text.contains("%3 = xor %0, %2"), "{text}");
5551 assert!(text.contains("%4 = sub %3, %2"), "{text}");
5552 assert!(!text.contains("call"), "the call does not happen:\n{text}");
5553
5554 let text = body("int abs(int);\nint f(int x) { return abs(x); }\n");
5557 assert!(text.contains("iconst.i32 31"), "{text}");
5558 let text = body("long labs(long);\nlong f(long x) { return labs(x); }\n");
5559 assert!(text.contains("iconst.i64 63"), "{text}");
5560
5561 let text = body("long long f(long long x) { return __builtin_llabs(x); }\n");
5564 assert!(!text.contains("call"), "{text}");
5565
5566 let text = ir(concat!(
5568 "long long llabs(long long b);\n",
5569 "long long g(long long x) { return llabs(x); }\n",
5570 "long long llabs(long long b) { return 7; }\n",
5571 ));
5572 assert!(!text.contains("call @llabs"), "{text}");
5573 }
5574
5575 #[test]
5582 fn a_byte_swap_is_arithmetic_and_not_a_call() {
5583 let text = body("unsigned f(unsigned x) { return __builtin_bswap32(x); }\n");
5584 assert_eq!(text, "block0(%0: i32):\n %1 = bswap %0\n return %1\n");
5585
5586 let text = body("unsigned f(unsigned char c) { return __builtin_bswap32(c); }\n");
5589 assert!(text.contains("zext.i32 %0"), "widened first: {text}");
5590 assert!(text.contains("bswap %1"), "and swapped at four bytes: {text}");
5591 }
5592
5593 #[test]
5599 fn the_byte_swaps_reverse_at_the_width_their_name_says() {
5600 for (name, ty, width) in [
5601 ("__builtin_bswap16", "unsigned short", "i16"),
5602 ("__builtin_bswap32", "unsigned", "i32"),
5603 ("__builtin_bswap64", "unsigned long long", "i64"),
5604 ] {
5605 let source = format!("{ty} f({ty} x) {{ return {name}(x); }}\n");
5606 let text = body(&source);
5607 assert_eq!(
5608 text,
5609 format!("block0(%0: {width}):\n %1 = bswap %0\n return %1\n"),
5610 "{name}"
5611 );
5612 }
5613 }
5614
5615 #[test]
5622 fn the_bit_counts_are_instructions_and_not_calls() {
5623 let text = body("int f(unsigned x) { return __builtin_clz(x); }\n");
5624 assert_eq!(text, "block0(%0: i32):\n %1 = ctlz %0\n return %1\n");
5625
5626 let text = body("int f(unsigned x) { return __builtin_ctz(x); }\n");
5627 assert_eq!(text, "block0(%0: i32):\n %1 = cttz %0\n return %1\n");
5628
5629 let text = body("int f(unsigned x) { return __builtin_popcount(x); }\n");
5630 assert_eq!(text, "block0(%0: i32):\n %1 = ctpop %0\n return %1\n");
5631 }
5632
5633 #[test]
5642 fn the_bit_counts_ask_about_the_width_their_name_says() {
5643 let text = body("int f(unsigned long long x) { return __builtin_clzll(x); }\n");
5644 assert!(text.starts_with("block0(%0: i64):"), "counted at eight bytes: {text}");
5645 assert!(text.contains("%1 = ctlz %0"), "{text}");
5646 assert!(text.contains("trunc.i32 %1"), "and answered in an int: {text}");
5647
5648 let text = body("int f(unsigned long long x) { return __builtin_clz(x); }\n");
5651 assert!(text.contains("trunc.i32 %0"), "narrowed to what was asked about: {text}");
5652 assert!(text.contains("ctlz %1"), "and counted there: {text}");
5653
5654 let text = body("int f(unsigned long x) { return __builtin_popcountl(x); }\n");
5655 assert!(text.contains("%1 = ctpop %0"), "{text}");
5656 assert!(!text.contains("call"), "{text}");
5657 }
5658
5659 #[test]
5664 fn a_parity_is_the_low_bit_of_the_set_bit_count() {
5665 let text = body("int f(unsigned x) { return __builtin_parity(x); }\n");
5666 assert!(text.contains("%1 = ctpop %0"), "{text}");
5667 assert!(text.contains("iconst.i32 1"), "{text}");
5668 assert!(text.contains("and %1, %2"), "the low bit of it: {text}");
5669 }
5670
5671 #[test]
5677 fn the_first_set_bit_is_one_based_and_zero_for_a_zero() {
5678 let text = body("int f(int x) { return __builtin_ffs(x); }\n");
5679 assert!(text.contains("%1 = cttz %0"), "{text}");
5680 assert!(text.contains("%4 = add %1, %2"), "one more than the count: {text}");
5681 assert!(text.contains("%5 = icmp ne %0, %3"), "whether there was a bit at all: {text}");
5682 assert!(text.contains("%7 = sub %3, %6"), "spread to a mask: {text}");
5683 assert!(text.contains("%8 = and %4, %7"), "and kept only then: {text}");
5684 assert!(!text.contains("br_if"), "no branch: {text}");
5685 }
5686
5687 #[test]
5697 fn the_redundant_sign_bit_count_is_instructions_and_not_a_call() {
5698 let text = body("int f(int x) { return __builtin_clrsb(x); }\n");
5699 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
5700 assert!(text.contains("%2 = ashr %0, %1"), "the sign over every bit: {text}");
5701 assert!(text.contains("%3 = xor %0, %2"), "folded onto it: {text}");
5702 assert!(text.contains("%5 = shl %3, %4"), "one less than the count: {text}");
5703 assert!(text.contains("%6 = or %5, %4"), "with something to count at zero: {text}");
5704 assert!(text.contains("%7 = ctlz %6"), "{text}");
5705 assert!(!text.contains("call"), "{text}");
5706 assert!(!text.contains("br_if"), "no branch: {text}");
5707 }
5708
5709 #[test]
5715 fn the_unsigned_absolute_value_family_answers_in_the_unsigned_type() {
5716 let text = body("unsigned f(int x) { return __builtin_uabs(x); }\n");
5717 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
5718 assert!(text.contains("%4 = sub %3, %2"), "{text}");
5719 assert!(!text.contains("call"), "nothing declares uabs, so a call would not link: {text}");
5720
5721 let text = body("unsigned long long f(long long x) { return __builtin_ullabs(x); }\n");
5722 assert!(text.contains("iconst.i64 63"), "at the width the name says: {text}");
5723
5724 let text = body("int f(int x) { return __builtin_uabs(x) > 2147483647u; }\n");
5727 assert!(text.contains("icmp ugt"), "compared unsigned: {text}");
5728 }
5729
5730 #[test]
5738 fn the_widest_absolute_value_is_whichever_type_the_target_makes_intmax_t() {
5739 let text = body("long f(long x) { return __builtin_imaxabs(x); }\n");
5740 assert!(text.contains("iconst.i64 63"), "{text}");
5741 assert!(text.contains("%4 = sub %3, %2"), "{text}");
5742 assert!(!text.contains("call"), "{text}");
5743
5744 let text = body("unsigned long f(long x) { return __builtin_umaxabs(x); }\n");
5745 assert!(text.contains("iconst.i64 63"), "{text}");
5746 assert!(!text.contains("call"), "{text}");
5747 }
5748
5749 #[test]
5757 fn an_overflow_predicate_writes_nothing_and_answers_the_bit_the_check_would() {
5758 let text =
5759 body("int f(int a, int b) { return __builtin_add_overflow_p(a, b, (int) 0); }\n");
5760 assert!(text.contains("%2, %3 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
5761 assert!(!text.contains("store"), "nothing is written: {text}");
5762 assert!(!text.contains("call"), "{text}");
5763
5764 let text =
5767 body("int f(int a, int b) { return __builtin_mul_overflow_p(a, b, (long long) 0); }\n");
5768 assert!(text.contains("smul_overflow.(i64, i1)"), "{text}");
5769 assert!(!text.contains("store"), "{text}");
5770
5771 let text = body(concat!(
5774 "int g(void);\n",
5775 "int f(int a, int b) { return __builtin_sub_overflow_p(a, b, g()); }\n",
5776 ));
5777 assert!(!text.contains("call @g"), "the third argument is not evaluated: {text}");
5778 }
5779
5780 #[test]
5790 fn an_overflow_check_is_arithmetic_and_not_a_call() {
5791 let text =
5792 body("int f(int a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
5793 assert!(text.contains("%3, %4 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
5794 assert!(text.contains("store %3 -> %2"), "{text}");
5795 assert!(!text.contains("call"), "{text}");
5796
5797 let text =
5798 body("int f(int a, int b, int *r) { return __builtin_sub_overflow(a, b, r); }\n");
5799 assert!(text.contains("ssub_overflow.(i32, i1) %0, %1"), "{text}");
5800
5801 let text =
5802 body("int f(int a, int b, int *r) { return __builtin_mul_overflow(a, b, r); }\n");
5803 assert!(text.contains("smul_overflow.(i32, i1) %0, %1"), "{text}");
5804
5805 let text = body(
5808 "int f(unsigned a, unsigned b, unsigned *r) { return __builtin_add_overflow(a, b, r); }\n",
5809 );
5810 assert!(text.contains("uadd_overflow.(i32, i1) %0, %1"), "{text}");
5811 }
5812
5813 #[test]
5821 fn an_overflow_check_is_done_at_a_type_that_holds_every_operand() {
5822 let text = body(
5823 "int f(unsigned a, int b, long long *r) { return __builtin_add_overflow(a, b, r); }\n",
5824 );
5825 assert!(text.contains("%3 = zext.i64 %0"), "the unsigned operand keeps its value: {text}");
5826 assert!(text.contains("%4 = sext.i64 %1"), "and so does the signed one: {text}");
5827 assert!(text.contains("sadd_overflow.(i64, i1) %3, %4"), "{text}");
5828
5829 let text = body(
5832 "int f(long long a, long long b, long long *r) { return __builtin_mul_overflow(a, b, r); }\n",
5833 );
5834 assert!(text.contains("smul_overflow.(i64, i1) %0, %1"), "{text}");
5835 assert!(!text.contains("sext."), "{text}");
5836 assert!(!text.contains("zext.i64"), "{text}");
5838 }
5839
5840 #[test]
5848 fn an_overflow_check_writes_the_wrapped_answer_whether_or_not_it_fit() {
5849 let text =
5850 body("int f(int a, int b, char *r) { return __builtin_sub_overflow(a, b, r); }\n");
5851 assert!(text.contains("%3, %4 = ssub_overflow.(i32, i1) %0, %1"), "{text}");
5852 assert!(text.contains("%5 = trunc.i8 %3"), "narrowed to where it goes: {text}");
5853 assert!(text.contains("%6 = sext.i32 %5"), "and back: {text}");
5854 assert!(text.contains("%7 = icmp ne %6, %3"), "which is whether it fit: {text}");
5855 assert!(text.contains("store %5 -> %2"), "the narrowed value is stored either way: {text}");
5856 assert!(text.contains("%8 = or %4, %7"), "and either bit is an overflow: {text}");
5857 }
5858
5859 #[test]
5866 fn a_call_needing_more_than_the_widest_type_still_compiles() {
5867 for name in ["add", "sub", "mul"] {
5868 let source = format!(
5869 "int f(unsigned __int128 a, long long b, __int128 *r) {{\n \
5870 return __builtin_{name}_overflow(a, b, r);\n}}\n"
5871 );
5872 let mut opts = options();
5873 opts.emit = EmitKind::MirFinal;
5874 assert!(!run(&opts, &source).failed(), "{name} was refused or stopped the back end");
5875 }
5876 }
5877
5878 #[test]
5881 fn an_overflow_check_over_something_that_is_not_an_integer_says_so() {
5882 let messages =
5883 errors("int f(double a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
5884 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
5885
5886 let messages =
5887 errors("int f(int a, int b, double *r) { return __builtin_add_overflow(a, b, r); }\n");
5888 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
5889 }
5890
5891 #[test]
5902 fn an_ordered_access_is_ordered_in_the_ir() {
5903 let text = body("int f(int *p) { return __atomic_load_n(p, 0); }\n");
5904 assert!(text.contains("atomic_load.i32 %0, align 4, relaxed"), "{text}");
5905
5906 let text = body("long f(long *p) { return __atomic_load_n(p, 2); }\n");
5907 assert!(text.contains("atomic_load.i64 %0, align 8, acquire"), "{text}");
5908
5909 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
5910 assert!(text.contains("atomic_store %1 -> %0, align 4, release"), "{text}");
5911
5912 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
5913 assert!(text.contains("atomic_store %1 -> %0, align 4, seq_cst"), "{text}");
5914
5915 let text = body("void f(char *p, int v) { __atomic_store_n(p, v, 0); }\n");
5918 assert!(text.contains("trunc.i8 %1"), "{text}");
5919 assert!(text.contains("atomic_store %2 -> %0, align 1, relaxed"), "{text}");
5920 }
5921
5922 #[test]
5931 fn an_ordered_access_is_the_plain_instruction_on_this_machine() {
5932 let text = asm("int f(int *p) { return __atomic_load_n(p, 5); }\n");
5933 assert!(text.contains("movl\t(%rdi), %eax"), "{text}");
5934 assert!(!text.contains("mfence"), "a load needs no barrier here: {text}");
5935
5936 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
5937 assert!(text.contains("movl\t%esi, (%rdi)"), "{text}");
5938 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
5939
5940 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
5941 let (before, after) = text.split_once("mfence").expect("a barrier: {text}");
5942 assert!(before.contains("movl\t%esi, (%rdi)"), "the store comes first: {text}");
5943 assert!(!after.contains("movl"), "and nothing else is between them: {text}");
5944 }
5945
5946 #[test]
5956 fn a_barrier_is_one_instruction_at_the_strongest_ordering_and_none_below_it() {
5957 assert!(asm("void f(void) { __atomic_thread_fence(5); }\n").contains("mfence"));
5958 assert!(asm("void f(void) { __sync_synchronize(); }\n").contains("mfence"));
5959
5960 for weaker in ["1", "2", "3", "4"] {
5961 let source = format!("void f(void) {{ __atomic_thread_fence({weaker}); }}\n");
5962 assert!(!asm(&source).contains("mfence"), "{weaker} costs nothing here");
5963 }
5964 }
5965
5966 #[test]
5976 fn the_three_x86_fences_are_the_barrier_the_strongest_ordering_gives() {
5977 for name in ["__builtin_ia32_sfence", "__builtin_ia32_lfence", "__builtin_ia32_mfence"] {
5978 let source = format!("void f(void) {{ {name}(); }}\n");
5979 assert!(asm(&source).contains("mfence"), "{name} is a barrier");
5980 let text = body(&source);
5981 assert!(text.contains("fence seq_cst"), "{name}: {text}");
5982 }
5983
5984 let result = run(&options(), "void f(void) { __builtin_ia32_sfence(1); }\n");
5985 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
5986 assert!(result.messages[0].contains("too many arguments"), "{:?}", result.messages);
5987 }
5988
5989 #[test]
5995 fn a_compare_and_exchange_is_one_instruction_answering_two_things() {
5996 let text =
5999 body("int f(int *p, int e, int d) { return __sync_val_compare_and_swap(p, e, d); }\n");
6000 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
6001 assert!(text.contains("return %3"), "the value it found: {text}");
6002
6003 let text =
6004 body("int f(int *p, int e, int d) { return __sync_bool_compare_and_swap(p, e, d); }\n");
6005 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
6006 assert!(text.contains("zext.i32 %4"), "whether it happened: {text}");
6007
6008 let text = body(
6011 "int f(int *p, int *e, int d) { return __atomic_compare_exchange_n(p, e, d, 0, 4, 2); }\n",
6012 );
6013 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
6014 assert!(text.contains("%4, %5 = cmpxchg.(i32, i1) %0, %3, %2, align 4, acq_rel"), "{text}");
6015 assert!(text.contains("br_if %5, block2, block1"), "{text}");
6016 assert!(text.contains("store %4 -> %1, align 4"), "{text}");
6017
6018 let text = body(
6021 "int f(int *p, int *e, int *d) { return __atomic_compare_exchange(p, e, d, 0, 5, 5); }\n",
6022 );
6023 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
6024 assert!(text.contains("%4 = load.i32 %2, align 4"), "{text}");
6025 assert!(text.contains("%5, %6 = cmpxchg.(i32, i1) %0, %3, %4, align 4, seq_cst"), "{text}");
6026 }
6027
6028 #[test]
6035 fn a_compare_and_exchange_is_a_locked_instruction_at_the_width_of_the_object() {
6036 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
6037 for (ty, suffix, reg) in widths {
6038 let source = format!(
6039 "int f({ty} *p, {ty} e, {ty} d) {{ return __sync_bool_compare_and_swap(p, e, d); }}\n"
6040 );
6041 let text = asm(&source);
6042 assert!(text.contains("\tlock\n"), "{ty}: {text}");
6043 assert!(text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
6044 assert!(text.contains("sete\t"), "{ty}: {text}");
6045 }
6046 let source =
6047 "int f(long *p, long e, long d) { return __sync_bool_compare_and_swap(p, e, d); }\n";
6048 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
6049
6050 for order in ["0", "2", "3", "4", "5"] {
6054 let call = format!("__atomic_compare_exchange_n(p, e, d, 0, {order}, 0)");
6055 let source = format!("int f(int *p, int *e, int d) {{ return {call}; }}\n");
6056 let text = asm(&source);
6057 assert!(text.contains("cmpxchgl\t"), "{order}: {text}");
6058 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
6059 }
6060 }
6061
6062 #[test]
6074 fn a_read_modify_write_is_one_instruction_and_the_arithmetic_a_name_asks_for() {
6075 let text = body("int f(int *p, int v) { return __atomic_fetch_add(p, v, 5); }\n");
6076 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
6077 assert!(text.contains("return %2"), "the value that was there: {text}");
6078
6079 let text = body("int f(int *p, int v) { return __atomic_add_fetch(p, v, 5); }\n");
6080 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
6081 assert!(text.contains("%3 = add %2, %1"), "and the value afterwards: {text}");
6082
6083 let text = body("int f(int *p, int v) { return __atomic_sub_fetch(p, v, 5); }\n");
6084 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
6085 assert!(text.contains("%3 = sub %2, %1"), "{text}");
6086
6087 let text = body("int f(int *p, int v) { return __sync_fetch_and_sub(p, v); }\n");
6089 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
6090
6091 let text = body("int f(int *p, int v) { return __atomic_exchange_n(p, v, 5); }\n");
6094 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, seq_cst"), "{text}");
6095
6096 let text = body("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
6097 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, acquire"), "{text}");
6098
6099 let text = body("void f(int *p) { __sync_lock_release(p); }\n");
6102 assert!(text.contains("release"), "{text}");
6103 assert!(text.contains("%1 = iconst.i32 0"), "{text}");
6104
6105 let text = body("void f(int *p, int guard) { __sync_lock_release(p, guard); }\n");
6109 assert!(text.contains("%2 = iconst.i32 0"), "{text}");
6110 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
6111
6112 let text = body("int f(int *p, int v) { return __atomic_fetch_and(p, v, 5); }\n");
6115 assert!(text.contains("%2 = atomic_rmw.i32 and %0, %1, align 4, seq_cst"), "{text}");
6116
6117 let text = body("int f(int *p, int v) { return __sync_or_and_fetch(p, v); }\n");
6118 assert!(text.contains("%2 = atomic_rmw.i32 or %0, %1, align 4, seq_cst"), "{text}");
6119 assert!(text.contains("%3 = or %2, %1"), "and the value afterwards: {text}");
6120
6121 let text = body("int f(int *p, int v) { return __atomic_nand_fetch(p, v, 5); }\n");
6124 assert!(text.contains("%2 = atomic_rmw.i32 nand %0, %1, align 4, seq_cst"), "{text}");
6125 assert!(text.contains("%3 = and %2, %1"), "{text}");
6126 assert!(text.contains("%4 = iconst.i32 -1"), "{text}");
6127 assert!(text.contains("%5 = xor %3, %4"), "{text}");
6128 }
6129
6130 #[test]
6141 fn a_bitwise_read_modify_write_is_a_loop_around_the_compare_and_exchange() {
6142 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
6143 for (ty, suffix, reg) in widths {
6144 for (name, call, insn) in [
6145 ("and", "__atomic_fetch_and(p, v, 5)", "and"),
6146 ("or", "__sync_fetch_and_or(p, v)", "or"),
6147 ("xor", "__atomic_xor_fetch(p, v, 5)", "xor"),
6148 ] {
6149 let source = format!("{ty} f({ty} *p, {ty} v) {{ return {call}; }}\n");
6150 let text = asm(&source);
6151 assert!(text.contains("\tlock\n"), "{ty} {name}: {text}");
6152 assert!(
6153 text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")),
6154 "{ty} {name}: {text}"
6155 );
6156 assert!(text.contains(&format!("{insn}{suffix}\t")), "{ty} {name}: {text}");
6157 assert!(!text.contains("\txadd"), "{ty} {name} is not an add: {text}");
6159 assert!(!text.contains("\txchg"), "{ty} {name} is not an exchange: {text}");
6160 }
6161 }
6162 let source = "long f(long *p, long v) { return __atomic_fetch_or(p, v, 5); }\n";
6163 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
6164
6165 let text = asm("int f(int *p, int v) { return __sync_fetch_and_nand(p, v); }\n");
6169 assert!(text.contains("cmpxchgl\t"), "{text}");
6170 assert!(text.contains("andl\t"), "{text}");
6171 assert!(text.contains("notl\t"), "{text}");
6172 }
6173
6174 #[test]
6183 fn an_access_through_a_second_pointer_is_the_same_access_and_one_more() {
6184 let text = body("void f(int *p, int *r) { __atomic_load(p, r, 5); }\n");
6185 assert!(text.contains("%2 = atomic_load.i32 %0, align 4, seq_cst"), "{text}");
6186 assert!(text.contains("store %2 -> %1, align 4"), "and out through the place: {text}");
6187
6188 let text = body("void f(int *p, int *v) { __atomic_store(p, v, 3); }\n");
6189 assert!(text.contains("%2 = load.i32 %1, align 4"), "in through the place: {text}");
6190 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
6191
6192 let text = body("void f(int *p, int *v, int *r) { __atomic_exchange(p, v, r, 5); }\n");
6195 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
6196 assert!(text.contains("%4 = atomic_rmw.i32 xchg %0, %3, align 4, seq_cst"), "{text}");
6197 assert!(text.contains("store %4 -> %2, align 4"), "{text}");
6198 }
6199
6200 #[test]
6211 fn a_flag_is_an_exchange_of_one_byte_and_a_store_of_a_zero_over_the_same_byte() {
6212 for pointer in ["char", "int", "void"] {
6213 let source = format!("int f({pointer} *p) {{ return __atomic_test_and_set(p, 5); }}\n");
6214 let text = body(&source);
6215 assert!(text.contains("%1 = iconst.i8 1"), "{pointer}: {text}");
6216 assert!(
6217 text.contains("%2 = atomic_rmw.i8 xchg %0, %1, align 1, seq_cst"),
6218 "{pointer}: {text}"
6219 );
6220 assert!(text.contains("%4 = icmp ne %2, %3"), "{pointer}: {text}");
6221
6222 let source = format!("void f({pointer} *p) {{ __atomic_clear(p, 3); }}\n");
6223 let text = body(&source);
6224 assert!(text.contains("atomic_store %2 -> %0, align 1, release"), "{pointer}: {text}");
6225 }
6226
6227 let text = asm("int f(int *p) { return __atomic_test_and_set(p, 5); }\n");
6230 assert!(text.contains("xchgb\t%al, (%rdi)"), "{text}");
6231 assert!(text.contains("setne\t"), "{text}");
6232 }
6233
6234 #[test]
6242 fn a_read_modify_write_is_an_exchange_or_a_locked_add_at_the_width_of_the_object() {
6243 let widths = [("char", "b", "%sil"), ("short", "w", "%si"), ("int", "l", "%esi")];
6244 for (ty, suffix, reg) in widths {
6245 let source =
6246 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_fetch_add(p, v, 5); }}\n");
6247 let text = asm(&source);
6248 assert!(text.contains("\tlock\n"), "{ty}: {text}");
6249 assert!(text.contains(&format!("xadd{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
6250
6251 let source =
6252 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_exchange_n(p, v, 5); }}\n");
6253 let text = asm(&source);
6254 assert!(text.contains(&format!("xchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
6255 assert!(!text.contains("\tlock\n"), "an exchange is locked already: {ty}: {text}");
6256 }
6257 let source = "long f(long *p, long v) { return __atomic_fetch_add(p, v, 5); }\n";
6258 assert!(asm(source).contains("xaddq\t%rsi, (%rdi)"), "{}", asm(source));
6259
6260 let source = "int f(int *p, int v) { return __atomic_fetch_sub(p, v, 5); }\n";
6263 let text = asm(source);
6264 assert!(text.contains("negl\t"), "{text}");
6265 assert!(text.contains("xaddl\t"), "{text}");
6266
6267 for order in ["0", "2", "3", "4", "5"] {
6270 let source =
6271 format!("int f(int *p, int v) {{ return __atomic_fetch_add(p, v, {order}); }}\n");
6272 let text = asm(&source);
6273 assert!(text.contains("xaddl\t"), "{order}: {text}");
6274 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
6275 }
6276
6277 let text = asm("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
6281 assert!(text.contains("xchgl\t%esi, (%rdi)"), "{text}");
6282 let text = asm("void f(int *p) { __sync_lock_release(p); }\n");
6289 assert!(text.contains("xorl\t%eax, %eax"), "{text}");
6290 assert!(text.contains("movl\t%eax, (%rdi)"), "{text}");
6291 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
6292 }
6293
6294 #[test]
6306 fn the_lock_free_questions_are_answered_as_constants() {
6307 for size in ["1", "2", "4", "8"] {
6308 let source =
6309 format!("int f(void) {{ return __atomic_always_lock_free({size}, 0); }}\n");
6310 let text = asm(&source);
6311 assert!(text.contains("movb\t$1, %al"), "{size} bytes is lock free: {text}");
6312 assert!(!text.contains("call"), "and is not a call: {text}");
6313 }
6314 for size in ["3", "16", "sizeof(long double)"] {
6315 let source = format!("int f(void) {{ return __atomic_is_lock_free({size}, 0); }}\n");
6316 let text = asm(&source);
6317 assert!(text.contains("movb\t$0, %al"), "{size} bytes is not: {text}");
6318 assert!(!text.contains("call"), "and is not a call either: {text}");
6319 }
6320
6321 let text = asm("int f(int n) { return __atomic_is_lock_free(n, 0); }\n");
6325 assert!(text.contains("movb\t$0, %al"), "a size nobody knows is not lock free: {text}");
6326 let text = asm("int f(int *p) { return __atomic_always_lock_free(8, p); }\n");
6327 assert!(text.contains("movb\t$0, %al"), "eight bytes at four is not: {text}");
6328 let text = asm("int f(long *p) { return __atomic_always_lock_free(8, p); }\n");
6329 assert!(text.contains("movb\t$1, %al"), "and at eight it is: {text}");
6330 }
6331
6332 #[test]
6344 fn a_memory_order_an_operation_cannot_carry_is_read_as_the_strongest() {
6345 let mut opts = options();
6346 opts.emit = EmitKind::Ir;
6347
6348 let acquire_store = run(&opts, "void f(int *p, int v) { __atomic_store_n(p, v, 2); }\n");
6349 assert!(acquire_store.text().contains("seq_cst"), "{:?}", acquire_store.text());
6350 assert!(acquire_store.messages[0].contains("[W0333]"), "{:?}", acquire_store.messages);
6351
6352 let nonsense = run(&opts, "int f(int *p) { return __atomic_load_n(p, 99); }\n");
6353 assert!(nonsense.text().contains("seq_cst"), "{:?}", nonsense.text());
6354 assert!(nonsense.messages[0].contains("[W0333]"), "{:?}", nonsense.messages);
6355
6356 let computed = run(&opts, "int f(int *p, int n) { return __atomic_load_n(p, n); }\n");
6357 assert!(computed.text().contains("seq_cst"), "{:?}", computed.text());
6358 assert_eq!(computed.messages, Vec::<String>::new(), "a computed order is not a mistake");
6359 }
6360
6361 #[test]
6373 fn a_conversion_between_a_float_and_the_widest_unsigned_integer_is_written_without_a_branch() {
6374 let text = asm("double f(unsigned long long x) { return (double)x; }\n");
6375 assert!(text.contains("cvtsi2sdq"), "the signed conversion is what runs: {text}");
6376 assert!(text.contains("shrq"), "with the value halved first: {text}");
6377 assert!(text.contains("addsd"), "and doubled after: {text}");
6378 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
6379
6380 let text = asm("unsigned long long f(double d) { return (unsigned long long)d; }\n");
6381 assert!(text.contains("cvttsd2siq"), "the signed conversion is what runs: {text}");
6382 assert!(text.contains("subsd"), "with half the range taken off first: {text}");
6383 assert!(text.contains("shlq\t$63"), "and the top bit put back: {text}");
6384 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
6385 }
6386
6387 #[test]
6398 fn a_plain_name_the_program_took_is_the_programs_own_function() {
6399 let taken = concat!(
6400 "static long long llabs(long long b) { return 7; }\n",
6401 "long long f(long long x) { return llabs(x); }\n",
6402 );
6403 assert!(ir(taken).contains("call @llabs"), "a static definition is the program's own");
6404
6405 let retyped = concat!("int llabs(int b);\n", "int f(int x) { return llabs(x); }\n",);
6406 assert!(ir(retyped).contains("call @llabs"), "another type is another function");
6407
6408 let plain = concat!(
6409 "long long llabs(long long b);\n",
6410 "long long f(long long x) { return llabs(x); }\n",
6411 );
6412 let mut opts = options();
6413 opts.emit = EmitKind::Ir;
6414 assert!(!run(&opts, plain).text().contains("call @llabs"), "the library's by default");
6415
6416 opts.builtins = false;
6417 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin");
6418
6419 opts.builtins = true;
6420 opts.no_builtin = vec!["llabs".to_owned()];
6421 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin-llabs");
6422 let one = "long labs(long b);\nlong f(long x) { return labs(x); }\n";
6423 assert!(!run(&opts, one).text().contains("call @labs"), "one name and not the family");
6424
6425 opts.no_builtin = Vec::new();
6428 opts.builtins = false;
6429 let prefixed = "long long f(long long x) { return __builtin_llabs(x); }\n";
6430 assert!(!run(&opts, prefixed).text().contains("call @llabs"), "the prefix is a promise");
6431 }
6432
6433 #[test]
6446 fn the_hint_builtins_are_their_first_argument_and_the_hint_leaves_no_trace() {
6447 let text = ir(concat!(
6448 "long a = __builtin_expect(7, 1);\n",
6449 "long b = __builtin_expect_with_probability(9, 1, 0.9);\n",
6450 "unsigned long c = sizeof(__builtin_expect((char)1, 1));\n",
6451 ));
6452 assert!(text.contains("global @a : i64 = 7,"), "{text}");
6453 assert!(text.contains("global @b : i64 = 9,"), "{text}");
6454 assert!(text.contains("global @c : i64 = 8,"), "{text}");
6455 assert!(!text.contains("__builtin_expect"), "it is not a call to anything:\n{text}");
6456
6457 let text = body("long f(char c) { return __builtin_expect(c, 1); }\n");
6460 assert!(text.contains("sext"), "{text}");
6461
6462 let one = "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 1\n %2 = sext.i64 %1\n return %0\n";
6466 assert_eq!(body("int f(void) { int i = 0; __builtin_expect(1, i++); return i; }\n"), one);
6467 let source = "int g(void) { int i = 0; __builtin_expect_with_probability(1, i++, 0.5); return i; }\n";
6468 assert_eq!(body(source), one);
6469
6470 let kept = body("int f(int n) { int i = 0; __builtin_expect(n, i++); return i; }\n");
6475 assert!(kept.contains("add.nsw"), "the hint still runs: {kept}");
6476 assert!(kept.ends_with("return %3\n"), "and the answer is what it left behind: {kept}");
6477 let both = "int g(int n) { int i = 0; __builtin_expect_with_probability(n, i++, 0.5); return i; }\n";
6478 assert!(body(both).contains("add.nsw"), "and so does the one with three arguments");
6479 }
6480
6481 #[test]
6493 fn a_promise_that_control_does_not_arrive_writes_no_instruction() {
6494 let promised = "int f(int x) { if (x) return 1; __builtin_unreachable(); }\n";
6495 let text = ir(promised);
6496 assert!(text.contains(" unreachable_hint\n"), "{text}");
6497 assert!(!text.contains("call"), "it is not a call to anything:\n{text}");
6498
6499 let after = body("int g(int x) { __builtin_unreachable(); return x; }\n");
6503 assert!(after.contains("return"), "{after}");
6504
6505 let text = asm(promised);
6508 let mine = text.split_once("\nf:\n").expect("a definition").1;
6509 let mine = mine.split_once("\t.size").expect("a definition").0;
6510 let plain = asm("int f(int x) { if (x) return 1; }\n");
6511 let plain = plain.split_once("\nf:\n").expect("a definition").1;
6512 let plain = plain.split_once("\t.size").expect("a definition").0;
6513 assert_eq!(mine, plain);
6514 let last = mine.lines().rfind(|line| !line.trim_start().starts_with('.'));
6517 assert_eq!(last.map(str::trim), Some("ret"), "{mine}");
6518 assert!(!mine.contains("ud2"), "{mine}");
6519 }
6520
6521 #[test]
6528 fn a_library_builtin_is_diagnosed_under_the_name_the_program_wrote() {
6529 let mut opts = options();
6530 opts.emit = EmitKind::Ir;
6531 let messages = run(&opts, "void f(void) { __builtin_abort(1); }\n").messages;
6532 assert!(
6533 messages.iter().any(|m| m.contains("__builtin_abort")),
6534 "expected the written name in {messages:?}"
6535 );
6536 }
6537
6538 #[test]
6546 fn a_builtin_nothing_lowers_is_refused_by_name() {
6547 let mut opts = options();
6548 opts.emit = EmitKind::Ir;
6549 let builtin = "__atomic_signal_fence";
6550 let source = format!("int counter;\nint f(void) {{ return ({builtin}(5), 0); }}\n");
6551 let messages = run(&opts, &source).messages;
6552 let named = messages.iter().any(|m| m.contains(builtin) && m.contains("E0686"));
6553 assert!(named, "expected {builtin} to be refused by name in {messages:?}");
6554 }
6555
6556 #[test]
6565 fn what_is_refused_is_the_call_and_not_the_name() {
6566 let text = ir(concat!(
6567 "void __atomic_signal_fence(int order) { (void)order; }\n",
6568 "void f(void) { __atomic_signal_fence(5); }\n",
6569 ));
6570 assert!(text.contains("call @__atomic_signal_fence"), "{text}");
6571 }
6572
6573 #[test]
6582 fn the_object_size_of_an_address_is_what_the_layout_leaves_in_front_of_it() {
6583 let text = ir(concat!(
6584 "struct S { char a[8]; int n; char b[12]; };\n",
6585 "char g[32];\n",
6586 "struct S gs;\n",
6587 "unsigned long whole = __builtin_object_size(g, 0);\n",
6588 "unsigned long moved = __builtin_object_size(g + 4, 0);\n",
6589 "unsigned long back = __builtin_object_size(g + 30 - 2, 0);\n",
6590 "unsigned long outer = __builtin_object_size(gs.a, 0);\n",
6591 "unsigned long inner = __builtin_object_size(gs.a, 1);\n",
6592 "unsigned long scalar = __builtin_object_size(&gs.n, 1);\n",
6593 "unsigned long after = __builtin_object_size(&gs.n, 0);\n",
6594 "unsigned long into = __builtin_object_size(&gs.b[2], 1);\n",
6595 "unsigned long text = __builtin_object_size(\"hello\", 0);\n",
6596 "unsigned long dyn = __builtin_dynamic_object_size(gs.b, 1);\n",
6597 ));
6598 for (name, size) in [
6599 ("whole", 32),
6600 ("moved", 28),
6601 ("back", 4),
6602 ("outer", 24),
6603 ("inner", 8),
6604 ("scalar", 4),
6605 ("after", 16),
6606 ("into", 10),
6607 ("text", 6),
6608 ("dyn", 12),
6609 ] {
6610 let said = format!("global @{name} : i64 = {size},");
6611 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
6612 }
6613 }
6614
6615 #[test]
6623 fn the_object_behind_an_address_can_be_one_with_automatic_storage() {
6624 let text = body(concat!(
6625 "struct S { char a[8]; int n; char b[12]; };\n",
6626 "unsigned long f(void) {\n",
6627 " char loc[20];\n",
6628 " struct S ls;\n",
6629 " return __builtin_object_size(loc + 3, 0) + __builtin_object_size(ls.b + 2, 1);\n",
6630 "}\n",
6631 ));
6632 assert!(text.contains("iconst.i64 17"), "twenty bytes with three used: {text}");
6633 assert!(text.contains("iconst.i64 10"), "twelve bytes with two used: {text}");
6634 }
6635
6636 #[test]
6646 fn an_address_with_no_object_in_sight_answers_at_the_end_of_the_range_its_kind_asks_for() {
6647 let text = ir(concat!(
6648 "struct T { int n; char f[]; };\n",
6649 "extern char *p;\n",
6650 "extern struct T *t;\n",
6651 "unsigned long largest = __builtin_object_size(p, 0);\n",
6652 "unsigned long nearest = __builtin_object_size(p, 1);\n",
6653 "unsigned long least = __builtin_object_size(p, 2);\n",
6654 "unsigned long tight = __builtin_object_size(p, 3);\n",
6655 "unsigned long flex = __builtin_object_size(t->f, 1);\n",
6656 "int says = __builtin_object_size(p, 0) == (unsigned long)-1;\n",
6657 ));
6658 for name in ["largest", "nearest", "flex"] {
6659 let said = format!("global @{name} : i64 = -1,");
6663 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
6664 }
6665 for name in ["least", "tight"] {
6666 let said = format!("global @{name} : i64 = 0,");
6667 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
6668 }
6669 assert!(text.contains("global @says : i32 = 1,"), "{text}");
6670 }
6671
6672 #[test]
6679 fn the_address_an_object_size_is_asked_about_is_not_evaluated() {
6680 let text = body(concat!(
6681 "extern char *side(void);\n",
6682 "unsigned long f(void) { return __builtin_object_size(side(), 0); }\n",
6683 ));
6684 assert!(!text.contains("call"), "nothing is called: {text}");
6685 }
6686
6687 #[test]
6692 fn a_kind_that_is_not_one_of_the_four_is_refused() {
6693 for source in [
6694 "extern char *p;\nextern int k;\nunsigned long f(void) ".to_owned()
6695 + "{ return __builtin_object_size(p, k); }\n",
6696 "extern char *p;\nunsigned long f(void) { return __builtin_object_size(p, 4); }\n"
6697 .to_owned(),
6698 "extern char *p;\nunsigned long f(void) ".to_owned()
6699 + "{ return __builtin_dynamic_object_size(p, -1); }\n",
6700 ] {
6701 let messages = errors(&source);
6702 let named = messages.iter().any(|m| m.contains("E0709") && m.contains("0 to 3"));
6703 assert!(named, "expected a complaint about the kind in {messages:?}");
6704 }
6705 }
6706
6707 #[test]
6713 fn the_pair_that_saves_a_place_lowers_to_the_two_markers() {
6714 let text = ir(concat!(
6715 "void *buf[5];\n",
6716 "int f(void) {\n",
6717 " if (__builtin_setjmp(buf)) return 2;\n",
6718 " return 1;\n",
6719 "}\n",
6720 "void g(void) { __builtin_longjmp(buf, 1); }\n",
6721 ));
6722 assert!(text.contains("= setjmp_marker.i32 %0\n"), "the save answers a value: {text}");
6723 assert!(text.contains(" longjmp_marker %0\n"), "the restore answers nothing: {text}");
6724 assert!(!text.contains("call @"), "neither of them is a call: {text}");
6725 }
6726
6727 #[test]
6735 fn a_local_of_a_function_that_saves_a_place_gets_a_slot() {
6736 let text = ir(concat!(
6737 "void *buf[5];\n",
6738 "int f(int x) { int a = 0; if (__builtin_setjmp(buf)) return a; a = 1; return x; }\n",
6739 "int g(int x) { int a = 0; if (x) return a; a = 1; return x; }\n",
6740 ));
6741 let (saves, plain) = text.split_once("func @g").expect("both functions");
6742 assert_eq!(saves.matches("= alloca").count(), 2, "the parameter and the local: {text}");
6743 assert!(saves.contains("store %9 -> %2"), "the local is written through: {text}");
6744 assert!(!plain.contains("alloca"), "nothing in the plain one needs a slot: {text}");
6745 }
6746
6747 #[test]
6756 fn the_save_writes_four_words_and_carries_on_in_a_new_block() {
6757 let text =
6758 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
6759 let body = text.split_once("\nf:\n").expect("the function").1;
6760 assert!(body.contains("\tmovq\t%rsp, %rbp\n"), "a frame pointer whatever: {text}");
6761 assert!(body.contains("\tsubq\t$8, %rsp\n"), "no red zone: {text}");
6762 assert!(body.contains("\tmovq\t%rbp, (%rax)\n"), "the frame pointer: {text}");
6763 assert!(body.contains("\tmovq\t%rsp, 16(%rax)\n"), "the stack pointer: {text}");
6764 assert!(body.contains("\tleaq\t.Lf_1(%rip), %rcx\n"), "where to come back to: {text}");
6765 assert!(body.contains("\tmovq\t%rcx, 8(%rax)\n"), "and that goes in the buffer: {text}");
6766 let back = body.split_once(".Lf_1:\n").expect("the block control comes back to").1;
6767 assert!(back.starts_with("\tmovq\t(%rsp), %rax\n"), "the answer is read back: {text}");
6768 }
6769
6770 #[test]
6778 fn a_save_destroys_every_register_the_allocator_hands_out() {
6779 let text =
6780 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
6781 for reg in ["%rbx", "%r12", "%r13", "%r14", "%r15"] {
6782 assert!(text.contains(&format!("\tpushq\t{reg}\n")), "{reg} is saved: {text}");
6783 assert!(text.contains(&format!("\tpopq\t{reg}\n")), "{reg} is restored: {text}");
6784 }
6785 }
6786
6787 #[test]
6794 fn the_restore_puts_the_frame_back_before_it_jumps() {
6795 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
6796 let mut opts = options();
6797 opts.emit = EmitKind::Asm;
6798 opts.opt_level = level;
6799 let source = "void *buf[5];\nvoid g(void) { __builtin_longjmp(buf, 1); }\n";
6800 let result = run(&opts, source);
6801 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
6802 let text = result.text().to_owned();
6803 let jump = text.find("\tjmp\t*%").unwrap_or_else(|| panic!("an indirect jump: {text}"));
6804 let stack = text.find(", %rsp\n").unwrap_or_else(|| panic!("the stack back: {text}"));
6805 let frame = text.find(", %rbp\n").unwrap_or_else(|| panic!("the frame back: {text}"));
6806 assert!(stack < jump, "the stack goes back first at {level:?}: {text}");
6807 assert!(frame < jump, "and so does the frame at {level:?}: {text}");
6808 }
6809 }
6810
6811 #[test]
6817 fn a_longjmp_whose_second_argument_is_not_one_is_turned_down() {
6818 for source in [
6819 "void *buf[5];\nvoid f(void) { __builtin_longjmp(buf, 0); }\n",
6820 "void *buf[5];\nextern int v;\nvoid f(void) { __builtin_longjmp(buf, v); }\n",
6821 ] {
6822 let messages = errors(source);
6823 let named = messages.iter().any(|m| m.contains("E0710"));
6824 assert!(named, "expected a complaint about the value in {messages:?}");
6825 }
6826 }
6827
6828 #[test]
6833 fn a_static_function_nothing_refers_to_is_not_emitted() {
6834 let text = ir("static int dropped(void) { return 1; }\n\
6835 static int kept(void) { return 2; }\n\
6836 int main(void) { return kept(); }\n");
6837 assert!(text.contains("func @kept"), "{text}");
6838 assert!(!text.contains("dropped"), "{text}");
6839 }
6840
6841 #[test]
6847 fn two_static_functions_that_only_call_each_other_are_both_dropped() {
6848 let text = ir("static int ping(void);\n\
6849 static int pong(void) { return ping(); }\n\
6850 static int ping(void) { return pong(); }\n\
6851 int main(void) { return 0; }\n");
6852 assert!(!text.contains("ping"), "{text}");
6853 assert!(!text.contains("pong"), "{text}");
6854 }
6855
6856 #[test]
6862 fn naming_a_static_function_anywhere_keeps_it() {
6863 let text = ir("static int by_address(void) { return 1; }\n\
6864 static int in_an_image(void) { return 2; }\n\
6865 static int deeper(void) { return 3; }\n\
6866 static int reaches_deeper(void) { return deeper(); }\n\
6867 static int (*table[1])(void) = {in_an_image};\n\
6868 int main(void) {\n\
6869 int (*p)(void) = by_address;\n\
6870 return p() + table[0]() + reaches_deeper();\n\
6871 }\n");
6872 for kept in ["by_address", "in_an_image", "deeper", "reaches_deeper"] {
6873 assert!(text.contains(&format!("func @{kept}")), "expected {kept} in:\n{text}");
6874 }
6875 }
6876
6877 #[test]
6883 fn an_attribute_keeps_a_static_function_nothing_refers_to() {
6884 for attribute in ["used", "retain", "constructor", "destructor", "__used__"] {
6885 let source = format!(
6886 "__attribute__(({attribute})) static int kept(void) {{ return 1; }}\n\
6887 int main(void) {{ return 0; }}\n"
6888 );
6889 let text = ir(&source);
6890 assert!(text.contains("func @kept"), "for {attribute}:\n{text}");
6891 }
6892 }
6893
6894 #[test]
6897 fn a_function_anything_could_call_is_emitted_without_being_called() {
6898 let text =
6899 ir("int nobody_here_calls_it(void) { return 1; }\nint main(void) { return 0; }\n");
6900 assert!(text.contains("func @nobody_here_calls_it"), "{text}");
6901 }
6902
6903 #[test]
6910 fn a_classification_c_has_an_operator_for_is_that_operator() {
6911 for (builtin, operator) in [
6912 ("__builtin_isgreater", "binary >"),
6913 ("__builtin_isgreaterequal", "binary >="),
6914 ("__builtin_isless", "binary <"),
6915 ("__builtin_islessequal", "binary <="),
6916 ] {
6917 let source = format!("int f(double x, double y) {{ return {builtin}(x, y); }}\n");
6918 let text = tast(&source);
6919 assert!(text.contains(&format!("{operator} : int")), "for {builtin}:\n{text}");
6920 }
6921 }
6922
6923 #[test]
6932 fn the_classification_builtins_are_comparisons_and_not_calls() {
6933 let text = body("int f(double x, double y) { return __builtin_isunordered(x, y); }\n");
6934 assert_eq!(
6935 text,
6936 "block0(%0: f64, %1: f64):\n %2 = fcmp uno %0, %1\n %3 = zext.i32 \
6937 %2\n return %3\n"
6938 );
6939
6940 let text = body("int f(double x, double y) { return __builtin_islessgreater(x, y); }\n");
6942 assert!(text.contains("fcmp one %0, %1"), "{text}");
6943
6944 let text = body("int f(double x) { return __builtin_isnan(x); }\n");
6945 assert!(text.contains("fcmp uno %0, %0"), "{text}");
6946
6947 let text = body("int f(double x) { return __builtin_isinf(x); }\n");
6948 assert!(text.contains("fconst.f64 0x7ff0000000000000"), "{text}");
6949 assert!(text.contains("fconst.f64 0xfff0000000000000"), "{text}");
6950 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
6951 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
6952 assert!(text.contains("%5 = or %3, %4"), "{text}");
6953
6954 let text = body("int f(double x) { return __builtin_isfinite(x); }\n");
6957 assert!(text.contains("%3 = fcmp olt %2, %0"), "{text}");
6958 assert!(text.contains("%4 = fcmp olt %0, %1"), "{text}");
6959 assert!(text.contains("%5 = and %3, %4"), "{text}");
6960
6961 let text = body("int f(double x) { return __builtin_signbit(x); }\n");
6962 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
6963 assert!(text.contains("icmp slt %1, %2"), "{text}");
6964
6965 let text = body("int f(long double x) { return __builtin_signbitl(x); }\n");
6969 assert!(text.contains("load.i16"), "{text}");
6970 assert!(text.contains("icmp slt"), "{text}");
6971 assert!(!text.contains("i80"), "{text}");
6972
6973 let text = body("double g(void);\nint f(void) { return __builtin_isnan(g()); }\n");
6976 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
6977 }
6978
6979 #[test]
6986 fn a_classification_spelling_that_names_a_width_converts_before_it_asks() {
6987 let text = ir(concat!(
6988 "int a = __builtin_isinff(1e300);\n",
6989 "int b = __builtin_isinf(1e300);\n",
6990 "int c = __builtin_isnan(0.0);\n",
6994 "int d = __builtin_signbit(-0.0);\n",
6995 "int e = __builtin_islessgreater(1.0, 2.0);\n",
6996 ));
6997 assert!(text.contains("global @a : i32 = 1,"), "{text}");
6998 assert!(text.contains("global @b : i32 = 0,"), "{text}");
6999 assert!(text.contains("global @c : i32 = 0,"), "{text}");
7000 assert!(text.contains("global @d : i32 = 1,"), "{text}");
7001 assert!(text.contains("global @e : i32 = 1,"), "{text}");
7002 }
7003
7004 #[test]
7006 fn a_classification_builtin_refuses_an_argument_that_is_not_floating_point() {
7007 let mut opts = options();
7008 opts.emit = EmitKind::Ir;
7009 let source = concat!(
7010 "int a(int x) { return __builtin_isnan(x); }\n",
7011 "int b(int x, int y) { return __builtin_isunordered(x, y); }\n",
7012 "int c(double x) { return __builtin_isnan(x, x); }\n",
7013 );
7014 let messages = run(&opts, source).messages;
7015 assert_eq!(
7016 messages,
7017 [
7018 "/main.c:1:23: error: non-floating-point argument in call to function \
7019 '__builtin_isnan' [E0685]",
7020 "/main.c:2:30: error: non-floating-point arguments in call to function \
7021 '__builtin_isunordered' [E0685]",
7022 "/main.c:3:26: error: too many arguments to function '__builtin_isnan' [E0511]",
7023 ]
7024 );
7025 }
7026
7027 #[test]
7036 fn the_last_three_classification_builtins_are_comparisons_and_not_calls() {
7037 let text = body("int f(double x) { return __builtin_isnormal(x); }\n");
7038 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
7042 assert!(text.contains("%2 = iconst.i64 9223372036854775807"), "{text}");
7043 assert!(text.contains("%3 = and %1, %2"), "{text}");
7044 assert!(text.contains("%4 = iconst.i64 4503599627370496"), "{text}");
7045 assert!(text.contains("%5 = iconst.i64 9218868437227405312"), "{text}");
7046 assert!(text.contains("%6 = icmp uge %3, %4"), "{text}");
7047 assert!(text.contains("%7 = icmp ult %3, %5"), "{text}");
7048 assert!(text.contains("%8 = and %6, %7"), "{text}");
7049
7050 let text = body("int f(long double x) { return __builtin_isnormal(x); }\n");
7055 assert!(text.contains("fconst.f80 0x18000000000000000"), "{text}");
7056 assert!(text.contains("fconst.f80 0x7fff8000000000000000"), "{text}");
7057 assert!(text.contains("fcmp oge"), "{text}");
7058 assert!(text.contains("fcmp olt"), "{text}");
7059
7060 let text = body("int f(double x) { return __builtin_isinf_sign(x); }\n");
7061 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
7062 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
7063 assert!(text.contains("%7 = sub %5, %6"), "{text}");
7064
7065 let text = body("int f(double x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n");
7066 assert!(text.contains("fcmp uno %0, %0"), "{text}");
7067 assert!(text.contains("fcmp oeq %0, %6"), "{text}");
7068 assert_eq!(text.matches(" = zext.i32 ").count(), 4, "{text}");
7072 assert_eq!(text.matches(" = xor ").count(), 4, "{text}");
7073 assert!(!text.contains("call"), "{text}");
7074
7075 let text = body(concat!(
7078 "double g(void);\n",
7079 "int f(void) { return __builtin_fpclassify(0, 1, 2, 3, 4, g()); }\n",
7080 ));
7081 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
7082 }
7083
7084 #[test]
7091 fn the_last_three_classification_builtins_fold_where_their_operand_is_a_constant() {
7092 let text = ir(concat!(
7093 "int a = __builtin_isnormal(1.0);\n",
7094 "int b = __builtin_isnormal(0.0);\n",
7095 "int c = __builtin_isnormal(1.0 / 0.0);\n",
7096 "int d = __builtin_isinf_sign(-1.0 / 0.0);\n",
7097 "int e = __builtin_isinf_sign(1.0);\n",
7098 "int g = __builtin_fpclassify(0, 1, 2, 3, 4, 0.0);\n",
7099 "int h = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0);\n",
7100 "int i = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0 / 0.0);\n",
7101 ));
7102 assert!(text.contains("global @a : i32 = 1,"), "{text}");
7103 assert!(text.contains("global @b : i32 = 0,"), "{text}");
7104 assert!(text.contains("global @c : i32 = 0,"), "{text}");
7105 assert!(text.contains("global @d : i32 = -1,"), "{text}");
7106 assert!(text.contains("global @e : i32 = 0,"), "{text}");
7107 assert!(text.contains("global @g : i32 = 4,"), "{text}");
7108 assert!(text.contains("global @h : i32 = 2,"), "{text}");
7109 assert!(text.contains("global @i : i32 = 1,"), "{text}");
7110 }
7111
7112 #[test]
7118 fn fpclassify_refuses_an_answer_that_is_not_an_integer_constant() {
7119 let mut opts = options();
7120 opts.emit = EmitKind::Ir;
7121 let source = concat!(
7122 "int a(double x, int n) { return __builtin_fpclassify(0, 1, n, 3, 4, x); }\n",
7123 "int b(double x) { return __builtin_fpclassify(0, 1, 2, 3, x); }\n",
7124 "int c(int x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n",
7125 );
7126 let messages = run(&opts, source).messages;
7127 assert_eq!(
7128 messages,
7129 [
7130 "/main.c:1:60: error: non-const integer argument 3 in call to function \
7131 '__builtin_fpclassify' [E0687]",
7132 "/main.c:2:26: error: too few arguments to function '__builtin_fpclassify' \
7133 [E0511]",
7134 "/main.c:3:23: error: non-floating-point argument in call to function \
7135 '__builtin_fpclassify' [E0685]",
7136 ]
7137 );
7138 }
7139
7140 #[test]
7148 fn a_builtin_whose_answer_is_a_constant_is_one_and_not_a_call() {
7149 let text = ir(concat!(
7150 "double a = __builtin_inf();\n",
7151 "float b = __builtin_huge_valf();\n",
7152 "long double c = __builtin_infl();\n",
7153 "double d = __builtin_huge_val();\n",
7154 ));
7155 assert!(text.contains("global @a : f64 = 0x7ff0000000000000,"), "{text}");
7156 assert!(text.contains("global @b : f32 = 0x7f800000,"), "{text}");
7157 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
7158 assert!(text.contains("global @d : f64 = 0x7ff0000000000000,"), "{text}");
7159 assert!(!text.contains("call"), "{text}");
7160 }
7161
7162 #[test]
7171 fn a_nan_is_written_with_the_payload_the_program_asked_for() {
7172 let text = ir(concat!(
7173 "double a = __builtin_nan(\"\");\n",
7174 "double b = __builtin_nan(\"0x1\");\n",
7175 "double c = __builtin_nan(\"010\");\n",
7177 "double d = __builtin_nans(\"\");\n",
7178 "double e = __builtin_nans(\"0x1\");\n",
7179 "float f = __builtin_nanf(\"0x1\");\n",
7180 "float g = __builtin_nansf(\"\");\n",
7181 "long double h = __builtin_nansl(\"\");\n",
7182 ));
7183 assert!(text.contains("global @a : f64 = 0x7ff8000000000000,"), "{text}");
7184 assert!(text.contains("global @b : f64 = 0x7ff8000000000001,"), "{text}");
7185 assert!(text.contains("global @c : f64 = 0x7ff8000000000008,"), "{text}");
7186 assert!(text.contains("global @d : f64 = 0x7ff4000000000000,"), "{text}");
7187 assert!(text.contains("global @e : f64 = 0x7ff0000000000001,"), "{text}");
7188 assert!(text.contains("global @f : f32 = 0x7fc00001,"), "{text}");
7189 assert!(text.contains("global @g : f32 = 0x7fa00000,"), "{text}");
7190 assert!(text.contains("f80 0x7fffa000000000000000"), "{text}");
7191
7192 let text = ir(concat!(
7195 "double f(const char *p) { return __builtin_nan(p); }\n",
7196 "double g(void) { return __builtin_nans(\"1x\"); }\n",
7197 ));
7198 assert_eq!(text.matches("call @nan(").count(), 1, "{text}");
7199 assert_eq!(text.matches("call @nans(").count(), 1, "{text}");
7200 }
7201
7202 #[test]
7210 fn the_length_and_the_order_of_a_string_literal_are_known_here() {
7211 let text = ir(concat!(
7212 "unsigned long a = __builtin_strlen(\"hello\");\n",
7213 "unsigned long b = __builtin_strlen(\"a\\0bc\");\n",
7214 "int c = __builtin_strcmp(\"X\", \"X\\376\") < 0;\n",
7215 "int d = __builtin_strcmp(\"abc\", \"abc\");\n",
7216 "int e = __builtin_strcmp(\"abc\", \"ab\") > 0;\n",
7217 ));
7218 assert!(text.contains("global @a : i64 = 5,"), "{text}");
7219 assert!(text.contains("global @b : i64 = 1,"), "{text}");
7220 assert!(text.contains("global @c : i32 = 1,"), "{text}");
7221 assert!(text.contains("global @d : i32 = 0,"), "{text}");
7222 assert!(text.contains("global @e : i32 = 1,"), "{text}");
7223 assert!(!text.contains("call"), "{text}");
7224
7225 let text = ir("unsigned long f(const char *p) { return __builtin_strlen(p); }\n");
7227 assert!(text.contains("call @strlen("), "{text}");
7228 }
7229
7230 #[test]
7237 fn a_sign_builtin_is_a_mask_over_the_bits_and_not_a_call() {
7238 let text = body("double f(double x) { return __builtin_fabs(x); }\n");
7239 assert!(text.contains("bitcast.i64 %0"), "{text}");
7240 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
7241 assert!(text.contains("and %1, %2"), "{text}");
7242 assert!(text.contains("bitcast.f64 %3"), "{text}");
7243 assert!(!text.contains("call"), "{text}");
7244
7245 let text = body("double f(double x, double y) { return __builtin_copysign(x, y); }\n");
7246 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
7247 assert!(text.contains("%8 = or %4, %7"), "{text}");
7248 assert!(!text.contains("call"), "{text}");
7249
7250 let text = body("long double f(long double x) { return __builtin_fabsl(x); }\n");
7253 assert!(text.contains("iconst.i16 32767"), "{text}");
7254 assert!(text.contains("load.f80"), "{text}");
7255 assert!(!text.contains("call"), "{text}");
7256
7257 let text = body("double f(float x) { return __builtin_fabs(x); }\n");
7260 assert!(text.contains("fpext.f64 %0"), "{text}");
7261 assert!(text.contains("bitcast.i64 %1"), "{text}");
7262 }
7263
7264 #[test]
7271 fn a_shuffle_picks_each_lane_by_the_low_bits_of_the_mask() {
7272 let text = body(concat!(
7273 "typedef int v2 __attribute__((vector_size(8)));\n",
7274 "void f(v2 *v, v2 m) { *v = __builtin_shuffle(*v, m); }\n",
7275 ));
7276 assert!(text.contains("memcpy"), "{text}");
7277 assert_eq!(text.matches("iconst.i32 1\n").count(), 2, "{text}");
7278 assert_eq!(text.matches(" = and ").count(), 2, "{text}");
7279 assert!(!text.contains("call"), "{text}");
7280
7281 let text = body(concat!(
7284 "typedef char v4 __attribute__((vector_size(4)));\n",
7285 "v4 f(v4 a, v4 b, v4 m) { return __builtin_shuffle(a, b, m); }\n",
7286 ));
7287 assert_eq!(text.matches("iconst.i32 7\n").count(), 4, "{text}");
7288 assert!(text.contains("zext.i32"), "{text}");
7289 assert!(!text.contains("call"), "{text}");
7290 }
7291
7292 #[test]
7296 fn the_arguments_a_function_was_called_with_are_saved_on_the_way_in() {
7297 let text =
7298 mir("void *f(int a, double b) { (void)a; (void)b; return __builtin_apply_args(); }\n");
7299 assert!(text.matches("x64.mov_mr_64").count() >= 7, "{text}");
7301 assert!(text.matches("x64.movaps_mr").count() >= 8, "{text}");
7302 for reg in ["$rdi", "$rsi", "$rdx", "$rcx", "$r8", "$r9", "$xmm0", "$xmm7"] {
7303 assert!(text.contains(reg), "{reg} is not saved in\n{text}");
7304 }
7305
7306 let text = mir("int f(int a) { return a; }\n");
7308 assert!(!text.contains("movaps_mr"), "{text}");
7309 }
7310
7311 #[test]
7315 fn a_call_built_from_saved_arguments_loads_every_argument_register() {
7316 let text = mir(concat!(
7317 "void *g(void *args, void (*h)()) {\n",
7318 " return __builtin_apply(h, args, 64);\n",
7319 "}\n",
7320 ));
7321 assert!(text.matches("x64.mov_rm_64").count() >= 7, "{text}");
7322 assert!(text.matches("x64.movaps_rm").count() >= 8, "{text}");
7323 assert!(text.contains("call"), "{text}");
7324 assert!(text.matches("x64.movaps_mr").count() >= 2, "{text}");
7326
7327 let mut opts = options();
7329 opts.emit = EmitKind::Ir;
7330 let result = run(
7331 &opts,
7332 "void *g(void *a, void (*h)(), int n) { return __builtin_apply(h, a, n); }\n",
7333 );
7334 assert!(result.failed(), "{:?}", result.messages);
7335 assert!(
7336 result
7337 .messages
7338 .iter()
7339 .any(|m| m.contains("the size given to '__builtin_apply' is a constant")),
7340 "{:?}",
7341 result.messages
7342 );
7343 }
7344
7345 #[test]
7347 fn a_shuffle_refuses_what_gcc_refuses() {
7348 let mut opts = options();
7349 opts.emit = EmitKind::Ir;
7350 let source = concat!(
7351 "typedef int v4 __attribute__((vector_size(16)));\n",
7352 "typedef float f4 __attribute__((vector_size(16)));\n",
7353 "typedef short s8 __attribute__((vector_size(16)));\n",
7354 "typedef long long l4 __attribute__((vector_size(32)));\n",
7355 "void a(v4 x, f4 m) { __builtin_shuffle(x, m); }\n",
7356 "void b(int x, v4 m) { __builtin_shuffle(x, m); }\n",
7357 "void c(v4 x, f4 y, v4 m) { __builtin_shuffle(x, y, m); }\n",
7358 "void d(v4 x, s8 m) { __builtin_shuffle(x, m); }\n",
7359 "void e(f4 x, l4 m) { __builtin_shuffle(x, m); }\n",
7360 "void g(v4 x) { __builtin_shuffle(x); }\n",
7361 );
7362 let messages = run(&opts, source).messages;
7363 let wanted = [
7364 "last argument must be an integer vector [E0715]",
7365 "arguments must be vectors [E0715]",
7366 "argument vectors must be of the same type [E0715]",
7367 "number of elements of the argument vector(s) and the mask vector should be the same \
7368 [E0715]",
7369 "argument vector(s) inner type must have the same size as inner type of the mask \
7370 [E0715]",
7371 "too few arguments to function '__builtin_shuffle' [E0511]",
7372 ];
7373 assert_eq!(messages.len(), wanted.len(), "{messages:?}");
7374 for (message, wanted) in messages.iter().zip(wanted) {
7375 assert!(message.ends_with(wanted), "{message}");
7376 }
7377 }
7378
7379 #[test]
7388 fn the_plain_math_names_are_the_same_mask_and_not_a_call() {
7389 let text =
7390 body(concat!("double fabs(double x);\n", "double f(double x) { return fabs(x); }\n",));
7391 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
7392 assert!(!text.contains("call"), "{text}");
7393
7394 let text =
7395 body(concat!("float fabsf(float x);\n", "float f(float x) { return fabsf(x); }\n",));
7396 assert!(text.contains("bitcast.i32 %0"), "{text}");
7397 assert!(!text.contains("call"), "{text}");
7398
7399 let text = body(concat!(
7400 "double copysign(double x, double y);\n",
7401 "double f(double x, double y) { return copysign(x, y); }\n",
7402 ));
7403 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
7404 assert!(!text.contains("call"), "{text}");
7405
7406 let text = body(concat!(
7407 "float copysignf(float x, float y);\n",
7408 "float f(float x, float y) { return copysignf(x, y); }\n",
7409 ));
7410 assert!(!text.contains("call"), "{text}");
7411
7412 let text = ir(concat!(
7416 "long double fabsl(long double x);\n",
7417 "long double f(long double x) { return fabsl(x); }\n",
7418 ));
7419 assert!(text.contains("call @fabsl"), "{text}");
7420 }
7421
7422 #[test]
7430 fn a_plain_math_name_the_program_took_is_the_programs_own_function() {
7431 let taken = concat!(
7432 "static double fabs(double b) { return 7; }\n",
7433 "double f(double x) { return fabs(x); }\n",
7434 );
7435 assert!(ir(taken).contains("call @fabs"), "a static definition is the program's own");
7436
7437 let retyped = concat!("int fabs(int b);\n", "int f(int x) { return fabs(x); }\n");
7438 assert!(ir(retyped).contains("call @fabs"), "another type is another function");
7439
7440 let plain = concat!("double fabs(double b);\n", "double f(double x) { return fabs(x); }\n");
7441 let mut opts = options();
7442 opts.emit = EmitKind::Ir;
7443 assert!(!run(&opts, plain).text().contains("call @fabs"), "the library's by default");
7444
7445 opts.builtins = false;
7446 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin");
7447
7448 opts.builtins = true;
7449 opts.no_builtin = vec!["fabs".to_owned()];
7450 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin-fabs");
7451 let one = concat!(
7452 "double copysign(double a, double b);\n",
7453 "double f(double x) { return copysign(x, 1.0); }\n",
7454 );
7455 assert!(!run(&opts, one).text().contains("call @copysign"), "one name and not the family");
7456
7457 opts.no_builtin = Vec::new();
7459 opts.builtins = false;
7460 let prefixed = "double f(double x) { return __builtin_fabs(x); }\n";
7461 assert!(!run(&opts, prefixed).text().contains("call @fabs"), "the prefix is not a library");
7462 }
7463
7464 #[test]
7473 fn the_sign_builtins_answer_a_zero_and_a_nan_the_way_the_bits_say() {
7474 let text = ir(concat!(
7475 "double a = __builtin_fabs(-3.5);\n",
7476 "double b = __builtin_copysign(1.0, -0.0);\n",
7477 "double c = __builtin_copysign(0.0, -2.0);\n",
7478 "double d = __builtin_copysign(-__builtin_nan(\"\"), 1.0);\n",
7480 "double e = __builtin_fabs(-__builtin_nan(\"0x1\"));\n",
7481 "float g = __builtin_copysignf(-0.0f, 2.0f);\n",
7482 "long double h = __builtin_copysignl(1.0L, -1.0L);\n",
7483 "long double i = __builtin_fabsl(-__builtin_infl());\n",
7484 ));
7485 assert!(text.contains("global @a : f64 = 0x400c000000000000,"), "{text}");
7486 assert!(text.contains("global @b : f64 = 0xbff0000000000000,"), "{text}");
7487 assert!(text.contains("global @c : f64 = 0x8000000000000000,"), "{text}");
7488 assert!(text.contains("global @d : f64 = 0x7ff8000000000000,"), "{text}");
7489 assert!(text.contains("global @e : f64 = 0x7ff8000000000001,"), "{text}");
7490 assert!(text.contains("global @g : f32 = 0x0,"), "{text}");
7491 assert!(text.contains("f80 0xbfff8000000000000000"), "{text}");
7492 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
7493 }
7494
7495 #[test]
7502 fn the_sign_of_a_long_double_is_in_the_word_at_the_top_of_it() {
7503 for source in [
7504 "int f(long double x) { return __builtin_signbit(x); }\n",
7505 "long double f(long double x) { return __builtin_fabsl(x); }\n",
7506 "long double f(long double x, long double y) { return __builtin_copysignl(x, y); }\n",
7507 "int f(long double x) { return __builtin_isnormal(x); }\n",
7508 ] {
7509 let text = body(source);
7510 assert!(!text.contains("i80"), "{text}");
7511 assert!(text.contains("i16"), "{text}");
7512 }
7513 }
7514
7515 #[test]
7523 fn the_complex_builtins_are_the_halves_of_the_value_and_not_a_call() {
7524 let text = body("double f(_Complex double z) { return __builtin_creal(z); }\n");
7525 assert!(!text.contains("call"), "{text}");
7526 let text = body("double f(_Complex double z) { return __builtin_cimag(z); }\n");
7527 assert!(!text.contains("call"), "{text}");
7528
7529 let text = body("_Complex double f(_Complex double z) { return __builtin_conj(z); }\n");
7532 assert_eq!(text.matches("fneg").count(), 1, "{text}");
7533 assert!(!text.contains("call"), "{text}");
7534 let negated = body("_Complex double f(_Complex double z) { return -z; }\n");
7535 assert_eq!(negated.matches("fneg").count(), 2, "{negated}");
7536
7537 let written = body("_Complex double f(_Complex double z) { return ~z; }\n");
7540 assert_eq!(written, text, "the name and the operator are the same thing");
7541
7542 let text = body(concat!(
7544 "double creal(_Complex double z);\n",
7545 "double f(_Complex double z) { return creal(z); }\n",
7546 ));
7547 assert!(!text.contains("call"), "{text}");
7548 let text = body(concat!(
7549 "_Complex float conjf(_Complex float z);\n",
7550 "_Complex float f(_Complex float z) { return conjf(z); }\n",
7551 ));
7552 assert_eq!(text.matches("fneg").count(), 1, "{text}");
7553 assert!(!text.contains("call"), "{text}");
7554
7555 let taken = concat!(
7558 "static double creal(_Complex double z) { return 7; }\n",
7559 "double f(_Complex double z) { return creal(z); }\n",
7560 );
7561 assert!(ir(taken).contains("call @creal"), "a static definition is the program's own");
7562 let retyped = concat!("int cimag(int z);\n", "int f(int z) { return cimag(z); }\n");
7563 assert!(ir(retyped).contains("call @cimag"), "another type is another function");
7564 let plain = concat!(
7565 "double cimag(_Complex double z);\n",
7566 "double f(_Complex double z) { return cimag(z); }\n",
7567 );
7568 let mut opts = options();
7569 opts.emit = EmitKind::Ir;
7570 opts.builtins = false;
7571 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin");
7572 opts.builtins = true;
7573 opts.no_builtin = vec!["cimag".to_owned()];
7574 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin-cimag");
7575
7576 let text = ir(concat!(
7578 "double a = __builtin_creal(1.5 + 2.5i);\n",
7579 "double b = __builtin_cimag(1.5 + 2.5i);\n",
7580 "_Complex double c = __builtin_conj(1.5 + 2.5i);\n",
7581 ));
7582 assert!(text.contains("global @a : f64 = 0x3ff8000000000000,"), "{text}");
7583 assert!(text.contains("global @b : f64 = 0x4004000000000000,"), "{text}");
7584 assert!(
7585 text.contains("{ f64 0x3ff8000000000000, f64 0xc004000000000000 }"),
7586 "the conjugate of a constant is the constant with the second half negated: {text}"
7587 );
7588 assert!(!text.contains("call"), "{text}");
7589 }
7590
7591 #[test]
7599 fn a_math_library_builtin_of_a_constant_is_the_answer_and_not_a_call() {
7600 let text = ir(concat!(
7601 "double a = __builtin_ceil(1.5);\n",
7602 "double b = __builtin_floor(1.5);\n",
7603 "double c = __builtin_trunc(-1.5);\n",
7604 "double d = __builtin_round(2.5);\n",
7607 "double e = __builtin_ceil(-0.5);\n",
7609 "double f = __builtin_fmax(1.0, 2.0);\n",
7610 "double g = __builtin_fmin(1.0, 2.0);\n",
7611 "float h = __builtin_ceilf(1.25f);\n",
7612 "double ceil(double x);\n",
7615 "double i = ceil(2.25);\n",
7616 ));
7617 assert!(text.contains("global @a : f64 = 0x4000000000000000,"), "{text}");
7618 assert!(text.contains("global @b : f64 = 0x3ff0000000000000,"), "{text}");
7619 assert!(text.contains("global @c : f64 = 0xbff0000000000000,"), "{text}");
7620 assert!(text.contains("global @d : f64 = 0x4008000000000000,"), "{text}");
7621 assert!(text.contains("global @e : f64 = 0x8000000000000000,"), "{text}");
7622 assert!(text.contains("global @f : f64 = 0x4000000000000000,"), "{text}");
7623 assert!(text.contains("global @g : f64 = 0x3ff0000000000000,"), "{text}");
7624 assert!(text.contains("global @h : f32 = 0x40000000,"), "{text}");
7625 assert!(text.contains("global @i : f64 = 0x4008000000000000,"), "{text}");
7626 assert!(!text.contains("call"), "{text}");
7627 }
7628
7629 #[test]
7637 fn a_math_library_builtin_of_anything_else_is_a_call_to_the_library() {
7638 let text = ir(concat!(
7639 "double f(double x) { return __builtin_ceil(x); }\n",
7640 "float g(float x) { return __builtin_floorf(x); }\n",
7641 "double h(double x, double y) { return __builtin_fmax(x, y); }\n",
7642 ));
7643 assert!(text.contains("call @ceil("), "{text}");
7644 assert!(text.contains("call @floorf("), "{text}");
7645 assert!(text.contains("call @fmax("), "{text}");
7646
7647 let text = ir(concat!(
7651 "double f(void) { return __builtin_rint(2.5); }\n",
7652 "double g(void) { return __builtin_nearbyint(2.5); }\n",
7653 ));
7654 assert!(text.contains("call @rint("), "{text}");
7655 assert!(text.contains("call @nearbyint("), "{text}");
7656
7657 let text = ir("double f(void) { return __builtin_fmin(__builtin_nan(\"\"), 1.0); }\n");
7660 assert!(text.contains("call @fmin("), "{text}");
7661
7662 let plain = concat!("double ceil(double x);\n", "double f(void) { return ceil(2.25); }\n");
7665 let mut opts = options();
7666 opts.emit = EmitKind::Ir;
7667 opts.no_builtin = vec!["ceil".to_owned()];
7668 assert!(run(&opts, plain).text().contains("call @ceil("), "-fno-builtin-ceil");
7669 }
7670
7671 #[test]
7678 fn a_constexpr_object_is_a_constant_wherever_one_is_required() {
7679 let text = ir(concat!(
7680 "constexpr int side = 4;\n",
7681 "constexpr int wider = side + 1;\n",
7682 "constexpr double half = 1.5;\n",
7683 "struct point { int x; int y; };\n",
7684 "constexpr struct point origin = { 5, 6 };\n",
7685 "int square[side * side];\n",
7686 "int rectangle[wider];\n",
7687 "int rounded[(int)half * 2];\n",
7688 "int across[origin.y];\n",
7689 "enum named { four = side };\n",
7690 "int e = four;\n",
7691 ));
7692 assert!(text.contains("global @square : bytes 64 ="), "{text}");
7693 assert!(text.contains("global @rectangle : bytes 20 ="), "{text}");
7694 assert!(text.contains("global @rounded : bytes 8 ="), "{text}");
7695 assert!(text.contains("global @across : bytes 24 ="), "{text}");
7696 assert!(text.contains("global @e : i32 = 4,"), "{text}");
7697
7698 let mut opts = options();
7701 opts.emit = EmitKind::Ir;
7702 let konst = "const int n = 1;\nint a[n];\n";
7703 let message = "/main.c:2:5: error: variably modified 'a' at file scope [E0538]";
7704 assert_eq!(run(&opts, konst).messages, [message]);
7705
7706 let subscript = "constexpr int t[3] = { 1, 2, 3 };\nint a[t[1]];\n";
7708 assert_eq!(run(&opts, subscript).messages, [message]);
7709
7710 let address = "constexpr int c = 3;\nint *p = &c;\n";
7712 let warning = "/main.c:2:6: warning: initialization discards 'const' qualifier from \
7713 pointer target type [E0514]";
7714 assert_eq!(run(&opts, address).messages, [warning]);
7715 }
7716
7717 #[test]
7726 fn a_member_whose_size_was_refused_is_not_a_flexible_array_member() {
7727 let mut opts = options();
7728 opts.emit = EmitKind::Ir;
7729
7730 let alone = "int k;\nextern struct D { int a[k]; } ed;\n";
7731 let message = "/main.c:2:23: error: variably modified 'a' at file scope [E0538]";
7732 assert_eq!(run(&opts, alone).messages, [message]);
7733
7734 let first = "int k;\nextern struct E { int a[k]; int b; } ee;\n";
7736 assert_eq!(run(&opts, first).messages, [message]);
7737
7738 let negative = "struct F { int a[-1]; };\n";
7741 let refused = "/main.c:1:18: error: size of array 'a' is negative [E0536]";
7742 assert_eq!(run(&opts, negative).messages, [refused]);
7743
7744 let flexible = "struct G { int a[]; };\n";
7747 let named = "/main.c:1:16: error: flexible array member in a struct with no named \
7748 members [E0554]";
7749 assert_eq!(run(&opts, flexible).messages, [named]);
7750 }
7751
7752 #[test]
7767 fn a_pointer_to_an_array_gains_a_qualifier_the_same_way_a_pointer_to_anything_else_does() {
7768 let mut opts = options();
7769 opts.emit = EmitKind::Ir;
7770 let prefix = "typedef unsigned int B[4];\nstruct H { B category[2]; };\n";
7771
7772 let adding = format!("{prefix}const B *f(struct H *h) {{ return &h->category[0]; }}\n");
7774 assert_eq!(run(&opts, &adding).messages, [] as [String; 0]);
7775
7776 let plain = concat!(
7779 "const unsigned int (*f(unsigned int (*p)[4]))[4] { return p; }\n",
7780 "const unsigned int (*g(unsigned int (*p)[2][3]))[2][3] { return p; }\n",
7781 );
7782 assert_eq!(run(&opts, plain).messages, [] as [String; 0]);
7783
7784 let dropping = format!("{prefix}B *f(const B *p) {{ return p; }}\n");
7787 let warning = "/main.c:3:27: warning: return discards 'const' qualifier from pointer target type \
7788 [E0514]";
7789 assert_eq!(run(&opts, &dropping).messages, [warning]);
7790
7791 let wrong = "const unsigned int (*f(unsigned short (*p)[4]))[4] { return p; }\n";
7794 let error = "/main.c:1:61: error: returning 'unsigned short (*)[4]' from a function with \
7795 incompatible return type 'const unsigned int (*)[4]' [E0512]";
7796 assert_eq!(run(&opts, wrong).messages, [error]);
7797 }
7798
7799 #[test]
7808 fn an_old_style_definition_takes_its_types_from_the_declarations_under_its_list() {
7809 let mut opts = options();
7812 opts.std = Std::C17;
7813 let source = concat!(
7814 "int add(a, b)\n",
7815 "int a;\n",
7816 "int b;\n",
7817 "{ return a + b; }\n",
7818 "int promoted(c)\n",
7819 "char c;\n",
7820 "{ return c; }\n",
7821 "int narrow(char);\n",
7822 "int narrow(c)\n",
7823 "char c;\n",
7824 "{ return c; }\n",
7825 "int first(a)\n",
7826 "int a[4];\n",
7827 "{ return a[0]; }\n",
7828 );
7829 let result = run(&opts, source);
7830 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
7831 let text = result.text();
7832 assert!(text.contains("add : int(int, int) function external defined"), "{text}");
7833 assert!(text.contains("promoted : int(int) function external defined"), "{text}");
7834 assert!(text.contains("c : char object automatic defined"), "{text}");
7836 assert!(text.contains("narrow : int(char) function external defined"), "{text}");
7837 assert!(text.contains("first : int(int *) function external defined"), "{text}");
7839 }
7840
7841 #[test]
7848 fn the_two_halves_of_an_old_style_parameter_list_have_to_agree() {
7849 let mut opts = options();
7850 opts.std = Std::C17;
7851 for (source, message) in [
7852 ("int f(a, a)\nint a;\n{ return a; }\n", "1:10: error: multiple parameters named 'a'"),
7853 (
7854 "int f(a)\nint a;\nint b;\n{ return a; }\n",
7855 "3:5: error: declaration for parameter 'b' but no such parameter",
7856 ),
7857 ("int f(a)\nint a;\nint a;\n{ return a; }\n", "3:5: error: redefinition of parameter"),
7858 ("int f(a)\nint a = 1;\n{ return a; }\n", "2:5: error: parameter 'a' is initialized"),
7859 (
7860 "int f(a)\nstatic int a;\n{ return a; }\n",
7861 "2:12: error: storage class specified for parameter 'a'",
7862 ),
7863 (
7864 "int f(char);\nint f(a)\nshort a;\n{ return a; }\n",
7865 "2:7: error: argument 'a' doesn't match prototype",
7866 ),
7867 ] {
7868 let result = run(&opts, source);
7869 assert!(result.failed(), "expected this to fail:\n{source}");
7870 assert!(result.messages[0].contains(message), "{:?}", result.messages);
7871 }
7872
7873 let implicit = "int f(a, b)\nint a;\n{ return a + b; }\n";
7876 let mut older = options();
7877 older.std = Std::C89;
7878 assert!(!run(&older, implicit).failed(), "{:?}", run(&older, implicit).messages);
7879 let result = run(&opts, implicit);
7880 assert!(
7881 result.messages[0].contains("1:10: error: type of 'b' defaults to 'int'"),
7882 "{:?}",
7883 result.messages
7884 );
7885
7886 let mut newer = options();
7890 newer.std = Std::C23;
7891 let plain = "int f(a)\nint a;\n{ return a; }\n";
7892 let result = run(&newer, plain);
7893 assert!(!result.failed(), "{:?}", result.messages);
7894 assert_eq!(
7895 result.messages,
7896 ["/main.c:1:5: warning: old-style function definition [E0412]"]
7897 );
7898 assert!(run(&opts, plain).messages.is_empty(), "and nothing to say in the dialects before");
7899 }
7900
7901 #[test]
7908 fn the_obsolete_designators_are_taken_and_are_pedantic_warnings() {
7909 let array = "int a[8] = { [3] 7 };\n";
7910 let member = "struct s { int x; } v = { x: 7 };\n";
7911 for source in [array, member] {
7912 let result = run(&options(), source);
7913 assert!(!result.failed(), "{:?}", result.messages);
7914 assert!(result.messages.is_empty(), "nothing to say: {:?}", result.messages);
7915 }
7916
7917 let mut asked = options();
7918 asked.pedantic = true;
7919 assert_eq!(
7920 run(&asked, array).messages,
7921 ["/main.c:1:18: warning: obsolete designator, write `[i] =` instead [E0415]"]
7922 );
7923 assert_eq!(
7924 run(&asked, member).messages,
7925 ["/main.c:1:27: warning: obsolete designator, write `.field =` instead [E0413]"]
7926 );
7927 }
7928
7929 #[test]
7936 fn a_type_is_refused_when_it_passes_the_largest_object_and_not_before() {
7937 let text = ir(concat!(
7938 "struct huge_struct { short buf[(1L << 62) - 256]; int a, b, c, d; };\n",
7939 "struct brim { char buf[9223372036854775807L]; };\n",
7940 "struct bitty { char buf[9223372036854775800L]; int x : 1; };\n",
7941 "unsigned long h = sizeof(struct huge_struct);\n",
7942 "unsigned long b = sizeof(struct brim);\n",
7943 "unsigned long y = sizeof(struct bitty);\n",
7944 ));
7945 assert!(text.contains("global @h : i64 = 9223372036854775312,"), "{text}");
7946 assert!(text.contains("global @b : i64 = 9223372036854775807,"), "{text}");
7947 assert!(text.contains("global @y : i64 = 9223372036854775804,"), "{text}");
7948
7949 let mut opts = options();
7950 opts.emit = EmitKind::Ir;
7951 let over = "struct over { char buf[9223372036854775800L]; char x[8]; };\n";
7952 let message = "/main.c:1:1: error: type 'struct over' is too large [E0560]";
7953 assert_eq!(run(&opts, over).messages, [message]);
7954 let array = "struct wide { short buf[1L << 62]; };\n";
7955 let message = "/main.c:1:25: error: size of array 'buf' exceeds \
7956 maximum object size '9223372036854775807' [E0537]";
7957 assert_eq!(run(&opts, array).messages[0], message);
7958 }
7959
7960 fn compile_bytes(source: &[u8]) -> Compiled {
7965 let mut opts = options();
7966 opts.emit = EmitKind::Ir;
7967 let mut fs = MemoryFileSystem::new();
7968 fs.insert("/main.c", source.to_vec());
7969 compile(&opts, "/main.c", &fs)
7970 }
7971
7972 #[test]
7979 fn a_byte_that_is_not_a_character_is_kept_in_a_literal_and_refused_outside_one() {
7980 let mut source = b"char s[] = \"a".to_vec();
7981 source.push(0xff);
7982 source.extend_from_slice(b"b\";\nchar c = '");
7983 source.push(0xff);
7984 source.extend_from_slice(b"';\n");
7985 let result = compile_bytes(&source);
7986 assert_eq!(result.messages, Vec::<String>::new(), "a raw byte in a literal is that byte");
7987 assert!(result.text().contains(r#"bytes "a\ffb\00""#), "{}", result.text());
7988 assert!(result.text().contains("global @c : i8 = -1,"), "{}", result.text());
7990
7991 let mut stray = b"int a".to_vec();
7992 stray.push(0xff);
7993 stray.extend_from_slice(b" = 1;\n");
7994 let result = compile_bytes(&stray);
7995 assert!(
7996 result.messages.iter().any(|m| m.contains("source is not valid UTF-8 here")),
7997 "{:?}",
7998 result.messages
7999 );
8000 }
8001
8002 #[test]
8003 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
8004 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
8005 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
8006 let expected = "\
8007func @add(i32, i32) -> i32, linkage(external) {
8008block0(%0: i32, %1: i32):
8009 %2 = add.nsw %0, %1
8010 return %2
8011}
8012";
8013 assert!(text.contains(expected), "{text}");
8014 }
8015
8016 #[test]
8017 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
8018 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
8019 assert!(!text.contains("alloca"), "{text}");
8020 assert!(!text.contains("load"), "{text}");
8021 assert!(!text.contains("store"), "{text}");
8022 }
8023
8024 #[test]
8025 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
8026 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
8027 let expected = "\
8028block0:
8029 %0 = alloca, size 4, align 4
8030 %1 = iconst.i32 1
8031 store %1 -> %0, align 4, tbaa !1
8032 %2 = call @g(%0) : (ptr) -> i32
8033 return %2
8034";
8035 assert_eq!(text, expected);
8036 }
8037
8038 #[test]
8039 fn a_loop_carries_what_it_changes_as_block_parameters() {
8040 let text = body(
8043 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
8044 return total;\n}\n",
8045 );
8046 assert!(!text.contains("alloca"), "{text}");
8047 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
8048 assert!(text.contains("jump block1("), "{text}");
8049 }
8050
8051 #[test]
8052 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
8053 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
8054 assert!(text.contains("icmp slt %0, %1"), "{text}");
8055 assert!(!text.contains("zext"), "{text}");
8056 }
8057
8058 #[test]
8059 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
8060 let text = body("int f(int a, int b) { return a && b; }\n");
8061 let expected = "\
8062block0(%0: i32, %1: i32):
8063 %2 = iconst.i32 0
8064 %3 = icmp ne %0, %2
8065 %4 = iconst.i1 0
8066 br_if %3, block1, block2(%4)
8067
8068block1:
8069 %5 = iconst.i32 0
8070 %6 = icmp ne %1, %5
8071 jump block2(%6)
8072
8073block2(%7: i1):
8074 %8 = zext.i32 %7
8075 return %8
8076";
8077 assert_eq!(text, expected);
8078 }
8079
8080 #[test]
8081 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
8082 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
8083 assert!(!text.contains("block3"), "{text}");
8086 assert!(!text.contains("iconst.i32 3"), "{text}");
8087 }
8088
8089 #[test]
8090 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
8091 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
8092 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
8093 assert!(body("int f(void) { }\n").contains("unreachable"));
8094 }
8095
8096 #[test]
8097 fn a_structure_is_copied_rather_than_held_in_a_value() {
8098 let text = body(
8099 "struct point { int x, y; };\n\
8100 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
8101 );
8102 assert!(text.contains("memcpy"), "{text}");
8103 }
8104
8105 #[test]
8106 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
8107 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
8108 assert!(text.contains("memset"), "{text}");
8109 }
8110
8111 #[test]
8112 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
8113 let text = body(
8114 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
8115 default: r = 4; } return r; }\n",
8116 );
8117 let expected = "\
8118block0(%0: i32):
8119 %1 = iconst.i32 0
8120 switch %0, block1, [1 => block2, 2 => block3(%1)]
8121
8122block1:
8123 %2 = iconst.i32 4
8124 jump block4(%2)
8125
8126block2:
8127 %3 = iconst.i32 1
8128 jump block3(%3)
8129
8130block3(%4: i32):
8131 %5 = iconst.i32 2
8132 %6 = add.nsw %4, %5
8133 jump block4(%6)
8134
8135block4(%7: i32):
8136 return %7
8137";
8138 assert_eq!(text, expected);
8139 }
8140
8141 #[test]
8142 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
8143 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
8146 assert!(text.contains("%2 = sub %0, %1"), "{text}");
8147 assert!(text.contains("icmp ule"), "{text}");
8148 assert!(!text.contains("switch"), "{text}");
8149 }
8150
8151 #[test]
8152 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
8153 let text = body(
8154 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
8155 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
8156 );
8157 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
8160 assert!(text.contains("block5:\n jump block7("), "{text}");
8161 assert!(text.contains("block6:\n jump block8("), "{text}");
8162 }
8163
8164 #[test]
8165 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
8166 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
8167 }
8168
8169 #[test]
8170 fn a_label_a_loop_is_only_entered_through_builds_the_loop_around_it() {
8171 let text = body(
8176 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
8177 return n; }\n",
8178 );
8179 assert!(text.contains("switch %0, block1(%1), [1 => block2, 2 => block3(%1)]"), "{text}");
8182 assert!(text.contains("block3(%3: i32):\n %4 = iconst.i32 1"), "{text}");
8183 assert!(text.contains("block4:\n jump block3("), "{text}");
8184 }
8185
8186 #[test]
8187 fn a_goto_into_a_loop_body_enters_it_without_the_test() {
8188 let text = body("int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n");
8191 assert!(text.starts_with("block0(%0: i32, %1: i32):\n jump block1(%1)"), "{text}");
8192 assert!(text.contains("block1(%2: i32):\n %3 = iconst.i32 1"), "{text}");
8193 assert!(text.contains("br_if %6, block2, block3"), "{text}");
8194 }
8195
8196 #[test]
8197 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
8198 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
8199 assert!(!text.contains("alloca"), "{text}");
8203 assert!(text.contains("block2(%4: i32):\n return %4"), "{text}");
8204 assert_eq!(text.matches("jump block2(").count(), 2, "{text}");
8205 }
8206
8207 #[test]
8208 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
8209 let text =
8210 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
8211 assert!(!text.contains("alloca"), "{text}");
8212 assert!(text.contains("block1(%2: i32):"), "{text}");
8213 assert!(text.contains("jump block1(%5)"), "{text}");
8214 }
8215
8216 #[test]
8217 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
8218 assert_eq!(
8221 body("int f(int x) { return x; spare: return 0; }\n"),
8222 "block0(%0: i32):\n return %0\n"
8223 );
8224 }
8225
8226 #[test]
8227 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
8228 let text = body(
8229 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
8230 );
8231 assert_eq!(
8234 text,
8235 "\
8236block0(%0: ptr):
8237 %1 = load.i8 %0, align 1
8238 %2 = iconst.i8 3
8239 %3 = ashr %1, %2
8240 %4 = sext.i32 %3
8241 return %4
8242"
8243 );
8244 }
8245
8246 #[test]
8247 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
8248 let text =
8252 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
8253 assert_eq!(
8254 text,
8255 "\
8256block0(%0: ptr, %1: i32):
8257 %2 = iconst.i32 16777215
8258 %3 = and %1, %2
8259 %4 = trunc.i16 %3
8260 store %4 -> %0, align 2
8261 %5 = iconst.i32 16
8262 %6 = lshr %3, %5
8263 %7 = trunc.i8 %6
8264 %8 = iconst.i64 2
8265 %9 = ptr_add %0, %8
8266 store %7 -> %9, align 1
8267 return
8268"
8269 );
8270 }
8271
8272 #[test]
8273 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
8274 let text =
8275 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
8276 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
8279 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
8280 }
8281
8282 #[test]
8283 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
8284 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
8287 assert_eq!(text.matches("ashr").count(), 0, "{text}");
8288 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
8289 }
8290
8291 #[test]
8292 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
8293 let text = body(
8297 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
8298 );
8299 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
8300 }
8301
8302 #[test]
8303 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
8304 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
8307 assert!(
8308 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
8309 "{text}"
8310 );
8311 }
8312
8313 #[test]
8314 fn an_initialized_flexible_array_member_makes_the_object_larger_than_its_type() {
8315 let text = ir(concat!(
8320 "struct a { int i; int j[]; } x = { 1, { 2, 0, 2, 3 } };\n",
8321 "struct b { char c; char p[]; } y = { 'o', \"wx\" };\n",
8322 "struct c { char c; char p[]; } z = { '9', { 'e', 'b' } };\n",
8323 "char s[2] = \"hi\";\n",
8324 ));
8325 assert!(
8326 text.contains("global @x : bytes 20 = { i32 1, i32 2, i32 0, i32 2, i32 3 }"),
8327 "{text}"
8328 );
8329 assert!(text.contains("global @y : bytes 4 = { i8 111, bytes \"wx\\00\" }"), "{text}");
8330 assert!(text.contains("global @z : bytes 3 = { i8 57, i8 101, i8 98 }"), "{text}");
8331 assert!(text.contains("global @s : bytes 2 = { bytes \"hi\" }"), "{text}");
8334 }
8335
8336 #[test]
8337 fn a_definition_takes_a_parameter_it_left_unnamed() {
8338 let text = ir("int f(int a, int) { return a; }\n");
8342 assert!(text.contains("func @f(i32, i32) -> i32"), "{text}");
8343 assert!(text.contains("block0(%0: i32, %1: i32):"), "{text}");
8344
8345 let text = ir("int g(int, int n) { return n; }\n");
8348 assert!(text.contains("block0(%0: i32, %1: i32):\n return %1\n"), "{text}");
8349 }
8350
8351 #[test]
8352 fn an_assignment_of_a_structure_is_the_object_it_wrote() {
8353 let text = body(concat!(
8358 "struct s { int f; int g; };\n",
8359 "void h(struct s *a, struct s *c, struct s *d, struct s *e)\n",
8360 "{ *d = *e = a[0] = *c; }\n",
8361 ));
8362 assert_eq!(text.matches("memcpy").count(), 3, "{text}");
8363 assert!(text.contains("memcpy %8, %1, size 8, align 4\n"), "{text}");
8364 assert!(text.contains("memcpy %3, %8, size 8, align 4\n"), "{text}");
8365 assert!(text.contains("memcpy %2, %3, size 8, align 4\n"), "{text}");
8366 }
8367
8368 #[test]
8369 fn a_string_literal_stops_at_the_end_of_the_array_it_is_filling() {
8370 let mut opts = options();
8375 opts.emit = EmitKind::Ir;
8376 let result = run(
8377 &opts,
8378 concat!(
8379 "const char a[2][3] = { \"1234\", \"xyz\" };\n",
8380 "static const char b[3][5] = { \"12345\", \"678\", \"9\" };\n",
8381 "union u { struct { char x[4]; char y[4]; }; struct { char z[8]; }; };\n",
8382 "const union u c = { { \"1234\", \"567\" } };\n",
8383 ),
8384 );
8385 let text = result.text();
8386 assert_eq!(
8387 result.messages,
8388 ["/main.c:1:24: warning: initializer-string for array of 'const char' is too long \
8389 (5 chars into 3 available) [E0637]"]
8390 );
8391 assert!(text.contains("global @a : bytes 6 = { bytes \"123\", bytes \"xyz\" }"), "{text}");
8392 assert!(
8393 text.contains(
8394 "global @b : bytes 15 = { bytes \"12345\", bytes \"678\\00\", zero 1, \
8395 bytes \"9\\00\", zero 3 }"
8396 ),
8397 "{text}"
8398 );
8399 assert!(
8402 text.contains("global @c : bytes 8 = { bytes \"1234\", bytes \"567\\00\" }"),
8403 "{text}"
8404 );
8405 }
8406
8407 #[test]
8408 fn a_cast_of_a_record_to_its_own_type_is_the_object_that_was_cast() {
8409 let text = body(concat!(
8413 "struct s { int a, b; };\nstruct v { struct s s; int t; };\n",
8414 "void g(struct v *);\n",
8415 "void f(struct s *p) { struct v w = { (struct s)*p, 5 }; g(&w); }\n",
8416 ));
8417 assert_eq!(text.matches("memcpy").count(), 1, "{text}");
8418 }
8419
8420 #[test]
8421 fn a_compound_literal_read_in_a_static_initializer_lays_its_bytes_into_the_image() {
8422 let text = ir(concat!(
8427 "struct s { int x; };\n",
8428 "struct t { struct s s; int o; } a = { (struct s){ 2 }, 3 };\n",
8429 "int n = (int){ 7 };\n",
8430 "struct u { struct s p; struct s q; } b = { (struct s){ 1 }, (struct s){ } };\n",
8431 ));
8432 assert!(text.contains("global @a : bytes 8 = { i32 2, i32 3 }"), "{text}");
8433 assert!(text.contains("global @n : i32 = 7,"), "{text}");
8434 assert!(text.contains("global @b : bytes 8 = { i32 1, zero 4 }"), "{text}");
8437 }
8438
8439 #[test]
8440 fn the_address_of_a_compound_literal_asks_for_the_object_it_points_at() {
8441 let text = ir("struct s { int x; };\nstruct s *q = &(struct s){ 9 };\n");
8445 assert!(text.contains("global @.Lanon.0 : i32 = 9, align 4, linkage(internal)"), "{text}");
8446 assert!(text.contains("global @q : bytes 8 = { addr.8 @.Lanon.0 }"), "{text}");
8447 }
8448
8449 #[test]
8450 fn an_object_of_no_size_at_all_has_an_image_with_nothing_in_it() {
8451 let text = ir("unsigned char foo[1][0];\n");
8455 assert!(text.contains("global @foo : bytes 0 = {}, align 1"), "{text}");
8456 }
8457
8458 #[test]
8459 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
8460 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
8463 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
8464 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
8465 }
8466
8467 #[test]
8468 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
8469 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
8473 assert!(
8474 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
8475 "{text}"
8476 );
8477 }
8478
8479 #[test]
8480 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
8481 let text = body(
8486 "\
8487struct s { int a, b; };
8488struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
8489",
8490 );
8491 assert!(text.contains("block3(%7: ptr)"), "{text}");
8493 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
8494 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
8495 }
8496
8497 #[test]
8505 fn the_left_side_of_a_conditional_with_no_middle_is_evaluated_once() {
8506 let text = body("int f(int i) { return ++i ?: 10; }\n");
8507 assert!(text.contains("jump block3(%2)"), "the arm is the value that was tested: {text}");
8508 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
8509
8510 let text = body("long f(int i) { return ++i ?: 10L; }\n");
8513 assert!(text.contains("%5 = sext.i64 %2"), "the arm widens what was tested: {text}");
8514 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
8515
8516 let text = body("int g(void);\nint f(void) { return g() ?: 10; }\n");
8518 assert_eq!(text.matches("call @g").count(), 1, "called once: {text}");
8519
8520 let text = body("int f(int i) { return ++i ? ++i : 10; }\n");
8523 assert_eq!(text.matches("add.nsw").count(), 2, "incremented twice: {text}");
8524 }
8525
8526 #[test]
8527 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
8528 let text = ir("\
8532struct pair { int a, b; };
8533struct pair make(int a, int b);
8534struct pair twice(struct pair p) { return make(p.a, p.b); }
8535");
8536 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
8537 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
8538 }
8539
8540 #[test]
8541 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
8542 let text = ir("\
8546struct big { double v[8]; };
8547struct big grow(struct big b);
8548struct big twice(struct big b) { return grow(grow(b)); }
8549");
8550 assert!(
8551 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
8552 "{text}"
8553 );
8554 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
8555 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
8558 }
8559
8560 #[test]
8561 fn a_structure_passed_to_a_variadic_function_says_so_at_the_call() {
8562 let text = ir("\
8567struct big { double v[8]; };
8568struct pair { int a, b; };
8569int p(const char *, ...);
8570int f(struct big b, struct pair q) { return p(\"\", 1, b, q); }
8571");
8572 assert!(
8573 text.contains("call @p(%4, %5, %2 byval(64, align 8), %6) : (ptr, ...) -> i32"),
8574 "{text}"
8575 );
8576 }
8577
8578 #[test]
8579 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
8580 let body = body(
8583 "\
8584struct pair { int a, b; };
8585struct pair make(int a, int b);
8586int second(void) { return make(1, 2).b; }
8587",
8588 );
8589 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
8590 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
8591 }
8592
8593 #[test]
8594 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
8595 let source = "\
8599struct hfa { float x, y, z; };
8600int take(struct hfa h);
8601int give(struct hfa h) { return take(h); }
8602";
8603 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
8604 let mut opts = options();
8605 opts.emit = EmitKind::Ir;
8606 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
8607 let result = run(&opts, source);
8608 assert_eq!(result.messages, Vec::<String>::new());
8609 assert!(result.text().contains("func @take(f32, f32, f32) -> i32"), "{}", result.text());
8610 }
8611
8612 #[test]
8613 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
8614 let source = "\
8617int use(int *);
8618void f(int n) {
8619 {
8620 int a[n];
8621 use(a);
8622 }
8623 use(0);
8624}
8625";
8626 let body = body(source);
8627 assert!(body.contains("mul.nsw"), "{body}");
8628 assert!(body.contains("stacksave"), "{body}");
8629 assert!(body.contains("alloca %"), "{body}");
8630 assert!(body.contains("stackrestore"), "{body}");
8631 }
8632
8633 #[test]
8634 fn a_goto_out_of_the_scope_of_one_gives_its_stack_back_on_the_way() {
8635 let source = "\
8640int use(int *);
8641int f(int n) {
8642 {
8643 int a[n];
8644 if (use(a)) goto out;
8645 use(0);
8646 }
8647out:
8648 return 0;
8649}
8650";
8651 let body = body(source);
8652 assert_eq!(body.matches("stackrestore").count(), 2, "{body}");
8654 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
8655 assert!(after.starts_with(" %4\n jump block"), "{body}");
8656 }
8657
8658 #[test]
8659 fn a_goto_to_a_label_the_array_is_still_alive_at_leaves_the_stack_alone() {
8660 let source = "\
8664int use(int *);
8665int f(int n) {
8666 int a[n];
8667again:
8668 if (use(a)) goto again;
8669 return 0;
8670}
8671";
8672 let body = body(source);
8673 assert!(body.contains("stacksave"), "{body}");
8674 assert!(!body.contains("stackrestore"), "{body}");
8675 }
8676
8677 #[test]
8678 fn a_goto_back_to_a_label_in_front_of_one_gives_it_back_every_time_round() {
8679 let source = "\
8684int use(int *);
8685int f(int n) {
8686again:
8687 {
8688 int a[n];
8689 if (use(a)) goto again;
8690 }
8691 return 0;
8692}
8693";
8694 let body = body(source);
8695 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
8696 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
8697 assert!(after.starts_with(" %4\n jump block1\n"), "{body}");
8698 }
8699
8700 #[test]
8701 fn the_head_of_a_for_loop_is_a_scope_that_closes_where_the_loop_is_left() {
8702 let source = "\
8708int f(void);
8709void t(void) {
8710 int count = 10;
8711 for (; count--;) {
8712 int b[f()];
8713 int i;
8714 for (i = 0; i < f(); i++) {
8715 b[i] = count;
8716 }
8717 }
8718}
8719";
8720 let body = body(source);
8721 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
8725 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
8726 let next = after.split("\n\n").next().expect("the block the restore is in");
8729 assert!(next.contains("jump block1("), "{body}");
8730 }
8731
8732 #[test]
8733 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
8734 let source = "\
8737unsigned long f(int n) {
8738 int a[n];
8739 n = 0;
8740 return sizeof a;
8741}
8742";
8743 let body = body(source);
8744 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
8746 }
8747
8748 #[test]
8749 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
8750 let source = "\
8753int use(int);
8754int f(int x) {
8755 return ({
8756 int t = use(x);
8757 t * t;
8758 });
8759}
8760";
8761 let expected = "\
8762block0(%0: i32):
8763 %1 = call @use(%0) : (i32) -> i32
8764 %2 = mul.nsw %1, %1
8765 return %2
8766";
8767 assert_eq!(body(source), expected);
8768 }
8769
8770 #[test]
8771 fn a_comma_whose_value_is_an_object_names_the_object_the_right_side_named() {
8772 let source = "\
8776struct pair { int a, b; };
8777void bail(void);
8778int f(struct pair p) {
8779 return (bail(), p).b;
8780}
8781";
8782 let expected = "\
8783block0(%0: i64):
8784 %1 = alloca, size 8, align 4
8785 store %0 -> %1, align 4
8786 call @bail() : ()
8787 %2 = iconst.i64 4
8788 %3 = ptr_add %1, %2
8789 %4 = load.i32 %3, align 4, tbaa !1
8790 return %4
8791";
8792 assert_eq!(body(source), expected);
8793 }
8794
8795 #[test]
8796 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
8797 let source = "int f(int x) { return ({ return x; 0; }); }\n";
8801 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
8802 }
8803
8804 #[test]
8805 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
8806 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
8810 let expected = "\
8811block0(%0: ptr):
8812 %1 = va_arg.f64 %0
8813 %2 = va_arg.f64 %0
8814 %3 = fadd %1, %2
8815 return %3
8816";
8817 assert_eq!(body(source), expected);
8818 }
8819
8820 #[test]
8821 fn one_that_reads_a_structure_answers_where_the_object_is() {
8822 let source = "\
8836struct s { int a; long b; };
8837long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.b; }
8838";
8839 let expected = "\
8840block0(%0: ptr):
8841 %1 = alloca, size 16, align 16
8842 %2 = va_object %0, size 16, align 8, in(int 8 at 0, int 8 at 8)
8843 memcpy %1, %2, size 16, align 8
8844 %3 = iconst.i64 8
8845 %4 = ptr_add %1, %3
8846 %5 = load.i64 %4, align 8, tbaa !1
8847 return %5
8848";
8849 assert_eq!(body(source), expected);
8850 }
8851
8852 #[test]
8856 fn the_classification_says_which_registers_the_object_arrived_in() {
8857 let source = "\
8858struct s { double a; double b; };
8859double f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a; }
8860";
8861 assert!(
8862 body(source)
8863 .contains("va_object %0, size 16, align 8, in(float f64 at 0, float f64 at 8)"),
8864 "{}",
8865 body(source)
8866 );
8867
8868 let big = "\
8869struct s { long a[4]; };
8870long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a[0]; }
8871";
8872 assert!(body(big).contains("va_object %0, size 32, align 8\n"), "{}", body(big));
8873 }
8874
8875 #[test]
8876 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
8877 let source = "\
8881int f(int c) {
8882 void *p = c ? &&one : &&two;
8883 goto *p;
8884one:
8885 return 1;
8886two:
8887 return 2;
8888}
8889";
8890 let expected = "\
8891block0(%0: i32):
8892 %1 = iconst.i32 0
8893 %2 = icmp ne %0, %1
8894 br_if %2, block1, block2
8895
8896block1:
8897 %3 = block_addr block3
8898 jump block4(%3)
8899
8900block2:
8901 %4 = block_addr block5
8902 jump block4(%4)
8903
8904block3:
8905 %5 = iconst.i32 1
8906 return %5
8907
8908block4(%6: ptr):
8909 indirect_br %6, block3, block5
8910
8911block5:
8912 %7 = iconst.i32 2
8913 return %7
8914";
8915 assert_eq!(body(source), expected);
8916 }
8917
8918 fn dispatch(labels: usize) -> String {
8921 let mask = labels - 1;
8922 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
8923 for index in 0..labels {
8924 source.push_str(&format!(" &&a{index},"));
8925 }
8926 source.push_str(" };\n\tint w = n, x = n + 1, y = n + 2, z = n + 3;\n");
8927 source.push_str(&format!("\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
8928 for index in 0..labels {
8929 let step = match index % 4 {
8930 0 => "w += x;",
8931 1 => "x += y;",
8932 2 => "y += z;",
8933 _ => "z += w;",
8934 };
8935 source.push_str(&format!("a{index}:\n\t{step}\n"));
8936 source.push_str("\tif (--n <= 0) return w + x + y + z;\n");
8937 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
8938 }
8939 source.push_str("}\n");
8940 source
8941 }
8942
8943 fn in_front_of_the_jump(text: &str) -> usize {
8945 let (before, _) = text.split_once("\tjmp\t*%").expect("a jump through a register");
8946 before.lines().rev().take_while(|line| line.starts_with("\tmov")).count()
8947 }
8948
8949 #[test]
8959 fn a_jump_through_a_register_writes_what_it_carries_and_not_the_whole_table() {
8960 let small = in_front_of_the_jump(&asm(&dispatch(4)));
8961 let large = in_front_of_the_jump(&asm(&dispatch(32)));
8962 assert_eq!(small, large, "eight times the labels and the same values in hand");
8963 assert!(large <= 8, "the values the loop keeps, and not a set of them per label: {large}");
8964 }
8965
8966 fn crowded(labels: usize) -> String {
8969 const VALUES: usize = 24;
8970 let mask = labels - 1;
8971 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
8972 for index in 0..labels {
8973 source.push_str(&format!(" &&a{index},"));
8974 }
8975 source.push_str(" };\n\t");
8976 for value in 0..VALUES {
8977 source.push_str(&format!("int v{value} = n + {value}; "));
8978 }
8979 let sum: Vec<String> = (0..VALUES).map(|value| format!("v{value}")).collect();
8980 source.push_str(&format!("\n\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
8981 for index in 0..labels {
8982 let (to, from) = (index % VALUES, (index + 1) % VALUES);
8983 source.push_str(&format!("a{index}:\n\tv{to} += v{from};\n"));
8984 source.push_str(&format!("\tif (--n <= 0) return {};\n", sum.join(" + ")));
8985 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
8986 }
8987 source.push_str("}\n");
8988 source
8989 }
8990
8991 fn the_frame(text: &str) -> u64 {
8993 text.lines()
8994 .find_map(|line| {
8995 let (size, _) = line.strip_prefix("\tsubq\t$")?.split_once(", %rsp")?;
8996 size.parse().ok()
8997 })
8998 .expect("a function that opens a frame")
8999 }
9000
9001 #[test]
9010 fn a_frame_holds_what_is_wanted_at_once_and_not_a_slot_for_every_label() {
9011 let small = the_frame(&asm(&crowded(16)));
9012 let large = the_frame(&asm(&crowded(64)));
9013 assert_eq!(small, large, "four times the labels and the same values: {small}, {large}");
9014 }
9015
9016 #[test]
9025 fn a_template_that_names_its_own_registers_gets_the_ones_it_named() {
9026 let source = "void save(void *nlr) {
9027 __asm volatile (
9028 \"movq %%rsp, 32(%%rdi) \\n\"
9029 \"movq %%rbx, 40(%%rdi) \\n\"
9030 \"movq %%r12, 48(%%rdi) \\n\"
9031 : : \"D\" (nlr) : \"memory\");
9032}
9033";
9034 let text = asm(source);
9035 assert!(text.contains("\tmovq\t%rsp, 32(%rdi)\n"), "{text}");
9036 assert!(text.contains("\tmovq\t%rbx, 40(%rdi)\n"), "{text}");
9037 assert!(text.contains("\tmovq\t%r12, 48(%rdi)\n"), "{text}");
9038 }
9039
9040 #[test]
9041 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
9042 let source = "void **next(void);
9045void f(void) { goto *next(); }
9046";
9047 let expected = "\
9048block0:
9049 %0 = call @next() : () -> ptr
9050 unreachable
9051";
9052 assert_eq!(body(source), expected);
9053 }
9054
9055 #[test]
9056 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
9057 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
9060 let expected = "\
9061block0:
9062 inline_asm.volatile \"mfence\", \"\", \"memory\"()
9063 return
9064";
9065 assert_eq!(body(source), expected);
9066 }
9067
9068 #[test]
9069 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
9070 let source = "\
9073int f(int x, int y) {
9074 int r;
9075 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
9076 return r + y;
9077}
9078";
9079 let expected = "\
9080block0(%0: i32, %1: i32):
9081 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
9082 %4 = add.nsw %2, %3
9083 return %4
9084";
9085 assert_eq!(body(source), expected);
9086 }
9087
9088 #[test]
9089 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
9090 let source = "\
9095struct pair { int a, b; };
9096int f(int x) {
9097 int slot = x;
9098 struct pair p = { x, x };
9099 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
9100 return slot + p.a;
9101}
9102";
9103 let text = body(source);
9104 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
9105 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
9106 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
9107 }
9108
9109 #[test]
9110 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
9111 let source = "\
9116int f(int x) {
9117 int r = 7;
9118 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
9119 return r;
9120away:
9121 return r;
9122}
9123";
9124 let expected = "\
9125block0(%0: i32):
9126 %1 = iconst.i32 7
9127 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
9128
9129block1:
9130 return %2
9131
9132block2:
9133 return %1
9134";
9135 assert_eq!(body(source), expected);
9136 }
9137
9138 #[test]
9139 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
9140 let mut opts = options();
9144 opts.emit = EmitKind::Ir;
9145 for (source, expected) in [
9146 (
9147 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
9148 "output operand constraint lacks '='",
9149 ),
9150 (
9151 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
9152 "lvalue required in 'asm' statement",
9153 ),
9154 (
9155 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
9156 "read-only variable 'g' used as 'asm' output",
9157 ),
9158 (
9159 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
9160 "input operand constraint contains '='",
9161 ),
9162 (
9163 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
9164 "memory input 0 is not directly addressable",
9165 ),
9166 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
9167 (
9168 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
9169 "duplicate asm operand name 'a'",
9170 ),
9171 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
9172 ] {
9173 let result = run(&opts, source);
9174 assert!(result.failed(), "expected this to be reported:\n{source}");
9175 assert!(
9176 result.messages.iter().any(|m| m.contains(expected)),
9177 "{expected}\n{:?}",
9178 result.messages
9179 );
9180 }
9181 }
9182
9183 #[test]
9188 fn an_asm_at_file_scope_that_is_directives_becomes_the_objects_it_defines() {
9189 let text = ir(concat!(
9190 "__asm__(\n",
9191 " \".section .rodata\\n\"\n",
9192 " \".globl first\\n\"\n",
9193 " \".balign 8\\n\"\n",
9194 " \"first:\\n\"\n",
9195 " \".long 1\\n\"\n",
9196 " \".long 2\\n\"\n",
9197 " \".globl last\\n\"\n",
9198 " \"last:\\n\"\n",
9199 " \".quad last - first\\n\");\n",
9200 "extern const int first[];\n",
9201 "extern const long last;\n",
9202 ));
9203 assert!(text.contains("global @first : bytes 8 = { i32 1, i32 2 }, align 8"), "{text}");
9204 assert!(text.contains("global @last : i64 = 8"), "{text}");
9205 }
9206
9207 #[test]
9211 fn a_name_an_asm_at_file_scope_defined_is_not_undone_by_a_declaration_of_it() {
9212 let text = ir(concat!(
9213 "__asm__(\".data\\n.globl counter\\ncounter:\\n.long 7\\n\");\n",
9214 "extern int counter;\n",
9215 "int read(void) { return counter; }\n",
9216 ));
9217 assert!(text.contains("global @counter : i32 = 7"), "{text}");
9218 }
9219
9220 #[test]
9225 fn bytes_under_no_label_at_file_scope_are_a_global_in_front_of_the_label() {
9226 let text = ir(concat!(
9227 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n662:\\n",
9228 ".pushsection .data.ignore\\n.byte 7\\n.popsection\\n.byte 662b - 661b\\n\");\n",
9229 "extern unsigned char stuff[];\n",
9230 "int read(void) { return stuff[0]; }\n",
9231 ));
9232 let under = text.find("global @.Lasm.0 : i8 = 41").expect(&text);
9233 let named = text.find("global @stuff : i8 = 42").expect(&text);
9234 assert!(under < named, "the bytes under no label come first: {text}");
9235 assert!(text.contains("global @.Lasm.1 : i8 = 7, align 1, linkage(internal), section"));
9236 let after = text.find("global @.Lasm.2 : i8 = 1").expect(&text);
9241 assert!(named < after, "{text}");
9242 }
9243
9244 #[test]
9249 fn a_distance_from_here_at_file_scope_is_a_hole_naming_the_global_it_measures_to() {
9250 let text = ir(concat!(
9251 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n",
9252 ".pushsection .data.ignore\\n.long 661b - .\\n.popsection\\n\");\n",
9253 "extern unsigned char stuff[];\n",
9254 "int read(void) { return stuff[0]; }\n",
9255 ));
9256 assert!(text.contains("global @.Lasm.1 : bytes 4 = { away.4 @stuff }"), "{text}");
9260 }
9261
9262 #[test]
9267 fn a_set_at_file_scope_is_a_second_name_for_what_it_names() {
9268 let text = ir(concat!(
9269 "void base(void) {}\n",
9270 "__asm__(\".weak one\\n.set one, base\");\n",
9271 "__asm__(\".globl two\\n.set two, base\");\n",
9272 "__asm__(\".set three, base\");\n",
9273 "void three(void) {}\n",
9274 ));
9275 assert!(text.contains("alias @one = @base, linkage(weak)"), "{text}");
9276 assert!(text.contains("alias @two = @base"), "{text}");
9277 assert!(!text.contains("alias @three"), "a definition of the name wins: {text}");
9278 assert!(text.contains("func @three"), "{text}");
9279 }
9280
9281 #[test]
9286 fn a_set_of_a_name_this_file_does_not_define_says_so() {
9287 let messages = errors("__asm__(\".set here, elsewhere\");\n");
9288 assert!(
9289 messages
9290 .iter()
9291 .any(|m| m.contains("'here' is aliased to undefined symbol 'elsewhere'")
9292 && m.contains("E0697")),
9293 "{messages:?}"
9294 );
9295 }
9296
9297 #[test]
9300 fn an_incbin_at_file_scope_is_the_bytes_of_the_file_it_names() {
9301 let mut opts = options();
9302 opts.emit = EmitKind::Ir;
9303 let mut fs = MemoryFileSystem::new();
9304 fs.insert(
9305 "/main.c",
9306 b"__asm__(\".data\\n.globl blob\\nblob:\\n.incbin \\\"seed\\\"\\n\");\n".to_vec(),
9307 );
9308 fs.insert("seed", b"hi".to_vec());
9309 let result = compile(&opts, "/main.c", &fs);
9310 assert_eq!(result.messages, Vec::<String>::new());
9311 let text = result.text();
9312 assert!(text.contains("global @blob : bytes 2 = { bytes \"hi\" }"), "{text}");
9313 }
9314
9315 #[test]
9318 fn an_incbin_naming_a_file_that_is_not_there_says_which_file() {
9319 let messages = errors("__asm__(\".data\\nb:\\n.incbin \\\"nowhere\\\"\\n\");\n");
9320 assert!(
9321 messages
9322 .iter()
9323 .any(|m| m.contains("cannot open 'nowhere' for reading") && m.contains("E0702")),
9324 "{messages:?}"
9325 );
9326 }
9327
9328 #[test]
9331 fn an_instruction_in_an_asm_at_file_scope_is_refused_rather_than_ignored() {
9332 for source in [
9333 "__asm__(\".text\\n.globl f\\nf:\\n ret\\n\");\n",
9334 "__asm__(\".data\\n.set alias, 4\\n\");\n",
9335 ] {
9336 let messages = errors(source);
9337 assert!(
9338 messages
9339 .iter()
9340 .any(|m| m.contains("not supported yet")
9341 && m.contains("in an `asm` at file scope")),
9342 "{source}\n{messages:?}"
9343 );
9344 }
9345 }
9346
9347 #[test]
9357 fn a_naked_function_is_its_own_prologue_and_its_own_ending() {
9358 let text = asm(concat!(
9359 "unsigned nlr_push_tail(void *nlr);\n",
9360 "__attribute__((naked)) unsigned nlr_push(void *nlr) {\n",
9361 " __asm volatile(\n",
9362 " \"movq (%rsp), %rax\\n\"\n",
9363 " \"movq %rax, 16(%rdi)\\n\"\n",
9364 " \"movq %rbx, 40(%rdi)\\n\"\n",
9365 " \"jmp nlr_push_tail\\n\");\n",
9366 "}\n",
9367 ));
9368 assert!(text.contains("\tmovq\t(%rsp), %rax\n"), "{text}");
9369 assert!(text.contains("\tjmp\tnlr_push_tail\n"), "{text}");
9370 assert!(text.contains("\tud2\n"), "{text}");
9371 assert!(!text.contains("\tpushq\t"), "nothing is saved in front of it: {text}");
9372 assert!(!text.contains("\tret\n"), "the jump is where it ends: {text}");
9373 }
9374
9375 #[test]
9378 fn what_a_function_without_a_prologue_cannot_be_given_is_refused() {
9379 let mut opts = options();
9380 opts.emit = EmitKind::Asm;
9381 for (source, why) in [
9382 (
9383 "__attribute__((naked)) void f(void) { volatile long a[8]; a[0] = 1; }\n",
9384 "bytes of frame",
9385 ),
9386 (
9387 "__attribute__((naked)) void f(int n) { char a[n]; __asm(\"nop\" ::\"r\"(a)); }\n",
9388 "has no prologue to point a frame pointer at it with",
9389 ),
9390 ("void elsewhere(void); void f(void) { __asm(\"jmp elsewhere\"); }\n", "jumps out of"),
9391 ] {
9392 let result = run(&opts, source);
9393 assert!(result.failed(), "expected this to be refused:\n{source}");
9394 assert!(
9395 result.messages.iter().any(|message| message.contains(why)),
9396 "{:?}",
9397 result.messages
9398 );
9399 }
9400 }
9401
9402 #[test]
9403 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
9404 let mut opts = options();
9405 opts.emit = EmitKind::Ir;
9406 for source in [
9407 "int f(int n) { void *p = &&out; if (n) goto *p; { int a[n]; out: return 1; } }\n",
9408 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
9409 ] {
9410 let result = run(&opts, source);
9411 assert!(result.failed(), "expected this to be reported:\n{source}");
9412 assert!(
9413 result.messages.iter().any(|m| m.contains("not supported yet")),
9414 "{:?}",
9415 result.messages
9416 );
9417 }
9418 }
9419
9420 fn round_trip(source: &str) -> (String, String) {
9422 let printed = ir(source);
9423 let mut opts = options();
9424 opts.emit = EmitKind::Ir;
9425 let mut fs = MemoryFileSystem::new();
9426 fs.insert("/main.ir", printed.clone().into_bytes());
9427 let result = compile_ir(&opts, "/main.ir", &fs);
9428 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
9429 (printed, result.text().to_owned())
9430 }
9431
9432 #[test]
9433 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
9434 let (printed, again) = round_trip(
9438 "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",
9439 );
9440 assert_eq!(printed, again);
9441 }
9442
9443 #[test]
9444 fn ir_that_is_not_ir_says_which_line_stopped_it() {
9445 let mut opts = options();
9446 opts.emit = EmitKind::Ir;
9447 let mut fs = MemoryFileSystem::new();
9448 let text = "\
9449; ModuleID = 'a.c'
9450; format 0
9451target triple = \"x86_64-unknown-linux-gnu\"
9452target datalayout = \"e-p:64:64-i64:64-S128\"
9453
9454func @f(), linkage(external) {
9455block0:
9456 frobnicate
9457}
9458";
9459 fs.insert("/main.ir", text.as_bytes().to_vec());
9460 let result = compile_ir(&opts, "/main.ir", &fs);
9461 assert!(result.failed());
9462 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
9463 }
9464
9465 #[test]
9466 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
9467 let mut opts = options();
9470 opts.emit = EmitKind::Ir;
9471 let mut fs = MemoryFileSystem::new();
9472 let text = "\
9473; ModuleID = 'a.c'
9474; format 0
9475target triple = \"x86_64-unknown-linux-gnu\"
9476target datalayout = \"e-p:64:64-i64:64-S128\"
9477
9478func @f(), linkage(external) {
9479block0:
9480 %0 = iconst.i32 1
9481 return %0
9482}
9483";
9484 fs.insert("/main.ir", text.as_bytes().to_vec());
9485 let result = compile_ir(&opts, "/main.ir", &fs);
9486 assert!(result.failed());
9487 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
9488 }
9489
9490 #[test]
9491 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
9492 let mut fs = MemoryFileSystem::new();
9494 fs.insert("/main.ir", Vec::new());
9495 let result = compile_ir(&options(), "/main.ir", &fs);
9496 assert!(result.failed());
9497 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
9498 }
9499
9500 #[test]
9501 fn the_printed_ir_reads_back_as_the_same_module() {
9502 let text = ir("\
9505struct point { int x, y; };
9506static const char greeting[] = \"hi\";
9507int table[4] = { 1, 2, 3 };
9508int puts(const char *);
9509double half(double x) { return x / 2.0; }
9510int f(int n) {
9511 int total = 0;
9512 for (int i = 0; i < n; i++) {
9513 if (i == 3) continue;
9514 total += table[i];
9515 }
9516 switch (n) {
9517 case 0: total = 1;
9518 case 1: total++; break;
9519 default: total = -total;
9520 }
9521 struct point p = { total, 1 };
9522 int *q = &p.y;
9523 puts(greeting);
9524 return p.x + *q;
9525}
9526int dispatch(int c) {
9527 void *p = c ? &&one : &&two;
9528 goto *p;
9529one:
9530 return 1;
9531two:
9532 return 2;
9533}
9534int assembly(int x, int *p) {
9535 int r;
9536 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
9537 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
9538 return r;
9539away:
9540 return 0;
9541}
9542");
9543 let mut names = Interner::new();
9544 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
9545 assert_eq!(rucc_ir::print(&module, &names), text);
9546 }
9547
9548 #[test]
9549 fn what_save_temps_keeps_is_the_text_that_was_compiled_and_the_assembly_that_was_assembled() {
9550 let mut opts = options();
9554 opts.emit = EmitKind::Object;
9555 opts.save_temps = rucc_session::SaveTemps::Object;
9556 let result = run(&opts, "#define N 2\nint a[N];\n");
9557 assert_eq!(result.messages, Vec::<String>::new());
9558 let text = result.temps.preprocessed.expect("the preprocessed text");
9559 assert!(text.contains("int a[2];"), "{text}");
9560 assert!(text.starts_with("# 1 \"/main.c\""), "{text}");
9561 let asm = result.temps.assembly.expect("the assembly");
9562 assert!(asm.contains("a:"), "{asm}");
9563 assert!(matches!(result.artifact, Artifact::Object { .. }), "{:?}", result.artifact);
9564 }
9565
9566 #[test]
9567 fn nothing_is_kept_unless_the_flag_asked_for_it() {
9568 let mut opts = options();
9571 opts.emit = EmitKind::Object;
9572 assert_eq!(run(&opts, "int a;\n").temps, Temps::default());
9573 }
9574
9575 #[test]
9576 fn a_compilation_that_stops_before_the_back_end_keeps_the_text_and_no_assembly() {
9577 let mut opts = options();
9580 opts.emit = EmitKind::Ir;
9581 opts.save_temps = rucc_session::SaveTemps::Cwd;
9582 let result = run(&opts, "int a;\n");
9583 assert!(result.temps.preprocessed.is_some());
9584 assert_eq!(result.temps.assembly, None);
9585 }
9586
9587 fn span(from: u64, len: u64, held: rucc_debug::Held) -> rucc_debug::Span {
9589 rucc_debug::Span { from, len, held }
9590 }
9591
9592 #[test]
9593 fn two_stretches_that_meet_and_agree_come_out_as_one() {
9594 let one = span(0, 4, rucc_debug::Held::Reg(3));
9595 let two = span(4, 4, rucc_debug::Held::Reg(3));
9596 assert_eq!(settle(vec![two, one]), vec![span(0, 8, rucc_debug::Held::Reg(3))]);
9597 }
9598
9599 #[test]
9600 fn a_stretch_another_starts_inside_and_disagrees_with_ends_where_the_other_starts() {
9601 let one = span(0, 8, rucc_debug::Held::Reg(3));
9602 let two = span(4, 8, rucc_debug::Held::Reg(4));
9603 let settled = settle(vec![one, two]);
9606 assert_eq!(
9607 settled,
9608 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 8, rucc_debug::Held::Reg(4))]
9609 );
9610 }
9611
9612 #[test]
9613 fn a_stretch_cut_by_one_that_ends_first_does_not_come_back_after_it() {
9614 let one = span(0, 16, rucc_debug::Held::Reg(3));
9617 let two = span(4, 4, rucc_debug::Held::Reg(4));
9618 assert_eq!(
9619 settle(vec![one, two]),
9620 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 4, rucc_debug::Held::Reg(4))]
9621 );
9622 }
9623
9624 #[test]
9625 fn a_stretch_inside_another_that_agrees_with_it_cuts_nothing() {
9626 let one = span(0, 16, rucc_debug::Held::Reg(3));
9627 let two = span(4, 4, rucc_debug::Held::Reg(3));
9628 assert_eq!(settle(vec![one, two]), vec![span(0, 16, rucc_debug::Held::Reg(3))]);
9629 }
9630
9631 #[test]
9632 fn a_stretch_two_others_disagree_over_the_whole_of_says_nothing_at_all() {
9633 let one = span(0, 8, rucc_debug::Held::Reg(3));
9634 let two = span(0, 8, rucc_debug::Held::Frame(-16));
9635 assert_eq!(settle(vec![one, two]), Vec::new());
9636 }
9637
9638 #[test]
9639 fn stretches_with_a_gap_between_them_keep_the_gap() {
9640 let one = span(0, 4, rucc_debug::Held::Reg(3));
9641 let two = span(16, 4, rucc_debug::Held::Reg(3));
9642 assert_eq!(settle(vec![one, two]), vec![one, two]);
9643 }
9644
9645 fn extent(len: usize) -> rucc_object::Extent {
9647 rucc_object::Extent {
9648 name: "f".to_owned(),
9649 start: 0,
9650 len,
9651 align: 1,
9652 binding: rucc_object::Binding::Global,
9653 visibility: rucc_object::Visibility::Default,
9654 patch: None,
9655 }
9656 }
9657
9658 fn row(at: usize, lo: u32, hi: u32) -> rucc_asm::Row {
9660 let span = Span::new(lo, hi);
9661 rucc_asm::Row { at, span, inst: None }
9662 }
9663
9664 #[test]
9665 fn a_row_ends_where_the_next_address_begins() {
9666 let rows = [row(0, 0, 1), row(4, 1, 2), row(10, 2, 3)];
9667 assert_eq!(ends(&extent(16), &rows), vec![4, 10, 16]);
9668 }
9669
9670 #[test]
9671 fn rows_sharing_an_address_all_end_where_the_next_address_begins() {
9672 let rows = [row(0, 0, 1), row(4, 1, 2), row(4, 2, 3), row(4, 3, 4)];
9675 assert_eq!(ends(&extent(12), &rows), vec![4, 12, 12, 12]);
9676 }
9677
9678 #[test]
9679 fn the_rows_of_a_scope_that_are_next_to_each_other_come_out_as_one_stretch() {
9680 let rows = [row(0, 0, 4), row(4, 10, 14), row(8, 14, 18), row(12, 40, 44)];
9681 let ends = ends(&extent(16), &rows);
9682 let scope = Span::new(8, 20);
9683 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 8 }]);
9684 }
9685
9686 #[test]
9687 fn a_scope_the_back_end_split_in_two_comes_out_as_two_stretches() {
9688 let rows = [row(0, 10, 14), row(4, 40, 44), row(8, 14, 18)];
9689 let ends = ends(&extent(12), &rows);
9690 let scope = Span::new(8, 20);
9691 let over = spread(scope, &ends, &rows);
9692 assert_eq!(
9693 over,
9694 vec![rucc_debug::Reach { from: 0, len: 4 }, rucc_debug::Reach { from: 8, len: 4 }]
9695 );
9696 }
9697
9698 #[test]
9699 fn a_row_with_no_source_of_its_own_belongs_to_no_scope() {
9700 let rows = [rucc_asm::Row { at: 0, span: Span::DUMMY, inst: None }, row(4, 10, 14)];
9702 let ends = ends(&extent(8), &rows);
9703 let scope = Span::new(0, 20);
9704 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 4 }]);
9705 }
9706
9707 fn scope(parent: Option<usize>, lo: u32, hi: u32) -> crate::shapes::Scope {
9709 let span = Span::new(lo, hi);
9710 crate::shapes::Scope { parent, span }
9711 }
9712
9713 #[test]
9714 fn a_function_gets_the_scopes_its_own_locals_are_in_and_nothing_else() {
9715 let scopes = [scope(None, 0, 10), scope(None, 20, 30), scope(Some(1), 22, 26)];
9717 let rows = [row(0, 22, 24), row(4, 26, 28)];
9718 let (out, at) = nests(&[Some(2)], &scopes, &extent(8), &rows);
9719 assert_eq!(at.get(&1), Some(&0));
9722 assert_eq!(at.get(&2), Some(&1));
9723 assert_eq!(at.get(&0), None);
9724 assert_eq!(out.len(), 2);
9725 assert_eq!(out[0].parent, None);
9726 assert_eq!(out[1].parent, Some(0));
9727 assert_eq!(out[0].over, vec![rucc_debug::Reach { from: 0, len: 8 }]);
9728 assert_eq!(out[1].over, vec![rucc_debug::Reach { from: 0, len: 4 }]);
9729 }
9730
9731 #[test]
9732 fn a_local_written_straight_into_the_body_pulls_no_scope_in() {
9733 let scopes = [scope(None, 20, 30)];
9734 let rows = [row(0, 22, 24)];
9735 let (out, at) = nests(&[None], &scopes, &extent(4), &rows);
9736 assert_eq!(out, Vec::new());
9737 assert!(at.is_empty());
9738 }
9739
9740 #[test]
9741 fn a_scope_whose_code_all_went_away_is_still_one_of_the_functions_scopes() {
9742 let scopes = [scope(None, 20, 30)];
9746 let rows = [row(0, 40, 44)];
9747 let (out, at) = nests(&[Some(0)], &scopes, &extent(4), &rows);
9748 assert_eq!(at.get(&0), Some(&0));
9749 assert_eq!(out.len(), 1);
9750 assert_eq!(out[0].over, Vec::new());
9751 }
9752}