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 exceptions: opts.exceptions,
386 read: &mut read,
387 },
388 );
389 let failed = lowered.diagnostics.iter().any(|d| d.severity.is_fatal());
393 if !failed {
394 if let Err(errors) = rucc_ir::verify(&lowered.module, &sess.interner) {
399 for error in errors {
400 diagnostics.push(internal(&format!("invalid IR, {error}")));
401 }
402 } else if let Err(complaints) =
403 instrument(&mut lowered.module, &mut sess.interner, opts)
404 .map(|done| instrumented = done)
405 {
406 diagnostics.extend(complaints);
407 } else if let Err(complaints) = optimize(
408 &mut lowered.module,
409 &mut sess.interner,
410 &sess.target,
411 opts,
412 name,
413 &mut dumps,
414 &mut remarks,
415 ) {
416 diagnostics.extend(complaints);
417 } else if opts.emit == EmitKind::SafetySummary {
418 artifact = Artifact::Text(
423 rucc_safety::summarize(
424 &lowered.module,
425 &sess.interner,
426 name,
427 opts.safety.as_str(),
428 instrumented.checks,
429 instrumented.interposed,
430 instrumented.crossings,
431 )
432 .render(),
433 );
434 } else if opts.emit == EmitKind::Ir {
435 artifact =
440 Artifact::Text(rucc_ir::print(&lowered.module, &sess.interner));
441 } else {
442 match generate(
445 &mut lowered.module,
446 &mut sess.interner,
447 &sess.target,
448 opts,
449 &mut Recording {
450 fired: &mut fired,
451 pressure: &mut pressure,
452 lowerings: &mut lowerings,
453 },
454 &mut temps.assembly,
455 Origin { map: &sess.sources, name, meaning: &meaning },
456 ) {
457 Ok(made) => artifact = made,
458 Err(complaints) => diagnostics.extend(complaints),
459 }
460 }
461 }
462 diagnostics.extend(lowered.diagnostics);
463 }
464 _ => {}
465 }
466 }
467 diagnostics.extend(checked.diagnostics);
468 }
469 let mut wants = rucc_opt::Wants::none();
472 for spec in &opts.opt_info {
473 let _ = wants.add(spec);
475 }
476 if wants.wants(rucc_opt::stats::Kind::Optimized) {
477 remarks.push_str(&lowerings.remarks(name));
478 }
479
480 let mut messages = Vec::with_capacity(diagnostics.len());
481 let mut errors = 0;
482 for diag in &diagnostics {
483 if rucc_diag::dropped(diag, &sess.sources, opts.warnings, opts.system_header_warnings) {
489 continue;
490 }
491 if diag.severity.is_fatal()
492 || (diag.severity == Severity::Warning && opts.warnings_are_errors)
493 {
494 errors += 1;
495 }
496 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
497 }
498 if errors > 0 {
499 artifact = Artifact::Nothing;
501 }
502 Compiled { artifact, messages, errors, fired, pressure, lowerings, dumps, remarks, deps, temps }
505}
506
507#[must_use]
517pub fn compile_ir(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
518 let mut sess = Session::new(opts.clone());
519 if opts.emit != EmitKind::Ir {
520 return failure(format!(
521 "{name}: an input of IR can only be emitted as IR, and `--emit={}` asks for what \
522 the C in front of it became",
523 opts.emit.as_str()
524 ));
525 }
526 let bytes = match fs.read(Path::new(name)) {
527 Ok(bytes) => bytes,
528 Err(e) => return failure(format!("{name}: {e}")),
529 };
530 let Ok(text) = std::str::from_utf8(bytes.as_slice()) else {
531 return failure(format!("{name}: this is not text, so it is not IR"));
532 };
533
534 let module = match rucc_ir::parse(text, &mut sess.interner) {
535 Ok(module) => module,
536 Err(error) => {
537 return failure(format!("{name}:{}: {}", error.line, error.message));
538 }
539 };
540 let mut diagnostics: Vec<Diagnostic> = Vec::new();
541 if let Err(errors) = rucc_ir::verify(&module, &sess.interner) {
542 for error in errors {
543 diagnostics.push(invalid(&format!("invalid IR, {error}")));
544 }
545 }
546 let mut messages = Vec::with_capacity(diagnostics.len());
547 for diag in &diagnostics {
548 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
549 }
550 let errors = u32::try_from(messages.len()).unwrap_or(u32::MAX);
551 let artifact = if errors > 0 {
552 Artifact::Nothing
553 } else {
554 Artifact::Text(rucc_ir::print(&module, &sess.interner))
555 };
556 Compiled {
558 artifact,
559 messages,
560 errors,
561 fired: Fired::new(),
562 pressure: Pressure::new(),
563 lowerings: Lowerings::new(),
564 dumps: Vec::new(),
565 remarks: String::new(),
566 deps: Vec::new(),
567 temps: Temps::default(),
568 }
569}
570
571fn instrument(
594 module: &mut rucc_ir::Module,
595 names: &mut Interner,
596 opts: &Options,
597) -> Result<Instrumented, Vec<Diagnostic>> {
598 if !opts.safety.instruments() {
599 return Ok(Instrumented::default());
600 }
601 let mut checks = rucc_safety::run(module, opts.subobject, opts.promise, opts.races);
602 checks.freed = rucc_safety::ending::checks(module, names);
610 let interposed = rucc_safety::redirect(module, names);
615 let crossings = rucc_safety::witness(module, names);
618 match rucc_ir::verify(module, names) {
619 Ok(()) => Ok(Instrumented { checks, interposed, crossings }),
620 Err(errors) => Err(errors
621 .iter()
622 .map(|e| internal(&format!("invalid IR after check insertion, {e}")))
623 .collect()),
624 }
625}
626
627#[derive(Clone, Copy, Debug, Default)]
633struct Instrumented {
634 checks: rucc_safety::Counts,
636 interposed: usize,
638 crossings: rucc_safety::Sites,
640}
641
642fn optimize(
654 module: &mut rucc_ir::Module,
655 names: &mut Interner,
656 target: &TargetInfo,
657 opts: &Options,
658 file: &str,
659 dumps: &mut Vec<rucc_opt::Dump>,
660 remarks: &mut String,
661) -> Result<(), Vec<Diagnostic>> {
662 let mut settings = rucc_opt::Options::for_level(opts.opt_level);
663 settings.interposition = match opts.interposition {
669 true => replaceable(target, opts),
670 false => IrPic::Executable,
671 };
672 settings.toggles.clone_from(&opts.passes);
673 settings.builtins = opts.builtins && opts.hosted;
678 settings.no_builtin.clone_from(&opts.no_builtin);
679 settings.fuel = opts.pass_fuel.iter().cloned().collect();
680 settings.global_fuel = opts.pass_fuel_global;
681 settings.verify |= opts.verify_each;
682 for (on, spec) in &opts.pass_gates {
683 if let Err(why) = settings.gates.add(*on, spec) {
686 return Err(vec![internal(&why)]);
687 }
688 }
689 for spec in &opts.dump_ir {
690 if let Err(why) = settings.dumps.add(spec) {
693 return Err(vec![internal(&why)]);
694 }
695 }
696 let mut wants = rucc_opt::Wants::none();
697 for spec in &opts.opt_info {
698 if let Err(why) = wants.add(spec) {
701 return Err(vec![internal(&why)]);
702 }
703 }
704 let report = rucc_opt::run(module, names, &settings);
705 remarks.push_str(&rucc_opt::optinfo::render(file, &report, names, wants));
706 dumps.extend(report.dumps);
707 match report.broke.is_empty() {
708 true => Ok(()),
709 false => Err(report.broke.iter().map(|why| internal(why)).collect()),
710 }
711}
712
713fn replaceable(target: &TargetInfo, opts: &Options) -> IrPic {
747 match (target.tuple.os().object_format(), opts.pic) {
748 (Some(ObjectFormat::Elf), Pic::Library) => IrPic::Library,
749 _ => IrPic::Executable,
750 }
751}
752
753#[derive(Clone, Copy)]
760struct Origin<'a> {
761 map: &'a SourceMap,
763 name: &'a str,
765 meaning: &'a crate::shapes::Meaning,
767}
768
769fn generate(
770 module: &mut rucc_ir::Module,
771 names: &mut Interner,
772 target: &TargetInfo,
773 opts: &Options,
774 recording: &mut Recording<'_>,
775 assembly: &mut Option<String>,
776 origin: Origin<'_>,
777) -> Result<Artifact, Vec<Diagnostic>> {
778 let Some(machine) = Machine::for_target(target) else {
779 return Err(vec![unsupported(&format!(
780 "there is no back end for {} in this compiler yet, so there is nothing to generate",
781 target.tuple
782 ))]);
783 };
784 if opts.protector != Protector::None && machine.conv.guard.is_none() {
789 return Err(vec![unsupported(&format!(
790 "{} is not supported for {} yet, because the stack protector on that target is not \
791 the one this compiler writes",
792 opts.protector, target.tuple
793 ))]);
794 }
795 if opts.control.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
801 return Err(vec![unsupported(&format!(
802 "-fcf-protection={} is not supported for {} yet, because what says a file was built \
803 for it there is not the note this compiler writes",
804 opts.control, target.tuple
805 ))]);
806 }
807 let profile = match machine.conv.trace {
813 Some(trace) => opts.profile.then(|| opts.hook.early(trace.fentry)),
814 None if opts.profile => {
815 return Err(vec![unsupported(&format!(
816 "-pg is not supported for {} yet, because the profiler's hook on that target is \
817 not the one this compiler calls",
818 target.tuple
819 ))]);
820 }
821 None => None,
822 };
823 if opts.patchable.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
828 return Err(vec![unsupported(&format!(
829 "-fpatchable-function-entry= is not supported for {} yet, because what records where \
830 the room is there is not the section this compiler writes",
831 target.tuple
832 ))]);
833 }
834 let flags = pipeline::Flags {
835 frame_pointer: opts.keeps_frame_pointer(),
836 red_zone: opts.red_zone,
837 stack_clash: opts.stack_clash,
838 landing: opts.control.branch(),
839 profile: match profile {
840 None => pipeline::Profile::No,
841 Some(true) => pipeline::Profile::Early,
842 Some(false) => pipeline::Profile::Late,
843 },
844 patch: pipeline::Room { after: opts.patchable.after(), before: opts.patchable.before },
845 reorder: opts.reorder_blocks.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
852 reuse: opts.stack_reuse.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
857 schedule: opts.schedule_insns.unwrap_or_else(|| opts.opt_level.schedules()),
862 align_loops: opts.align_loops.unwrap_or(false),
868 accurate: opts.cycle_accurate_model,
870 verify: opts.verify_each,
873 goal: Goal::for_size(opts.opt_level.is_size()),
878 switch: opts.switch_shape.as_deref().and_then(rucc_codegen::switch::Force::named),
880 sibling: opts.sibling_calls.unwrap_or_else(|| opts.opt_level.sibling_calls()),
882 };
883
884 if opts.safety.instruments() {
893 rucc_opt::heap::annotate(module, names);
903 rucc_safety::handover::arrange(module);
910 rucc_safety::lower(module, names);
911 if let Err(errors) = rucc_ir::verify(module, names) {
912 return Err(errors
913 .iter()
914 .map(|e| internal(&format!("invalid IR after check lowering, {e}")))
915 .collect());
916 }
917 }
918
919 let copies = target.tuple.arch() == Arch::X86_64;
930 let elsewhere = Elsewhere::of(module, replaceable(target, opts), target.object_format, copies);
931
932 let mut funcs = Vec::new();
933 let mut complaints = Vec::new();
934 for id in module.funcs() {
935 if module[id].is_declaration() {
936 continue;
937 }
938 match pipeline::compile_recording(
939 &mut module[id],
940 names,
941 &machine,
942 &elsewhere,
943 flags,
944 recording,
945 ) {
946 Ok(func) => funcs.push(func),
947 Err(why) => {
948 let name = names.resolve(module[id].name).to_owned();
949 let span = why.inst().map_or(Span::DUMMY, |inst| module[id].span(inst));
952 let said = format!("cannot generate code for '{name}': {why}");
953 complaints.push(unsupported_at(&said, span));
954 }
955 }
956 }
957 if !complaints.is_empty() {
958 return Err(complaints);
959 }
960 let (globals, aliases) = match opts.emit {
966 EmitKind::Asm | EmitKind::Object | EmitKind::Archive | EmitKind::Executable => (
967 rucc_asm::globals(module, names, target.object_format).map_err(refused)?,
968 rucc_asm::aliases(module, names).map_err(refused)?,
969 ),
970 _ => (rucc_asm::Globals::default(), Vec::new()),
971 };
972 let unwind = opts.unwinds();
976 match opts.emit {
977 EmitKind::Asm => {
978 rucc_asm::print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
979 .map(Artifact::Text)
980 .map_err(refused)
981 }
982 EmitKind::Object | EmitKind::Archive | EmitKind::Executable => {
986 if opts.save_temps.wanted() {
987 let listing = rucc_asm::print(
988 &funcs,
989 &globals,
990 &aliases,
991 names,
992 target,
993 unwind,
994 output(opts, target),
995 );
996 *assembly = Some(listing.map_err(refused)?);
997 }
998 let aarch64 = target.tuple.arch() == Arch::Aarch64;
1009 if aarch64 || rucc_asm::kept(&funcs, names, target) {
1010 let print = if opts.debug_info { rucc_asm::print_marked } else { rucc_asm::print };
1011 let listing =
1012 print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
1013 .map_err(refused)?;
1014 let read = rucc_asm::read(&listing, target.tuple.arch()).map_err(|trouble| {
1015 let what = if aarch64 {
1016 "a unit for aarch64"
1017 } else {
1018 "an `asm` template kept as text"
1019 };
1020 vec![unsupported(&format!(
1021 "{what}, whose listing the assembler stopped at on line {}: {}",
1022 trouble.line, trouble.why
1023 ))]
1024 })?;
1025 let info = if opts.debug_info {
1026 let assembled =
1027 placed(&read, &funcs, names, target).map_err(|why| vec![internal(&why)])?;
1028 describe(&assembled, &globals.image(), &funcs, origin, opts, target)
1029 .map_err(|why| vec![internal(&why)])?
1030 } else {
1031 rucc_object::Info::default()
1032 };
1033 let defines = rucc_object::assembled_defines(&read);
1034 let bytes =
1035 rucc_object::assembled_described(&read, &TargetInfo::new(opts.target), &info)
1036 .map_err(wrote)?;
1037 return Ok(Artifact::Object { bytes, defines });
1038 }
1039 let assembled = rucc_asm::assemble(&funcs, names, target, unwind, opts.debug_info)
1040 .map_err(refused)?;
1041 let data = globals.image();
1042 let info = if opts.debug_info {
1046 describe(&assembled, &data, &funcs, origin, opts, target)
1047 .map_err(|why| vec![internal(&why)])?
1048 } else {
1049 rucc_object::Info::default()
1050 };
1051 let text = assembled.text;
1052 let bytes =
1055 rucc_object::write(&text, &data, &aliases, target, output(opts, target), &info)
1056 .map_err(wrote)?;
1057 let defines = rucc_object::defines(&text, &data, &aliases, target).map_err(wrote)?;
1062 Ok(Artifact::Object { bytes, defines })
1063 }
1064 _ => Ok(Artifact::Text(rucc_mir::print(&funcs, names, target.regs))),
1065 }
1066}
1067
1068fn placed(
1080 read: &rucc_object::Assembled,
1081 funcs: &[rucc_mir::Func],
1082 names: &Interner,
1083 target: &TargetInfo,
1084) -> Result<rucc_asm::Assembled, String> {
1085 let at: HashMap<&str, &rucc_object::Name> =
1086 read.names.iter().map(|name| (name.name.as_str(), name)).collect();
1087 let offset = |name: &str| match at.get(name).map(|name| name.at) {
1088 Some(rucc_object::Held::In { part, offset }) => Some((part, offset)),
1089 _ => None,
1090 };
1091 let mut text = rucc_object::Text::default();
1092 let mut lines = Vec::with_capacity(funcs.len());
1093 for (which, func) in funcs.iter().enumerate() {
1094 let name = names.resolve(func.name);
1095 let Some((part, start)) = offset(name) else {
1096 return Err(format!("the listing has no label for the function '{name}'"));
1097 };
1098 let mut rows = Vec::with_capacity(func.inst_count() + 1);
1099 if !func.declared.is_dummy() {
1100 rows.push(rucc_asm::Row { at: 0, span: func.declared, inst: None });
1101 }
1102 for block in func.blocks() {
1103 for inst in func.insts(block) {
1104 let label = rucc_asm::mark(target, which, inst);
1105 let Some((held, here)) = offset(&label) else {
1106 return Err(format!("the listing has no label '{label}'"));
1107 };
1108 if held != part || here < start {
1109 return Err(format!("the label '{label}' is not inside '{name}'"));
1110 }
1111 let at = usize::try_from(here - start).map_err(|why| why.to_string())?;
1112 rows.push(rucc_asm::Row { at, span: func.span(inst), inst: Some(inst) });
1113 }
1114 }
1115 let len = at.get(name).map_or(0, |name| name.size);
1116 text.funcs.push(rucc_object::Extent {
1117 name: name.to_owned(),
1118 start: usize::try_from(start).map_err(|why| why.to_string())?,
1119 len: usize::try_from(len).map_err(|why| why.to_string())?,
1120 align: func.align.unwrap_or(rucc_object::FUNC_ALIGN),
1121 binding: rucc_object::Binding::Global,
1122 visibility: rucc_object::Visibility::Default,
1123 patch: None,
1124 });
1125 lines.push(rows);
1126 }
1127 Ok(rucc_asm::Assembled { text, lines, frames: None })
1128}
1129
1130fn describe(
1149 assembled: &rucc_asm::Assembled,
1150 data: &rucc_object::Data,
1151 machine: &[rucc_mir::Func],
1152 origin: Origin<'_>,
1153 opts: &Options,
1154 target: &TargetInfo,
1155) -> Result<rucc_object::Info, String> {
1156 let rucc_asm::Assembled { text, lines, frames } = assembled;
1157 let rewrite = |path: &str| opts.prefix_map.debug.apply(path).into_owned();
1158 let mut files: Vec<String> = Vec::new();
1163 let mut funcs = Vec::with_capacity(text.funcs.len());
1164 for ((extent, rows), built) in text.funcs.iter().zip(lines).zip(machine) {
1165 let mut out: Vec<rucc_debug::Row> = Vec::with_capacity(rows.len());
1166 for row in rows {
1167 if row.span.is_dummy() {
1168 continue;
1169 }
1170 let Some(at) = origin.map.presumed(row.span.lo) else {
1171 continue;
1172 };
1173 let which = interned(&mut files, rewrite(at.name));
1174 let place = rucc_debug::Row {
1175 at: row.at as u64,
1176 file: which,
1177 line: at.line,
1178 column: at.column,
1179 };
1180 match out.last() {
1190 Some(last) if last.at == place.at => {}
1191 _ => out.push(place),
1192 }
1193 }
1194 if let Some(first) = out.first_mut() {
1201 first.at = 0;
1202 }
1203 let known = origin.meaning.funcs.get(&extent.name);
1209 let decl = known.map(|known| rucc_debug::Place {
1210 file: interned(&mut files, rewrite(&known.file)),
1211 line: known.line,
1212 });
1213 let mut sig = known.and_then(|known| known.sig.clone());
1222 let mut placed: Vec<(u32, i32)> = built.locals.clone();
1223 let mut spots = stretches(extent, rows, built, target);
1224 for &decl in &built.sharing {
1229 if !spots.iter().any(|(at, _)| *at == decl) {
1230 spots.push((decl, Vec::new()));
1231 }
1232 }
1233 if let (Some(sig), Some(known)) = (sig.as_mut(), known) {
1234 for (param, decl) in sig.params.iter_mut().zip(&known.params) {
1235 let Some(decl) = *decl else { continue };
1236 if let Some(which) = placed.iter().position(|&(at, _)| at == decl) {
1237 let at = rucc_debug::Held::Frame(i64::from(placed.remove(which).1));
1238 param.spot = Some(rucc_debug::Spot::Always(at));
1239 continue;
1240 }
1241 let Some(which) = spots.iter().position(|(at, _)| *at == decl) else { continue };
1245 param.spot = Some(rucc_debug::Spot::Over(spots.remove(which).1));
1246 }
1247 }
1248 let mut locals = Vec::with_capacity(placed.len() + spots.len());
1252 let mut wants: Vec<Option<usize>> = Vec::with_capacity(locals.capacity());
1256 for (decl, at) in placed {
1257 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1258 wants.push(named.scope);
1259 locals.push(rucc_debug::Local {
1260 name: named.name.clone(),
1261 ty: named.ty,
1262 decl: Some(rucc_debug::Place {
1263 file: interned(&mut files, rewrite(&named.file)),
1264 line: named.line,
1265 }),
1266 spot: rucc_debug::Spot::Always(rucc_debug::Held::Frame(i64::from(at))),
1267 scope: None,
1268 });
1269 }
1270 spots.sort_by_key(|(decl, _)| *decl);
1274 for (decl, spans) in spots {
1275 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1276 wants.push(named.scope);
1277 locals.push(rucc_debug::Local {
1278 name: named.name.clone(),
1279 ty: named.ty,
1280 decl: Some(rucc_debug::Place {
1281 file: interned(&mut files, rewrite(&named.file)),
1282 line: named.line,
1283 }),
1284 spot: rucc_debug::Spot::Over(spans),
1285 scope: None,
1286 });
1287 }
1288 let (scopes, at) = nests(&wants, &origin.meaning.scopes, extent, rows);
1293 for (local, want) in locals.iter_mut().zip(&wants) {
1294 local.scope = want.and_then(|want| at.get(&want).copied());
1295 }
1296 funcs.push(rucc_debug::Function {
1297 name: extent.name.clone(),
1298 len: extent.len as u64,
1299 rows: out,
1300 decl,
1301 sig,
1302 external: known.is_some_and(|known| known.external),
1303 locals,
1304 scopes,
1305 });
1306 }
1307 let mut globals = Vec::new();
1314 for object in &data.objects {
1315 let Some(held) = origin.meaning.objects.get(&object.name) else { continue };
1316 globals.push(rucc_debug::Global {
1317 name: object.name.clone(),
1318 ty: held.ty,
1319 decl: Some(rucc_debug::Place {
1320 file: interned(&mut files, rewrite(&held.file)),
1321 line: held.line,
1322 }),
1323 external: held.external,
1324 });
1325 }
1326 let unit = rucc_debug::Unit {
1327 name: rewrite(origin.name),
1328 dir: rewrite(opts.working_dir.as_deref().unwrap_or(".")),
1331 producer: format!("rucc {}", crate::VERSION),
1332 files,
1333 types: origin.meaning.types.clone(),
1334 funcs,
1335 globals,
1336 pointer: u8::try_from(target.pointer_width / 8).unwrap_or(8),
1337 frames: opts.unwinds() || frames.is_some(),
1342 };
1343 let mut info = rucc_debug::write(&unit).map_err(|why| why.to_string())?;
1344 info.chunks.extend(frames.clone());
1345 Ok(info)
1346}
1347
1348fn stretches(
1359 extent: &rucc_object::Extent,
1360 rows: &[rucc_asm::Row],
1361 built: &rucc_mir::Func,
1362 target: &TargetInfo,
1363) -> Vec<(u32, Vec<rucc_debug::Span>)> {
1364 let (false, Some(regs)) = (built.kept.is_empty(), target.call_regs) else {
1367 return Vec::new();
1368 };
1369 let ends = ends(extent, rows);
1370 let mut bounds = vec![None; built.inst_count()];
1371 for (which, row) in rows.iter().enumerate() {
1372 let Some(inst) = row.inst else { continue };
1373 bounds[inst.index()] = Some((row.at as u64, ends[which]));
1374 }
1375 let mut spots: Vec<(u32, Vec<rucc_debug::Span>)> = Vec::new();
1376 for kept in &built.kept {
1377 let (Some((from, _)), Some((_, to))) = (bounds[kept.from.index()], bounds[kept.to.index()])
1378 else {
1379 continue;
1380 };
1381 if to <= from {
1382 continue;
1383 }
1384 let held = match kept.at {
1385 rucc_mir::Where::Reg { reg, class } => match regs.dwarf(class, reg) {
1388 Some(number) => rucc_debug::Held::Reg(number),
1389 None => continue,
1390 },
1391 rucc_mir::Where::Frame(at) => rucc_debug::Held::Frame(i64::from(at)),
1392 };
1393 let span = rucc_debug::Span { from, len: to - from, held };
1394 match spots.iter_mut().find(|(decl, _)| *decl == kept.decl) {
1395 Some((_, spans)) => spans.push(span),
1396 None => spots.push((kept.decl, vec![span])),
1397 }
1398 }
1399 for (_, spans) in &mut spots {
1400 *spans = settle(std::mem::take(spans));
1401 }
1402 spots.retain(|(_, spans)| !spans.is_empty());
1403 spots
1404}
1405
1406fn ends(extent: &rucc_object::Extent, rows: &[rucc_asm::Row]) -> Vec<u64> {
1416 let mut out = vec![extent.len as u64; rows.len()];
1417 let mut next = extent.len as u64;
1418 for which in (0..rows.len()).rev() {
1419 let at = rows[which].at as u64;
1420 out[which] = match next > at {
1423 true => next,
1424 false => out.get(which + 1).copied().unwrap_or(extent.len as u64),
1425 };
1426 next = next.min(at);
1427 }
1428 out
1429}
1430
1431fn nests(
1447 wants: &[Option<usize>],
1448 scopes: &[crate::shapes::Scope],
1449 extent: &rucc_object::Extent,
1450 rows: &[rucc_asm::Row],
1451) -> (Vec<rucc_debug::Scope>, HashMap<usize, usize>) {
1452 let mut needed: Vec<usize> = Vec::new();
1453 for &want in wants {
1454 let mut up = want;
1455 while let Some(which) = up {
1456 if needed.contains(&which) {
1457 break;
1458 }
1459 needed.push(which);
1460 up = scopes.get(which).and_then(|scope| scope.parent);
1461 }
1462 }
1463 needed.sort_unstable();
1466 let at: HashMap<usize, usize> =
1467 needed.iter().enumerate().map(|(which, &scope)| (scope, which)).collect();
1468 let ends = ends(extent, rows);
1469 let out = needed
1470 .iter()
1471 .map(|&which| {
1472 let scope = &scopes[which];
1473 rucc_debug::Scope {
1474 parent: scope.parent.and_then(|parent| at.get(&parent).copied()),
1475 over: spread(scope.span, &ends, rows),
1476 }
1477 })
1478 .collect();
1479 (out, at)
1480}
1481
1482fn spread(span: Span, ends: &[u64], rows: &[rucc_asm::Row]) -> Vec<rucc_debug::Reach> {
1490 let mut out: Vec<rucc_debug::Reach> = Vec::new();
1491 for (which, row) in rows.iter().enumerate() {
1492 if row.span.is_dummy() || row.span.lo < span.lo || row.span.hi > span.hi {
1493 continue;
1494 }
1495 let (from, to) = (row.at as u64, ends[which]);
1496 if to <= from {
1497 continue;
1498 }
1499 match out.last_mut() {
1500 Some(last) if last.from + last.len >= from => {
1501 last.len = to.saturating_sub(last.from).max(last.len);
1502 }
1503 _ => out.push(rucc_debug::Reach { from, len: to - from }),
1504 }
1505 }
1506 out
1507}
1508
1509fn settle(mut spans: Vec<rucc_debug::Span>) -> Vec<rucc_debug::Span> {
1526 spans.sort_by_key(|span| (span.from, span.len));
1527 for which in 0..spans.len() {
1528 let (from, end, held) =
1529 (spans[which].from, spans[which].from + spans[which].len, spans[which].held);
1530 let later = spans[which + 1..]
1531 .iter()
1532 .take_while(|later| later.from < end)
1533 .find(|later| later.from > from && later.held != held);
1534 if let Some(later) = later {
1535 spans[which].len = later.from - from;
1536 }
1537 }
1538 let mut edges: Vec<u64> =
1541 spans.iter().flat_map(|span| [span.from, span.from + span.len]).collect();
1542 edges.sort_unstable();
1543 edges.dedup();
1544 let mut out: Vec<rucc_debug::Span> = Vec::new();
1545 let mut first = 0;
1546 for pair in edges.windows(2) {
1547 let (from, to) = (pair[0], pair[1]);
1548 while spans.get(first).is_some_and(|span| span.from + span.len <= from) {
1552 first += 1;
1553 }
1554 let mut held = None;
1555 let mut agreed = true;
1556 for span in &spans[first..] {
1557 if span.from >= to {
1558 break;
1559 }
1560 if span.from > from || span.from + span.len < to {
1561 continue;
1562 }
1563 match held {
1564 None => held = Some(span.held),
1565 Some(seen) => agreed &= seen == span.held,
1566 }
1567 }
1568 let (Some(held), true) = (held, agreed) else { continue };
1569 match out.last_mut() {
1570 Some(last) if last.from + last.len == from && last.held == held => {
1571 last.len += to - from
1572 }
1573 _ => out.push(rucc_debug::Span { from, len: to - from, held }),
1574 }
1575 }
1576 out
1577}
1578
1579fn interned(files: &mut Vec<String>, name: String) -> usize {
1585 match files.iter().position(|have| *have == name) {
1586 Some(which) => which,
1587 None => {
1588 files.push(name);
1589 files.len() - 1
1590 }
1591 }
1592}
1593
1594fn output(opts: &Options, target: &TargetInfo) -> rucc_object::Output {
1606 let mut features = 0;
1607 if target.tuple.arch() == Arch::X86_64 {
1608 if opts.control.branch() {
1609 features |= rucc_object::Property::IBT;
1610 }
1611 if opts.control.ret() {
1612 features |= rucc_object::Property::SHSTK;
1613 }
1614 }
1615 rucc_object::Output {
1616 sections: rucc_object::Sections {
1617 functions: opts.function_sections,
1618 data: opts.data_sections,
1619 },
1620 property: rucc_object::Property { features },
1621 }
1622}
1623
1624fn wrote(why: rucc_object::Error) -> Vec<Diagnostic> {
1630 match why {
1631 rucc_object::Error::Format { .. } => vec![unsupported(&why.to_string())],
1632 rucc_object::Error::Refused { .. } => vec![internal(&why.to_string())],
1633 }
1634}
1635
1636fn refused(why: rucc_asm::Error) -> Vec<Diagnostic> {
1643 match why {
1644 rucc_asm::Error::Thread { .. }
1645 | rucc_asm::Error::IFunc { .. }
1646 | rucc_asm::Error::Frame { .. } => {
1647 vec![unsupported(&why.to_string())]
1648 }
1649 _ => vec![internal(&why.to_string())],
1650 }
1651}
1652
1653fn unsupported(message: &str) -> Diagnostic {
1659 unsupported_at(message, Span::DUMMY)
1660}
1661
1662fn unsupported_at(message: &str, span: Span) -> Diagnostic {
1668 Diagnostic::error(message.to_owned(), span)
1669 .with_code("E0653")
1670 .note("this construct is not lowered yet, see https://github.com/tamnd/rucc/issues", span)
1671}
1672
1673fn invalid(message: &str) -> Diagnostic {
1675 Diagnostic::error(message.to_owned(), Span::DUMMY).with_code("E0661")
1676}
1677
1678fn internal(message: &str) -> Diagnostic {
1680 Diagnostic::error(format!("internal error: {message}"), Span::DUMMY)
1681 .with_code("E0652")
1682 .note("this is a bug in rucc rather than in the program, please report it", Span::DUMMY)
1683}
1684
1685fn failure(message: String) -> Compiled {
1688 Compiled {
1689 artifact: Artifact::Nothing,
1690 messages: vec![format!("rucc: error: {message}")],
1691 errors: 1,
1692 fired: Fired::new(),
1693 pressure: Pressure::new(),
1694 lowerings: Lowerings::new(),
1695 dumps: Vec::new(),
1696 remarks: String::new(),
1697 deps: Vec::new(),
1698 temps: Temps::default(),
1699 }
1700}
1701
1702#[cfg(test)]
1703mod tests {
1704 use rucc_session::{MemoryFileSystem, Std};
1705 use rucc_target::Triple;
1706
1707 use super::*;
1708
1709 fn options() -> Options {
1710 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1711 opts.emit = EmitKind::Tast;
1712 opts.frame_pointer = Some(false);
1715 opts
1716 }
1717
1718 fn run(opts: &Options, source: &str) -> Compiled {
1719 let mut fs = MemoryFileSystem::new();
1720 fs.insert("/main.c", source.to_owned().into_bytes());
1721 compile(opts, "/main.c", &fs)
1722 }
1723
1724 fn freestanding() -> Options {
1728 let mut opts = options();
1729 opts.hosted = false;
1730 opts.search.push_system(rucc_session::runtime::DIR);
1731 opts
1732 }
1733
1734 fn shipped(source: &str) -> String {
1736 let result = run(&freestanding(), source);
1737 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1738 result.text().to_owned()
1739 }
1740
1741 fn tast(source: &str) -> String {
1743 let result = run(&options(), source);
1744 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1745 result.text().to_owned()
1746 }
1747
1748 #[test]
1749 fn the_shipped_stdarg_declares_a_list_and_the_four_operators() {
1750 let text = shipped(concat!(
1751 "#include <stdarg.h>\n",
1752 "int sum(int n, ...) {\n",
1753 " va_list ap, copy;\n",
1754 " va_start(ap, n);\n",
1755 " va_copy(copy, ap);\n",
1756 " int total = va_arg(ap, int) + va_arg(copy, int);\n",
1757 " va_end(ap);\n",
1758 " va_end(copy);\n",
1759 " return total;\n",
1760 "}\n",
1761 ));
1762 assert!(text.contains("va-start"), "{text}");
1763 assert!(text.contains("va-copy"), "{text}");
1764 assert!(text.contains("va-arg"), "{text}");
1765 assert!(text.contains("va-end"), "{text}");
1766 }
1767
1768 #[test]
1772 fn stdarg_hands_out_the_type_alone_when_that_is_all_that_was_asked_for() {
1773 let text = shipped(concat!(
1774 "#define __need___va_list\n",
1775 "#include <stdarg.h>\n",
1776 "int vprint(const char *f, __gnuc_va_list ap);\n",
1777 "#ifdef va_start\n",
1778 "#error va_start should not be defined\n",
1779 "#endif\n",
1780 "#ifdef _VA_LIST_DEFINED\n",
1781 "#error va_list should not have been made\n",
1782 "#endif\n",
1783 ));
1784 assert!(text.contains("vprint"), "{text}");
1785 }
1786
1787 #[test]
1790 fn stddef_answers_one_piece_at_a_time_and_the_next_request_still_gets_through() {
1791 let text = shipped(concat!(
1792 "#define __need_size_t\n",
1793 "#include <stddef.h>\n",
1794 "#ifdef offsetof\n",
1795 "#error offsetof should not be defined yet\n",
1796 "#endif\n",
1797 "#define __need_ptrdiff_t\n",
1798 "#include <stddef.h>\n",
1799 "#include <stddef.h>\n",
1800 "size_t a;\n",
1801 "ptrdiff_t b;\n",
1802 "wchar_t c;\n",
1803 "max_align_t d;\n",
1804 "void *e = NULL;\n",
1805 "struct P { int x; long y; };\n",
1806 "size_t f = offsetof(struct P, y);\n",
1807 ));
1808 assert!(text.contains("decl #0 a : unsigned long"), "{text}");
1809 assert!(text.contains("decl #1 b : long"), "{text}");
1810 }
1811
1812 #[test]
1813 fn the_shipped_limits_and_float_are_the_targets_own_answers() {
1814 let text = shipped(concat!(
1815 "#include <limits.h>\n",
1816 "#include <float.h>\n",
1817 "int bits = CHAR_BIT;\n",
1818 "long big = LONG_MAX;\n",
1819 "int low = INT_MIN;\n",
1820 "int radix = FLT_RADIX;\n",
1821 "int digits = DBL_MANT_DIG;\n",
1822 ));
1823 assert!(text.contains("const 8 : int"), "{text}");
1824 assert!(text.contains("const 9223372036854775807 : long"), "{text}");
1825 assert!(text.contains("const 2 : int"), "{text}");
1826 assert!(text.contains("const 53 : int"), "{text}");
1827 }
1828
1829 #[test]
1833 fn the_shipped_stdint_writes_the_whole_set_when_there_is_no_library_to_defer_to() {
1834 let text = shipped(concat!(
1835 "#include <stdint.h>\n",
1836 "int64_t a = INT64_C(1);\n",
1837 "uint_least16_t b;\n",
1838 "intptr_t c;\n",
1839 "uintmax_t d = UINTMAX_MAX;\n",
1840 "int wide = sizeof(int_fast64_t);\n",
1841 ));
1842 assert!(text.contains("decl #0 a : long"), "{text}");
1843 assert!(text.contains("decl #1 b : unsigned short"), "{text}");
1844 assert!(text.contains("decl #2 c : long"), "{text}");
1845 }
1846
1847 #[test]
1858 fn the_shipped_mmintrin_defines_the_mmx_type_and_the_operations_over_it() {
1859 let text = shipped(concat!(
1860 "#include <mmintrin.h>\n",
1861 "__m64 add(__m64 a, __m64 b) { return _mm_add_pi16(a, b); }\n",
1862 "__m64 pack(__m64 a, __m64 b) { return _m_packsswb(a, b); }\n",
1863 "__m64 shift(__m64 a) { return _mm_srai_pi32(a, 3); }\n",
1864 "int low(__m64 a) { return _mm_cvtsi64_si32(a); }\n",
1865 "void done(void) { _mm_empty(); }\n",
1866 ));
1867 assert!(text.contains("add"), "{text}");
1868 assert!(text.contains("pack"), "{text}");
1869 assert!(text.contains("shift"), "{text}");
1870 }
1871
1872 #[test]
1877 fn the_shipped_mm_malloc_asks_for_aligned_memory_and_gives_it_back() {
1878 let text = shipped(concat!(
1879 "#include <mm_malloc.h>\n",
1880 "void *get(void) { return _mm_malloc(64, 16); }\n",
1881 "void put(void *p) { _mm_free(p); }\n",
1882 ));
1883 assert!(text.contains("get"), "{text}");
1884 assert!(text.contains("put"), "{text}");
1885 }
1886
1887 #[test]
1899 fn the_shipped_xmmintrin_defines_the_sse_type_and_the_operations_over_it() {
1900 let text = shipped(concat!(
1901 "#include <xmmintrin.h>\n",
1902 "__m128 add(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1903 "__m128 one(__m128 a, __m128 b) { return _mm_max_ss(a, b); }\n",
1904 "__m128 mask(__m128 a, __m128 b) { return _mm_cmpnle_ps(a, b); }\n",
1905 "__m128 pick(__m128 a, __m128 b) { return _mm_shuffle_ps(a, b, _MM_SHUFFLE(0,1,2,3)); }\n",
1906 "int bits(__m128 a) { return _mm_movemask_ps(a); }\n",
1907 "int near(__m128 a) { return _mm_cvtss_si32(a); }\n",
1908 "__m128 wide(__m64 a) { return _mm_cvtpi16_ps(a); }\n",
1909 "void *room(void) { return _mm_malloc(64, 16); }\n",
1910 "void hint(const float *p) { _mm_prefetch(p, _MM_HINT_T0); _mm_sfence(); }\n",
1911 ));
1912 assert!(text.contains("add"), "{text}");
1913 assert!(text.contains("mask"), "{text}");
1914 assert!(text.contains("pick"), "{text}");
1915 assert!(text.contains("wide"), "{text}");
1916 }
1917
1918 #[test]
1925 fn the_shipped_xmmintrin_leaves_out_the_names_that_need_an_instruction() {
1926 let text = rucc_session::runtime::header("xmmintrin.h").expect("xmmintrin.h is shipped");
1927 for absent in [
1928 "_mm_sqrt_ps",
1929 "_mm_sqrt_ss",
1930 "_mm_rsqrt_ps",
1931 "_mm_rsqrt_ss",
1932 "_mm_getcsr",
1933 "_mm_setcsr",
1934 ] {
1935 let defined = text.contains(&format!("{absent}("));
1936 assert!(!defined, "{absent} is defined and the header says it is not");
1937 assert!(text.contains(absent), "{absent} is absent and unexplained");
1938 }
1939 }
1940
1941 #[test]
1942 fn the_shipped_emmintrin_defines_both_sse2_types_and_the_operations_over_them() {
1943 let text = shipped(concat!(
1944 "#include <emmintrin.h>\n",
1945 "__m128i add(__m128i a, __m128i b) { return _mm_add_epi64(a, b); }\n",
1946 "__m128i wide(__m128i a, __m128i b) { return _mm_mul_epu32(a, b); }\n",
1947 "__m128i pick(__m128i a) { return _mm_shuffle_epi32(a, _MM_SHUFFLE(0,1,2,3)); }\n",
1948 "__m128i up(__m128i a) { return _mm_slli_epi64(a, 13); }\n",
1949 "__m128i down(__m128i a) { return _mm_srli_si128(a, 3); }\n",
1950 "__m128i pack(__m128i a, __m128i b) { return _mm_packus_epi16(a, b); }\n",
1951 "int bits(__m128i a) { return _mm_movemask_epi8(a); }\n",
1952 "__m128d sum(__m128d a, __m128d b) { return _mm_add_sd(a, b); }\n",
1953 "__m128d mask(__m128d a, __m128d b) { return _mm_cmpunord_pd(a, b); }\n",
1954 "__m128i near(__m128d a) { return _mm_cvtpd_epi32(a); }\n",
1955 "__m128d over(__m128 a) { return _mm_cvtps_pd(a); }\n",
1956 "__m128i half(__m64 a) { return _mm_movpi64_epi64(a); }\n",
1957 "__m128i grab(void const *p) { return _mm_loadu_si128(p); }\n",
1958 "void wall(void) { _mm_lfence(); _mm_mfence(); }\n",
1959 ));
1960 assert!(text.contains("wide"), "{text}");
1961 assert!(text.contains("pack"), "{text}");
1962 assert!(text.contains("near"), "{text}");
1963 assert!(text.contains("half"), "{text}");
1964 }
1965
1966 #[test]
1970 fn the_shipped_immintrin_reaches_the_names_the_headers_under_it_define() {
1971 let text = shipped(concat!(
1972 "#include <immintrin.h>\n",
1973 "unsigned long long matching(unsigned char tag, unsigned char const *bucket) {\n",
1974 " __m128i const want = _mm_set1_epi8((char)tag);\n",
1975 " __m128i const chunk = _mm_loadu_si128((__m128i const *)(void const *)bucket);\n",
1976 " __m128i const same = _mm_cmpeq_epi8(chunk, want);\n",
1977 " return (unsigned long long)_mm_movemask_epi8(same);\n",
1978 "}\n",
1979 "__m64 narrow(__m64 a, __m64 b) { return _mm_add_pi32(a, b); }\n",
1980 "__m128 single(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1981 ));
1982 assert!(text.contains("matching"), "{text}");
1983 assert!(text.contains("narrow"), "the MMX header is not reached: {text}");
1984 assert!(text.contains("single"), "the SSE header is not reached: {text}");
1985 }
1986
1987 #[test]
1991 fn the_shipped_x86intrin_reaches_the_fences_windows_headers_ask_it_for() {
1992 let text = shipped(concat!(
1993 "#include <x86intrin.h>\n",
1994 "void barriers(void *p) {\n",
1995 " _mm_lfence();\n",
1996 " _mm_sfence();\n",
1997 " _mm_mfence();\n",
1998 " _mm_pause();\n",
1999 " _mm_clflush(p);\n",
2000 "}\n",
2001 "__m128i wide(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
2002 ));
2003 assert!(text.contains("barriers"), "{text}");
2004 assert!(text.contains("wide"), "the SSE2 header is not reached: {text}");
2005 }
2006
2007 #[test]
2011 fn the_umbrella_and_the_header_under_it_can_both_be_included() {
2012 let text = shipped(concat!(
2013 "#include <immintrin.h>\n",
2014 "#include <emmintrin.h>\n",
2015 "#include <immintrin.h>\n",
2016 "#include <x86intrin.h>\n",
2017 "__m128i twice(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
2018 ));
2019 assert!(text.contains("twice"), "{text}");
2020 }
2021
2022 #[test]
2025 fn the_shipped_arm_neon_has_what_xxhash_asks_it_for() {
2026 let mut opts = freestanding();
2027 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2028 let source = concat!(
2029 "#include <arm_neon.h>\n",
2030 "uint64x2_t acc(uint64x2_t sum, const void *in, const void *key) {\n",
2031 " uint8x16_t data = vld1q_u8((const uint8_t *)in);\n",
2032 " uint8x16_t k = vld1q_u8((const uint8_t *)key);\n",
2033 " uint64x2_t mixed = vreinterpretq_u64_u8(veorq_u8(data, k));\n",
2034 " uint32x2_t lo = vmovn_u64(mixed);\n",
2035 " uint32x2_t hi = vshrn_n_u64(mixed, 32);\n",
2036 " return vmlal_u32(sum, lo, hi);\n",
2037 "}\n",
2038 "uint32x4x2_t pair(uint32x4_t a, uint32x4_t b) { return vzipq_u32(a, b); }\n",
2039 "uint32_t total(uint32x4_t a) { return vaddvq_u32(a); }\n",
2040 );
2041 let result = run(&opts, source);
2042 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2043 assert!(result.text().contains("pair"), "{}", result.text());
2044 assert!(result.text().contains("total"), "{}", result.text());
2045 }
2046
2047 #[test]
2049 fn the_shipped_arm_neon_refuses_another_target() {
2050 let result = run(&freestanding(), "#include <arm_neon.h>\n");
2051 let said = result.messages.join("\n");
2052 assert!(said.contains("arm_neon.h is for AArch64"), "{said}");
2053 }
2054
2055 #[test]
2059 fn the_shipped_emmintrin_leaves_out_the_two_square_roots() {
2060 let text = rucc_session::runtime::header("emmintrin.h").expect("emmintrin.h is shipped");
2061 for absent in ["_mm_sqrt_pd", "_mm_sqrt_sd"] {
2062 let defined = text.contains(&format!("{absent}("));
2063 assert!(!defined, "{absent} is defined and the header says it is not");
2064 assert!(text.contains(absent), "{absent} is absent and unexplained");
2065 }
2066 }
2067
2068 #[test]
2069 fn the_three_formality_headers_still_have_to_work() {
2070 let text = shipped(concat!(
2071 "#include <stdbool.h>\n",
2072 "#include <stdalign.h>\n",
2073 "#include <iso646.h>\n",
2074 "#include <stdnoreturn.h>\n",
2075 "int t = true and not false;\n",
2076 "_Alignas(16) char buf[16];\n",
2077 "int a = alignof(long);\n",
2078 ));
2079 assert!(text.contains("decl #0 t : int"), "{text}");
2080 assert!(text.contains("const 8 : unsigned long"), "{text}");
2081 }
2082
2083 #[test]
2091 fn every_shipped_header_can_be_included_twice() {
2092 let once: String = rucc_session::runtime::names()
2095 .iter()
2096 .filter(|name| **name != "arm_neon.h")
2097 .map(|name| format!("#include <{name}>\n"))
2098 .collect();
2099 let twice = once.repeat(2);
2100 assert_eq!(shipped(&format!("{once}int x;\n")), shipped(&format!("{twice}int x;\n")));
2101
2102 let mut opts = freestanding();
2103 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2104 let tree = |source: &str| {
2105 let result = run(&opts, source);
2106 assert_eq!(
2107 result.messages,
2108 Vec::<String>::new(),
2109 "expected this to compile:\n{source}"
2110 );
2111 result.text().to_owned()
2112 };
2113 let neon = "#include <arm_neon.h>\n";
2114 assert_eq!(tree(&format!("{neon}int x;\n")), tree(&format!("{neon}{neon}int x;\n")));
2115 }
2116
2117 #[test]
2118 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
2119 let fs = MemoryFileSystem::new();
2120 let result = compile(&options(), "/nope.c", &fs);
2121 assert!(result.failed());
2122 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
2123 assert!(result.text().is_empty());
2124 }
2125
2126 #[test]
2127 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
2128 let text = tast("int x = 1;\n");
2129 let expected = "\
2130decl #0 x : int object external static defined
2131 init
2132 +0
2133 const 1 : int
2134";
2135 assert_eq!(text, expected);
2136 }
2137
2138 #[test]
2139 fn the_macros_are_expanded_before_anything_is_parsed() {
2140 let text = tast("#define N 2\nint a[N];\n");
2144 assert!(text.starts_with("decl #0 a : int[2] object external static tentative"), "{text}");
2145 }
2146
2147 #[test]
2153 fn a_pragma_is_not_a_declaration_and_the_parse_walks_past_the_ones_it_does_not_read() {
2154 let text = tast(concat!(
2155 "#pragma pack(4)\n",
2156 "struct s { int a; };\n",
2157 "#pragma pack()\n",
2158 "int b;\n",
2159 "_Pragma(\"GCC visibility push(default)\") int c;\n",
2160 ));
2161 assert!(text.contains("decl #0 b : int"), "{text}");
2162 assert!(text.contains("decl #1 c : int"), "{text}");
2163 }
2164
2165 #[test]
2173 fn the_layout_attributes_move_the_members_and_the_record_the_way_gcc_lays_them_out() {
2174 tast(concat!(
2175 "struct A { char c; int i; } __attribute__((packed));\n",
2176 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2177 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2178 "struct B { char c; int i; } __attribute__((aligned));\n",
2181 "_Static_assert(sizeof(struct B) == 16 && _Alignof(struct B) == 16, \"B\");\n",
2182 "struct C { char c; int i __attribute__((packed)); };\n",
2183 "_Static_assert(sizeof(struct C) == 5 && _Alignof(struct C) == 1, \"C\");\n",
2184 "_Static_assert(__builtin_offsetof(struct C, i) == 1, \"C.i\");\n",
2185 "struct D { char c; int i; } __attribute__((packed, aligned(4)));\n",
2186 "_Static_assert(sizeof(struct D) == 8 && _Alignof(struct D) == 4, \"D\");\n",
2187 "_Static_assert(__builtin_offsetof(struct D, i) == 1, \"D.i\");\n",
2188 "struct E { char c; _Alignas(8) int i; };\n",
2189 "_Static_assert(sizeof(struct E) == 16 && _Alignof(struct E) == 8, \"E\");\n",
2190 "_Static_assert(__builtin_offsetof(struct E, i) == 8, \"E.i\");\n",
2191 "struct F { char c; int i __attribute__((aligned(8))); };\n",
2192 "_Static_assert(sizeof(struct F) == 16 && _Alignof(struct F) == 8, \"F\");\n",
2193 "struct G { char c; short s; } __attribute__((aligned(2)));\n",
2196 "_Static_assert(sizeof(struct G) == 4 && _Alignof(struct G) == 2, \"G\");\n",
2197 "struct H { char c; int i; } __attribute__((aligned(2)));\n",
2198 "_Static_assert(sizeof(struct H) == 8 && _Alignof(struct H) == 4, \"H\");\n",
2199 "struct I { [[gnu::packed]] char c; int i; };\n",
2202 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2203 "struct J { char c; [[gnu::packed]] int i; };\n",
2204 "_Static_assert(sizeof(struct J) == 5 && _Alignof(struct J) == 1, \"J\");\n",
2205 "struct M { char c; int i : 5; int j : 20; } __attribute__((packed));\n",
2206 "_Static_assert(sizeof(struct M) == 5 && _Alignof(struct M) == 1, \"M\");\n",
2207 "struct N { char c; long long l; } __attribute__((aligned(32)));\n",
2208 "_Static_assert(sizeof(struct N) == 32 && _Alignof(struct N) == 32, \"N\");\n",
2209 "union L { char c; int i; } __attribute__((packed));\n",
2210 "_Static_assert(sizeof(union L) == 4 && _Alignof(union L) == 1, \"L\");\n",
2211 "struct O { char c; int i; } __attribute__((__packed__));\n",
2215 "_Static_assert(sizeof(struct O) == 5 && _Alignof(struct O) == 1, \"O\");\n",
2216 "struct P { char c; int i; } __attribute__((__aligned__(8)));\n",
2217 "_Static_assert(sizeof(struct P) == 8 && _Alignof(struct P) == 8, \"P\");\n",
2218 ));
2219 }
2220
2221 #[test]
2234 fn a_transparent_union_takes_the_member_a_value_fits_and_is_declared_either_way() {
2235 let text = tast(concat!(
2236 "struct one { int x; };\n",
2237 "struct two { long y; };\n",
2238 "typedef union { struct one *a; struct two *b; void *any; }\n",
2239 " __attribute__((__transparent_union__)) arg;\n",
2240 "int takes(arg v);\n",
2241 "int f(struct one *p, struct two *q, char *c) {\n",
2242 " return takes(p) + takes(q) + takes(c) + takes(0);\n",
2243 "}\n",
2244 "int takes(struct one *p);\n",
2246 "int (*as_a_member)(struct one *) = takes;\n",
2247 "int (*as_the_union)(arg) = takes;\n",
2248 ));
2249 assert!(text.contains("compound-literal"), "{text}");
2250 }
2251
2252 #[test]
2258 fn the_attribute_on_the_declarator_of_a_typedef_is_the_one_glibc_writes() {
2259 let text = tast(concat!(
2260 "struct sockaddr { int family; };\n",
2261 "struct sockaddr_in { int family; int addr; };\n",
2262 "typedef union { struct sockaddr *plain; struct sockaddr_in *inet; }\n",
2263 " addr_arg __attribute__((__transparent_union__));\n",
2264 "int bind_to(int fd, addr_arg where);\n",
2265 "int f(struct sockaddr_in *where) { return bind_to(0, where); }\n",
2266 ));
2267 assert!(text.contains("compound-literal"), "{text}");
2268 }
2269
2270 #[test]
2278 fn a_transparent_union_that_cannot_keep_the_promise_is_dropped_with_a_word_about_it() {
2279 let result = run(
2280 &options(),
2281 concat!(
2282 "union wider { int small; double large; } __attribute__((transparent_union));\n",
2283 "struct plain { int x; } __attribute__((transparent_union));\n",
2284 ),
2285 );
2286 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
2287 assert!(!result.failed(), "{:?}", result.messages);
2288 for message in &result.messages {
2289 assert!(message.contains("'transparent_union' attribute ignored"), "{message}");
2290 }
2291 assert!(result.messages[0].contains("first member"), "{:?}", result.messages);
2292 assert!(result.messages[1].contains("only a union"), "{:?}", result.messages);
2293 }
2294
2295 #[test]
2304 fn an_access_to_a_packed_member_says_the_alignment_the_layout_left_it() {
2305 let packed = body(concat!(
2306 "struct P { char c; int v; } __attribute__((packed));\n",
2307 "int f(struct P *p) { return p->v; }\n",
2308 ));
2309 assert!(packed.contains("load.i32 %2, align 1,"), "{packed}");
2310 let plain = body(concat!(
2312 "struct P { char c; int v; };\n",
2313 "int f(struct P *p) { return p->v; }\n",
2314 ));
2315 assert!(plain.contains("load.i32 %2, align 4,"), "{plain}");
2316 }
2317
2318 #[test]
2325 fn what_is_inside_a_packed_member_is_no_more_aligned_than_the_member_is() {
2326 let stepped = body(concat!(
2327 "struct P { char c; int v[4]; } __attribute__((packed));\n",
2328 "int f(struct P *p, int i) { return p->v[i]; }\n",
2329 ));
2330 assert!(stepped.contains(", align 1,"), "{stepped}");
2331 assert!(!stepped.contains(", align 4,"), "{stepped}");
2332 let nested = body(concat!(
2333 "struct Inner { int v; };\n",
2334 "struct P { char c; struct Inner in; } __attribute__((packed));\n",
2335 "int f(struct P *p) { return p->in.v; }\n",
2336 ));
2337 assert!(nested.contains(", align 1,"), "{nested}");
2338 assert!(!nested.contains(", align 4,"), "{nested}");
2339 }
2340
2341 #[test]
2357 fn an_access_through_a_typedef_that_lowered_its_alignment_says_the_one_the_typedef_asked_for() {
2358 let through = body(concat!(
2359 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2360 "unsigned int f(const void *p) { return *(const unalign32 *)p; }\n",
2361 ));
2362 assert!(through.contains("load.i32 %0, align 1,"), "{through}");
2363 let stepped = body(concat!(
2366 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2367 "unsigned int f(unalign32 *p, int i) { return p[i]; }\n",
2368 ));
2369 assert!(stepped.contains(", align 1,"), "{stepped}");
2370 assert!(!stepped.contains(", align 4,"), "{stepped}");
2371 let plain = body(concat!(
2374 "typedef unsigned int word;\n",
2375 "unsigned int f(const void *p) { return *(const word *)p; }\n",
2376 ));
2377 assert!(plain.contains("load.i32 %0, align 4,"), "{plain}");
2378 }
2379
2380 #[test]
2391 fn a_vector_read_through_a_typedef_that_lowered_its_alignment_comes_back_a_piece_at_a_time() {
2392 let prefix = concat!(
2393 "typedef long long v2di __attribute__((__vector_size__(16)));\n",
2394 "typedef long long v2di_u __attribute__((__vector_size__(16), __aligned__(1)));\n",
2395 );
2396 let loaded =
2397 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di_u *)p; }}"));
2398 assert_eq!(loaded.matches("align 1\n").count(), 2, "{loaded}");
2399 assert!(!loaded.contains("align 16"), "{loaded}");
2400 let stored = body(&format!("{prefix}void f(void *p, v2di b) {{ *(v2di_u *)p = b; }}"));
2403 assert!(stored.contains("memcpy %0, %3, size 16, align 1"), "{stored}");
2404 let aligned =
2406 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di *)p; }}"));
2407 assert!(aligned.contains("align 16"), "{aligned}");
2408 }
2409
2410 #[test]
2419 fn the_aligned_attribute_on_a_declaration_raises_what_that_one_object_is_aligned_to() {
2420 tast(concat!(
2421 "int v __attribute__((aligned(64)));\n",
2422 "_Static_assert(__alignof__(v) == 64, \"v\");\n",
2423 "__attribute__((aligned(32))) int w;\n",
2426 "_Static_assert(__alignof__(w) == 32, \"w\");\n",
2427 "[[gnu::aligned(16)]] int x;\n",
2428 "_Static_assert(__alignof__(x) == 16, \"x\");\n",
2429 "int y __attribute__((aligned(2)));\n",
2432 "_Static_assert(__alignof__(y) == 4, \"y\");\n",
2433 "void f(void) { int a __attribute__((aligned(128)));\n",
2435 "_Static_assert(__alignof__(a) == 128, \"a\"); (void)a; }\n",
2436 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2439 "void g(void) __attribute__((aligned(256)));\n",
2442 "void g(void) {}\n",
2443 "_Static_assert(__alignof__(g) == 256, \"g\");\n",
2444 ));
2445 }
2446
2447 #[test]
2451 fn what_a_declaration_asked_to_be_aligned_to_is_what_the_assembler_is_told() {
2452 let text = asm(concat!(
2453 "int v __attribute__((aligned(64)));\n",
2454 "void g(void) __attribute__((aligned(256)));\n",
2455 "void g(void) {}\n",
2456 "void plain(void) {}\n",
2457 ));
2458 assert!(text.contains("\t.p2align\t6\n\t.type\tv, @object\n"), "{text}");
2459 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2460 assert!(text.contains("\t.p2align\t4, 0x90\n\t.globl\tplain\n"), "{text}");
2461 }
2462
2463 #[test]
2469 fn the_alignment_the_command_line_asked_of_every_function_is_a_floor_under_all_of_them() {
2470 let source = concat!(
2471 "void g(void) __attribute__((aligned(256)));\n",
2472 "void g(void) {}\n",
2473 "void small(void) __attribute__((aligned(4)));\n",
2474 "void small(void) {}\n",
2475 "void plain(void) {}\n",
2476 );
2477 let listing = |align: Option<u32>| {
2478 let mut opts = options();
2479 opts.emit = EmitKind::Asm;
2480 opts.align_functions = align;
2481 let result = run(&opts, source);
2482 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
2483 result.text().to_owned()
2484 };
2485
2486 let text = listing(Some(32));
2487 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "the larger one wins: {text}");
2488 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tsmall\n"), "{text}");
2489 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tplain\n"), "{text}");
2490
2491 let text = listing(Some(8));
2494 assert!(text.contains("\t.p2align\t3, 0x90\n\t.globl\tplain\n"), "{text}");
2495 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2496 }
2497
2498 #[test]
2507 fn an_aligned_typedef_says_what_an_object_of_it_is_aligned_to_and_may_lower_it() {
2508 tast(concat!(
2509 "typedef int L __attribute__((aligned(2)));\n",
2510 "_Static_assert(__alignof__(L) == 2, \"L\");\n",
2511 "_Static_assert(_Alignof(L) == 2, \"L alignof\");\n",
2512 "_Static_assert(sizeof(L) == 4, \"L size\");\n",
2514 "struct T { char c; L x; };\n",
2515 "_Static_assert(sizeof(struct T) == 6, \"T\");\n",
2516 "_Static_assert(__builtin_offsetof(struct T, x) == 2, \"T.x\");\n",
2517 "typedef int H __attribute__((aligned(16)));\n",
2519 "_Static_assert(__alignof__(H) == 16, \"H\");\n",
2520 "_Static_assert(sizeof(H) == 4, \"H size\");\n",
2521 "struct U { char c; H x; };\n",
2522 "_Static_assert(sizeof(struct U) == 32, \"U\");\n",
2523 "_Static_assert(__builtin_offsetof(struct U, x) == 16, \"U.x\");\n",
2524 "typedef L M __attribute__((aligned(8)));\n",
2527 "_Static_assert(__alignof__(M) == 8, \"M\");\n",
2528 "typedef L N;\n",
2531 "_Static_assert(__alignof__(N) == 2, \"N\");\n",
2532 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2534 ));
2535 let text = asm(concat!(
2536 "typedef int L __attribute__((aligned(2)));\n",
2537 "typedef int H __attribute__((aligned(16)));\n",
2538 "L low;\n",
2539 "H high;\n",
2540 ));
2541 assert!(text.contains("\t.p2align\t1\n\t.type\tlow, @object\n"), "{text}");
2542 assert!(text.contains("\t.p2align\t4\n\t.type\thigh, @object\n"), "{text}");
2543 }
2544
2545 #[test]
2553 fn the_vector_size_attribute_builds_a_type_of_lanes_and_measures_it_in_bytes() {
2554 tast(concat!(
2555 "typedef int __attribute__((vector_size(16))) v4si;\n",
2556 "_Static_assert(sizeof(v4si) == 16 && _Alignof(v4si) == 16, \"v4si\");\n",
2557 "typedef char __attribute__((vector_size(16))) v16qi;\n",
2558 "_Static_assert(sizeof(v16qi) == 16, \"v16qi\");\n",
2559 "typedef int __attribute__((vector_size(4))) v1si;\n",
2562 "_Static_assert(sizeof(v1si) == 4, \"v1si\");\n",
2563 "typedef float __attribute__((__vector_size__(8))) v2sf;\n",
2565 "_Static_assert(sizeof(v2sf) == 8, \"v2sf\");\n",
2566 "typedef short [[gnu::vector_size(8)]] v4hi;\n",
2567 "_Static_assert(sizeof(v4hi) == 8, \"v4hi\");\n",
2568 "v4si g;\n",
2571 "_Static_assert(sizeof(g[0]) == 4, \"lane\");\n",
2572 "_Static_assert(sizeof(g + g) == 16, \"whole\");\n",
2573 "_Static_assert(sizeof(g + 1) == 16, \"broadcast\");\n",
2576 "_Static_assert(sizeof(v4si[3]) == 48, \"array\");\n",
2578 ));
2579 }
2580
2581 #[test]
2591 fn a_vector_is_written_whole_into_an_array_of_them_and_named_by_a_type_name() {
2592 tast(concat!(
2593 "typedef int __attribute__((vector_size(8))) v2si;\n",
2594 "v2si table[] = { (v2si){ 1, 2 }, (v2si){ 3, 4 } };\n",
2595 "_Static_assert(sizeof(table) == 16, \"two of them and not eight lanes\");\n",
2596 "v2si written = (int __attribute__((vector_size(8)))){ 5, 6 };\n",
2598 "_Static_assert(sizeof((int __attribute__((vector_size(16)))){ 0 }) == 16, \"named\");\n",
2599 "v2si lanes[2] = { 1, 2, 3, 4 };\n",
2602 "_Static_assert(sizeof(lanes) == 16, \"still elided\");\n",
2603 ));
2604 }
2605
2606 #[test]
2614 fn a_lane_is_assignable_and_a_shift_takes_a_count_of_its_own_lane() {
2615 let result = run(
2616 &options(),
2617 concat!(
2618 "typedef int __attribute__((vector_size(16))) v4si;\n",
2619 "typedef unsigned __attribute__((vector_size(16))) v4ui;\n",
2620 "void write(v4si *out, v4ui a, v4si b, int n) {\n",
2621 " v4si v = { 1, 2, 3, 4 };\n",
2622 " v[0] = n;\n",
2623 " v[1] += n;\n",
2624 " v[2]++;\n",
2625 " *&v[3] = n;\n",
2626 " v4ui shifted = a >> b;\n",
2628 " shifted <<= b;\n",
2629 " *out = v + (v4si)shifted + (1 << b);\n",
2632 "}\n",
2633 "void refused(const v4si c) {\n",
2636 " c[0] = 1;\n",
2637 "}\n",
2638 ),
2639 );
2640 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
2641 assert!(result.messages[0].contains("assignment of read-only"), "{:?}", result.messages);
2642 }
2643
2644 #[test]
2656 fn a_record_that_asks_for_the_other_byte_order_swaps_every_scalar_it_holds() {
2657 let read = "int f(struct s *p) { return p->i; }\n";
2658 let big = "struct s { int i; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
2659 assert!(body(&format!("{big}{read}")).contains("bswap"), "{big}");
2660
2661 let armoured =
2662 "struct s { int i; } __attribute__((__scalar_storage_order__(\"big-endian\")));\n";
2663 assert!(body(&format!("{armoured}{read}")).contains("bswap"), "{armoured}");
2664
2665 let front = "struct __attribute__((scalar_storage_order(\"big-endian\"))) s { int i; };\n";
2666 assert!(body(&format!("{front}{read}")).contains("bswap"), "{front}");
2667
2668 let standard = "struct s { int i; } [[gnu::scalar_storage_order(\"big-endian\")]];\n";
2669 assert!(body(&format!("{standard}{read}")).contains("bswap"), "{standard}");
2670
2671 let same =
2672 "struct s { int i; } __attribute__((scalar_storage_order(\"little-endian\")));\n";
2673 assert!(!body(&format!("{same}{read}")).contains("bswap"), "{same}");
2674
2675 let byte = "struct s { char c; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
2677 let source = format!("{byte}int f(struct s *p) {{ return p->c; }}\n");
2678 assert!(!body(&source).contains("bswap"), "{byte}");
2679
2680 tast(concat!(
2681 "struct s { int i; short h; char c; }",
2682 " __attribute__((scalar_storage_order(\"big-endian\")));\n",
2683 "_Static_assert(sizeof(struct s) == 8 && _Alignof(struct s) == 4, \"s\");\n",
2684 "_Static_assert(__builtin_offsetof(struct s, h) == 4, \"s.h\");\n",
2685 "_Static_assert(__builtin_offsetof(struct s, c) == 6, \"s.c\");\n",
2686 ));
2687 }
2688
2689 #[test]
2695 fn a_bit_field_in_one_of_those_records_is_counted_from_the_top_of_its_bytes() {
2696 let members = "short i : 12; char c1 : 1; char c2 : 1; char c3 : 1; char c4 : 1;";
2697 let read = "int f(struct s *p) { return p->i; }\n";
2698 let plain = format!("struct s {{ {members} }};\n{read}");
2699 let reversed = format!(
2700 "struct s {{ {members} }} __attribute__((scalar_storage_order(\"big-endian\")));\n\
2701 {read}"
2702 );
2703 assert!(body(&plain).contains("shl"), "{}", body(&plain));
2704 assert!(!body(&plain).contains("bswap"), "{}", body(&plain));
2705 let built = body(&reversed);
2708 assert!(built.contains("bswap"), "{built}");
2709 assert!(!built.contains("shl"), "{built}");
2710 assert!(built.contains("ashr"), "{built}");
2711 }
2712
2713 #[test]
2720 fn the_address_of_a_scalar_stored_the_other_way_round_is_refused() {
2721 let opts = options();
2722 let record = "struct s { int i; int a[2]; struct in { int n; } w; }\n\
2723 __attribute__((scalar_storage_order(\"big-endian\")));\n";
2724 let taken = format!("{record}int *f(struct s *p) {{ return &p->i; }}\n");
2725 assert_eq!(
2726 run(&opts, &taken).messages,
2727 ["/main.c:3:30: error: cannot take address of scalar with reverse storage order \
2728 [E0712]"]
2729 );
2730 let element = format!("{record}int *f(struct s *p) {{ return &p->a[0]; }}\n");
2731 let messages = run(&opts, &element).messages;
2732 assert!(messages[0].contains("[E0712]"), "{messages:?}");
2733
2734 let whole = format!("{record}int *f(struct s *p) {{ return (int *) &p->w; }}\n");
2735 assert_eq!(run(&opts, &whole).messages, Vec::<String>::new(), "{whole}");
2736 }
2737
2738 #[test]
2742 fn a_storage_order_that_names_neither_end_is_refused_with_the_two_words_that_are_taken() {
2743 let opts = options();
2744 let wrong = "struct s { int i; } __attribute__((scalar_storage_order(\"middle\")));\n";
2745 assert_eq!(
2746 run(&opts, wrong).messages,
2747 ["/main.c:1:36: error: 'scalar_storage_order' argument must be one of \"big-endian\" \
2748 or \"little-endian\" [E0688]"]
2749 );
2750 let bare = "struct s { int i; } __attribute__((scalar_storage_order));\n";
2751 let messages = run(&opts, bare).messages;
2752 assert!(messages[0].contains("[E0688]"), "{messages:?}");
2753 }
2754
2755 #[test]
2765 fn packing_is_what_decides_whether_a_bit_field_may_straddle_its_own_storage() {
2766 assert_eq!(bit_field_byte("struct s { int x : 12; char y : 6; };"), 2);
2768 assert_eq!(
2769 bit_field_byte("struct s { int x : 12; char y : 6; } __attribute__((packed));"),
2770 1
2771 );
2772 assert_eq!(
2773 bit_field_byte("struct s { int x : 12; __attribute__((packed)) char y : 6; };"),
2774 1
2775 );
2776 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { int x : 12; char y : 6; };"), 1);
2777 assert_eq!(bit_field_byte("struct s { char x; int y : 30; };"), 4);
2779 assert_eq!(bit_field_byte("struct s { char x; int y : 30; } __attribute__((packed));"), 1);
2780 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { char x; int y : 30; };"), 1);
2782 assert_eq!(bit_field_byte("#pragma pack(2)\nstruct s { char x; int y : 30; };"), 1);
2783 }
2784
2785 fn bit_field_byte(record: &str) -> u64 {
2787 let source = format!("{record}\nint f(struct s *p) {{ return p->y; }}\n");
2788 let body = body(&source);
2789 let Some((before, _)) = body.split_once("ptr_add") else { return 0 };
2790 let (_, constant) = before.rsplit_once("iconst.i64 ").expect("an offset constant");
2791 constant.lines().next().expect("a line").trim().parse().expect("a byte offset")
2792 }
2793
2794 #[test]
2800 fn an_attribute_among_the_specifiers_is_kept_beside_the_ones_written_in_front() {
2801 tast(concat!(
2802 "struct a { char c; __attribute__((aligned(8))) int i; };\n",
2803 "_Static_assert(sizeof(struct a) == 16 && _Alignof(struct a) == 8, \"a\");\n",
2804 "_Static_assert(__builtin_offsetof(struct a, i) == 8, \"a.i\");\n",
2805 "struct b { char c; __attribute__((packed)) int i; };\n",
2806 "_Static_assert(sizeof(struct b) == 5 && _Alignof(struct b) == 1, \"b\");\n",
2807 "_Static_assert(__builtin_offsetof(struct b, i) == 1, \"b.i\");\n",
2808 "typedef struct { char c; int i; } __attribute__((packed)) c;\n",
2809 "_Static_assert(sizeof(c) == 5 && _Alignof(c) == 1, \"c\");\n",
2810 ));
2811 }
2812
2813 #[test]
2819 fn pragma_pack_caps_every_member_and_is_read_where_the_body_closes() {
2820 tast(concat!(
2821 "#pragma pack(1)\n",
2822 "struct A { char c; int i; };\n",
2823 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2824 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2825 "#pragma pack()\n",
2826 "struct B { char c; int i; };\n",
2827 "_Static_assert(sizeof(struct B) == 8 && _Alignof(struct B) == 4, \"B\");\n",
2828 "#pragma pack(2)\n",
2829 "struct C { char c; int i; double d; };\n",
2830 "_Static_assert(sizeof(struct C) == 14 && _Alignof(struct C) == 2, \"C\");\n",
2831 "_Static_assert(__builtin_offsetof(struct C, d) == 6, \"C.d\");\n",
2832 "struct K { char c; int i __attribute__((aligned(8))); };\n",
2834 "_Static_assert(sizeof(struct K) == 6 && _Alignof(struct K) == 2, \"K\");\n",
2835 "_Static_assert(__builtin_offsetof(struct K, i) == 2, \"K.i\");\n",
2836 "struct J { char c; int i; } __attribute__((aligned(8)));\n",
2838 "_Static_assert(sizeof(struct J) == 8 && _Alignof(struct J) == 8, \"J\");\n",
2839 "#pragma pack()\n",
2840 "#pragma pack(push, 1)\n",
2841 "struct D { char c; short s; };\n",
2842 "_Static_assert(sizeof(struct D) == 3 && _Alignof(struct D) == 1, \"D\");\n",
2843 "#pragma pack(pop)\n",
2844 "struct E { char c; short s; };\n",
2845 "_Static_assert(sizeof(struct E) == 4 && _Alignof(struct E) == 2, \"E\");\n",
2846 "struct H { char c;\n",
2848 "#pragma pack(1)\n",
2849 " int i; };\n",
2850 "_Static_assert(sizeof(struct H) == 5 && _Alignof(struct H) == 1, \"H\");\n",
2851 "#pragma pack(1)\n",
2852 "struct I { char c;\n",
2853 "#pragma pack()\n",
2854 " int i; };\n",
2855 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2856 "#pragma pack()\n",
2857 "#pragma pack(push, 8)\n",
2859 "#pragma pack(push, 1)\n",
2860 "struct P { char c; int i; };\n",
2861 "_Static_assert(sizeof(struct P) == 5 && _Alignof(struct P) == 1, \"P\");\n",
2862 "#pragma pack(pop)\n",
2863 "struct Q { char c; int i; };\n",
2864 "_Static_assert(sizeof(struct Q) == 8 && _Alignof(struct Q) == 4, \"Q\");\n",
2865 "#pragma pack(pop)\n",
2866 "#pragma pack(16)\n",
2868 "struct R { char c; int i; };\n",
2869 "_Static_assert(sizeof(struct R) == 8 && _Alignof(struct R) == 4, \"R\");\n",
2870 "#pragma pack()\n",
2871 "#pragma pack(1)\n",
2872 "struct S { char c; int i : 5; int j : 20; };\n",
2873 "_Static_assert(sizeof(struct S) == 5 && _Alignof(struct S) == 1, \"S\");\n",
2874 "union T { char c; int i; };\n",
2875 "_Static_assert(sizeof(union T) == 4 && _Alignof(union T) == 1, \"T\");\n",
2876 "#pragma pack()\n",
2877 ));
2878 }
2879
2880 #[test]
2884 fn a_pack_line_that_is_not_one_is_reported_in_the_words_gcc_uses() {
2885 let result = run(
2886 &options(),
2887 concat!(
2888 "#pragma pack 4\n",
2889 "#pragma pack(pop)\n",
2890 "#pragma pack(3)\n",
2891 "#pragma pack(1) junk\n",
2892 "#pragma pack(push, 1\n",
2893 "#pragma pack(x)\n",
2894 "#pragma pack(0)\n",
2897 "#pragma pack(push)\n",
2898 "struct s { char c; int i; };\n",
2899 "#pragma pack(pop)\n",
2900 "#pragma pack(pop, foo)\n",
2901 ),
2902 );
2903 let expected = [
2904 "missing `(` after `#pragma pack` - ignored",
2905 "`#pragma pack (pop)` encountered without matching `#pragma pack (push)`",
2906 "alignment must be a small power of two, not 3",
2907 "junk at end of `#pragma pack`",
2908 "malformed `#pragma pack(push[, id][, <n>])` - ignored",
2909 "unknown action `x` for `#pragma pack` - ignored",
2910 "`#pragma pack(pop, foo)` encountered without matching `#pragma pack(push, foo)`",
2911 ];
2912 assert_eq!(result.messages.len(), expected.len(), "{:?}", result.messages);
2913 for (message, want) in result.messages.iter().zip(expected) {
2914 assert!(message.contains(want), "expected {want:?} in {message:?}");
2915 }
2916 }
2917
2918 #[test]
2925 fn a_declaration_behind_an_empty_macro_is_not_eaten_by_the_pragma_above_it() {
2926 let result = run(
2927 &options(),
2928 concat!(
2929 "#pragma pack(push, 1)\n",
2930 "#pragma pack(pop)\n",
2931 "#define API\n",
2932 "API const char version[] = \"3.53.4\";\n",
2933 "const char *get(void) { return version; }\n",
2934 ),
2935 );
2936 assert!(result.messages.is_empty(), "{:?}", result.messages);
2937 }
2938
2939 #[test]
2943 fn the_wide_integer_answers_to_all_three_of_its_names() {
2944 let text = tast("__uint128_t a; __int128_t b; unsigned __int128 c;\n");
2945 assert!(text.contains("decl #0 a : unsigned __int128"), "{text}");
2946 assert!(text.contains("decl #1 b : __int128"), "{text}");
2947 assert!(text.contains("decl #2 c : unsigned __int128"), "{text}");
2948 }
2949
2950 #[test]
2951 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
2952 let text = tast("long f(int a, long b) { return a + b; }\n");
2956 assert!(text.contains("convert arithmetic"), "{text}");
2957 }
2958
2959 #[test]
2960 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
2961 for source in [
2962 "#error stop\n",
2963 "int f(void) { return 1 + ; }\n",
2964 "int f(void) { return undeclared; }\n",
2965 ] {
2966 let result = run(&options(), source);
2967 assert!(result.failed(), "expected this to fail:\n{source}");
2968 assert!(
2969 result.text().is_empty(),
2970 "a file that did not compile wrote a tree:\n{source}"
2971 );
2972 }
2973 }
2974
2975 #[test]
2976 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
2977 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
2981 assert_eq!(result.errors, 1, "{:?}", result.messages);
2982 }
2983
2984 #[test]
2985 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
2986 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
2990 assert_eq!(result.errors, 1, "{:?}", result.messages);
2991 }
2992
2993 #[test]
2994 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
2995 let source = "int f(void) { char c = 300; return c; }\n";
2996 let plain = run(&options(), source);
2997 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
2998 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
2999 assert!(!plain.text().is_empty(), "a warning is not a reason to write nothing");
3000
3001 let mut opts = options();
3002 opts.warnings_are_errors = true;
3003 let strict = run(&opts, source);
3004 assert!(strict.failed());
3005 assert!(strict.text().is_empty(), "and under -Werror it is a reason to write nothing");
3006 for message in &strict.messages {
3007 assert!(!message.contains("warning:"), "{message}");
3008 }
3009 }
3010
3011 #[test]
3012 fn w_drops_the_warning_before_werror_can_promote_it() {
3013 let source = "int f(void) { char c = 300; return c; }\n";
3014 let mut opts = options();
3015 opts.warnings = false;
3016 let quiet = run(&opts, source);
3017 assert_eq!(quiet.messages, Vec::<String>::new());
3018 assert_eq!(quiet.errors, 0);
3019 assert!(!quiet.text().is_empty(), "and the file still compiles");
3020
3021 opts.warnings_are_errors = true;
3024 let both = run(&opts, source);
3025 assert_eq!(both.messages, Vec::<String>::new());
3026 assert!(!both.failed(), "-w -Werror is not an error about a warning nobody saw");
3027 }
3028
3029 #[test]
3030 fn the_dialect_reaches_the_keywords_and_the_checking() {
3031 let source = "typeof(1) x;\n";
3034 let mut opts = options();
3035 opts.std = Std::C23;
3036 opts.gnu_extensions = false;
3037 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
3038
3039 opts.std = Std::C17;
3040 assert!(run(&opts, source).failed());
3041 }
3042
3043 #[test]
3044 fn asking_for_a_kind_that_is_not_written_yet_runs_the_front_end_and_writes_nothing() {
3045 let mut opts = options();
3046 opts.emit = EmitKind::Object;
3047 let result = run(&opts, "int x = 1;\n");
3048 assert!(!result.failed(), "{:?}", result.messages);
3049 assert!(result.text().is_empty());
3050 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
3053 }
3054
3055 fn mir(source: &str) -> String {
3057 let mut opts = options();
3058 opts.emit = EmitKind::MirFinal;
3059 let result = run(&opts, source);
3060 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3061 result.text().to_owned()
3062 }
3063
3064 #[test]
3070 fn a_function_goes_from_c_to_instructions_with_real_registers_in_them() {
3071 let text = mir("int add(int a, int b) { return a + b; }\n");
3072 assert!(text.starts_with("mfunc @add {"), "{text}");
3073 assert!(text.contains("x64.add_rr_32"), "{text}");
3074 assert!(text.contains("x64.ret"), "{text}");
3075 assert!(!text.contains('%'), "{text}");
3078 }
3079
3080 #[test]
3082 fn a_function_with_no_body_produces_no_machine_function() {
3083 let text = mir("int g(int);\nint f(int a) { return g(a); }\n");
3084 assert_eq!(text.matches("mfunc @").count(), 1, "{text}");
3085 assert!(text.contains("mfunc @f {"), "{text}");
3086 assert!(text.contains("x64.call"), "{text}");
3087 }
3088
3089 #[test]
3091 fn every_definition_in_the_file_is_generated_and_they_keep_their_order() {
3092 let text = mir("int a(int x) { return x; }\nint b(int x) { return x; }\n");
3093 let first = text.find("mfunc @a").expect("the first function");
3094 let second = text.find("mfunc @b").expect("the second function");
3095 assert!(first < second, "{text}");
3096 }
3097
3098 #[test]
3100 fn the_target_decides_which_convention_the_generated_code_follows() {
3101 let mut opts = options();
3102 opts.emit = EmitKind::MirFinal;
3103 let linux = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
3104 assert!(linux.contains("$rdi"), "{linux}");
3105
3106 opts.target = "x86_64-pc-windows-msvc".parse::<Triple>().unwrap();
3107 let windows = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
3108 assert!(windows.contains("$rcx"), "{windows}");
3109 assert!(!windows.contains("$rdi"), "{windows}");
3110 }
3111
3112 #[test]
3120 fn a_tagged_member_with_no_name_is_a_member_on_windows_and_nothing_on_linux() {
3121 let source = concat!(
3122 "struct S { union U { int i; void *p; }; unsigned long tymed; };\n",
3123 "int size(void) { return sizeof(struct S); }\n",
3124 "int f(struct S *s) { s->i = 1; return s->i; }\n",
3125 );
3126
3127 let mut opts = options();
3128 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3129 let windows = run(&opts, source);
3130 assert!(windows.messages.is_empty(), "{:?}", windows.messages);
3131
3132 let linux = run(&options(), source);
3133 assert_eq!(linux.messages.len(), 3, "{:?}", linux.messages);
3134 assert!(linux.messages[0].contains("does not declare anything"), "{:?}", linux.messages);
3135
3136 let mut opts = options();
3139 opts.ms_extensions = Some(true);
3140 let asked = run(&opts, source);
3141 assert!(asked.messages.is_empty(), "{:?}", asked.messages);
3142 }
3143
3144 #[test]
3146 fn a_target_this_has_no_back_end_for_is_reported_rather_than_generated() {
3147 let mut opts = options();
3148 opts.emit = EmitKind::MirFinal;
3149 opts.target = "riscv64-unknown-linux-gnu".parse::<Triple>().unwrap();
3150 let result = run(&opts, "int f(int a) { return a; }\n");
3151 assert!(result.failed());
3152 assert!(result.messages[0].contains("no back end for riscv64"), "{:?}", result.messages);
3153 assert!(result.text().is_empty());
3154 }
3155
3156 #[test]
3159 fn an_aarch64_target_is_written_as_aarch64_assembly() {
3160 let mut opts = options();
3161 opts.emit = EmitKind::Asm;
3162 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3163 let source = "int g(int);\nint f(int a, int b) { return g(a) + b; }\n";
3164 let result = run(&opts, source);
3165 assert!(!result.failed(), "{:?}", result.messages);
3166 let text = result.text();
3167 for line in ["stp x29, x30, [sp, #-16]!", "mov x29, sp", "bl g", "ldp x29, x30, [sp], #16"]
3168 {
3169 assert!(text.contains(line), "{line} is not in\n{text}");
3170 }
3171 assert!(!text.contains('%'), "{text}");
3172 }
3173
3174 #[test]
3177 fn an_aarch64_target_reaches_an_object_file() {
3178 let mut opts = options();
3179 opts.emit = EmitKind::Object;
3180 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3181 let source = concat!(
3182 "int g(int);\n",
3183 "int table[4] = {1, 2, 3, 4};\n",
3184 "int f(int a, int b) { return g(a) + table[b & 3]; }\n",
3185 );
3186 let result = run(&opts, source);
3187 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3188 let bytes = match result.artifact {
3189 Artifact::Object { bytes, defines } => {
3190 assert_eq!(defines, ["f", "table"]);
3191 bytes
3192 }
3193 other => panic!("expected an object, got {other:?}"),
3194 };
3195 assert_eq!(&bytes[..4], b"\x7fELF");
3196 assert_eq!(&bytes[18..20], &183u16.to_le_bytes(), "EM_AARCH64");
3197
3198 opts.debug_info = true;
3201 let result = run(&opts, source);
3202 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3203 let bytes = match result.artifact {
3204 Artifact::Object { bytes, .. } => bytes,
3205 other => panic!("expected an object, got {other:?}"),
3206 };
3207 let has = |name: &[u8]| bytes.windows(name.len()).any(|at| at == name);
3208 assert!(has(b".debug_line\0") && has(b".debug_info\0"));
3209 assert!(!has(b"rucc_row"), "a row label reached the symbol table");
3210 }
3211
3212 #[test]
3215 fn the_aarch64_vector_type_names_are_declared_on_that_target_and_nowhere_else() {
3216 let mut opts = options();
3217 opts.emit = EmitKind::Asm;
3218 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3219 let source = "typedef __Float32x4_t f4;\n__SVFloat32_t sv(__SVFloat32_t, __SVBool_t);\n\
3220 int n = sizeof(f4) + sizeof(__Int8x8_t);\n\
3221 int f(f4 v) { int __Uint8x16_t = 3; return v[1] + __Uint8x16_t; }\n";
3222 let result = run(&opts, source);
3223 assert!(!result.failed(), "{:?}", result.messages);
3224 assert!(result.text().contains(".long\t24"), "{}", result.text());
3225 opts.target = "x86_64-unknown-linux-gnu".parse::<Triple>().unwrap();
3226 let result = run(&opts, "typedef __Float32x4_t f4;\n");
3227 assert!(result.failed());
3228 let result = run(&opts, "int __Float32x4_t = 1;\n");
3229 assert!(!result.failed(), "{:?}", result.messages);
3230 }
3231
3232 #[test]
3235 fn an_aarch64_result_in_memory_is_reached_through_x8() {
3236 let mut opts = options();
3237 opts.emit = EmitKind::Asm;
3238 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3239 let source = "struct big { long a, b, c; };\nstruct big make(long v);\n\
3240 long f(long v) { return make(v).c; }\n\
3241 struct big g(long v) { struct big b = { v, v, v }; return b; }\n";
3242 let result = run(&opts, source);
3243 assert!(!result.failed(), "{:?}", result.messages);
3244 let text = result.text();
3245 assert!(text.contains("x8"), "{text}");
3246 assert!(text.contains("bl make"), "{text}");
3247 }
3248
3249 #[test]
3252 fn an_aarch64_remainder_is_a_division_and_a_multiply_subtract() {
3253 let mut opts = options();
3254 opts.emit = EmitKind::Asm;
3255 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3256 let source = "int s(int a, int b) { return a % b; }\n\
3257 unsigned long u(unsigned long a, unsigned long b) { return a % b; }\n";
3258 let result = run(&opts, source);
3259 assert!(!result.failed(), "{:?}", result.messages);
3260 let text = result.text();
3261 let at = |what: &str| text.find(what).unwrap_or_else(|| panic!("{what} is not in\n{text}"));
3262 assert!(at("sdiv w") < at("msub w"), "{text}");
3263 assert!(at("udiv x") < at("msub x"), "{text}");
3264 }
3265
3266 #[test]
3269 fn an_aarch64_jump_table_is_reached_with_adr() {
3270 let mut opts = options();
3271 opts.emit = EmitKind::Asm;
3272 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3273 let source = "int f(int x) { switch (x) { case 0: return 10; case 1: return 21; \
3274 case 2: return 32; case 3: return 43; case 4: return 54; case 5: return 65; \
3275 case 6: return 76; case 7: return 87; case 8: return 98; case 9: return 9; \
3276 case 10: return 19; case 11: return 29; default: return 0; } }\n";
3277 let result = run(&opts, source);
3278 assert!(!result.failed(), "{:?}", result.messages);
3279 let text = result.text();
3280 let at = |what: &str| text.find(what).unwrap_or_else(|| panic!("{what} is not in\n{text}"));
3281 assert!(at("adr x") < at("ldrsw x"), "{text}");
3282 assert!(at("ldrsw x") < at("br x"), "{text}");
3283 assert!(text.contains("_j0:"), "{text}");
3284 assert!(text.contains(".long"), "{text}");
3285 }
3286
3287 #[test]
3291 fn an_aarch64_va_start_writes_the_five_fields_of_its_list() {
3292 let mut opts = options();
3293 opts.emit = EmitKind::Asm;
3294 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3295 let source = "typedef __builtin_va_list va_list;\n\
3296 int f(int n, ...) { va_list ap; __builtin_va_start(ap, n); \
3297 int x = __builtin_va_arg(ap, int); double d = __builtin_va_arg(ap, double); \
3298 __builtin_va_end(ap); return x + (int)d; }\n";
3299 let result = run(&opts, source);
3300 assert!(!result.failed(), "{:?}", result.messages);
3301 let text = result.text();
3302 assert!(text.contains("#-56"), "{text}");
3303 assert!(text.contains("#-128"), "{text}");
3304 assert!(text.contains("#24]"), "{text}");
3305 assert!(text.contains("#28]"), "{text}");
3306 assert!(text.contains("str q"), "{text}");
3307 }
3308
3309 #[test]
3312 fn an_aarch64_long_double_is_a_quad_in_a_vector_register() {
3313 let mut opts = options();
3314 opts.emit = EmitKind::Asm;
3315 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3316 let source = "void f(long double *p, long double x) { *p = *p + x; }\n";
3317 let result = run(&opts, source);
3318 assert!(!result.failed(), "{:?}", result.messages);
3319 let text = result.text();
3320 assert!(text.contains("ldr q"), "{text}");
3321 assert!(text.contains("str q"), "{text}");
3322 assert!(text.contains("__addtf3"), "{text}");
3323 }
3324
3325 #[test]
3328 fn an_aarch64_thread_local_is_reached_through_tpidr_el0() {
3329 let mut opts = options();
3330 opts.emit = EmitKind::Asm;
3331 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3332 let source = "__thread int n;\nint *f(void) { return &n; }\n\
3333 void *g(void) { return __builtin_thread_pointer(); }\n";
3334 let result = run(&opts, source);
3335 assert!(!result.failed(), "{:?}", result.messages);
3336 let text = result.text();
3337 assert!(text.contains(":gottprel:n"), "{text}");
3338 assert!(text.contains(":gottprel_lo12:n]"), "{text}");
3339 assert_eq!(text.matches("mrs x").count(), 2, "{text}");
3340 assert!(text.contains("tpidr_el0"), "{text}");
3341 }
3342
3343 #[test]
3346 fn a_darwin_thread_local_is_reached_through_its_descriptor() {
3347 let source = "__thread int n = 5;\nint *f(void) { return &n; }\n";
3348 for (triple, wanted) in [
3349 ("aarch64-apple-darwin", &["_n@TLVPPAGE\n", "_n@TLVPPAGEOFF]\n", "\tblr x"][..]),
3350 ("x86_64-apple-darwin", &["_n@TLVP(%rip), %rdi\n", "\tcall\t*%"][..]),
3351 ] {
3352 let mut opts = options();
3353 opts.emit = EmitKind::Asm;
3354 opts.target = triple.parse::<Triple>().unwrap();
3355 let result = run(&opts, source);
3356 assert!(!result.failed(), "{triple}: {:?}", result.messages);
3357 let text = result.text();
3358 for want in wanted {
3359 assert!(text.contains(want), "{triple} wanted {want:?}:\n{text}");
3360 }
3361 assert!(text.contains("\n_n:\n\t.quad\t__tlv_bootstrap\n"), "{text}");
3362 assert!(!text.contains("tpidr_el0") && !text.contains("%fs"), "{text}");
3363 }
3364 }
3365
3366 #[test]
3368 fn the_thread_pointer_is_refused_on_darwin() {
3369 let mut opts = options();
3370 opts.emit = EmitKind::Asm;
3371 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3372 let result = run(&opts, "void *f(void) { return __builtin_thread_pointer(); }\n");
3373 assert!(result.failed());
3374 assert!(result.messages[0].contains("thread pointer"), "{:?}", result.messages);
3375 }
3376
3377 #[test]
3380 fn a_darwin_variadic_definition_saves_nothing_and_walks_the_stack() {
3381 let mut opts = options();
3382 opts.emit = EmitKind::Asm;
3383 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3384 let source = "int f(int n, ...) { __builtin_va_list ap; __builtin_va_start(ap, n);\n\
3385 int r = __builtin_va_arg(ap, int); __builtin_va_end(ap); return r; }\n";
3386 let result = run(&opts, source);
3387 assert!(!result.failed(), "{:?}", result.messages);
3388 let text = result.text();
3389 assert!(!text.contains("str q"), "{text}");
3390 assert!(!text.contains("x7"), "{text}");
3391 }
3392
3393 #[test]
3396 fn a_darwin_call_puts_its_variadic_arguments_in_memory() {
3397 let mut opts = options();
3398 opts.emit = EmitKind::Asm;
3399 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3400 let source = "int printf(const char *, ...);\n\
3401 int g(double x) { return printf(\"%d %f\", 7, x); }\n";
3402 let result = run(&opts, source);
3403 assert!(!result.failed(), "{:?}", result.messages);
3404 let text = result.text();
3405 assert!(text.contains("str d0, [sp, #8]"), "{text}");
3406 }
3407
3408 #[test]
3412 fn a_darwin_listing_is_one_apples_assembler_reads() {
3413 let mut opts = options();
3414 opts.emit = EmitKind::Asm;
3415 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3416 let source = "extern int ext;\n\
3417 int g[4];\n\
3418 int f(int i) { return g[i] + ext; }\n";
3419 let result = run(&opts, source);
3420 assert!(!result.failed(), "{:?}", result.messages);
3421 let text = result.text();
3422 assert!(text.contains(", _g@PAGE\n"), "{text}");
3423 assert!(text.contains(", _g@PAGEOFF\n"), "{text}");
3424 assert!(text.contains(", _ext@GOTPAGE\n"), "{text}");
3425 assert!(text.contains(", _ext@GOTPAGEOFF]\n"), "{text}");
3426 assert!(!text.contains(":lo12:"), "{text}");
3427 assert!(text.contains("\t.globl\t_g\n\t.zerofill\t__DATA,__bss,_g,16,2\n"), "{text}");
3428 }
3429
3430 #[test]
3436 fn a_signed_char_on_aarch64_is_widened_with_its_sign_before_it_is_added_to() {
3437 for target in ["aarch64-linux-gnu", "aarch64-apple-darwin"] {
3438 let mut opts = options();
3439 opts.emit = EmitKind::Asm;
3440 opts.target = target.parse::<Triple>().unwrap();
3441 let source = "int f(signed char *p) { return *p + 1; }\n\
3442 unsigned g(unsigned short *p) { return *p + 1u; }\n";
3443 let result = run(&opts, source);
3444 assert!(!result.failed(), "{:?}", result.messages);
3445 let text = result.text();
3446 let signed = text.contains("\tsxtb w") || text.contains("\tldrsb w");
3447 assert!(signed, "{target}: {text}");
3448 }
3449 }
3450
3451 #[test]
3463 fn a_construct_the_back_end_cannot_reach_yet_is_reported_against_its_function() {
3464 let mut opts = options();
3465 opts.emit = EmitKind::MirFinal;
3466 let source = "void a(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
3467 s; s.x = 1; v[0] = s.x; }\n\
3468 void b(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
3469 s; s.x = 1; v[0] = s.x; }\n";
3470 let result = run(&opts, source);
3471 assert!(result.failed());
3472 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
3473 assert!(result.messages[0].contains("cannot generate code for 'a'"), "{:?}", result);
3474 assert!(result.messages[0].contains("wants more alignment"), "{:?}", result);
3475 assert!(result.messages[1].contains("cannot generate code for 'b'"), "{:?}", result);
3476 assert!(result.text().is_empty());
3477 }
3478
3479 #[test]
3487 fn a_variable_length_array_walks_its_pages_where_every_page_of_the_frame_is_to_be_touched() {
3488 let mut opts = options();
3489 opts.emit = EmitKind::MirFinal;
3490 let source = "void a(int n) { int v[n]; v[0] = 1; }\n";
3491 let plain = run(&opts, source);
3492 assert!(!plain.failed(), "{:?}", plain.messages);
3493 assert!(!plain.text().contains("cmp_set_a_64"), "{}", plain.text());
3494
3495 opts.stack_clash = true;
3496 let result = run(&opts, source);
3497 assert!(!result.failed(), "{:?}", result.messages);
3498 assert!(result.text().contains("cmp_set_a_64"), "{}", result.text());
3499 assert!(result.text().contains("or_mi_8"), "{}", result.text());
3500 }
3501
3502 #[test]
3512 fn a_function_that_keeps_a_frame_pointer_on_windows_reaches_an_object_file() {
3513 let mut opts = options();
3514 opts.emit = EmitKind::Object;
3515 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3516 let source = concat!(
3517 "void use(void *p);\n",
3518 "void array(int n) { int v[n]; v[0] = 1; use(v); }\n",
3519 "void taken(unsigned long n) { use(__builtin_alloca(n)); }\n",
3520 );
3521 let result = run(&opts, source);
3522 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3523 let bytes = match result.artifact {
3524 Artifact::Object { bytes, .. } => bytes,
3525 other => panic!("expected an object, got {other:?}"),
3526 };
3527 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3528
3529 let mut opts = options();
3532 opts.emit = EmitKind::Object;
3533 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3534 }
3535
3536 #[test]
3547 fn the_address_of_a_function_this_file_only_declares_reaches_a_windows_object() {
3548 let source = concat!(
3549 "void other(void *p);\n",
3550 "void takes(void (*f)(void *));\n",
3551 "void (*held)(void *);\n",
3552 "void pass(void) { takes(other); }\n",
3553 "void keep(void) { held = other; }\n",
3554 "void call(void) { other(0); }\n",
3555 );
3556 let mut opts = options();
3557 opts.emit = EmitKind::Object;
3558 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3559 let result = run(&opts, source);
3560 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3561 let bytes = match result.artifact {
3562 Artifact::Object { bytes, .. } => bytes,
3563 other => panic!("expected an object, got {other:?}"),
3564 };
3565 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3566
3567 let mut opts = options();
3570 opts.emit = EmitKind::Object;
3571 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3572 }
3573
3574 #[test]
3588 fn an_opcode_with_no_name_in_the_rule_language_is_named_by_its_own_spelling() {
3589 let mut opts = options();
3590 opts.emit = EmitKind::MirFinal;
3591 let source =
3592 "long double f(int a) {\n __int128 wide = a;\n return (long double) wide;\n}\n";
3593 let result = run(&opts, source);
3594 assert!(result.failed());
3595 assert!(
3596 result.messages[0].contains("no rule lowers a `sext` producing a `i128`"),
3597 "{result:?}"
3598 );
3599 assert!(result.messages[0].contains(":2:"), "the line the widening is on: {result:?}");
3600 assert!(!result.messages[0].contains("this instruction"), "{result:?}");
3601 }
3602
3603 #[test]
3605 fn the_note_on_unfinished_work_points_at_the_issues_rather_than_at_the_plan() {
3606 let mut opts = options();
3607 opts.emit = EmitKind::MirFinal;
3608 let source = "long double f(int a) { __int128 wide = a; return (long double) wide; }\n";
3609 let result = run(&opts, source);
3610 assert!(result.failed());
3611 let note = result.messages.iter().find(|line| line.contains("note:")).expect("a note");
3612 assert!(note.contains("https://github.com/tamnd/rucc/issues"), "{note}");
3613 assert!(!note.contains("spec/17-milestones.md"), "{note}");
3614 }
3615
3616 #[test]
3618 fn the_frame_flags_on_the_command_line_reach_the_generated_frame() {
3619 let source = "int f(int a) { return a; }\n";
3620 assert!(!mir(source).contains("$rbp"), "a leaf needs no frame pointer when told so");
3621
3622 let mut opts = options();
3623 opts.emit = EmitKind::MirFinal;
3624 opts.frame_pointer = Some(true);
3625 let kept = run(&opts, source).text().to_owned();
3626 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
3627
3628 opts.frame_pointer = None;
3630 let kept = run(&opts, source).text().to_owned();
3631 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
3632 }
3633
3634 fn asm(source: &str) -> String {
3636 let mut opts = options();
3637 opts.emit = EmitKind::Asm;
3638 let result = run(&opts, source);
3639 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3640 result.text().to_owned()
3641 }
3642
3643 #[test]
3650 fn a_function_goes_from_c_to_assembly_an_assembler_would_take() {
3651 let text = asm("int add(int a, int b) { return a + b; }\n");
3652 assert!(text.contains("\t.globl\tadd\n"), "{text}");
3653 assert!(text.contains("\t.type\tadd, @function\n"), "{text}");
3654 assert!(text.contains("\nadd:\n"), "{text}");
3655 assert!(text.contains("\taddl\t"), "{text}");
3656 assert!(text.contains("\tret\n"), "{text}");
3657 assert!(text.contains("\t.size\tadd, .-add\n"), "{text}");
3658 assert!(text.contains(".note.GNU-stack"), "{text}");
3661 }
3662
3663 #[test]
3669 fn a_call_through_a_function_pointer_goes_through_the_register_it_is_in() {
3670 let text = asm("int g(int);\nint f(int (*p)(int), int a) { return p(a) + g(a); }\n");
3671 assert!(text.contains("\tcall\t*%"), "{text}");
3672 assert!(text.contains("\tcall\tg\n"), "{text}");
3673 assert!(text.contains("%rdi"), "{text}");
3677 }
3678
3679 #[test]
3683 fn the_address_of_a_global_is_read_from_the_instruction_pointer() {
3684 let text = asm("extern int counter;\nint f(void) { return counter; }\n");
3685 assert!(text.contains("\tmovl\tcounter(%rip), %eax\n"), "{text}");
3686 }
3687
3688 #[test]
3697 fn a_branch_on_a_comparison_jumps_on_the_opposite_of_what_it_compared() {
3698 let arms = "return 1; return 2;";
3699 let signed = [("==", "jne"), ("!=", "je"), ("<", "jge"), ("<=", "jg"), (">", "jle")];
3700 for (operator, jump) in signed.into_iter().chain([(">=", "jl")]) {
3701 let text = asm(&format!("int f(int a, int b) {{ if (a {operator} b) {arms} }}\n"));
3702 assert!(
3703 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
3704 "{operator}: {text}"
3705 );
3706 assert!(!text.contains("\tset"), "{operator}: {text}");
3707 assert!(!text.contains("\ttest"), "{operator}: {text}");
3708 }
3709 let unsigned = [("<", "jae"), ("<=", "ja"), (">", "jbe"), (">=", "jb")];
3710 for (operator, jump) in unsigned {
3711 let source =
3712 format!("int f(unsigned a, unsigned b) {{ if (a {operator} b) {arms} }}\n");
3713 let text = asm(&source);
3714 assert!(
3715 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
3716 "{operator}: {text}"
3717 );
3718 }
3719
3720 let text = asm("int f(int a) { if (a < 7) return 1; return 2; }\n");
3723 assert!(text.contains("\tcmpl\t$7, %edi\n\tjge\t"), "{text}");
3724 }
3725
3726 #[test]
3732 fn a_comparison_whose_answer_the_program_wanted_still_writes_a_byte() {
3733 let text = asm("int f(int a, int b) { return a < b; }\n");
3734 assert!(text.contains("\tsetl\t"), "{text}");
3735 }
3736
3737 fn optimized(source: &str) -> String {
3739 let mut opts = options();
3740 opts.emit = EmitKind::Asm;
3741 opts.opt_level = rucc_session::OptLevel::O2;
3742 let result = run(&opts, source);
3743 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3744 result.text().to_owned()
3745 }
3746
3747 #[test]
3749 fn opt_info_says_what_each_switch_became_and_a_forced_shape_is_what_it_says() {
3750 let arms: String = (0..40)
3751 .map(|k| format!("case {}: return g({k});", k * 17))
3752 .collect::<Vec<_>>()
3753 .join(" ");
3754 let source = format!("int g(int);\nint f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n");
3755 let said = |shape: Option<&str>| {
3756 let mut opts = options();
3757 opts.emit = EmitKind::Asm;
3758 opts.opt_level = rucc_session::OptLevel::O2;
3759 opts.opt_info = vec![String::new()];
3760 opts.switch_shape = shape.map(str::to_owned);
3761 let result = run(&opts, &source);
3762 assert_eq!(result.messages, Vec::<String>::new());
3763 let lines: Vec<String> = result
3764 .remarks
3765 .lines()
3766 .filter(|line| line.contains("[switch-lowering]"))
3767 .map(str::to_owned)
3768 .collect();
3769 assert_eq!(lines.len(), 1, "{}", result.remarks);
3770 lines[0].clone()
3771 };
3772 assert!(said(None).contains(": f: optimized: switch of 40 cases lowered as a tree;"));
3773 assert!(said(Some("table")).contains("lowered as a table;"));
3774 assert!(said(Some("walk")).contains("lowered as a walk;"));
3775 }
3776
3777 #[test]
3787 fn a_switch_whose_arms_are_a_function_of_the_label_is_a_range_check_and_arithmetic() {
3788 let arms: String =
3789 (0..16).map(|k| format!("case {k}: return {};", k + 1)).collect::<Vec<_>>().join(" ");
3790 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3791 assert!(text.contains("\tcmpl\t$15, %edi\n\tja\t"), "{text}");
3792 assert!(text.contains("\taddl\t$1, %edi"), "{text}");
3793 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
3794 }
3795
3796 #[test]
3804 fn a_dense_switch_whose_arms_are_not_a_line_is_a_load_from_a_table() {
3805 let arms: String = (0..16)
3806 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
3807 .collect::<Vec<_>>()
3808 .join(" ");
3809 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3810 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
3811 assert!(!text.contains("\tjmp\t*"), "{text}");
3812 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
3813 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
3814 let section = text[..text.find("CSWTCH.0:").unwrap_or(0)].rfind("\t.section\t.rodata");
3815 assert!(section.is_some(), "{text}");
3816 assert_eq!(table.matches("\t.long\t").count(), 16, "{text}");
3817 assert!(table.contains("\t.long\t100\n"), "{text}");
3818 }
3819
3820 #[test]
3826 fn a_switch_whose_arms_give_strings_is_a_table_of_how_far_away_they_are() {
3827 let text = optimized(
3828 "const char *f(int k) { switch (k) { case 0: return \"zero\"; \
3829 case 1: return \"one\"; case 2: return \"two\"; case 3: return \"three\"; } \
3830 return \"many\"; }\n",
3831 );
3832 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
3833 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
3834 assert!(!text.contains(".data.rel.ro"), "{text}");
3835 let at = text.find("CSWTCH.0:").expect("the table is in the output");
3836 assert!(text[..at].rfind("\t.section\t.rodata").is_some(), "{text}");
3837 let table = &text[at..];
3838 assert_eq!(table.matches(" - .\n").count(), 4, "{text}");
3839 assert!(table.contains("\t.long\t.Lstr.1+4 - .\n"), "{text}");
3840 }
3841
3842 #[test]
3848 fn a_table_at_os_has_cells_as_narrow_as_its_answers() {
3849 let arms: String = (0..16)
3850 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
3851 .collect::<Vec<_>>()
3852 .join(" ");
3853 let mut opts = options();
3854 opts.emit = EmitKind::Asm;
3855 opts.opt_level = rucc_session::OptLevel::Os;
3856 let result = run(&opts, &format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
3857 assert_eq!(result.messages, Vec::<String>::new());
3858 let text = result.text();
3859 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
3860 assert_eq!(table.matches("\t.byte\t").count(), 16, "{text}");
3861 assert!(text.contains("\tmovsbl\t"), "{text}");
3862 }
3863
3864 #[test]
3871 fn a_table_that_covers_its_operand_has_no_range_check() {
3872 let text = optimized(
3873 "int f(unsigned x) { switch (x & 3) { case 0: return 5; case 1: return 9; \
3874 case 2: return 2; case 3: return 7; } return -1; }\n",
3875 );
3876 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
3877 assert!(!text.contains("\tcmp"), "{text}");
3878 assert!(!text.contains("$-1"), "{text}");
3879 }
3880
3881 #[test]
3886 fn a_store_to_a_local_the_loop_just_read_is_a_conditional_move() {
3887 let text = optimized(
3888 "int f(const int *v, int n, int k) { int best[8] = {0}; \
3889 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; \
3890 return best[k & 7]; }\n",
3891 );
3892 assert!(text.contains("\tcmovgl"), "{text}");
3893 assert!(!text.contains("\tset"), "{text}");
3894 assert!(!text.contains("\ttestb"), "{text}");
3895 }
3896
3897 #[test]
3899 fn a_store_to_a_global_the_loop_just_read_keeps_its_branch() {
3900 let text = optimized(
3901 "int best[8]; void f(const int *v, int n) { \
3902 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; }\n",
3903 );
3904 assert!(!text.contains("\tcmov"), "{text}");
3905 }
3906
3907 #[test]
3915 fn a_conversion_from_a_constant_double_is_the_number_it_converts_to() {
3916 let text = optimized("int f(void) { double d = 2.75; return (int) d; }\n");
3917 assert!(text.contains("movl\t$2, %eax"), "{text}");
3918 assert!(!text.contains("cvttsd2si"), "{text}");
3919 }
3920
3921 #[test]
3929 fn a_slot_of_a_read_only_table_is_the_value_the_table_holds() {
3930 let text =
3931 optimized("static const int t[4] = {10, 20, 30, 40};\nint f(void) { return t[2]; }\n");
3932 assert!(text.contains("movl\t$30, %eax"), "{text}");
3933 assert!(!text.contains("t(%rip)"), "{text}");
3934 }
3935
3936 #[test]
3939 fn a_byte_of_a_read_only_string_is_the_byte_the_string_spells() {
3940 let text = optimized("static const char s[] = \"abc\";\nint f(void) { return s[1]; }\n");
3941 assert!(text.contains("movl\t$98, %eax"), "{text}");
3942 }
3943
3944 #[test]
3948 fn a_table_that_is_not_read_only_keeps_its_load() {
3949 let text = optimized(
3950 "static int t[4] = {10, 20, 30, 40};\nvoid g(int x) { t[2] = x; }\nint f(void) { return t[2]; }\n",
3951 );
3952 assert!(!text.contains("movl\t$30, %eax"), "{text}");
3953 }
3954
3955 #[test]
3962 fn a_call_guarded_by_a_condition_a_read_only_object_settles_is_not_emitted() {
3963 let text = optimized(
3964 "void link_error(void);\nconst double one = 1.0;\nint main(void) { if ((int) one != 1) link_error(); return 0; }\n",
3965 );
3966 assert!(!text.contains("call\tlink_error"), "{text}");
3967 }
3968
3969 #[test]
3971 fn a_cast_between_a_pointer_and_an_integer_leaves_the_value_where_it_is() {
3972 let text = asm("long f(void *p) { return (long)p; }\n");
3973 for line in text.lines().filter(|line| line.starts_with('\t') && !line.contains('.')) {
3978 let mnemonic = line.split_whitespace().next().unwrap_or("");
3979 assert!(matches!(mnemonic, "movq" | "ret"), "{line} in\n{text}");
3980 }
3981 }
3982
3983 #[test]
3987 fn an_argument_past_the_last_register_is_read_out_of_the_caller_s_stack() {
3988 let six = "long a, long b, long c, long d, long e, long f";
3989 let text = asm(&format!("long f({six}, long g, long h) {{ return g + h; }}\n"));
3990
3991 assert!(text.contains("\tmovq\t8(%rsp), "), "{text}");
3998 assert!(text.contains("\taddq\t16(%rsp), "), "{text}");
3999
4000 let narrow = asm(&format!("int f({six}, int g) {{ return g; }}\n"));
4004 assert!(narrow.contains("\tmovl\t8(%rsp), "), "{narrow}");
4005 let eight =
4006 "double a, double b, double c, double d, double e, double f, double g, double h";
4007 let float = asm(&format!("double f({eight}, double i) {{ return i; }}\n"));
4008 assert!(float.contains("\tmovsd\t8(%rsp), "), "{float}");
4009 }
4010
4011 #[test]
4014 fn a_call_writes_the_arguments_with_no_register_left_at_the_stack_pointer() {
4015 let six = "1, 2, 3, 4, 5, 6";
4016 let decl = "long g(long, long, long, long, long, long, long, long);\n";
4017 let text = asm(&format!("{decl}long f(void) {{ return g({six}, 7, 8); }}\n"));
4018
4019 assert!(text.contains("\tmovq\t%"), "{text}");
4020 assert!(text.contains(", (%rsp)\n"), "{text}");
4021 assert!(text.contains(", 8(%rsp)\n"), "{text}");
4022 assert!(text.contains("\tsubq\t$"), "{text}");
4024
4025 let narrow = "int g(int, int, int, int, int, int, int);\n";
4027 let text = asm(&format!("{narrow}int f(void) {{ return g({six}, 7); }}\n"));
4028 assert!(text.contains("\tmovl\t%"), "{text}");
4029 assert!(text.contains(", (%rsp)\n"), "{text}");
4030 }
4031
4032 #[test]
4035 fn a_variadic_call_counts_registers_and_not_arguments() {
4036 let nine = "1., 2., 3., 4., 5., 6., 7., 8., 9.";
4037 let decl = "int g(int, ...);\n";
4038 let text = asm(&format!("{decl}int f(void) {{ return g(0, {nine}); }}\n"));
4039
4040 assert!(text.contains("\tmovl\t$8, "), "eight registers, not nine: {text}");
4041 assert!(text.contains("\tmovsd\t%"), "{text}");
4042 assert!(text.contains(", (%rsp)\n"), "{text}");
4043 }
4044
4045 #[test]
4050 fn a_variadic_function_writes_the_argument_registers_it_was_handed_into_its_frame() {
4051 let body =
4052 "__builtin_va_list ap; __builtin_va_start(ap, n); __builtin_va_end(ap); return n;";
4053 let text = asm(&format!("int f(int n, ...) {{ {body} }}\n"));
4054
4055 let stores = |mnemonic: &str| text.matches(&format!("\t{mnemonic}\t%")).count();
4058 assert!(text.contains(", 8(%r"), "the second slot, not the first: {text}");
4059 assert!(!text.contains(", 0(%r"), "{text}");
4060 assert_eq!(stores("movaps"), 8, "every vector register: {text}");
4063 assert_eq!(stores("movsd"), 0, "and the whole of each one: {text}");
4064
4065 assert!(text.contains("\tsubq\t$"), "{text}");
4067 }
4068
4069 #[test]
4072 fn va_start_writes_the_four_fields_the_psabi_describes() {
4073 let start = "__builtin_va_list ap; __builtin_va_start(ap, d);";
4074 let params = "int a, int b, int c, double d";
4075 let text = asm(&format!("int f({params}, ...) {{ {start} return a; }}\n"));
4076
4077 assert!(text.contains(" movl $24, "), "{text}");
4081 assert!(text.contains(" movl $64, "), "{text}");
4082 assert!(text.contains(", 8(%r"), "{text}");
4086 assert!(text.contains(", 16(%r"), "{text}");
4087 let frame: u32 = text
4088 .lines()
4089 .find_map(|line| line.trim().strip_prefix("subq $")?.split(',').next()?.parse().ok())
4090 .expect("a variadic function takes a frame for the save area");
4091 let above = |line: &str| {
4092 let at: u32 = line.trim().strip_prefix("leaq ")?.split('(').next()?.parse().ok()?;
4093 Some(at > frame)
4094 };
4095 assert!(text.lines().filter_map(above).any(|it| it), "{frame}: {text}");
4096 }
4097
4098 #[test]
4101 fn va_arg_branches_on_whether_the_argument_is_still_in_the_save_area() {
4102 let read = "__builtin_va_list ap; __builtin_va_start(ap, n);";
4103 let ints = format!("int f(int n, ...) {{ {read} return __builtin_va_arg(ap, int); }}\n");
4104 let text = asm(&ints);
4105
4106 assert!(text.contains("$40, "), "{text}");
4109 assert!(text.contains(" cmpl "), "{text}");
4110 assert!(text.contains(" ja "), "{text}");
4114
4115 let arg = "__builtin_va_arg(ap, double)";
4116 let text = asm(&format!("double f(int n, ...) {{ {read} return {arg}; }}\n"));
4117 assert!(text.contains("$160, "), "the last vector slot: {text}");
4118 }
4119
4120 #[test]
4123 fn a_structure_assignment_is_a_move_for_each_word_of_it() {
4124 let decl = "struct pair { long a, b; };\n";
4125 let body = "struct pair p = *q; return p.a + p.b;";
4126 let text = asm(&format!("{decl}long f(struct pair *q) {{ {body} }}\n"));
4127
4128 assert!(!text.contains("memcpy"), "nothing calls the library: {text}");
4129 assert!(!text.contains("\tcall"), "{text}");
4130 assert!(text.matches("\tmovq\t").count() >= 4, "two words each way: {text}");
4132 }
4133
4134 #[test]
4137 fn how_wide_a_word_of_a_copy_is_follows_the_alignment() {
4138 let decl = "struct bytes { char a[8]; };\n";
4139 let body = "struct bytes p = *q; return p.a[0];";
4140 let text = asm(&format!("{decl}int f(struct bytes *q) {{ {body} }}\n"));
4141
4142 assert!(text.matches("\tmovb\t").count() >= 16, "a byte at a time: {text}");
4144 }
4145
4146 #[test]
4149 fn the_part_of_an_initialiser_that_names_nothing_is_stored_as_zero() {
4150 let decl = "struct wide { long a, b, c; };\n";
4151 let text = asm(&format!("{decl}long f(void) {{ struct wide w = {{ 7 }}; return w.c; }}\n"));
4152
4153 assert!(!text.contains("memset"), "nothing calls the library: {text}");
4154 assert!(text.contains("\tmovq\t$0, ") || text.contains("\txorl\t"), "the zero: {text}");
4159 }
4160
4161 #[test]
4164 fn a_copy_too_large_to_unroll_calls_the_runtime() {
4165 let decl = "struct huge { char a[4096]; };\n";
4166 let mut opts = options();
4167 opts.emit = EmitKind::Asm;
4168 let source = format!("{decl}void f(struct huge *p, struct huge *q) {{ *p = *q; }}\n");
4169 let result = run(&opts, &source);
4170 assert!(!result.failed(), "{:?}", result.messages);
4171 let text = result.text();
4172 assert!(text.contains("call") && text.contains("memcpy"), "{text}");
4173 assert!(text.contains("4096"), "the size travels: {text}");
4176 }
4177
4178 #[test]
4185 fn a_structure_too_large_to_unroll_is_copied_into_the_argument_area_by_the_runtime() {
4186 let decl = "struct huge { char a[4096]; };\nint take(struct huge);\n";
4187 let text = asm(&format!("{decl}int f(struct huge *p) {{ return take(*p); }}\n"));
4188
4189 let copy = text.find("call\tmemcpy").expect("the copy");
4190 let call = text.find("call\ttake").expect("the call");
4191 assert!(copy < call, "the copy comes first: {text}");
4192 assert!(text.contains("movq\t%rsp, %rdi"), "the destination: {text}");
4197 assert!(text.contains("$4096, %edx"), "the size: {text}");
4198 }
4199
4200 #[test]
4203 fn a_realigned_frame_reads_them_through_the_frame_pointer() {
4204 let six = "long a, long b, long c, long d, long e, long f";
4205 let body = "_Alignas(32) long wide[4]; wide[0] = g; return wide[0];";
4206 let text = asm(&format!("long f({six}, long g) {{ {body} }}\n"));
4207
4208 assert!(text.contains("\tandq\t$-32, %rsp"), "{text}");
4212 assert!(text.contains("\tmovq\t16(%rbp), "), "{text}");
4213 assert!(!text.contains("\tmovq\t16(%rsp), "), "{text}");
4214 }
4215
4216 #[test]
4218 fn the_target_decides_how_the_assembly_is_spelled() {
4219 let mut opts = options();
4220 opts.emit = EmitKind::Asm;
4221 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
4222 let text = run(&opts, "int f(void) { return 0; }\n").text().to_owned();
4223 assert!(text.contains("__TEXT,__text"), "{text}");
4224 assert!(text.contains("\n_f:\n"), "{text}");
4225 assert!(!text.contains(".note.GNU-stack"), "{text}");
4226 }
4227
4228 fn obj(source: &str) -> Vec<u8> {
4230 let mut opts = options();
4231 opts.emit = EmitKind::Object;
4232 let result = run(&opts, source);
4233 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4234 match result.artifact {
4235 Artifact::Object { bytes, .. } => bytes,
4236 other => panic!("expected an object, got {other:?}"),
4237 }
4238 }
4239
4240 #[test]
4246 fn a_function_goes_from_c_to_an_object_a_linker_would_take() {
4247 let bytes = obj("int add(int a, int b) { return a + b; }\n");
4248 assert_eq!(&bytes[..4], b"\x7fELF", "an object file starts by saying it is one");
4249 let text = asm("int add(int a, int b) { return a + b; }\n");
4250 assert!(
4251 text.contains("\taddl\t"),
4252 "and the listing of it is the same instructions:\n{text}"
4253 );
4254 }
4255
4256 #[test]
4258 fn a_variable_goes_from_c_to_the_section_it_belongs_in() {
4259 let text = asm("int counter = 42;\nstatic int hidden;\nconst int fixed = 7;\n");
4260 assert!(text.contains("\t.data\n\t.globl\tcounter\n"), "{text}");
4261 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
4262 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
4263 assert!(text.contains("\t.bss\n\t.p2align\t2\n"), "{text}");
4266 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
4267 assert!(!text.contains(".globl\thidden"), "{text}");
4268 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4271 }
4272
4273 #[test]
4280 fn a_bit_field_initializer_writes_every_byte_of_the_value_and_not_only_the_ones_that_are_set() {
4281 let text = asm("struct s { unsigned f : 20; } x = { 0x12300 };\n");
4282 assert!(text.contains("\t.data\n"), "there is something to write: {text}");
4283 assert!(text.contains("\nx:\n\t.ascii\t\"\\000#\\001\"\n"), "and it is the value: {text}");
4284
4285 let text = asm("struct s { unsigned a : 8; unsigned b : 8; } x = { 0, 3 };\n");
4288 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\003\"\n"), "{text}");
4289
4290 let text = asm("struct s { unsigned long long f : 40; } x = { 0x100000 };\n");
4293 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\000\\020\"\n\t.space\t5\n"), "{text}");
4294
4295 let text = asm("struct s { unsigned f : 20; } x = { 0 };\n");
4297 assert!(text.contains("\t.bss\n"), "an object of zeroes is zeroes: {text}");
4298 assert!(text.contains("\nx:\n\t.space\t4\n"), "{text}");
4299 }
4300
4301 #[test]
4303 fn a_string_literal_is_a_variable_with_a_name_no_program_could_write() {
4304 let text = asm("const char *f(void) { return \"hi\"; }\n");
4305 assert!(text.contains("\t.ascii\t\"hi\\000\"\n"), "{text}");
4306 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4307 let label = text
4308 .lines()
4309 .find(|line| line.starts_with(".Lstr"))
4310 .unwrap_or_else(|| panic!("a label for the literal in\n{text}"));
4311 assert!(!text.contains(&format!(".globl\t{}", label.trim_end_matches(':'))), "{text}");
4312 }
4313
4314 #[test]
4316 fn an_address_in_an_initializer_is_left_to_the_linker() {
4317 let source = "int counter;\nint *p = &counter;\n";
4318 let text = asm(source);
4319 assert!(text.contains("\np:\n\t.quad\tcounter\n"), "{text}");
4320 let bytes = obj(source);
4323 assert!(bytes.windows(8).any(|w| w == b"counter\0"), "the object has to name it");
4324 }
4325
4326 #[test]
4335 fn a_constant_holding_an_address_goes_in_the_section_the_loader_may_write_once() {
4336 let text = asm("static void a(void) {}\nstatic void b(void) {}\n\
4339 struct m { void (*x)(void); void (*y)(void); };\n\
4340 const struct m t = { a, b };\n");
4341 assert!(text.contains("\t.section\t.data.rel.ro.local,\"aw\",@progbits\n"), "{text}");
4342 assert!(text.contains("\nt:\n\t.quad\ta\n\t.quad\tb\n"), "{text}");
4343
4344 let text =
4347 asm("void a(void);\nstruct m { void (*x)(void); };\nconst struct m t = { a };\n");
4348 assert!(text.contains("\t.section\t.data.rel.ro,\"aw\",@progbits\n"), "{text}");
4349
4350 let text = asm("const int fixed = 7;\n");
4352 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4353 }
4354
4355 #[test]
4362 fn a_thread_local_variable_is_storage_a_thread_gets_a_copy_of_and_an_offset_into_it() {
4363 let text = asm("_Thread_local int x = 1;\nint read(void) { return x; }\n");
4364 assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
4367 assert!(text.contains("\t.type\tx, @tls_object\n"), "{text}");
4368 assert!(text.contains("x@GOTTPOFF(%rip)"), "{text}");
4371 assert!(text.contains("%fs:0"), "{text}");
4372 }
4373
4374 #[test]
4380 fn the_address_of_this_thread_s_own_storage_is_read_out_of_the_segment_register() {
4381 let text = asm("void *here(void) { return __builtin_thread_pointer(); }\n");
4382 assert!(text.contains("movq\t%fs:0, "), "{text}");
4383 assert!(!text.contains("GOTTPOFF"), "{text}");
4385 }
4386
4387 #[test]
4398 fn a_prefetch_is_one_of_four_instructions_and_the_locality_is_what_picks() {
4399 for (locality, wanted) in
4400 [(0, "prefetchnta"), (1, "prefetcht2"), (2, "prefetcht1"), (3, "prefetcht0")]
4401 {
4402 let source =
4403 format!("void warm(void *p) {{ __builtin_prefetch(p, 0, {locality}); }}\n");
4404 let text = asm(&source);
4405 assert!(text.contains(&format!("\t{wanted}\t")), "locality {locality}: {text}");
4406 }
4407 let text = asm("void warm(void *p) { __builtin_prefetch(p); }\n");
4409 assert!(text.contains("\tprefetcht0\t"), "{text}");
4410 let text = asm("void warm(void *p) { __builtin_prefetch(p, 1); }\n");
4413 assert!(text.contains("\tprefetcht0\t"), "{text}");
4414 assert!(!text.contains("prefetchw"), "{text}");
4415 }
4416
4417 #[test]
4420 fn an_aarch64_prefetch_is_a_prfm_that_says_the_locality_and_whether_it_writes() {
4421 let mut opts = options();
4422 opts.emit = EmitKind::Asm;
4423 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
4424 for (write, kind) in [(0, "pld"), (1, "pst")] {
4425 for (locality, wanted) in [(0, "l1strm"), (1, "l3keep"), (2, "l2keep"), (3, "l1keep")] {
4426 let source = format!(
4427 "void warm(void *p) {{ __builtin_prefetch(p, {write}, {locality}); }}\n"
4428 );
4429 let result = run(&opts, &source);
4430 assert_eq!(result.messages, Vec::<String>::new(), "{source}");
4431 let text = result.text();
4432 assert!(text.contains("prfm"), "{source}{text}");
4433 assert!(text.contains(&format!("{kind}{wanted}, [x0]")), "{source}{text}");
4434 }
4435 }
4436 }
4437
4438 #[test]
4449 fn a_trap_is_the_instruction_the_machine_has_no_meaning_for() {
4450 let text = asm("void stop(void) { __builtin_trap(); }\n");
4451 assert!(text.contains("\tud2\n"), "{text}");
4452 assert!(!text.contains("\tcall"), "a stop is not a call to anything: {text}");
4453
4454 let text = asm("int stop(int a) { __builtin_trap(); return a + 1; }\n");
4455 assert!(text.contains("\tud2\n"), "{text}");
4456 assert!(text.contains("\taddl\t"), "the block goes on after a stop: {text}");
4457 }
4458
4459 #[test]
4471 fn assume_aligned_is_its_first_argument_and_keeps_the_rest() {
4472 let text = asm("void *aligned(char *p) { return __builtin_assume_aligned(p, 16); }\n");
4473 assert!(!text.contains("assume_aligned"), "{text}");
4474 assert!(!text.contains("\tcall"), "nothing is called for an alignment fact: {text}");
4475
4476 let source = "unsigned long width(void);\n\
4477 void *aligned(char *p) { return __builtin_assume_aligned(p, width()); }\n";
4478 let text = asm(source);
4479 assert!(!text.contains("assume_aligned"), "{text}");
4480 assert!(text.contains("width"), "the argument that is not the answer still runs: {text}");
4481 }
4482
4483 #[test]
4493 fn the_frame_address_is_the_frame_pointer_after_walking_that_many_links() {
4494 let text = asm("void *here(void) { return __builtin_frame_address(0); }\n");
4495 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
4496 assert!(text.contains("movq\t%rbp, %rax"), "{text}");
4497 assert!(!text.contains("\tcall"), "a frame address is not a call to anything: {text}");
4498
4499 let walk = |depth: u32| {
4500 let source = format!("void *up(void) {{ return __builtin_frame_address({depth}); }}\n");
4501 asm(&source).matches("movq\t(%r").count()
4502 };
4503 assert_eq!(walk(1), 1, "one link is one load");
4504 assert_eq!(walk(3), 3, "three links are three loads");
4505 }
4506
4507 #[test]
4517 fn the_return_address_is_one_word_above_the_frame_the_walk_ended_at() {
4518 let text = asm("void *back(void) { return __builtin_return_address(0); }\n");
4519 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
4520 assert!(text.contains("movq\t8(%rbp), %rax"), "{text}");
4521 assert!(!text.contains("\tcall"), "a return address is not a call to anything: {text}");
4522
4523 let text = asm("void *back(void) { return __builtin_return_address(2); }\n");
4524 assert_eq!(text.matches("movq\t(%r").count(), 2, "two links are two loads: {text}");
4525 assert!(text.contains("movq\t8(%r"), "and the answer is above the last of them: {text}");
4526 }
4527
4528 #[test]
4539 fn a_depth_that_is_not_a_small_constant_is_refused() {
4540 let mut opts = options();
4541 opts.emit = EmitKind::Ir;
4542 for source in [
4543 "void *up(int n) { return __builtin_return_address(n); }\n",
4544 "void *up(void) { return __builtin_frame_address(1000); }\n",
4545 ] {
4546 let messages = run(&opts, source).messages;
4547 let named = messages.iter().any(|m| m.contains("E0705"));
4548 assert!(named, "expected a refusal in {messages:?}");
4549 }
4550 }
4551
4552 #[test]
4564 fn an_alloca_takes_the_bytes_off_the_stack_pointer_and_answers_where_they_are() {
4565 let text =
4566 asm("void use(void *p); void f(unsigned long n) { use(__builtin_alloca(n)); }\n");
4567 assert!(text.contains("andq\t$-16"), "the size is rounded up to sixteen: {text}");
4568 assert!(text.contains("subq\t%rdi, %rsp"), "and taken off the stack pointer: {text}");
4569 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
4570
4571 let plain = concat!(
4574 "extern void *alloca(__SIZE_TYPE__);\n",
4575 "void use(void *p);\n",
4576 "void f(unsigned long n) { use(alloca(n)); }\n",
4577 );
4578 let text = asm(plain);
4579 assert!(text.contains("subq\t%rdi, %rsp"), "the plain name is the same bytes: {text}");
4580 assert_eq!(text.matches("\tcall").count(), 1, "and is not a call either: {text}");
4581
4582 let own = concat!(
4585 "static void *alloca(unsigned long n) { return 0; }\n",
4586 "void *f(unsigned long n) { return alloca(n); }\n",
4587 );
4588 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
4589 }
4590
4591 #[test]
4606 fn a_builtin_the_program_never_declared_is_the_builtin_rather_than_the_one_c89_wrote_down() {
4607 let mut opts = options();
4611 opts.permissive = true;
4612 let undeclared = "void use(void *p);
4613void f(unsigned long n) { use(alloca(n)); }
4614";
4615 assert_eq!(
4616 run(&opts, undeclared).messages,
4617 [
4618 "/main.c:2:31: warning: implicit declaration of function 'alloca' [E0521]",
4619 "/main.c:2:31: warning: incompatible implicit declaration of built-in function \
4620 'alloca' [E0713]",
4621 ]
4622 );
4623
4624 opts.emit = EmitKind::Asm;
4625 let text = run(&opts, undeclared).text().to_owned();
4626 assert!(text.contains("subq\t%rdi, %rsp"), "the bytes come off the stack: {text}");
4627 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
4628
4629 let string = "unsigned long f(void) { return strlen(\"abc\"); }\n";
4632 let text = run(&opts, string).text().to_owned();
4633 assert!(text.contains("call\tstrlen"), "strlen is still a call: {text}");
4634
4635 let own = concat!(
4638 "static void *alloca(unsigned long n) { return 0; }\n",
4639 "void *f(unsigned long n) { return alloca(n); }\n",
4640 );
4641 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
4642 }
4643
4644 #[test]
4654 fn the_bytes_an_alloca_took_are_still_there_at_the_end_of_the_block_that_took_them() {
4655 let inner = "{ use(__builtin_alloca(n)); }";
4656 for body in [inner.to_owned(), format!("int a[n]; {inner} use(a);")] {
4657 let source = format!("void use(void *p);\nvoid f(unsigned long n) {{ {body} }}\n");
4658 let text = asm(&source);
4659 for line in text.lines().filter(|line| line.trim_end().ends_with(", %rsp")) {
4663 let taking = line.contains("subq");
4664 let leaving = line.contains("%rbp");
4665 assert!(taking || leaving, "nothing puts the stack back: {line} in {text}");
4666 }
4667 }
4668 }
4669
4670 #[test]
4672 fn the_object_and_the_listing_are_two_spellings_of_one_compilation() {
4673 let source = "int callee(void); int g(void) { return callee(); }\n";
4677 let bytes = obj(source);
4678 assert!(
4679 bytes.windows(7).any(|w| w == b"callee\0"),
4680 "the object has to name the callee for the linker to find it"
4681 );
4682 let text = asm(source);
4683 assert!(text.contains("\tcall\tcallee\n"), "{text}");
4684 }
4685
4686 #[test]
4692 fn compiling_for_an_executable_produces_an_object_and_not_a_dump() {
4693 let mut opts = options();
4694 opts.emit = EmitKind::Executable;
4696 let result = run(&opts, "int main(void) { return 0; }\n");
4697 assert_eq!(result.messages, Vec::<String>::new());
4698 match result.artifact {
4699 Artifact::Object { bytes, .. } => assert_eq!(&bytes[..4], b"\x7fELF"),
4700 other => panic!("expected an object, got {other:?}"),
4701 }
4702 }
4703
4704 #[test]
4706 fn a_platform_with_no_object_writer_is_said_so_rather_than_written_as_elf() {
4707 let mut opts = options();
4708 opts.emit = EmitKind::Object;
4709 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
4710 let result = run(&opts, "int f(void) { return 0; }\n");
4711 assert!(result.failed(), "an object nobody can read is worse than a message");
4712 assert!(
4713 result.messages.iter().any(|m| m.contains("no object writer")),
4714 "{:?}",
4715 result.messages
4716 );
4717 }
4718
4719 fn ir(source: &str) -> String {
4721 let mut opts = options();
4722 opts.emit = EmitKind::Ir;
4723 let result = run(&opts, source);
4724 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4725 result.text().to_owned()
4726 }
4727
4728 fn errors(source: &str) -> Vec<String> {
4730 let mut opts = options();
4731 opts.emit = EmitKind::Ir;
4732 let result = run(&opts, source);
4733 assert!(result.failed(), "expected this to be refused:\n{source}");
4734 result.messages
4735 }
4736
4737 fn body(source: &str) -> String {
4739 let text = ir(source);
4740 let (_, rest) = text.split_once("{\n").expect("a function definition");
4741 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
4742 body.to_owned()
4743 }
4744
4745 #[test]
4753 fn gnu89_inline_is_what_decides_whether_a_bare_inline_definition_reaches_the_module() {
4754 let source = "inline int f(int x) { return x + 1; }\n";
4755 let with = |flag: bool| {
4756 let mut opts = options();
4757 opts.emit = EmitKind::Ir;
4758 opts.gnu89_inline = flag;
4759 let result = run(&opts, source);
4760 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
4761 result.text().to_owned()
4762 };
4763
4764 assert!(!with(false).contains("block0"), "no body: {}", with(false));
4767
4768 assert!(with(true).contains("block0"), "a body: {}", with(true));
4771 }
4772
4773 #[test]
4780 fn an_access_through_a_type_names_the_type_it_went_through() {
4781 let source = "\
4782struct s { int a; float b; };\n\
4783union u { int i; float f; };\n\
4784int scalar(int *p) { return *p; }\n\
4785float member(struct s *p) { p->a = 1; return p->b; }\n\
4786int element(int *a, long i) { return a[i]; }\n\
4787float through_a_union(union u *p) { p->i = 1; return p->f; }\n";
4788 let text = ir(source);
4789 assert!(text.contains(r#"!0 = tbaa "char""#), "the root: {text}");
4790 assert!(text.contains(r#"tbaa "int", parent !0"#), "int under it: {text}");
4791 assert!(text.contains(r#"tbaa "float", parent !0"#), "float under it: {text}");
4792 let named = text.lines().filter(|line| line.contains(", tbaa !")).count();
4795 assert_eq!(named, 6, "six accesses: {text}");
4796 }
4797
4798 #[test]
4805 fn turning_strict_aliasing_off_leaves_the_type_off_every_access() {
4806 let source = "int punned(float *f, int *i) { *i = 1; *f = 2.0f; return *i; }\n";
4807 let mut opts = options();
4808 opts.emit = EmitKind::Ir;
4809 opts.strict_aliasing = false;
4810 let result = run(&opts, source);
4811 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
4812 let text = result.text().to_owned();
4813 assert!(!text.contains("tbaa"), "not even the root: {text}");
4814 }
4815
4816 #[test]
4822 fn instrumenting_functions_calls_the_hooks_around_every_body_but_the_hooks() {
4823 let source = concat!(
4824 "#define NOCHK __attribute__((no_instrument_function))\n",
4825 "void __cyg_profile_func_enter(void *, void *) NOCHK;\n",
4826 "void __cyg_profile_func_exit(void *, void *) NOCHK;\n",
4827 "int calls;\n",
4828 "int pick(int x) { if (x) return 1; return 2; }\n",
4829 "void quiet(void) NOCHK;\n",
4830 "void quiet(void) { calls++; }\n",
4831 "void __cyg_profile_func_enter(void *fn, void *site) { calls++; }\n",
4832 "void __cyg_profile_func_exit(void *fn, void *site) { calls--; }\n",
4833 );
4834 let mut opts = options();
4835 opts.emit = EmitKind::Ir;
4836 opts.instrument_functions = true;
4837 let result = run(&opts, source);
4838 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
4839 let text = result.text().to_owned();
4840 let body = |name: &str| -> String {
4841 let open = format!("func @{name}(");
4842 let start = text.find(&open).unwrap_or_else(|| panic!("no {name}: {text}"));
4843 let rest = &text[start..];
4844 rest[..rest.find("\n}").unwrap_or(rest.len())].to_owned()
4845 };
4846 let pick = body("pick");
4847 assert_eq!(pick.matches("call @__cyg_profile_func_enter(").count(), 1, "{pick}");
4848 assert_eq!(pick.matches("call @__cyg_profile_func_exit(").count(), 2, "{pick}");
4849 assert!(pick.contains("return_address"), "{pick}");
4850 assert!(pick.contains("global_addr @pick"), "{pick}");
4851 for quiet in ["quiet", "__cyg_profile_func_enter", "__cyg_profile_func_exit"] {
4852 assert!(!body(quiet).contains("call "), "{quiet} is left alone: {text}");
4853 }
4854
4855 opts.instrument_functions = false;
4856 let result = run(&opts, source);
4857 assert!(!result.text().contains("call @__cyg_profile"), "off unless asked for");
4858 }
4859
4860 #[test]
4865 fn a_call_an_unwind_would_leave_a_cleanup_behind_is_refused_under_exceptions() {
4866 let source = concat!(
4867 "void done(int *p);\n",
4868 "void work(void);\n",
4869 "void calls(void) { int x __attribute__((cleanup(done))) = 1; work(); }\n",
4870 "int quiet(int y) { int x __attribute__((cleanup(done))) = y; return x + 1; }\n",
4871 "void after(void) { { int x __attribute__((cleanup(done))) = 1; } work(); }\n",
4872 );
4873 let mut opts = options();
4874 opts.emit = EmitKind::Ir;
4875 let result = run(&opts, source);
4876 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
4877
4878 opts.exceptions = true;
4879 let result = run(&opts, source);
4880 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
4881 assert!(result.messages[0].contains("landing pad"), "{:?}", result.messages);
4882 assert!(result.messages[0].contains(":3:"), "the call in calls: {:?}", result.messages);
4883 }
4884
4885 #[test]
4893 fn a_bare_return_from_a_function_that_promised_a_value_gives_back_a_zero() {
4894 let mut opts = options();
4895 opts.emit = EmitKind::Ir;
4896 opts.std = Std::C89;
4897 let compiled = |source: &str| {
4898 let result = run(&opts, source);
4899 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
4900 result.text().to_owned()
4901 };
4902
4903 let text = compiled("int f(int x) { if (x) return; return 3; }\n");
4904 assert!(text.contains("iconst.i32 0\n return"), "zero goes back: {text}");
4905 assert!(!text.contains("unreachable"), "the branch that reached it is kept: {text}");
4906
4907 let text = compiled("double f(int x) { if (x) return; return 1.0; }\n");
4909 assert!(text.contains("fconst.f64 0x0\n return"), "a float zero goes back: {text}");
4910 }
4911
4912 #[test]
4920 fn a_call_to_a_name_nothing_declared_declares_it_as_c89_said_to() {
4921 let mut opts = options();
4922 opts.emit = EmitKind::Ir;
4923 opts.std = Std::C89;
4924 let compiled = |source: &str| {
4925 let result = run(&opts, source);
4926 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
4927 result.text().to_owned()
4928 };
4929
4930 let text = compiled("int f(void) { return g(); }\n");
4932 assert!(text.contains("call @g"), "the call is to the name that was written: {text}");
4933 assert!(text.contains("i32"), "and it gives back an int: {text}");
4934
4935 let text = compiled("int f(char c) { return g(c); }\n");
4938 assert!(text.contains("sext.i32"), "the argument is promoted: {text}");
4939
4940 let mut opts = options();
4943 opts.std = Std::C89;
4944 let said = run(&opts, "int f(void) { return h; }\n").messages.join("\n");
4945 assert!(said.contains("'h' undeclared"), "not a call, so not declared: {said}");
4946 }
4947
4948 #[test]
4958 fn a_name_called_before_it_is_defined_still_gets_its_definition() {
4959 let mut opts = options();
4960 opts.emit = EmitKind::Ir;
4961 opts.std = Std::C89;
4962 let text = run(&opts, "int f(void) { return dummy(); }\ndummy () { return 7; }\n")
4963 .text()
4964 .to_owned();
4965 assert!(text.contains("func @f()"), "the caller is there: {text}");
4966 assert!(text.contains("func @dummy"), "and so is what it calls: {text}");
4967 assert!(text.contains("iconst.i32 7"), "with the body it was given: {text}");
4968 }
4969
4970 #[test]
4978 fn an_old_style_parameter_is_converted_from_what_the_call_promoted_it_to() {
4979 let mut opts = options();
4980 opts.emit = EmitKind::Ir;
4981 opts.std = Std::C89;
4982 let compiled = |source: &str| run(&opts, source).text().to_owned();
4983
4984 let text = compiled("f (c) unsigned char c; { return c; }\n");
4985 assert!(text.contains("func @f(i32"), "an int arrives: {text}");
4986 assert!(text.contains("trunc.i8"), "and is cut down to what was declared: {text}");
4987 assert!(text.contains("zext.i32"), "then read back unsigned: {text}");
4988
4989 let text = compiled("f (s) short s; { return s; }\n");
4991 assert!(text.contains("trunc.i16"), "cut down: {text}");
4992 assert!(text.contains("sext.i32"), "and read back signed: {text}");
4993
4994 let text = compiled("f (x) float x; { return x * 2; }\n");
4997 assert!(text.contains("func @f(f64"), "a double arrives: {text}");
4998 assert!(text.contains("fptrunc.f32"), "and is narrowed to the float: {text}");
4999
5000 let text = compiled("int f(unsigned char c) { return c; }\n");
5003 assert!(text.contains("func @f(i8)"), "the declared type arrives: {text}");
5004 assert!(!text.contains("trunc"), "so there is nothing to cut down: {text}");
5005 }
5006
5007 #[test]
5016 fn the_rules_gcc_promoted_are_decided_by_the_dialect_and_by_fpermissive() {
5017 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
5019 let cases = [
5020 ("static counted;\n", ["", "error", "warning", "error"]),
5021 ("int f(void) { return g(); }\n", ["", "error", "warning", "error"]),
5022 ("int f(x) { return x; }\n", ["", "error", "warning", "error"]),
5023 ("int *p;\nvoid h(void) { p = 1; }\n", ["warning", "error", "warning", "error"]),
5024 (
5025 "char *q;\nint *r;\nvoid k(void) { r = q; }\n",
5026 ["warning", "error", "warning", "error"],
5027 ),
5028 ("int f(void) { return; }\n", ["", "error", "warning", "error"]),
5029 ("void g(void) { return 1; }\n", ["warning", "error", "warning", "error"]),
5030 ];
5031
5032 for (source, wanted) in cases {
5033 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
5034 let mut opts = options();
5035 opts.std = std;
5036 opts.permissive = permissive;
5037 let said = run(&opts, source).messages.join("\n");
5038 let severity = if said.contains(": error: ") {
5039 "error"
5040 } else if said.contains(": warning: ") {
5041 "warning"
5042 } else {
5043 ""
5044 };
5045 let how = if permissive { " -fpermissive" } else { "" };
5046 assert_eq!(
5047 severity,
5048 wanted,
5049 "under -std={}{how}, {source} was answered with `{said}`",
5050 std.as_str()
5051 );
5052 if wanted.is_empty() {
5053 assert!(said.is_empty(), "nothing to say, but said `{said}`");
5054 }
5055 }
5056 }
5057 }
5058
5059 #[test]
5068 fn the_three_variadic_builtins_answer_a_bad_list_the_way_a_call_answers_a_bad_argument() {
5069 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
5070 let cases = [
5071 (
5072 "int f(int n, ...) { char *p; return __builtin_va_arg(p, int); }\n",
5073 "first argument to 'va_arg' not of type 'va_list'",
5074 ["error", "error", "error", "error"],
5075 ),
5076 (
5077 "void f(int n, ...) { char *p; __builtin_va_start(p, n); }\n",
5078 "passing argument 1 of '__builtin_va_start' from incompatible pointer type",
5079 ["warning", "error", "warning", "error"],
5080 ),
5081 (
5082 "void f(int n, ...) { int x; __builtin_va_end(x); }\n",
5083 "passing argument 1 of '__builtin_va_end' makes pointer from integer without a \
5084 cast",
5085 ["warning", "error", "warning", "error"],
5086 ),
5087 (
5088 "void f(int n, ...) { __builtin_va_list a; char *p; __builtin_va_copy(a, p); }\n",
5089 "passing argument 2 of '__builtin_va_copy' from incompatible pointer type",
5090 ["warning", "error", "warning", "error"],
5091 ),
5092 ];
5093
5094 for (source, message, wanted) in cases {
5095 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
5096 let mut opts = options();
5097 opts.std = std;
5098 opts.permissive = permissive;
5099 let said = run(&opts, source).messages.join("\n");
5100 let how = if permissive { " -fpermissive" } else { "" };
5101 assert!(
5102 said.contains(&format!(": {wanted}: {message}")),
5103 "under -std={}{how}, {source} was answered with `{said}`",
5104 std.as_str()
5105 );
5106 }
5107 }
5108 }
5109
5110 fn safe_ir(tier: rucc_session::Safety, source: &str) -> String {
5112 let mut opts = options();
5113 opts.emit = EmitKind::Ir;
5114 opts.safety = tier;
5115 let result = run(&opts, source);
5116 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5117 result.text().to_owned()
5118 }
5119
5120 const READS_THROUGH_A_POINTER: &str = "int read(int *p) { return p[1]; }\n";
5121
5122 fn padded_ir(padding: Padding, source: &str) -> String {
5124 let mut opts = options();
5125 opts.emit = EmitKind::Ir;
5126 opts.safety = rucc_session::Safety::Detect;
5127 opts.padding = padding;
5128 let result = run(&opts, source);
5129 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5130 result.text().to_owned()
5131 }
5132
5133 const FILLS_A_RECORD_A_MEMBER_AT_A_TIME: &str = "struct padded { char tag; int value; };\n\
5134 void fill(struct padded *p) { p->tag = 1; p->value = 2; }\n";
5135
5136 #[test]
5137 fn a_record_filled_a_member_at_a_time_comes_out_whole_when_padding_does_not_participate() {
5138 let text = padded_ir(Padding::Ignored, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
5142 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
5143 }
5144
5145 #[test]
5146 fn a_store_says_only_what_it_wrote_when_padding_does_participate() {
5147 let text = padded_ir(Padding::Tracked, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
5150 assert!(!text.contains("owns"), "{text}");
5151 }
5152
5153 #[test]
5154 fn a_member_of_a_union_owns_nothing_after_it() {
5155 let text = padded_ir(
5159 Padding::Ignored,
5160 "union u { char tag; long wide; };\nvoid fill(union u *p) { p->tag = 1; }\n",
5161 );
5162 assert!(!text.contains("owns"), "{text}");
5163 }
5164
5165 #[test]
5166 fn an_inner_records_trailing_padding_reaches_the_outer_records() {
5167 let text = padded_ir(
5172 Padding::Ignored,
5173 "struct inner { char c; };\n\
5174 struct outer { struct inner in; int x; };\n\
5175 void fill(struct outer *p) { p->in.c = 1; p->x = 2; }\n",
5176 );
5177 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
5178 }
5179
5180 #[test]
5181 fn a_build_that_did_not_ask_for_the_monitor_is_compiled_the_way_it_always_was() {
5182 let text = ir(READS_THROUGH_A_POINTER);
5186 assert!(!text.contains("check_"), "{text}");
5187 assert!(!text.contains("cap_of"), "{text}");
5188 }
5189
5190 #[test]
5191 fn asking_for_a_tier_puts_the_checks_in_before_the_optimizer_sees_them() {
5192 let text = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5193 assert!(text.contains("cap_of"), "{text}");
5194 assert!(text.contains("check_bounds"), "{text}");
5195 assert!(text.contains("check_live"), "{text}");
5196 assert!(text.contains("check_deriv"), "{text}");
5198 assert!(text.contains("check_type"), "{text}");
5200 }
5201
5202 #[test]
5203 fn the_three_tiers_that_are_not_off_all_check_the_same_accesses_so_far() {
5204 let detect = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5208 for tier in [rucc_session::Safety::Enforce, rucc_session::Safety::Kernel] {
5209 assert_eq!(safe_ir(tier, READS_THROUGH_A_POINTER), detect, "{tier}");
5210 }
5211 }
5212
5213 fn summary(tier: rucc_session::Safety, source: &str) -> String {
5215 let mut opts = options();
5216 opts.emit = EmitKind::SafetySummary;
5217 opts.safety = tier;
5218 let result = run(&opts, source);
5219 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5220 result.text().to_owned()
5221 }
5222
5223 #[test]
5224 fn the_summary_counts_the_checks_that_went_in_and_the_ones_still_standing() {
5225 let text = summary(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5226 assert!(text.contains("\"tier\": \"detect\""), "{text}");
5227 assert!(
5229 text.contains("\"bounds\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"),
5230 "{text}"
5231 );
5232 assert!(
5233 text.contains(
5234 "\"derivation\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"
5235 ),
5236 "{text}"
5237 );
5238 }
5239
5240 #[test]
5241 fn a_build_without_the_monitor_summarises_as_a_build_with_no_checks_in_it() {
5242 let text = summary(rucc_session::Safety::Off, READS_THROUGH_A_POINTER);
5246 assert!(text.contains("\"tier\": \"off\""), "{text}");
5247 assert!(
5248 text.contains("\"bounds\": { \"emitted\": 0, \"remaining\": 0, \"discharged\": 0 }"),
5249 "{text}"
5250 );
5251 }
5252
5253 #[test]
5254 fn a_call_the_boundary_models_is_counted_apart_from_one_it_does_not() {
5255 let text = summary(
5256 rucc_session::Safety::Detect,
5257 "void *memcpy(void *, const void *, unsigned long);\n\
5258 int puts(const char *);\n\
5259 void f(char *d, char *s) { memcpy(d, s, 4); puts(d); }\n",
5260 );
5261 assert!(text.contains("\"interposed\": 1"), "{text}");
5262 assert!(text.contains("\"puts\""), "{text}");
5263 assert!(!text.contains("__rucc_wrap_memcpy\""), "{text}");
5267 }
5268
5269 #[test]
5270 fn an_address_taken_of_a_library_function_is_counted_the_way_a_call_to_one_is() {
5271 let text = summary(
5276 rucc_session::Safety::Detect,
5277 "void *memcpy(void *, const void *, unsigned long);\n\
5278 int puts(const char *);\n\
5279 void *table[2] = { (void *)memcpy, (void *)puts };\n\
5280 void *f(int i) { return table[i]; }\n",
5281 );
5282 assert!(text.contains("\"interposed\": 1"), "{text}");
5283 assert!(text.contains("\"puts\""), "{text}");
5284 assert!(!text.contains("\"memcpy\""), "{text}");
5285 }
5286
5287 #[test]
5288 fn the_two_directions_a_pointer_crosses_the_boundary_are_counted_apart() {
5289 let text = summary(
5293 rucc_session::Safety::Detect,
5294 "void *notes_open(void);\n\
5295 char *f(char *p) { char *q = notes_open(); return q ? q : p; }\n",
5296 );
5297 assert!(text.contains("\"crossings\": { \"entered\": 1, \"returned\": 1 }"), "{text}");
5298 assert!(text.contains("\"notes_open\""), "{text}");
5299 }
5300
5301 #[test]
5302 fn a_static_function_nobody_takes_the_address_of_is_not_a_crossing() {
5303 let text = summary(
5306 rucc_session::Safety::Detect,
5307 "static int len(const char *p) { return p ? 1 : 0; }\n\
5308 int f(void) { return len(\"x\"); }\n",
5309 );
5310 assert!(text.contains("\"crossings\": { \"entered\": 0, \"returned\": 0 }"), "{text}");
5311 }
5312
5313 fn granules(source: &str) -> String {
5315 let mut opts = options();
5316 opts.emit = EmitKind::TypeGranules;
5317 let result = run(&opts, source);
5318 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5319 result.text().to_owned()
5320 }
5321
5322 #[test]
5323 fn the_granule_report_names_every_record_and_both_keyings() {
5324 let text = granules(
5325 "struct hot { char *p; int a; int b; };\n\
5326 int f(struct hot *h) { return h->a; }\n",
5327 );
5328 assert!(text.contains("struct hot"), "{text}");
5329 assert!(text.contains("every type distinct"), "{text}");
5332 assert!(text.contains("every pointer one type"), "{text}");
5333 assert!(text.contains("budget"), "{text}");
5334 }
5335
5336 #[test]
5337 fn a_record_nothing_uses_is_still_measured() {
5338 let text = granules("struct unused { long a; double b; };\nint f(void) { return 0; }\n");
5341 assert!(text.contains("struct unused"), "{text}");
5342 }
5343
5344 #[test]
5345 fn the_granule_report_stops_before_anything_is_lowered() {
5346 let text = granules(
5350 "struct wide { long double d; };\n\
5351 long double f(long double x) { return x * x; }\n",
5352 );
5353 assert!(text.contains("struct wide"), "{text}");
5354 }
5355
5356 #[test]
5357 fn a_witness_reaches_the_assembler_as_a_call_to_the_runtime() {
5358 let text = safe_asm(rucc_session::Safety::Detect, "char *f(char *p) { return p; }\n");
5361 assert!(text.contains("\tcall\t__rucc_cap_witness\n"), "{text}");
5362 }
5363
5364 #[test]
5365 fn a_pointer_turned_into_an_integer_is_on_the_trust_set() {
5366 let text = summary(
5367 rucc_session::Safety::Detect,
5368 "unsigned long f(int *p) { return (unsigned long) p; }\n",
5369 );
5370 assert!(text.contains("\"exposed\": 1"), "{text}");
5371 }
5372
5373 fn safe_asm(tier: rucc_session::Safety, source: &str) -> String {
5375 let mut opts = options();
5376 opts.emit = EmitKind::Asm;
5377 opts.safety = tier;
5378 let result = run(&opts, source);
5379 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5380 result.text().to_owned()
5381 }
5382
5383 #[test]
5384 fn a_check_reaches_the_assembler_as_a_call_to_the_runtime() {
5385 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5386 assert!(text.contains("\tcall\t__rucc_check_bounds\n"), "{text}");
5387 assert!(text.contains("\tcall\t__rucc_check_live\n"), "{text}");
5388 assert!(text.contains("\tcall\t__rucc_check_deriv\n"), "{text}");
5389 assert!(text.contains("\tcall\t__rucc_check_typed_init\n"), "{text}");
5392 }
5393
5394 #[test]
5395 fn every_check_that_reached_the_assembler_has_a_row_describing_it() {
5396 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5402 let section = format!("\t.section\t{},", rucc_safety::SECTION);
5403 assert_eq!(text.matches(§ion).count(), 4, "{text}");
5404 for index in 0..4 {
5405 let name = format!("__rucc_safety_desc_{index}");
5406 assert!(text.contains(&format!("{name}:\n")), "{text}");
5409 assert!(text.contains(&format!("{name}(%rip)")), "{text}");
5410 }
5411 assert!(!text.contains("__rucc_safety_desc_4"), "{text}");
5412 }
5413
5414 #[test]
5422 fn builtin_constant_p_is_folded_where_it_is_written_rather_than_called() {
5423 let text = ir(concat!(
5424 "int g;\n",
5425 "int a = __builtin_constant_p(1);\n",
5426 "int b = __builtin_constant_p(g);\n",
5427 "int c = __builtin_constant_p(\"abc\");\n",
5428 "int d = __builtin_constant_p(&g);\n",
5429 "int e = __builtin_constant_p(1.5);\n",
5430 "int h = __builtin_choose_expr(__builtin_constant_p(3), 11, 22);\n",
5431 ));
5432 assert!(text.contains("global @a : i32 = 1,"), "{text}");
5433 assert!(text.contains("global @b : i32 = 0,"), "{text}");
5434 assert!(text.contains("global @c : i32 = 1,"), "{text}");
5435 assert!(text.contains("global @d : i32 = 0,"), "{text}");
5436 assert!(text.contains("global @e : i32 = 1,"), "{text}");
5437 assert!(text.contains("global @h : i32 = 11,"), "{text}");
5438 assert!(!text.contains("__builtin_constant_p"), "it is not a call to anything:\n{text}");
5439
5440 let text = body("int f(void) { int i = 0; __builtin_constant_p(i++); return i; }\n");
5444 assert_eq!(text, "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 0\n return %0\n");
5445 }
5446
5447 #[test]
5456 fn a_call_to_a_library_builtin_reaches_the_library_function() {
5457 let text = body("void f(void) { __builtin_abort(); }\n");
5458 assert_eq!(text, "block0:\n call @abort() : ()\n return\n");
5459
5460 let text = ir("int f(const char *s) { return __builtin_puts(s) + __builtin_strlen(s); }\n");
5463 assert!(text.contains("call @puts(%0) : (ptr) -> i32"), "{text}");
5464 assert!(text.contains("call @strlen(%0) : (ptr) -> i64"), "{text}");
5465 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
5466 }
5467
5468 #[test]
5481 fn a_chk_builtin_reaches_the_checking_function_and_keeps_the_size() {
5482 let text = ir(concat!(
5483 "char d[8];\n",
5484 "void f(const char *s, unsigned long n) {\n",
5485 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
5486 " __builtin___strcpy_chk(d, s, __builtin_object_size(d, 1));\n",
5487 " __builtin___memset_chk(d, 0, n, 8);\n",
5488 "}\n",
5489 ));
5490 assert!(text.contains("call @__memcpy_chk("), "{text}");
5491 assert!(text.contains("call @__strcpy_chk("), "{text}");
5492 assert!(text.contains("call @__memset_chk("), "{text}");
5493 assert!(text.contains("iconst.i64 8"), "the object size reaches the call: {text}");
5494 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
5495 }
5496
5497 #[test]
5505 fn a_checking_call_whose_size_says_nothing_is_known_is_the_plain_library_call() {
5506 let text = ir(concat!(
5507 "extern char *p;\n",
5508 "char d[8];\n",
5509 "void f(const char *s, unsigned long n) {\n",
5510 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
5511 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
5512 " __builtin___strcpy_chk(p, s, __builtin_object_size(p, 0));\n",
5513 " __builtin___stpncpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
5514 " __builtin___sprintf_chk(p, 1, __builtin_object_size(p, 0), s);\n",
5515 "}\n",
5516 ));
5517
5518 assert!(
5520 text.contains("call @__memcpy_chk(%2, %0, %1, %3) : (ptr, ptr, i64, i64)"),
5521 "{text}"
5522 );
5523
5524 assert!(text.contains("call @memcpy(%6, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
5527 assert!(text.contains("call @strcpy(%10, %0) : (ptr, ptr) -> ptr"), "{text}");
5528 assert!(text.contains("call @stpncpy(%14, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
5529
5530 assert!(text.contains("call @__sprintf_chk("), "{text}");
5533
5534 let asm = asm(concat!(
5537 "void f(char *p, const char *s, unsigned long n) {\n",
5538 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
5539 "}\n",
5540 ));
5541 assert!(asm.contains("call\tmemcpy"), "{asm}");
5542 assert!(!asm.contains("$-1"), "the size that went away leaves no instruction:\n{asm}");
5543 }
5544
5545 #[test]
5553 fn the_v_spellings_of_the_chk_family_take_the_list_a_va_list_parameter_holds() {
5554 let text = ir(concat!(
5555 "char d[64];\n",
5556 "int f(const char *fmt, ...) {\n",
5557 " __builtin_va_list ap;\n",
5558 " __builtin_va_start(ap, fmt);\n",
5559 " int n = __builtin___vsprintf_chk(d, 1, __builtin_object_size(d, 0), fmt, ap);\n",
5560 " __builtin_va_end(ap);\n",
5561 " return n;\n",
5562 "}\n",
5563 ));
5564 assert!(text.contains("call @__vsprintf_chk("), "{text}");
5565 assert!(text.contains("iconst.i64 64"), "the object size reaches the call: {text}");
5566 }
5567
5568 #[test]
5579 fn the_absolute_value_family_is_the_magnitude_and_not_a_call() {
5580 let text = body(concat!(
5581 "long long llabs(long long);\n",
5582 "long long f(long long x) { return llabs(x); }\n",
5583 ));
5584 assert!(text.contains("%1 = iconst.i64 63"), "{text}");
5585 assert!(text.contains("%2 = ashr %0, %1"), "{text}");
5586 assert!(text.contains("%3 = xor %0, %2"), "{text}");
5587 assert!(text.contains("%4 = sub %3, %2"), "{text}");
5588 assert!(!text.contains("call"), "the call does not happen:\n{text}");
5589
5590 let text = body("int abs(int);\nint f(int x) { return abs(x); }\n");
5593 assert!(text.contains("iconst.i32 31"), "{text}");
5594 let text = body("long labs(long);\nlong f(long x) { return labs(x); }\n");
5595 assert!(text.contains("iconst.i64 63"), "{text}");
5596
5597 let text = body("long long f(long long x) { return __builtin_llabs(x); }\n");
5600 assert!(!text.contains("call"), "{text}");
5601
5602 let text = ir(concat!(
5604 "long long llabs(long long b);\n",
5605 "long long g(long long x) { return llabs(x); }\n",
5606 "long long llabs(long long b) { return 7; }\n",
5607 ));
5608 assert!(!text.contains("call @llabs"), "{text}");
5609 }
5610
5611 #[test]
5618 fn a_byte_swap_is_arithmetic_and_not_a_call() {
5619 let text = body("unsigned f(unsigned x) { return __builtin_bswap32(x); }\n");
5620 assert_eq!(text, "block0(%0: i32):\n %1 = bswap %0\n return %1\n");
5621
5622 let text = body("unsigned f(unsigned char c) { return __builtin_bswap32(c); }\n");
5625 assert!(text.contains("zext.i32 %0"), "widened first: {text}");
5626 assert!(text.contains("bswap %1"), "and swapped at four bytes: {text}");
5627 }
5628
5629 #[test]
5635 fn the_byte_swaps_reverse_at_the_width_their_name_says() {
5636 for (name, ty, width) in [
5637 ("__builtin_bswap16", "unsigned short", "i16"),
5638 ("__builtin_bswap32", "unsigned", "i32"),
5639 ("__builtin_bswap64", "unsigned long long", "i64"),
5640 ] {
5641 let source = format!("{ty} f({ty} x) {{ return {name}(x); }}\n");
5642 let text = body(&source);
5643 assert_eq!(
5644 text,
5645 format!("block0(%0: {width}):\n %1 = bswap %0\n return %1\n"),
5646 "{name}"
5647 );
5648 }
5649 }
5650
5651 #[test]
5658 fn the_bit_counts_are_instructions_and_not_calls() {
5659 let text = body("int f(unsigned x) { return __builtin_clz(x); }\n");
5660 assert_eq!(text, "block0(%0: i32):\n %1 = ctlz %0\n return %1\n");
5661
5662 let text = body("int f(unsigned x) { return __builtin_ctz(x); }\n");
5663 assert_eq!(text, "block0(%0: i32):\n %1 = cttz %0\n return %1\n");
5664
5665 let text = body("int f(unsigned x) { return __builtin_popcount(x); }\n");
5666 assert_eq!(text, "block0(%0: i32):\n %1 = ctpop %0\n return %1\n");
5667 }
5668
5669 #[test]
5678 fn the_bit_counts_ask_about_the_width_their_name_says() {
5679 let text = body("int f(unsigned long long x) { return __builtin_clzll(x); }\n");
5680 assert!(text.starts_with("block0(%0: i64):"), "counted at eight bytes: {text}");
5681 assert!(text.contains("%1 = ctlz %0"), "{text}");
5682 assert!(text.contains("trunc.i32 %1"), "and answered in an int: {text}");
5683
5684 let text = body("int f(unsigned long long x) { return __builtin_clz(x); }\n");
5687 assert!(text.contains("trunc.i32 %0"), "narrowed to what was asked about: {text}");
5688 assert!(text.contains("ctlz %1"), "and counted there: {text}");
5689
5690 let text = body("int f(unsigned long x) { return __builtin_popcountl(x); }\n");
5691 assert!(text.contains("%1 = ctpop %0"), "{text}");
5692 assert!(!text.contains("call"), "{text}");
5693 }
5694
5695 #[test]
5700 fn a_parity_is_the_low_bit_of_the_set_bit_count() {
5701 let text = body("int f(unsigned x) { return __builtin_parity(x); }\n");
5702 assert!(text.contains("%1 = ctpop %0"), "{text}");
5703 assert!(text.contains("iconst.i32 1"), "{text}");
5704 assert!(text.contains("and %1, %2"), "the low bit of it: {text}");
5705 }
5706
5707 #[test]
5713 fn the_first_set_bit_is_one_based_and_zero_for_a_zero() {
5714 let text = body("int f(int x) { return __builtin_ffs(x); }\n");
5715 assert!(text.contains("%1 = cttz %0"), "{text}");
5716 assert!(text.contains("%4 = add %1, %2"), "one more than the count: {text}");
5717 assert!(text.contains("%5 = icmp ne %0, %3"), "whether there was a bit at all: {text}");
5718 assert!(text.contains("%7 = sub %3, %6"), "spread to a mask: {text}");
5719 assert!(text.contains("%8 = and %4, %7"), "and kept only then: {text}");
5720 assert!(!text.contains("br_if"), "no branch: {text}");
5721 }
5722
5723 #[test]
5733 fn the_redundant_sign_bit_count_is_instructions_and_not_a_call() {
5734 let text = body("int f(int x) { return __builtin_clrsb(x); }\n");
5735 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
5736 assert!(text.contains("%2 = ashr %0, %1"), "the sign over every bit: {text}");
5737 assert!(text.contains("%3 = xor %0, %2"), "folded onto it: {text}");
5738 assert!(text.contains("%5 = shl %3, %4"), "one less than the count: {text}");
5739 assert!(text.contains("%6 = or %5, %4"), "with something to count at zero: {text}");
5740 assert!(text.contains("%7 = ctlz %6"), "{text}");
5741 assert!(!text.contains("call"), "{text}");
5742 assert!(!text.contains("br_if"), "no branch: {text}");
5743 }
5744
5745 #[test]
5751 fn the_unsigned_absolute_value_family_answers_in_the_unsigned_type() {
5752 let text = body("unsigned f(int x) { return __builtin_uabs(x); }\n");
5753 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
5754 assert!(text.contains("%4 = sub %3, %2"), "{text}");
5755 assert!(!text.contains("call"), "nothing declares uabs, so a call would not link: {text}");
5756
5757 let text = body("unsigned long long f(long long x) { return __builtin_ullabs(x); }\n");
5758 assert!(text.contains("iconst.i64 63"), "at the width the name says: {text}");
5759
5760 let text = body("int f(int x) { return __builtin_uabs(x) > 2147483647u; }\n");
5763 assert!(text.contains("icmp ugt"), "compared unsigned: {text}");
5764 }
5765
5766 #[test]
5774 fn the_widest_absolute_value_is_whichever_type_the_target_makes_intmax_t() {
5775 let text = body("long f(long x) { return __builtin_imaxabs(x); }\n");
5776 assert!(text.contains("iconst.i64 63"), "{text}");
5777 assert!(text.contains("%4 = sub %3, %2"), "{text}");
5778 assert!(!text.contains("call"), "{text}");
5779
5780 let text = body("unsigned long f(long x) { return __builtin_umaxabs(x); }\n");
5781 assert!(text.contains("iconst.i64 63"), "{text}");
5782 assert!(!text.contains("call"), "{text}");
5783 }
5784
5785 #[test]
5793 fn an_overflow_predicate_writes_nothing_and_answers_the_bit_the_check_would() {
5794 let text =
5795 body("int f(int a, int b) { return __builtin_add_overflow_p(a, b, (int) 0); }\n");
5796 assert!(text.contains("%2, %3 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
5797 assert!(!text.contains("store"), "nothing is written: {text}");
5798 assert!(!text.contains("call"), "{text}");
5799
5800 let text =
5803 body("int f(int a, int b) { return __builtin_mul_overflow_p(a, b, (long long) 0); }\n");
5804 assert!(text.contains("smul_overflow.(i64, i1)"), "{text}");
5805 assert!(!text.contains("store"), "{text}");
5806
5807 let text = body(concat!(
5810 "int g(void);\n",
5811 "int f(int a, int b) { return __builtin_sub_overflow_p(a, b, g()); }\n",
5812 ));
5813 assert!(!text.contains("call @g"), "the third argument is not evaluated: {text}");
5814 }
5815
5816 #[test]
5826 fn an_overflow_check_is_arithmetic_and_not_a_call() {
5827 let text =
5828 body("int f(int a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
5829 assert!(text.contains("%3, %4 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
5830 assert!(text.contains("store %3 -> %2"), "{text}");
5831 assert!(!text.contains("call"), "{text}");
5832
5833 let text =
5834 body("int f(int a, int b, int *r) { return __builtin_sub_overflow(a, b, r); }\n");
5835 assert!(text.contains("ssub_overflow.(i32, i1) %0, %1"), "{text}");
5836
5837 let text =
5838 body("int f(int a, int b, int *r) { return __builtin_mul_overflow(a, b, r); }\n");
5839 assert!(text.contains("smul_overflow.(i32, i1) %0, %1"), "{text}");
5840
5841 let text = body(
5844 "int f(unsigned a, unsigned b, unsigned *r) { return __builtin_add_overflow(a, b, r); }\n",
5845 );
5846 assert!(text.contains("uadd_overflow.(i32, i1) %0, %1"), "{text}");
5847 }
5848
5849 #[test]
5857 fn an_overflow_check_is_done_at_a_type_that_holds_every_operand() {
5858 let text = body(
5859 "int f(unsigned a, int b, long long *r) { return __builtin_add_overflow(a, b, r); }\n",
5860 );
5861 assert!(text.contains("%3 = zext.i64 %0"), "the unsigned operand keeps its value: {text}");
5862 assert!(text.contains("%4 = sext.i64 %1"), "and so does the signed one: {text}");
5863 assert!(text.contains("sadd_overflow.(i64, i1) %3, %4"), "{text}");
5864
5865 let text = body(
5868 "int f(long long a, long long b, long long *r) { return __builtin_mul_overflow(a, b, r); }\n",
5869 );
5870 assert!(text.contains("smul_overflow.(i64, i1) %0, %1"), "{text}");
5871 assert!(!text.contains("sext."), "{text}");
5872 assert!(!text.contains("zext.i64"), "{text}");
5874 }
5875
5876 #[test]
5884 fn an_overflow_check_writes_the_wrapped_answer_whether_or_not_it_fit() {
5885 let text =
5886 body("int f(int a, int b, char *r) { return __builtin_sub_overflow(a, b, r); }\n");
5887 assert!(text.contains("%3, %4 = ssub_overflow.(i32, i1) %0, %1"), "{text}");
5888 assert!(text.contains("%5 = trunc.i8 %3"), "narrowed to where it goes: {text}");
5889 assert!(text.contains("%6 = sext.i32 %5"), "and back: {text}");
5890 assert!(text.contains("%7 = icmp ne %6, %3"), "which is whether it fit: {text}");
5891 assert!(text.contains("store %5 -> %2"), "the narrowed value is stored either way: {text}");
5892 assert!(text.contains("%8 = or %4, %7"), "and either bit is an overflow: {text}");
5893 }
5894
5895 #[test]
5902 fn a_call_needing_more_than_the_widest_type_still_compiles() {
5903 for name in ["add", "sub", "mul"] {
5904 let source = format!(
5905 "int f(unsigned __int128 a, long long b, __int128 *r) {{\n \
5906 return __builtin_{name}_overflow(a, b, r);\n}}\n"
5907 );
5908 let mut opts = options();
5909 opts.emit = EmitKind::MirFinal;
5910 assert!(!run(&opts, &source).failed(), "{name} was refused or stopped the back end");
5911 }
5912 }
5913
5914 #[test]
5917 fn an_overflow_check_over_something_that_is_not_an_integer_says_so() {
5918 let messages =
5919 errors("int f(double a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
5920 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
5921
5922 let messages =
5923 errors("int f(int a, int b, double *r) { return __builtin_add_overflow(a, b, r); }\n");
5924 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
5925 }
5926
5927 #[test]
5938 fn an_ordered_access_is_ordered_in_the_ir() {
5939 let text = body("int f(int *p) { return __atomic_load_n(p, 0); }\n");
5940 assert!(text.contains("atomic_load.i32 %0, align 4, relaxed"), "{text}");
5941
5942 let text = body("long f(long *p) { return __atomic_load_n(p, 2); }\n");
5943 assert!(text.contains("atomic_load.i64 %0, align 8, acquire"), "{text}");
5944
5945 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
5946 assert!(text.contains("atomic_store %1 -> %0, align 4, release"), "{text}");
5947
5948 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
5949 assert!(text.contains("atomic_store %1 -> %0, align 4, seq_cst"), "{text}");
5950
5951 let text = body("void f(char *p, int v) { __atomic_store_n(p, v, 0); }\n");
5954 assert!(text.contains("trunc.i8 %1"), "{text}");
5955 assert!(text.contains("atomic_store %2 -> %0, align 1, relaxed"), "{text}");
5956 }
5957
5958 #[test]
5967 fn an_ordered_access_is_the_plain_instruction_on_this_machine() {
5968 let text = asm("int f(int *p) { return __atomic_load_n(p, 5); }\n");
5969 assert!(text.contains("movl\t(%rdi), %eax"), "{text}");
5970 assert!(!text.contains("mfence"), "a load needs no barrier here: {text}");
5971
5972 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
5973 assert!(text.contains("movl\t%esi, (%rdi)"), "{text}");
5974 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
5975
5976 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
5977 let (before, after) = text.split_once("mfence").expect("a barrier: {text}");
5978 assert!(before.contains("movl\t%esi, (%rdi)"), "the store comes first: {text}");
5979 assert!(!after.contains("movl"), "and nothing else is between them: {text}");
5980 }
5981
5982 #[test]
5992 fn a_barrier_is_one_instruction_at_the_strongest_ordering_and_none_below_it() {
5993 assert!(asm("void f(void) { __atomic_thread_fence(5); }\n").contains("mfence"));
5994 assert!(asm("void f(void) { __sync_synchronize(); }\n").contains("mfence"));
5995
5996 for weaker in ["1", "2", "3", "4"] {
5997 let source = format!("void f(void) {{ __atomic_thread_fence({weaker}); }}\n");
5998 assert!(!asm(&source).contains("mfence"), "{weaker} costs nothing here");
5999 }
6000 }
6001
6002 #[test]
6012 fn the_three_x86_fences_are_the_barrier_the_strongest_ordering_gives() {
6013 for name in ["__builtin_ia32_sfence", "__builtin_ia32_lfence", "__builtin_ia32_mfence"] {
6014 let source = format!("void f(void) {{ {name}(); }}\n");
6015 assert!(asm(&source).contains("mfence"), "{name} is a barrier");
6016 let text = body(&source);
6017 assert!(text.contains("fence seq_cst"), "{name}: {text}");
6018 }
6019
6020 let result = run(&options(), "void f(void) { __builtin_ia32_sfence(1); }\n");
6021 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
6022 assert!(result.messages[0].contains("too many arguments"), "{:?}", result.messages);
6023 }
6024
6025 #[test]
6031 fn a_compare_and_exchange_is_one_instruction_answering_two_things() {
6032 let text =
6035 body("int f(int *p, int e, int d) { return __sync_val_compare_and_swap(p, e, d); }\n");
6036 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
6037 assert!(text.contains("return %3"), "the value it found: {text}");
6038
6039 let text =
6040 body("int f(int *p, int e, int d) { return __sync_bool_compare_and_swap(p, e, d); }\n");
6041 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
6042 assert!(text.contains("zext.i32 %4"), "whether it happened: {text}");
6043
6044 let text = body(
6047 "int f(int *p, int *e, int d) { return __atomic_compare_exchange_n(p, e, d, 0, 4, 2); }\n",
6048 );
6049 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
6050 assert!(text.contains("%4, %5 = cmpxchg.(i32, i1) %0, %3, %2, align 4, acq_rel"), "{text}");
6051 assert!(text.contains("br_if %5, block2, block1"), "{text}");
6052 assert!(text.contains("store %4 -> %1, align 4"), "{text}");
6053
6054 let text = body(
6057 "int f(int *p, int *e, int *d) { return __atomic_compare_exchange(p, e, d, 0, 5, 5); }\n",
6058 );
6059 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
6060 assert!(text.contains("%4 = load.i32 %2, align 4"), "{text}");
6061 assert!(text.contains("%5, %6 = cmpxchg.(i32, i1) %0, %3, %4, align 4, seq_cst"), "{text}");
6062 }
6063
6064 #[test]
6071 fn a_compare_and_exchange_is_a_locked_instruction_at_the_width_of_the_object() {
6072 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
6073 for (ty, suffix, reg) in widths {
6074 let source = format!(
6075 "int f({ty} *p, {ty} e, {ty} d) {{ return __sync_bool_compare_and_swap(p, e, d); }}\n"
6076 );
6077 let text = asm(&source);
6078 assert!(text.contains("\tlock\n"), "{ty}: {text}");
6079 assert!(text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
6080 assert!(text.contains("sete\t"), "{ty}: {text}");
6081 }
6082 let source =
6083 "int f(long *p, long e, long d) { return __sync_bool_compare_and_swap(p, e, d); }\n";
6084 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
6085
6086 for order in ["0", "2", "3", "4", "5"] {
6090 let call = format!("__atomic_compare_exchange_n(p, e, d, 0, {order}, 0)");
6091 let source = format!("int f(int *p, int *e, int d) {{ return {call}; }}\n");
6092 let text = asm(&source);
6093 assert!(text.contains("cmpxchgl\t"), "{order}: {text}");
6094 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
6095 }
6096 }
6097
6098 #[test]
6110 fn a_read_modify_write_is_one_instruction_and_the_arithmetic_a_name_asks_for() {
6111 let text = body("int f(int *p, int v) { return __atomic_fetch_add(p, v, 5); }\n");
6112 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
6113 assert!(text.contains("return %2"), "the value that was there: {text}");
6114
6115 let text = body("int f(int *p, int v) { return __atomic_add_fetch(p, v, 5); }\n");
6116 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
6117 assert!(text.contains("%3 = add %2, %1"), "and the value afterwards: {text}");
6118
6119 let text = body("int f(int *p, int v) { return __atomic_sub_fetch(p, v, 5); }\n");
6120 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
6121 assert!(text.contains("%3 = sub %2, %1"), "{text}");
6122
6123 let text = body("int f(int *p, int v) { return __sync_fetch_and_sub(p, v); }\n");
6125 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
6126
6127 let text = body("int f(int *p, int v) { return __atomic_exchange_n(p, v, 5); }\n");
6130 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, seq_cst"), "{text}");
6131
6132 let text = body("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
6133 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, acquire"), "{text}");
6134
6135 let text = body("void f(int *p) { __sync_lock_release(p); }\n");
6138 assert!(text.contains("release"), "{text}");
6139 assert!(text.contains("%1 = iconst.i32 0"), "{text}");
6140
6141 let text = body("void f(int *p, int guard) { __sync_lock_release(p, guard); }\n");
6145 assert!(text.contains("%2 = iconst.i32 0"), "{text}");
6146 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
6147
6148 let text = body("int f(int *p, int v) { return __atomic_fetch_and(p, v, 5); }\n");
6151 assert!(text.contains("%2 = atomic_rmw.i32 and %0, %1, align 4, seq_cst"), "{text}");
6152
6153 let text = body("int f(int *p, int v) { return __sync_or_and_fetch(p, v); }\n");
6154 assert!(text.contains("%2 = atomic_rmw.i32 or %0, %1, align 4, seq_cst"), "{text}");
6155 assert!(text.contains("%3 = or %2, %1"), "and the value afterwards: {text}");
6156
6157 let text = body("int f(int *p, int v) { return __atomic_nand_fetch(p, v, 5); }\n");
6160 assert!(text.contains("%2 = atomic_rmw.i32 nand %0, %1, align 4, seq_cst"), "{text}");
6161 assert!(text.contains("%3 = and %2, %1"), "{text}");
6162 assert!(text.contains("%4 = iconst.i32 -1"), "{text}");
6163 assert!(text.contains("%5 = xor %3, %4"), "{text}");
6164 }
6165
6166 #[test]
6177 fn a_bitwise_read_modify_write_is_a_loop_around_the_compare_and_exchange() {
6178 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
6179 for (ty, suffix, reg) in widths {
6180 for (name, call, insn) in [
6181 ("and", "__atomic_fetch_and(p, v, 5)", "and"),
6182 ("or", "__sync_fetch_and_or(p, v)", "or"),
6183 ("xor", "__atomic_xor_fetch(p, v, 5)", "xor"),
6184 ] {
6185 let source = format!("{ty} f({ty} *p, {ty} v) {{ return {call}; }}\n");
6186 let text = asm(&source);
6187 assert!(text.contains("\tlock\n"), "{ty} {name}: {text}");
6188 assert!(
6189 text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")),
6190 "{ty} {name}: {text}"
6191 );
6192 assert!(text.contains(&format!("{insn}{suffix}\t")), "{ty} {name}: {text}");
6193 assert!(!text.contains("\txadd"), "{ty} {name} is not an add: {text}");
6195 assert!(!text.contains("\txchg"), "{ty} {name} is not an exchange: {text}");
6196 }
6197 }
6198 let source = "long f(long *p, long v) { return __atomic_fetch_or(p, v, 5); }\n";
6199 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
6200
6201 let text = asm("int f(int *p, int v) { return __sync_fetch_and_nand(p, v); }\n");
6205 assert!(text.contains("cmpxchgl\t"), "{text}");
6206 assert!(text.contains("andl\t"), "{text}");
6207 assert!(text.contains("notl\t"), "{text}");
6208 }
6209
6210 #[test]
6219 fn an_access_through_a_second_pointer_is_the_same_access_and_one_more() {
6220 let text = body("void f(int *p, int *r) { __atomic_load(p, r, 5); }\n");
6221 assert!(text.contains("%2 = atomic_load.i32 %0, align 4, seq_cst"), "{text}");
6222 assert!(text.contains("store %2 -> %1, align 4"), "and out through the place: {text}");
6223
6224 let text = body("void f(int *p, int *v) { __atomic_store(p, v, 3); }\n");
6225 assert!(text.contains("%2 = load.i32 %1, align 4"), "in through the place: {text}");
6226 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
6227
6228 let text = body("void f(int *p, int *v, int *r) { __atomic_exchange(p, v, r, 5); }\n");
6231 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
6232 assert!(text.contains("%4 = atomic_rmw.i32 xchg %0, %3, align 4, seq_cst"), "{text}");
6233 assert!(text.contains("store %4 -> %2, align 4"), "{text}");
6234 }
6235
6236 #[test]
6247 fn a_flag_is_an_exchange_of_one_byte_and_a_store_of_a_zero_over_the_same_byte() {
6248 for pointer in ["char", "int", "void"] {
6249 let source = format!("int f({pointer} *p) {{ return __atomic_test_and_set(p, 5); }}\n");
6250 let text = body(&source);
6251 assert!(text.contains("%1 = iconst.i8 1"), "{pointer}: {text}");
6252 assert!(
6253 text.contains("%2 = atomic_rmw.i8 xchg %0, %1, align 1, seq_cst"),
6254 "{pointer}: {text}"
6255 );
6256 assert!(text.contains("%4 = icmp ne %2, %3"), "{pointer}: {text}");
6257
6258 let source = format!("void f({pointer} *p) {{ __atomic_clear(p, 3); }}\n");
6259 let text = body(&source);
6260 assert!(text.contains("atomic_store %2 -> %0, align 1, release"), "{pointer}: {text}");
6261 }
6262
6263 let text = asm("int f(int *p) { return __atomic_test_and_set(p, 5); }\n");
6266 assert!(text.contains("xchgb\t%al, (%rdi)"), "{text}");
6267 assert!(text.contains("setne\t"), "{text}");
6268 }
6269
6270 #[test]
6278 fn a_read_modify_write_is_an_exchange_or_a_locked_add_at_the_width_of_the_object() {
6279 let widths = [("char", "b", "%sil"), ("short", "w", "%si"), ("int", "l", "%esi")];
6280 for (ty, suffix, reg) in widths {
6281 let source =
6282 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_fetch_add(p, v, 5); }}\n");
6283 let text = asm(&source);
6284 assert!(text.contains("\tlock\n"), "{ty}: {text}");
6285 assert!(text.contains(&format!("xadd{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
6286
6287 let source =
6288 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_exchange_n(p, v, 5); }}\n");
6289 let text = asm(&source);
6290 assert!(text.contains(&format!("xchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
6291 assert!(!text.contains("\tlock\n"), "an exchange is locked already: {ty}: {text}");
6292 }
6293 let source = "long f(long *p, long v) { return __atomic_fetch_add(p, v, 5); }\n";
6294 assert!(asm(source).contains("xaddq\t%rsi, (%rdi)"), "{}", asm(source));
6295
6296 let source = "int f(int *p, int v) { return __atomic_fetch_sub(p, v, 5); }\n";
6299 let text = asm(source);
6300 assert!(text.contains("negl\t"), "{text}");
6301 assert!(text.contains("xaddl\t"), "{text}");
6302
6303 for order in ["0", "2", "3", "4", "5"] {
6306 let source =
6307 format!("int f(int *p, int v) {{ return __atomic_fetch_add(p, v, {order}); }}\n");
6308 let text = asm(&source);
6309 assert!(text.contains("xaddl\t"), "{order}: {text}");
6310 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
6311 }
6312
6313 let text = asm("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
6317 assert!(text.contains("xchgl\t%esi, (%rdi)"), "{text}");
6318 let text = asm("void f(int *p) { __sync_lock_release(p); }\n");
6325 assert!(text.contains("xorl\t%eax, %eax"), "{text}");
6326 assert!(text.contains("movl\t%eax, (%rdi)"), "{text}");
6327 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
6328 }
6329
6330 #[test]
6342 fn the_lock_free_questions_are_answered_as_constants() {
6343 for size in ["1", "2", "4", "8"] {
6344 let source =
6345 format!("int f(void) {{ return __atomic_always_lock_free({size}, 0); }}\n");
6346 let text = asm(&source);
6347 assert!(text.contains("movb\t$1, %al"), "{size} bytes is lock free: {text}");
6348 assert!(!text.contains("call"), "and is not a call: {text}");
6349 }
6350 for size in ["3", "16", "sizeof(long double)"] {
6351 let source = format!("int f(void) {{ return __atomic_is_lock_free({size}, 0); }}\n");
6352 let text = asm(&source);
6353 assert!(text.contains("movb\t$0, %al"), "{size} bytes is not: {text}");
6354 assert!(!text.contains("call"), "and is not a call either: {text}");
6355 }
6356
6357 let text = asm("int f(int n) { return __atomic_is_lock_free(n, 0); }\n");
6361 assert!(text.contains("movb\t$0, %al"), "a size nobody knows is not lock free: {text}");
6362 let text = asm("int f(int *p) { return __atomic_always_lock_free(8, p); }\n");
6363 assert!(text.contains("movb\t$0, %al"), "eight bytes at four is not: {text}");
6364 let text = asm("int f(long *p) { return __atomic_always_lock_free(8, p); }\n");
6365 assert!(text.contains("movb\t$1, %al"), "and at eight it is: {text}");
6366 }
6367
6368 #[test]
6380 fn a_memory_order_an_operation_cannot_carry_is_read_as_the_strongest() {
6381 let mut opts = options();
6382 opts.emit = EmitKind::Ir;
6383
6384 let acquire_store = run(&opts, "void f(int *p, int v) { __atomic_store_n(p, v, 2); }\n");
6385 assert!(acquire_store.text().contains("seq_cst"), "{:?}", acquire_store.text());
6386 assert!(acquire_store.messages[0].contains("[W0333]"), "{:?}", acquire_store.messages);
6387
6388 let nonsense = run(&opts, "int f(int *p) { return __atomic_load_n(p, 99); }\n");
6389 assert!(nonsense.text().contains("seq_cst"), "{:?}", nonsense.text());
6390 assert!(nonsense.messages[0].contains("[W0333]"), "{:?}", nonsense.messages);
6391
6392 let computed = run(&opts, "int f(int *p, int n) { return __atomic_load_n(p, n); }\n");
6393 assert!(computed.text().contains("seq_cst"), "{:?}", computed.text());
6394 assert_eq!(computed.messages, Vec::<String>::new(), "a computed order is not a mistake");
6395 }
6396
6397 #[test]
6409 fn a_conversion_between_a_float_and_the_widest_unsigned_integer_is_written_without_a_branch() {
6410 let text = asm("double f(unsigned long long x) { return (double)x; }\n");
6411 assert!(text.contains("cvtsi2sdq"), "the signed conversion is what runs: {text}");
6412 assert!(text.contains("shrq"), "with the value halved first: {text}");
6413 assert!(text.contains("addsd"), "and doubled after: {text}");
6414 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
6415
6416 let text = asm("unsigned long long f(double d) { return (unsigned long long)d; }\n");
6417 assert!(text.contains("cvttsd2siq"), "the signed conversion is what runs: {text}");
6418 assert!(text.contains("subsd"), "with half the range taken off first: {text}");
6419 assert!(text.contains("shlq\t$63"), "and the top bit put back: {text}");
6420 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
6421 }
6422
6423 #[test]
6434 fn a_plain_name_the_program_took_is_the_programs_own_function() {
6435 let taken = concat!(
6436 "static long long llabs(long long b) { return 7; }\n",
6437 "long long f(long long x) { return llabs(x); }\n",
6438 );
6439 assert!(ir(taken).contains("call @llabs"), "a static definition is the program's own");
6440
6441 let retyped = concat!("int llabs(int b);\n", "int f(int x) { return llabs(x); }\n",);
6442 assert!(ir(retyped).contains("call @llabs"), "another type is another function");
6443
6444 let plain = concat!(
6445 "long long llabs(long long b);\n",
6446 "long long f(long long x) { return llabs(x); }\n",
6447 );
6448 let mut opts = options();
6449 opts.emit = EmitKind::Ir;
6450 assert!(!run(&opts, plain).text().contains("call @llabs"), "the library's by default");
6451
6452 opts.builtins = false;
6453 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin");
6454
6455 opts.builtins = true;
6456 opts.no_builtin = vec!["llabs".to_owned()];
6457 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin-llabs");
6458 let one = "long labs(long b);\nlong f(long x) { return labs(x); }\n";
6459 assert!(!run(&opts, one).text().contains("call @labs"), "one name and not the family");
6460
6461 opts.no_builtin = Vec::new();
6464 opts.builtins = false;
6465 let prefixed = "long long f(long long x) { return __builtin_llabs(x); }\n";
6466 assert!(!run(&opts, prefixed).text().contains("call @llabs"), "the prefix is a promise");
6467 }
6468
6469 #[test]
6482 fn the_hint_builtins_are_their_first_argument_and_the_hint_leaves_no_trace() {
6483 let text = ir(concat!(
6484 "long a = __builtin_expect(7, 1);\n",
6485 "long b = __builtin_expect_with_probability(9, 1, 0.9);\n",
6486 "unsigned long c = sizeof(__builtin_expect((char)1, 1));\n",
6487 ));
6488 assert!(text.contains("global @a : i64 = 7,"), "{text}");
6489 assert!(text.contains("global @b : i64 = 9,"), "{text}");
6490 assert!(text.contains("global @c : i64 = 8,"), "{text}");
6491 assert!(!text.contains("__builtin_expect"), "it is not a call to anything:\n{text}");
6492
6493 let text = body("long f(char c) { return __builtin_expect(c, 1); }\n");
6496 assert!(text.contains("sext"), "{text}");
6497
6498 let one = "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 1\n %2 = sext.i64 %1\n return %0\n";
6502 assert_eq!(body("int f(void) { int i = 0; __builtin_expect(1, i++); return i; }\n"), one);
6503 let source = "int g(void) { int i = 0; __builtin_expect_with_probability(1, i++, 0.5); return i; }\n";
6504 assert_eq!(body(source), one);
6505
6506 let kept = body("int f(int n) { int i = 0; __builtin_expect(n, i++); return i; }\n");
6511 assert!(kept.contains("add.nsw"), "the hint still runs: {kept}");
6512 assert!(kept.ends_with("return %3\n"), "and the answer is what it left behind: {kept}");
6513 let both = "int g(int n) { int i = 0; __builtin_expect_with_probability(n, i++, 0.5); return i; }\n";
6514 assert!(body(both).contains("add.nsw"), "and so does the one with three arguments");
6515 }
6516
6517 #[test]
6529 fn a_promise_that_control_does_not_arrive_writes_no_instruction() {
6530 let promised = "int f(int x) { if (x) return 1; __builtin_unreachable(); }\n";
6531 let text = ir(promised);
6532 assert!(text.contains(" unreachable_hint\n"), "{text}");
6533 assert!(!text.contains("call"), "it is not a call to anything:\n{text}");
6534
6535 let after = body("int g(int x) { __builtin_unreachable(); return x; }\n");
6539 assert!(after.contains("return"), "{after}");
6540
6541 let text = asm(promised);
6544 let mine = text.split_once("\nf:\n").expect("a definition").1;
6545 let mine = mine.split_once("\t.size").expect("a definition").0;
6546 let plain = asm("int f(int x) { if (x) return 1; }\n");
6547 let plain = plain.split_once("\nf:\n").expect("a definition").1;
6548 let plain = plain.split_once("\t.size").expect("a definition").0;
6549 assert_eq!(mine, plain);
6550 let last = mine.lines().rfind(|line| !line.trim_start().starts_with('.'));
6553 assert_eq!(last.map(str::trim), Some("ret"), "{mine}");
6554 assert!(!mine.contains("ud2"), "{mine}");
6555 }
6556
6557 #[test]
6564 fn a_library_builtin_is_diagnosed_under_the_name_the_program_wrote() {
6565 let mut opts = options();
6566 opts.emit = EmitKind::Ir;
6567 let messages = run(&opts, "void f(void) { __builtin_abort(1); }\n").messages;
6568 assert!(
6569 messages.iter().any(|m| m.contains("__builtin_abort")),
6570 "expected the written name in {messages:?}"
6571 );
6572 }
6573
6574 #[test]
6582 fn a_builtin_nothing_lowers_is_refused_by_name() {
6583 let mut opts = options();
6584 opts.emit = EmitKind::Ir;
6585 let builtin = "__atomic_signal_fence";
6586 let source = format!("int counter;\nint f(void) {{ return ({builtin}(5), 0); }}\n");
6587 let messages = run(&opts, &source).messages;
6588 let named = messages.iter().any(|m| m.contains(builtin) && m.contains("E0686"));
6589 assert!(named, "expected {builtin} to be refused by name in {messages:?}");
6590 }
6591
6592 #[test]
6601 fn what_is_refused_is_the_call_and_not_the_name() {
6602 let text = ir(concat!(
6603 "void __atomic_signal_fence(int order) { (void)order; }\n",
6604 "void f(void) { __atomic_signal_fence(5); }\n",
6605 ));
6606 assert!(text.contains("call @__atomic_signal_fence"), "{text}");
6607 }
6608
6609 #[test]
6618 fn the_object_size_of_an_address_is_what_the_layout_leaves_in_front_of_it() {
6619 let text = ir(concat!(
6620 "struct S { char a[8]; int n; char b[12]; };\n",
6621 "char g[32];\n",
6622 "struct S gs;\n",
6623 "unsigned long whole = __builtin_object_size(g, 0);\n",
6624 "unsigned long moved = __builtin_object_size(g + 4, 0);\n",
6625 "unsigned long back = __builtin_object_size(g + 30 - 2, 0);\n",
6626 "unsigned long outer = __builtin_object_size(gs.a, 0);\n",
6627 "unsigned long inner = __builtin_object_size(gs.a, 1);\n",
6628 "unsigned long scalar = __builtin_object_size(&gs.n, 1);\n",
6629 "unsigned long after = __builtin_object_size(&gs.n, 0);\n",
6630 "unsigned long into = __builtin_object_size(&gs.b[2], 1);\n",
6631 "unsigned long text = __builtin_object_size(\"hello\", 0);\n",
6632 "unsigned long dyn = __builtin_dynamic_object_size(gs.b, 1);\n",
6633 ));
6634 for (name, size) in [
6635 ("whole", 32),
6636 ("moved", 28),
6637 ("back", 4),
6638 ("outer", 24),
6639 ("inner", 8),
6640 ("scalar", 4),
6641 ("after", 16),
6642 ("into", 10),
6643 ("text", 6),
6644 ("dyn", 12),
6645 ] {
6646 let said = format!("global @{name} : i64 = {size},");
6647 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
6648 }
6649 }
6650
6651 #[test]
6659 fn the_object_behind_an_address_can_be_one_with_automatic_storage() {
6660 let text = body(concat!(
6661 "struct S { char a[8]; int n; char b[12]; };\n",
6662 "unsigned long f(void) {\n",
6663 " char loc[20];\n",
6664 " struct S ls;\n",
6665 " return __builtin_object_size(loc + 3, 0) + __builtin_object_size(ls.b + 2, 1);\n",
6666 "}\n",
6667 ));
6668 assert!(text.contains("iconst.i64 17"), "twenty bytes with three used: {text}");
6669 assert!(text.contains("iconst.i64 10"), "twelve bytes with two used: {text}");
6670 }
6671
6672 #[test]
6682 fn an_address_with_no_object_in_sight_answers_at_the_end_of_the_range_its_kind_asks_for() {
6683 let text = ir(concat!(
6684 "struct T { int n; char f[]; };\n",
6685 "extern char *p;\n",
6686 "extern struct T *t;\n",
6687 "unsigned long largest = __builtin_object_size(p, 0);\n",
6688 "unsigned long nearest = __builtin_object_size(p, 1);\n",
6689 "unsigned long least = __builtin_object_size(p, 2);\n",
6690 "unsigned long tight = __builtin_object_size(p, 3);\n",
6691 "unsigned long flex = __builtin_object_size(t->f, 1);\n",
6692 "int says = __builtin_object_size(p, 0) == (unsigned long)-1;\n",
6693 ));
6694 for name in ["largest", "nearest", "flex"] {
6695 let said = format!("global @{name} : i64 = -1,");
6699 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
6700 }
6701 for name in ["least", "tight"] {
6702 let said = format!("global @{name} : i64 = 0,");
6703 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
6704 }
6705 assert!(text.contains("global @says : i32 = 1,"), "{text}");
6706 }
6707
6708 #[test]
6715 fn the_address_an_object_size_is_asked_about_is_not_evaluated() {
6716 let text = body(concat!(
6717 "extern char *side(void);\n",
6718 "unsigned long f(void) { return __builtin_object_size(side(), 0); }\n",
6719 ));
6720 assert!(!text.contains("call"), "nothing is called: {text}");
6721 }
6722
6723 #[test]
6728 fn a_kind_that_is_not_one_of_the_four_is_refused() {
6729 for source in [
6730 "extern char *p;\nextern int k;\nunsigned long f(void) ".to_owned()
6731 + "{ return __builtin_object_size(p, k); }\n",
6732 "extern char *p;\nunsigned long f(void) { return __builtin_object_size(p, 4); }\n"
6733 .to_owned(),
6734 "extern char *p;\nunsigned long f(void) ".to_owned()
6735 + "{ return __builtin_dynamic_object_size(p, -1); }\n",
6736 ] {
6737 let messages = errors(&source);
6738 let named = messages.iter().any(|m| m.contains("E0709") && m.contains("0 to 3"));
6739 assert!(named, "expected a complaint about the kind in {messages:?}");
6740 }
6741 }
6742
6743 #[test]
6749 fn the_pair_that_saves_a_place_lowers_to_the_two_markers() {
6750 let text = ir(concat!(
6751 "void *buf[5];\n",
6752 "int f(void) {\n",
6753 " if (__builtin_setjmp(buf)) return 2;\n",
6754 " return 1;\n",
6755 "}\n",
6756 "void g(void) { __builtin_longjmp(buf, 1); }\n",
6757 ));
6758 assert!(text.contains("= setjmp_marker.i32 %0\n"), "the save answers a value: {text}");
6759 assert!(text.contains(" longjmp_marker %0\n"), "the restore answers nothing: {text}");
6760 assert!(!text.contains("call @"), "neither of them is a call: {text}");
6761 }
6762
6763 #[test]
6771 fn a_local_of_a_function_that_saves_a_place_gets_a_slot() {
6772 let text = ir(concat!(
6773 "void *buf[5];\n",
6774 "int f(int x) { int a = 0; if (__builtin_setjmp(buf)) return a; a = 1; return x; }\n",
6775 "int g(int x) { int a = 0; if (x) return a; a = 1; return x; }\n",
6776 ));
6777 let (saves, plain) = text.split_once("func @g").expect("both functions");
6778 assert_eq!(saves.matches("= alloca").count(), 2, "the parameter and the local: {text}");
6779 assert!(saves.contains("store %9 -> %2"), "the local is written through: {text}");
6780 assert!(!plain.contains("alloca"), "nothing in the plain one needs a slot: {text}");
6781 }
6782
6783 #[test]
6792 fn the_save_writes_four_words_and_carries_on_in_a_new_block() {
6793 let text =
6794 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
6795 let body = text.split_once("\nf:\n").expect("the function").1;
6796 assert!(body.contains("\tmovq\t%rsp, %rbp\n"), "a frame pointer whatever: {text}");
6797 assert!(body.contains("\tsubq\t$8, %rsp\n"), "no red zone: {text}");
6798 assert!(body.contains("\tmovq\t%rbp, (%rax)\n"), "the frame pointer: {text}");
6799 assert!(body.contains("\tmovq\t%rsp, 16(%rax)\n"), "the stack pointer: {text}");
6800 assert!(body.contains("\tleaq\t.Lf_1(%rip), %rcx\n"), "where to come back to: {text}");
6801 assert!(body.contains("\tmovq\t%rcx, 8(%rax)\n"), "and that goes in the buffer: {text}");
6802 let back = body.split_once(".Lf_1:\n").expect("the block control comes back to").1;
6803 assert!(back.starts_with("\tmovq\t(%rsp), %rax\n"), "the answer is read back: {text}");
6804 }
6805
6806 #[test]
6814 fn a_save_destroys_every_register_the_allocator_hands_out() {
6815 let text =
6816 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
6817 for reg in ["%rbx", "%r12", "%r13", "%r14", "%r15"] {
6818 assert!(text.contains(&format!("\tpushq\t{reg}\n")), "{reg} is saved: {text}");
6819 assert!(text.contains(&format!("\tpopq\t{reg}\n")), "{reg} is restored: {text}");
6820 }
6821 }
6822
6823 #[test]
6830 fn the_restore_puts_the_frame_back_before_it_jumps() {
6831 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
6832 let mut opts = options();
6833 opts.emit = EmitKind::Asm;
6834 opts.opt_level = level;
6835 let source = "void *buf[5];\nvoid g(void) { __builtin_longjmp(buf, 1); }\n";
6836 let result = run(&opts, source);
6837 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
6838 let text = result.text().to_owned();
6839 let jump = text.find("\tjmp\t*%").unwrap_or_else(|| panic!("an indirect jump: {text}"));
6840 let stack = text.find(", %rsp\n").unwrap_or_else(|| panic!("the stack back: {text}"));
6841 let frame = text.find(", %rbp\n").unwrap_or_else(|| panic!("the frame back: {text}"));
6842 assert!(stack < jump, "the stack goes back first at {level:?}: {text}");
6843 assert!(frame < jump, "and so does the frame at {level:?}: {text}");
6844 }
6845 }
6846
6847 #[test]
6853 fn a_longjmp_whose_second_argument_is_not_one_is_turned_down() {
6854 for source in [
6855 "void *buf[5];\nvoid f(void) { __builtin_longjmp(buf, 0); }\n",
6856 "void *buf[5];\nextern int v;\nvoid f(void) { __builtin_longjmp(buf, v); }\n",
6857 ] {
6858 let messages = errors(source);
6859 let named = messages.iter().any(|m| m.contains("E0710"));
6860 assert!(named, "expected a complaint about the value in {messages:?}");
6861 }
6862 }
6863
6864 #[test]
6869 fn a_static_function_nothing_refers_to_is_not_emitted() {
6870 let text = ir("static int dropped(void) { return 1; }\n\
6871 static int kept(void) { return 2; }\n\
6872 int main(void) { return kept(); }\n");
6873 assert!(text.contains("func @kept"), "{text}");
6874 assert!(!text.contains("dropped"), "{text}");
6875 }
6876
6877 #[test]
6883 fn two_static_functions_that_only_call_each_other_are_both_dropped() {
6884 let text = ir("static int ping(void);\n\
6885 static int pong(void) { return ping(); }\n\
6886 static int ping(void) { return pong(); }\n\
6887 int main(void) { return 0; }\n");
6888 assert!(!text.contains("ping"), "{text}");
6889 assert!(!text.contains("pong"), "{text}");
6890 }
6891
6892 #[test]
6898 fn naming_a_static_function_anywhere_keeps_it() {
6899 let text = ir("static int by_address(void) { return 1; }\n\
6900 static int in_an_image(void) { return 2; }\n\
6901 static int deeper(void) { return 3; }\n\
6902 static int reaches_deeper(void) { return deeper(); }\n\
6903 static int (*table[1])(void) = {in_an_image};\n\
6904 int main(void) {\n\
6905 int (*p)(void) = by_address;\n\
6906 return p() + table[0]() + reaches_deeper();\n\
6907 }\n");
6908 for kept in ["by_address", "in_an_image", "deeper", "reaches_deeper"] {
6909 assert!(text.contains(&format!("func @{kept}")), "expected {kept} in:\n{text}");
6910 }
6911 }
6912
6913 #[test]
6919 fn an_attribute_keeps_a_static_function_nothing_refers_to() {
6920 for attribute in ["used", "retain", "constructor", "destructor", "__used__"] {
6921 let source = format!(
6922 "__attribute__(({attribute})) static int kept(void) {{ return 1; }}\n\
6923 int main(void) {{ return 0; }}\n"
6924 );
6925 let text = ir(&source);
6926 assert!(text.contains("func @kept"), "for {attribute}:\n{text}");
6927 }
6928 }
6929
6930 #[test]
6933 fn a_function_anything_could_call_is_emitted_without_being_called() {
6934 let text =
6935 ir("int nobody_here_calls_it(void) { return 1; }\nint main(void) { return 0; }\n");
6936 assert!(text.contains("func @nobody_here_calls_it"), "{text}");
6937 }
6938
6939 #[test]
6946 fn a_classification_c_has_an_operator_for_is_that_operator() {
6947 for (builtin, operator) in [
6948 ("__builtin_isgreater", "binary >"),
6949 ("__builtin_isgreaterequal", "binary >="),
6950 ("__builtin_isless", "binary <"),
6951 ("__builtin_islessequal", "binary <="),
6952 ] {
6953 let source = format!("int f(double x, double y) {{ return {builtin}(x, y); }}\n");
6954 let text = tast(&source);
6955 assert!(text.contains(&format!("{operator} : int")), "for {builtin}:\n{text}");
6956 }
6957 }
6958
6959 #[test]
6968 fn the_classification_builtins_are_comparisons_and_not_calls() {
6969 let text = body("int f(double x, double y) { return __builtin_isunordered(x, y); }\n");
6970 assert_eq!(
6971 text,
6972 "block0(%0: f64, %1: f64):\n %2 = fcmp uno %0, %1\n %3 = zext.i32 \
6973 %2\n return %3\n"
6974 );
6975
6976 let text = body("int f(double x, double y) { return __builtin_islessgreater(x, y); }\n");
6978 assert!(text.contains("fcmp one %0, %1"), "{text}");
6979
6980 let text = body("int f(double x) { return __builtin_isnan(x); }\n");
6981 assert!(text.contains("fcmp uno %0, %0"), "{text}");
6982
6983 let text = body("int f(double x) { return __builtin_isinf(x); }\n");
6984 assert!(text.contains("fconst.f64 0x7ff0000000000000"), "{text}");
6985 assert!(text.contains("fconst.f64 0xfff0000000000000"), "{text}");
6986 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
6987 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
6988 assert!(text.contains("%5 = or %3, %4"), "{text}");
6989
6990 let text = body("int f(double x) { return __builtin_isfinite(x); }\n");
6993 assert!(text.contains("%3 = fcmp olt %2, %0"), "{text}");
6994 assert!(text.contains("%4 = fcmp olt %0, %1"), "{text}");
6995 assert!(text.contains("%5 = and %3, %4"), "{text}");
6996
6997 let text = body("int f(double x) { return __builtin_signbit(x); }\n");
6998 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
6999 assert!(text.contains("icmp slt %1, %2"), "{text}");
7000
7001 let text = body("int f(long double x) { return __builtin_signbitl(x); }\n");
7005 assert!(text.contains("load.i16"), "{text}");
7006 assert!(text.contains("icmp slt"), "{text}");
7007 assert!(!text.contains("i80"), "{text}");
7008
7009 let text = body("double g(void);\nint f(void) { return __builtin_isnan(g()); }\n");
7012 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
7013 }
7014
7015 #[test]
7022 fn a_classification_spelling_that_names_a_width_converts_before_it_asks() {
7023 let text = ir(concat!(
7024 "int a = __builtin_isinff(1e300);\n",
7025 "int b = __builtin_isinf(1e300);\n",
7026 "int c = __builtin_isnan(0.0);\n",
7030 "int d = __builtin_signbit(-0.0);\n",
7031 "int e = __builtin_islessgreater(1.0, 2.0);\n",
7032 ));
7033 assert!(text.contains("global @a : i32 = 1,"), "{text}");
7034 assert!(text.contains("global @b : i32 = 0,"), "{text}");
7035 assert!(text.contains("global @c : i32 = 0,"), "{text}");
7036 assert!(text.contains("global @d : i32 = 1,"), "{text}");
7037 assert!(text.contains("global @e : i32 = 1,"), "{text}");
7038 }
7039
7040 #[test]
7042 fn a_classification_builtin_refuses_an_argument_that_is_not_floating_point() {
7043 let mut opts = options();
7044 opts.emit = EmitKind::Ir;
7045 let source = concat!(
7046 "int a(int x) { return __builtin_isnan(x); }\n",
7047 "int b(int x, int y) { return __builtin_isunordered(x, y); }\n",
7048 "int c(double x) { return __builtin_isnan(x, x); }\n",
7049 );
7050 let messages = run(&opts, source).messages;
7051 assert_eq!(
7052 messages,
7053 [
7054 "/main.c:1:23: error: non-floating-point argument in call to function \
7055 '__builtin_isnan' [E0685]",
7056 "/main.c:2:30: error: non-floating-point arguments in call to function \
7057 '__builtin_isunordered' [E0685]",
7058 "/main.c:3:26: error: too many arguments to function '__builtin_isnan' [E0511]",
7059 ]
7060 );
7061 }
7062
7063 #[test]
7072 fn the_last_three_classification_builtins_are_comparisons_and_not_calls() {
7073 let text = body("int f(double x) { return __builtin_isnormal(x); }\n");
7074 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
7078 assert!(text.contains("%2 = iconst.i64 9223372036854775807"), "{text}");
7079 assert!(text.contains("%3 = and %1, %2"), "{text}");
7080 assert!(text.contains("%4 = iconst.i64 4503599627370496"), "{text}");
7081 assert!(text.contains("%5 = iconst.i64 9218868437227405312"), "{text}");
7082 assert!(text.contains("%6 = icmp uge %3, %4"), "{text}");
7083 assert!(text.contains("%7 = icmp ult %3, %5"), "{text}");
7084 assert!(text.contains("%8 = and %6, %7"), "{text}");
7085
7086 let text = body("int f(long double x) { return __builtin_isnormal(x); }\n");
7091 assert!(text.contains("fconst.f80 0x18000000000000000"), "{text}");
7092 assert!(text.contains("fconst.f80 0x7fff8000000000000000"), "{text}");
7093 assert!(text.contains("fcmp oge"), "{text}");
7094 assert!(text.contains("fcmp olt"), "{text}");
7095
7096 let text = body("int f(double x) { return __builtin_isinf_sign(x); }\n");
7097 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
7098 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
7099 assert!(text.contains("%7 = sub %5, %6"), "{text}");
7100
7101 let text = body("int f(double x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n");
7102 assert!(text.contains("fcmp uno %0, %0"), "{text}");
7103 assert!(text.contains("fcmp oeq %0, %6"), "{text}");
7104 assert_eq!(text.matches(" = zext.i32 ").count(), 4, "{text}");
7108 assert_eq!(text.matches(" = xor ").count(), 4, "{text}");
7109 assert!(!text.contains("call"), "{text}");
7110
7111 let text = body(concat!(
7114 "double g(void);\n",
7115 "int f(void) { return __builtin_fpclassify(0, 1, 2, 3, 4, g()); }\n",
7116 ));
7117 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
7118 }
7119
7120 #[test]
7127 fn the_last_three_classification_builtins_fold_where_their_operand_is_a_constant() {
7128 let text = ir(concat!(
7129 "int a = __builtin_isnormal(1.0);\n",
7130 "int b = __builtin_isnormal(0.0);\n",
7131 "int c = __builtin_isnormal(1.0 / 0.0);\n",
7132 "int d = __builtin_isinf_sign(-1.0 / 0.0);\n",
7133 "int e = __builtin_isinf_sign(1.0);\n",
7134 "int g = __builtin_fpclassify(0, 1, 2, 3, 4, 0.0);\n",
7135 "int h = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0);\n",
7136 "int i = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0 / 0.0);\n",
7137 ));
7138 assert!(text.contains("global @a : i32 = 1,"), "{text}");
7139 assert!(text.contains("global @b : i32 = 0,"), "{text}");
7140 assert!(text.contains("global @c : i32 = 0,"), "{text}");
7141 assert!(text.contains("global @d : i32 = -1,"), "{text}");
7142 assert!(text.contains("global @e : i32 = 0,"), "{text}");
7143 assert!(text.contains("global @g : i32 = 4,"), "{text}");
7144 assert!(text.contains("global @h : i32 = 2,"), "{text}");
7145 assert!(text.contains("global @i : i32 = 1,"), "{text}");
7146 }
7147
7148 #[test]
7154 fn fpclassify_refuses_an_answer_that_is_not_an_integer_constant() {
7155 let mut opts = options();
7156 opts.emit = EmitKind::Ir;
7157 let source = concat!(
7158 "int a(double x, int n) { return __builtin_fpclassify(0, 1, n, 3, 4, x); }\n",
7159 "int b(double x) { return __builtin_fpclassify(0, 1, 2, 3, x); }\n",
7160 "int c(int x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n",
7161 );
7162 let messages = run(&opts, source).messages;
7163 assert_eq!(
7164 messages,
7165 [
7166 "/main.c:1:60: error: non-const integer argument 3 in call to function \
7167 '__builtin_fpclassify' [E0687]",
7168 "/main.c:2:26: error: too few arguments to function '__builtin_fpclassify' \
7169 [E0511]",
7170 "/main.c:3:23: error: non-floating-point argument in call to function \
7171 '__builtin_fpclassify' [E0685]",
7172 ]
7173 );
7174 }
7175
7176 #[test]
7184 fn a_builtin_whose_answer_is_a_constant_is_one_and_not_a_call() {
7185 let text = ir(concat!(
7186 "double a = __builtin_inf();\n",
7187 "float b = __builtin_huge_valf();\n",
7188 "long double c = __builtin_infl();\n",
7189 "double d = __builtin_huge_val();\n",
7190 ));
7191 assert!(text.contains("global @a : f64 = 0x7ff0000000000000,"), "{text}");
7192 assert!(text.contains("global @b : f32 = 0x7f800000,"), "{text}");
7193 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
7194 assert!(text.contains("global @d : f64 = 0x7ff0000000000000,"), "{text}");
7195 assert!(!text.contains("call"), "{text}");
7196 }
7197
7198 #[test]
7207 fn a_nan_is_written_with_the_payload_the_program_asked_for() {
7208 let text = ir(concat!(
7209 "double a = __builtin_nan(\"\");\n",
7210 "double b = __builtin_nan(\"0x1\");\n",
7211 "double c = __builtin_nan(\"010\");\n",
7213 "double d = __builtin_nans(\"\");\n",
7214 "double e = __builtin_nans(\"0x1\");\n",
7215 "float f = __builtin_nanf(\"0x1\");\n",
7216 "float g = __builtin_nansf(\"\");\n",
7217 "long double h = __builtin_nansl(\"\");\n",
7218 ));
7219 assert!(text.contains("global @a : f64 = 0x7ff8000000000000,"), "{text}");
7220 assert!(text.contains("global @b : f64 = 0x7ff8000000000001,"), "{text}");
7221 assert!(text.contains("global @c : f64 = 0x7ff8000000000008,"), "{text}");
7222 assert!(text.contains("global @d : f64 = 0x7ff4000000000000,"), "{text}");
7223 assert!(text.contains("global @e : f64 = 0x7ff0000000000001,"), "{text}");
7224 assert!(text.contains("global @f : f32 = 0x7fc00001,"), "{text}");
7225 assert!(text.contains("global @g : f32 = 0x7fa00000,"), "{text}");
7226 assert!(text.contains("f80 0x7fffa000000000000000"), "{text}");
7227
7228 let text = ir(concat!(
7231 "double f(const char *p) { return __builtin_nan(p); }\n",
7232 "double g(void) { return __builtin_nans(\"1x\"); }\n",
7233 ));
7234 assert_eq!(text.matches("call @nan(").count(), 1, "{text}");
7235 assert_eq!(text.matches("call @nans(").count(), 1, "{text}");
7236 }
7237
7238 #[test]
7246 fn the_length_and_the_order_of_a_string_literal_are_known_here() {
7247 let text = ir(concat!(
7248 "unsigned long a = __builtin_strlen(\"hello\");\n",
7249 "unsigned long b = __builtin_strlen(\"a\\0bc\");\n",
7250 "int c = __builtin_strcmp(\"X\", \"X\\376\") < 0;\n",
7251 "int d = __builtin_strcmp(\"abc\", \"abc\");\n",
7252 "int e = __builtin_strcmp(\"abc\", \"ab\") > 0;\n",
7253 ));
7254 assert!(text.contains("global @a : i64 = 5,"), "{text}");
7255 assert!(text.contains("global @b : i64 = 1,"), "{text}");
7256 assert!(text.contains("global @c : i32 = 1,"), "{text}");
7257 assert!(text.contains("global @d : i32 = 0,"), "{text}");
7258 assert!(text.contains("global @e : i32 = 1,"), "{text}");
7259 assert!(!text.contains("call"), "{text}");
7260
7261 let text = ir("unsigned long f(const char *p) { return __builtin_strlen(p); }\n");
7263 assert!(text.contains("call @strlen("), "{text}");
7264 }
7265
7266 #[test]
7273 fn a_sign_builtin_is_a_mask_over_the_bits_and_not_a_call() {
7274 let text = body("double f(double x) { return __builtin_fabs(x); }\n");
7275 assert!(text.contains("bitcast.i64 %0"), "{text}");
7276 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
7277 assert!(text.contains("and %1, %2"), "{text}");
7278 assert!(text.contains("bitcast.f64 %3"), "{text}");
7279 assert!(!text.contains("call"), "{text}");
7280
7281 let text = body("double f(double x, double y) { return __builtin_copysign(x, y); }\n");
7282 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
7283 assert!(text.contains("%8 = or %4, %7"), "{text}");
7284 assert!(!text.contains("call"), "{text}");
7285
7286 let text = body("long double f(long double x) { return __builtin_fabsl(x); }\n");
7289 assert!(text.contains("iconst.i16 32767"), "{text}");
7290 assert!(text.contains("load.f80"), "{text}");
7291 assert!(!text.contains("call"), "{text}");
7292
7293 let text = body("double f(float x) { return __builtin_fabs(x); }\n");
7296 assert!(text.contains("fpext.f64 %0"), "{text}");
7297 assert!(text.contains("bitcast.i64 %1"), "{text}");
7298 }
7299
7300 #[test]
7307 fn a_shuffle_picks_each_lane_by_the_low_bits_of_the_mask() {
7308 let text = body(concat!(
7309 "typedef int v2 __attribute__((vector_size(8)));\n",
7310 "void f(v2 *v, v2 m) { *v = __builtin_shuffle(*v, m); }\n",
7311 ));
7312 assert!(text.contains("memcpy"), "{text}");
7313 assert_eq!(text.matches("iconst.i32 1\n").count(), 2, "{text}");
7314 assert_eq!(text.matches(" = and ").count(), 2, "{text}");
7315 assert!(!text.contains("call"), "{text}");
7316
7317 let text = body(concat!(
7320 "typedef char v4 __attribute__((vector_size(4)));\n",
7321 "v4 f(v4 a, v4 b, v4 m) { return __builtin_shuffle(a, b, m); }\n",
7322 ));
7323 assert_eq!(text.matches("iconst.i32 7\n").count(), 4, "{text}");
7324 assert!(text.contains("zext.i32"), "{text}");
7325 assert!(!text.contains("call"), "{text}");
7326 }
7327
7328 #[test]
7332 fn the_arguments_a_function_was_called_with_are_saved_on_the_way_in() {
7333 let text =
7334 mir("void *f(int a, double b) { (void)a; (void)b; return __builtin_apply_args(); }\n");
7335 assert!(text.matches("x64.mov_mr_64").count() >= 7, "{text}");
7337 assert!(text.matches("x64.movaps_mr").count() >= 8, "{text}");
7338 for reg in ["$rdi", "$rsi", "$rdx", "$rcx", "$r8", "$r9", "$xmm0", "$xmm7"] {
7339 assert!(text.contains(reg), "{reg} is not saved in\n{text}");
7340 }
7341
7342 let text = mir("int f(int a) { return a; }\n");
7344 assert!(!text.contains("movaps_mr"), "{text}");
7345 }
7346
7347 #[test]
7351 fn a_call_built_from_saved_arguments_loads_every_argument_register() {
7352 let text = mir(concat!(
7353 "void *g(void *args, void (*h)()) {\n",
7354 " return __builtin_apply(h, args, 64);\n",
7355 "}\n",
7356 ));
7357 assert!(text.matches("x64.mov_rm_64").count() >= 7, "{text}");
7358 assert!(text.matches("x64.movaps_rm").count() >= 8, "{text}");
7359 assert!(text.contains("call"), "{text}");
7360 assert!(text.matches("x64.movaps_mr").count() >= 2, "{text}");
7362
7363 let mut opts = options();
7365 opts.emit = EmitKind::Ir;
7366 let result = run(
7367 &opts,
7368 "void *g(void *a, void (*h)(), int n) { return __builtin_apply(h, a, n); }\n",
7369 );
7370 assert!(result.failed(), "{:?}", result.messages);
7371 assert!(
7372 result
7373 .messages
7374 .iter()
7375 .any(|m| m.contains("the size given to '__builtin_apply' is a constant")),
7376 "{:?}",
7377 result.messages
7378 );
7379 }
7380
7381 #[test]
7383 fn a_shuffle_refuses_what_gcc_refuses() {
7384 let mut opts = options();
7385 opts.emit = EmitKind::Ir;
7386 let source = concat!(
7387 "typedef int v4 __attribute__((vector_size(16)));\n",
7388 "typedef float f4 __attribute__((vector_size(16)));\n",
7389 "typedef short s8 __attribute__((vector_size(16)));\n",
7390 "typedef long long l4 __attribute__((vector_size(32)));\n",
7391 "void a(v4 x, f4 m) { __builtin_shuffle(x, m); }\n",
7392 "void b(int x, v4 m) { __builtin_shuffle(x, m); }\n",
7393 "void c(v4 x, f4 y, v4 m) { __builtin_shuffle(x, y, m); }\n",
7394 "void d(v4 x, s8 m) { __builtin_shuffle(x, m); }\n",
7395 "void e(f4 x, l4 m) { __builtin_shuffle(x, m); }\n",
7396 "void g(v4 x) { __builtin_shuffle(x); }\n",
7397 );
7398 let messages = run(&opts, source).messages;
7399 let wanted = [
7400 "last argument must be an integer vector [E0715]",
7401 "arguments must be vectors [E0715]",
7402 "argument vectors must be of the same type [E0715]",
7403 "number of elements of the argument vector(s) and the mask vector should be the same \
7404 [E0715]",
7405 "argument vector(s) inner type must have the same size as inner type of the mask \
7406 [E0715]",
7407 "too few arguments to function '__builtin_shuffle' [E0511]",
7408 ];
7409 assert_eq!(messages.len(), wanted.len(), "{messages:?}");
7410 for (message, wanted) in messages.iter().zip(wanted) {
7411 assert!(message.ends_with(wanted), "{message}");
7412 }
7413 }
7414
7415 #[test]
7424 fn the_plain_math_names_are_the_same_mask_and_not_a_call() {
7425 let text =
7426 body(concat!("double fabs(double x);\n", "double f(double x) { return fabs(x); }\n",));
7427 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
7428 assert!(!text.contains("call"), "{text}");
7429
7430 let text =
7431 body(concat!("float fabsf(float x);\n", "float f(float x) { return fabsf(x); }\n",));
7432 assert!(text.contains("bitcast.i32 %0"), "{text}");
7433 assert!(!text.contains("call"), "{text}");
7434
7435 let text = body(concat!(
7436 "double copysign(double x, double y);\n",
7437 "double f(double x, double y) { return copysign(x, y); }\n",
7438 ));
7439 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
7440 assert!(!text.contains("call"), "{text}");
7441
7442 let text = body(concat!(
7443 "float copysignf(float x, float y);\n",
7444 "float f(float x, float y) { return copysignf(x, y); }\n",
7445 ));
7446 assert!(!text.contains("call"), "{text}");
7447
7448 let text = ir(concat!(
7452 "long double fabsl(long double x);\n",
7453 "long double f(long double x) { return fabsl(x); }\n",
7454 ));
7455 assert!(text.contains("call @fabsl"), "{text}");
7456 }
7457
7458 #[test]
7466 fn a_plain_math_name_the_program_took_is_the_programs_own_function() {
7467 let taken = concat!(
7468 "static double fabs(double b) { return 7; }\n",
7469 "double f(double x) { return fabs(x); }\n",
7470 );
7471 assert!(ir(taken).contains("call @fabs"), "a static definition is the program's own");
7472
7473 let retyped = concat!("int fabs(int b);\n", "int f(int x) { return fabs(x); }\n");
7474 assert!(ir(retyped).contains("call @fabs"), "another type is another function");
7475
7476 let plain = concat!("double fabs(double b);\n", "double f(double x) { return fabs(x); }\n");
7477 let mut opts = options();
7478 opts.emit = EmitKind::Ir;
7479 assert!(!run(&opts, plain).text().contains("call @fabs"), "the library's by default");
7480
7481 opts.builtins = false;
7482 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin");
7483
7484 opts.builtins = true;
7485 opts.no_builtin = vec!["fabs".to_owned()];
7486 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin-fabs");
7487 let one = concat!(
7488 "double copysign(double a, double b);\n",
7489 "double f(double x) { return copysign(x, 1.0); }\n",
7490 );
7491 assert!(!run(&opts, one).text().contains("call @copysign"), "one name and not the family");
7492
7493 opts.no_builtin = Vec::new();
7495 opts.builtins = false;
7496 let prefixed = "double f(double x) { return __builtin_fabs(x); }\n";
7497 assert!(!run(&opts, prefixed).text().contains("call @fabs"), "the prefix is not a library");
7498 }
7499
7500 #[test]
7509 fn the_sign_builtins_answer_a_zero_and_a_nan_the_way_the_bits_say() {
7510 let text = ir(concat!(
7511 "double a = __builtin_fabs(-3.5);\n",
7512 "double b = __builtin_copysign(1.0, -0.0);\n",
7513 "double c = __builtin_copysign(0.0, -2.0);\n",
7514 "double d = __builtin_copysign(-__builtin_nan(\"\"), 1.0);\n",
7516 "double e = __builtin_fabs(-__builtin_nan(\"0x1\"));\n",
7517 "float g = __builtin_copysignf(-0.0f, 2.0f);\n",
7518 "long double h = __builtin_copysignl(1.0L, -1.0L);\n",
7519 "long double i = __builtin_fabsl(-__builtin_infl());\n",
7520 ));
7521 assert!(text.contains("global @a : f64 = 0x400c000000000000,"), "{text}");
7522 assert!(text.contains("global @b : f64 = 0xbff0000000000000,"), "{text}");
7523 assert!(text.contains("global @c : f64 = 0x8000000000000000,"), "{text}");
7524 assert!(text.contains("global @d : f64 = 0x7ff8000000000000,"), "{text}");
7525 assert!(text.contains("global @e : f64 = 0x7ff8000000000001,"), "{text}");
7526 assert!(text.contains("global @g : f32 = 0x0,"), "{text}");
7527 assert!(text.contains("f80 0xbfff8000000000000000"), "{text}");
7528 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
7529 }
7530
7531 #[test]
7538 fn the_sign_of_a_long_double_is_in_the_word_at_the_top_of_it() {
7539 for source in [
7540 "int f(long double x) { return __builtin_signbit(x); }\n",
7541 "long double f(long double x) { return __builtin_fabsl(x); }\n",
7542 "long double f(long double x, long double y) { return __builtin_copysignl(x, y); }\n",
7543 "int f(long double x) { return __builtin_isnormal(x); }\n",
7544 ] {
7545 let text = body(source);
7546 assert!(!text.contains("i80"), "{text}");
7547 assert!(text.contains("i16"), "{text}");
7548 }
7549 }
7550
7551 #[test]
7559 fn the_complex_builtins_are_the_halves_of_the_value_and_not_a_call() {
7560 let text = body("double f(_Complex double z) { return __builtin_creal(z); }\n");
7561 assert!(!text.contains("call"), "{text}");
7562 let text = body("double f(_Complex double z) { return __builtin_cimag(z); }\n");
7563 assert!(!text.contains("call"), "{text}");
7564
7565 let text = body("_Complex double f(_Complex double z) { return __builtin_conj(z); }\n");
7568 assert_eq!(text.matches("fneg").count(), 1, "{text}");
7569 assert!(!text.contains("call"), "{text}");
7570 let negated = body("_Complex double f(_Complex double z) { return -z; }\n");
7571 assert_eq!(negated.matches("fneg").count(), 2, "{negated}");
7572
7573 let written = body("_Complex double f(_Complex double z) { return ~z; }\n");
7576 assert_eq!(written, text, "the name and the operator are the same thing");
7577
7578 let text = body(concat!(
7580 "double creal(_Complex double z);\n",
7581 "double f(_Complex double z) { return creal(z); }\n",
7582 ));
7583 assert!(!text.contains("call"), "{text}");
7584 let text = body(concat!(
7585 "_Complex float conjf(_Complex float z);\n",
7586 "_Complex float f(_Complex float z) { return conjf(z); }\n",
7587 ));
7588 assert_eq!(text.matches("fneg").count(), 1, "{text}");
7589 assert!(!text.contains("call"), "{text}");
7590
7591 let taken = concat!(
7594 "static double creal(_Complex double z) { return 7; }\n",
7595 "double f(_Complex double z) { return creal(z); }\n",
7596 );
7597 assert!(ir(taken).contains("call @creal"), "a static definition is the program's own");
7598 let retyped = concat!("int cimag(int z);\n", "int f(int z) { return cimag(z); }\n");
7599 assert!(ir(retyped).contains("call @cimag"), "another type is another function");
7600 let plain = concat!(
7601 "double cimag(_Complex double z);\n",
7602 "double f(_Complex double z) { return cimag(z); }\n",
7603 );
7604 let mut opts = options();
7605 opts.emit = EmitKind::Ir;
7606 opts.builtins = false;
7607 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin");
7608 opts.builtins = true;
7609 opts.no_builtin = vec!["cimag".to_owned()];
7610 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin-cimag");
7611
7612 let text = ir(concat!(
7614 "double a = __builtin_creal(1.5 + 2.5i);\n",
7615 "double b = __builtin_cimag(1.5 + 2.5i);\n",
7616 "_Complex double c = __builtin_conj(1.5 + 2.5i);\n",
7617 ));
7618 assert!(text.contains("global @a : f64 = 0x3ff8000000000000,"), "{text}");
7619 assert!(text.contains("global @b : f64 = 0x4004000000000000,"), "{text}");
7620 assert!(
7621 text.contains("{ f64 0x3ff8000000000000, f64 0xc004000000000000 }"),
7622 "the conjugate of a constant is the constant with the second half negated: {text}"
7623 );
7624 assert!(!text.contains("call"), "{text}");
7625 }
7626
7627 #[test]
7635 fn a_math_library_builtin_of_a_constant_is_the_answer_and_not_a_call() {
7636 let text = ir(concat!(
7637 "double a = __builtin_ceil(1.5);\n",
7638 "double b = __builtin_floor(1.5);\n",
7639 "double c = __builtin_trunc(-1.5);\n",
7640 "double d = __builtin_round(2.5);\n",
7643 "double e = __builtin_ceil(-0.5);\n",
7645 "double f = __builtin_fmax(1.0, 2.0);\n",
7646 "double g = __builtin_fmin(1.0, 2.0);\n",
7647 "float h = __builtin_ceilf(1.25f);\n",
7648 "double ceil(double x);\n",
7651 "double i = ceil(2.25);\n",
7652 ));
7653 assert!(text.contains("global @a : f64 = 0x4000000000000000,"), "{text}");
7654 assert!(text.contains("global @b : f64 = 0x3ff0000000000000,"), "{text}");
7655 assert!(text.contains("global @c : f64 = 0xbff0000000000000,"), "{text}");
7656 assert!(text.contains("global @d : f64 = 0x4008000000000000,"), "{text}");
7657 assert!(text.contains("global @e : f64 = 0x8000000000000000,"), "{text}");
7658 assert!(text.contains("global @f : f64 = 0x4000000000000000,"), "{text}");
7659 assert!(text.contains("global @g : f64 = 0x3ff0000000000000,"), "{text}");
7660 assert!(text.contains("global @h : f32 = 0x40000000,"), "{text}");
7661 assert!(text.contains("global @i : f64 = 0x4008000000000000,"), "{text}");
7662 assert!(!text.contains("call"), "{text}");
7663 }
7664
7665 #[test]
7673 fn a_math_library_builtin_of_anything_else_is_a_call_to_the_library() {
7674 let text = ir(concat!(
7675 "double f(double x) { return __builtin_ceil(x); }\n",
7676 "float g(float x) { return __builtin_floorf(x); }\n",
7677 "double h(double x, double y) { return __builtin_fmax(x, y); }\n",
7678 ));
7679 assert!(text.contains("call @ceil("), "{text}");
7680 assert!(text.contains("call @floorf("), "{text}");
7681 assert!(text.contains("call @fmax("), "{text}");
7682
7683 let text = ir(concat!(
7687 "double f(void) { return __builtin_rint(2.5); }\n",
7688 "double g(void) { return __builtin_nearbyint(2.5); }\n",
7689 ));
7690 assert!(text.contains("call @rint("), "{text}");
7691 assert!(text.contains("call @nearbyint("), "{text}");
7692
7693 let text = ir("double f(void) { return __builtin_fmin(__builtin_nan(\"\"), 1.0); }\n");
7696 assert!(text.contains("call @fmin("), "{text}");
7697
7698 let plain = concat!("double ceil(double x);\n", "double f(void) { return ceil(2.25); }\n");
7701 let mut opts = options();
7702 opts.emit = EmitKind::Ir;
7703 opts.no_builtin = vec!["ceil".to_owned()];
7704 assert!(run(&opts, plain).text().contains("call @ceil("), "-fno-builtin-ceil");
7705 }
7706
7707 #[test]
7714 fn a_constexpr_object_is_a_constant_wherever_one_is_required() {
7715 let text = ir(concat!(
7716 "constexpr int side = 4;\n",
7717 "constexpr int wider = side + 1;\n",
7718 "constexpr double half = 1.5;\n",
7719 "struct point { int x; int y; };\n",
7720 "constexpr struct point origin = { 5, 6 };\n",
7721 "int square[side * side];\n",
7722 "int rectangle[wider];\n",
7723 "int rounded[(int)half * 2];\n",
7724 "int across[origin.y];\n",
7725 "enum named { four = side };\n",
7726 "int e = four;\n",
7727 ));
7728 assert!(text.contains("global @square : bytes 64 ="), "{text}");
7729 assert!(text.contains("global @rectangle : bytes 20 ="), "{text}");
7730 assert!(text.contains("global @rounded : bytes 8 ="), "{text}");
7731 assert!(text.contains("global @across : bytes 24 ="), "{text}");
7732 assert!(text.contains("global @e : i32 = 4,"), "{text}");
7733
7734 let mut opts = options();
7737 opts.emit = EmitKind::Ir;
7738 let konst = "const int n = 1;\nint a[n];\n";
7739 let message = "/main.c:2:5: error: variably modified 'a' at file scope [E0538]";
7740 assert_eq!(run(&opts, konst).messages, [message]);
7741
7742 let subscript = "constexpr int t[3] = { 1, 2, 3 };\nint a[t[1]];\n";
7744 assert_eq!(run(&opts, subscript).messages, [message]);
7745
7746 let address = "constexpr int c = 3;\nint *p = &c;\n";
7748 let warning = "/main.c:2:6: warning: initialization discards 'const' qualifier from \
7749 pointer target type [E0514]";
7750 assert_eq!(run(&opts, address).messages, [warning]);
7751 }
7752
7753 #[test]
7762 fn a_member_whose_size_was_refused_is_not_a_flexible_array_member() {
7763 let mut opts = options();
7764 opts.emit = EmitKind::Ir;
7765
7766 let alone = "int k;\nextern struct D { int a[k]; } ed;\n";
7767 let message = "/main.c:2:23: error: variably modified 'a' at file scope [E0538]";
7768 assert_eq!(run(&opts, alone).messages, [message]);
7769
7770 let first = "int k;\nextern struct E { int a[k]; int b; } ee;\n";
7772 assert_eq!(run(&opts, first).messages, [message]);
7773
7774 let negative = "struct F { int a[-1]; };\n";
7777 let refused = "/main.c:1:18: error: size of array 'a' is negative [E0536]";
7778 assert_eq!(run(&opts, negative).messages, [refused]);
7779
7780 let flexible = "struct G { int a[]; };\n";
7783 let named = "/main.c:1:16: error: flexible array member in a struct with no named \
7784 members [E0554]";
7785 assert_eq!(run(&opts, flexible).messages, [named]);
7786 }
7787
7788 #[test]
7803 fn a_pointer_to_an_array_gains_a_qualifier_the_same_way_a_pointer_to_anything_else_does() {
7804 let mut opts = options();
7805 opts.emit = EmitKind::Ir;
7806 let prefix = "typedef unsigned int B[4];\nstruct H { B category[2]; };\n";
7807
7808 let adding = format!("{prefix}const B *f(struct H *h) {{ return &h->category[0]; }}\n");
7810 assert_eq!(run(&opts, &adding).messages, [] as [String; 0]);
7811
7812 let plain = concat!(
7815 "const unsigned int (*f(unsigned int (*p)[4]))[4] { return p; }\n",
7816 "const unsigned int (*g(unsigned int (*p)[2][3]))[2][3] { return p; }\n",
7817 );
7818 assert_eq!(run(&opts, plain).messages, [] as [String; 0]);
7819
7820 let dropping = format!("{prefix}B *f(const B *p) {{ return p; }}\n");
7823 let warning = "/main.c:3:27: warning: return discards 'const' qualifier from pointer target type \
7824 [E0514]";
7825 assert_eq!(run(&opts, &dropping).messages, [warning]);
7826
7827 let wrong = "const unsigned int (*f(unsigned short (*p)[4]))[4] { return p; }\n";
7830 let error = "/main.c:1:61: error: returning 'unsigned short (*)[4]' from a function with \
7831 incompatible return type 'const unsigned int (*)[4]' [E0512]";
7832 assert_eq!(run(&opts, wrong).messages, [error]);
7833 }
7834
7835 #[test]
7844 fn an_old_style_definition_takes_its_types_from_the_declarations_under_its_list() {
7845 let mut opts = options();
7848 opts.std = Std::C17;
7849 let source = concat!(
7850 "int add(a, b)\n",
7851 "int a;\n",
7852 "int b;\n",
7853 "{ return a + b; }\n",
7854 "int promoted(c)\n",
7855 "char c;\n",
7856 "{ return c; }\n",
7857 "int narrow(char);\n",
7858 "int narrow(c)\n",
7859 "char c;\n",
7860 "{ return c; }\n",
7861 "int first(a)\n",
7862 "int a[4];\n",
7863 "{ return a[0]; }\n",
7864 );
7865 let result = run(&opts, source);
7866 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
7867 let text = result.text();
7868 assert!(text.contains("add : int(int, int) function external defined"), "{text}");
7869 assert!(text.contains("promoted : int(int) function external defined"), "{text}");
7870 assert!(text.contains("c : char object automatic defined"), "{text}");
7872 assert!(text.contains("narrow : int(char) function external defined"), "{text}");
7873 assert!(text.contains("first : int(int *) function external defined"), "{text}");
7875 }
7876
7877 #[test]
7884 fn the_two_halves_of_an_old_style_parameter_list_have_to_agree() {
7885 let mut opts = options();
7886 opts.std = Std::C17;
7887 for (source, message) in [
7888 ("int f(a, a)\nint a;\n{ return a; }\n", "1:10: error: multiple parameters named 'a'"),
7889 (
7890 "int f(a)\nint a;\nint b;\n{ return a; }\n",
7891 "3:5: error: declaration for parameter 'b' but no such parameter",
7892 ),
7893 ("int f(a)\nint a;\nint a;\n{ return a; }\n", "3:5: error: redefinition of parameter"),
7894 ("int f(a)\nint a = 1;\n{ return a; }\n", "2:5: error: parameter 'a' is initialized"),
7895 (
7896 "int f(a)\nstatic int a;\n{ return a; }\n",
7897 "2:12: error: storage class specified for parameter 'a'",
7898 ),
7899 (
7900 "int f(char);\nint f(a)\nshort a;\n{ return a; }\n",
7901 "2:7: error: argument 'a' doesn't match prototype",
7902 ),
7903 ] {
7904 let result = run(&opts, source);
7905 assert!(result.failed(), "expected this to fail:\n{source}");
7906 assert!(result.messages[0].contains(message), "{:?}", result.messages);
7907 }
7908
7909 let implicit = "int f(a, b)\nint a;\n{ return a + b; }\n";
7912 let mut older = options();
7913 older.std = Std::C89;
7914 assert!(!run(&older, implicit).failed(), "{:?}", run(&older, implicit).messages);
7915 let result = run(&opts, implicit);
7916 assert!(
7917 result.messages[0].contains("1:10: error: type of 'b' defaults to 'int'"),
7918 "{:?}",
7919 result.messages
7920 );
7921
7922 let mut newer = options();
7926 newer.std = Std::C23;
7927 let plain = "int f(a)\nint a;\n{ return a; }\n";
7928 let result = run(&newer, plain);
7929 assert!(!result.failed(), "{:?}", result.messages);
7930 assert_eq!(
7931 result.messages,
7932 ["/main.c:1:5: warning: old-style function definition [E0412]"]
7933 );
7934 assert!(run(&opts, plain).messages.is_empty(), "and nothing to say in the dialects before");
7935 }
7936
7937 #[test]
7944 fn the_obsolete_designators_are_taken_and_are_pedantic_warnings() {
7945 let array = "int a[8] = { [3] 7 };\n";
7946 let member = "struct s { int x; } v = { x: 7 };\n";
7947 for source in [array, member] {
7948 let result = run(&options(), source);
7949 assert!(!result.failed(), "{:?}", result.messages);
7950 assert!(result.messages.is_empty(), "nothing to say: {:?}", result.messages);
7951 }
7952
7953 let mut asked = options();
7954 asked.pedantic = true;
7955 assert_eq!(
7956 run(&asked, array).messages,
7957 ["/main.c:1:18: warning: obsolete designator, write `[i] =` instead [E0415]"]
7958 );
7959 assert_eq!(
7960 run(&asked, member).messages,
7961 ["/main.c:1:27: warning: obsolete designator, write `.field =` instead [E0413]"]
7962 );
7963 }
7964
7965 #[test]
7972 fn a_type_is_refused_when_it_passes_the_largest_object_and_not_before() {
7973 let text = ir(concat!(
7974 "struct huge_struct { short buf[(1L << 62) - 256]; int a, b, c, d; };\n",
7975 "struct brim { char buf[9223372036854775807L]; };\n",
7976 "struct bitty { char buf[9223372036854775800L]; int x : 1; };\n",
7977 "unsigned long h = sizeof(struct huge_struct);\n",
7978 "unsigned long b = sizeof(struct brim);\n",
7979 "unsigned long y = sizeof(struct bitty);\n",
7980 ));
7981 assert!(text.contains("global @h : i64 = 9223372036854775312,"), "{text}");
7982 assert!(text.contains("global @b : i64 = 9223372036854775807,"), "{text}");
7983 assert!(text.contains("global @y : i64 = 9223372036854775804,"), "{text}");
7984
7985 let mut opts = options();
7986 opts.emit = EmitKind::Ir;
7987 let over = "struct over { char buf[9223372036854775800L]; char x[8]; };\n";
7988 let message = "/main.c:1:1: error: type 'struct over' is too large [E0560]";
7989 assert_eq!(run(&opts, over).messages, [message]);
7990 let array = "struct wide { short buf[1L << 62]; };\n";
7991 let message = "/main.c:1:25: error: size of array 'buf' exceeds \
7992 maximum object size '9223372036854775807' [E0537]";
7993 assert_eq!(run(&opts, array).messages[0], message);
7994 }
7995
7996 fn compile_bytes(source: &[u8]) -> Compiled {
8001 let mut opts = options();
8002 opts.emit = EmitKind::Ir;
8003 let mut fs = MemoryFileSystem::new();
8004 fs.insert("/main.c", source.to_vec());
8005 compile(&opts, "/main.c", &fs)
8006 }
8007
8008 #[test]
8015 fn a_byte_that_is_not_a_character_is_kept_in_a_literal_and_refused_outside_one() {
8016 let mut source = b"char s[] = \"a".to_vec();
8017 source.push(0xff);
8018 source.extend_from_slice(b"b\";\nchar c = '");
8019 source.push(0xff);
8020 source.extend_from_slice(b"';\n");
8021 let result = compile_bytes(&source);
8022 assert_eq!(result.messages, Vec::<String>::new(), "a raw byte in a literal is that byte");
8023 assert!(result.text().contains(r#"bytes "a\ffb\00""#), "{}", result.text());
8024 assert!(result.text().contains("global @c : i8 = -1,"), "{}", result.text());
8026
8027 let mut stray = b"int a".to_vec();
8028 stray.push(0xff);
8029 stray.extend_from_slice(b" = 1;\n");
8030 let result = compile_bytes(&stray);
8031 assert!(
8032 result.messages.iter().any(|m| m.contains("source is not valid UTF-8 here")),
8033 "{:?}",
8034 result.messages
8035 );
8036 }
8037
8038 #[test]
8039 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
8040 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
8041 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
8042 let expected = "\
8043func @add(i32, i32) -> i32, linkage(external) {
8044block0(%0: i32, %1: i32):
8045 %2 = add.nsw %0, %1
8046 return %2
8047}
8048";
8049 assert!(text.contains(expected), "{text}");
8050 }
8051
8052 #[test]
8053 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
8054 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
8055 assert!(!text.contains("alloca"), "{text}");
8056 assert!(!text.contains("load"), "{text}");
8057 assert!(!text.contains("store"), "{text}");
8058 }
8059
8060 #[test]
8061 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
8062 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
8063 let expected = "\
8064block0:
8065 %0 = alloca, size 4, align 4
8066 %1 = iconst.i32 1
8067 store %1 -> %0, align 4, tbaa !1
8068 %2 = call @g(%0) : (ptr) -> i32
8069 return %2
8070";
8071 assert_eq!(text, expected);
8072 }
8073
8074 #[test]
8075 fn a_loop_carries_what_it_changes_as_block_parameters() {
8076 let text = body(
8079 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
8080 return total;\n}\n",
8081 );
8082 assert!(!text.contains("alloca"), "{text}");
8083 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
8084 assert!(text.contains("jump block1("), "{text}");
8085 }
8086
8087 #[test]
8088 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
8089 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
8090 assert!(text.contains("icmp slt %0, %1"), "{text}");
8091 assert!(!text.contains("zext"), "{text}");
8092 }
8093
8094 #[test]
8095 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
8096 let text = body("int f(int a, int b) { return a && b; }\n");
8097 let expected = "\
8098block0(%0: i32, %1: i32):
8099 %2 = iconst.i32 0
8100 %3 = icmp ne %0, %2
8101 %4 = iconst.i1 0
8102 br_if %3, block1, block2(%4)
8103
8104block1:
8105 %5 = iconst.i32 0
8106 %6 = icmp ne %1, %5
8107 jump block2(%6)
8108
8109block2(%7: i1):
8110 %8 = zext.i32 %7
8111 return %8
8112";
8113 assert_eq!(text, expected);
8114 }
8115
8116 #[test]
8117 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
8118 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
8119 assert!(!text.contains("block3"), "{text}");
8122 assert!(!text.contains("iconst.i32 3"), "{text}");
8123 }
8124
8125 #[test]
8126 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
8127 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
8128 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
8129 assert!(body("int f(void) { }\n").contains("unreachable"));
8130 }
8131
8132 #[test]
8133 fn a_structure_is_copied_rather_than_held_in_a_value() {
8134 let text = body(
8135 "struct point { int x, y; };\n\
8136 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
8137 );
8138 assert!(text.contains("memcpy"), "{text}");
8139 }
8140
8141 #[test]
8142 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
8143 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
8144 assert!(text.contains("memset"), "{text}");
8145 }
8146
8147 #[test]
8148 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
8149 let text = body(
8150 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
8151 default: r = 4; } return r; }\n",
8152 );
8153 let expected = "\
8154block0(%0: i32):
8155 %1 = iconst.i32 0
8156 switch %0, block1, [1 => block2, 2 => block3(%1)]
8157
8158block1:
8159 %2 = iconst.i32 4
8160 jump block4(%2)
8161
8162block2:
8163 %3 = iconst.i32 1
8164 jump block3(%3)
8165
8166block3(%4: i32):
8167 %5 = iconst.i32 2
8168 %6 = add.nsw %4, %5
8169 jump block4(%6)
8170
8171block4(%7: i32):
8172 return %7
8173";
8174 assert_eq!(text, expected);
8175 }
8176
8177 #[test]
8178 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
8179 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
8182 assert!(text.contains("%2 = sub %0, %1"), "{text}");
8183 assert!(text.contains("icmp ule"), "{text}");
8184 assert!(!text.contains("switch"), "{text}");
8185 }
8186
8187 #[test]
8188 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
8189 let text = body(
8190 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
8191 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
8192 );
8193 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
8196 assert!(text.contains("block5:\n jump block7("), "{text}");
8197 assert!(text.contains("block6:\n jump block8("), "{text}");
8198 }
8199
8200 #[test]
8201 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
8202 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
8203 }
8204
8205 #[test]
8206 fn a_label_a_loop_is_only_entered_through_builds_the_loop_around_it() {
8207 let text = body(
8212 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
8213 return n; }\n",
8214 );
8215 assert!(text.contains("switch %0, block1(%1), [1 => block2, 2 => block3(%1)]"), "{text}");
8218 assert!(text.contains("block3(%3: i32):\n %4 = iconst.i32 1"), "{text}");
8219 assert!(text.contains("block4:\n jump block3("), "{text}");
8220 }
8221
8222 #[test]
8223 fn a_goto_into_a_loop_body_enters_it_without_the_test() {
8224 let text = body("int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n");
8227 assert!(text.starts_with("block0(%0: i32, %1: i32):\n jump block1(%1)"), "{text}");
8228 assert!(text.contains("block1(%2: i32):\n %3 = iconst.i32 1"), "{text}");
8229 assert!(text.contains("br_if %6, block2, block3"), "{text}");
8230 }
8231
8232 #[test]
8233 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
8234 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
8235 assert!(!text.contains("alloca"), "{text}");
8239 assert!(text.contains("block2(%4: i32):\n return %4"), "{text}");
8240 assert_eq!(text.matches("jump block2(").count(), 2, "{text}");
8241 }
8242
8243 #[test]
8244 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
8245 let text =
8246 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
8247 assert!(!text.contains("alloca"), "{text}");
8248 assert!(text.contains("block1(%2: i32):"), "{text}");
8249 assert!(text.contains("jump block1(%5)"), "{text}");
8250 }
8251
8252 #[test]
8253 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
8254 assert_eq!(
8257 body("int f(int x) { return x; spare: return 0; }\n"),
8258 "block0(%0: i32):\n return %0\n"
8259 );
8260 }
8261
8262 #[test]
8263 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
8264 let text = body(
8265 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
8266 );
8267 assert_eq!(
8270 text,
8271 "\
8272block0(%0: ptr):
8273 %1 = load.i8 %0, align 1
8274 %2 = iconst.i8 3
8275 %3 = ashr %1, %2
8276 %4 = sext.i32 %3
8277 return %4
8278"
8279 );
8280 }
8281
8282 #[test]
8283 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
8284 let text =
8288 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
8289 assert_eq!(
8290 text,
8291 "\
8292block0(%0: ptr, %1: i32):
8293 %2 = iconst.i32 16777215
8294 %3 = and %1, %2
8295 %4 = trunc.i16 %3
8296 store %4 -> %0, align 2
8297 %5 = iconst.i32 16
8298 %6 = lshr %3, %5
8299 %7 = trunc.i8 %6
8300 %8 = iconst.i64 2
8301 %9 = ptr_add %0, %8
8302 store %7 -> %9, align 1
8303 return
8304"
8305 );
8306 }
8307
8308 #[test]
8309 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
8310 let text =
8311 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
8312 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
8315 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
8316 }
8317
8318 #[test]
8319 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
8320 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
8323 assert_eq!(text.matches("ashr").count(), 0, "{text}");
8324 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
8325 }
8326
8327 #[test]
8328 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
8329 let text = body(
8333 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
8334 );
8335 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
8336 }
8337
8338 #[test]
8339 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
8340 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
8343 assert!(
8344 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
8345 "{text}"
8346 );
8347 }
8348
8349 #[test]
8350 fn an_initialized_flexible_array_member_makes_the_object_larger_than_its_type() {
8351 let text = ir(concat!(
8356 "struct a { int i; int j[]; } x = { 1, { 2, 0, 2, 3 } };\n",
8357 "struct b { char c; char p[]; } y = { 'o', \"wx\" };\n",
8358 "struct c { char c; char p[]; } z = { '9', { 'e', 'b' } };\n",
8359 "char s[2] = \"hi\";\n",
8360 ));
8361 assert!(
8362 text.contains("global @x : bytes 20 = { i32 1, i32 2, i32 0, i32 2, i32 3 }"),
8363 "{text}"
8364 );
8365 assert!(text.contains("global @y : bytes 4 = { i8 111, bytes \"wx\\00\" }"), "{text}");
8366 assert!(text.contains("global @z : bytes 3 = { i8 57, i8 101, i8 98 }"), "{text}");
8367 assert!(text.contains("global @s : bytes 2 = { bytes \"hi\" }"), "{text}");
8370 }
8371
8372 #[test]
8373 fn a_definition_takes_a_parameter_it_left_unnamed() {
8374 let text = ir("int f(int a, int) { return a; }\n");
8378 assert!(text.contains("func @f(i32, i32) -> i32"), "{text}");
8379 assert!(text.contains("block0(%0: i32, %1: i32):"), "{text}");
8380
8381 let text = ir("int g(int, int n) { return n; }\n");
8384 assert!(text.contains("block0(%0: i32, %1: i32):\n return %1\n"), "{text}");
8385 }
8386
8387 #[test]
8388 fn an_assignment_of_a_structure_is_the_object_it_wrote() {
8389 let text = body(concat!(
8394 "struct s { int f; int g; };\n",
8395 "void h(struct s *a, struct s *c, struct s *d, struct s *e)\n",
8396 "{ *d = *e = a[0] = *c; }\n",
8397 ));
8398 assert_eq!(text.matches("memcpy").count(), 3, "{text}");
8399 assert!(text.contains("memcpy %8, %1, size 8, align 4\n"), "{text}");
8400 assert!(text.contains("memcpy %3, %8, size 8, align 4\n"), "{text}");
8401 assert!(text.contains("memcpy %2, %3, size 8, align 4\n"), "{text}");
8402 }
8403
8404 #[test]
8405 fn a_string_literal_stops_at_the_end_of_the_array_it_is_filling() {
8406 let mut opts = options();
8411 opts.emit = EmitKind::Ir;
8412 let result = run(
8413 &opts,
8414 concat!(
8415 "const char a[2][3] = { \"1234\", \"xyz\" };\n",
8416 "static const char b[3][5] = { \"12345\", \"678\", \"9\" };\n",
8417 "union u { struct { char x[4]; char y[4]; }; struct { char z[8]; }; };\n",
8418 "const union u c = { { \"1234\", \"567\" } };\n",
8419 ),
8420 );
8421 let text = result.text();
8422 assert_eq!(
8423 result.messages,
8424 ["/main.c:1:24: warning: initializer-string for array of 'const char' is too long \
8425 (5 chars into 3 available) [E0637]"]
8426 );
8427 assert!(text.contains("global @a : bytes 6 = { bytes \"123\", bytes \"xyz\" }"), "{text}");
8428 assert!(
8429 text.contains(
8430 "global @b : bytes 15 = { bytes \"12345\", bytes \"678\\00\", zero 1, \
8431 bytes \"9\\00\", zero 3 }"
8432 ),
8433 "{text}"
8434 );
8435 assert!(
8438 text.contains("global @c : bytes 8 = { bytes \"1234\", bytes \"567\\00\" }"),
8439 "{text}"
8440 );
8441 }
8442
8443 #[test]
8444 fn a_cast_of_a_record_to_its_own_type_is_the_object_that_was_cast() {
8445 let text = body(concat!(
8449 "struct s { int a, b; };\nstruct v { struct s s; int t; };\n",
8450 "void g(struct v *);\n",
8451 "void f(struct s *p) { struct v w = { (struct s)*p, 5 }; g(&w); }\n",
8452 ));
8453 assert_eq!(text.matches("memcpy").count(), 1, "{text}");
8454 }
8455
8456 #[test]
8457 fn a_compound_literal_read_in_a_static_initializer_lays_its_bytes_into_the_image() {
8458 let text = ir(concat!(
8463 "struct s { int x; };\n",
8464 "struct t { struct s s; int o; } a = { (struct s){ 2 }, 3 };\n",
8465 "int n = (int){ 7 };\n",
8466 "struct u { struct s p; struct s q; } b = { (struct s){ 1 }, (struct s){ } };\n",
8467 ));
8468 assert!(text.contains("global @a : bytes 8 = { i32 2, i32 3 }"), "{text}");
8469 assert!(text.contains("global @n : i32 = 7,"), "{text}");
8470 assert!(text.contains("global @b : bytes 8 = { i32 1, zero 4 }"), "{text}");
8473 }
8474
8475 #[test]
8476 fn the_address_of_a_compound_literal_asks_for_the_object_it_points_at() {
8477 let text = ir("struct s { int x; };\nstruct s *q = &(struct s){ 9 };\n");
8481 assert!(text.contains("global @.Lanon.0 : i32 = 9, align 4, linkage(internal)"), "{text}");
8482 assert!(text.contains("global @q : bytes 8 = { addr.8 @.Lanon.0 }"), "{text}");
8483 }
8484
8485 #[test]
8486 fn an_object_of_no_size_at_all_has_an_image_with_nothing_in_it() {
8487 let text = ir("unsigned char foo[1][0];\n");
8491 assert!(text.contains("global @foo : bytes 0 = {}, align 1"), "{text}");
8492 }
8493
8494 #[test]
8495 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
8496 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
8499 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
8500 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
8501 }
8502
8503 #[test]
8504 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
8505 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
8509 assert!(
8510 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
8511 "{text}"
8512 );
8513 }
8514
8515 #[test]
8516 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
8517 let text = body(
8522 "\
8523struct s { int a, b; };
8524struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
8525",
8526 );
8527 assert!(text.contains("block3(%7: ptr)"), "{text}");
8529 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
8530 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
8531 }
8532
8533 #[test]
8541 fn the_left_side_of_a_conditional_with_no_middle_is_evaluated_once() {
8542 let text = body("int f(int i) { return ++i ?: 10; }\n");
8543 assert!(text.contains("jump block3(%2)"), "the arm is the value that was tested: {text}");
8544 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
8545
8546 let text = body("long f(int i) { return ++i ?: 10L; }\n");
8549 assert!(text.contains("%5 = sext.i64 %2"), "the arm widens what was tested: {text}");
8550 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
8551
8552 let text = body("int g(void);\nint f(void) { return g() ?: 10; }\n");
8554 assert_eq!(text.matches("call @g").count(), 1, "called once: {text}");
8555
8556 let text = body("int f(int i) { return ++i ? ++i : 10; }\n");
8559 assert_eq!(text.matches("add.nsw").count(), 2, "incremented twice: {text}");
8560 }
8561
8562 #[test]
8563 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
8564 let text = ir("\
8568struct pair { int a, b; };
8569struct pair make(int a, int b);
8570struct pair twice(struct pair p) { return make(p.a, p.b); }
8571");
8572 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
8573 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
8574 }
8575
8576 #[test]
8577 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
8578 let text = ir("\
8582struct big { double v[8]; };
8583struct big grow(struct big b);
8584struct big twice(struct big b) { return grow(grow(b)); }
8585");
8586 assert!(
8587 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
8588 "{text}"
8589 );
8590 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
8591 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
8594 }
8595
8596 #[test]
8597 fn a_structure_passed_to_a_variadic_function_says_so_at_the_call() {
8598 let text = ir("\
8603struct big { double v[8]; };
8604struct pair { int a, b; };
8605int p(const char *, ...);
8606int f(struct big b, struct pair q) { return p(\"\", 1, b, q); }
8607");
8608 assert!(
8609 text.contains("call @p(%4, %5, %2 byval(64, align 8), %6) : (ptr, ...) -> i32"),
8610 "{text}"
8611 );
8612 }
8613
8614 #[test]
8615 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
8616 let body = body(
8619 "\
8620struct pair { int a, b; };
8621struct pair make(int a, int b);
8622int second(void) { return make(1, 2).b; }
8623",
8624 );
8625 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
8626 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
8627 }
8628
8629 #[test]
8630 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
8631 let source = "\
8635struct hfa { float x, y, z; };
8636int take(struct hfa h);
8637int give(struct hfa h) { return take(h); }
8638";
8639 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
8640 let mut opts = options();
8641 opts.emit = EmitKind::Ir;
8642 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
8643 let result = run(&opts, source);
8644 assert_eq!(result.messages, Vec::<String>::new());
8645 assert!(result.text().contains("func @take(f32, f32, f32) -> i32"), "{}", result.text());
8646 }
8647
8648 #[test]
8649 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
8650 let source = "\
8653int use(int *);
8654void f(int n) {
8655 {
8656 int a[n];
8657 use(a);
8658 }
8659 use(0);
8660}
8661";
8662 let body = body(source);
8663 assert!(body.contains("mul.nsw"), "{body}");
8664 assert!(body.contains("stacksave"), "{body}");
8665 assert!(body.contains("alloca %"), "{body}");
8666 assert!(body.contains("stackrestore"), "{body}");
8667 }
8668
8669 #[test]
8670 fn a_goto_out_of_the_scope_of_one_gives_its_stack_back_on_the_way() {
8671 let source = "\
8676int use(int *);
8677int f(int n) {
8678 {
8679 int a[n];
8680 if (use(a)) goto out;
8681 use(0);
8682 }
8683out:
8684 return 0;
8685}
8686";
8687 let body = body(source);
8688 assert_eq!(body.matches("stackrestore").count(), 2, "{body}");
8690 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
8691 assert!(after.starts_with(" %4\n jump block"), "{body}");
8692 }
8693
8694 #[test]
8695 fn a_goto_to_a_label_the_array_is_still_alive_at_leaves_the_stack_alone() {
8696 let source = "\
8700int use(int *);
8701int f(int n) {
8702 int a[n];
8703again:
8704 if (use(a)) goto again;
8705 return 0;
8706}
8707";
8708 let body = body(source);
8709 assert!(body.contains("stacksave"), "{body}");
8710 assert!(!body.contains("stackrestore"), "{body}");
8711 }
8712
8713 #[test]
8714 fn a_goto_back_to_a_label_in_front_of_one_gives_it_back_every_time_round() {
8715 let source = "\
8720int use(int *);
8721int f(int n) {
8722again:
8723 {
8724 int a[n];
8725 if (use(a)) goto again;
8726 }
8727 return 0;
8728}
8729";
8730 let body = body(source);
8731 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
8732 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
8733 assert!(after.starts_with(" %4\n jump block1\n"), "{body}");
8734 }
8735
8736 #[test]
8737 fn the_head_of_a_for_loop_is_a_scope_that_closes_where_the_loop_is_left() {
8738 let source = "\
8744int f(void);
8745void t(void) {
8746 int count = 10;
8747 for (; count--;) {
8748 int b[f()];
8749 int i;
8750 for (i = 0; i < f(); i++) {
8751 b[i] = count;
8752 }
8753 }
8754}
8755";
8756 let body = body(source);
8757 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
8761 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
8762 let next = after.split("\n\n").next().expect("the block the restore is in");
8765 assert!(next.contains("jump block1("), "{body}");
8766 }
8767
8768 #[test]
8769 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
8770 let source = "\
8773unsigned long f(int n) {
8774 int a[n];
8775 n = 0;
8776 return sizeof a;
8777}
8778";
8779 let body = body(source);
8780 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
8782 }
8783
8784 #[test]
8785 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
8786 let source = "\
8789int use(int);
8790int f(int x) {
8791 return ({
8792 int t = use(x);
8793 t * t;
8794 });
8795}
8796";
8797 let expected = "\
8798block0(%0: i32):
8799 %1 = call @use(%0) : (i32) -> i32
8800 %2 = mul.nsw %1, %1
8801 return %2
8802";
8803 assert_eq!(body(source), expected);
8804 }
8805
8806 #[test]
8807 fn a_comma_whose_value_is_an_object_names_the_object_the_right_side_named() {
8808 let source = "\
8812struct pair { int a, b; };
8813void bail(void);
8814int f(struct pair p) {
8815 return (bail(), p).b;
8816}
8817";
8818 let expected = "\
8819block0(%0: i64):
8820 %1 = alloca, size 8, align 4
8821 store %0 -> %1, align 4
8822 call @bail() : ()
8823 %2 = iconst.i64 4
8824 %3 = ptr_add %1, %2
8825 %4 = load.i32 %3, align 4, tbaa !1
8826 return %4
8827";
8828 assert_eq!(body(source), expected);
8829 }
8830
8831 #[test]
8832 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
8833 let source = "int f(int x) { return ({ return x; 0; }); }\n";
8837 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
8838 }
8839
8840 #[test]
8841 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
8842 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
8846 let expected = "\
8847block0(%0: ptr):
8848 %1 = va_arg.f64 %0
8849 %2 = va_arg.f64 %0
8850 %3 = fadd %1, %2
8851 return %3
8852";
8853 assert_eq!(body(source), expected);
8854 }
8855
8856 #[test]
8857 fn one_that_reads_a_structure_answers_where_the_object_is() {
8858 let source = "\
8872struct s { int a; long b; };
8873long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.b; }
8874";
8875 let expected = "\
8876block0(%0: ptr):
8877 %1 = alloca, size 16, align 16
8878 %2 = va_object %0, size 16, align 8, in(int 8 at 0, int 8 at 8)
8879 memcpy %1, %2, size 16, align 8
8880 %3 = iconst.i64 8
8881 %4 = ptr_add %1, %3
8882 %5 = load.i64 %4, align 8, tbaa !1
8883 return %5
8884";
8885 assert_eq!(body(source), expected);
8886 }
8887
8888 #[test]
8892 fn the_classification_says_which_registers_the_object_arrived_in() {
8893 let source = "\
8894struct s { double a; double b; };
8895double f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a; }
8896";
8897 assert!(
8898 body(source)
8899 .contains("va_object %0, size 16, align 8, in(float f64 at 0, float f64 at 8)"),
8900 "{}",
8901 body(source)
8902 );
8903
8904 let big = "\
8905struct s { long a[4]; };
8906long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a[0]; }
8907";
8908 assert!(body(big).contains("va_object %0, size 32, align 8\n"), "{}", body(big));
8909 }
8910
8911 #[test]
8912 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
8913 let source = "\
8917int f(int c) {
8918 void *p = c ? &&one : &&two;
8919 goto *p;
8920one:
8921 return 1;
8922two:
8923 return 2;
8924}
8925";
8926 let expected = "\
8927block0(%0: i32):
8928 %1 = iconst.i32 0
8929 %2 = icmp ne %0, %1
8930 br_if %2, block1, block2
8931
8932block1:
8933 %3 = block_addr block3
8934 jump block4(%3)
8935
8936block2:
8937 %4 = block_addr block5
8938 jump block4(%4)
8939
8940block3:
8941 %5 = iconst.i32 1
8942 return %5
8943
8944block4(%6: ptr):
8945 indirect_br %6, block3, block5
8946
8947block5:
8948 %7 = iconst.i32 2
8949 return %7
8950";
8951 assert_eq!(body(source), expected);
8952 }
8953
8954 fn dispatch(labels: usize) -> String {
8957 let mask = labels - 1;
8958 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
8959 for index in 0..labels {
8960 source.push_str(&format!(" &&a{index},"));
8961 }
8962 source.push_str(" };\n\tint w = n, x = n + 1, y = n + 2, z = n + 3;\n");
8963 source.push_str(&format!("\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
8964 for index in 0..labels {
8965 let step = match index % 4 {
8966 0 => "w += x;",
8967 1 => "x += y;",
8968 2 => "y += z;",
8969 _ => "z += w;",
8970 };
8971 source.push_str(&format!("a{index}:\n\t{step}\n"));
8972 source.push_str("\tif (--n <= 0) return w + x + y + z;\n");
8973 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
8974 }
8975 source.push_str("}\n");
8976 source
8977 }
8978
8979 fn in_front_of_the_jump(text: &str) -> usize {
8981 let (before, _) = text.split_once("\tjmp\t*%").expect("a jump through a register");
8982 before.lines().rev().take_while(|line| line.starts_with("\tmov")).count()
8983 }
8984
8985 #[test]
8995 fn a_jump_through_a_register_writes_what_it_carries_and_not_the_whole_table() {
8996 let small = in_front_of_the_jump(&asm(&dispatch(4)));
8997 let large = in_front_of_the_jump(&asm(&dispatch(32)));
8998 assert_eq!(small, large, "eight times the labels and the same values in hand");
8999 assert!(large <= 8, "the values the loop keeps, and not a set of them per label: {large}");
9000 }
9001
9002 fn crowded(labels: usize) -> String {
9005 const VALUES: usize = 24;
9006 let mask = labels - 1;
9007 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
9008 for index in 0..labels {
9009 source.push_str(&format!(" &&a{index},"));
9010 }
9011 source.push_str(" };\n\t");
9012 for value in 0..VALUES {
9013 source.push_str(&format!("int v{value} = n + {value}; "));
9014 }
9015 let sum: Vec<String> = (0..VALUES).map(|value| format!("v{value}")).collect();
9016 source.push_str(&format!("\n\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
9017 for index in 0..labels {
9018 let (to, from) = (index % VALUES, (index + 1) % VALUES);
9019 source.push_str(&format!("a{index}:\n\tv{to} += v{from};\n"));
9020 source.push_str(&format!("\tif (--n <= 0) return {};\n", sum.join(" + ")));
9021 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
9022 }
9023 source.push_str("}\n");
9024 source
9025 }
9026
9027 fn the_frame(text: &str) -> u64 {
9029 text.lines()
9030 .find_map(|line| {
9031 let (size, _) = line.strip_prefix("\tsubq\t$")?.split_once(", %rsp")?;
9032 size.parse().ok()
9033 })
9034 .expect("a function that opens a frame")
9035 }
9036
9037 #[test]
9046 fn a_frame_holds_what_is_wanted_at_once_and_not_a_slot_for_every_label() {
9047 let small = the_frame(&asm(&crowded(16)));
9048 let large = the_frame(&asm(&crowded(64)));
9049 assert_eq!(small, large, "four times the labels and the same values: {small}, {large}");
9050 }
9051
9052 #[test]
9061 fn a_template_that_names_its_own_registers_gets_the_ones_it_named() {
9062 let source = "void save(void *nlr) {
9063 __asm volatile (
9064 \"movq %%rsp, 32(%%rdi) \\n\"
9065 \"movq %%rbx, 40(%%rdi) \\n\"
9066 \"movq %%r12, 48(%%rdi) \\n\"
9067 : : \"D\" (nlr) : \"memory\");
9068}
9069";
9070 let text = asm(source);
9071 assert!(text.contains("\tmovq\t%rsp, 32(%rdi)\n"), "{text}");
9072 assert!(text.contains("\tmovq\t%rbx, 40(%rdi)\n"), "{text}");
9073 assert!(text.contains("\tmovq\t%r12, 48(%rdi)\n"), "{text}");
9074 }
9075
9076 #[test]
9077 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
9078 let source = "void **next(void);
9081void f(void) { goto *next(); }
9082";
9083 let expected = "\
9084block0:
9085 %0 = call @next() : () -> ptr
9086 unreachable
9087";
9088 assert_eq!(body(source), expected);
9089 }
9090
9091 #[test]
9092 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
9093 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
9096 let expected = "\
9097block0:
9098 inline_asm.volatile \"mfence\", \"\", \"memory\"()
9099 return
9100";
9101 assert_eq!(body(source), expected);
9102 }
9103
9104 #[test]
9105 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
9106 let source = "\
9109int f(int x, int y) {
9110 int r;
9111 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
9112 return r + y;
9113}
9114";
9115 let expected = "\
9116block0(%0: i32, %1: i32):
9117 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
9118 %4 = add.nsw %2, %3
9119 return %4
9120";
9121 assert_eq!(body(source), expected);
9122 }
9123
9124 #[test]
9125 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
9126 let source = "\
9131struct pair { int a, b; };
9132int f(int x) {
9133 int slot = x;
9134 struct pair p = { x, x };
9135 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
9136 return slot + p.a;
9137}
9138";
9139 let text = body(source);
9140 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
9141 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
9142 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
9143 }
9144
9145 #[test]
9146 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
9147 let source = "\
9152int f(int x) {
9153 int r = 7;
9154 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
9155 return r;
9156away:
9157 return r;
9158}
9159";
9160 let expected = "\
9161block0(%0: i32):
9162 %1 = iconst.i32 7
9163 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
9164
9165block1:
9166 return %2
9167
9168block2:
9169 return %1
9170";
9171 assert_eq!(body(source), expected);
9172 }
9173
9174 #[test]
9175 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
9176 let mut opts = options();
9180 opts.emit = EmitKind::Ir;
9181 for (source, expected) in [
9182 (
9183 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
9184 "output operand constraint lacks '='",
9185 ),
9186 (
9187 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
9188 "lvalue required in 'asm' statement",
9189 ),
9190 (
9191 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
9192 "read-only variable 'g' used as 'asm' output",
9193 ),
9194 (
9195 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
9196 "input operand constraint contains '='",
9197 ),
9198 (
9199 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
9200 "memory input 0 is not directly addressable",
9201 ),
9202 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
9203 (
9204 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
9205 "duplicate asm operand name 'a'",
9206 ),
9207 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
9208 ] {
9209 let result = run(&opts, source);
9210 assert!(result.failed(), "expected this to be reported:\n{source}");
9211 assert!(
9212 result.messages.iter().any(|m| m.contains(expected)),
9213 "{expected}\n{:?}",
9214 result.messages
9215 );
9216 }
9217 }
9218
9219 #[test]
9224 fn an_asm_at_file_scope_that_is_directives_becomes_the_objects_it_defines() {
9225 let text = ir(concat!(
9226 "__asm__(\n",
9227 " \".section .rodata\\n\"\n",
9228 " \".globl first\\n\"\n",
9229 " \".balign 8\\n\"\n",
9230 " \"first:\\n\"\n",
9231 " \".long 1\\n\"\n",
9232 " \".long 2\\n\"\n",
9233 " \".globl last\\n\"\n",
9234 " \"last:\\n\"\n",
9235 " \".quad last - first\\n\");\n",
9236 "extern const int first[];\n",
9237 "extern const long last;\n",
9238 ));
9239 assert!(text.contains("global @first : bytes 8 = { i32 1, i32 2 }, align 8"), "{text}");
9240 assert!(text.contains("global @last : i64 = 8"), "{text}");
9241 }
9242
9243 #[test]
9247 fn a_name_an_asm_at_file_scope_defined_is_not_undone_by_a_declaration_of_it() {
9248 let text = ir(concat!(
9249 "__asm__(\".data\\n.globl counter\\ncounter:\\n.long 7\\n\");\n",
9250 "extern int counter;\n",
9251 "int read(void) { return counter; }\n",
9252 ));
9253 assert!(text.contains("global @counter : i32 = 7"), "{text}");
9254 }
9255
9256 #[test]
9261 fn bytes_under_no_label_at_file_scope_are_a_global_in_front_of_the_label() {
9262 let text = ir(concat!(
9263 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n662:\\n",
9264 ".pushsection .data.ignore\\n.byte 7\\n.popsection\\n.byte 662b - 661b\\n\");\n",
9265 "extern unsigned char stuff[];\n",
9266 "int read(void) { return stuff[0]; }\n",
9267 ));
9268 let under = text.find("global @.Lasm.0 : i8 = 41").expect(&text);
9269 let named = text.find("global @stuff : i8 = 42").expect(&text);
9270 assert!(under < named, "the bytes under no label come first: {text}");
9271 assert!(text.contains("global @.Lasm.1 : i8 = 7, align 1, linkage(internal), section"));
9272 let after = text.find("global @.Lasm.2 : i8 = 1").expect(&text);
9277 assert!(named < after, "{text}");
9278 }
9279
9280 #[test]
9285 fn a_distance_from_here_at_file_scope_is_a_hole_naming_the_global_it_measures_to() {
9286 let text = ir(concat!(
9287 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n",
9288 ".pushsection .data.ignore\\n.long 661b - .\\n.popsection\\n\");\n",
9289 "extern unsigned char stuff[];\n",
9290 "int read(void) { return stuff[0]; }\n",
9291 ));
9292 assert!(text.contains("global @.Lasm.1 : bytes 4 = { away.4 @stuff }"), "{text}");
9296 }
9297
9298 #[test]
9303 fn a_set_at_file_scope_is_a_second_name_for_what_it_names() {
9304 let text = ir(concat!(
9305 "void base(void) {}\n",
9306 "__asm__(\".weak one\\n.set one, base\");\n",
9307 "__asm__(\".globl two\\n.set two, base\");\n",
9308 "__asm__(\".set three, base\");\n",
9309 "void three(void) {}\n",
9310 ));
9311 assert!(text.contains("alias @one = @base, linkage(weak)"), "{text}");
9312 assert!(text.contains("alias @two = @base"), "{text}");
9313 assert!(!text.contains("alias @three"), "a definition of the name wins: {text}");
9314 assert!(text.contains("func @three"), "{text}");
9315 }
9316
9317 #[test]
9322 fn a_set_of_a_name_this_file_does_not_define_says_so() {
9323 let messages = errors("__asm__(\".set here, elsewhere\");\n");
9324 assert!(
9325 messages
9326 .iter()
9327 .any(|m| m.contains("'here' is aliased to undefined symbol 'elsewhere'")
9328 && m.contains("E0697")),
9329 "{messages:?}"
9330 );
9331 }
9332
9333 #[test]
9336 fn an_incbin_at_file_scope_is_the_bytes_of_the_file_it_names() {
9337 let mut opts = options();
9338 opts.emit = EmitKind::Ir;
9339 let mut fs = MemoryFileSystem::new();
9340 fs.insert(
9341 "/main.c",
9342 b"__asm__(\".data\\n.globl blob\\nblob:\\n.incbin \\\"seed\\\"\\n\");\n".to_vec(),
9343 );
9344 fs.insert("seed", b"hi".to_vec());
9345 let result = compile(&opts, "/main.c", &fs);
9346 assert_eq!(result.messages, Vec::<String>::new());
9347 let text = result.text();
9348 assert!(text.contains("global @blob : bytes 2 = { bytes \"hi\" }"), "{text}");
9349 }
9350
9351 #[test]
9354 fn an_incbin_naming_a_file_that_is_not_there_says_which_file() {
9355 let messages = errors("__asm__(\".data\\nb:\\n.incbin \\\"nowhere\\\"\\n\");\n");
9356 assert!(
9357 messages
9358 .iter()
9359 .any(|m| m.contains("cannot open 'nowhere' for reading") && m.contains("E0702")),
9360 "{messages:?}"
9361 );
9362 }
9363
9364 #[test]
9367 fn an_instruction_in_an_asm_at_file_scope_is_refused_rather_than_ignored() {
9368 for source in [
9369 "__asm__(\".text\\n.globl f\\nf:\\n ret\\n\");\n",
9370 "__asm__(\".data\\n.set alias, 4\\n\");\n",
9371 ] {
9372 let messages = errors(source);
9373 assert!(
9374 messages
9375 .iter()
9376 .any(|m| m.contains("not supported yet")
9377 && m.contains("in an `asm` at file scope")),
9378 "{source}\n{messages:?}"
9379 );
9380 }
9381 }
9382
9383 #[test]
9393 fn a_naked_function_is_its_own_prologue_and_its_own_ending() {
9394 let text = asm(concat!(
9395 "unsigned nlr_push_tail(void *nlr);\n",
9396 "__attribute__((naked)) unsigned nlr_push(void *nlr) {\n",
9397 " __asm volatile(\n",
9398 " \"movq (%rsp), %rax\\n\"\n",
9399 " \"movq %rax, 16(%rdi)\\n\"\n",
9400 " \"movq %rbx, 40(%rdi)\\n\"\n",
9401 " \"jmp nlr_push_tail\\n\");\n",
9402 "}\n",
9403 ));
9404 assert!(text.contains("\tmovq\t(%rsp), %rax\n"), "{text}");
9405 assert!(text.contains("\tjmp\tnlr_push_tail\n"), "{text}");
9406 assert!(text.contains("\tud2\n"), "{text}");
9407 assert!(!text.contains("\tpushq\t"), "nothing is saved in front of it: {text}");
9408 assert!(!text.contains("\tret\n"), "the jump is where it ends: {text}");
9409 }
9410
9411 #[test]
9414 fn what_a_function_without_a_prologue_cannot_be_given_is_refused() {
9415 let mut opts = options();
9416 opts.emit = EmitKind::Asm;
9417 for (source, why) in [
9418 (
9419 "__attribute__((naked)) void f(void) { volatile long a[8]; a[0] = 1; }\n",
9420 "bytes of frame",
9421 ),
9422 (
9423 "__attribute__((naked)) void f(int n) { char a[n]; __asm(\"nop\" ::\"r\"(a)); }\n",
9424 "has no prologue to point a frame pointer at it with",
9425 ),
9426 ("void elsewhere(void); void f(void) { __asm(\"jmp elsewhere\"); }\n", "jumps out of"),
9427 ] {
9428 let result = run(&opts, source);
9429 assert!(result.failed(), "expected this to be refused:\n{source}");
9430 assert!(
9431 result.messages.iter().any(|message| message.contains(why)),
9432 "{:?}",
9433 result.messages
9434 );
9435 }
9436 }
9437
9438 #[test]
9439 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
9440 let mut opts = options();
9441 opts.emit = EmitKind::Ir;
9442 for source in [
9443 "int f(int n) { void *p = &&out; if (n) goto *p; { int a[n]; out: return 1; } }\n",
9444 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
9445 ] {
9446 let result = run(&opts, source);
9447 assert!(result.failed(), "expected this to be reported:\n{source}");
9448 assert!(
9449 result.messages.iter().any(|m| m.contains("not supported yet")),
9450 "{:?}",
9451 result.messages
9452 );
9453 }
9454 }
9455
9456 fn round_trip(source: &str) -> (String, String) {
9458 let printed = ir(source);
9459 let mut opts = options();
9460 opts.emit = EmitKind::Ir;
9461 let mut fs = MemoryFileSystem::new();
9462 fs.insert("/main.ir", printed.clone().into_bytes());
9463 let result = compile_ir(&opts, "/main.ir", &fs);
9464 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
9465 (printed, result.text().to_owned())
9466 }
9467
9468 #[test]
9469 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
9470 let (printed, again) = round_trip(
9474 "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",
9475 );
9476 assert_eq!(printed, again);
9477 }
9478
9479 #[test]
9480 fn ir_that_is_not_ir_says_which_line_stopped_it() {
9481 let mut opts = options();
9482 opts.emit = EmitKind::Ir;
9483 let mut fs = MemoryFileSystem::new();
9484 let text = "\
9485; ModuleID = 'a.c'
9486; format 0
9487target triple = \"x86_64-unknown-linux-gnu\"
9488target datalayout = \"e-p:64:64-i64:64-S128\"
9489
9490func @f(), linkage(external) {
9491block0:
9492 frobnicate
9493}
9494";
9495 fs.insert("/main.ir", text.as_bytes().to_vec());
9496 let result = compile_ir(&opts, "/main.ir", &fs);
9497 assert!(result.failed());
9498 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
9499 }
9500
9501 #[test]
9502 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
9503 let mut opts = options();
9506 opts.emit = EmitKind::Ir;
9507 let mut fs = MemoryFileSystem::new();
9508 let text = "\
9509; ModuleID = 'a.c'
9510; format 0
9511target triple = \"x86_64-unknown-linux-gnu\"
9512target datalayout = \"e-p:64:64-i64:64-S128\"
9513
9514func @f(), linkage(external) {
9515block0:
9516 %0 = iconst.i32 1
9517 return %0
9518}
9519";
9520 fs.insert("/main.ir", text.as_bytes().to_vec());
9521 let result = compile_ir(&opts, "/main.ir", &fs);
9522 assert!(result.failed());
9523 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
9524 }
9525
9526 #[test]
9527 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
9528 let mut fs = MemoryFileSystem::new();
9530 fs.insert("/main.ir", Vec::new());
9531 let result = compile_ir(&options(), "/main.ir", &fs);
9532 assert!(result.failed());
9533 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
9534 }
9535
9536 #[test]
9537 fn the_printed_ir_reads_back_as_the_same_module() {
9538 let text = ir("\
9541struct point { int x, y; };
9542static const char greeting[] = \"hi\";
9543int table[4] = { 1, 2, 3 };
9544int puts(const char *);
9545double half(double x) { return x / 2.0; }
9546int f(int n) {
9547 int total = 0;
9548 for (int i = 0; i < n; i++) {
9549 if (i == 3) continue;
9550 total += table[i];
9551 }
9552 switch (n) {
9553 case 0: total = 1;
9554 case 1: total++; break;
9555 default: total = -total;
9556 }
9557 struct point p = { total, 1 };
9558 int *q = &p.y;
9559 puts(greeting);
9560 return p.x + *q;
9561}
9562int dispatch(int c) {
9563 void *p = c ? &&one : &&two;
9564 goto *p;
9565one:
9566 return 1;
9567two:
9568 return 2;
9569}
9570int assembly(int x, int *p) {
9571 int r;
9572 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
9573 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
9574 return r;
9575away:
9576 return 0;
9577}
9578");
9579 let mut names = Interner::new();
9580 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
9581 assert_eq!(rucc_ir::print(&module, &names), text);
9582 }
9583
9584 #[test]
9585 fn what_save_temps_keeps_is_the_text_that_was_compiled_and_the_assembly_that_was_assembled() {
9586 let mut opts = options();
9590 opts.emit = EmitKind::Object;
9591 opts.save_temps = rucc_session::SaveTemps::Object;
9592 let result = run(&opts, "#define N 2\nint a[N];\n");
9593 assert_eq!(result.messages, Vec::<String>::new());
9594 let text = result.temps.preprocessed.expect("the preprocessed text");
9595 assert!(text.contains("int a[2];"), "{text}");
9596 assert!(text.starts_with("# 1 \"/main.c\""), "{text}");
9597 let asm = result.temps.assembly.expect("the assembly");
9598 assert!(asm.contains("a:"), "{asm}");
9599 assert!(matches!(result.artifact, Artifact::Object { .. }), "{:?}", result.artifact);
9600 }
9601
9602 #[test]
9603 fn nothing_is_kept_unless_the_flag_asked_for_it() {
9604 let mut opts = options();
9607 opts.emit = EmitKind::Object;
9608 assert_eq!(run(&opts, "int a;\n").temps, Temps::default());
9609 }
9610
9611 #[test]
9612 fn a_compilation_that_stops_before_the_back_end_keeps_the_text_and_no_assembly() {
9613 let mut opts = options();
9616 opts.emit = EmitKind::Ir;
9617 opts.save_temps = rucc_session::SaveTemps::Cwd;
9618 let result = run(&opts, "int a;\n");
9619 assert!(result.temps.preprocessed.is_some());
9620 assert_eq!(result.temps.assembly, None);
9621 }
9622
9623 fn span(from: u64, len: u64, held: rucc_debug::Held) -> rucc_debug::Span {
9625 rucc_debug::Span { from, len, held }
9626 }
9627
9628 #[test]
9629 fn two_stretches_that_meet_and_agree_come_out_as_one() {
9630 let one = span(0, 4, rucc_debug::Held::Reg(3));
9631 let two = span(4, 4, rucc_debug::Held::Reg(3));
9632 assert_eq!(settle(vec![two, one]), vec![span(0, 8, rucc_debug::Held::Reg(3))]);
9633 }
9634
9635 #[test]
9636 fn a_stretch_another_starts_inside_and_disagrees_with_ends_where_the_other_starts() {
9637 let one = span(0, 8, rucc_debug::Held::Reg(3));
9638 let two = span(4, 8, rucc_debug::Held::Reg(4));
9639 let settled = settle(vec![one, two]);
9642 assert_eq!(
9643 settled,
9644 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 8, rucc_debug::Held::Reg(4))]
9645 );
9646 }
9647
9648 #[test]
9649 fn a_stretch_cut_by_one_that_ends_first_does_not_come_back_after_it() {
9650 let one = span(0, 16, rucc_debug::Held::Reg(3));
9653 let two = span(4, 4, rucc_debug::Held::Reg(4));
9654 assert_eq!(
9655 settle(vec![one, two]),
9656 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 4, rucc_debug::Held::Reg(4))]
9657 );
9658 }
9659
9660 #[test]
9661 fn a_stretch_inside_another_that_agrees_with_it_cuts_nothing() {
9662 let one = span(0, 16, rucc_debug::Held::Reg(3));
9663 let two = span(4, 4, rucc_debug::Held::Reg(3));
9664 assert_eq!(settle(vec![one, two]), vec![span(0, 16, rucc_debug::Held::Reg(3))]);
9665 }
9666
9667 #[test]
9668 fn a_stretch_two_others_disagree_over_the_whole_of_says_nothing_at_all() {
9669 let one = span(0, 8, rucc_debug::Held::Reg(3));
9670 let two = span(0, 8, rucc_debug::Held::Frame(-16));
9671 assert_eq!(settle(vec![one, two]), Vec::new());
9672 }
9673
9674 #[test]
9675 fn stretches_with_a_gap_between_them_keep_the_gap() {
9676 let one = span(0, 4, rucc_debug::Held::Reg(3));
9677 let two = span(16, 4, rucc_debug::Held::Reg(3));
9678 assert_eq!(settle(vec![one, two]), vec![one, two]);
9679 }
9680
9681 fn extent(len: usize) -> rucc_object::Extent {
9683 rucc_object::Extent {
9684 name: "f".to_owned(),
9685 start: 0,
9686 len,
9687 align: 1,
9688 binding: rucc_object::Binding::Global,
9689 visibility: rucc_object::Visibility::Default,
9690 patch: None,
9691 }
9692 }
9693
9694 fn row(at: usize, lo: u32, hi: u32) -> rucc_asm::Row {
9696 let span = Span::new(lo, hi);
9697 rucc_asm::Row { at, span, inst: None }
9698 }
9699
9700 #[test]
9701 fn a_row_ends_where_the_next_address_begins() {
9702 let rows = [row(0, 0, 1), row(4, 1, 2), row(10, 2, 3)];
9703 assert_eq!(ends(&extent(16), &rows), vec![4, 10, 16]);
9704 }
9705
9706 #[test]
9707 fn rows_sharing_an_address_all_end_where_the_next_address_begins() {
9708 let rows = [row(0, 0, 1), row(4, 1, 2), row(4, 2, 3), row(4, 3, 4)];
9711 assert_eq!(ends(&extent(12), &rows), vec![4, 12, 12, 12]);
9712 }
9713
9714 #[test]
9715 fn the_rows_of_a_scope_that_are_next_to_each_other_come_out_as_one_stretch() {
9716 let rows = [row(0, 0, 4), row(4, 10, 14), row(8, 14, 18), row(12, 40, 44)];
9717 let ends = ends(&extent(16), &rows);
9718 let scope = Span::new(8, 20);
9719 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 8 }]);
9720 }
9721
9722 #[test]
9723 fn a_scope_the_back_end_split_in_two_comes_out_as_two_stretches() {
9724 let rows = [row(0, 10, 14), row(4, 40, 44), row(8, 14, 18)];
9725 let ends = ends(&extent(12), &rows);
9726 let scope = Span::new(8, 20);
9727 let over = spread(scope, &ends, &rows);
9728 assert_eq!(
9729 over,
9730 vec![rucc_debug::Reach { from: 0, len: 4 }, rucc_debug::Reach { from: 8, len: 4 }]
9731 );
9732 }
9733
9734 #[test]
9735 fn a_row_with_no_source_of_its_own_belongs_to_no_scope() {
9736 let rows = [rucc_asm::Row { at: 0, span: Span::DUMMY, inst: None }, row(4, 10, 14)];
9738 let ends = ends(&extent(8), &rows);
9739 let scope = Span::new(0, 20);
9740 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 4 }]);
9741 }
9742
9743 fn scope(parent: Option<usize>, lo: u32, hi: u32) -> crate::shapes::Scope {
9745 let span = Span::new(lo, hi);
9746 crate::shapes::Scope { parent, span }
9747 }
9748
9749 #[test]
9750 fn a_function_gets_the_scopes_its_own_locals_are_in_and_nothing_else() {
9751 let scopes = [scope(None, 0, 10), scope(None, 20, 30), scope(Some(1), 22, 26)];
9753 let rows = [row(0, 22, 24), row(4, 26, 28)];
9754 let (out, at) = nests(&[Some(2)], &scopes, &extent(8), &rows);
9755 assert_eq!(at.get(&1), Some(&0));
9758 assert_eq!(at.get(&2), Some(&1));
9759 assert_eq!(at.get(&0), None);
9760 assert_eq!(out.len(), 2);
9761 assert_eq!(out[0].parent, None);
9762 assert_eq!(out[1].parent, Some(0));
9763 assert_eq!(out[0].over, vec![rucc_debug::Reach { from: 0, len: 8 }]);
9764 assert_eq!(out[1].over, vec![rucc_debug::Reach { from: 0, len: 4 }]);
9765 }
9766
9767 #[test]
9768 fn a_local_written_straight_into_the_body_pulls_no_scope_in() {
9769 let scopes = [scope(None, 20, 30)];
9770 let rows = [row(0, 22, 24)];
9771 let (out, at) = nests(&[None], &scopes, &extent(4), &rows);
9772 assert_eq!(out, Vec::new());
9773 assert!(at.is_empty());
9774 }
9775
9776 #[test]
9777 fn a_scope_whose_code_all_went_away_is_still_one_of_the_functions_scopes() {
9778 let scopes = [scope(None, 20, 30)];
9782 let rows = [row(0, 40, 44)];
9783 let (out, at) = nests(&[Some(0)], &scopes, &extent(4), &rows);
9784 assert_eq!(at.get(&0), Some(&0));
9785 assert_eq!(out.len(), 1);
9786 assert_eq!(out[0].over, Vec::new());
9787 }
9788}