1use std::collections::HashMap;
14use std::path::Path;
15
16use rucc_base::{Interner, Symbol};
17use rucc_codegen::coverage::Fired;
18use rucc_codegen::elsewhere::{Elsewhere, Slot};
19use rucc_codegen::lowering::Lowerings;
20use rucc_codegen::pipeline::{self, Machine, Recording};
21use rucc_codegen::pressure::Pressure;
22use rucc_codegen::usage::StackUsage;
23use rucc_cost::Goal;
24use rucc_diag::{Diagnostic, Severity, SourceMap, Span};
25use rucc_ir::{FpContract, Pic as IrPic, Visibility as IrVisibility};
26use rucc_lex::{Convert, Keywords, PpToken, convert};
27use rucc_lower::Protector as LowerProtector;
28use rucc_sema::{Checker, Context as CheckContext};
29use rucc_session::{
30 Contract, EmitKind, FileSystem, Options, Padding, Pic, Protector, Session, Visibility,
31};
32use rucc_target::TargetInfo;
33use rucc_tuple::{Arch, ObjectFormat};
34
35use crate::preprocess::render;
36
37#[derive(Debug, Clone, PartialEq, Eq, Default)]
44pub enum Artifact {
45 #[default]
48 Nothing,
49 Text(String),
51 Object {
58 bytes: Vec<u8>,
60 defines: Vec<String>,
64 },
65}
66
67impl Artifact {
68 #[must_use]
70 pub fn bytes(&self) -> &[u8] {
71 match self {
72 Artifact::Nothing => &[],
73 Artifact::Text(text) => text.as_bytes(),
74 Artifact::Object { bytes, .. } => bytes,
75 }
76 }
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
81pub struct Compiled {
82 pub artifact: Artifact,
84 pub messages: Vec<String>,
86 pub errors: u32,
88 pub fired: Fired,
94 pub pressure: Pressure,
99 pub lowerings: Lowerings,
104 pub dumps: Vec<rucc_opt::Dump>,
110 pub remarks: String,
116 pub deps: Vec<rucc_pp::Dependency>,
121 pub temps: Temps,
128 pub timing: crate::trace::Timing,
130 pub stack_usage: String,
137}
138
139#[derive(Debug, Clone, PartialEq, Eq, Default)]
146pub struct Temps {
147 pub preprocessed: Option<String>,
149 pub assembly: Option<String>,
151}
152
153impl Compiled {
154 #[must_use]
156 pub fn failed(&self) -> bool {
157 self.errors > 0
158 }
159
160 #[must_use]
165 pub fn text(&self) -> &str {
166 match &self.artifact {
167 Artifact::Text(text) => text,
168 _ => "",
169 }
170 }
171}
172
173#[must_use]
186pub fn compile(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
187 let mut clock = crate::trace::Clock::start();
188 let mut sess = Session::new(opts.clone());
189 let mut keywords = Keywords::new(&mut sess.interner, opts.std, opts.gnu_extensions);
193 if sess.target.tuple.os() == rucc_tuple::Os::Windows {
194 keywords = if sess.target.tuple.env() == rucc_tuple::Env::Msvc {
195 keywords.msvc(&mut sess.interner)
196 } else {
197 keywords.windows()
198 };
199 }
200 let mut diagnostics: Vec<Diagnostic> = Vec::new();
201 let mut fired = Fired::new();
203 let mut pressure = Pressure::new();
205 let mut lowerings = Lowerings::asked(opts.lowering_dump.is_some());
206 let mut stack = StackUsage::new();
210 let mut dumps = Vec::new();
212 let mut remarks = String::new();
213 let mut passes = Vec::new();
215 let mut temps = Temps::default();
217
218 let bytes = match fs.read(Path::new(name)) {
219 Ok(bytes) => bytes,
220 Err(e) => return failure(format!("{name}: {e}")),
221 };
222 let Ok(file) = sess.sources.add_shared(crate::phase::source_name(name), bytes, None) else {
223 return failure(format!("{name}: the source map has no room left for this file"));
224 };
225 clock.lap("read");
226
227 let mut pp = rucc_pp::Preprocessor::with_prefix_map(opts.prefix_map.macros.clone());
231 let predef = rucc_pp::Predef::for_options(opts);
232 let expanded: Vec<PpToken> = {
233 let mut tokens = Vec::new();
234 {
239 let mut cx =
240 rucc_pp::Context::new(&mut sess.interner, &mut sess.sources, fs, &opts.search);
241 cx.lex = rucc_lex::Options::for_dialect(opts.std, opts.gnu_extensions);
242 cx.pedantic = opts.pedantic;
243 if pp.predefine(&sess.target, &predef, &mut cx).is_err() {
244 return failure(format!(
245 "{name}: the source map has no room for the built in macros"
246 ));
247 }
248 if pp.preinclude(&opts.preincludes, &mut tokens, &mut cx).is_err() {
249 return failure(format!("{name}: the source map has no room for the command line"));
250 }
251 tokens.append(&mut pp.run(file, &mut cx));
252 }
253 if opts.save_temps.wanted() {
254 temps.preprocessed = Some(rucc_pp::print(
255 file,
256 &tokens,
257 pp.line_directives(),
258 &sess.sources,
259 &sess.interner,
260 rucc_pp::PrintOptions { line_markers: opts.line_markers },
261 ));
262 }
263 tokens.iter().map(|token| token.to_pp()).collect()
264 };
265 diagnostics.extend(pp.take_diagnostics());
266 let deps = pp.dependencies().to_vec();
269 clock.lap("preprocess");
270
271 let cx = Convert {
274 keywords: &keywords,
275 interner: &sess.interner,
276 target: &sess.target,
277 std: opts.std,
278 gnu: opts.gnu_extensions,
279 pedantic: opts.pedantic,
280 };
281 let (tokens, complaints) = convert(&expanded, &cx);
282 diagnostics.extend(complaints);
283 clock.lap("convert");
284
285 let type_names: Vec<Symbol> =
287 sess.target.type_names().iter().filter_map(|&(name, _)| sess.interner.find(name)).collect();
288 let parsed = rucc_parse::parse(
289 &tokens,
290 rucc_parse::Context {
291 interner: &sess.interner,
292 std: opts.std,
293 gnu: opts.gnu_extensions,
294 pedantic: opts.pedantic,
295 error_limit: opts.error_limit as usize,
296 type_names: &type_names,
297 },
298 );
299 clock.lap("parse");
300 let parse_failed = parsed.diagnostics.iter().any(|d| d.severity.is_fatal());
301 diagnostics.extend(parsed.diagnostics);
302 let comments = parsed.comments;
303
304 let mut artifact = Artifact::Nothing;
305 let mut instrumented = Instrumented::default();
308 if !parse_failed {
309 let mut checker = Checker::new(
310 &parsed.ast,
311 CheckContext {
312 names: &sess.interner,
313 target: &sess.target,
314 std: opts.std,
315 gnu: opts.gnu_extensions,
316 pedantic: opts.pedantic,
317 permissive: opts.permissive,
318 gnu89_inline: opts.gnu89_inline,
319 error_limit: opts.error_limit as usize,
320 builtins: opts.builtins && opts.hosted,
323 no_builtin: &opts.no_builtin,
324 short_enums: opts.short_enums,
325 ms_extensions: sess.ms_extensions(),
326 trapping_math: opts.trapping_math,
327 isa: opts.isa,
328 },
329 );
330 checker.check_unit();
331 let checked = checker.finish();
332 clock.lap("check");
333 if !checked.failed() {
334 match opts.emit {
335 EmitKind::Tast => {
336 artifact = Artifact::Text(rucc_sema::print(
337 &checked.tast,
338 &checked.types,
339 &sess.interner,
340 ));
341 }
342 EmitKind::TypeGranules => {
346 artifact = Artifact::Text(rucc_types::granule_report(
347 &checked.types,
348 &sess.interner,
349 &sess.target,
350 ));
351 }
352 EmitKind::Ir
353 | EmitKind::MirFinal
354 | EmitKind::Asm
355 | EmitKind::Object
356 | EmitKind::Archive
357 | EmitKind::Executable
358 | EmitKind::SafetySummary => {
359 let mut read = |named: &str| {
364 fs.read(Path::new(named))
365 .map(|bytes| bytes.as_slice().to_vec())
366 .map_err(|why| why.to_string())
367 };
368 let meaning = if opts.debug_info {
374 crate::shapes::collect(
375 &checked.tast,
376 &checked.types,
377 &sess.target,
378 &sess.interner,
379 &sess.sources,
380 )
381 } else {
382 crate::shapes::Meaning::default()
383 };
384 let common = sess.common();
385 let mut lowered = rucc_lower::lower(
386 crate::phase::source_name(name),
387 rucc_lower::Context {
388 tast: &checked.tast,
389 types: &checked.types,
390 target: &sess.target,
391 names: &mut sess.interner,
392 visibility: match opts.visibility {
393 Visibility::Default => IrVisibility::Default,
394 Visibility::Hidden => IrVisibility::Hidden,
395 Visibility::Protected => IrVisibility::Protected,
396 },
397 protector: match opts.protector {
398 Protector::None => LowerProtector::None,
399 Protector::Buffers => LowerProtector::Buffers,
400 Protector::Strong => LowerProtector::Strong,
401 Protector::All => LowerProtector::All,
402 },
403 wrapping: rucc_lower::Wrapping {
404 signed: opts.wrapping.signed,
405 pointer: opts.wrapping.pointer,
406 trap: opts.wrapping.trap,
407 },
408 aliasing: opts.strict_aliasing,
409 padding: opts.padding == Padding::Ignored,
410 contract: match opts.fp_contract {
411 Contract::Off => FpContract::Off,
412 Contract::On => FpContract::On,
413 Contract::Fast => FpContract::Fast,
414 },
415 align: opts.align_functions,
416 instrument: opts.instrument_functions,
417 exceptions: opts.exceptions,
418 common,
419 read: &mut read,
420 },
421 );
422 clock.lap("lower");
426 for option in linker_options(&comments, &sess.target) {
427 lowered.module.add_linker_option(option);
428 }
429 let failed = lowered.diagnostics.iter().any(|d| d.severity.is_fatal());
430 if !failed {
431 if let Err(errors) = clock
436 .time("verify", || rucc_ir::verify(&lowered.module, &sess.interner))
437 {
438 for error in errors {
439 diagnostics.push(internal(&format!("invalid IR, {error}")));
440 }
441 } else if let Err(complaints) = clock
442 .time("instrument", || {
443 instrument(&mut lowered.module, &mut sess.interner, opts)
444 })
445 .map(|done| instrumented = done)
446 {
447 diagnostics.extend(complaints);
448 } else if let Err(complaints) = clock
449 .time("optimize", || {
450 optimize(
451 &mut lowered.module,
452 &mut sess.interner,
453 &sess.target,
454 opts,
455 name,
456 &mut dumps,
457 &mut remarks,
458 )
459 })
460 .map(|times| passes = times)
461 {
462 diagnostics.extend(complaints);
463 } else if opts.emit == EmitKind::SafetySummary {
464 artifact = Artifact::Text(
469 rucc_safety::summarize(
470 &lowered.module,
471 &sess.interner,
472 name,
473 opts.safety.as_str(),
474 instrumented.checks,
475 instrumented.interposed,
476 instrumented.crossings,
477 )
478 .render(),
479 );
480 } else if opts.emit == EmitKind::Ir {
481 artifact =
486 Artifact::Text(rucc_ir::print(&lowered.module, &sess.interner));
487 } else {
488 let made = clock.time("generate", || {
491 generate(
492 &mut lowered.module,
493 &mut sess.interner,
494 &sess.target,
495 opts,
496 &mut Recording {
497 fired: &mut fired,
498 pressure: &mut pressure,
499 lowerings: &mut lowerings,
500 stack: &mut stack,
501 },
502 &mut temps.assembly,
503 Origin { map: &sess.sources, name, meaning: &meaning },
504 )
505 });
506 match made {
507 Ok(made) => artifact = made,
508 Err(complaints) => diagnostics.extend(complaints),
509 }
510 }
511 }
512 diagnostics.extend(lowered.diagnostics);
513 }
514 EmitKind::SyntaxOnly => {}
516 _ => {}
517 }
518 }
519 diagnostics.extend(checked.diagnostics);
520 }
521 let mut wants = rucc_opt::Wants::none();
524 for spec in &opts.opt_info {
525 let _ = wants.add(spec);
527 }
528 if wants.wants(rucc_opt::stats::Kind::Optimized) {
529 remarks.push_str(&lowerings.remarks(name));
530 }
531
532 let mut messages = Vec::with_capacity(diagnostics.len());
533 let mut errors = 0;
534 for diag in &diagnostics {
535 if rucc_diag::dropped(diag, &sess.sources, opts.warnings, opts.system_header_warnings) {
541 continue;
542 }
543 if diag.severity.is_fatal()
544 || (diag.severity == Severity::Warning && opts.warnings_are_errors)
545 {
546 errors += 1;
547 }
548 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
549 }
550 if errors > 0 {
551 artifact = Artifact::Nothing;
553 }
554 let stack_usage = if opts.stack_usage && errors == 0 {
557 su_file(&stack, &sess.sources, crate::phase::source_name(name))
558 } else {
559 String::new()
560 };
561 clock.passes(passes);
564 let timing = clock.finish();
565 Compiled {
566 artifact,
567 messages,
568 errors,
569 fired,
570 pressure,
571 lowerings,
572 dumps,
573 remarks,
574 deps,
575 temps,
576 timing,
577 stack_usage,
578 }
579}
580
581#[must_use]
591pub fn compile_ir(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
592 let mut sess = Session::new(opts.clone());
593 if opts.emit != EmitKind::Ir {
594 return failure(format!(
595 "{name}: an input of IR can only be emitted as IR, and `--emit={}` asks for what \
596 the C in front of it became",
597 opts.emit.as_str()
598 ));
599 }
600 let bytes = match fs.read(Path::new(name)) {
601 Ok(bytes) => bytes,
602 Err(e) => return failure(format!("{name}: {e}")),
603 };
604 let Ok(text) = std::str::from_utf8(bytes.as_slice()) else {
605 return failure(format!("{name}: this is not text, so it is not IR"));
606 };
607
608 let module = match rucc_ir::parse(text, &mut sess.interner) {
609 Ok(module) => module,
610 Err(error) => {
611 return failure(format!("{name}:{}: {}", error.line, error.message));
612 }
613 };
614 let mut diagnostics: Vec<Diagnostic> = Vec::new();
615 if let Err(errors) = rucc_ir::verify(&module, &sess.interner) {
616 for error in errors {
617 diagnostics.push(invalid(&format!("invalid IR, {error}")));
618 }
619 }
620 let mut messages = Vec::with_capacity(diagnostics.len());
621 for diag in &diagnostics {
622 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
623 }
624 let errors = u32::try_from(messages.len()).unwrap_or(u32::MAX);
625 let artifact = if errors > 0 {
626 Artifact::Nothing
627 } else {
628 Artifact::Text(rucc_ir::print(&module, &sess.interner))
629 };
630 Compiled {
632 artifact,
633 messages,
634 errors,
635 fired: Fired::new(),
636 pressure: Pressure::new(),
637 lowerings: Lowerings::new(),
638 dumps: Vec::new(),
639 remarks: String::new(),
640 deps: Vec::new(),
641 temps: Temps::default(),
642 timing: crate::trace::Timing::default(),
643 stack_usage: String::new(),
644 }
645}
646
647fn linker_options(comments: &[rucc_parse::Comment], target: &TargetInfo) -> Vec<String> {
675 if target.tuple.os().object_format() != Some(ObjectFormat::Coff) {
676 return Vec::new();
677 }
678 comments
679 .iter()
680 .map(|comment| match comment {
681 rucc_parse::Comment::Lib(lib) => {
682 let lower = lib.to_ascii_lowercase();
683 let suffix =
684 if lower.ends_with(".lib") || lower.ends_with(".a") { "" } else { ".lib" };
685 if lib.contains(' ') {
686 format!("/DEFAULTLIB:\"{lib}{suffix}\"")
687 } else {
688 format!("/DEFAULTLIB:{lib}{suffix}")
689 }
690 }
691 rucc_parse::Comment::Linker(option) => option.clone(),
692 })
693 .collect()
694}
695
696fn instrument(
697 module: &mut rucc_ir::Module,
698 names: &mut Interner,
699 opts: &Options,
700) -> Result<Instrumented, Vec<Diagnostic>> {
701 if !opts.safety.instruments() {
702 return Ok(Instrumented::default());
703 }
704 let mut checks = rucc_safety::run(module, opts.subobject, opts.promise, opts.races);
705 checks.freed = rucc_safety::ending::checks(module, names);
713 let interposed = rucc_safety::redirect(module, names);
718 let crossings = rucc_safety::witness(module, names);
721 match rucc_ir::verify(module, names) {
722 Ok(()) => Ok(Instrumented { checks, interposed, crossings }),
723 Err(errors) => Err(errors
724 .iter()
725 .map(|e| internal(&format!("invalid IR after check insertion, {e}")))
726 .collect()),
727 }
728}
729
730#[derive(Clone, Copy, Debug, Default)]
736struct Instrumented {
737 checks: rucc_safety::Counts,
739 interposed: usize,
741 crossings: rucc_safety::Sites,
743}
744
745fn optimize(
759 module: &mut rucc_ir::Module,
760 names: &mut Interner,
761 target: &TargetInfo,
762 opts: &Options,
763 file: &str,
764 dumps: &mut Vec<rucc_opt::Dump>,
765 remarks: &mut String,
766) -> Result<Vec<(&'static str, std::time::Duration)>, Vec<Diagnostic>> {
767 let mut settings = rucc_opt::Options::for_level(opts.opt_level);
768 settings.interposition = match opts.interposition {
774 true => replaceable(target, opts),
775 false => IrPic::Executable,
776 };
777 settings.toggles.clone_from(&opts.passes);
778 settings.builtins = opts.builtins && opts.hosted;
783 settings.no_builtin.clone_from(&opts.no_builtin);
784 settings.isa = opts.isa;
787 settings.fuel = opts.pass_fuel.iter().cloned().collect();
788 settings.global_fuel = opts.pass_fuel_global;
789 settings.verify |= opts.verify_each;
790 for (on, spec) in &opts.pass_gates {
791 if let Err(why) = settings.gates.add(*on, spec) {
794 return Err(vec![internal(&why)]);
795 }
796 }
797 for spec in &opts.dump_ir {
798 if let Err(why) = settings.dumps.add(spec) {
801 return Err(vec![internal(&why)]);
802 }
803 }
804 let mut wants = rucc_opt::Wants::none();
805 for spec in &opts.opt_info {
806 if let Err(why) = wants.add(spec) {
809 return Err(vec![internal(&why)]);
810 }
811 }
812 let report = rucc_opt::run(module, names, &settings);
813 remarks.push_str(&rucc_opt::optinfo::render(file, &report, names, wants));
814 dumps.extend(report.dumps);
815 match report.broke.is_empty() {
816 true => Ok(report.time),
817 false => Err(report.broke.iter().map(|why| internal(why)).collect()),
818 }
819}
820
821fn replaceable(target: &TargetInfo, opts: &Options) -> IrPic {
855 match (target.tuple.os().object_format(), opts.pic) {
856 (Some(ObjectFormat::Elf), Pic::Library) => IrPic::Library,
857 _ => IrPic::Executable,
858 }
859}
860
861#[derive(Clone, Copy)]
868struct Origin<'a> {
869 map: &'a SourceMap,
871 name: &'a str,
873 meaning: &'a crate::shapes::Meaning,
875}
876
877fn generate(
878 module: &mut rucc_ir::Module,
879 names: &mut Interner,
880 target: &TargetInfo,
881 opts: &Options,
882 recording: &mut Recording<'_>,
883 assembly: &mut Option<String>,
884 origin: Origin<'_>,
885) -> Result<Artifact, Vec<Diagnostic>> {
886 let Some(machine) = Machine::for_target(target) else {
887 return Err(vec![unsupported(&format!(
888 "there is no back end for {} in this compiler yet, so there is nothing to generate",
889 target.tuple
890 ))]);
891 };
892 if opts.protector != Protector::None && machine.conv.guard.is_none() {
897 return Err(vec![unsupported(&format!(
898 "{} is not supported for {} yet, because the stack protector on that target is not \
899 the one this compiler writes",
900 opts.protector, target.tuple
901 ))]);
902 }
903 if opts.control.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
909 return Err(vec![unsupported(&format!(
910 "-fcf-protection={} is not supported for {} yet, because what says a file was built \
911 for it there is not the note this compiler writes",
912 opts.control, target.tuple
913 ))]);
914 }
915 let profile = match machine.conv.trace {
921 Some(trace) => opts.profile.then(|| opts.hook.early(trace.fentry)),
922 None if opts.profile => {
923 return Err(vec![unsupported(&format!(
924 "-pg is not supported for {} yet, because the profiler's hook on that target is \
925 not the one this compiler calls",
926 target.tuple
927 ))]);
928 }
929 None => None,
930 };
931 if opts.patchable.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
936 return Err(vec![unsupported(&format!(
937 "-fpatchable-function-entry= is not supported for {} yet, because what records where \
938 the room is there is not the section this compiler writes",
939 target.tuple
940 ))]);
941 }
942 let flags = pipeline::Flags {
943 frame_pointer: opts.keeps_frame_pointer(),
944 red_zone: opts.red_zone,
945 stack_clash: opts.stack_clash,
946 landing: opts.control.branch(),
947 profile: match profile {
948 None => pipeline::Profile::No,
949 Some(true) => pipeline::Profile::Early,
950 Some(false) => pipeline::Profile::Late,
951 },
952 patch: pipeline::Room { after: opts.patchable.after(), before: opts.patchable.before },
953 reorder: opts.reorder_blocks.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
960 reuse: opts.stack_reuse.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
965 schedule: opts.schedule_insns.unwrap_or_else(|| opts.opt_level.schedules()),
970 align_loops: opts.align_loops.unwrap_or(false),
976 accurate: opts.cycle_accurate_model,
978 verify: opts.verify_each,
981 allocator: if opts.backtracking.unwrap_or_else(|| opts.opt_level.runs_optimizer()) {
985 pipeline::Allocator::Backtracking
986 } else {
987 pipeline::Allocator::Single
988 },
989 goal: Goal::for_size(opts.opt_level.is_size()),
994 switch: opts.switch_shape.as_deref().and_then(rucc_codegen::switch::Force::named),
996 sibling: opts.sibling_calls.unwrap_or_else(|| opts.opt_level.sibling_calls()),
998 debug: opts.debug_info,
999 };
1000
1001 if opts.safety.instruments() {
1010 rucc_opt::heap::annotate(module, names);
1020 rucc_safety::handover::arrange(module);
1027 rucc_safety::lower(module, names);
1028 if let Err(errors) = rucc_ir::verify(module, names) {
1029 return Err(errors
1030 .iter()
1031 .map(|e| internal(&format!("invalid IR after check lowering, {e}")))
1032 .collect());
1033 }
1034 }
1035
1036 let copies = target.tuple.arch() == Arch::X86_64;
1047 let elsewhere = Elsewhere::of(module, replaceable(target, opts), target.object_format, copies);
1048
1049 let mut funcs = Vec::new();
1050 let mut complaints = Vec::new();
1051 for id in module.funcs() {
1052 if module[id].is_declaration() {
1053 continue;
1054 }
1055 match pipeline::compile_recording(
1056 &mut module[id],
1057 names,
1058 &machine,
1059 &elsewhere,
1060 flags,
1061 recording,
1062 ) {
1063 Ok(func) => funcs.push(func),
1064 Err(why) => {
1065 let name = names.resolve(module[id].name).to_owned();
1066 let span = why.inst().map_or(Span::DUMMY, |inst| module[id].span(inst));
1069 let said = format!("cannot generate code for '{name}': {why}");
1070 complaints.push(unsupported_at(&said, span));
1071 }
1072 }
1073 }
1074 if !complaints.is_empty() {
1075 return Err(complaints);
1076 }
1077 let (globals, aliases) = match opts.emit {
1083 EmitKind::Asm | EmitKind::Object | EmitKind::Archive | EmitKind::Executable => {
1084 let mut globals =
1085 rucc_asm::globals(module, names, target.object_format).map_err(refused)?;
1086 globals.pointers(elsewhere.referred(module).into_iter().map(|name| {
1091 let target = names.resolve(name).to_owned();
1092 (Slot::Referred.name(&target), target)
1093 }));
1094 if target.tuple.env() == rucc_tuple::Env::Msvc {
1095 globals.keep_imported(
1096 elsewhere
1097 .imported_variables(module)
1098 .into_iter()
1099 .map(|name| names.resolve(name).to_owned()),
1100 );
1101 }
1102 (globals, rucc_asm::aliases(module, names).map_err(refused)?)
1103 }
1104 _ => (rucc_asm::Globals::default(), Vec::new()),
1105 };
1106 let unwind = opts.unwinds();
1110 match opts.emit {
1111 EmitKind::Asm => {
1112 rucc_asm::print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
1113 .map(Artifact::Text)
1114 .map_err(refused)
1115 }
1116 EmitKind::Object | EmitKind::Archive | EmitKind::Executable => {
1120 if opts.save_temps.wanted() {
1121 let listing = rucc_asm::print(
1122 &funcs,
1123 &globals,
1124 &aliases,
1125 names,
1126 target,
1127 unwind,
1128 output(opts, target),
1129 );
1130 *assembly = Some(listing.map_err(refused)?);
1131 }
1132 let aarch64 = target.tuple.arch() == Arch::Aarch64;
1143 if aarch64 || globals.kept() || rucc_asm::kept(&funcs, names, target) {
1144 let print = if opts.debug_info { rucc_asm::print_marked } else { rucc_asm::print };
1148 let listing =
1149 print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
1150 .map_err(refused)?;
1151 let arch = target.tuple.arch();
1152 let read =
1153 rucc_asm::read_as(&listing, arch, target.object_format).map_err(|trouble| {
1154 let what = if aarch64 {
1155 "a unit for aarch64"
1156 } else if globals.kept() {
1157 "an `asm` at file scope"
1158 } else {
1159 "an `asm` template kept as text"
1160 };
1161 vec![unsupported(&format!(
1162 "{what}, whose listing the assembler stopped at on line {}: {}",
1163 trouble.line, trouble.why
1164 ))]
1165 })?;
1166 let info = if opts.debug_info {
1167 let assembled =
1168 placed(&read, &funcs, names, target).map_err(|why| vec![internal(&why)])?;
1169 describe(&assembled, &globals.image(), &funcs, origin, opts, target)
1170 .map_err(|why| vec![internal(&why)])?
1171 } else {
1172 rucc_object::Info::default()
1173 };
1174 let defines = rucc_object::assembled_defines(&read);
1175 let bytes =
1176 rucc_object::assembled_described(&read, target, &info).map_err(wrote)?;
1177 return Ok(Artifact::Object { bytes, defines });
1178 }
1179 let assembled = rucc_asm::assemble(&funcs, names, target, unwind, opts.debug_info)
1180 .map_err(refused)?;
1181 let data = globals.image();
1182 let info = if opts.debug_info {
1186 describe(&assembled, &data, &funcs, origin, opts, target)
1187 .map_err(|why| vec![internal(&why)])?
1188 } else {
1189 rucc_object::Info::default()
1190 };
1191 let text = assembled.text;
1192 let bytes =
1195 rucc_object::write(&text, &data, &aliases, target, output(opts, target), &info)
1196 .map_err(wrote)?;
1197 let defines = rucc_object::defines(&text, &data, &aliases, target).map_err(wrote)?;
1202 Ok(Artifact::Object { bytes, defines })
1203 }
1204 _ => Ok(Artifact::Text(rucc_mir::print(&funcs, names, target.regs))),
1205 }
1206}
1207
1208fn placed(
1220 read: &rucc_object::Assembled,
1221 funcs: &[rucc_mir::Func],
1222 names: &Interner,
1223 target: &TargetInfo,
1224) -> Result<rucc_asm::Assembled, String> {
1225 let at: HashMap<&str, &rucc_object::Name> =
1226 read.names.iter().map(|name| (name.name.as_str(), name)).collect();
1227 let offset = |name: &str| match at.get(name).map(|name| name.at) {
1228 Some(rucc_object::Held::In { part, offset }) => Some((part, offset)),
1229 _ => None,
1230 };
1231 let mut text = rucc_object::Text::default();
1232 let mut lines = Vec::with_capacity(funcs.len());
1233 let symbol = rucc_asm::Directives::of(target.object_format).symbol();
1237 for (which, func) in funcs.iter().enumerate() {
1238 let name = names.resolve(func.name);
1239 let Some((part, start)) = offset(&format!("{symbol}{name}")) else {
1240 return Err(format!("the listing has no label for the function '{name}'"));
1241 };
1242 let mut rows = Vec::with_capacity(func.inst_count() + 1);
1243 if !func.declared.is_dummy() {
1244 rows.push(rucc_asm::Row { at: 0, span: func.declared, inst: None });
1245 }
1246 for block in func.blocks() {
1247 for inst in func.insts(block) {
1248 let label = rucc_asm::mark(target, which, inst);
1249 let Some((held, here)) = offset(&label) else {
1250 return Err(format!("the listing has no label '{label}'"));
1251 };
1252 if held != part || here < start {
1253 return Err(format!("the label '{label}' is not inside '{name}'"));
1254 }
1255 let at = usize::try_from(here - start).map_err(|why| why.to_string())?;
1256 rows.push(rucc_asm::Row { at, span: func.span(inst), inst: Some(inst) });
1257 }
1258 }
1259 let size = at.get(format!("{symbol}{name}").as_str()).map_or(0, |name| name.size);
1262 let len = match offset(&rucc_asm::mark_end(target, which)) {
1263 Some((held, end)) if size == 0 && held == part && end >= start => end - start,
1264 _ => size,
1265 };
1266 text.funcs.push(rucc_object::Extent {
1267 name: name.to_owned(),
1268 start: usize::try_from(start).map_err(|why| why.to_string())?,
1269 len: usize::try_from(len).map_err(|why| why.to_string())?,
1270 align: func.align.unwrap_or(rucc_object::FUNC_ALIGN),
1271 binding: rucc_object::Binding::Global,
1272 visibility: rucc_object::Visibility::Default,
1273 patch: None,
1274 landings: Vec::new(),
1275 });
1276 lines.push(rows);
1277 }
1278 Ok(rucc_asm::Assembled { text, lines, frames: None })
1279}
1280
1281fn describe(
1300 assembled: &rucc_asm::Assembled,
1301 data: &rucc_object::Data,
1302 machine: &[rucc_mir::Func],
1303 origin: Origin<'_>,
1304 opts: &Options,
1305 target: &TargetInfo,
1306) -> Result<rucc_object::Info, String> {
1307 let rucc_asm::Assembled { text, lines, frames } = assembled;
1308 let rewrite = |path: &str| opts.prefix_map.debug.apply(path).into_owned();
1309 let mut files: Vec<String> = Vec::new();
1314 let mut funcs = Vec::with_capacity(text.funcs.len());
1315 for ((extent, rows), built) in text.funcs.iter().zip(lines).zip(machine) {
1316 let mut out: Vec<rucc_debug::Row> = Vec::with_capacity(rows.len());
1317 for row in rows {
1318 if row.span.is_dummy() {
1319 continue;
1320 }
1321 let Some(at) = origin.map.presumed(row.span.lo) else {
1322 continue;
1323 };
1324 let which = interned(&mut files, rewrite(at.name));
1325 let place = rucc_debug::Row {
1326 at: row.at as u64,
1327 file: which,
1328 line: at.line,
1329 column: at.column,
1330 };
1331 match out.last() {
1341 Some(last) if last.at == place.at => {}
1342 _ => out.push(place),
1343 }
1344 }
1345 if let Some(first) = out.first_mut() {
1352 first.at = 0;
1353 }
1354 let known = origin.meaning.funcs.get(&extent.name);
1360 let decl = known.map(|known| rucc_debug::Place {
1361 file: interned(&mut files, rewrite(&known.file)),
1362 line: known.line,
1363 });
1364 let mut sig = known.and_then(|known| known.sig.clone());
1373 let mut placed: Vec<(u32, i32)> = built.locals.clone();
1374 let mut spots = stretches(extent, rows, built, target);
1375 for &decl in &built.sharing {
1380 if !spots.iter().any(|(at, _)| *at == decl) {
1381 spots.push((decl, Vec::new()));
1382 }
1383 }
1384 if let (Some(sig), Some(known)) = (sig.as_mut(), known) {
1385 for (param, decl) in sig.params.iter_mut().zip(&known.params) {
1386 let Some(decl) = *decl else { continue };
1387 if let Some(which) = placed.iter().position(|&(at, _)| at == decl) {
1388 let at = rucc_debug::Held::Frame(i64::from(placed.remove(which).1));
1389 param.spot = Some(rucc_debug::Spot::Always(at));
1390 continue;
1391 }
1392 let Some(which) = spots.iter().position(|(at, _)| *at == decl) else { continue };
1396 param.spot = Some(rucc_debug::Spot::Over(spots.remove(which).1));
1397 }
1398 }
1399 let mut locals = Vec::with_capacity(placed.len() + spots.len());
1403 let mut wants: Vec<Option<usize>> = Vec::with_capacity(locals.capacity());
1407 for (decl, at) in placed {
1408 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1409 wants.push(named.scope);
1410 locals.push(rucc_debug::Local {
1411 name: named.name.clone(),
1412 ty: named.ty,
1413 decl: Some(rucc_debug::Place {
1414 file: interned(&mut files, rewrite(&named.file)),
1415 line: named.line,
1416 }),
1417 spot: rucc_debug::Spot::Always(rucc_debug::Held::Frame(i64::from(at))),
1418 scope: None,
1419 });
1420 }
1421 spots.sort_by_key(|(decl, _)| *decl);
1425 for (decl, spans) in spots {
1426 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1427 wants.push(named.scope);
1428 locals.push(rucc_debug::Local {
1429 name: named.name.clone(),
1430 ty: named.ty,
1431 decl: Some(rucc_debug::Place {
1432 file: interned(&mut files, rewrite(&named.file)),
1433 line: named.line,
1434 }),
1435 spot: rucc_debug::Spot::Over(spans),
1436 scope: None,
1437 });
1438 }
1439 let (scopes, at) = nests(&wants, &origin.meaning.scopes, extent, rows);
1444 for (local, want) in locals.iter_mut().zip(&wants) {
1445 local.scope = want.and_then(|want| at.get(&want).copied());
1446 }
1447 funcs.push(rucc_debug::Function {
1448 name: extent.name.clone(),
1449 len: extent.len as u64,
1450 rows: out,
1451 decl,
1452 sig,
1453 external: known.is_some_and(|known| known.external),
1454 locals,
1455 scopes,
1456 });
1457 }
1458 let mut globals = Vec::new();
1465 for object in &data.objects {
1466 let Some(held) = origin.meaning.objects.get(&object.name) else { continue };
1467 globals.push(rucc_debug::Global {
1468 name: object.name.clone(),
1469 ty: held.ty,
1470 decl: Some(rucc_debug::Place {
1471 file: interned(&mut files, rewrite(&held.file)),
1472 line: held.line,
1473 }),
1474 external: held.external,
1475 });
1476 }
1477 let unit = rucc_debug::Unit {
1478 name: rewrite(origin.name),
1479 dir: rewrite(opts.working_dir.as_deref().unwrap_or(".")),
1482 producer: format!("rucc {}", crate::VERSION),
1483 files,
1484 types: origin.meaning.types.clone(),
1485 funcs,
1486 globals,
1487 pointer: u8::try_from(target.pointer_width / 8).unwrap_or(8),
1488 frames: opts.unwinds() || frames.is_some(),
1493 mach_o: target.object_format == rucc_target::ObjectFormat::MachO,
1494 };
1495 let mut info = rucc_debug::write(&unit).map_err(|why| why.to_string())?;
1496 info.chunks.extend(frames.clone());
1497 Ok(info)
1498}
1499
1500fn stretches(
1511 extent: &rucc_object::Extent,
1512 rows: &[rucc_asm::Row],
1513 built: &rucc_mir::Func,
1514 target: &TargetInfo,
1515) -> Vec<(u32, Vec<rucc_debug::Span>)> {
1516 let (false, Some(regs)) = (built.kept.is_empty(), target.call_regs) else {
1519 return Vec::new();
1520 };
1521 let ends = ends(extent, rows);
1522 let mut bounds = vec![None; built.inst_count()];
1523 for (which, row) in rows.iter().enumerate() {
1524 let Some(inst) = row.inst else { continue };
1525 bounds[inst.index()] = Some((row.at as u64, ends[which]));
1526 }
1527 let mut spots: Vec<(u32, Vec<rucc_debug::Span>)> = Vec::new();
1528 for kept in &built.kept {
1529 let (Some((from, _)), Some((_, to))) = (bounds[kept.from.index()], bounds[kept.to.index()])
1530 else {
1531 continue;
1532 };
1533 if to <= from {
1534 continue;
1535 }
1536 let held = match kept.at {
1537 rucc_mir::Where::Reg { reg, class } => match regs.dwarf(class, reg) {
1540 Some(number) => rucc_debug::Held::Reg(number),
1541 None => continue,
1542 },
1543 rucc_mir::Where::Frame(at) => rucc_debug::Held::Frame(i64::from(at)),
1544 };
1545 let span = rucc_debug::Span { from, len: to - from, held };
1546 match spots.iter_mut().find(|(decl, _)| *decl == kept.decl) {
1547 Some((_, spans)) => spans.push(span),
1548 None => spots.push((kept.decl, vec![span])),
1549 }
1550 }
1551 for (_, spans) in &mut spots {
1552 *spans = settle(std::mem::take(spans));
1553 }
1554 spots.retain(|(_, spans)| !spans.is_empty());
1555 spots
1556}
1557
1558fn ends(extent: &rucc_object::Extent, rows: &[rucc_asm::Row]) -> Vec<u64> {
1568 let mut out = vec![extent.len as u64; rows.len()];
1569 let mut next = extent.len as u64;
1570 for which in (0..rows.len()).rev() {
1571 let at = rows[which].at as u64;
1572 out[which] = match next > at {
1575 true => next,
1576 false => out.get(which + 1).copied().unwrap_or(extent.len as u64),
1577 };
1578 next = next.min(at);
1579 }
1580 out
1581}
1582
1583fn nests(
1599 wants: &[Option<usize>],
1600 scopes: &[crate::shapes::Scope],
1601 extent: &rucc_object::Extent,
1602 rows: &[rucc_asm::Row],
1603) -> (Vec<rucc_debug::Scope>, HashMap<usize, usize>) {
1604 let mut needed: Vec<usize> = Vec::new();
1605 for &want in wants {
1606 let mut up = want;
1607 while let Some(which) = up {
1608 if needed.contains(&which) {
1609 break;
1610 }
1611 needed.push(which);
1612 up = scopes.get(which).and_then(|scope| scope.parent);
1613 }
1614 }
1615 needed.sort_unstable();
1618 let at: HashMap<usize, usize> =
1619 needed.iter().enumerate().map(|(which, &scope)| (scope, which)).collect();
1620 let ends = ends(extent, rows);
1621 let out = needed
1622 .iter()
1623 .map(|&which| {
1624 let scope = &scopes[which];
1625 rucc_debug::Scope {
1626 parent: scope.parent.and_then(|parent| at.get(&parent).copied()),
1627 over: spread(scope.span, &ends, rows),
1628 }
1629 })
1630 .collect();
1631 (out, at)
1632}
1633
1634fn spread(span: Span, ends: &[u64], rows: &[rucc_asm::Row]) -> Vec<rucc_debug::Reach> {
1642 let mut out: Vec<rucc_debug::Reach> = Vec::new();
1643 for (which, row) in rows.iter().enumerate() {
1644 if row.span.is_dummy() || row.span.lo < span.lo || row.span.hi > span.hi {
1645 continue;
1646 }
1647 let (from, to) = (row.at as u64, ends[which]);
1648 if to <= from {
1649 continue;
1650 }
1651 match out.last_mut() {
1652 Some(last) if last.from + last.len >= from => {
1653 last.len = to.saturating_sub(last.from).max(last.len);
1654 }
1655 _ => out.push(rucc_debug::Reach { from, len: to - from }),
1656 }
1657 }
1658 out
1659}
1660
1661fn settle(mut spans: Vec<rucc_debug::Span>) -> Vec<rucc_debug::Span> {
1678 spans.sort_by_key(|span| (span.from, span.len));
1679 for which in 0..spans.len() {
1680 let (from, end, held) =
1681 (spans[which].from, spans[which].from + spans[which].len, spans[which].held);
1682 let later = spans[which + 1..]
1683 .iter()
1684 .take_while(|later| later.from < end)
1685 .find(|later| later.from > from && later.held != held);
1686 if let Some(later) = later {
1687 spans[which].len = later.from - from;
1688 }
1689 }
1690 let mut edges: Vec<u64> =
1693 spans.iter().flat_map(|span| [span.from, span.from + span.len]).collect();
1694 edges.sort_unstable();
1695 edges.dedup();
1696 let mut out: Vec<rucc_debug::Span> = Vec::new();
1697 let mut first = 0;
1698 for pair in edges.windows(2) {
1699 let (from, to) = (pair[0], pair[1]);
1700 while spans.get(first).is_some_and(|span| span.from + span.len <= from) {
1704 first += 1;
1705 }
1706 let mut held = None;
1707 let mut agreed = true;
1708 for span in &spans[first..] {
1709 if span.from >= to {
1710 break;
1711 }
1712 if span.from > from || span.from + span.len < to {
1713 continue;
1714 }
1715 match held {
1716 None => held = Some(span.held),
1717 Some(seen) => agreed &= seen == span.held,
1718 }
1719 }
1720 let (Some(held), true) = (held, agreed) else { continue };
1721 match out.last_mut() {
1722 Some(last) if last.from + last.len == from && last.held == held => {
1723 last.len += to - from
1724 }
1725 _ => out.push(rucc_debug::Span { from, len: to - from, held }),
1726 }
1727 }
1728 out
1729}
1730
1731fn interned(files: &mut Vec<String>, name: String) -> usize {
1737 match files.iter().position(|have| *have == name) {
1738 Some(which) => which,
1739 None => {
1740 files.push(name);
1741 files.len() - 1
1742 }
1743 }
1744}
1745
1746fn output(opts: &Options, target: &TargetInfo) -> rucc_object::Output {
1758 let mut features = 0;
1759 if target.tuple.arch() == Arch::X86_64 {
1760 if opts.control.branch() {
1761 features |= rucc_object::Property::IBT;
1762 }
1763 if opts.control.ret() {
1764 features |= rucc_object::Property::SHSTK;
1765 }
1766 }
1767 rucc_object::Output {
1768 sections: rucc_object::Sections {
1769 functions: opts.function_sections,
1770 data: opts.data_sections,
1771 },
1772 property: rucc_object::Property { features },
1773 }
1774}
1775
1776fn wrote(why: rucc_object::Error) -> Vec<Diagnostic> {
1782 match why {
1783 rucc_object::Error::Format { .. } => vec![unsupported(&why.to_string())],
1784 rucc_object::Error::Refused { .. } => vec![internal(&why.to_string())],
1785 }
1786}
1787
1788fn refused(why: rucc_asm::Error) -> Vec<Diagnostic> {
1795 match why {
1796 rucc_asm::Error::Thread { .. }
1797 | rucc_asm::Error::IFunc { .. }
1798 | rucc_asm::Error::Frame { .. } => {
1799 vec![unsupported(&why.to_string())]
1800 }
1801 _ => vec![internal(&why.to_string())],
1802 }
1803}
1804
1805fn unsupported(message: &str) -> Diagnostic {
1811 unsupported_at(message, Span::DUMMY)
1812}
1813
1814fn unsupported_at(message: &str, span: Span) -> Diagnostic {
1820 Diagnostic::error(message.to_owned(), span)
1821 .with_code("E0653")
1822 .note("this construct is not lowered yet, see https://github.com/tamnd/rucc/issues", span)
1823}
1824
1825fn invalid(message: &str) -> Diagnostic {
1827 Diagnostic::error(message.to_owned(), Span::DUMMY).with_code("E0661")
1828}
1829
1830fn internal(message: &str) -> Diagnostic {
1832 Diagnostic::error(format!("internal error: {message}"), Span::DUMMY)
1833 .with_code("E0652")
1834 .note("this is a bug in rucc rather than in the program, please report it", Span::DUMMY)
1835}
1836
1837fn su_file(stack: &StackUsage, sources: &SourceMap, file: &str) -> String {
1844 let mut out = String::new();
1845 for row in stack.rows() {
1846 let span = row.span();
1847 let at = (!span.is_dummy()).then(|| sources.presumed(span.lo)).flatten();
1848 let (name, line, column) = at.map_or((file, 0, 0), |at| (at.name, at.line, at.column));
1849 out.push_str(&row.line(name, line, column));
1850 }
1851 out
1852}
1853
1854fn failure(message: String) -> Compiled {
1857 Compiled {
1858 artifact: Artifact::Nothing,
1859 messages: vec![format!("rucc: error: {message}")],
1860 errors: 1,
1861 fired: Fired::new(),
1862 pressure: Pressure::new(),
1863 lowerings: Lowerings::new(),
1864 dumps: Vec::new(),
1865 remarks: String::new(),
1866 deps: Vec::new(),
1867 temps: Temps::default(),
1868 timing: crate::trace::Timing::default(),
1869 stack_usage: String::new(),
1870 }
1871}
1872
1873#[cfg(test)]
1874mod tests {
1875 use rucc_session::{MemoryFileSystem, Std};
1876 use rucc_target::Triple;
1877
1878 use super::*;
1879
1880 fn options() -> Options {
1881 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1882 opts.emit = EmitKind::Tast;
1883 opts.frame_pointer = Some(false);
1886 opts
1887 }
1888
1889 fn run(opts: &Options, source: &str) -> Compiled {
1890 let mut fs = MemoryFileSystem::new();
1891 fs.insert("/main.c", source.to_owned().into_bytes());
1892 compile(opts, "/main.c", &fs)
1893 }
1894
1895 fn freestanding() -> Options {
1899 let mut opts = options();
1900 opts.hosted = false;
1901 opts.search.push_system(rucc_session::runtime::DIR);
1902 opts
1903 }
1904
1905 fn shipped(source: &str) -> String {
1907 let result = run(&freestanding(), source);
1908 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1909 result.text().to_owned()
1910 }
1911
1912 fn tast(source: &str) -> String {
1914 let result = run(&options(), source);
1915 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1916 result.text().to_owned()
1917 }
1918
1919 #[test]
1920 fn the_shipped_stdarg_declares_a_list_and_the_four_operators() {
1921 let text = shipped(concat!(
1922 "#include <stdarg.h>\n",
1923 "int sum(int n, ...) {\n",
1924 " va_list ap, copy;\n",
1925 " va_start(ap, n);\n",
1926 " va_copy(copy, ap);\n",
1927 " int total = va_arg(ap, int) + va_arg(copy, int);\n",
1928 " va_end(ap);\n",
1929 " va_end(copy);\n",
1930 " return total;\n",
1931 "}\n",
1932 ));
1933 assert!(text.contains("va-start"), "{text}");
1934 assert!(text.contains("va-copy"), "{text}");
1935 assert!(text.contains("va-arg"), "{text}");
1936 assert!(text.contains("va-end"), "{text}");
1937 }
1938
1939 #[test]
1943 fn stdarg_hands_out_the_type_alone_when_that_is_all_that_was_asked_for() {
1944 let text = shipped(concat!(
1945 "#define __need___va_list\n",
1946 "#include <stdarg.h>\n",
1947 "int vprint(const char *f, __gnuc_va_list ap);\n",
1948 "#ifdef va_start\n",
1949 "#error va_start should not be defined\n",
1950 "#endif\n",
1951 "#ifdef _VA_LIST_DEFINED\n",
1952 "#error va_list should not have been made\n",
1953 "#endif\n",
1954 ));
1955 assert!(text.contains("vprint"), "{text}");
1956 }
1957
1958 #[test]
1961 fn stddef_answers_one_piece_at_a_time_and_the_next_request_still_gets_through() {
1962 let text = shipped(concat!(
1963 "#define __need_size_t\n",
1964 "#include <stddef.h>\n",
1965 "#ifdef offsetof\n",
1966 "#error offsetof should not be defined yet\n",
1967 "#endif\n",
1968 "#define __need_ptrdiff_t\n",
1969 "#include <stddef.h>\n",
1970 "#include <stddef.h>\n",
1971 "size_t a;\n",
1972 "ptrdiff_t b;\n",
1973 "wchar_t c;\n",
1974 "max_align_t d;\n",
1975 "void *e = NULL;\n",
1976 "struct P { int x; long y; };\n",
1977 "size_t f = offsetof(struct P, y);\n",
1978 ));
1979 assert!(text.contains("decl #0 a : unsigned long"), "{text}");
1980 assert!(text.contains("decl #1 b : long"), "{text}");
1981 }
1982
1983 #[test]
1984 fn the_shipped_limits_and_float_are_the_targets_own_answers() {
1985 let text = shipped(concat!(
1986 "#include <limits.h>\n",
1987 "#include <float.h>\n",
1988 "int bits = CHAR_BIT;\n",
1989 "long big = LONG_MAX;\n",
1990 "int low = INT_MIN;\n",
1991 "int radix = FLT_RADIX;\n",
1992 "int digits = DBL_MANT_DIG;\n",
1993 ));
1994 assert!(text.contains("const 8 : int"), "{text}");
1995 assert!(text.contains("const 9223372036854775807 : long"), "{text}");
1996 assert!(text.contains("const 2 : int"), "{text}");
1997 assert!(text.contains("const 53 : int"), "{text}");
1998 }
1999
2000 #[test]
2004 fn the_shipped_stdint_writes_the_whole_set_when_there_is_no_library_to_defer_to() {
2005 let text = shipped(concat!(
2006 "#include <stdint.h>\n",
2007 "int64_t a = INT64_C(1);\n",
2008 "uint_least16_t b;\n",
2009 "intptr_t c;\n",
2010 "uintmax_t d = UINTMAX_MAX;\n",
2011 "int wide = sizeof(int_fast64_t);\n",
2012 ));
2013 assert!(text.contains("decl #0 a : long"), "{text}");
2014 assert!(text.contains("decl #1 b : unsigned short"), "{text}");
2015 assert!(text.contains("decl #2 c : long"), "{text}");
2016 }
2017
2018 #[test]
2029 fn the_shipped_mmintrin_defines_the_mmx_type_and_the_operations_over_it() {
2030 let text = shipped(concat!(
2031 "#include <mmintrin.h>\n",
2032 "__m64 add(__m64 a, __m64 b) { return _mm_add_pi16(a, b); }\n",
2033 "__m64 pack(__m64 a, __m64 b) { return _m_packsswb(a, b); }\n",
2034 "__m64 shift(__m64 a) { return _mm_srai_pi32(a, 3); }\n",
2035 "int low(__m64 a) { return _mm_cvtsi64_si32(a); }\n",
2036 "void done(void) { _mm_empty(); }\n",
2037 ));
2038 assert!(text.contains("add"), "{text}");
2039 assert!(text.contains("pack"), "{text}");
2040 assert!(text.contains("shift"), "{text}");
2041 }
2042
2043 #[test]
2048 fn the_shipped_mm_malloc_asks_for_aligned_memory_and_gives_it_back() {
2049 let text = shipped(concat!(
2050 "#include <mm_malloc.h>\n",
2051 "void *get(void) { return _mm_malloc(64, 16); }\n",
2052 "void put(void *p) { _mm_free(p); }\n",
2053 ));
2054 assert!(text.contains("get"), "{text}");
2055 assert!(text.contains("put"), "{text}");
2056 }
2057
2058 #[test]
2070 fn the_shipped_xmmintrin_defines_the_sse_type_and_the_operations_over_it() {
2071 let text = shipped(concat!(
2072 "#include <xmmintrin.h>\n",
2073 "__m128 add(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
2074 "__m128 one(__m128 a, __m128 b) { return _mm_max_ss(a, b); }\n",
2075 "__m128 mask(__m128 a, __m128 b) { return _mm_cmpnle_ps(a, b); }\n",
2076 "__m128 pick(__m128 a, __m128 b) { return _mm_shuffle_ps(a, b, _MM_SHUFFLE(0,1,2,3)); }\n",
2077 "int bits(__m128 a) { return _mm_movemask_ps(a); }\n",
2078 "int near(__m128 a) { return _mm_cvtss_si32(a); }\n",
2079 "__m128 wide(__m64 a) { return _mm_cvtpi16_ps(a); }\n",
2080 "void *room(void) { return _mm_malloc(64, 16); }\n",
2081 "void hint(const float *p) { _mm_prefetch(p, _MM_HINT_T0); _mm_sfence(); }\n",
2082 ));
2083 assert!(text.contains("add"), "{text}");
2084 assert!(text.contains("mask"), "{text}");
2085 assert!(text.contains("pick"), "{text}");
2086 assert!(text.contains("wide"), "{text}");
2087 }
2088
2089 #[test]
2096 fn the_shipped_xmmintrin_leaves_out_the_names_that_need_an_instruction() {
2097 let text = rucc_session::runtime::header("xmmintrin.h").expect("xmmintrin.h is shipped");
2098 for absent in [
2099 "_mm_sqrt_ps",
2100 "_mm_sqrt_ss",
2101 "_mm_rsqrt_ps",
2102 "_mm_rsqrt_ss",
2103 "_mm_getcsr",
2104 "_mm_setcsr",
2105 ] {
2106 let defined = text.contains(&format!("{absent}("));
2107 assert!(!defined, "{absent} is defined and the header says it is not");
2108 assert!(text.contains(absent), "{absent} is absent and unexplained");
2109 }
2110 }
2111
2112 #[test]
2113 fn the_shipped_emmintrin_defines_both_sse2_types_and_the_operations_over_them() {
2114 let text = shipped(concat!(
2115 "#include <emmintrin.h>\n",
2116 "__m128i add(__m128i a, __m128i b) { return _mm_add_epi64(a, b); }\n",
2117 "__m128i wide(__m128i a, __m128i b) { return _mm_mul_epu32(a, b); }\n",
2118 "__m128i pick(__m128i a) { return _mm_shuffle_epi32(a, _MM_SHUFFLE(0,1,2,3)); }\n",
2119 "__m128i up(__m128i a) { return _mm_slli_epi64(a, 13); }\n",
2120 "__m128i down(__m128i a) { return _mm_srli_si128(a, 3); }\n",
2121 "__m128i pack(__m128i a, __m128i b) { return _mm_packus_epi16(a, b); }\n",
2122 "int bits(__m128i a) { return _mm_movemask_epi8(a); }\n",
2123 "__m128d sum(__m128d a, __m128d b) { return _mm_add_sd(a, b); }\n",
2124 "__m128d mask(__m128d a, __m128d b) { return _mm_cmpunord_pd(a, b); }\n",
2125 "__m128i near(__m128d a) { return _mm_cvtpd_epi32(a); }\n",
2126 "__m128d over(__m128 a) { return _mm_cvtps_pd(a); }\n",
2127 "__m128i half(__m64 a) { return _mm_movpi64_epi64(a); }\n",
2128 "__m128i grab(void const *p) { return _mm_loadu_si128(p); }\n",
2129 "void wall(void) { _mm_lfence(); _mm_mfence(); }\n",
2130 ));
2131 assert!(text.contains("wide"), "{text}");
2132 assert!(text.contains("pack"), "{text}");
2133 assert!(text.contains("near"), "{text}");
2134 assert!(text.contains("half"), "{text}");
2135 }
2136
2137 #[test]
2141 fn the_shipped_immintrin_reaches_the_names_the_headers_under_it_define() {
2142 let text = shipped(concat!(
2143 "#include <immintrin.h>\n",
2144 "unsigned long long matching(unsigned char tag, unsigned char const *bucket) {\n",
2145 " __m128i const want = _mm_set1_epi8((char)tag);\n",
2146 " __m128i const chunk = _mm_loadu_si128((__m128i const *)(void const *)bucket);\n",
2147 " __m128i const same = _mm_cmpeq_epi8(chunk, want);\n",
2148 " return (unsigned long long)_mm_movemask_epi8(same);\n",
2149 "}\n",
2150 "__m64 narrow(__m64 a, __m64 b) { return _mm_add_pi32(a, b); }\n",
2151 "__m128 single(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
2152 ));
2153 assert!(text.contains("matching"), "{text}");
2154 assert!(text.contains("narrow"), "the MMX header is not reached: {text}");
2155 assert!(text.contains("single"), "the SSE header is not reached: {text}");
2156 }
2157
2158 #[test]
2162 fn the_shipped_x86intrin_reaches_the_fences_windows_headers_ask_it_for() {
2163 let text = shipped(concat!(
2164 "#include <x86intrin.h>\n",
2165 "void barriers(void *p) {\n",
2166 " _mm_lfence();\n",
2167 " _mm_sfence();\n",
2168 " _mm_mfence();\n",
2169 " _mm_pause();\n",
2170 " _mm_clflush(p);\n",
2171 "}\n",
2172 "__m128i wide(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
2173 ));
2174 assert!(text.contains("barriers"), "{text}");
2175 assert!(text.contains("wide"), "the SSE2 header is not reached: {text}");
2176 }
2177
2178 #[test]
2182 fn the_umbrella_and_the_header_under_it_can_both_be_included() {
2183 let text = shipped(concat!(
2184 "#include <immintrin.h>\n",
2185 "#include <emmintrin.h>\n",
2186 "#include <immintrin.h>\n",
2187 "#include <x86intrin.h>\n",
2188 "__m128i twice(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
2189 ));
2190 assert!(text.contains("twice"), "{text}");
2191 }
2192
2193 #[test]
2196 fn the_shipped_arm_neon_has_what_xxhash_asks_it_for() {
2197 let mut opts = freestanding();
2198 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2199 let source = concat!(
2200 "#include <arm_neon.h>\n",
2201 "uint64x2_t acc(uint64x2_t sum, const void *in, const void *key) {\n",
2202 " uint8x16_t data = vld1q_u8((const uint8_t *)in);\n",
2203 " uint8x16_t k = vld1q_u8((const uint8_t *)key);\n",
2204 " uint64x2_t mixed = vreinterpretq_u64_u8(veorq_u8(data, k));\n",
2205 " uint32x2_t lo = vmovn_u64(mixed);\n",
2206 " uint32x2_t hi = vshrn_n_u64(mixed, 32);\n",
2207 " return vmlal_u32(sum, lo, hi);\n",
2208 "}\n",
2209 "uint32x4x2_t pair(uint32x4_t a, uint32x4_t b) { return vzipq_u32(a, b); }\n",
2210 "uint32_t total(uint32x4_t a) { return vaddvq_u32(a); }\n",
2211 );
2212 let result = run(&opts, source);
2213 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2214 assert!(result.text().contains("pair"), "{}", result.text());
2215 assert!(result.text().contains("total"), "{}", result.text());
2216 }
2217
2218 #[test]
2220 fn the_shipped_arm_neon_refuses_another_target() {
2221 let result = run(&freestanding(), "#include <arm_neon.h>\n");
2222 let said = result.messages.join("\n");
2223 assert!(said.contains("arm_neon.h is for AArch64"), "{said}");
2224 }
2225
2226 #[test]
2230 fn the_shipped_emmintrin_leaves_out_the_two_square_roots() {
2231 let text = rucc_session::runtime::header("emmintrin.h").expect("emmintrin.h is shipped");
2232 for absent in ["_mm_sqrt_pd", "_mm_sqrt_sd"] {
2233 let defined = text.contains(&format!("{absent}("));
2234 assert!(!defined, "{absent} is defined and the header says it is not");
2235 assert!(text.contains(absent), "{absent} is absent and unexplained");
2236 }
2237 }
2238
2239 #[test]
2244 fn the_shipped_nmmintrin_is_one_instruction_per_step_under_sse4_2() {
2245 let mut opts = freestanding();
2246 opts.emit = EmitKind::Asm;
2247 let mut choices = rucc_target::Choices::new();
2248 choices.read("sse4.2").expect("gcc knows sse4.2");
2249 opts.isa = choices.over(opts.isa);
2250 let source = concat!(
2251 "#include <nmmintrin.h>\n",
2252 "unsigned b(unsigned c, unsigned char v) { return _mm_crc32_u8(c, v); }\n",
2253 "unsigned w(unsigned c, unsigned short v) { return _mm_crc32_u16(c, v); }\n",
2254 "unsigned l(unsigned c, unsigned v) { return _mm_crc32_u32(c, v); }\n",
2255 "unsigned long long q(unsigned long long c, unsigned long long v) {\n",
2256 " return _mm_crc32_u64(c, v);\n",
2257 "}\n",
2258 "int n(unsigned v) { return _mm_popcnt_u32(v); }\n",
2259 "long long m(unsigned long long v) { return _mm_popcnt_u64(v); }\n",
2260 );
2261 let result = run(&opts, source);
2262 assert_eq!(result.messages, Vec::<String>::new());
2263 let text = result.text();
2264 for step in ["crc32b", "crc32w", "crc32l", "crc32q", "popcntl", "popcntq"] {
2265 assert!(text.contains(step), "no {step} in:\n{text}");
2266 }
2267 }
2268
2269 #[test]
2272 fn the_shipped_smmintrin_refuses_a_caller_not_built_for_the_checksum() {
2273 let result = run(
2274 &freestanding(),
2275 "#include <immintrin.h>\nunsigned f(unsigned c) { return _mm_crc32_u32(c, 1); }\n",
2276 );
2277 let said = result.messages.join("\n");
2278 let refusal = "inlining failed in call to 'always_inline' '_mm_crc32_u32': target \
2279 specific option mismatch";
2280 assert!(said.contains(refusal), "{said}");
2281 }
2282
2283 #[test]
2288 fn a_function_built_for_sse4_2_calls_the_steps_without_a_flag() {
2289 let mut opts = freestanding();
2290 opts.emit = EmitKind::Asm;
2291 let source = concat!(
2292 "#include <nmmintrin.h>\n",
2293 "#if defined(__has_attribute) && __has_attribute (target)\n",
2294 "__attribute__((target(\"sse4.2\")))\n",
2295 "#endif\n",
2296 "unsigned l(unsigned c, unsigned v) { return _mm_crc32_u32(c, v); }\n",
2297 "__attribute__((target(\"popcnt\")))\n",
2298 "int n(unsigned v) { return _mm_popcnt_u32(v); }\n",
2299 );
2300 let result = run(&opts, source);
2301 assert_eq!(result.messages, Vec::<String>::new());
2302 let text = result.text();
2303 assert!(text.contains("crc32l") && text.contains("popcntl"), "{text}");
2304 let l = &text[text.find("\nl:").expect("l is defined")..];
2305 let l = &l[..l.find("ret").expect("l returns")];
2306 assert!(l.contains("crc32l") && !l.contains("call"), "{l}");
2307 }
2308
2309 #[test]
2316 fn the_shipped_avx512_headers_pass_postgres_probes() {
2317 let popcount = concat!(
2318 "#include <immintrin.h>\n",
2319 "#include <stdint.h>\n",
2320 "char buf[sizeof(__m512i)];\n",
2321 "#if defined(__has_attribute) && __has_attribute (target)\n",
2322 "__attribute__((target(\"avx512vpopcntdq,avx512bw\")))\n",
2323 "#endif\n",
2324 "int popcount_test(void)\n",
2325 "{\n",
2326 " int64_t popcnt = 0;\n",
2327 " __m512i accum = _mm512_setzero_si512();\n",
2328 " __m512i val = _mm512_maskz_loadu_epi8((__mmask64) 0xf0f0f0f0f0f0f0f0, (const __m512i *) buf);\n",
2329 " __m512i cnt = _mm512_popcnt_epi64(val);\n",
2330 " accum = _mm512_add_epi64(accum, cnt);\n",
2331 " popcnt = _mm512_reduce_add_epi64(accum);\n",
2332 " return (int) popcnt;\n",
2333 "}\n",
2334 );
2335 let pclmul = concat!(
2336 "#include <immintrin.h>\n",
2337 "__m512i x;\n",
2338 "__m512i y;\n",
2339 "#if defined(__has_attribute) && __has_attribute (target)\n",
2340 "__attribute__((target(\"vpclmulqdq,avx512vl\")))\n",
2341 "#endif\n",
2342 "int avx512_pclmul_test(void)\n",
2343 "{\n",
2344 " __m128i z;\n",
2345 " x = _mm512_xor_si512(_mm512_zextsi128_si512(_mm_cvtsi32_si128(0)), x);\n",
2346 " y = _mm512_clmulepi64_epi128(x, y, 0);\n",
2347 " z = _mm_ternarylogic_epi64(\n",
2348 " _mm512_castsi512_si128(y),\n",
2349 " _mm512_extracti32x4_epi32(y, 1),\n",
2350 " _mm512_extracti32x4_epi32(y, 2),\n",
2351 " 0x96);\n",
2352 " return _mm_crc32_u64(0, _mm_extract_epi64(z, 0));\n",
2353 "}\n",
2354 );
2355 let checks: [(&str, &str, &[&str]); 2] = [
2356 (popcount, "popcount_test", &["kmovq", "vmovdqu8", "vpopcntq", "vpaddq", "vshufi64x2"]),
2357 (pclmul, "avx512_pclmul_test", &["vpxorq", "vpclmulqdq", "vpternlogq", "crc32q"]),
2358 ];
2359 for (source, name, wanted) in checks {
2360 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
2361 let mut opts = freestanding();
2362 opts.emit = EmitKind::Asm;
2363 opts.opt_level = level;
2364 let result = run(&opts, source);
2365 assert_eq!(result.messages, Vec::<String>::new(), "{name} at {level:?}");
2366 let text = result.text();
2367 let start = text.find(&format!("\n{name}:")).expect("the probe is written out");
2368 let body = &text[start..];
2369 let body = &body[..body.find(".size").unwrap_or(body.len())];
2370 for instruction in wanted {
2371 assert!(
2372 body.contains(instruction),
2373 "no {instruction} at {level:?} in:\n{body}"
2374 );
2375 }
2376 assert!(!body.contains("call"), "a call left behind at {level:?} in:\n{body}");
2377 }
2378 }
2379 }
2380
2381 #[test]
2384 fn the_shipped_avx512_headers_refuse_a_caller_not_built_for_them() {
2385 let result = run(
2386 &freestanding(),
2387 "#include <immintrin.h>\n__m512i f(__m512i a) { return _mm512_popcnt_epi64(a); }\n",
2388 );
2389 let said = result.messages.join("\n");
2390 let refusal = "inlining failed in call to 'always_inline' '_mm512_popcnt_epi64': target \
2391 specific option mismatch";
2392 assert!(said.contains(refusal), "{said}");
2393 }
2394
2395 #[test]
2400 fn the_sse3_to_sse4_2_intrinsics_are_the_instructions_under_the_attribute() {
2401 let mut opts = freestanding();
2402 opts.emit = EmitKind::Asm;
2403 let source = concat!(
2404 "#include <immintrin.h>\n",
2405 "__attribute__((target(\"sse3\")))\n",
2406 "__m128i a(const __m128i *p) { return _mm_lddqu_si128(p); }\n",
2407 "__attribute__((target(\"sse3\")))\n",
2408 "__m128 b(__m128 x, __m128 y) { return _mm_hadd_ps(x, y); }\n",
2409 "__attribute__((target(\"ssse3\")))\n",
2410 "__m128i c(__m128i x, __m128i y) { return _mm_shuffle_epi8(_mm_abs_epi32(x), y); }\n",
2411 "__attribute__((target(\"ssse3\")))\n",
2412 "__m128i d(__m128i x, __m128i y) { return _mm_alignr_epi8(x, y, 5); }\n",
2413 "__attribute__((target(\"sse4.1\")))\n",
2414 "int e(__m128i x, __m128i y) {\n",
2415 " return _mm_extract_epi32(_mm_min_epi32(_mm_mullo_epi32(x, y), y), 2);\n",
2416 "}\n",
2417 "__attribute__((target(\"sse4.1\")))\n",
2418 "__m128 f(__m128 x) { return _mm_round_ps(x, _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); }\n",
2419 "__attribute__((target(\"sse4.1\")))\n",
2420 "__m128i g(__m128i x, __m128i y, __m128i m) { return _mm_blendv_epi8(x, y, m); }\n",
2421 "__attribute__((target(\"sse4.1\")))\n",
2422 "int h(__m128i x) { return _mm_testz_si128(x, x); }\n",
2423 "__attribute__((target(\"sse4.2\")))\n",
2424 "__m128i i(__m128i x, __m128i y) { return _mm_cmpgt_epi64(x, y); }\n",
2425 "__attribute__((target(\"sse4.2\")))\n",
2426 "int j(__m128i x, __m128i y) { return _mm_cmpistri(x, y, _SIDD_CMP_EQUAL_EACH); }\n",
2427 );
2428 let result = run(&opts, source);
2429 assert_eq!(result.messages, Vec::<String>::new());
2430 let text = result.text();
2431 for insn in [
2432 "lddqu",
2433 "haddps",
2434 "pabsd",
2435 "pshufb",
2436 "palignr $5,",
2437 "pmulld",
2438 "pminsd",
2439 "pextrd $2,",
2440 "roundps $8,",
2441 "pblendvb",
2442 "ptest",
2443 "pcmpgtq",
2444 "pcmpistri $8,",
2445 ] {
2446 assert!(text.contains(insn), "no {insn} in:\n{text}");
2447 }
2448 assert!(!text.contains("call"), "{text}");
2449 }
2450
2451 #[test]
2454 fn the_sse3_to_sse4_1_intrinsics_are_refused_a_caller_not_built_for_them() {
2455 let source = concat!(
2456 "#include <immintrin.h>\n",
2457 "__m128i f(__m128i x, __m128i y) { return _mm_shuffle_epi8(x, y); }\n",
2458 );
2459 let said = run(&freestanding(), source).messages.join("\n");
2460 let refusal = "inlining failed in call to 'always_inline' '_mm_shuffle_epi8': target \
2461 specific option mismatch";
2462 assert!(said.contains(refusal), "{said}");
2463
2464 let mut opts = freestanding();
2465 let mut choices = rucc_target::Choices::new();
2466 choices.read("ssse3").expect("gcc knows ssse3");
2467 opts.isa = choices.over(opts.isa);
2468 let result =
2469 run(&opts, &format!("{source}__m128 g(__m128 x) {{ return _mm_movehdup_ps(x); }}\n"));
2470 assert_eq!(result.messages, Vec::<String>::new());
2471 let result = run(
2472 &opts,
2473 "#include <immintrin.h>\n__m128i h(__m128i x) { return _mm_abs_epi8(_mm_cvtepi8_epi32(x)); }\n",
2474 );
2475 let said = result.messages.join("\n");
2476 assert!(said.contains("'_mm_cvtepi8_epi32': target specific option mismatch"), "{said}");
2477 }
2478
2479 #[test]
2482 fn a_target_string_gcc_does_not_know_is_refused() {
2483 for (string, name) in [("sse5", "sse5"), ("+crc", "+crc"), ("sse4.2,foo", "foo")] {
2484 let source =
2485 format!("__attribute__((target(\"{string}\"))) int f(void) {{ return 0; }}\n");
2486 let said = run(&freestanding(), &source).messages.join("\n");
2487 let wanted = format!("attribute 'target' argument '{name}' is unknown");
2488 assert!(said.contains(&wanted), "{string}: {said}");
2489 }
2490 }
2491
2492 fn aarch64_asm(march: &str) -> Options {
2495 let mut opts = freestanding();
2496 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2497 opts.isa = rucc_target::Isa::aarch64_march(march);
2498 opts.emit = EmitKind::Asm;
2499 opts
2500 }
2501
2502 const POSTGRES_ARMV8_PROBE: &str = concat!(
2505 "#include <arm_acle.h>\n",
2506 "unsigned int crc;\n",
2507 "int main(void) {\n",
2508 " crc = __crc32cb(crc, 0);\n",
2509 " crc = __crc32ch(crc, 0);\n",
2510 " crc = __crc32cw(crc, 0);\n",
2511 " crc = __crc32cd(crc, 0);\n",
2512 " return crc == 0;\n",
2513 "}\n",
2514 );
2515
2516 #[test]
2520 fn the_shipped_arm_acle_is_one_instruction_per_step_under_crc() {
2521 for march in ["armv8-a+crc", "armv8-a+crc+simd", "armv8.1-a"] {
2522 let result = run(&aarch64_asm(march), POSTGRES_ARMV8_PROBE);
2523 assert_eq!(result.messages, Vec::<String>::new(), "{march}");
2524 let text = result.text();
2525 for step in ["crc32cb", "crc32ch", "crc32cw", "crc32cx"] {
2526 assert!(text.contains(step), "{march}: no {step} in:\n{text}");
2527 }
2528 }
2529 let source = concat!(
2530 "#include <arm_acle.h>\n",
2531 "#ifndef __ARM_FEATURE_CRC32\n",
2532 "#error \"no __ARM_FEATURE_CRC32\"\n",
2533 "#endif\n",
2534 "uint32_t b(uint32_t c, uint8_t v) { return __crc32b(c, v); }\n",
2535 "uint32_t h(uint32_t c, uint16_t v) { return __crc32h(c, v); }\n",
2536 "uint32_t w(uint32_t c, uint32_t v) { return __crc32w(c, v); }\n",
2537 "uint32_t d(uint32_t c, uint64_t v) { return __crc32d(c, v); }\n",
2538 );
2539 let result = run(&aarch64_asm("armv8-a+crc"), source);
2540 assert_eq!(result.messages, Vec::<String>::new());
2541 let text = result.text();
2542 for step in ["crc32b", "crc32h", "crc32w", "crc32x"] {
2543 assert!(text.contains(step), "no {step} in:\n{text}");
2544 }
2545 }
2546
2547 #[test]
2551 fn the_shipped_arm_acle_refuses_a_caller_not_built_for_crc() {
2552 for march in ["armv8-a", "armv8-a+simd", "armv8.1-a+nocrc"] {
2553 let said = run(&aarch64_asm(march), POSTGRES_ARMV8_PROBE).messages.join("\n");
2554 let refusal = "inlining failed in call to 'always_inline' '__crc32cb': target \
2555 specific option mismatch";
2556 assert!(said.contains(refusal), "{march}: {said}");
2557 assert!(said.contains("'-march=' with '+crc'"), "{march}: {said}");
2558 }
2559 }
2560
2561 #[test]
2565 fn a_function_built_for_crc_calls_the_steps_without_a_flag() {
2566 let source = concat!(
2567 "#include <arm_acle.h>\n",
2568 "#ifdef __ARM_FEATURE_CRC32\n",
2569 "#error \"plain armv8-a has no CRC32\"\n",
2570 "#endif\n",
2571 "__attribute__((target(\"+crc\")))\n",
2572 "uint32_t cd(uint32_t c, uint64_t v) { return __crc32cd(c, v); }\n",
2573 "__attribute__((target(\"arch=armv8.1-a\")))\n",
2574 "uint32_t cb(uint32_t c, uint8_t v) { return __crc32cb(c, v); }\n",
2575 );
2576 let result = run(&aarch64_asm("armv8-a"), source);
2577 assert_eq!(result.messages, Vec::<String>::new());
2578 let text = result.text();
2579 let cd = &text[text.find("\ncd:").expect("cd is defined")..];
2580 let cd = &cd[..cd.find("ret").expect("cd returns")];
2581 assert!(cd.contains("crc32cx") && !cd.contains("__crc32"), "{cd}");
2582 assert!(text.contains("crc32cb"), "{text}");
2583 }
2584
2585 #[test]
2587 fn the_shipped_arm_acle_refuses_another_target() {
2588 let result = run(&freestanding(), "#include <arm_acle.h>\n");
2589 let said = result.messages.join("\n");
2590 assert!(said.contains("arm_acle.h is for AArch64"), "{said}");
2591 }
2592
2593 #[test]
2596 fn an_aarch64_target_string_is_still_accepted() {
2597 let mut opts = freestanding();
2598 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2599 let source = "__attribute__((target(\"+crc\"))) int f(void) { return 0; }\n";
2600 let result = run(&opts, source);
2601 assert_eq!(result.messages, Vec::<String>::new());
2602 }
2603
2604 #[test]
2605 fn the_three_formality_headers_still_have_to_work() {
2606 let text = shipped(concat!(
2607 "#include <stdbool.h>\n",
2608 "#include <stdalign.h>\n",
2609 "#include <iso646.h>\n",
2610 "#include <stdnoreturn.h>\n",
2611 "int t = true and not false;\n",
2612 "_Alignas(16) char buf[16];\n",
2613 "int a = alignof(long);\n",
2614 ));
2615 assert!(text.contains("decl #0 t : int"), "{text}");
2616 assert!(text.contains("const 8 : unsigned long"), "{text}");
2617 }
2618
2619 #[test]
2627 fn every_shipped_header_can_be_included_twice() {
2628 let once: String = rucc_session::runtime::names()
2632 .iter()
2633 .filter(|name| {
2634 !["arm_acle.h", "arm_neon.h", "intrin.h", "setjmp.h", "vadefs.h"].contains(*name)
2635 })
2636 .map(|name| format!("#include <{name}>\n"))
2637 .collect();
2638 let twice = once.repeat(2);
2639 assert_eq!(shipped(&format!("{once}int x;\n")), shipped(&format!("{twice}int x;\n")));
2640
2641 let mut opts = freestanding();
2642 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2643 let tree = |source: &str| {
2644 let result = run(&opts, source);
2645 assert_eq!(
2646 result.messages,
2647 Vec::<String>::new(),
2648 "expected this to compile:\n{source}"
2649 );
2650 result.text().to_owned()
2651 };
2652 let neon = "#include <arm_neon.h>\n#include <arm_acle.h>\n";
2653 assert_eq!(tree(&format!("{neon}int x;\n")), tree(&format!("{neon}{neon}int x;\n")));
2654 }
2655
2656 #[test]
2657 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
2658 let fs = MemoryFileSystem::new();
2659 let result = compile(&options(), "/nope.c", &fs);
2660 assert!(result.failed());
2661 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
2662 assert!(result.text().is_empty());
2663 }
2664
2665 #[test]
2666 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
2667 let text = tast("int x = 1;\n");
2668 let expected = "\
2669decl #0 x : int object external static defined
2670 init
2671 +0
2672 const 1 : int
2673";
2674 assert_eq!(text, expected);
2675 }
2676
2677 #[test]
2678 fn the_macros_are_expanded_before_anything_is_parsed() {
2679 let text = tast("#define N 2\nint a[N];\n");
2683 assert!(text.starts_with("decl #0 a : int[2] object external static tentative"), "{text}");
2684 }
2685
2686 #[test]
2692 fn a_pragma_is_not_a_declaration_and_the_parse_walks_past_the_ones_it_does_not_read() {
2693 let text = tast(concat!(
2694 "#pragma pack(4)\n",
2695 "struct s { int a; };\n",
2696 "#pragma pack()\n",
2697 "int b;\n",
2698 "_Pragma(\"GCC visibility push(default)\") int c;\n",
2699 ));
2700 assert!(text.contains("decl #0 b : int"), "{text}");
2701 assert!(text.contains("decl #1 c : int"), "{text}");
2702 }
2703
2704 #[test]
2708 fn the_byte_swaps_and_the_bit_counts_of_a_constant_are_constants() {
2709 tast(concat!(
2710 "static const unsigned magic = __builtin_bswap32(0x11223344u);\n",
2711 "_Static_assert(__builtin_bswap16(0x1234) == 0x3412, \"16\");\n",
2712 "_Static_assert(__builtin_bswap32(0x11223344u) == 0x44332211u, \"32\");\n",
2713 "_Static_assert(__builtin_bswap64(0x0102030405060708ull) == 0x0807060504030201ull, \"64\");\n",
2714 "_Static_assert(__builtin_popcountll(-1ll) == 64 && __builtin_popcount(-1) == 32, \"ones\");\n",
2715 "_Static_assert(__builtin_parity(7) == 1 && __builtin_parity(3) == 0, \"parity\");\n",
2716 "_Static_assert(__builtin_ffs(0) == 0 && __builtin_ffs(8) == 4, \"ffs\");\n",
2717 "_Static_assert(__builtin_clrsb(0) == 31 && __builtin_clrsb(-1) == 31, \"clrsb\");\n",
2718 "_Static_assert(__builtin_clrsbl(1) == 62, \"clrsbl\");\n",
2719 "_Static_assert(__builtin_clz(1) == 31 && __builtin_clzl(1) == 63, \"clz\");\n",
2720 "_Static_assert(__builtin_ctzll(1ull << 40) == 40, \"ctz\");\n",
2721 "_Static_assert(__builtin_clz(0) == 32 && __builtin_ctzll(0) == 64, \"zero\");\n",
2722 ));
2723 }
2724
2725 #[test]
2733 fn the_layout_attributes_move_the_members_and_the_record_the_way_gcc_lays_them_out() {
2734 tast(concat!(
2735 "struct A { char c; int i; } __attribute__((packed));\n",
2736 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2737 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2738 "struct B { char c; int i; } __attribute__((aligned));\n",
2741 "_Static_assert(sizeof(struct B) == 16 && _Alignof(struct B) == 16, \"B\");\n",
2742 "struct C { char c; int i __attribute__((packed)); };\n",
2743 "_Static_assert(sizeof(struct C) == 5 && _Alignof(struct C) == 1, \"C\");\n",
2744 "_Static_assert(__builtin_offsetof(struct C, i) == 1, \"C.i\");\n",
2745 "struct D { char c; int i; } __attribute__((packed, aligned(4)));\n",
2746 "_Static_assert(sizeof(struct D) == 8 && _Alignof(struct D) == 4, \"D\");\n",
2747 "_Static_assert(__builtin_offsetof(struct D, i) == 1, \"D.i\");\n",
2748 "struct E { char c; _Alignas(8) int i; };\n",
2749 "_Static_assert(sizeof(struct E) == 16 && _Alignof(struct E) == 8, \"E\");\n",
2750 "_Static_assert(__builtin_offsetof(struct E, i) == 8, \"E.i\");\n",
2751 "struct F { char c; int i __attribute__((aligned(8))); };\n",
2752 "_Static_assert(sizeof(struct F) == 16 && _Alignof(struct F) == 8, \"F\");\n",
2753 "struct G { char c; short s; } __attribute__((aligned(2)));\n",
2756 "_Static_assert(sizeof(struct G) == 4 && _Alignof(struct G) == 2, \"G\");\n",
2757 "struct H { char c; int i; } __attribute__((aligned(2)));\n",
2758 "_Static_assert(sizeof(struct H) == 8 && _Alignof(struct H) == 4, \"H\");\n",
2759 "struct I { [[gnu::packed]] char c; int i; };\n",
2762 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2763 "struct J { char c; [[gnu::packed]] int i; };\n",
2764 "_Static_assert(sizeof(struct J) == 5 && _Alignof(struct J) == 1, \"J\");\n",
2765 "struct M { char c; int i : 5; int j : 20; } __attribute__((packed));\n",
2766 "_Static_assert(sizeof(struct M) == 5 && _Alignof(struct M) == 1, \"M\");\n",
2767 "struct N { char c; long long l; } __attribute__((aligned(32)));\n",
2768 "_Static_assert(sizeof(struct N) == 32 && _Alignof(struct N) == 32, \"N\");\n",
2769 "union L { char c; int i; } __attribute__((packed));\n",
2770 "_Static_assert(sizeof(union L) == 4 && _Alignof(union L) == 1, \"L\");\n",
2771 "struct O { char c; int i; } __attribute__((__packed__));\n",
2775 "_Static_assert(sizeof(struct O) == 5 && _Alignof(struct O) == 1, \"O\");\n",
2776 "struct P { char c; int i; } __attribute__((__aligned__(8)));\n",
2777 "_Static_assert(sizeof(struct P) == 8 && _Alignof(struct P) == 8, \"P\");\n",
2778 ));
2779 }
2780
2781 #[test]
2794 fn a_transparent_union_takes_the_member_a_value_fits_and_is_declared_either_way() {
2795 let text = tast(concat!(
2796 "struct one { int x; };\n",
2797 "struct two { long y; };\n",
2798 "typedef union { struct one *a; struct two *b; void *any; }\n",
2799 " __attribute__((__transparent_union__)) arg;\n",
2800 "int takes(arg v);\n",
2801 "int f(struct one *p, struct two *q, char *c) {\n",
2802 " return takes(p) + takes(q) + takes(c) + takes(0);\n",
2803 "}\n",
2804 "int takes(struct one *p);\n",
2806 "int (*as_a_member)(struct one *) = takes;\n",
2807 "int (*as_the_union)(arg) = takes;\n",
2808 ));
2809 assert!(text.contains("compound-literal"), "{text}");
2810 }
2811
2812 #[test]
2818 fn the_attribute_on_the_declarator_of_a_typedef_is_the_one_glibc_writes() {
2819 let text = tast(concat!(
2820 "struct sockaddr { int family; };\n",
2821 "struct sockaddr_in { int family; int addr; };\n",
2822 "typedef union { struct sockaddr *plain; struct sockaddr_in *inet; }\n",
2823 " addr_arg __attribute__((__transparent_union__));\n",
2824 "int bind_to(int fd, addr_arg where);\n",
2825 "int f(struct sockaddr_in *where) { return bind_to(0, where); }\n",
2826 ));
2827 assert!(text.contains("compound-literal"), "{text}");
2828 }
2829
2830 #[test]
2838 fn a_transparent_union_that_cannot_keep_the_promise_is_dropped_with_a_word_about_it() {
2839 let result = run(
2840 &options(),
2841 concat!(
2842 "union wider { int small; double large; } __attribute__((transparent_union));\n",
2843 "struct plain { int x; } __attribute__((transparent_union));\n",
2844 ),
2845 );
2846 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
2847 assert!(!result.failed(), "{:?}", result.messages);
2848 for message in &result.messages {
2849 assert!(message.contains("'transparent_union' attribute ignored"), "{message}");
2850 }
2851 assert!(result.messages[0].contains("first member"), "{:?}", result.messages);
2852 assert!(result.messages[1].contains("only a union"), "{:?}", result.messages);
2853 }
2854
2855 #[test]
2864 fn an_access_to_a_packed_member_says_the_alignment_the_layout_left_it() {
2865 let packed = body(concat!(
2866 "struct P { char c; int v; } __attribute__((packed));\n",
2867 "int f(struct P *p) { return p->v; }\n",
2868 ));
2869 assert!(packed.contains("load.i32 %2, align 1,"), "{packed}");
2870 let plain = body(concat!(
2872 "struct P { char c; int v; };\n",
2873 "int f(struct P *p) { return p->v; }\n",
2874 ));
2875 assert!(plain.contains("load.i32 %2, align 4,"), "{plain}");
2876 }
2877
2878 #[test]
2885 fn what_is_inside_a_packed_member_is_no_more_aligned_than_the_member_is() {
2886 let stepped = body(concat!(
2887 "struct P { char c; int v[4]; } __attribute__((packed));\n",
2888 "int f(struct P *p, int i) { return p->v[i]; }\n",
2889 ));
2890 assert!(stepped.contains(", align 1,"), "{stepped}");
2891 assert!(!stepped.contains(", align 4,"), "{stepped}");
2892 let nested = body(concat!(
2893 "struct Inner { int v; };\n",
2894 "struct P { char c; struct Inner in; } __attribute__((packed));\n",
2895 "int f(struct P *p) { return p->in.v; }\n",
2896 ));
2897 assert!(nested.contains(", align 1,"), "{nested}");
2898 assert!(!nested.contains(", align 4,"), "{nested}");
2899 }
2900
2901 #[test]
2917 fn an_access_through_a_typedef_that_lowered_its_alignment_says_the_one_the_typedef_asked_for() {
2918 let through = body(concat!(
2919 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2920 "unsigned int f(const void *p) { return *(const unalign32 *)p; }\n",
2921 ));
2922 assert!(through.contains("load.i32 %0, align 1,"), "{through}");
2923 let stepped = body(concat!(
2926 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2927 "unsigned int f(unalign32 *p, int i) { return p[i]; }\n",
2928 ));
2929 assert!(stepped.contains(", align 1,"), "{stepped}");
2930 assert!(!stepped.contains(", align 4,"), "{stepped}");
2931 let plain = body(concat!(
2934 "typedef unsigned int word;\n",
2935 "unsigned int f(const void *p) { return *(const word *)p; }\n",
2936 ));
2937 assert!(plain.contains("load.i32 %0, align 4,"), "{plain}");
2938 }
2939
2940 #[test]
2951 fn a_vector_read_through_a_typedef_that_lowered_its_alignment_comes_back_a_piece_at_a_time() {
2952 let prefix = concat!(
2953 "typedef long long v2di __attribute__((__vector_size__(16)));\n",
2954 "typedef long long v2di_u __attribute__((__vector_size__(16), __aligned__(1)));\n",
2955 );
2956 let loaded =
2957 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di_u *)p; }}"));
2958 assert_eq!(loaded.matches("align 1\n").count(), 2, "{loaded}");
2959 assert!(!loaded.contains("align 16"), "{loaded}");
2960 let stored = body(&format!("{prefix}void f(void *p, v2di b) {{ *(v2di_u *)p = b; }}"));
2963 assert!(stored.contains("memcpy %0, %3, size 16, align 1"), "{stored}");
2964 let aligned =
2966 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di *)p; }}"));
2967 assert!(aligned.contains("align 16"), "{aligned}");
2968 }
2969
2970 #[test]
2979 fn the_aligned_attribute_on_a_declaration_raises_what_that_one_object_is_aligned_to() {
2980 tast(concat!(
2981 "int v __attribute__((aligned(64)));\n",
2982 "_Static_assert(__alignof__(v) == 64, \"v\");\n",
2983 "__attribute__((aligned(32))) int w;\n",
2986 "_Static_assert(__alignof__(w) == 32, \"w\");\n",
2987 "[[gnu::aligned(16)]] int x;\n",
2988 "_Static_assert(__alignof__(x) == 16, \"x\");\n",
2989 "int y __attribute__((aligned(2)));\n",
2992 "_Static_assert(__alignof__(y) == 4, \"y\");\n",
2993 "void f(void) { int a __attribute__((aligned(128)));\n",
2995 "_Static_assert(__alignof__(a) == 128, \"a\"); (void)a; }\n",
2996 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2999 "void g(void) __attribute__((aligned(256)));\n",
3002 "void g(void) {}\n",
3003 "_Static_assert(__alignof__(g) == 256, \"g\");\n",
3004 ));
3005 }
3006
3007 #[test]
3011 fn what_a_declaration_asked_to_be_aligned_to_is_what_the_assembler_is_told() {
3012 let text = asm(concat!(
3013 "int v __attribute__((aligned(64)));\n",
3014 "void g(void) __attribute__((aligned(256)));\n",
3015 "void g(void) {}\n",
3016 "void plain(void) {}\n",
3017 ));
3018 assert!(text.contains("\t.p2align\t6\n\t.type\tv, @object\n"), "{text}");
3019 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
3020 assert!(text.contains("\t.p2align\t4, 0x90\n\t.globl\tplain\n"), "{text}");
3021 }
3022
3023 #[test]
3029 fn the_alignment_the_command_line_asked_of_every_function_is_a_floor_under_all_of_them() {
3030 let source = concat!(
3031 "void g(void) __attribute__((aligned(256)));\n",
3032 "void g(void) {}\n",
3033 "void small(void) __attribute__((aligned(4)));\n",
3034 "void small(void) {}\n",
3035 "void plain(void) {}\n",
3036 );
3037 let listing = |align: Option<u32>| {
3038 let mut opts = options();
3039 opts.emit = EmitKind::Asm;
3040 opts.align_functions = align;
3041 let result = run(&opts, source);
3042 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
3043 result.text().to_owned()
3044 };
3045
3046 let text = listing(Some(32));
3047 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "the larger one wins: {text}");
3048 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tsmall\n"), "{text}");
3049 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tplain\n"), "{text}");
3050
3051 let text = listing(Some(8));
3054 assert!(text.contains("\t.p2align\t3, 0x90\n\t.globl\tplain\n"), "{text}");
3055 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
3056 }
3057
3058 #[test]
3067 fn an_aligned_typedef_says_what_an_object_of_it_is_aligned_to_and_may_lower_it() {
3068 tast(concat!(
3069 "typedef int L __attribute__((aligned(2)));\n",
3070 "_Static_assert(__alignof__(L) == 2, \"L\");\n",
3071 "_Static_assert(_Alignof(L) == 2, \"L alignof\");\n",
3072 "_Static_assert(sizeof(L) == 4, \"L size\");\n",
3074 "struct T { char c; L x; };\n",
3075 "_Static_assert(sizeof(struct T) == 6, \"T\");\n",
3076 "_Static_assert(__builtin_offsetof(struct T, x) == 2, \"T.x\");\n",
3077 "typedef int H __attribute__((aligned(16)));\n",
3079 "_Static_assert(__alignof__(H) == 16, \"H\");\n",
3080 "_Static_assert(sizeof(H) == 4, \"H size\");\n",
3081 "struct U { char c; H x; };\n",
3082 "_Static_assert(sizeof(struct U) == 32, \"U\");\n",
3083 "_Static_assert(__builtin_offsetof(struct U, x) == 16, \"U.x\");\n",
3084 "typedef L M __attribute__((aligned(8)));\n",
3087 "_Static_assert(__alignof__(M) == 8, \"M\");\n",
3088 "typedef L N;\n",
3091 "_Static_assert(__alignof__(N) == 2, \"N\");\n",
3092 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
3094 ));
3095 let text = asm(concat!(
3096 "typedef int L __attribute__((aligned(2)));\n",
3097 "typedef int H __attribute__((aligned(16)));\n",
3098 "L low;\n",
3099 "H high;\n",
3100 ));
3101 assert!(text.contains("\t.p2align\t1\n\t.type\tlow, @object\n"), "{text}");
3102 assert!(text.contains("\t.p2align\t4\n\t.type\thigh, @object\n"), "{text}");
3103 }
3104
3105 #[test]
3116 fn a_conditional_with_a_vector_in_each_arm_is_that_vector() {
3117 tast(concat!(
3118 "typedef long long v2di __attribute__((vector_size(16)));\n",
3119 "typedef long long m128i __attribute__((vector_size(16), may_alias));\n",
3120 "v2di id(v2di);\n",
3121 "v2di f(v2di x, v2di r, int c) { return c ? x : c > 1 ? id(x) : r; }\n",
3122 "m128i g(m128i x, v2di r, int c) { return c ? x : r; }\n",
3123 "_Static_assert(sizeof(1 ? (v2di){0} : (v2di){1}) == 16, \"whole\");\n",
3124 ));
3125 let refused = errors(concat!(
3126 "typedef long long v2di __attribute__((vector_size(16)));\n",
3127 "typedef int v4si __attribute__((vector_size(16)));\n",
3128 "v2di f(v2di x, v4si r, int c) { return c ? x : r; }\n",
3129 ));
3130 assert!(refused.iter().any(|m| m.contains("type mismatch in conditional")), "{refused:?}");
3131 }
3132
3133 #[test]
3134 fn the_vector_size_attribute_builds_a_type_of_lanes_and_measures_it_in_bytes() {
3135 tast(concat!(
3136 "typedef int __attribute__((vector_size(16))) v4si;\n",
3137 "_Static_assert(sizeof(v4si) == 16 && _Alignof(v4si) == 16, \"v4si\");\n",
3138 "typedef char __attribute__((vector_size(16))) v16qi;\n",
3139 "_Static_assert(sizeof(v16qi) == 16, \"v16qi\");\n",
3140 "typedef int __attribute__((vector_size(4))) v1si;\n",
3143 "_Static_assert(sizeof(v1si) == 4, \"v1si\");\n",
3144 "typedef float __attribute__((__vector_size__(8))) v2sf;\n",
3146 "_Static_assert(sizeof(v2sf) == 8, \"v2sf\");\n",
3147 "typedef short [[gnu::vector_size(8)]] v4hi;\n",
3148 "_Static_assert(sizeof(v4hi) == 8, \"v4hi\");\n",
3149 "v4si g;\n",
3152 "_Static_assert(sizeof(g[0]) == 4, \"lane\");\n",
3153 "_Static_assert(sizeof(g + g) == 16, \"whole\");\n",
3154 "_Static_assert(sizeof(g + 1) == 16, \"broadcast\");\n",
3157 "_Static_assert(sizeof(v4si[3]) == 48, \"array\");\n",
3159 ));
3160 }
3161
3162 #[test]
3172 fn a_vector_is_written_whole_into_an_array_of_them_and_named_by_a_type_name() {
3173 tast(concat!(
3174 "typedef int __attribute__((vector_size(8))) v2si;\n",
3175 "v2si table[] = { (v2si){ 1, 2 }, (v2si){ 3, 4 } };\n",
3176 "_Static_assert(sizeof(table) == 16, \"two of them and not eight lanes\");\n",
3177 "v2si written = (int __attribute__((vector_size(8)))){ 5, 6 };\n",
3179 "_Static_assert(sizeof((int __attribute__((vector_size(16)))){ 0 }) == 16, \"named\");\n",
3180 "v2si lanes[2] = { 1, 2, 3, 4 };\n",
3183 "_Static_assert(sizeof(lanes) == 16, \"still elided\");\n",
3184 ));
3185 }
3186
3187 #[test]
3195 fn a_lane_is_assignable_and_a_shift_takes_a_count_of_its_own_lane() {
3196 let result = run(
3197 &options(),
3198 concat!(
3199 "typedef int __attribute__((vector_size(16))) v4si;\n",
3200 "typedef unsigned __attribute__((vector_size(16))) v4ui;\n",
3201 "void write(v4si *out, v4ui a, v4si b, int n) {\n",
3202 " v4si v = { 1, 2, 3, 4 };\n",
3203 " v[0] = n;\n",
3204 " v[1] += n;\n",
3205 " v[2]++;\n",
3206 " *&v[3] = n;\n",
3207 " v4ui shifted = a >> b;\n",
3209 " shifted <<= b;\n",
3210 " *out = v + (v4si)shifted + (1 << b);\n",
3213 "}\n",
3214 "void refused(const v4si c) {\n",
3217 " c[0] = 1;\n",
3218 "}\n",
3219 ),
3220 );
3221 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
3222 assert!(result.messages[0].contains("assignment of read-only"), "{:?}", result.messages);
3223 }
3224
3225 #[test]
3237 fn a_record_that_asks_for_the_other_byte_order_swaps_every_scalar_it_holds() {
3238 let read = "int f(struct s *p) { return p->i; }\n";
3239 let big = "struct s { int i; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
3240 assert!(body(&format!("{big}{read}")).contains("bswap"), "{big}");
3241
3242 let armoured =
3243 "struct s { int i; } __attribute__((__scalar_storage_order__(\"big-endian\")));\n";
3244 assert!(body(&format!("{armoured}{read}")).contains("bswap"), "{armoured}");
3245
3246 let front = "struct __attribute__((scalar_storage_order(\"big-endian\"))) s { int i; };\n";
3247 assert!(body(&format!("{front}{read}")).contains("bswap"), "{front}");
3248
3249 let standard = "struct s { int i; } [[gnu::scalar_storage_order(\"big-endian\")]];\n";
3250 assert!(body(&format!("{standard}{read}")).contains("bswap"), "{standard}");
3251
3252 let same =
3253 "struct s { int i; } __attribute__((scalar_storage_order(\"little-endian\")));\n";
3254 assert!(!body(&format!("{same}{read}")).contains("bswap"), "{same}");
3255
3256 let byte = "struct s { char c; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
3258 let source = format!("{byte}int f(struct s *p) {{ return p->c; }}\n");
3259 assert!(!body(&source).contains("bswap"), "{byte}");
3260
3261 tast(concat!(
3262 "struct s { int i; short h; char c; }",
3263 " __attribute__((scalar_storage_order(\"big-endian\")));\n",
3264 "_Static_assert(sizeof(struct s) == 8 && _Alignof(struct s) == 4, \"s\");\n",
3265 "_Static_assert(__builtin_offsetof(struct s, h) == 4, \"s.h\");\n",
3266 "_Static_assert(__builtin_offsetof(struct s, c) == 6, \"s.c\");\n",
3267 ));
3268 }
3269
3270 #[test]
3276 fn a_bit_field_in_one_of_those_records_is_counted_from_the_top_of_its_bytes() {
3277 let members = "short i : 12; char c1 : 1; char c2 : 1; char c3 : 1; char c4 : 1;";
3278 let read = "int f(struct s *p) { return p->i; }\n";
3279 let plain = format!("struct s {{ {members} }};\n{read}");
3280 let reversed = format!(
3281 "struct s {{ {members} }} __attribute__((scalar_storage_order(\"big-endian\")));\n\
3282 {read}"
3283 );
3284 assert!(body(&plain).contains("shl"), "{}", body(&plain));
3285 assert!(!body(&plain).contains("bswap"), "{}", body(&plain));
3286 let built = body(&reversed);
3289 assert!(built.contains("bswap"), "{built}");
3290 assert!(!built.contains("shl"), "{built}");
3291 assert!(built.contains("ashr"), "{built}");
3292 }
3293
3294 #[test]
3301 fn the_address_of_a_scalar_stored_the_other_way_round_is_refused() {
3302 let opts = options();
3303 let record = "struct s { int i; int a[2]; struct in { int n; } w; }\n\
3304 __attribute__((scalar_storage_order(\"big-endian\")));\n";
3305 let taken = format!("{record}int *f(struct s *p) {{ return &p->i; }}\n");
3306 assert_eq!(
3307 run(&opts, &taken).messages,
3308 ["/main.c:3:30: error: cannot take address of scalar with reverse storage order \
3309 [E0712]"]
3310 );
3311 let element = format!("{record}int *f(struct s *p) {{ return &p->a[0]; }}\n");
3312 let messages = run(&opts, &element).messages;
3313 assert!(messages[0].contains("[E0712]"), "{messages:?}");
3314
3315 let whole = format!("{record}int *f(struct s *p) {{ return (int *) &p->w; }}\n");
3316 assert_eq!(run(&opts, &whole).messages, Vec::<String>::new(), "{whole}");
3317 }
3318
3319 #[test]
3323 fn a_storage_order_that_names_neither_end_is_refused_with_the_two_words_that_are_taken() {
3324 let opts = options();
3325 let wrong = "struct s { int i; } __attribute__((scalar_storage_order(\"middle\")));\n";
3326 assert_eq!(
3327 run(&opts, wrong).messages,
3328 ["/main.c:1:36: error: 'scalar_storage_order' argument must be one of \"big-endian\" \
3329 or \"little-endian\" [E0688]"]
3330 );
3331 let bare = "struct s { int i; } __attribute__((scalar_storage_order));\n";
3332 let messages = run(&opts, bare).messages;
3333 assert!(messages[0].contains("[E0688]"), "{messages:?}");
3334 }
3335
3336 #[test]
3342 fn ms_struct_and_gcc_struct_choose_the_bit_field_rule_for_one_record() {
3343 let source = concat!(
3344 "struct __attribute__((ms_struct)) m { unsigned x : 3; char c; };\n",
3345 "struct g { unsigned x : 3; char c; } __attribute__((__gcc_struct__));\n",
3346 "struct p { unsigned x : 3; char c; };\n",
3347 "typedef struct { char c; int : 20; } __attribute__((gcc_struct)) u;\n",
3348 "_Static_assert(sizeof(struct m) == 8 && __builtin_offsetof(struct m, c) == 4, \"m\");\n",
3349 "_Static_assert(sizeof(struct g) == 4 && __builtin_offsetof(struct g, c) == 1, \"g\");\n",
3350 "_Static_assert(sizeof(u) == 4 && _Alignof(u) == 1, \"u\");\n",
3351 );
3352 let windows = "_Static_assert(sizeof(struct p) == 8, \"p\");\n";
3353 let linux = "_Static_assert(sizeof(struct p) == 4, \"p\");\n";
3354
3355 let mut opts = options();
3356 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3357 let result = run(&opts, &format!("{source}{windows}"));
3358 assert_eq!(result.messages, Vec::<String>::new());
3359 let result = run(&options(), &format!("{source}{linux}"));
3360 assert_eq!(result.messages, Vec::<String>::new());
3361
3362 let mut opts = options();
3363 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3364 let ignored = concat!(
3365 "struct __attribute__((ms_struct)) m { unsigned x : 3; char c; };\n",
3366 "_Static_assert(sizeof(struct m) == 4, \"m\");\n",
3367 );
3368 assert_eq!(run(&opts, ignored).messages, Vec::<String>::new());
3369 }
3370
3371 #[test]
3373 fn a_record_that_asks_for_both_rules_gets_the_first() {
3374 let source = concat!(
3375 "struct __attribute__((gcc_struct, ms_struct)) s { unsigned x : 3; char c; };\n",
3376 "_Static_assert(sizeof(struct s) == 4, \"s\");\n",
3377 );
3378 assert_eq!(
3379 run(&options(), source).messages,
3380 ["/main.c:1:35: warning: 'ms_struct' incompatible attribute ignored [E0746]"]
3381 );
3382 let same = "struct __attribute__((ms_struct, ms_struct)) s { unsigned x : 3; char c; };\n";
3383 assert_eq!(run(&options(), same).messages, Vec::<String>::new());
3384 }
3385
3386 #[test]
3390 fn format_takes_the_archetypes_gcc_knows_on_the_target() {
3391 let source = concat!(
3392 "int a(const char *, ...) __attribute__((format(ms_printf, 1, 2)));\n",
3393 "int b(const char *, ...) __attribute__((__format__(__gnu_printf__, 1, 2)));\n",
3394 "__attribute__((format(ms_scanf, 1, 2))) int c(const char *, ...);\n",
3395 "int d(const char *, ...) __attribute__((format(gnu_scanf, 1, 2)));\n",
3396 "unsigned long e(char *, unsigned long, const char *, const void *)\n",
3397 " __attribute__((format(ms_strftime, 3, 0)));\n",
3398 "unsigned long f(char *, unsigned long, const char *, const void *)\n",
3399 " __attribute__((format(gnu_strftime, 3, 0)));\n",
3400 "int g(const char *, ...) __attribute__((format(printf, 1, 2)));\n",
3401 );
3402 let mut opts = options();
3403 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3404 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3405
3406 let linux = run(&options(), source).messages;
3407 assert_eq!(linux.len(), 3, "{linux:?}");
3408 assert!(
3409 linux[0]
3410 .ends_with("warning: 'ms_printf' is an unrecognized format function type [E0747]"),
3411 "{linux:?}"
3412 );
3413 assert!(linux[1].contains("'ms_scanf'"), "{linux:?}");
3414 assert!(linux[2].contains("'ms_strftime'"), "{linux:?}");
3415
3416 let bogus = "int h(const char *, ...) __attribute__((format(bogus, 1, 2)));\n";
3417 let messages = run(&opts, bogus).messages;
3418 assert_eq!(messages.len(), 1, "{messages:?}");
3419 assert!(messages[0].contains("'bogus' is an unrecognized format"), "{messages:?}");
3420 }
3421
3422 #[test]
3432 fn packing_is_what_decides_whether_a_bit_field_may_straddle_its_own_storage() {
3433 assert_eq!(bit_field_byte("struct s { int x : 12; char y : 6; };"), 2);
3435 assert_eq!(
3436 bit_field_byte("struct s { int x : 12; char y : 6; } __attribute__((packed));"),
3437 1
3438 );
3439 assert_eq!(
3440 bit_field_byte("struct s { int x : 12; __attribute__((packed)) char y : 6; };"),
3441 1
3442 );
3443 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { int x : 12; char y : 6; };"), 1);
3444 assert_eq!(bit_field_byte("struct s { char x; int y : 30; };"), 4);
3446 assert_eq!(bit_field_byte("struct s { char x; int y : 30; } __attribute__((packed));"), 1);
3447 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { char x; int y : 30; };"), 1);
3449 assert_eq!(bit_field_byte("#pragma pack(2)\nstruct s { char x; int y : 30; };"), 1);
3450 }
3451
3452 fn bit_field_byte(record: &str) -> u64 {
3454 let source = format!("{record}\nint f(struct s *p) {{ return p->y; }}\n");
3455 let body = body(&source);
3456 let Some((before, _)) = body.split_once("ptr_add") else { return 0 };
3457 let (_, constant) = before.rsplit_once("iconst.i64 ").expect("an offset constant");
3458 constant.lines().next().expect("a line").trim().parse().expect("a byte offset")
3459 }
3460
3461 #[test]
3467 fn an_attribute_among_the_specifiers_is_kept_beside_the_ones_written_in_front() {
3468 tast(concat!(
3469 "struct a { char c; __attribute__((aligned(8))) int i; };\n",
3470 "_Static_assert(sizeof(struct a) == 16 && _Alignof(struct a) == 8, \"a\");\n",
3471 "_Static_assert(__builtin_offsetof(struct a, i) == 8, \"a.i\");\n",
3472 "struct b { char c; __attribute__((packed)) int i; };\n",
3473 "_Static_assert(sizeof(struct b) == 5 && _Alignof(struct b) == 1, \"b\");\n",
3474 "_Static_assert(__builtin_offsetof(struct b, i) == 1, \"b.i\");\n",
3475 "typedef struct { char c; int i; } __attribute__((packed)) c;\n",
3476 "_Static_assert(sizeof(c) == 5 && _Alignof(c) == 1, \"c\");\n",
3477 ));
3478 }
3479
3480 #[test]
3486 fn pragma_pack_caps_every_member_and_is_read_where_the_body_closes() {
3487 tast(concat!(
3488 "#pragma pack(1)\n",
3489 "struct A { char c; int i; };\n",
3490 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
3491 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
3492 "#pragma pack()\n",
3493 "struct B { char c; int i; };\n",
3494 "_Static_assert(sizeof(struct B) == 8 && _Alignof(struct B) == 4, \"B\");\n",
3495 "#pragma pack(2)\n",
3496 "struct C { char c; int i; double d; };\n",
3497 "_Static_assert(sizeof(struct C) == 14 && _Alignof(struct C) == 2, \"C\");\n",
3498 "_Static_assert(__builtin_offsetof(struct C, d) == 6, \"C.d\");\n",
3499 "struct K { char c; int i __attribute__((aligned(8))); };\n",
3501 "_Static_assert(sizeof(struct K) == 6 && _Alignof(struct K) == 2, \"K\");\n",
3502 "_Static_assert(__builtin_offsetof(struct K, i) == 2, \"K.i\");\n",
3503 "struct J { char c; int i; } __attribute__((aligned(8)));\n",
3505 "_Static_assert(sizeof(struct J) == 8 && _Alignof(struct J) == 8, \"J\");\n",
3506 "#pragma pack()\n",
3507 "#pragma pack(push, 1)\n",
3508 "struct D { char c; short s; };\n",
3509 "_Static_assert(sizeof(struct D) == 3 && _Alignof(struct D) == 1, \"D\");\n",
3510 "#pragma pack(pop)\n",
3511 "struct E { char c; short s; };\n",
3512 "_Static_assert(sizeof(struct E) == 4 && _Alignof(struct E) == 2, \"E\");\n",
3513 "struct H { char c;\n",
3515 "#pragma pack(1)\n",
3516 " int i; };\n",
3517 "_Static_assert(sizeof(struct H) == 5 && _Alignof(struct H) == 1, \"H\");\n",
3518 "#pragma pack(1)\n",
3519 "struct I { char c;\n",
3520 "#pragma pack()\n",
3521 " int i; };\n",
3522 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
3523 "#pragma pack()\n",
3524 "#pragma pack(push, 8)\n",
3526 "#pragma pack(push, 1)\n",
3527 "struct P { char c; int i; };\n",
3528 "_Static_assert(sizeof(struct P) == 5 && _Alignof(struct P) == 1, \"P\");\n",
3529 "#pragma pack(pop)\n",
3530 "struct Q { char c; int i; };\n",
3531 "_Static_assert(sizeof(struct Q) == 8 && _Alignof(struct Q) == 4, \"Q\");\n",
3532 "#pragma pack(pop)\n",
3533 "#pragma pack(16)\n",
3535 "struct R { char c; int i; };\n",
3536 "_Static_assert(sizeof(struct R) == 8 && _Alignof(struct R) == 4, \"R\");\n",
3537 "#pragma pack()\n",
3538 "#pragma pack(1)\n",
3539 "struct S { char c; int i : 5; int j : 20; };\n",
3540 "_Static_assert(sizeof(struct S) == 5 && _Alignof(struct S) == 1, \"S\");\n",
3541 "union T { char c; int i; };\n",
3542 "_Static_assert(sizeof(union T) == 4 && _Alignof(union T) == 1, \"T\");\n",
3543 "#pragma pack()\n",
3544 ));
3545 }
3546
3547 #[test]
3551 fn a_pack_line_that_is_not_one_is_reported_in_the_words_gcc_uses() {
3552 let result = run(
3553 &options(),
3554 concat!(
3555 "#pragma pack 4\n",
3556 "#pragma pack(pop)\n",
3557 "#pragma pack(3)\n",
3558 "#pragma pack(1) junk\n",
3559 "#pragma pack(push, 1\n",
3560 "#pragma pack(x)\n",
3561 "#pragma pack(0)\n",
3564 "#pragma pack(push)\n",
3565 "struct s { char c; int i; };\n",
3566 "#pragma pack(pop)\n",
3567 "#pragma pack(pop, foo)\n",
3568 ),
3569 );
3570 let expected = [
3571 "missing `(` after `#pragma pack` - ignored",
3572 "`#pragma pack (pop)` encountered without matching `#pragma pack (push)`",
3573 "alignment must be a small power of two, not 3",
3574 "junk at end of `#pragma pack`",
3575 "malformed `#pragma pack(push[, id][, <n>])` - ignored",
3576 "unknown action `x` for `#pragma pack` - ignored",
3577 "`#pragma pack(pop, foo)` encountered without matching `#pragma pack(push, foo)`",
3578 ];
3579 assert_eq!(result.messages.len(), expected.len(), "{:?}", result.messages);
3580 for (message, want) in result.messages.iter().zip(expected) {
3581 assert!(message.contains(want), "expected {want:?} in {message:?}");
3582 }
3583 }
3584
3585 #[test]
3592 fn a_declaration_behind_an_empty_macro_is_not_eaten_by_the_pragma_above_it() {
3593 let result = run(
3594 &options(),
3595 concat!(
3596 "#pragma pack(push, 1)\n",
3597 "#pragma pack(pop)\n",
3598 "#define API\n",
3599 "API const char version[] = \"3.53.4\";\n",
3600 "const char *get(void) { return version; }\n",
3601 ),
3602 );
3603 assert!(result.messages.is_empty(), "{:?}", result.messages);
3604 }
3605
3606 #[test]
3610 fn the_wide_integer_answers_to_all_three_of_its_names() {
3611 let text = tast("__uint128_t a; __int128_t b; unsigned __int128 c;\n");
3612 assert!(text.contains("decl #0 a : unsigned __int128"), "{text}");
3613 assert!(text.contains("decl #1 b : __int128"), "{text}");
3614 assert!(text.contains("decl #2 c : unsigned __int128"), "{text}");
3615 }
3616
3617 #[test]
3618 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
3619 let text = tast("long f(int a, long b) { return a + b; }\n");
3623 assert!(text.contains("convert arithmetic"), "{text}");
3624 }
3625
3626 #[test]
3627 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
3628 for source in [
3629 "#error stop\n",
3630 "int f(void) { return 1 + ; }\n",
3631 "int f(void) { return undeclared; }\n",
3632 ] {
3633 let result = run(&options(), source);
3634 assert!(result.failed(), "expected this to fail:\n{source}");
3635 assert!(
3636 result.text().is_empty(),
3637 "a file that did not compile wrote a tree:\n{source}"
3638 );
3639 }
3640 }
3641
3642 #[test]
3643 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
3644 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
3648 assert_eq!(result.errors, 1, "{:?}", result.messages);
3649 }
3650
3651 #[test]
3652 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
3653 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
3657 assert_eq!(result.errors, 1, "{:?}", result.messages);
3658 }
3659
3660 #[test]
3661 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
3662 let source = "int f(void) { char c = 300; return c; }\n";
3663 let plain = run(&options(), source);
3664 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
3665 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
3666 assert!(!plain.text().is_empty(), "a warning is not a reason to write nothing");
3667
3668 let mut opts = options();
3669 opts.warnings_are_errors = true;
3670 let strict = run(&opts, source);
3671 assert!(strict.failed());
3672 assert!(strict.text().is_empty(), "and under -Werror it is a reason to write nothing");
3673 for message in &strict.messages {
3674 assert!(!message.contains("warning:"), "{message}");
3675 }
3676 }
3677
3678 #[test]
3679 fn w_drops_the_warning_before_werror_can_promote_it() {
3680 let source = "int f(void) { char c = 300; return c; }\n";
3681 let mut opts = options();
3682 opts.warnings = false;
3683 let quiet = run(&opts, source);
3684 assert_eq!(quiet.messages, Vec::<String>::new());
3685 assert_eq!(quiet.errors, 0);
3686 assert!(!quiet.text().is_empty(), "and the file still compiles");
3687
3688 opts.warnings_are_errors = true;
3691 let both = run(&opts, source);
3692 assert_eq!(both.messages, Vec::<String>::new());
3693 assert!(!both.failed(), "-w -Werror is not an error about a warning nobody saw");
3694 }
3695
3696 #[test]
3697 fn the_dialect_reaches_the_keywords_and_the_checking() {
3698 let source = "typeof(1) x;\n";
3701 let mut opts = options();
3702 opts.std = Std::C23;
3703 opts.gnu_extensions = false;
3704 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
3705
3706 opts.std = Std::C17;
3707 assert!(run(&opts, source).failed());
3708 }
3709
3710 #[test]
3711 fn asking_for_a_kind_that_is_not_written_yet_runs_the_front_end_and_writes_nothing() {
3712 let mut opts = options();
3713 opts.emit = EmitKind::Object;
3714 let result = run(&opts, "int x = 1;\n");
3715 assert!(!result.failed(), "{:?}", result.messages);
3716 assert!(result.text().is_empty());
3717 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
3720 }
3721
3722 fn mir(source: &str) -> String {
3724 let mut opts = options();
3725 opts.emit = EmitKind::MirFinal;
3726 let result = run(&opts, source);
3727 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3728 result.text().to_owned()
3729 }
3730
3731 #[test]
3737 fn a_function_goes_from_c_to_instructions_with_real_registers_in_them() {
3738 let text = mir("int add(int a, int b) { return a + b; }\n");
3739 assert!(text.starts_with("mfunc @add {"), "{text}");
3740 assert!(text.contains("x64.add_rr_32"), "{text}");
3741 assert!(text.contains("x64.ret"), "{text}");
3742 assert!(!text.contains('%'), "{text}");
3745 }
3746
3747 #[test]
3749 fn a_function_with_no_body_produces_no_machine_function() {
3750 let text = mir("int g(int);\nint f(int a) { return g(a); }\n");
3751 assert_eq!(text.matches("mfunc @").count(), 1, "{text}");
3752 assert!(text.contains("mfunc @f {"), "{text}");
3753 assert!(text.contains("x64.call"), "{text}");
3754 }
3755
3756 #[test]
3758 fn every_definition_in_the_file_is_generated_and_they_keep_their_order() {
3759 let text = mir("int a(int x) { return x; }\nint b(int x) { return x; }\n");
3760 let first = text.find("mfunc @a").expect("the first function");
3761 let second = text.find("mfunc @b").expect("the second function");
3762 assert!(first < second, "{text}");
3763 }
3764
3765 #[test]
3767 fn the_target_decides_which_convention_the_generated_code_follows() {
3768 let mut opts = options();
3769 opts.emit = EmitKind::MirFinal;
3770 let linux = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
3771 assert!(linux.contains("$rdi"), "{linux}");
3772
3773 opts.target = "x86_64-pc-windows-msvc".parse::<Triple>().unwrap();
3774 let windows = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
3775 assert!(windows.contains("$rcx"), "{windows}");
3776 assert!(!windows.contains("$rdi"), "{windows}");
3777 }
3778
3779 #[test]
3787 fn a_tagged_member_with_no_name_is_a_member_on_windows_and_nothing_on_linux() {
3788 let source = concat!(
3789 "struct S { union U { int i; void *p; }; unsigned long tymed; };\n",
3790 "int size(void) { return sizeof(struct S); }\n",
3791 "int f(struct S *s) { s->i = 1; return s->i; }\n",
3792 );
3793
3794 let mut opts = options();
3795 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3796 let windows = run(&opts, source);
3797 assert!(windows.messages.is_empty(), "{:?}", windows.messages);
3798
3799 let linux = run(&options(), source);
3800 assert_eq!(linux.messages.len(), 3, "{:?}", linux.messages);
3801 assert!(linux.messages[0].contains("does not declare anything"), "{:?}", linux.messages);
3802
3803 let mut opts = options();
3806 opts.ms_extensions = Some(true);
3807 let asked = run(&opts, source);
3808 assert!(asked.messages.is_empty(), "{:?}", asked.messages);
3809 }
3810
3811 #[test]
3813 fn a_target_this_has_no_back_end_for_is_reported_rather_than_generated() {
3814 let mut opts = options();
3815 opts.emit = EmitKind::MirFinal;
3816 opts.target = "riscv64-unknown-linux-gnu".parse::<Triple>().unwrap();
3817 let result = run(&opts, "int f(int a) { return a; }\n");
3818 assert!(result.failed());
3819 assert!(result.messages[0].contains("no back end for riscv64"), "{:?}", result.messages);
3820 assert!(result.text().is_empty());
3821 }
3822
3823 #[test]
3826 fn an_aarch64_target_is_written_as_aarch64_assembly() {
3827 let mut opts = options();
3828 opts.emit = EmitKind::Asm;
3829 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3830 let source = "int g(int);\nint f(int a, int b) { return g(a) + b; }\n";
3831 let result = run(&opts, source);
3832 assert!(!result.failed(), "{:?}", result.messages);
3833 let text = result.text();
3834 for line in ["stp x29, x30, [sp, #-16]!", "mov x29, sp", "bl g", "ldp x29, x30, [sp], #16"]
3835 {
3836 assert!(text.contains(line), "{line} is not in\n{text}");
3837 }
3838 assert!(!text.contains('%'), "{text}");
3839 }
3840
3841 #[test]
3844 fn an_aarch64_target_reaches_an_object_file() {
3845 let mut opts = options();
3846 opts.emit = EmitKind::Object;
3847 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3848 let source = concat!(
3849 "int g(int);\n",
3850 "int table[4] = {1, 2, 3, 4};\n",
3851 "int f(int a, int b) { return g(a) + table[b & 3]; }\n",
3852 );
3853 let result = run(&opts, source);
3854 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3855 let bytes = match result.artifact {
3856 Artifact::Object { bytes, defines } => {
3857 assert_eq!(defines, ["f", "table"]);
3858 bytes
3859 }
3860 other => panic!("expected an object, got {other:?}"),
3861 };
3862 assert_eq!(&bytes[..4], b"\x7fELF");
3863 assert_eq!(&bytes[18..20], &183u16.to_le_bytes(), "EM_AARCH64");
3864
3865 opts.debug_info = true;
3868 let result = run(&opts, source);
3869 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3870 let bytes = match result.artifact {
3871 Artifact::Object { bytes, .. } => bytes,
3872 other => panic!("expected an object, got {other:?}"),
3873 };
3874 let has = |name: &[u8]| bytes.windows(name.len()).any(|at| at == name);
3875 assert!(has(b".debug_line\0") && has(b".debug_info\0"));
3876 assert!(!has(b"rucc_row"), "a row label reached the symbol table");
3877 }
3878
3879 #[test]
3883 fn an_aarch64_object_keeps_the_landing_pads_of_its_cleanups() {
3884 let mut opts = options();
3885 opts.emit = EmitKind::Object;
3886 opts.exceptions = true;
3887 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3888 let source = concat!(
3889 "void g(void);\n",
3890 "void done(int *p);\n",
3891 "void f(void) { int a __attribute__((cleanup(done))) = 1; g(); }\n",
3892 );
3893 let result = run(&opts, source);
3894 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3895 let Artifact::Object { bytes, .. } = result.artifact else { panic!("an object") };
3896 let has = |name: &[u8]| bytes.windows(name.len()).any(|at| at == name);
3897 assert!(has(b".gcc_except_table\0"), "no call site table");
3898 assert!(has(b"zPLR\0"), "no header naming the personality routine");
3899 assert!(has(b"DW.ref.__gcc_personality_v0\0"));
3900 }
3901
3902 #[test]
3905 fn the_aarch64_vector_type_names_are_declared_on_that_target_and_nowhere_else() {
3906 let mut opts = options();
3907 opts.emit = EmitKind::Asm;
3908 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3909 let source = "typedef __Float32x4_t f4;\n__SVFloat32_t sv(__SVFloat32_t, __SVBool_t);\n\
3910 int n = sizeof(f4) + sizeof(__Int8x8_t);\n\
3911 int f(f4 v) { int __Uint8x16_t = 3; return v[1] + __Uint8x16_t; }\n";
3912 let result = run(&opts, source);
3913 assert!(!result.failed(), "{:?}", result.messages);
3914 assert!(result.text().contains(".long\t24"), "{}", result.text());
3915 opts.target = "x86_64-unknown-linux-gnu".parse::<Triple>().unwrap();
3916 let result = run(&opts, "typedef __Float32x4_t f4;\n");
3917 assert!(result.failed());
3918 let result = run(&opts, "int __Float32x4_t = 1;\n");
3919 assert!(!result.failed(), "{:?}", result.messages);
3920 }
3921
3922 #[test]
3925 fn an_aarch64_result_in_memory_is_reached_through_x8() {
3926 let mut opts = options();
3927 opts.emit = EmitKind::Asm;
3928 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3929 let source = "struct big { long a, b, c; };\nstruct big make(long v);\n\
3930 long f(long v) { return make(v).c; }\n\
3931 struct big g(long v) { struct big b = { v, v, v }; return b; }\n";
3932 let result = run(&opts, source);
3933 assert!(!result.failed(), "{:?}", result.messages);
3934 let text = result.text();
3935 assert!(text.contains("x8"), "{text}");
3936 assert!(text.contains("bl make"), "{text}");
3937 }
3938
3939 #[test]
3942 fn an_aarch64_remainder_is_a_division_and_a_multiply_subtract() {
3943 let mut opts = options();
3944 opts.emit = EmitKind::Asm;
3945 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3946 let source = "int s(int a, int b) { return a % b; }\n\
3947 unsigned long u(unsigned long a, unsigned long b) { return a % b; }\n";
3948 let result = run(&opts, source);
3949 assert!(!result.failed(), "{:?}", result.messages);
3950 let text = result.text();
3951 let at = |what: &str| text.find(what).unwrap_or_else(|| panic!("{what} is not in\n{text}"));
3952 assert!(at("sdiv w") < at("msub w"), "{text}");
3953 assert!(at("udiv x") < at("msub x"), "{text}");
3954 }
3955
3956 #[test]
3959 fn an_aarch64_jump_table_is_reached_with_adr() {
3960 let mut opts = options();
3961 opts.emit = EmitKind::Asm;
3962 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3963 let source = "int f(int x) { switch (x) { case 0: return 10; case 1: return 21; \
3964 case 2: return 32; case 3: return 43; case 4: return 54; case 5: return 65; \
3965 case 6: return 76; case 7: return 87; case 8: return 98; case 9: return 9; \
3966 case 10: return 19; case 11: return 29; default: return 0; } }\n";
3967 let result = run(&opts, source);
3968 assert!(!result.failed(), "{:?}", result.messages);
3969 let text = result.text();
3970 let at = |what: &str| text.find(what).unwrap_or_else(|| panic!("{what} is not in\n{text}"));
3971 assert!(at("adr x") < at("ldrsw x"), "{text}");
3972 assert!(at("ldrsw x") < at("br x"), "{text}");
3973 assert!(text.contains("_j0:"), "{text}");
3974 assert!(text.contains(".long"), "{text}");
3975 }
3976
3977 #[test]
3981 fn an_aarch64_va_start_writes_the_five_fields_of_its_list() {
3982 let mut opts = options();
3983 opts.emit = EmitKind::Asm;
3984 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3985 let source = "typedef __builtin_va_list va_list;\n\
3986 int f(int n, ...) { va_list ap; __builtin_va_start(ap, n); \
3987 int x = __builtin_va_arg(ap, int); double d = __builtin_va_arg(ap, double); \
3988 __builtin_va_end(ap); return x + (int)d; }\n";
3989 let result = run(&opts, source);
3990 assert!(!result.failed(), "{:?}", result.messages);
3991 let text = result.text();
3992 assert!(text.contains("#-56"), "{text}");
3993 assert!(text.contains("#-128"), "{text}");
3994 assert!(text.contains("#24]"), "{text}");
3995 assert!(text.contains("#28]"), "{text}");
3996 assert!(text.contains("str q"), "{text}");
3997 }
3998
3999 #[test]
4002 fn an_aarch64_long_double_is_a_quad_in_a_vector_register() {
4003 let mut opts = options();
4004 opts.emit = EmitKind::Asm;
4005 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
4006 let source = "void f(long double *p, long double x) { *p = *p + x; }\n";
4007 let result = run(&opts, source);
4008 assert!(!result.failed(), "{:?}", result.messages);
4009 let text = result.text();
4010 assert!(text.contains("ldr q"), "{text}");
4011 assert!(text.contains("str q"), "{text}");
4012 assert!(text.contains("__addtf3"), "{text}");
4013 }
4014
4015 #[test]
4018 fn an_aarch64_thread_local_is_reached_through_tpidr_el0() {
4019 let mut opts = options();
4020 opts.emit = EmitKind::Asm;
4021 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
4022 let source = "__thread int n;\nint *f(void) { return &n; }\n\
4023 void *g(void) { return __builtin_thread_pointer(); }\n";
4024 let result = run(&opts, source);
4025 assert!(!result.failed(), "{:?}", result.messages);
4026 let text = result.text();
4027 assert!(text.contains(":gottprel:n"), "{text}");
4028 assert!(text.contains(":gottprel_lo12:n]"), "{text}");
4029 assert_eq!(text.matches("mrs x").count(), 2, "{text}");
4030 assert!(text.contains("tpidr_el0"), "{text}");
4031 }
4032
4033 #[test]
4036 fn a_darwin_thread_local_is_reached_through_its_descriptor() {
4037 let source = "__thread int n = 5;\nint *f(void) { return &n; }\n";
4038 for (triple, wanted) in [
4039 ("aarch64-apple-darwin", &["_n@TLVPPAGE\n", "_n@TLVPPAGEOFF]\n", "\tblr x"][..]),
4040 ("x86_64-apple-darwin", &["_n@TLVP(%rip), %rdi\n", "\tcall\t*%"][..]),
4041 ] {
4042 let mut opts = options();
4043 opts.emit = EmitKind::Asm;
4044 opts.target = triple.parse::<Triple>().unwrap();
4045 let result = run(&opts, source);
4046 assert!(!result.failed(), "{triple}: {:?}", result.messages);
4047 let text = result.text();
4048 for want in wanted {
4049 assert!(text.contains(want), "{triple} wanted {want:?}:\n{text}");
4050 }
4051 assert!(text.contains("\n_n:\n\t.quad\t__tlv_bootstrap\n"), "{text}");
4052 assert!(!text.contains("tpidr_el0") && !text.contains("%fs"), "{text}");
4053 }
4054 }
4055
4056 #[test]
4058 fn the_thread_pointer_is_refused_on_darwin() {
4059 let mut opts = options();
4060 opts.emit = EmitKind::Asm;
4061 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
4062 let result = run(&opts, "void *f(void) { return __builtin_thread_pointer(); }\n");
4063 assert!(result.failed());
4064 assert!(result.messages[0].contains("thread pointer"), "{:?}", result.messages);
4065 }
4066
4067 #[test]
4070 fn a_darwin_variadic_definition_saves_nothing_and_walks_the_stack() {
4071 let mut opts = options();
4072 opts.emit = EmitKind::Asm;
4073 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
4074 let source = "int f(int n, ...) { __builtin_va_list ap; __builtin_va_start(ap, n);\n\
4075 int r = __builtin_va_arg(ap, int); __builtin_va_end(ap); return r; }\n";
4076 let result = run(&opts, source);
4077 assert!(!result.failed(), "{:?}", result.messages);
4078 let text = result.text();
4079 assert!(!text.contains("str q"), "{text}");
4080 assert!(!text.contains("x7"), "{text}");
4081 }
4082
4083 #[test]
4086 fn a_darwin_call_puts_its_variadic_arguments_in_memory() {
4087 let mut opts = options();
4088 opts.emit = EmitKind::Asm;
4089 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
4090 let source = "int printf(const char *, ...);\n\
4091 int g(double x) { return printf(\"%d %f\", 7, x); }\n";
4092 let result = run(&opts, source);
4093 assert!(!result.failed(), "{:?}", result.messages);
4094 let text = result.text();
4095 assert!(text.contains("str d0, [sp, #8]"), "{text}");
4096 }
4097
4098 #[test]
4102 fn a_darwin_listing_is_one_apples_assembler_reads() {
4103 let mut opts = options();
4104 opts.emit = EmitKind::Asm;
4105 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
4106 opts.common = Some(false);
4109 let source = "extern int ext;\n\
4110 int g[4];\n\
4111 int f(int i) { return g[i] + ext; }\n";
4112 let result = run(&opts, source);
4113 assert!(!result.failed(), "{:?}", result.messages);
4114 let text = result.text();
4115 assert!(text.contains(", _g@PAGE\n"), "{text}");
4116 assert!(text.contains(", _g@PAGEOFF\n"), "{text}");
4117 assert!(text.contains(", _ext@GOTPAGE\n"), "{text}");
4118 assert!(text.contains(", _ext@GOTPAGEOFF]\n"), "{text}");
4119 assert!(!text.contains(":lo12:"), "{text}");
4120 assert!(text.contains("\t.globl\t_g\n\t.zerofill\t__DATA,__bss,_g,16,2\n"), "{text}");
4121 }
4122
4123 #[test]
4129 fn a_signed_char_on_aarch64_is_widened_with_its_sign_before_it_is_added_to() {
4130 for target in ["aarch64-linux-gnu", "aarch64-apple-darwin"] {
4131 let mut opts = options();
4132 opts.emit = EmitKind::Asm;
4133 opts.target = target.parse::<Triple>().unwrap();
4134 let source = "int f(signed char *p) { return *p + 1; }\n\
4135 unsigned g(unsigned short *p) { return *p + 1u; }\n";
4136 let result = run(&opts, source);
4137 assert!(!result.failed(), "{:?}", result.messages);
4138 let text = result.text();
4139 let signed = text.contains("\tsxtb w") || text.contains("\tldrsb w");
4140 assert!(signed, "{target}: {text}");
4141 }
4142 }
4143
4144 #[test]
4156 fn a_construct_the_back_end_cannot_reach_yet_is_reported_against_its_function() {
4157 let mut opts = options();
4158 opts.emit = EmitKind::MirFinal;
4159 let source = "void a(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
4160 s; s.x = 1; v[0] = s.x; }\n\
4161 void b(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
4162 s; s.x = 1; v[0] = s.x; }\n";
4163 let result = run(&opts, source);
4164 assert!(result.failed());
4165 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
4166 assert!(result.messages[0].contains("cannot generate code for 'a'"), "{:?}", result);
4167 assert!(result.messages[0].contains("wants more alignment"), "{:?}", result);
4168 assert!(result.messages[1].contains("cannot generate code for 'b'"), "{:?}", result);
4169 assert!(result.text().is_empty());
4170 }
4171
4172 #[test]
4180 fn a_variable_length_array_walks_its_pages_where_every_page_of_the_frame_is_to_be_touched() {
4181 let mut opts = options();
4182 opts.emit = EmitKind::MirFinal;
4183 let source = "void a(int n) { int v[n]; v[0] = 1; }\n";
4184 let plain = run(&opts, source);
4185 assert!(!plain.failed(), "{:?}", plain.messages);
4186 assert!(!plain.text().contains("cmp_set_a_64"), "{}", plain.text());
4187
4188 opts.stack_clash = true;
4189 let result = run(&opts, source);
4190 assert!(!result.failed(), "{:?}", result.messages);
4191 assert!(result.text().contains("cmp_set_a_64"), "{}", result.text());
4192 assert!(result.text().contains("or_mi_8"), "{}", result.text());
4193 }
4194
4195 #[test]
4205 fn a_function_that_keeps_a_frame_pointer_on_windows_reaches_an_object_file() {
4206 let mut opts = options();
4207 opts.emit = EmitKind::Object;
4208 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
4209 let source = concat!(
4210 "void use(void *p);\n",
4211 "void array(int n) { int v[n]; v[0] = 1; use(v); }\n",
4212 "void taken(unsigned long n) { use(__builtin_alloca(n)); }\n",
4213 );
4214 let result = run(&opts, source);
4215 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
4216 let bytes = match result.artifact {
4217 Artifact::Object { bytes, .. } => bytes,
4218 other => panic!("expected an object, got {other:?}"),
4219 };
4220 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
4221
4222 let mut opts = options();
4225 opts.emit = EmitKind::Object;
4226 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
4227 }
4228
4229 #[test]
4240 fn the_address_of_a_function_this_file_only_declares_reaches_a_windows_object() {
4241 let source = concat!(
4242 "void other(void *p);\n",
4243 "void takes(void (*f)(void *));\n",
4244 "void (*held)(void *);\n",
4245 "void pass(void) { takes(other); }\n",
4246 "void keep(void) { held = other; }\n",
4247 "void call(void) { other(0); }\n",
4248 );
4249 let mut opts = options();
4250 opts.emit = EmitKind::Object;
4251 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
4252 let result = run(&opts, source);
4253 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
4254 let bytes = match result.artifact {
4255 Artifact::Object { bytes, .. } => bytes,
4256 other => panic!("expected an object, got {other:?}"),
4257 };
4258 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
4259
4260 let mut opts = options();
4263 opts.emit = EmitKind::Object;
4264 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
4265 }
4266
4267 #[test]
4271 fn a_pragma_comment_asks_the_windows_linker_for_a_library() {
4272 let source = concat!(
4273 "#pragma comment(lib, \"ws2_32\")\n",
4274 "#pragma comment(lib, \"my lib\")\n",
4275 "#pragma comment(lib, \"libz.a\")\n",
4276 "#pragma comment(linker, \"/include:x\")\n",
4277 "#pragma comment(user, \"nobody reads this\")\n",
4278 "int f(void) { return 0; }\n",
4279 );
4280 let wanted =
4281 " /DEFAULTLIB:ws2_32.lib /DEFAULTLIB:\"my lib.lib\" /DEFAULTLIB:libz.a /include:x";
4282 let mut opts = options();
4283 opts.emit = EmitKind::Object;
4284 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
4285 let result = run(&opts, source);
4286 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
4287 let Artifact::Object { bytes, .. } = result.artifact else { panic!("expected an object") };
4288 assert!(bytes.windows(wanted.len()).any(|at| at == wanted.as_bytes()), "no options");
4289
4290 opts.emit = EmitKind::Asm;
4291 let text = match run(&opts, source).artifact {
4292 Artifact::Text(text) => text,
4293 other => panic!("expected a listing, got {other:?}"),
4294 };
4295 assert!(text.contains("\t.ascii\t\" /DEFAULTLIB:\\\"my lib.lib\\\"\"\n"), "{text}");
4296
4297 let mut opts = options();
4298 opts.emit = EmitKind::Asm;
4299 let result = run(&opts, source);
4300 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
4301 let Artifact::Text(text) = result.artifact else { panic!("expected a listing") };
4302 assert!(!text.contains("DEFAULTLIB"), "{text}");
4303
4304 let result = run(&opts, "#pragma comment(lib)\nint f(void) { return 0; }\n");
4306 assert!(result.messages.iter().any(|m| m.contains("wants a string")), "{result:?}");
4307 }
4308
4309 #[test]
4323 fn an_opcode_with_no_name_in_the_rule_language_is_named_by_its_own_spelling() {
4324 let mut opts = options();
4325 opts.emit = EmitKind::MirFinal;
4326 let source =
4327 "long double f(int a) {\n __int128 wide = a;\n return (long double) wide;\n}\n";
4328 let result = run(&opts, source);
4329 assert!(result.failed());
4330 assert!(
4331 result.messages[0].contains("no rule lowers a `sext` producing a `i128`"),
4332 "{result:?}"
4333 );
4334 assert!(result.messages[0].contains(":2:"), "the line the widening is on: {result:?}");
4335 assert!(!result.messages[0].contains("this instruction"), "{result:?}");
4336 }
4337
4338 #[test]
4340 fn the_note_on_unfinished_work_points_at_the_issues_rather_than_at_the_plan() {
4341 let mut opts = options();
4342 opts.emit = EmitKind::MirFinal;
4343 let source = "long double f(int a) { __int128 wide = a; return (long double) wide; }\n";
4344 let result = run(&opts, source);
4345 assert!(result.failed());
4346 let note = result.messages.iter().find(|line| line.contains("note:")).expect("a note");
4347 assert!(note.contains("https://github.com/tamnd/rucc/issues"), "{note}");
4348 assert!(!note.contains("spec/17-milestones.md"), "{note}");
4349 }
4350
4351 #[test]
4353 fn the_frame_flags_on_the_command_line_reach_the_generated_frame() {
4354 let source = "int f(int a) { return a; }\n";
4355 assert!(!mir(source).contains("$rbp"), "a leaf needs no frame pointer when told so");
4356
4357 let mut opts = options();
4358 opts.emit = EmitKind::MirFinal;
4359 opts.frame_pointer = Some(true);
4360 let kept = run(&opts, source).text().to_owned();
4361 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
4362
4363 opts.frame_pointer = None;
4365 let kept = run(&opts, source).text().to_owned();
4366 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
4367 }
4368
4369 fn asm(source: &str) -> String {
4371 let mut opts = options();
4372 opts.emit = EmitKind::Asm;
4373 let result = run(&opts, source);
4374 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4375 result.text().to_owned()
4376 }
4377
4378 #[test]
4385 fn a_function_goes_from_c_to_assembly_an_assembler_would_take() {
4386 let text = asm("int add(int a, int b) { return a + b; }\n");
4387 assert!(text.contains("\t.globl\tadd\n"), "{text}");
4388 assert!(text.contains("\t.type\tadd, @function\n"), "{text}");
4389 assert!(text.contains("\nadd:\n"), "{text}");
4390 assert!(text.contains("\taddl\t"), "{text}");
4391 assert!(text.contains("\tret\n"), "{text}");
4392 assert!(text.contains("\t.size\tadd, .-add\n"), "{text}");
4393 assert!(text.contains(".note.GNU-stack"), "{text}");
4396 }
4397
4398 #[test]
4404 fn a_call_through_a_function_pointer_goes_through_the_register_it_is_in() {
4405 let text = asm("int g(int);\nint f(int (*p)(int), int a) { return p(a) + g(a); }\n");
4406 assert!(text.contains("\tcall\t*%"), "{text}");
4407 assert!(text.contains("\tcall\tg\n"), "{text}");
4408 assert!(text.contains("%rdi"), "{text}");
4412 }
4413
4414 #[test]
4418 fn the_address_of_a_global_is_read_from_the_instruction_pointer() {
4419 let text = asm("extern int counter;\nint f(void) { return counter; }\n");
4420 assert!(text.contains("\tmovl\tcounter(%rip), %eax\n"), "{text}");
4421 }
4422
4423 #[test]
4432 fn a_branch_on_a_comparison_jumps_on_the_opposite_of_what_it_compared() {
4433 let arms = "return 1; return 2;";
4434 let signed = [("==", "jne"), ("!=", "je"), ("<", "jge"), ("<=", "jg"), (">", "jle")];
4435 for (operator, jump) in signed.into_iter().chain([(">=", "jl")]) {
4436 let text = asm(&format!("int f(int a, int b) {{ if (a {operator} b) {arms} }}\n"));
4437 assert!(
4438 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
4439 "{operator}: {text}"
4440 );
4441 assert!(!text.contains("\tset"), "{operator}: {text}");
4442 assert!(!text.contains("\ttest"), "{operator}: {text}");
4443 }
4444 let unsigned = [("<", "jae"), ("<=", "ja"), (">", "jbe"), (">=", "jb")];
4445 for (operator, jump) in unsigned {
4446 let source =
4447 format!("int f(unsigned a, unsigned b) {{ if (a {operator} b) {arms} }}\n");
4448 let text = asm(&source);
4449 assert!(
4450 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
4451 "{operator}: {text}"
4452 );
4453 }
4454
4455 let text = asm("int f(int a) { if (a < 7) return 1; return 2; }\n");
4458 assert!(text.contains("\tcmpl\t$7, %edi\n\tjge\t"), "{text}");
4459 }
4460
4461 #[test]
4467 fn a_comparison_whose_answer_the_program_wanted_still_writes_a_byte() {
4468 let text = asm("int f(int a, int b) { return a < b; }\n");
4469 assert!(text.contains("\tsetl\t"), "{text}");
4470 }
4471
4472 fn optimized(source: &str) -> String {
4474 let mut opts = options();
4475 opts.emit = EmitKind::Asm;
4476 opts.opt_level = rucc_session::OptLevel::O2;
4477 let result = run(&opts, source);
4478 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4479 result.text().to_owned()
4480 }
4481
4482 #[test]
4484 fn opt_info_says_what_each_switch_became_and_a_forced_shape_is_what_it_says() {
4485 let arms: String = (0..40)
4486 .map(|k| format!("case {}: return g({k});", k * 17))
4487 .collect::<Vec<_>>()
4488 .join(" ");
4489 let source = format!("int g(int);\nint f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n");
4490 let said = |shape: Option<&str>| {
4491 let mut opts = options();
4492 opts.emit = EmitKind::Asm;
4493 opts.opt_level = rucc_session::OptLevel::O2;
4494 opts.opt_info = vec![String::new()];
4495 opts.switch_shape = shape.map(str::to_owned);
4496 let result = run(&opts, &source);
4497 assert_eq!(result.messages, Vec::<String>::new());
4498 let lines: Vec<String> = result
4499 .remarks
4500 .lines()
4501 .filter(|line| line.contains("[switch-lowering]"))
4502 .map(str::to_owned)
4503 .collect();
4504 assert_eq!(lines.len(), 1, "{}", result.remarks);
4505 lines[0].clone()
4506 };
4507 assert!(said(None).contains(": f: optimized: switch of 40 cases lowered as a tree;"));
4508 assert!(said(Some("table")).contains("lowered as a table;"));
4509 assert!(said(Some("walk")).contains("lowered as a walk;"));
4510 }
4511
4512 #[test]
4522 fn a_switch_whose_arms_are_a_function_of_the_label_is_a_range_check_and_arithmetic() {
4523 let arms: String =
4524 (0..16).map(|k| format!("case {k}: return {};", k + 1)).collect::<Vec<_>>().join(" ");
4525 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
4526 assert!(text.contains("\tcmpl\t$15, %edi\n\tja\t"), "{text}");
4527 assert!(text.contains("\taddl\t$1, %edi"), "{text}");
4528 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
4529 }
4530
4531 #[test]
4539 fn a_dense_switch_whose_arms_are_not_a_line_is_a_load_from_a_table() {
4540 let arms: String = (0..16)
4541 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
4542 .collect::<Vec<_>>()
4543 .join(" ");
4544 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
4545 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
4546 assert!(!text.contains("\tjmp\t*"), "{text}");
4547 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
4548 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
4549 let section = text[..text.find("CSWTCH.0:").unwrap_or(0)].rfind("\t.section\t.rodata");
4550 assert!(section.is_some(), "{text}");
4551 assert_eq!(table.matches("\t.long\t").count(), 16, "{text}");
4552 assert!(table.contains("\t.long\t100\n"), "{text}");
4553 }
4554
4555 #[test]
4561 fn a_switch_whose_arms_give_strings_is_a_table_of_how_far_away_they_are() {
4562 let text = optimized(
4563 "const char *f(int k) { switch (k) { case 0: return \"zero\"; \
4564 case 1: return \"one\"; case 2: return \"two\"; case 3: return \"three\"; } \
4565 return \"many\"; }\n",
4566 );
4567 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
4568 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
4569 assert!(!text.contains(".data.rel.ro"), "{text}");
4570 let at = text.find("CSWTCH.0:").expect("the table is in the output");
4571 assert!(text[..at].rfind("\t.section\t.rodata").is_some(), "{text}");
4572 let table = &text[at..];
4573 assert_eq!(table.matches(" - .\n").count(), 4, "{text}");
4574 assert!(table.contains("\t.long\t.Lstr.1+4 - .\n"), "{text}");
4575 }
4576
4577 #[test]
4583 fn a_table_at_os_has_cells_as_narrow_as_its_answers() {
4584 let arms: String = (0..16)
4585 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
4586 .collect::<Vec<_>>()
4587 .join(" ");
4588 let mut opts = options();
4589 opts.emit = EmitKind::Asm;
4590 opts.opt_level = rucc_session::OptLevel::Os;
4591 let result = run(&opts, &format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
4592 assert_eq!(result.messages, Vec::<String>::new());
4593 let text = result.text();
4594 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
4595 assert_eq!(table.matches("\t.byte\t").count(), 16, "{text}");
4596 assert!(text.contains("\tmovsbl\t"), "{text}");
4597 }
4598
4599 #[test]
4606 fn a_table_that_covers_its_operand_has_no_range_check() {
4607 let text = optimized(
4608 "int f(unsigned x) { switch (x & 3) { case 0: return 5; case 1: return 9; \
4609 case 2: return 2; case 3: return 7; } return -1; }\n",
4610 );
4611 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
4612 assert!(!text.contains("\tcmp"), "{text}");
4613 assert!(!text.contains("$-1"), "{text}");
4614 }
4615
4616 #[test]
4621 fn a_store_to_a_local_the_loop_just_read_is_a_conditional_move() {
4622 let text = optimized(
4623 "int f(const int *v, int n, int k) { int best[8] = {0}; \
4624 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; \
4625 return best[k & 7]; }\n",
4626 );
4627 assert!(text.contains("\tcmovgl"), "{text}");
4628 assert!(!text.contains("\tset"), "{text}");
4629 assert!(!text.contains("\ttestb"), "{text}");
4630 }
4631
4632 #[test]
4634 fn a_store_to_a_global_the_loop_just_read_keeps_its_branch() {
4635 let text = optimized(
4636 "int best[8]; void f(const int *v, int n) { \
4637 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; }\n",
4638 );
4639 assert!(!text.contains("\tcmov"), "{text}");
4640 }
4641
4642 #[test]
4650 fn a_conversion_from_a_constant_double_is_the_number_it_converts_to() {
4651 let text = optimized("int f(void) { double d = 2.75; return (int) d; }\n");
4652 assert!(text.contains("movl\t$2, %eax"), "{text}");
4653 assert!(!text.contains("cvttsd2si"), "{text}");
4654 }
4655
4656 #[test]
4664 fn a_slot_of_a_read_only_table_is_the_value_the_table_holds() {
4665 let text =
4666 optimized("static const int t[4] = {10, 20, 30, 40};\nint f(void) { return t[2]; }\n");
4667 assert!(text.contains("movl\t$30, %eax"), "{text}");
4668 assert!(!text.contains("t(%rip)"), "{text}");
4669 }
4670
4671 #[test]
4674 fn a_byte_of_a_read_only_string_is_the_byte_the_string_spells() {
4675 let text = optimized("static const char s[] = \"abc\";\nint f(void) { return s[1]; }\n");
4676 assert!(text.contains("movl\t$98, %eax"), "{text}");
4677 }
4678
4679 #[test]
4683 fn a_table_that_is_not_read_only_keeps_its_load() {
4684 let text = optimized(
4685 "static int t[4] = {10, 20, 30, 40};\nvoid g(int x) { t[2] = x; }\nint f(void) { return t[2]; }\n",
4686 );
4687 assert!(!text.contains("movl\t$30, %eax"), "{text}");
4688 }
4689
4690 #[test]
4697 fn a_call_guarded_by_a_condition_a_read_only_object_settles_is_not_emitted() {
4698 let text = optimized(
4699 "void link_error(void);\nconst double one = 1.0;\nint main(void) { if ((int) one != 1) link_error(); return 0; }\n",
4700 );
4701 assert!(!text.contains("call\tlink_error"), "{text}");
4702 }
4703
4704 #[test]
4706 fn a_cast_between_a_pointer_and_an_integer_leaves_the_value_where_it_is() {
4707 let text = asm("long f(void *p) { return (long)p; }\n");
4708 for line in text.lines().filter(|line| line.starts_with('\t') && !line.contains('.')) {
4713 let mnemonic = line.split_whitespace().next().unwrap_or("");
4714 assert!(matches!(mnemonic, "movq" | "ret"), "{line} in\n{text}");
4715 }
4716 }
4717
4718 #[test]
4722 fn an_argument_past_the_last_register_is_read_out_of_the_caller_s_stack() {
4723 let six = "long a, long b, long c, long d, long e, long f";
4724 let text = asm(&format!("long f({six}, long g, long h) {{ return g + h; }}\n"));
4725
4726 assert!(text.contains("\tmovq\t8(%rsp), "), "{text}");
4733 assert!(text.contains("\taddq\t16(%rsp), "), "{text}");
4734
4735 let narrow = asm(&format!("int f({six}, int g) {{ return g; }}\n"));
4739 assert!(narrow.contains("\tmovl\t8(%rsp), "), "{narrow}");
4740 let eight =
4741 "double a, double b, double c, double d, double e, double f, double g, double h";
4742 let float = asm(&format!("double f({eight}, double i) {{ return i; }}\n"));
4743 assert!(float.contains("\tmovsd\t8(%rsp), "), "{float}");
4744 }
4745
4746 #[test]
4749 fn a_call_writes_the_arguments_with_no_register_left_at_the_stack_pointer() {
4750 let six = "1, 2, 3, 4, 5, 6";
4751 let decl = "long g(long, long, long, long, long, long, long, long);\n";
4752 let text = asm(&format!("{decl}long f(void) {{ return g({six}, 7, 8); }}\n"));
4753
4754 assert!(text.contains("\tmovq\t%"), "{text}");
4755 assert!(text.contains(", (%rsp)\n"), "{text}");
4756 assert!(text.contains(", 8(%rsp)\n"), "{text}");
4757 assert!(text.contains("\tsubq\t$"), "{text}");
4759
4760 let narrow = "int g(int, int, int, int, int, int, int);\n";
4762 let text = asm(&format!("{narrow}int f(void) {{ return g({six}, 7); }}\n"));
4763 assert!(text.contains("\tmovl\t%"), "{text}");
4764 assert!(text.contains(", (%rsp)\n"), "{text}");
4765 }
4766
4767 #[test]
4770 fn a_variadic_call_counts_registers_and_not_arguments() {
4771 let nine = "1., 2., 3., 4., 5., 6., 7., 8., 9.";
4772 let decl = "int g(int, ...);\n";
4773 let text = asm(&format!("{decl}int f(void) {{ return g(0, {nine}); }}\n"));
4774
4775 assert!(text.contains("\tmovl\t$8, "), "eight registers, not nine: {text}");
4776 assert!(text.contains("\tmovsd\t%"), "{text}");
4777 assert!(text.contains(", (%rsp)\n"), "{text}");
4778 }
4779
4780 #[test]
4785 fn a_variadic_function_writes_the_argument_registers_it_was_handed_into_its_frame() {
4786 let body =
4787 "__builtin_va_list ap; __builtin_va_start(ap, n); __builtin_va_end(ap); return n;";
4788 let text = asm(&format!("int f(int n, ...) {{ {body} }}\n"));
4789
4790 let stores = |mnemonic: &str| text.matches(&format!("\t{mnemonic}\t%")).count();
4793 assert!(text.contains(", 8(%r"), "the second slot, not the first: {text}");
4794 assert!(!text.contains(", 0(%r"), "{text}");
4795 assert_eq!(stores("movaps"), 8, "every vector register: {text}");
4798 assert_eq!(stores("movsd"), 0, "and the whole of each one: {text}");
4799
4800 assert!(text.contains("\tsubq\t$"), "{text}");
4802 }
4803
4804 #[test]
4807 fn va_start_writes_the_four_fields_the_psabi_describes() {
4808 let start = "__builtin_va_list ap; __builtin_va_start(ap, d);";
4809 let params = "int a, int b, int c, double d";
4810 let text = asm(&format!("int f({params}, ...) {{ {start} return a; }}\n"));
4811
4812 assert!(text.contains(" movl $24, "), "{text}");
4816 assert!(text.contains(" movl $64, "), "{text}");
4817 assert!(text.contains(", 8(%r"), "{text}");
4821 assert!(text.contains(", 16(%r"), "{text}");
4822 let frame: u32 = text
4823 .lines()
4824 .find_map(|line| line.trim().strip_prefix("subq $")?.split(',').next()?.parse().ok())
4825 .expect("a variadic function takes a frame for the save area");
4826 let above = |line: &str| {
4827 let at: u32 = line.trim().strip_prefix("leaq ")?.split('(').next()?.parse().ok()?;
4828 Some(at > frame)
4829 };
4830 assert!(text.lines().filter_map(above).any(|it| it), "{frame}: {text}");
4831 }
4832
4833 #[test]
4836 fn va_arg_branches_on_whether_the_argument_is_still_in_the_save_area() {
4837 let read = "__builtin_va_list ap; __builtin_va_start(ap, n);";
4838 let ints = format!("int f(int n, ...) {{ {read} return __builtin_va_arg(ap, int); }}\n");
4839 let text = asm(&ints);
4840
4841 assert!(text.contains("$40, "), "{text}");
4844 assert!(text.contains(" cmpl "), "{text}");
4845 assert!(text.contains(" ja "), "{text}");
4849
4850 let arg = "__builtin_va_arg(ap, double)";
4851 let text = asm(&format!("double f(int n, ...) {{ {read} return {arg}; }}\n"));
4852 assert!(text.contains("$160, "), "the last vector slot: {text}");
4853 }
4854
4855 #[test]
4858 fn a_structure_assignment_is_a_move_for_each_word_of_it() {
4859 let decl = "struct pair { long a, b; };\n";
4860 let body = "struct pair p = *q; return p.a + p.b;";
4861 let text = asm(&format!("{decl}long f(struct pair *q) {{ {body} }}\n"));
4862
4863 assert!(!text.contains("memcpy"), "nothing calls the library: {text}");
4864 assert!(!text.contains("\tcall"), "{text}");
4865 assert!(text.matches("\tmovq\t").count() >= 4, "two words each way: {text}");
4867 }
4868
4869 #[test]
4872 fn how_wide_a_word_of_a_copy_is_follows_the_alignment() {
4873 let decl = "struct bytes { char a[8]; };\n";
4874 let body = "struct bytes p = *q; return p.a[0];";
4875 let text = asm(&format!("{decl}int f(struct bytes *q) {{ {body} }}\n"));
4876
4877 assert!(text.matches("\tmovb\t").count() >= 16, "a byte at a time: {text}");
4879 }
4880
4881 #[test]
4884 fn the_part_of_an_initialiser_that_names_nothing_is_stored_as_zero() {
4885 let decl = "struct wide { long a, b, c; };\n";
4886 let text = asm(&format!("{decl}long f(void) {{ struct wide w = {{ 7 }}; return w.c; }}\n"));
4887
4888 assert!(!text.contains("memset"), "nothing calls the library: {text}");
4889 assert!(text.contains("\tmovq\t$0, ") || text.contains("\txorl\t"), "the zero: {text}");
4894 }
4895
4896 #[test]
4899 fn a_copy_too_large_to_unroll_calls_the_runtime() {
4900 let decl = "struct huge { char a[4096]; };\n";
4901 let mut opts = options();
4902 opts.emit = EmitKind::Asm;
4903 let source = format!("{decl}void f(struct huge *p, struct huge *q) {{ *p = *q; }}\n");
4904 let result = run(&opts, &source);
4905 assert!(!result.failed(), "{:?}", result.messages);
4906 let text = result.text();
4907 assert!(text.contains("call") && text.contains("memcpy"), "{text}");
4908 assert!(text.contains("4096"), "the size travels: {text}");
4911 }
4912
4913 #[test]
4920 fn a_structure_too_large_to_unroll_is_copied_into_the_argument_area_by_the_runtime() {
4921 let decl = "struct huge { char a[4096]; };\nint take(struct huge);\n";
4922 let text = asm(&format!("{decl}int f(struct huge *p) {{ return take(*p); }}\n"));
4923
4924 let copy = text.find("call\tmemcpy").expect("the copy");
4925 let call = text.find("call\ttake").expect("the call");
4926 assert!(copy < call, "the copy comes first: {text}");
4927 assert!(text.contains("movq\t%rsp, %rdi"), "the destination: {text}");
4932 assert!(text.contains("$4096, %edx"), "the size: {text}");
4933 }
4934
4935 #[test]
4938 fn a_realigned_frame_reads_them_through_the_frame_pointer() {
4939 let six = "long a, long b, long c, long d, long e, long f";
4940 let body = "_Alignas(32) long wide[4]; wide[0] = g; return wide[0];";
4941 let text = asm(&format!("long f({six}, long g) {{ {body} }}\n"));
4942
4943 assert!(text.contains("\tandq\t$-32, %rsp"), "{text}");
4947 assert!(text.contains("\tmovq\t16(%rbp), "), "{text}");
4948 assert!(!text.contains("\tmovq\t16(%rsp), "), "{text}");
4949 }
4950
4951 #[test]
4953 fn the_target_decides_how_the_assembly_is_spelled() {
4954 let mut opts = options();
4955 opts.emit = EmitKind::Asm;
4956 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
4957 let text = run(&opts, "int f(void) { return 0; }\n").text().to_owned();
4958 assert!(text.contains("__TEXT,__text"), "{text}");
4959 assert!(text.contains("\n_f:\n"), "{text}");
4960 assert!(!text.contains(".note.GNU-stack"), "{text}");
4961 }
4962
4963 fn obj(source: &str) -> Vec<u8> {
4965 let mut opts = options();
4966 opts.emit = EmitKind::Object;
4967 let result = run(&opts, source);
4968 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4969 match result.artifact {
4970 Artifact::Object { bytes, .. } => bytes,
4971 other => panic!("expected an object, got {other:?}"),
4972 }
4973 }
4974
4975 #[test]
4981 fn a_function_goes_from_c_to_an_object_a_linker_would_take() {
4982 let bytes = obj("int add(int a, int b) { return a + b; }\n");
4983 assert_eq!(&bytes[..4], b"\x7fELF", "an object file starts by saying it is one");
4984 let text = asm("int add(int a, int b) { return a + b; }\n");
4985 assert!(
4986 text.contains("\taddl\t"),
4987 "and the listing of it is the same instructions:\n{text}"
4988 );
4989 }
4990
4991 #[test]
4993 fn a_variable_goes_from_c_to_the_section_it_belongs_in() {
4994 let text = asm("int counter = 42;\nstatic int hidden;\nconst int fixed = 7;\n");
4995 assert!(text.contains("\t.data\n\t.globl\tcounter\n"), "{text}");
4996 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
4997 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
4998 assert!(text.contains("\t.bss\n\t.p2align\t2\n"), "{text}");
5001 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
5002 assert!(!text.contains(".globl\thidden"), "{text}");
5003 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
5006 }
5007
5008 #[test]
5015 fn a_bit_field_initializer_writes_every_byte_of_the_value_and_not_only_the_ones_that_are_set() {
5016 let text = asm("struct s { unsigned f : 20; } x = { 0x12300 };\n");
5017 assert!(text.contains("\t.data\n"), "there is something to write: {text}");
5018 assert!(text.contains("\nx:\n\t.ascii\t\"\\000#\\001\"\n"), "and it is the value: {text}");
5019
5020 let text = asm("struct s { unsigned a : 8; unsigned b : 8; } x = { 0, 3 };\n");
5023 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\003\"\n"), "{text}");
5024
5025 let text = asm("struct s { unsigned long long f : 40; } x = { 0x100000 };\n");
5028 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\000\\020\"\n\t.space\t5\n"), "{text}");
5029
5030 let text = asm("struct s { unsigned f : 20; } x = { 0 };\n");
5032 assert!(text.contains("\t.bss\n"), "an object of zeroes is zeroes: {text}");
5033 assert!(text.contains("\nx:\n\t.space\t4\n"), "{text}");
5034 }
5035
5036 #[test]
5038 fn a_string_literal_is_a_variable_with_a_name_no_program_could_write() {
5039 let text = asm("const char *f(void) { return \"hi\"; }\n");
5040 assert!(text.contains("\t.ascii\t\"hi\\000\"\n"), "{text}");
5041 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
5042 let label = text
5043 .lines()
5044 .find(|line| line.starts_with(".Lstr"))
5045 .unwrap_or_else(|| panic!("a label for the literal in\n{text}"));
5046 assert!(!text.contains(&format!(".globl\t{}", label.trim_end_matches(':'))), "{text}");
5047 }
5048
5049 #[test]
5051 fn an_address_in_an_initializer_is_left_to_the_linker() {
5052 let source = "int counter;\nint *p = &counter;\n";
5053 let text = asm(source);
5054 assert!(text.contains("\np:\n\t.quad\tcounter\n"), "{text}");
5055 let bytes = obj(source);
5058 assert!(bytes.windows(8).any(|w| w == b"counter\0"), "the object has to name it");
5059 }
5060
5061 #[test]
5070 fn a_constant_holding_an_address_goes_in_the_section_the_loader_may_write_once() {
5071 let text = asm("static void a(void) {}\nstatic void b(void) {}\n\
5074 struct m { void (*x)(void); void (*y)(void); };\n\
5075 const struct m t = { a, b };\n");
5076 assert!(text.contains("\t.section\t.data.rel.ro.local,\"aw\",@progbits\n"), "{text}");
5077 assert!(text.contains("\nt:\n\t.quad\ta\n\t.quad\tb\n"), "{text}");
5078
5079 let text =
5082 asm("void a(void);\nstruct m { void (*x)(void); };\nconst struct m t = { a };\n");
5083 assert!(text.contains("\t.section\t.data.rel.ro,\"aw\",@progbits\n"), "{text}");
5084
5085 let text = asm("const int fixed = 7;\n");
5087 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
5088 }
5089
5090 #[test]
5097 fn a_thread_local_variable_is_storage_a_thread_gets_a_copy_of_and_an_offset_into_it() {
5098 let text = asm("_Thread_local int x = 1;\nint read(void) { return x; }\n");
5099 assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
5102 assert!(text.contains("\t.type\tx, @tls_object\n"), "{text}");
5103 assert!(text.contains("x@GOTTPOFF(%rip)"), "{text}");
5106 assert!(text.contains("%fs:0"), "{text}");
5107 }
5108
5109 #[test]
5115 fn the_address_of_this_thread_s_own_storage_is_read_out_of_the_segment_register() {
5116 let text = asm("void *here(void) { return __builtin_thread_pointer(); }\n");
5117 assert!(text.contains("movq\t%fs:0, "), "{text}");
5118 assert!(!text.contains("GOTTPOFF"), "{text}");
5120 }
5121
5122 #[test]
5128 fn sponentry_is_the_stack_pointer_the_function_was_entered_with() {
5129 let mut opts = freestanding();
5130 opts.emit = EmitKind::Asm;
5131 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
5132 let source = "void g(void *);\nvoid f(void) { g(__builtin_sponentry()); }\n";
5133 let result = run(&opts, source);
5134 assert_eq!(result.messages, Vec::<String>::new());
5135 let text = result.text();
5136 assert!(text.contains("add x0, sp, #16"), "{text}");
5137 let result = run(&freestanding(), source);
5138 assert_eq!(result.errors, 1, "{:?}", result.messages);
5139 assert!(result.messages[0].contains("only available on AArch64"), "{:?}", result.messages);
5140 }
5141
5142 #[test]
5153 fn a_prefetch_is_one_of_four_instructions_and_the_locality_is_what_picks() {
5154 for (locality, wanted) in
5155 [(0, "prefetchnta"), (1, "prefetcht2"), (2, "prefetcht1"), (3, "prefetcht0")]
5156 {
5157 let source =
5158 format!("void warm(void *p) {{ __builtin_prefetch(p, 0, {locality}); }}\n");
5159 let text = asm(&source);
5160 assert!(text.contains(&format!("\t{wanted}\t")), "locality {locality}: {text}");
5161 }
5162 let text = asm("void warm(void *p) { __builtin_prefetch(p); }\n");
5164 assert!(text.contains("\tprefetcht0\t"), "{text}");
5165 let text = asm("void warm(void *p) { __builtin_prefetch(p, 1); }\n");
5168 assert!(text.contains("\tprefetcht0\t"), "{text}");
5169 assert!(!text.contains("prefetchw"), "{text}");
5170 }
5171
5172 #[test]
5175 fn an_aarch64_prefetch_is_a_prfm_that_says_the_locality_and_whether_it_writes() {
5176 let mut opts = options();
5177 opts.emit = EmitKind::Asm;
5178 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
5179 for (write, kind) in [(0, "pld"), (1, "pst")] {
5180 for (locality, wanted) in [(0, "l1strm"), (1, "l3keep"), (2, "l2keep"), (3, "l1keep")] {
5181 let source = format!(
5182 "void warm(void *p) {{ __builtin_prefetch(p, {write}, {locality}); }}\n"
5183 );
5184 let result = run(&opts, &source);
5185 assert_eq!(result.messages, Vec::<String>::new(), "{source}");
5186 let text = result.text();
5187 assert!(text.contains("prfm"), "{source}{text}");
5188 assert!(text.contains(&format!("{kind}{wanted}, [x0]")), "{source}{text}");
5189 }
5190 }
5191 }
5192
5193 #[test]
5204 fn a_trap_is_the_instruction_the_machine_has_no_meaning_for() {
5205 let text = asm("void stop(void) { __builtin_trap(); }\n");
5206 assert!(text.contains("\tud2\n"), "{text}");
5207 assert!(!text.contains("\tcall"), "a stop is not a call to anything: {text}");
5208
5209 let text = asm("int stop(int a) { __builtin_trap(); return a + 1; }\n");
5210 assert!(text.contains("\tud2\n"), "{text}");
5211 assert!(text.contains("\taddl\t"), "the block goes on after a stop: {text}");
5212 }
5213
5214 #[test]
5218 fn cpu_init_is_a_call_to_the_libgcc_function_that_fills_in_the_model() {
5219 let text = asm("void start(void) { __builtin_cpu_init(); }\n");
5220 assert!(text.contains("\tcall\t__cpu_indicator_init"), "{text}");
5221 assert!(!text.contains("__builtin_cpu_init"), "{text}");
5222 }
5223
5224 #[test]
5230 fn the_time_stamp_counter_is_the_instruction_in_place() {
5231 let rdtsc = "unsigned long long f(void) { return __builtin_ia32_rdtsc(); }\n";
5232 let rdtscp =
5233 "unsigned long long f(unsigned int *aux) { return __builtin_ia32_rdtscp(aux); }\n";
5234 for (source, bytes) in [(rdtsc, "0x0f, 0x31"), (rdtscp, "0x0f, 0x01, 0xf9")] {
5235 let text = asm(source);
5236 assert!(text.contains(&format!("\t.byte\t{bytes}\n")), "{text}");
5237 assert!(!text.contains("\tcall\t"), "{text}");
5238 assert!(!text.contains("rdtsc"), "{text}");
5239 }
5240 let text = asm(rdtscp);
5243 assert!(text.contains("%ecx"), "{text}");
5244
5245 for (source, bytes) in [(rdtsc, &[0x0f, 0x31][..]), (rdtscp, &[0x0f, 0x01, 0xf9][..])] {
5246 let object = obj(source);
5247 assert!(object.windows(bytes.len()).any(|w| w == bytes), "{bytes:02x?}");
5248 assert!(!object.windows(9).any(|w| w == b"__rucc_ia"), "an undefined name");
5249 assert!(!object.windows(5).any(|w| w == b"rdtsc"), "an undefined name");
5250 }
5251
5252 let mut opts = options();
5255 opts.emit = EmitKind::Asm;
5256 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
5257 let said = run(&opts, rdtsc).messages.join("\n");
5258 assert!(said.contains("only available on x86-64"), "{said}");
5259 }
5260
5261 #[test]
5269 fn cpu_supports_is_a_bit_of_the_words_libgcc_fills_in() {
5270 let text = asm("int f(void) { return __builtin_cpu_supports(\"sse4.2\"); }\n");
5271 assert!(text.contains("__cpu_model"), "{text}");
5272 assert!(text.contains("12(%"), "the fourth word of the model: {text}");
5273 assert!(text.contains("$256"), "{text}");
5274 assert!(!text.contains("\tcall"), "the answer is a read and not a call: {text}");
5275
5276 let text = asm("int f(void) { return __builtin_cpu_supports(\"vpclmulqdq\"); }\n");
5277 assert!(text.contains("__cpu_features2"), "{text}");
5278 assert!(text.contains("$2,"), "{text}");
5279
5280 let text = asm("int f(void) { return __builtin_cpu_supports(\"xsave\"); }\n");
5281 assert!(text.contains("__cpu_features2"), "{text}");
5282 assert!(text.contains("4(%"), "the second word of the second object: {text}");
5283 assert!(text.contains("$131072"), "{text}");
5284
5285 let text = asm("int f(void) { return __builtin_cpu_supports(\"avx512vbmi2\"); }\n");
5286 assert!(text.contains("set"), "the top bit is answered as a one: {text}");
5287 }
5288
5289 #[test]
5292 fn cpu_is_compares_one_word_of_the_model_with_a_number() {
5293 let text = asm("int f(void) { return __builtin_cpu_is(\"amd\"); }\n");
5294 assert!(text.contains("__cpu_model"), "{text}");
5295 assert!(text.contains("$2,"), "{text}");
5296
5297 let text = asm("int f(void) { return __builtin_cpu_is(\"znver4\"); }\n");
5298 assert!(text.contains("8(%"), "the subtype is the third word: {text}");
5299 assert!(text.contains("$29,"), "{text}");
5300 }
5301
5302 #[test]
5306 fn a_cpu_builtin_takes_a_name_it_knows_written_as_a_literal() {
5307 let mut opts = options();
5308 opts.emit = EmitKind::Ir;
5309 for (source, wanted) in [
5310 (
5311 "int f(const char *s) { return __builtin_cpu_supports(s); }\n",
5312 "parameter to builtin must be a string constant or literal",
5313 ),
5314 (
5315 "int f(void) { return __builtin_cpu_supports(\"sse5\"); }\n",
5316 "parameter to builtin not valid: sse5",
5317 ),
5318 (
5319 "int f(void) { return __builtin_cpu_is(\"sse\"); }\n",
5320 "parameter to builtin not valid: sse",
5321 ),
5322 ] {
5323 let result = run(&opts, source);
5324 assert!(
5325 result.messages.iter().any(|m| m.contains(wanted)),
5326 "{source}{:?}",
5327 result.messages
5328 );
5329 }
5330 let text = asm("int f(void) { return __builtin_cpu_supports((const char *)\"avx2\"); }\n");
5332 assert!(text.contains("$1024"), "{text}");
5333
5334 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
5335 for source in [
5336 "void f(void) { __builtin_cpu_init(); }\n",
5337 "int f(void) { return __builtin_cpu_supports(\"sse4.2\"); }\n",
5338 ] {
5339 let result = run(&opts, source);
5340 assert!(
5341 result.messages.iter().any(|m| m.contains("only available on x86-64")),
5342 "{source}{:?}",
5343 result.messages
5344 );
5345 }
5346 }
5347
5348 #[test]
5360 fn assume_aligned_is_its_first_argument_and_keeps_the_rest() {
5361 let text = asm("void *aligned(char *p) { return __builtin_assume_aligned(p, 16); }\n");
5362 assert!(!text.contains("assume_aligned"), "{text}");
5363 assert!(!text.contains("\tcall"), "nothing is called for an alignment fact: {text}");
5364
5365 let source = "unsigned long width(void);\n\
5366 void *aligned(char *p) { return __builtin_assume_aligned(p, width()); }\n";
5367 let text = asm(source);
5368 assert!(!text.contains("assume_aligned"), "{text}");
5369 assert!(text.contains("width"), "the argument that is not the answer still runs: {text}");
5370 }
5371
5372 #[test]
5382 fn the_frame_address_is_the_frame_pointer_after_walking_that_many_links() {
5383 let text = asm("void *here(void) { return __builtin_frame_address(0); }\n");
5384 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
5385 assert!(text.contains("movq\t%rbp, %rax"), "{text}");
5386 assert!(!text.contains("\tcall"), "a frame address is not a call to anything: {text}");
5387
5388 let walk = |depth: u32| {
5389 let source = format!("void *up(void) {{ return __builtin_frame_address({depth}); }}\n");
5390 asm(&source).matches("movq\t(%r").count()
5391 };
5392 assert_eq!(walk(1), 1, "one link is one load");
5393 assert_eq!(walk(3), 3, "three links are three loads");
5394 }
5395
5396 #[test]
5406 fn the_return_address_is_one_word_above_the_frame_the_walk_ended_at() {
5407 let text = asm("void *back(void) { return __builtin_return_address(0); }\n");
5408 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
5409 assert!(text.contains("movq\t8(%rbp), %rax"), "{text}");
5410 assert!(!text.contains("\tcall"), "a return address is not a call to anything: {text}");
5411
5412 let text = asm("void *back(void) { return __builtin_return_address(2); }\n");
5413 assert_eq!(text.matches("movq\t(%r").count(), 2, "two links are two loads: {text}");
5414 assert!(text.contains("movq\t8(%r"), "and the answer is above the last of them: {text}");
5415 }
5416
5417 #[test]
5428 fn a_depth_that_is_not_a_small_constant_is_refused() {
5429 let mut opts = options();
5430 opts.emit = EmitKind::Ir;
5431 for source in [
5432 "void *up(int n) { return __builtin_return_address(n); }\n",
5433 "void *up(void) { return __builtin_frame_address(1000); }\n",
5434 ] {
5435 let messages = run(&opts, source).messages;
5436 let named = messages.iter().any(|m| m.contains("E0705"));
5437 assert!(named, "expected a refusal in {messages:?}");
5438 }
5439 }
5440
5441 #[test]
5453 fn an_alloca_takes_the_bytes_off_the_stack_pointer_and_answers_where_they_are() {
5454 let text =
5455 asm("void use(void *p); void f(unsigned long n) { use(__builtin_alloca(n)); }\n");
5456 assert!(text.contains("andq\t$-16"), "the size is rounded up to sixteen: {text}");
5457 assert!(text.contains("subq\t%rdi, %rsp"), "and taken off the stack pointer: {text}");
5458 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
5459
5460 let plain = concat!(
5463 "extern void *alloca(__SIZE_TYPE__);\n",
5464 "void use(void *p);\n",
5465 "void f(unsigned long n) { use(alloca(n)); }\n",
5466 );
5467 let text = asm(plain);
5468 assert!(text.contains("subq\t%rdi, %rsp"), "the plain name is the same bytes: {text}");
5469 assert_eq!(text.matches("\tcall").count(), 1, "and is not a call either: {text}");
5470
5471 let own = concat!(
5474 "static void *alloca(unsigned long n) { return 0; }\n",
5475 "void *f(unsigned long n) { return alloca(n); }\n",
5476 );
5477 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
5478 }
5479
5480 #[test]
5495 fn a_builtin_the_program_never_declared_is_the_builtin_rather_than_the_one_c89_wrote_down() {
5496 let mut opts = options();
5500 opts.permissive = true;
5501 let undeclared = "void use(void *p);
5502void f(unsigned long n) { use(alloca(n)); }
5503";
5504 assert_eq!(
5505 run(&opts, undeclared).messages,
5506 [
5507 "/main.c:2:31: warning: implicit declaration of function 'alloca' [E0521]",
5508 "/main.c:2:31: warning: incompatible implicit declaration of built-in function \
5509 'alloca' [E0713]",
5510 ]
5511 );
5512
5513 opts.emit = EmitKind::Asm;
5514 let text = run(&opts, undeclared).text().to_owned();
5515 assert!(text.contains("subq\t%rdi, %rsp"), "the bytes come off the stack: {text}");
5516 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
5517
5518 let string = "unsigned long f(void) { return strlen(\"abc\"); }\n";
5521 let text = run(&opts, string).text().to_owned();
5522 assert!(text.contains("call\tstrlen"), "strlen is still a call: {text}");
5523
5524 let own = concat!(
5527 "static void *alloca(unsigned long n) { return 0; }\n",
5528 "void *f(unsigned long n) { return alloca(n); }\n",
5529 );
5530 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
5531 }
5532
5533 #[test]
5543 fn the_bytes_an_alloca_took_are_still_there_at_the_end_of_the_block_that_took_them() {
5544 let inner = "{ use(__builtin_alloca(n)); }";
5545 for body in [inner.to_owned(), format!("int a[n]; {inner} use(a);")] {
5546 let source = format!("void use(void *p);\nvoid f(unsigned long n) {{ {body} }}\n");
5547 let text = asm(&source);
5548 for line in text.lines().filter(|line| line.trim_end().ends_with(", %rsp")) {
5552 let taking = line.contains("subq");
5553 let leaving = line.contains("%rbp");
5554 assert!(taking || leaving, "nothing puts the stack back: {line} in {text}");
5555 }
5556 }
5557 }
5558
5559 #[test]
5561 fn the_object_and_the_listing_are_two_spellings_of_one_compilation() {
5562 let source = "int callee(void); int g(void) { return callee(); }\n";
5566 let bytes = obj(source);
5567 assert!(
5568 bytes.windows(7).any(|w| w == b"callee\0"),
5569 "the object has to name the callee for the linker to find it"
5570 );
5571 let text = asm(source);
5572 assert!(text.contains("\tcall\tcallee\n"), "{text}");
5573 }
5574
5575 #[test]
5581 fn compiling_for_an_executable_produces_an_object_and_not_a_dump() {
5582 let mut opts = options();
5583 opts.emit = EmitKind::Executable;
5585 let result = run(&opts, "int main(void) { return 0; }\n");
5586 assert_eq!(result.messages, Vec::<String>::new());
5587 match result.artifact {
5588 Artifact::Object { bytes, .. } => assert_eq!(&bytes[..4], b"\x7fELF"),
5589 other => panic!("expected an object, got {other:?}"),
5590 }
5591 }
5592
5593 #[test]
5595 fn a_platform_with_no_object_writer_is_said_so_rather_than_written_as_elf() {
5596 let mut opts = options();
5597 opts.emit = EmitKind::Object;
5598 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
5599 let result = run(&opts, "int f(void) { return 0; }\n");
5600 assert!(result.failed(), "an object nobody can read is worse than a message");
5601 assert!(
5602 result.messages.iter().any(|m| m.contains("no object writer")),
5603 "{:?}",
5604 result.messages
5605 );
5606 }
5607
5608 fn ir(source: &str) -> String {
5610 let mut opts = options();
5611 opts.emit = EmitKind::Ir;
5612 let result = run(&opts, source);
5613 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5614 result.text().to_owned()
5615 }
5616
5617 fn errors(source: &str) -> Vec<String> {
5619 let mut opts = options();
5620 opts.emit = EmitKind::Ir;
5621 let result = run(&opts, source);
5622 assert!(result.failed(), "expected this to be refused:\n{source}");
5623 result.messages
5624 }
5625
5626 fn body(source: &str) -> String {
5628 let text = ir(source);
5629 let (_, rest) = text.split_once("{\n").expect("a function definition");
5630 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
5631 body.to_owned()
5632 }
5633
5634 #[test]
5642 fn gnu89_inline_is_what_decides_whether_a_bare_inline_definition_reaches_the_module() {
5643 let source = "inline int f(int x) { return x + 1; }\n";
5644 let with = |flag: bool| {
5645 let mut opts = options();
5646 opts.emit = EmitKind::Ir;
5647 opts.gnu89_inline = flag;
5648 let result = run(&opts, source);
5649 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5650 result.text().to_owned()
5651 };
5652
5653 assert!(!with(false).contains("block0"), "no body: {}", with(false));
5656
5657 assert!(with(true).contains("block0"), "a body: {}", with(true));
5660 }
5661
5662 #[test]
5669 fn an_access_through_a_type_names_the_type_it_went_through() {
5670 let source = "\
5671struct s { int a; float b; };\n\
5672union u { int i; float f; };\n\
5673int scalar(int *p) { return *p; }\n\
5674float member(struct s *p) { p->a = 1; return p->b; }\n\
5675int element(int *a, long i) { return a[i]; }\n\
5676float through_a_union(union u *p) { p->i = 1; return p->f; }\n";
5677 let text = ir(source);
5678 assert!(text.contains(r#"!0 = tbaa "char""#), "the root: {text}");
5679 assert!(text.contains(r#"tbaa "int", parent !0"#), "int under it: {text}");
5680 assert!(text.contains(r#"tbaa "float", parent !0"#), "float under it: {text}");
5681 let named = text.lines().filter(|line| line.contains(", tbaa !")).count();
5684 assert_eq!(named, 6, "six accesses: {text}");
5685 }
5686
5687 #[test]
5694 fn turning_strict_aliasing_off_leaves_the_type_off_every_access() {
5695 let source = "int punned(float *f, int *i) { *i = 1; *f = 2.0f; return *i; }\n";
5696 let mut opts = options();
5697 opts.emit = EmitKind::Ir;
5698 opts.strict_aliasing = false;
5699 let result = run(&opts, source);
5700 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5701 let text = result.text().to_owned();
5702 assert!(!text.contains("tbaa"), "not even the root: {text}");
5703 }
5704
5705 #[test]
5711 fn instrumenting_functions_calls_the_hooks_around_every_body_but_the_hooks() {
5712 let source = concat!(
5713 "#define NOCHK __attribute__((no_instrument_function))\n",
5714 "void __cyg_profile_func_enter(void *, void *) NOCHK;\n",
5715 "void __cyg_profile_func_exit(void *, void *) NOCHK;\n",
5716 "int calls;\n",
5717 "int pick(int x) { if (x) return 1; return 2; }\n",
5718 "void quiet(void) NOCHK;\n",
5719 "void quiet(void) { calls++; }\n",
5720 "void __cyg_profile_func_enter(void *fn, void *site) { calls++; }\n",
5721 "void __cyg_profile_func_exit(void *fn, void *site) { calls--; }\n",
5722 );
5723 let mut opts = options();
5724 opts.emit = EmitKind::Ir;
5725 opts.instrument_functions = true;
5726 let result = run(&opts, source);
5727 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5728 let text = result.text().to_owned();
5729 let body = |name: &str| -> String {
5730 let open = format!("func @{name}(");
5731 let start = text.find(&open).unwrap_or_else(|| panic!("no {name}: {text}"));
5732 let rest = &text[start..];
5733 rest[..rest.find("\n}").unwrap_or(rest.len())].to_owned()
5734 };
5735 let pick = body("pick");
5736 assert_eq!(pick.matches("call @__cyg_profile_func_enter(").count(), 1, "{pick}");
5737 assert_eq!(pick.matches("call @__cyg_profile_func_exit(").count(), 2, "{pick}");
5738 assert!(pick.contains("return_address"), "{pick}");
5739 assert!(pick.contains("global_addr @pick"), "{pick}");
5740 for quiet in ["quiet", "__cyg_profile_func_enter", "__cyg_profile_func_exit"] {
5741 assert!(!body(quiet).contains("call "), "{quiet} is left alone: {text}");
5742 }
5743
5744 opts.instrument_functions = false;
5745 let result = run(&opts, source);
5746 assert!(!result.text().contains("call @__cyg_profile"), "off unless asked for");
5747 }
5748
5749 #[test]
5754 fn calls_that_owe_the_same_handlers_share_one_landing_pad() {
5755 let source = concat!(
5756 "void done(int *p);\n",
5757 "void work(int);\n",
5758 "void f(void) {\n",
5759 " int a __attribute__((cleanup(done))) = 1;\n",
5760 " work(1); work(2);\n",
5761 " { int b __attribute__((cleanup(done))) = 2; work(3); work(4); }\n",
5762 " work(5); work(6);\n",
5763 "}\n",
5764 );
5765 let mut opts = options();
5766 opts.emit = EmitKind::Ir;
5767 opts.exceptions = true;
5768 let result = run(&opts, source);
5769 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5770 let text = result.text();
5771 assert_eq!(text.matches("= unwound").count(), 8, "every call has its edge: {text}");
5774 assert_eq!(text.matches("= landing").count(), 2, "one pad for a, one for b and a: {text}");
5775 }
5776
5777 #[test]
5781 fn a_goto_that_runs_handlers_gives_each_one_an_edge_to_what_is_still_owed() {
5782 let source = concat!(
5783 "void done(int *p);\n",
5784 "void f(int n) {\n",
5785 " int a __attribute__((cleanup(done))) = 1;\n",
5786 " { int b __attribute__((cleanup(done))) = 2;\n",
5787 " { int c __attribute__((cleanup(done))) = 3; if (n) goto out; }\n",
5788 " }\n",
5789 "out:\n",
5790 " return;\n",
5791 "}\n",
5792 );
5793 let mut opts = options();
5794 opts.emit = EmitKind::Ir;
5795 opts.exceptions = true;
5796 let result = run(&opts, source);
5797 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5798 let text = result.text();
5799 assert_eq!(text.matches("= unwound").count(), 5, "{text}");
5802 assert_eq!(text.matches("= landing").count(), 2, "{text}");
5803 }
5804
5805 #[test]
5811 fn a_call_an_unwind_would_leave_a_cleanup_behind_gets_a_landing_pad_under_exceptions() {
5812 let source = concat!(
5813 "void done(int *p);\n",
5814 "void work(void);\n",
5815 "void calls(void) { int x __attribute__((cleanup(done))) = 1; work(); }\n",
5816 "int quiet(int y) { int x __attribute__((cleanup(done))) = y; return x + 1; }\n",
5817 "void after(void) { { int x __attribute__((cleanup(done))) = 1; } work(); }\n",
5818 );
5819 let mut opts = options();
5820 opts.emit = EmitKind::Ir;
5821 let result = run(&opts, source);
5822 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5823 assert!(!result.text().contains("landing"), "{}", result.text());
5824
5825 opts.exceptions = true;
5826 let result = run(&opts, source);
5827 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5828 let text = result.text();
5829 assert_eq!(text.matches("= landing").count(), 1, "only the call in calls: {text}");
5830 assert!(text.contains("_Unwind_Resume"), "{text}");
5831
5832 opts.emit = EmitKind::Asm;
5833 let result = run(&opts, source);
5834 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5835 let text = result.text();
5836 assert!(text.contains(".cfi_personality 0x9b,DW.ref.__gcc_personality_v0"), "{text}");
5837 assert!(text.contains(".cfi_lsda 0x1b,.LLSDA_calls"), "{text}");
5838 assert!(text.contains(".gcc_except_table"), "{text}");
5839 assert_eq!(text.matches(".cfi_lsda").count(), 1, "{text}");
5840
5841 opts.emit = EmitKind::Object;
5842 let result = run(&opts, source);
5843 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5844 let bytes = result.artifact.bytes();
5845 let has = |what: &[u8]| bytes.windows(what.len()).any(|window| window == what);
5846 assert!(has(b".gcc_except_table\0"), "the call site table has a section");
5847 assert!(has(b"zPLR\0"), "a header naming the personality routine");
5848 assert!(has(b"zR\0"), "and the plain one for the functions with no pad");
5849 assert!(has(b"DW.ref.__gcc_personality_v0\0"), "the pointer the header reads through");
5850
5851 opts.emit = EmitKind::Ir;
5854 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
5855 let result = run(&opts, source);
5856 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
5857 assert!(result.messages[0].contains("landing pad"), "{:?}", result.messages);
5858 assert!(result.messages[0].contains(":3:"), "the call in calls: {:?}", result.messages);
5859 }
5860
5861 #[test]
5867 fn an_asm_at_file_scope_with_an_instruction_in_it_is_assembled() {
5868 let source = concat!(
5869 "extern void vide(void);\n",
5870 "__asm__(\".text;.globl _us;_us:;movl $0x1234ABCD, %eax;ret\");\n",
5871 "__asm__(\"vide: ret\");\n",
5872 "unsigned short _us(void);\n",
5873 "int main(void) { vide(); return _us() == 0xABCD ? 0 : 1; }\n",
5874 );
5875 let mut opts = options();
5876 opts.emit = EmitKind::Ir;
5877 let result = run(&opts, source);
5878 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5879 assert_eq!(result.text().matches("module asm ").count(), 2, "{}", result.text());
5880
5881 opts.emit = EmitKind::Asm;
5882 let result = run(&opts, source);
5883 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5884 let text = result.text();
5885 assert!(text.contains("#APP\nvide: ret\n#NO_APP\n"), "{text}");
5886 let main = text.find("main:").expect("main");
5887 assert!(text.find("#NO_APP").expect("the markers") < main, "templates first: {text}");
5888
5889 opts.emit = EmitKind::Object;
5890 let result = run(&opts, source);
5891 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5892 let (bytes, defines) = match result.artifact {
5893 Artifact::Object { bytes, defines } => (bytes, defines),
5894 other => panic!("expected an object, got {other:?}"),
5895 };
5896 assert!(defines.iter().any(|name| name == "_us"), "{defines:?}");
5897 let us = [0xb8, 0xcd, 0xab, 0x34, 0x12, 0xc3];
5899 assert!(bytes.windows(us.len()).any(|window| window == us), "the template's bytes");
5900
5901 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
5903 opts.emit = EmitKind::Ir;
5904 let result = run(&opts, source);
5905 assert!(!result.messages.is_empty(), "refused on Mach-O");
5906 assert!(result.messages[0].contains("the instruction 'movl'"), "{:?}", result.messages);
5907 }
5908
5909 #[test]
5917 fn a_bare_return_from_a_function_that_promised_a_value_gives_back_a_zero() {
5918 let mut opts = options();
5919 opts.emit = EmitKind::Ir;
5920 opts.std = Std::C89;
5921 let compiled = |source: &str| {
5922 let result = run(&opts, source);
5923 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
5924 result.text().to_owned()
5925 };
5926
5927 let text = compiled("int f(int x) { if (x) return; return 3; }\n");
5928 assert!(text.contains("iconst.i32 0\n return"), "zero goes back: {text}");
5929 assert!(!text.contains("unreachable"), "the branch that reached it is kept: {text}");
5930
5931 let text = compiled("double f(int x) { if (x) return; return 1.0; }\n");
5933 assert!(text.contains("fconst.f64 0x0\n return"), "a float zero goes back: {text}");
5934 }
5935
5936 #[test]
5944 fn a_call_to_a_name_nothing_declared_declares_it_as_c89_said_to() {
5945 let mut opts = options();
5946 opts.emit = EmitKind::Ir;
5947 opts.std = Std::C89;
5948 let compiled = |source: &str| {
5949 let result = run(&opts, source);
5950 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
5951 result.text().to_owned()
5952 };
5953
5954 let text = compiled("int f(void) { return g(); }\n");
5956 assert!(text.contains("call @g"), "the call is to the name that was written: {text}");
5957 assert!(text.contains("i32"), "and it gives back an int: {text}");
5958
5959 let text = compiled("int f(char c) { return g(c); }\n");
5962 assert!(text.contains("sext.i32"), "the argument is promoted: {text}");
5963
5964 let mut opts = options();
5967 opts.std = Std::C89;
5968 let said = run(&opts, "int f(void) { return h; }\n").messages.join("\n");
5969 assert!(said.contains("'h' undeclared"), "not a call, so not declared: {said}");
5970 }
5971
5972 #[test]
5982 fn a_name_called_before_it_is_defined_still_gets_its_definition() {
5983 let mut opts = options();
5984 opts.emit = EmitKind::Ir;
5985 opts.std = Std::C89;
5986 let text = run(&opts, "int f(void) { return dummy(); }\ndummy () { return 7; }\n")
5987 .text()
5988 .to_owned();
5989 assert!(text.contains("func @f()"), "the caller is there: {text}");
5990 assert!(text.contains("func @dummy"), "and so is what it calls: {text}");
5991 assert!(text.contains("iconst.i32 7"), "with the body it was given: {text}");
5992 }
5993
5994 #[test]
6002 fn an_old_style_parameter_is_converted_from_what_the_call_promoted_it_to() {
6003 let mut opts = options();
6004 opts.emit = EmitKind::Ir;
6005 opts.std = Std::C89;
6006 let compiled = |source: &str| run(&opts, source).text().to_owned();
6007
6008 let text = compiled("f (c) unsigned char c; { return c; }\n");
6009 assert!(text.contains("func @f(i32"), "an int arrives: {text}");
6010 assert!(text.contains("trunc.i8"), "and is cut down to what was declared: {text}");
6011 assert!(text.contains("zext.i32"), "then read back unsigned: {text}");
6012
6013 let text = compiled("f (s) short s; { return s; }\n");
6015 assert!(text.contains("trunc.i16"), "cut down: {text}");
6016 assert!(text.contains("sext.i32"), "and read back signed: {text}");
6017
6018 let text = compiled("f (x) float x; { return x * 2; }\n");
6021 assert!(text.contains("func @f(f64"), "a double arrives: {text}");
6022 assert!(text.contains("fptrunc.f32"), "and is narrowed to the float: {text}");
6023
6024 let text = compiled("int f(unsigned char c) { return c; }\n");
6027 assert!(text.contains("func @f(i8)"), "the declared type arrives: {text}");
6028 assert!(!text.contains("trunc"), "so there is nothing to cut down: {text}");
6029 }
6030
6031 #[test]
6040 fn the_rules_gcc_promoted_are_decided_by_the_dialect_and_by_fpermissive() {
6041 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
6043 let cases = [
6044 ("static counted;\n", ["", "error", "warning", "error"]),
6045 ("int f(void) { return g(); }\n", ["", "error", "warning", "error"]),
6046 ("int f(x) { return x; }\n", ["", "error", "warning", "error"]),
6047 ("int *p;\nvoid h(void) { p = 1; }\n", ["warning", "error", "warning", "error"]),
6048 (
6049 "char *q;\nint *r;\nvoid k(void) { r = q; }\n",
6050 ["warning", "error", "warning", "error"],
6051 ),
6052 ("int f(void) { return; }\n", ["", "error", "warning", "error"]),
6053 ("void g(void) { return 1; }\n", ["warning", "error", "warning", "error"]),
6054 ];
6055
6056 for (source, wanted) in cases {
6057 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
6058 let mut opts = options();
6059 opts.std = std;
6060 opts.permissive = permissive;
6061 let said = run(&opts, source).messages.join("\n");
6062 let severity = if said.contains(": error: ") {
6063 "error"
6064 } else if said.contains(": warning: ") {
6065 "warning"
6066 } else {
6067 ""
6068 };
6069 let how = if permissive { " -fpermissive" } else { "" };
6070 assert_eq!(
6071 severity,
6072 wanted,
6073 "under -std={}{how}, {source} was answered with `{said}`",
6074 std.as_str()
6075 );
6076 if wanted.is_empty() {
6077 assert!(said.is_empty(), "nothing to say, but said `{said}`");
6078 }
6079 }
6080 }
6081 }
6082
6083 #[test]
6092 fn the_three_variadic_builtins_answer_a_bad_list_the_way_a_call_answers_a_bad_argument() {
6093 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
6094 let cases = [
6095 (
6096 "int f(int n, ...) { char *p; return __builtin_va_arg(p, int); }\n",
6097 "first argument to 'va_arg' not of type 'va_list'",
6098 ["error", "error", "error", "error"],
6099 ),
6100 (
6101 "void f(int n, ...) { char *p; __builtin_va_start(p, n); }\n",
6102 "passing argument 1 of '__builtin_va_start' from incompatible pointer type",
6103 ["warning", "error", "warning", "error"],
6104 ),
6105 (
6106 "void f(int n, ...) { int x; __builtin_va_end(x); }\n",
6107 "passing argument 1 of '__builtin_va_end' makes pointer from integer without a \
6108 cast",
6109 ["warning", "error", "warning", "error"],
6110 ),
6111 (
6112 "void f(int n, ...) { __builtin_va_list a; char *p; __builtin_va_copy(a, p); }\n",
6113 "passing argument 2 of '__builtin_va_copy' from incompatible pointer type",
6114 ["warning", "error", "warning", "error"],
6115 ),
6116 ];
6117
6118 for (source, message, wanted) in cases {
6119 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
6120 let mut opts = options();
6121 opts.std = std;
6122 opts.permissive = permissive;
6123 let said = run(&opts, source).messages.join("\n");
6124 let how = if permissive { " -fpermissive" } else { "" };
6125 assert!(
6126 said.contains(&format!(": {wanted}: {message}")),
6127 "under -std={}{how}, {source} was answered with `{said}`",
6128 std.as_str()
6129 );
6130 }
6131 }
6132 }
6133
6134 fn safe_ir(tier: rucc_session::Safety, source: &str) -> String {
6136 let mut opts = options();
6137 opts.emit = EmitKind::Ir;
6138 opts.safety = tier;
6139 let result = run(&opts, source);
6140 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
6141 result.text().to_owned()
6142 }
6143
6144 const READS_THROUGH_A_POINTER: &str = "int read(int *p) { return p[1]; }\n";
6145
6146 fn padded_ir(padding: Padding, source: &str) -> String {
6148 let mut opts = options();
6149 opts.emit = EmitKind::Ir;
6150 opts.safety = rucc_session::Safety::Detect;
6151 opts.padding = padding;
6152 let result = run(&opts, source);
6153 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
6154 result.text().to_owned()
6155 }
6156
6157 const FILLS_A_RECORD_A_MEMBER_AT_A_TIME: &str = "struct padded { char tag; int value; };\n\
6158 void fill(struct padded *p) { p->tag = 1; p->value = 2; }\n";
6159
6160 #[test]
6161 fn a_record_filled_a_member_at_a_time_comes_out_whole_when_padding_does_not_participate() {
6162 let text = padded_ir(Padding::Ignored, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
6166 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
6167 }
6168
6169 #[test]
6170 fn a_store_says_only_what_it_wrote_when_padding_does_participate() {
6171 let text = padded_ir(Padding::Tracked, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
6174 assert!(!text.contains("owns"), "{text}");
6175 }
6176
6177 #[test]
6178 fn a_member_of_a_union_owns_nothing_after_it() {
6179 let text = padded_ir(
6183 Padding::Ignored,
6184 "union u { char tag; long wide; };\nvoid fill(union u *p) { p->tag = 1; }\n",
6185 );
6186 assert!(!text.contains("owns"), "{text}");
6187 }
6188
6189 #[test]
6190 fn an_inner_records_trailing_padding_reaches_the_outer_records() {
6191 let text = padded_ir(
6196 Padding::Ignored,
6197 "struct inner { char c; };\n\
6198 struct outer { struct inner in; int x; };\n\
6199 void fill(struct outer *p) { p->in.c = 1; p->x = 2; }\n",
6200 );
6201 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
6202 }
6203
6204 #[test]
6205 fn a_build_that_did_not_ask_for_the_monitor_is_compiled_the_way_it_always_was() {
6206 let text = ir(READS_THROUGH_A_POINTER);
6210 assert!(!text.contains("check_"), "{text}");
6211 assert!(!text.contains("cap_of"), "{text}");
6212 }
6213
6214 #[test]
6215 fn asking_for_a_tier_puts_the_checks_in_before_the_optimizer_sees_them() {
6216 let text = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
6217 assert!(text.contains("cap_of"), "{text}");
6218 assert!(text.contains("check_bounds"), "{text}");
6219 assert!(text.contains("check_live"), "{text}");
6220 assert!(text.contains("check_deriv"), "{text}");
6222 assert!(text.contains("check_type"), "{text}");
6224 }
6225
6226 #[test]
6227 fn the_three_tiers_that_are_not_off_all_check_the_same_accesses_so_far() {
6228 let detect = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
6232 for tier in [rucc_session::Safety::Enforce, rucc_session::Safety::Kernel] {
6233 assert_eq!(safe_ir(tier, READS_THROUGH_A_POINTER), detect, "{tier}");
6234 }
6235 }
6236
6237 fn summary(tier: rucc_session::Safety, source: &str) -> String {
6239 let mut opts = options();
6240 opts.emit = EmitKind::SafetySummary;
6241 opts.safety = tier;
6242 let result = run(&opts, source);
6243 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
6244 result.text().to_owned()
6245 }
6246
6247 #[test]
6248 fn the_summary_counts_the_checks_that_went_in_and_the_ones_still_standing() {
6249 let text = summary(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
6250 assert!(text.contains("\"tier\": \"detect\""), "{text}");
6251 assert!(
6253 text.contains("\"bounds\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"),
6254 "{text}"
6255 );
6256 assert!(
6257 text.contains(
6258 "\"derivation\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"
6259 ),
6260 "{text}"
6261 );
6262 }
6263
6264 #[test]
6265 fn a_build_without_the_monitor_summarises_as_a_build_with_no_checks_in_it() {
6266 let text = summary(rucc_session::Safety::Off, READS_THROUGH_A_POINTER);
6270 assert!(text.contains("\"tier\": \"off\""), "{text}");
6271 assert!(
6272 text.contains("\"bounds\": { \"emitted\": 0, \"remaining\": 0, \"discharged\": 0 }"),
6273 "{text}"
6274 );
6275 }
6276
6277 #[test]
6278 fn a_call_the_boundary_models_is_counted_apart_from_one_it_does_not() {
6279 let text = summary(
6280 rucc_session::Safety::Detect,
6281 "void *memcpy(void *, const void *, unsigned long);\n\
6282 int puts(const char *);\n\
6283 void f(char *d, char *s) { memcpy(d, s, 4); puts(d); }\n",
6284 );
6285 assert!(text.contains("\"interposed\": 1"), "{text}");
6286 assert!(text.contains("\"puts\""), "{text}");
6287 assert!(!text.contains("__rucc_wrap_memcpy\""), "{text}");
6291 }
6292
6293 #[test]
6294 fn an_address_taken_of_a_library_function_is_counted_the_way_a_call_to_one_is() {
6295 let text = summary(
6300 rucc_session::Safety::Detect,
6301 "void *memcpy(void *, const void *, unsigned long);\n\
6302 int puts(const char *);\n\
6303 void *table[2] = { (void *)memcpy, (void *)puts };\n\
6304 void *f(int i) { return table[i]; }\n",
6305 );
6306 assert!(text.contains("\"interposed\": 1"), "{text}");
6307 assert!(text.contains("\"puts\""), "{text}");
6308 assert!(!text.contains("\"memcpy\""), "{text}");
6309 }
6310
6311 #[test]
6312 fn the_two_directions_a_pointer_crosses_the_boundary_are_counted_apart() {
6313 let text = summary(
6317 rucc_session::Safety::Detect,
6318 "void *notes_open(void);\n\
6319 char *f(char *p) { char *q = notes_open(); return q ? q : p; }\n",
6320 );
6321 assert!(text.contains("\"crossings\": { \"entered\": 1, \"returned\": 1 }"), "{text}");
6322 assert!(text.contains("\"notes_open\""), "{text}");
6323 }
6324
6325 #[test]
6326 fn a_static_function_nobody_takes_the_address_of_is_not_a_crossing() {
6327 let text = summary(
6330 rucc_session::Safety::Detect,
6331 "static int len(const char *p) { return p ? 1 : 0; }\n\
6332 int f(void) { return len(\"x\"); }\n",
6333 );
6334 assert!(text.contains("\"crossings\": { \"entered\": 0, \"returned\": 0 }"), "{text}");
6335 }
6336
6337 fn granules(source: &str) -> String {
6339 let mut opts = options();
6340 opts.emit = EmitKind::TypeGranules;
6341 let result = run(&opts, source);
6342 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
6343 result.text().to_owned()
6344 }
6345
6346 #[test]
6347 fn the_granule_report_names_every_record_and_both_keyings() {
6348 let text = granules(
6349 "struct hot { char *p; int a; int b; };\n\
6350 int f(struct hot *h) { return h->a; }\n",
6351 );
6352 assert!(text.contains("struct hot"), "{text}");
6353 assert!(text.contains("every type distinct"), "{text}");
6356 assert!(text.contains("every pointer one type"), "{text}");
6357 assert!(text.contains("budget"), "{text}");
6358 }
6359
6360 #[test]
6361 fn a_record_nothing_uses_is_still_measured() {
6362 let text = granules("struct unused { long a; double b; };\nint f(void) { return 0; }\n");
6365 assert!(text.contains("struct unused"), "{text}");
6366 }
6367
6368 #[test]
6369 fn the_granule_report_stops_before_anything_is_lowered() {
6370 let text = granules(
6374 "struct wide { long double d; };\n\
6375 long double f(long double x) { return x * x; }\n",
6376 );
6377 assert!(text.contains("struct wide"), "{text}");
6378 }
6379
6380 #[test]
6381 fn a_witness_reaches_the_assembler_as_a_call_to_the_runtime() {
6382 let text = safe_asm(rucc_session::Safety::Detect, "char *f(char *p) { return p; }\n");
6385 assert!(text.contains("\tcall\t__rucc_cap_witness\n"), "{text}");
6386 }
6387
6388 #[test]
6389 fn a_pointer_turned_into_an_integer_is_on_the_trust_set() {
6390 let text = summary(
6391 rucc_session::Safety::Detect,
6392 "unsigned long f(int *p) { return (unsigned long) p; }\n",
6393 );
6394 assert!(text.contains("\"exposed\": 1"), "{text}");
6395 }
6396
6397 fn safe_asm(tier: rucc_session::Safety, source: &str) -> String {
6399 let mut opts = options();
6400 opts.emit = EmitKind::Asm;
6401 opts.safety = tier;
6402 let result = run(&opts, source);
6403 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
6404 result.text().to_owned()
6405 }
6406
6407 #[test]
6408 fn a_check_reaches_the_assembler_as_a_call_to_the_runtime() {
6409 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
6410 assert!(text.contains("\tcall\t__rucc_check_bounds\n"), "{text}");
6411 assert!(text.contains("\tcall\t__rucc_check_live\n"), "{text}");
6412 assert!(text.contains("\tcall\t__rucc_check_deriv\n"), "{text}");
6413 assert!(text.contains("\tcall\t__rucc_check_typed_init\n"), "{text}");
6416 }
6417
6418 #[test]
6419 fn every_check_that_reached_the_assembler_has_a_row_describing_it() {
6420 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
6426 let section = format!("\t.section\t{},", rucc_safety::SECTION);
6427 assert_eq!(text.matches(§ion).count(), 4, "{text}");
6428 for index in 0..4 {
6429 let name = format!("__rucc_safety_desc_{index}");
6430 assert!(text.contains(&format!("{name}:\n")), "{text}");
6433 assert!(text.contains(&format!("{name}(%rip)")), "{text}");
6434 }
6435 assert!(!text.contains("__rucc_safety_desc_4"), "{text}");
6436 }
6437
6438 #[test]
6446 fn builtin_constant_p_is_folded_where_it_is_written_rather_than_called() {
6447 let text = ir(concat!(
6448 "int g;\n",
6449 "int a = __builtin_constant_p(1);\n",
6450 "int b = __builtin_constant_p(g);\n",
6451 "int c = __builtin_constant_p(\"abc\");\n",
6452 "int d = __builtin_constant_p(&g);\n",
6453 "int e = __builtin_constant_p(1.5);\n",
6454 "int h = __builtin_choose_expr(__builtin_constant_p(3), 11, 22);\n",
6455 ));
6456 assert!(text.contains("global @a : i32 = 1,"), "{text}");
6457 assert!(text.contains("global @b : i32 = 0,"), "{text}");
6458 assert!(text.contains("global @c : i32 = 1,"), "{text}");
6459 assert!(text.contains("global @d : i32 = 0,"), "{text}");
6460 assert!(text.contains("global @e : i32 = 1,"), "{text}");
6461 assert!(text.contains("global @h : i32 = 11,"), "{text}");
6462 assert!(!text.contains("__builtin_constant_p"), "it is not a call to anything:\n{text}");
6463
6464 let text = body("int f(void) { int i = 0; __builtin_constant_p(i++); return i; }\n");
6468 assert_eq!(text, "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 0\n return %0\n");
6469 }
6470
6471 #[test]
6475 fn a_pure_function_that_returns_through_memory_is_still_called() {
6476 let text = optimized(concat!(
6477 "struct four { long a, b, c, d; };\n",
6478 "__attribute__((pure)) struct four made(int);\n",
6479 "__attribute__((const)) struct four counted(int);\n",
6480 "long f(int x) { return made(x).c + counted(x).d; }\n",
6481 ));
6482 assert!(text.contains("call\tmade"), "{text}");
6483 assert!(text.contains("call\tcounted"), "{text}");
6484 }
6485
6486 #[test]
6491 fn a_condition_on_a_builtin_constant_p_waits_for_the_optimizer() {
6492 let text = optimized(concat!(
6493 "void g(void);\n",
6494 "void f(void) { int size = sizeof(int); __builtin_constant_p(size) ? (void)0 : g(); }\n",
6495 "void h(void) { int size = sizeof(int); if (!__builtin_constant_p(size)) g(); }\n",
6496 ));
6497 assert!(!text.contains("call\tg") && !text.contains("jmp\tg"), "{text}");
6498 }
6499
6500 #[test]
6504 fn a_call_to_a_function_that_falls_off_its_end_comes_back() {
6505 let text = optimized(concat!(
6506 "int calls;\n",
6507 "__attribute__((noinline)) static int no_answer(int x) { calls += x; }\n",
6508 "void after(void);\n",
6509 "void f(void) { no_answer(1); after(); }\n",
6510 ));
6511 assert!(text.contains("call\tafter") || text.contains("jmp\tafter"), "{text}");
6512 }
6513
6514 #[test]
6523 fn a_call_to_a_library_builtin_reaches_the_library_function() {
6524 let text = body("void f(void) { __builtin_abort(); }\n");
6525 assert_eq!(text, "block0:\n call @abort() : ()\n return\n");
6526
6527 let text = ir("int f(const char *s) { return __builtin_puts(s) + __builtin_strlen(s); }\n");
6530 assert!(text.contains("call @puts(%0) : (ptr) -> i32"), "{text}");
6531 assert!(text.contains("call @strlen(%0) : (ptr) -> i64"), "{text}");
6532 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
6533 }
6534
6535 #[test]
6548 fn a_chk_builtin_reaches_the_checking_function_and_keeps_the_size() {
6549 let text = ir(concat!(
6550 "char d[8];\n",
6551 "void f(const char *s, unsigned long n) {\n",
6552 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
6553 " __builtin___strcpy_chk(d, s, __builtin_object_size(d, 1));\n",
6554 " __builtin___memset_chk(d, 0, n, 8);\n",
6555 "}\n",
6556 ));
6557 assert!(text.contains("call @__memcpy_chk("), "{text}");
6558 assert!(text.contains("call @__strcpy_chk("), "{text}");
6559 assert!(text.contains("call @__memset_chk("), "{text}");
6560 assert!(text.contains("iconst.i64 8"), "the object size reaches the call: {text}");
6561 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
6562 }
6563
6564 #[test]
6572 fn a_checking_call_whose_size_says_nothing_is_known_is_the_plain_library_call() {
6573 let text = ir(concat!(
6574 "extern char *p;\n",
6575 "char d[8];\n",
6576 "void f(const char *s, unsigned long n) {\n",
6577 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
6578 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
6579 " __builtin___strcpy_chk(p, s, __builtin_object_size(p, 0));\n",
6580 " __builtin___stpncpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
6581 " __builtin___sprintf_chk(p, 1, __builtin_object_size(p, 0), s);\n",
6582 "}\n",
6583 ));
6584
6585 assert!(
6587 text.contains("call @__memcpy_chk(%2, %0, %1, %3) : (ptr, ptr, i64, i64)"),
6588 "{text}"
6589 );
6590
6591 assert!(text.contains("call @memcpy(%6, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
6594 assert!(text.contains("call @strcpy(%10, %0) : (ptr, ptr) -> ptr"), "{text}");
6595 assert!(text.contains("call @stpncpy(%14, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
6596
6597 assert!(text.contains("call @__sprintf_chk("), "{text}");
6600
6601 let asm = asm(concat!(
6604 "void f(char *p, const char *s, unsigned long n) {\n",
6605 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
6606 "}\n",
6607 ));
6608 assert!(asm.contains("call\tmemcpy"), "{asm}");
6609 assert!(!asm.contains("$-1"), "the size that went away leaves no instruction:\n{asm}");
6610 }
6611
6612 #[test]
6620 fn the_v_spellings_of_the_chk_family_take_the_list_a_va_list_parameter_holds() {
6621 let text = ir(concat!(
6622 "char d[64];\n",
6623 "int f(const char *fmt, ...) {\n",
6624 " __builtin_va_list ap;\n",
6625 " __builtin_va_start(ap, fmt);\n",
6626 " int n = __builtin___vsprintf_chk(d, 1, __builtin_object_size(d, 0), fmt, ap);\n",
6627 " __builtin_va_end(ap);\n",
6628 " return n;\n",
6629 "}\n",
6630 ));
6631 assert!(text.contains("call @__vsprintf_chk("), "{text}");
6632 assert!(text.contains("iconst.i64 64"), "the object size reaches the call: {text}");
6633 }
6634
6635 #[test]
6646 fn the_absolute_value_family_is_the_magnitude_and_not_a_call() {
6647 let text = body(concat!(
6648 "long long llabs(long long);\n",
6649 "long long f(long long x) { return llabs(x); }\n",
6650 ));
6651 assert!(text.contains("%1 = iconst.i64 63"), "{text}");
6652 assert!(text.contains("%2 = ashr %0, %1"), "{text}");
6653 assert!(text.contains("%3 = xor %0, %2"), "{text}");
6654 assert!(text.contains("%4 = sub %3, %2"), "{text}");
6655 assert!(!text.contains("call"), "the call does not happen:\n{text}");
6656
6657 let text = body("int abs(int);\nint f(int x) { return abs(x); }\n");
6660 assert!(text.contains("iconst.i32 31"), "{text}");
6661 let text = body("long labs(long);\nlong f(long x) { return labs(x); }\n");
6662 assert!(text.contains("iconst.i64 63"), "{text}");
6663
6664 let text = body("long long f(long long x) { return __builtin_llabs(x); }\n");
6667 assert!(!text.contains("call"), "{text}");
6668
6669 let text = ir(concat!(
6671 "long long llabs(long long b);\n",
6672 "long long g(long long x) { return llabs(x); }\n",
6673 "long long llabs(long long b) { return 7; }\n",
6674 ));
6675 assert!(!text.contains("call @llabs"), "{text}");
6676 }
6677
6678 #[test]
6685 fn a_byte_swap_is_arithmetic_and_not_a_call() {
6686 let text = body("unsigned f(unsigned x) { return __builtin_bswap32(x); }\n");
6687 assert_eq!(text, "block0(%0: i32):\n %1 = bswap %0\n return %1\n");
6688
6689 let text = body("unsigned f(unsigned char c) { return __builtin_bswap32(c); }\n");
6692 assert!(text.contains("zext.i32 %0"), "widened first: {text}");
6693 assert!(text.contains("bswap %1"), "and swapped at four bytes: {text}");
6694 }
6695
6696 #[test]
6702 fn the_byte_swaps_reverse_at_the_width_their_name_says() {
6703 for (name, ty, width) in [
6704 ("__builtin_bswap16", "unsigned short", "i16"),
6705 ("__builtin_bswap32", "unsigned", "i32"),
6706 ("__builtin_bswap64", "unsigned long long", "i64"),
6707 ] {
6708 let source = format!("{ty} f({ty} x) {{ return {name}(x); }}\n");
6709 let text = body(&source);
6710 assert_eq!(
6711 text,
6712 format!("block0(%0: {width}):\n %1 = bswap %0\n return %1\n"),
6713 "{name}"
6714 );
6715 }
6716 }
6717
6718 #[test]
6725 fn the_bit_counts_are_instructions_and_not_calls() {
6726 let text = body("int f(unsigned x) { return __builtin_clz(x); }\n");
6727 assert_eq!(text, "block0(%0: i32):\n %1 = ctlz %0\n return %1\n");
6728
6729 let text = body("int f(unsigned x) { return __builtin_ctz(x); }\n");
6730 assert_eq!(text, "block0(%0: i32):\n %1 = cttz %0\n return %1\n");
6731
6732 let text = body("int f(unsigned x) { return __builtin_popcount(x); }\n");
6733 assert_eq!(text, "block0(%0: i32):\n %1 = ctpop %0\n return %1\n");
6734 }
6735
6736 #[test]
6745 fn the_bit_counts_ask_about_the_width_their_name_says() {
6746 let text = body("int f(unsigned long long x) { return __builtin_clzll(x); }\n");
6747 assert!(text.starts_with("block0(%0: i64):"), "counted at eight bytes: {text}");
6748 assert!(text.contains("%1 = ctlz %0"), "{text}");
6749 assert!(text.contains("trunc.i32 %1"), "and answered in an int: {text}");
6750
6751 let text = body("int f(unsigned long long x) { return __builtin_clz(x); }\n");
6754 assert!(text.contains("trunc.i32 %0"), "narrowed to what was asked about: {text}");
6755 assert!(text.contains("ctlz %1"), "and counted there: {text}");
6756
6757 let text = body("int f(unsigned long x) { return __builtin_popcountl(x); }\n");
6758 assert!(text.contains("%1 = ctpop %0"), "{text}");
6759 assert!(!text.contains("call"), "{text}");
6760 }
6761
6762 #[test]
6767 fn a_parity_is_the_low_bit_of_the_set_bit_count() {
6768 let text = body("int f(unsigned x) { return __builtin_parity(x); }\n");
6769 assert!(text.contains("%1 = ctpop %0"), "{text}");
6770 assert!(text.contains("iconst.i32 1"), "{text}");
6771 assert!(text.contains("and %1, %2"), "the low bit of it: {text}");
6772 }
6773
6774 #[test]
6780 fn the_first_set_bit_is_one_based_and_zero_for_a_zero() {
6781 let text = body("int f(int x) { return __builtin_ffs(x); }\n");
6782 assert!(text.contains("%1 = cttz %0"), "{text}");
6783 assert!(text.contains("%4 = add %1, %2"), "one more than the count: {text}");
6784 assert!(text.contains("%5 = icmp ne %0, %3"), "whether there was a bit at all: {text}");
6785 assert!(text.contains("%7 = sub %3, %6"), "spread to a mask: {text}");
6786 assert!(text.contains("%8 = and %4, %7"), "and kept only then: {text}");
6787 assert!(!text.contains("br_if"), "no branch: {text}");
6788 }
6789
6790 #[test]
6800 fn the_redundant_sign_bit_count_is_instructions_and_not_a_call() {
6801 let text = body("int f(int x) { return __builtin_clrsb(x); }\n");
6802 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
6803 assert!(text.contains("%2 = ashr %0, %1"), "the sign over every bit: {text}");
6804 assert!(text.contains("%3 = xor %0, %2"), "folded onto it: {text}");
6805 assert!(text.contains("%5 = shl %3, %4"), "one less than the count: {text}");
6806 assert!(text.contains("%6 = or %5, %4"), "with something to count at zero: {text}");
6807 assert!(text.contains("%7 = ctlz %6"), "{text}");
6808 assert!(!text.contains("call"), "{text}");
6809 assert!(!text.contains("br_if"), "no branch: {text}");
6810 }
6811
6812 #[test]
6818 fn the_unsigned_absolute_value_family_answers_in_the_unsigned_type() {
6819 let text = body("unsigned f(int x) { return __builtin_uabs(x); }\n");
6820 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
6821 assert!(text.contains("%4 = sub %3, %2"), "{text}");
6822 assert!(!text.contains("call"), "nothing declares uabs, so a call would not link: {text}");
6823
6824 let text = body("unsigned long long f(long long x) { return __builtin_ullabs(x); }\n");
6825 assert!(text.contains("iconst.i64 63"), "at the width the name says: {text}");
6826
6827 let text = body("int f(int x) { return __builtin_uabs(x) > 2147483647u; }\n");
6830 assert!(text.contains("icmp ugt"), "compared unsigned: {text}");
6831 }
6832
6833 #[test]
6841 fn the_widest_absolute_value_is_whichever_type_the_target_makes_intmax_t() {
6842 let text = body("long f(long x) { return __builtin_imaxabs(x); }\n");
6843 assert!(text.contains("iconst.i64 63"), "{text}");
6844 assert!(text.contains("%4 = sub %3, %2"), "{text}");
6845 assert!(!text.contains("call"), "{text}");
6846
6847 let text = body("unsigned long f(long x) { return __builtin_umaxabs(x); }\n");
6848 assert!(text.contains("iconst.i64 63"), "{text}");
6849 assert!(!text.contains("call"), "{text}");
6850 }
6851
6852 #[test]
6860 fn an_overflow_predicate_writes_nothing_and_answers_the_bit_the_check_would() {
6861 let text =
6862 body("int f(int a, int b) { return __builtin_add_overflow_p(a, b, (int) 0); }\n");
6863 assert!(text.contains("%2, %3 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
6864 assert!(!text.contains("store"), "nothing is written: {text}");
6865 assert!(!text.contains("call"), "{text}");
6866
6867 let text =
6870 body("int f(int a, int b) { return __builtin_mul_overflow_p(a, b, (long long) 0); }\n");
6871 assert!(text.contains("smul_overflow.(i64, i1)"), "{text}");
6872 assert!(!text.contains("store"), "{text}");
6873
6874 let text = body(concat!(
6877 "int g(void);\n",
6878 "int f(int a, int b) { return __builtin_sub_overflow_p(a, b, g()); }\n",
6879 ));
6880 assert!(!text.contains("call @g"), "the third argument is not evaluated: {text}");
6881 }
6882
6883 #[test]
6893 fn an_overflow_check_is_arithmetic_and_not_a_call() {
6894 let text =
6895 body("int f(int a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
6896 assert!(text.contains("%3, %4 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
6897 assert!(text.contains("store %3 -> %2"), "{text}");
6898 assert!(!text.contains("call"), "{text}");
6899
6900 let text =
6901 body("int f(int a, int b, int *r) { return __builtin_sub_overflow(a, b, r); }\n");
6902 assert!(text.contains("ssub_overflow.(i32, i1) %0, %1"), "{text}");
6903
6904 let text =
6905 body("int f(int a, int b, int *r) { return __builtin_mul_overflow(a, b, r); }\n");
6906 assert!(text.contains("smul_overflow.(i32, i1) %0, %1"), "{text}");
6907
6908 let text = body(
6911 "int f(unsigned a, unsigned b, unsigned *r) { return __builtin_add_overflow(a, b, r); }\n",
6912 );
6913 assert!(text.contains("uadd_overflow.(i32, i1) %0, %1"), "{text}");
6914 }
6915
6916 #[test]
6924 fn an_overflow_check_is_done_at_a_type_that_holds_every_operand() {
6925 let text = body(
6926 "int f(unsigned a, int b, long long *r) { return __builtin_add_overflow(a, b, r); }\n",
6927 );
6928 assert!(text.contains("%3 = zext.i64 %0"), "the unsigned operand keeps its value: {text}");
6929 assert!(text.contains("%4 = sext.i64 %1"), "and so does the signed one: {text}");
6930 assert!(text.contains("sadd_overflow.(i64, i1) %3, %4"), "{text}");
6931
6932 let text = body(
6935 "int f(long long a, long long b, long long *r) { return __builtin_mul_overflow(a, b, r); }\n",
6936 );
6937 assert!(text.contains("smul_overflow.(i64, i1) %0, %1"), "{text}");
6938 assert!(!text.contains("sext."), "{text}");
6939 assert!(!text.contains("zext.i64"), "{text}");
6941 }
6942
6943 #[test]
6951 fn an_overflow_check_writes_the_wrapped_answer_whether_or_not_it_fit() {
6952 let text =
6953 body("int f(int a, int b, char *r) { return __builtin_sub_overflow(a, b, r); }\n");
6954 assert!(text.contains("%3, %4 = ssub_overflow.(i32, i1) %0, %1"), "{text}");
6955 assert!(text.contains("%5 = trunc.i8 %3"), "narrowed to where it goes: {text}");
6956 assert!(text.contains("%6 = sext.i32 %5"), "and back: {text}");
6957 assert!(text.contains("%7 = icmp ne %6, %3"), "which is whether it fit: {text}");
6958 assert!(text.contains("store %5 -> %2"), "the narrowed value is stored either way: {text}");
6959 assert!(text.contains("%8 = or %4, %7"), "and either bit is an overflow: {text}");
6960 }
6961
6962 #[test]
6969 fn a_call_needing_more_than_the_widest_type_still_compiles() {
6970 for name in ["add", "sub", "mul"] {
6971 let source = format!(
6972 "int f(unsigned __int128 a, long long b, __int128 *r) {{\n \
6973 return __builtin_{name}_overflow(a, b, r);\n}}\n"
6974 );
6975 let mut opts = options();
6976 opts.emit = EmitKind::MirFinal;
6977 assert!(!run(&opts, &source).failed(), "{name} was refused or stopped the back end");
6978 }
6979 }
6980
6981 #[test]
6984 fn an_overflow_check_over_something_that_is_not_an_integer_says_so() {
6985 let messages =
6986 errors("int f(double a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
6987 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
6988
6989 let messages =
6990 errors("int f(int a, int b, double *r) { return __builtin_add_overflow(a, b, r); }\n");
6991 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
6992 }
6993
6994 #[test]
7005 fn an_ordered_access_is_ordered_in_the_ir() {
7006 let text = body("int f(int *p) { return __atomic_load_n(p, 0); }\n");
7007 assert!(text.contains("atomic_load.i32 %0, align 4, relaxed"), "{text}");
7008
7009 let text = body("long f(long *p) { return __atomic_load_n(p, 2); }\n");
7010 assert!(text.contains("atomic_load.i64 %0, align 8, acquire"), "{text}");
7011
7012 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
7013 assert!(text.contains("atomic_store %1 -> %0, align 4, release"), "{text}");
7014
7015 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
7016 assert!(text.contains("atomic_store %1 -> %0, align 4, seq_cst"), "{text}");
7017
7018 let text = body("void f(char *p, int v) { __atomic_store_n(p, v, 0); }\n");
7021 assert!(text.contains("trunc.i8 %1"), "{text}");
7022 assert!(text.contains("atomic_store %2 -> %0, align 1, relaxed"), "{text}");
7023 }
7024
7025 #[test]
7034 fn an_ordered_access_is_the_plain_instruction_on_this_machine() {
7035 let text = asm("int f(int *p) { return __atomic_load_n(p, 5); }\n");
7036 assert!(text.contains("movl\t(%rdi), %eax"), "{text}");
7037 assert!(!text.contains("mfence"), "a load needs no barrier here: {text}");
7038
7039 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
7040 assert!(text.contains("movl\t%esi, (%rdi)"), "{text}");
7041 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
7042
7043 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
7044 let (before, after) = text.split_once("mfence").expect("a barrier: {text}");
7045 assert!(before.contains("movl\t%esi, (%rdi)"), "the store comes first: {text}");
7046 assert!(!after.contains("movl"), "and nothing else is between them: {text}");
7047 }
7048
7049 #[test]
7059 fn a_barrier_is_one_instruction_at_the_strongest_ordering_and_none_below_it() {
7060 assert!(asm("void f(void) { __atomic_thread_fence(5); }\n").contains("mfence"));
7061 assert!(asm("void f(void) { __sync_synchronize(); }\n").contains("mfence"));
7062
7063 for weaker in ["1", "2", "3", "4"] {
7064 let source = format!("void f(void) {{ __atomic_thread_fence({weaker}); }}\n");
7065 assert!(!asm(&source).contains("mfence"), "{weaker} costs nothing here");
7066 }
7067 }
7068
7069 #[test]
7079 fn the_three_x86_fences_are_the_barrier_the_strongest_ordering_gives() {
7080 for name in ["__builtin_ia32_sfence", "__builtin_ia32_lfence", "__builtin_ia32_mfence"] {
7081 let source = format!("void f(void) {{ {name}(); }}\n");
7082 assert!(asm(&source).contains("mfence"), "{name} is a barrier");
7083 let text = body(&source);
7084 assert!(text.contains("fence seq_cst"), "{name}: {text}");
7085 }
7086
7087 let result = run(&options(), "void f(void) { __builtin_ia32_sfence(1); }\n");
7088 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
7089 assert!(result.messages[0].contains("too many arguments"), "{:?}", result.messages);
7090 }
7091
7092 #[test]
7098 fn a_compare_and_exchange_is_one_instruction_answering_two_things() {
7099 let text =
7102 body("int f(int *p, int e, int d) { return __sync_val_compare_and_swap(p, e, d); }\n");
7103 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
7104 assert!(text.contains("return %3"), "the value it found: {text}");
7105
7106 let text =
7107 body("int f(int *p, int e, int d) { return __sync_bool_compare_and_swap(p, e, d); }\n");
7108 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
7109 assert!(text.contains("zext.i32 %4"), "whether it happened: {text}");
7110
7111 let text = body(
7114 "int f(int *p, int *e, int d) { return __atomic_compare_exchange_n(p, e, d, 0, 4, 2); }\n",
7115 );
7116 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
7117 assert!(text.contains("%4, %5 = cmpxchg.(i32, i1) %0, %3, %2, align 4, acq_rel"), "{text}");
7118 assert!(text.contains("br_if %5, block2, block1"), "{text}");
7119 assert!(text.contains("store %4 -> %1, align 4"), "{text}");
7120
7121 let text = body(
7124 "int f(int *p, int *e, int *d) { return __atomic_compare_exchange(p, e, d, 0, 5, 5); }\n",
7125 );
7126 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
7127 assert!(text.contains("%4 = load.i32 %2, align 4"), "{text}");
7128 assert!(text.contains("%5, %6 = cmpxchg.(i32, i1) %0, %3, %4, align 4, seq_cst"), "{text}");
7129 }
7130
7131 #[test]
7138 fn a_compare_and_exchange_is_a_locked_instruction_at_the_width_of_the_object() {
7139 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
7140 for (ty, suffix, reg) in widths {
7141 let source = format!(
7142 "int f({ty} *p, {ty} e, {ty} d) {{ return __sync_bool_compare_and_swap(p, e, d); }}\n"
7143 );
7144 let text = asm(&source);
7145 assert!(text.contains("\tlock\n"), "{ty}: {text}");
7146 assert!(text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
7147 assert!(text.contains("sete\t"), "{ty}: {text}");
7148 }
7149 let source =
7150 "int f(long *p, long e, long d) { return __sync_bool_compare_and_swap(p, e, d); }\n";
7151 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
7152
7153 for order in ["0", "2", "3", "4", "5"] {
7157 let call = format!("__atomic_compare_exchange_n(p, e, d, 0, {order}, 0)");
7158 let source = format!("int f(int *p, int *e, int d) {{ return {call}; }}\n");
7159 let text = asm(&source);
7160 assert!(text.contains("cmpxchgl\t"), "{order}: {text}");
7161 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
7162 }
7163 }
7164
7165 #[test]
7177 fn a_read_modify_write_is_one_instruction_and_the_arithmetic_a_name_asks_for() {
7178 let text = body("int f(int *p, int v) { return __atomic_fetch_add(p, v, 5); }\n");
7179 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
7180 assert!(text.contains("return %2"), "the value that was there: {text}");
7181
7182 let text = body("int f(int *p, int v) { return __atomic_add_fetch(p, v, 5); }\n");
7183 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
7184 assert!(text.contains("%3 = add %2, %1"), "and the value afterwards: {text}");
7185
7186 let text = body("int f(int *p, int v) { return __atomic_sub_fetch(p, v, 5); }\n");
7187 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
7188 assert!(text.contains("%3 = sub %2, %1"), "{text}");
7189
7190 let text = body("int f(int *p, int v) { return __sync_fetch_and_sub(p, v); }\n");
7192 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
7193
7194 let text = body("int f(int *p, int v) { return __atomic_exchange_n(p, v, 5); }\n");
7197 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, seq_cst"), "{text}");
7198
7199 let text = body("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
7200 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, acquire"), "{text}");
7201
7202 let text = body("void f(int *p) { __sync_lock_release(p); }\n");
7205 assert!(text.contains("release"), "{text}");
7206 assert!(text.contains("%1 = iconst.i32 0"), "{text}");
7207
7208 let text = body("void f(int *p, int guard) { __sync_lock_release(p, guard); }\n");
7212 assert!(text.contains("%2 = iconst.i32 0"), "{text}");
7213 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
7214
7215 let text = body("int f(int *p, int v) { return __atomic_fetch_and(p, v, 5); }\n");
7218 assert!(text.contains("%2 = atomic_rmw.i32 and %0, %1, align 4, seq_cst"), "{text}");
7219
7220 let text = body("int f(int *p, int v) { return __sync_or_and_fetch(p, v); }\n");
7221 assert!(text.contains("%2 = atomic_rmw.i32 or %0, %1, align 4, seq_cst"), "{text}");
7222 assert!(text.contains("%3 = or %2, %1"), "and the value afterwards: {text}");
7223
7224 let text = body("int f(int *p, int v) { return __atomic_nand_fetch(p, v, 5); }\n");
7227 assert!(text.contains("%2 = atomic_rmw.i32 nand %0, %1, align 4, seq_cst"), "{text}");
7228 assert!(text.contains("%3 = and %2, %1"), "{text}");
7229 assert!(text.contains("%4 = iconst.i32 -1"), "{text}");
7230 assert!(text.contains("%5 = xor %3, %4"), "{text}");
7231 }
7232
7233 #[test]
7244 fn a_bitwise_read_modify_write_is_a_loop_around_the_compare_and_exchange() {
7245 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
7246 for (ty, suffix, reg) in widths {
7247 for (name, call, insn) in [
7248 ("and", "__atomic_fetch_and(p, v, 5)", "and"),
7249 ("or", "__sync_fetch_and_or(p, v)", "or"),
7250 ("xor", "__atomic_xor_fetch(p, v, 5)", "xor"),
7251 ] {
7252 let source = format!("{ty} f({ty} *p, {ty} v) {{ return {call}; }}\n");
7253 let text = asm(&source);
7254 assert!(text.contains("\tlock\n"), "{ty} {name}: {text}");
7255 assert!(
7256 text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")),
7257 "{ty} {name}: {text}"
7258 );
7259 assert!(text.contains(&format!("{insn}{suffix}\t")), "{ty} {name}: {text}");
7260 assert!(!text.contains("\txadd"), "{ty} {name} is not an add: {text}");
7262 assert!(!text.contains("\txchg"), "{ty} {name} is not an exchange: {text}");
7263 }
7264 }
7265 let source = "long f(long *p, long v) { return __atomic_fetch_or(p, v, 5); }\n";
7266 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
7267
7268 let text = asm("int f(int *p, int v) { return __sync_fetch_and_nand(p, v); }\n");
7272 assert!(text.contains("cmpxchgl\t"), "{text}");
7273 assert!(text.contains("andl\t"), "{text}");
7274 assert!(text.contains("notl\t"), "{text}");
7275 }
7276
7277 #[test]
7286 fn an_access_through_a_second_pointer_is_the_same_access_and_one_more() {
7287 let text = body("void f(int *p, int *r) { __atomic_load(p, r, 5); }\n");
7288 assert!(text.contains("%2 = atomic_load.i32 %0, align 4, seq_cst"), "{text}");
7289 assert!(text.contains("store %2 -> %1, align 4"), "and out through the place: {text}");
7290
7291 let text = body("void f(int *p, int *v) { __atomic_store(p, v, 3); }\n");
7292 assert!(text.contains("%2 = load.i32 %1, align 4"), "in through the place: {text}");
7293 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
7294
7295 let text = body("void f(int *p, int *v, int *r) { __atomic_exchange(p, v, r, 5); }\n");
7298 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
7299 assert!(text.contains("%4 = atomic_rmw.i32 xchg %0, %3, align 4, seq_cst"), "{text}");
7300 assert!(text.contains("store %4 -> %2, align 4"), "{text}");
7301 }
7302
7303 #[test]
7314 fn a_flag_is_an_exchange_of_one_byte_and_a_store_of_a_zero_over_the_same_byte() {
7315 for pointer in ["char", "int", "void"] {
7316 let source = format!("int f({pointer} *p) {{ return __atomic_test_and_set(p, 5); }}\n");
7317 let text = body(&source);
7318 assert!(text.contains("%1 = iconst.i8 1"), "{pointer}: {text}");
7319 assert!(
7320 text.contains("%2 = atomic_rmw.i8 xchg %0, %1, align 1, seq_cst"),
7321 "{pointer}: {text}"
7322 );
7323 assert!(text.contains("%4 = icmp ne %2, %3"), "{pointer}: {text}");
7324
7325 let source = format!("void f({pointer} *p) {{ __atomic_clear(p, 3); }}\n");
7326 let text = body(&source);
7327 assert!(text.contains("atomic_store %2 -> %0, align 1, release"), "{pointer}: {text}");
7328 }
7329
7330 let text = asm("int f(int *p) { return __atomic_test_and_set(p, 5); }\n");
7333 assert!(text.contains("xchgb\t%al, (%rdi)"), "{text}");
7334 assert!(text.contains("setne\t"), "{text}");
7335 }
7336
7337 #[test]
7345 fn a_read_modify_write_is_an_exchange_or_a_locked_add_at_the_width_of_the_object() {
7346 let widths = [("char", "b", "%sil"), ("short", "w", "%si"), ("int", "l", "%esi")];
7347 for (ty, suffix, reg) in widths {
7348 let source =
7349 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_fetch_add(p, v, 5); }}\n");
7350 let text = asm(&source);
7351 assert!(text.contains("\tlock\n"), "{ty}: {text}");
7352 assert!(text.contains(&format!("xadd{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
7353
7354 let source =
7355 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_exchange_n(p, v, 5); }}\n");
7356 let text = asm(&source);
7357 assert!(text.contains(&format!("xchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
7358 assert!(!text.contains("\tlock\n"), "an exchange is locked already: {ty}: {text}");
7359 }
7360 let source = "long f(long *p, long v) { return __atomic_fetch_add(p, v, 5); }\n";
7361 assert!(asm(source).contains("xaddq\t%rsi, (%rdi)"), "{}", asm(source));
7362
7363 let source = "int f(int *p, int v) { return __atomic_fetch_sub(p, v, 5); }\n";
7366 let text = asm(source);
7367 assert!(text.contains("negl\t"), "{text}");
7368 assert!(text.contains("xaddl\t"), "{text}");
7369
7370 for order in ["0", "2", "3", "4", "5"] {
7373 let source =
7374 format!("int f(int *p, int v) {{ return __atomic_fetch_add(p, v, {order}); }}\n");
7375 let text = asm(&source);
7376 assert!(text.contains("xaddl\t"), "{order}: {text}");
7377 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
7378 }
7379
7380 let text = asm("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
7384 assert!(text.contains("xchgl\t%esi, (%rdi)"), "{text}");
7385 let text = asm("void f(int *p) { __sync_lock_release(p); }\n");
7392 assert!(text.contains("xorl\t%eax, %eax"), "{text}");
7393 assert!(text.contains("movl\t%eax, (%rdi)"), "{text}");
7394 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
7395 }
7396
7397 #[test]
7409 fn the_lock_free_questions_are_answered_as_constants() {
7410 for size in ["1", "2", "4", "8"] {
7411 let source =
7412 format!("int f(void) {{ return __atomic_always_lock_free({size}, 0); }}\n");
7413 let text = asm(&source);
7414 assert!(text.contains("movb\t$1, %al"), "{size} bytes is lock free: {text}");
7415 assert!(!text.contains("call"), "and is not a call: {text}");
7416 }
7417 for size in ["3", "16", "sizeof(long double)"] {
7418 let source = format!("int f(void) {{ return __atomic_is_lock_free({size}, 0); }}\n");
7419 let text = asm(&source);
7420 assert!(text.contains("movb\t$0, %al"), "{size} bytes is not: {text}");
7421 assert!(!text.contains("call"), "and is not a call either: {text}");
7422 }
7423
7424 let text = asm("int f(int n) { return __atomic_is_lock_free(n, 0); }\n");
7428 assert!(text.contains("movb\t$0, %al"), "a size nobody knows is not lock free: {text}");
7429 let text = asm("int f(int *p) { return __atomic_always_lock_free(8, p); }\n");
7430 assert!(text.contains("movb\t$0, %al"), "eight bytes at four is not: {text}");
7431 let text = asm("int f(long *p) { return __atomic_always_lock_free(8, p); }\n");
7432 assert!(text.contains("movb\t$1, %al"), "and at eight it is: {text}");
7433 }
7434
7435 #[test]
7447 fn a_memory_order_an_operation_cannot_carry_is_read_as_the_strongest() {
7448 let mut opts = options();
7449 opts.emit = EmitKind::Ir;
7450
7451 let acquire_store = run(&opts, "void f(int *p, int v) { __atomic_store_n(p, v, 2); }\n");
7452 assert!(acquire_store.text().contains("seq_cst"), "{:?}", acquire_store.text());
7453 assert!(acquire_store.messages[0].contains("[W0333]"), "{:?}", acquire_store.messages);
7454
7455 let nonsense = run(&opts, "int f(int *p) { return __atomic_load_n(p, 99); }\n");
7456 assert!(nonsense.text().contains("seq_cst"), "{:?}", nonsense.text());
7457 assert!(nonsense.messages[0].contains("[W0333]"), "{:?}", nonsense.messages);
7458
7459 let computed = run(&opts, "int f(int *p, int n) { return __atomic_load_n(p, n); }\n");
7460 assert!(computed.text().contains("seq_cst"), "{:?}", computed.text());
7461 assert_eq!(computed.messages, Vec::<String>::new(), "a computed order is not a mistake");
7462 }
7463
7464 #[test]
7476 fn a_conversion_between_a_float_and_the_widest_unsigned_integer_is_written_without_a_branch() {
7477 let text = asm("double f(unsigned long long x) { return (double)x; }\n");
7478 assert!(text.contains("cvtsi2sdq"), "the signed conversion is what runs: {text}");
7479 assert!(text.contains("shrq"), "with the value halved first: {text}");
7480 assert!(text.contains("addsd"), "and doubled after: {text}");
7481 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
7482
7483 let text = asm("unsigned long long f(double d) { return (unsigned long long)d; }\n");
7484 assert!(text.contains("cvttsd2siq"), "the signed conversion is what runs: {text}");
7485 assert!(text.contains("subsd"), "with half the range taken off first: {text}");
7486 assert!(text.contains("shlq\t$63"), "and the top bit put back: {text}");
7487 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
7488 }
7489
7490 #[test]
7501 fn a_plain_name_the_program_took_is_the_programs_own_function() {
7502 let taken = concat!(
7503 "static long long llabs(long long b) { return 7; }\n",
7504 "long long f(long long x) { return llabs(x); }\n",
7505 );
7506 assert!(ir(taken).contains("call @llabs"), "a static definition is the program's own");
7507
7508 let retyped = concat!("int llabs(int b);\n", "int f(int x) { return llabs(x); }\n",);
7509 assert!(ir(retyped).contains("call @llabs"), "another type is another function");
7510
7511 let plain = concat!(
7512 "long long llabs(long long b);\n",
7513 "long long f(long long x) { return llabs(x); }\n",
7514 );
7515 let mut opts = options();
7516 opts.emit = EmitKind::Ir;
7517 assert!(!run(&opts, plain).text().contains("call @llabs"), "the library's by default");
7518
7519 opts.builtins = false;
7520 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin");
7521
7522 opts.builtins = true;
7523 opts.no_builtin = vec!["llabs".to_owned()];
7524 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin-llabs");
7525 let one = "long labs(long b);\nlong f(long x) { return labs(x); }\n";
7526 assert!(!run(&opts, one).text().contains("call @labs"), "one name and not the family");
7527
7528 opts.no_builtin = Vec::new();
7531 opts.builtins = false;
7532 let prefixed = "long long f(long long x) { return __builtin_llabs(x); }\n";
7533 assert!(!run(&opts, prefixed).text().contains("call @llabs"), "the prefix is a promise");
7534 }
7535
7536 #[test]
7549 fn the_hint_builtins_are_their_first_argument_and_the_hint_leaves_no_trace() {
7550 let text = ir(concat!(
7551 "long a = __builtin_expect(7, 1);\n",
7552 "long b = __builtin_expect_with_probability(9, 1, 0.9);\n",
7553 "unsigned long c = sizeof(__builtin_expect((char)1, 1));\n",
7554 ));
7555 assert!(text.contains("global @a : i64 = 7,"), "{text}");
7556 assert!(text.contains("global @b : i64 = 9,"), "{text}");
7557 assert!(text.contains("global @c : i64 = 8,"), "{text}");
7558 assert!(!text.contains("__builtin_expect"), "it is not a call to anything:\n{text}");
7559
7560 let text = body("long f(char c) { return __builtin_expect(c, 1); }\n");
7563 assert!(text.contains("sext"), "{text}");
7564
7565 let one = "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 1\n %2 = sext.i64 %1\n return %0\n";
7569 assert_eq!(body("int f(void) { int i = 0; __builtin_expect(1, i++); return i; }\n"), one);
7570 let source = "int g(void) { int i = 0; __builtin_expect_with_probability(1, i++, 0.5); return i; }\n";
7571 assert_eq!(body(source), one);
7572
7573 let kept = body("int f(int n) { int i = 0; __builtin_expect(n, i++); return i; }\n");
7578 assert!(kept.contains("add.nsw"), "the hint still runs: {kept}");
7579 assert!(kept.ends_with("return %3\n"), "and the answer is what it left behind: {kept}");
7580 let both = "int g(int n) { int i = 0; __builtin_expect_with_probability(n, i++, 0.5); return i; }\n";
7581 assert!(body(both).contains("add.nsw"), "and so does the one with three arguments");
7582 }
7583
7584 #[test]
7596 fn a_promise_that_control_does_not_arrive_writes_no_instruction() {
7597 let promised = "int f(int x) { if (x) return 1; __builtin_unreachable(); }\n";
7598 let text = ir(promised);
7599 assert!(text.contains(" unreachable_hint\n"), "{text}");
7600 assert!(!text.contains("call"), "it is not a call to anything:\n{text}");
7601
7602 let after = body("int g(int x) { __builtin_unreachable(); return x; }\n");
7606 assert!(after.contains("return"), "{after}");
7607
7608 let text = asm(promised);
7611 let mine = text.split_once("\nf:\n").expect("a definition").1;
7612 let mine = mine.split_once("\t.size").expect("a definition").0;
7613 let plain = asm("int f(int x) { if (x) return 1; }\n");
7614 let plain = plain.split_once("\nf:\n").expect("a definition").1;
7615 let plain = plain.split_once("\t.size").expect("a definition").0;
7616 assert_eq!(mine, plain);
7617 let last = mine.lines().rfind(|line| !line.trim_start().starts_with('.'));
7620 assert_eq!(last.map(str::trim), Some("ret"), "{mine}");
7621 assert!(!mine.contains("ud2"), "{mine}");
7622 }
7623
7624 #[test]
7631 fn a_library_builtin_is_diagnosed_under_the_name_the_program_wrote() {
7632 let mut opts = options();
7633 opts.emit = EmitKind::Ir;
7634 let messages = run(&opts, "void f(void) { __builtin_abort(1); }\n").messages;
7635 assert!(
7636 messages.iter().any(|m| m.contains("__builtin_abort")),
7637 "expected the written name in {messages:?}"
7638 );
7639 }
7640
7641 #[test]
7649 fn a_builtin_nothing_lowers_is_refused_by_name() {
7650 let mut opts = options();
7651 opts.emit = EmitKind::Ir;
7652 let builtin = "__atomic_signal_fence";
7653 let source = format!("int counter;\nint f(void) {{ return ({builtin}(5), 0); }}\n");
7654 let messages = run(&opts, &source).messages;
7655 let named = messages.iter().any(|m| m.contains(builtin) && m.contains("E0686"));
7656 assert!(named, "expected {builtin} to be refused by name in {messages:?}");
7657 }
7658
7659 #[test]
7668 fn what_is_refused_is_the_call_and_not_the_name() {
7669 let text = ir(concat!(
7670 "void __atomic_signal_fence(int order) { (void)order; }\n",
7671 "void f(void) { __atomic_signal_fence(5); }\n",
7672 ));
7673 assert!(text.contains("call @__atomic_signal_fence"), "{text}");
7674 }
7675
7676 #[test]
7685 fn the_object_size_of_an_address_is_what_the_layout_leaves_in_front_of_it() {
7686 let text = ir(concat!(
7687 "struct S { char a[8]; int n; char b[12]; };\n",
7688 "char g[32];\n",
7689 "struct S gs;\n",
7690 "unsigned long whole = __builtin_object_size(g, 0);\n",
7691 "unsigned long moved = __builtin_object_size(g + 4, 0);\n",
7692 "unsigned long back = __builtin_object_size(g + 30 - 2, 0);\n",
7693 "unsigned long outer = __builtin_object_size(gs.a, 0);\n",
7694 "unsigned long inner = __builtin_object_size(gs.a, 1);\n",
7695 "unsigned long scalar = __builtin_object_size(&gs.n, 1);\n",
7696 "unsigned long after = __builtin_object_size(&gs.n, 0);\n",
7697 "unsigned long into = __builtin_object_size(&gs.b[2], 1);\n",
7698 "unsigned long text = __builtin_object_size(\"hello\", 0);\n",
7699 "unsigned long dyn = __builtin_dynamic_object_size(gs.b, 1);\n",
7700 ));
7701 for (name, size) in [
7702 ("whole", 32),
7703 ("moved", 28),
7704 ("back", 4),
7705 ("outer", 24),
7706 ("inner", 8),
7707 ("scalar", 4),
7708 ("after", 16),
7709 ("into", 10),
7710 ("text", 6),
7711 ("dyn", 12),
7712 ] {
7713 let said = format!("global @{name} : i64 = {size},");
7714 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
7715 }
7716 }
7717
7718 #[test]
7726 fn the_object_behind_an_address_can_be_one_with_automatic_storage() {
7727 let text = body(concat!(
7728 "struct S { char a[8]; int n; char b[12]; };\n",
7729 "unsigned long f(void) {\n",
7730 " char loc[20];\n",
7731 " struct S ls;\n",
7732 " return __builtin_object_size(loc + 3, 0) + __builtin_object_size(ls.b + 2, 1);\n",
7733 "}\n",
7734 ));
7735 assert!(text.contains("iconst.i64 17"), "twenty bytes with three used: {text}");
7736 assert!(text.contains("iconst.i64 10"), "twelve bytes with two used: {text}");
7737 }
7738
7739 #[test]
7749 fn an_address_with_no_object_in_sight_answers_at_the_end_of_the_range_its_kind_asks_for() {
7750 let text = ir(concat!(
7751 "struct T { int n; char f[]; };\n",
7752 "extern char *p;\n",
7753 "extern struct T *t;\n",
7754 "unsigned long largest = __builtin_object_size(p, 0);\n",
7755 "unsigned long nearest = __builtin_object_size(p, 1);\n",
7756 "unsigned long least = __builtin_object_size(p, 2);\n",
7757 "unsigned long tight = __builtin_object_size(p, 3);\n",
7758 "unsigned long flex = __builtin_object_size(t->f, 1);\n",
7759 "int says = __builtin_object_size(p, 0) == (unsigned long)-1;\n",
7760 ));
7761 for name in ["largest", "nearest", "flex"] {
7762 let said = format!("global @{name} : i64 = -1,");
7766 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
7767 }
7768 for name in ["least", "tight"] {
7769 let said = format!("global @{name} : i64 = 0,");
7770 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
7771 }
7772 assert!(text.contains("global @says : i32 = 1,"), "{text}");
7773 }
7774
7775 #[test]
7782 fn the_address_an_object_size_is_asked_about_is_not_evaluated() {
7783 let text = body(concat!(
7784 "extern char *side(void);\n",
7785 "unsigned long f(void) { return __builtin_object_size(side(), 0); }\n",
7786 ));
7787 assert!(!text.contains("call"), "nothing is called: {text}");
7788 }
7789
7790 #[test]
7795 fn a_kind_that_is_not_one_of_the_four_is_refused() {
7796 for source in [
7797 "extern char *p;\nextern int k;\nunsigned long f(void) ".to_owned()
7798 + "{ return __builtin_object_size(p, k); }\n",
7799 "extern char *p;\nunsigned long f(void) { return __builtin_object_size(p, 4); }\n"
7800 .to_owned(),
7801 "extern char *p;\nunsigned long f(void) ".to_owned()
7802 + "{ return __builtin_dynamic_object_size(p, -1); }\n",
7803 ] {
7804 let messages = errors(&source);
7805 let named = messages.iter().any(|m| m.contains("E0709") && m.contains("0 to 3"));
7806 assert!(named, "expected a complaint about the kind in {messages:?}");
7807 }
7808 }
7809
7810 #[test]
7816 fn the_pair_that_saves_a_place_lowers_to_the_two_markers() {
7817 let text = ir(concat!(
7818 "void *buf[5];\n",
7819 "int f(void) {\n",
7820 " if (__builtin_setjmp(buf)) return 2;\n",
7821 " return 1;\n",
7822 "}\n",
7823 "void g(void) { __builtin_longjmp(buf, 1); }\n",
7824 ));
7825 assert!(text.contains("= setjmp_marker.i32 %0\n"), "the save answers a value: {text}");
7826 assert!(text.contains(" longjmp_marker %0\n"), "the restore answers nothing: {text}");
7827 assert!(!text.contains("call @"), "neither of them is a call: {text}");
7828 }
7829
7830 #[test]
7838 fn a_local_of_a_function_that_saves_a_place_gets_a_slot() {
7839 let text = ir(concat!(
7840 "void *buf[5];\n",
7841 "int f(int x) { int a = 0; if (__builtin_setjmp(buf)) return a; a = 1; return x; }\n",
7842 "int g(int x) { int a = 0; if (x) return a; a = 1; return x; }\n",
7843 ));
7844 let (saves, plain) = text.split_once("func @g").expect("both functions");
7845 assert_eq!(saves.matches("= alloca").count(), 2, "the parameter and the local: {text}");
7846 assert!(saves.contains("store %9 -> %2"), "the local is written through: {text}");
7847 assert!(!plain.contains("alloca"), "nothing in the plain one needs a slot: {text}");
7848 }
7849
7850 #[test]
7859 fn a_value_live_across_sigsetjmp_keeps_its_spill_slot() {
7860 each_spill_slot_written_once(&across("int __sigsetjmp(sigjmp_buf, int);\n", "__sigsetjmp"));
7861 }
7862
7863 #[test]
7866 fn a_value_live_across_a_returns_twice_call_keeps_its_spill_slot() {
7867 let declared = "int save_here(sigjmp_buf, int) __attribute__((__returns_twice__));\n";
7868 each_spill_slot_written_once(&across(declared, "save_here"));
7869 }
7870
7871 #[test]
7879 fn a_value_live_across_setjmp_shares_its_slot_with_nothing_in_the_first_arm() {
7880 let source = concat!(
7881 "typedef long jmp_buf[25];\n",
7882 "int _setjmp(jmp_buf);\n",
7883 "void longjmp(jmp_buf, int) __attribute__((noreturn));\n",
7884 "int printf(const char *, ...);\n",
7885 "static jmp_buf *stack;\n",
7886 "static volatile long long sink;\n",
7887 "static int cells[64];\n",
7888 "static volatile int seed_in = 3;\n",
7889 "static void work(void) { longjmp(*stack, 1); }\n",
7890 "int main(void) {\n",
7891 " int seed = seed_in;\n",
7892 " int v = seed * 2;\n",
7893 " jmp_buf buf;\n",
7894 " if (_setjmp(buf) == 0) {\n",
7895 " stack = &buf;\n",
7896 " int *p = &cells[seed + 3];\n",
7897 " int a = v + 8;\n",
7898 " int b = seed * 2005;\n",
7899 " int *q = &cells[v + 1];\n",
7900 " work();\n",
7901 " sink = *p + a + b + *q;\n",
7902 " } else {\n",
7903 " printf(\"%d\\n\", v);\n",
7904 " }\n",
7905 " return 0;\n",
7906 "}\n",
7907 );
7908 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
7909 let mut opts = options();
7910 opts.emit = EmitKind::Asm;
7911 opts.opt_level = level;
7912 let result = run(&opts, source);
7913 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
7914 let text = result.text();
7915 let body = text.split_once("\nmain:\n").expect("the function").1;
7916 let lines: Vec<&str> = body.lines().map(str::trim).collect();
7917 let save = lines.iter().position(|l| *l == "call\t_setjmp").expect("the save");
7918 let jump = lines[save..]
7919 .iter()
7920 .position(|l| *l == "call\twork" || *l == "call\tlongjmp")
7921 .map(|at| save + at)
7922 .unwrap_or_else(|| panic!("the call that jumps back at {level:?}:\n{text}"));
7923 let printf = lines.iter().position(|l| *l == "call\tprintf").expect("the handler");
7924 let slot = lines[jump..printf]
7926 .iter()
7927 .rev()
7928 .find_map(|l| l.strip_suffix(", %rsi").or_else(|| l.strip_suffix(", %esi")))
7929 .and_then(|l| l.split_once('\t'))
7930 .map(|(_, place)| place)
7931 .filter(|place| place.ends_with("(%rsp)") || place.ends_with("(%rbp)"))
7932 .unwrap_or_else(|| panic!("the handler reads v from a slot at {level:?}:\n{text}"));
7933 let writes = |l: &&str| {
7934 !l.starts_with("cmp") && !l.starts_with("test") && l.ends_with(&format!(", {slot}"))
7935 };
7936 assert!(
7937 lines[..save].iter().any(writes),
7938 "{slot} is written before the save at {level:?}:\n{text}"
7939 );
7940 assert!(
7941 !lines[save..jump].iter().any(writes),
7942 "{slot} is written again before the jump at {level:?}:\n{text}"
7943 );
7944 }
7945 }
7946
7947 fn across(declared: &str, save: &str) -> String {
7950 asm(&format!(
7951 "typedef long sigjmp_buf[25];\n{declared}int id(int);\nvoid thrower(int);\n\
7952 int work(int n) {{\n\
7953 \x20 int v0 = id(n), v1 = id(n + 1), v2 = id(n + 2), v3 = id(n + 3), v4 = id(n + 4);\n\
7954 \x20 sigjmp_buf b;\n\
7955 \x20 if ({save}(b, 0) == 0) {{\n\
7956 \x20 int w0 = id(v0 + v1), w1 = id(v1 + v2), w2 = id(v2 + v3);\n\
7957 \x20 int w3 = id(v3 + v4), w4 = id(v4 + v0);\n\
7958 \x20 thrower(n);\n\
7959 \x20 return w0 ^ w1 ^ w2 ^ w3 ^ w4;\n\
7960 \x20 }}\n\
7961 \x20 return v0 + v1 + v2 + v3 + v4;\n\
7962 }}\n"
7963 ))
7964 }
7965
7966 fn each_spill_slot_written_once(text: &str) {
7968 let mut stored = Vec::new();
7969 for line in text.lines().map(str::trim) {
7970 let Some(operands) = line.strip_prefix("movq\t%") else { continue };
7971 if let Some((_, place)) = operands.split_once(", ") {
7972 if place.ends_with("(%rsp)") {
7973 assert!(!stored.contains(&place), "{place} is written twice:\n{text}");
7974 stored.push(place);
7975 }
7976 }
7977 }
7978 assert!(!stored.is_empty(), "something should have been spilled:\n{text}");
7979 }
7980
7981 #[test]
7990 fn the_save_writes_four_words_and_carries_on_in_a_new_block() {
7991 let text =
7992 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
7993 let body = text.split_once("\nf:\n").expect("the function").1;
7994 assert!(body.contains("\tmovq\t%rsp, %rbp\n"), "a frame pointer whatever: {text}");
7995 assert!(body.contains("\tsubq\t$8, %rsp\n"), "no red zone: {text}");
7996 assert!(body.contains("\tmovq\t%rbp, (%rax)\n"), "the frame pointer: {text}");
7997 assert!(body.contains("\tmovq\t%rsp, 16(%rax)\n"), "the stack pointer: {text}");
7998 assert!(body.contains("\tleaq\t.Lf_1(%rip), %rcx\n"), "where to come back to: {text}");
7999 assert!(body.contains("\tmovq\t%rcx, 8(%rax)\n"), "and that goes in the buffer: {text}");
8000 let back = body.split_once(".Lf_1:\n").expect("the block control comes back to").1;
8001 assert!(back.starts_with("\tmovq\t(%rsp), %rax\n"), "the answer is read back: {text}");
8002 }
8003
8004 #[test]
8012 fn a_save_destroys_every_register_the_allocator_hands_out() {
8013 let text =
8014 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
8015 for reg in ["%rbx", "%r12", "%r13", "%r14", "%r15"] {
8016 assert!(text.contains(&format!("\tpushq\t{reg}\n")), "{reg} is saved: {text}");
8017 assert!(text.contains(&format!("\tpopq\t{reg}\n")), "{reg} is restored: {text}");
8018 }
8019 }
8020
8021 #[test]
8028 fn the_restore_puts_the_frame_back_before_it_jumps() {
8029 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
8030 let mut opts = options();
8031 opts.emit = EmitKind::Asm;
8032 opts.opt_level = level;
8033 let source = "void *buf[5];\nvoid g(void) { __builtin_longjmp(buf, 1); }\n";
8034 let result = run(&opts, source);
8035 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
8036 let text = result.text().to_owned();
8037 let jump = text.find("\tjmp\t*%").unwrap_or_else(|| panic!("an indirect jump: {text}"));
8038 let stack = text.find(", %rsp\n").unwrap_or_else(|| panic!("the stack back: {text}"));
8039 let frame = text.find(", %rbp\n").unwrap_or_else(|| panic!("the frame back: {text}"));
8040 assert!(stack < jump, "the stack goes back first at {level:?}: {text}");
8041 assert!(frame < jump, "and so does the frame at {level:?}: {text}");
8042 }
8043 }
8044
8045 #[test]
8051 fn a_longjmp_whose_second_argument_is_not_one_is_turned_down() {
8052 for source in [
8053 "void *buf[5];\nvoid f(void) { __builtin_longjmp(buf, 0); }\n",
8054 "void *buf[5];\nextern int v;\nvoid f(void) { __builtin_longjmp(buf, v); }\n",
8055 ] {
8056 let messages = errors(source);
8057 let named = messages.iter().any(|m| m.contains("E0710"));
8058 assert!(named, "expected a complaint about the value in {messages:?}");
8059 }
8060 }
8061
8062 #[test]
8067 fn a_static_function_nothing_refers_to_is_not_emitted() {
8068 let text = ir("static int dropped(void) { return 1; }\n\
8069 static int kept(void) { return 2; }\n\
8070 int main(void) { return kept(); }\n");
8071 assert!(text.contains("func @kept"), "{text}");
8072 assert!(!text.contains("dropped"), "{text}");
8073 }
8074
8075 #[test]
8081 fn two_static_functions_that_only_call_each_other_are_both_dropped() {
8082 let text = ir("static int ping(void);\n\
8083 static int pong(void) { return ping(); }\n\
8084 static int ping(void) { return pong(); }\n\
8085 int main(void) { return 0; }\n");
8086 assert!(!text.contains("ping"), "{text}");
8087 assert!(!text.contains("pong"), "{text}");
8088 }
8089
8090 #[test]
8096 fn naming_a_static_function_anywhere_keeps_it() {
8097 let text = ir("static int by_address(void) { return 1; }\n\
8098 static int in_an_image(void) { return 2; }\n\
8099 static int deeper(void) { return 3; }\n\
8100 static int reaches_deeper(void) { return deeper(); }\n\
8101 static int (*table[1])(void) = {in_an_image};\n\
8102 int main(void) {\n\
8103 int (*p)(void) = by_address;\n\
8104 return p() + table[0]() + reaches_deeper();\n\
8105 }\n");
8106 for kept in ["by_address", "in_an_image", "deeper", "reaches_deeper"] {
8107 assert!(text.contains(&format!("func @{kept}")), "expected {kept} in:\n{text}");
8108 }
8109 }
8110
8111 #[test]
8117 fn an_attribute_keeps_a_static_function_nothing_refers_to() {
8118 for attribute in ["used", "retain", "constructor", "destructor", "__used__"] {
8119 let source = format!(
8120 "__attribute__(({attribute})) static int kept(void) {{ return 1; }}\n\
8121 int main(void) {{ return 0; }}\n"
8122 );
8123 let text = ir(&source);
8124 assert!(text.contains("func @kept"), "for {attribute}:\n{text}");
8125 }
8126 }
8127
8128 #[test]
8131 fn nonnull_is_answered_yes_and_taken_with_or_without_operands() {
8132 let text = ir("#if !__has_attribute(nonnull) || !__has_attribute(__nonnull__)\n\
8133 #error nonnull\n\
8134 #endif\n\
8135 __attribute__((nonnull)) int first(char *p);\n\
8136 int both(char *a, int n, char *b) __attribute__((__nonnull__(1, 3)));\n\
8137 int both(char *a, int n, char *b) { return first(a) + n + (b != 0); }\n");
8138 assert!(text.contains("func @both"), "{text}");
8139 }
8140
8141 #[test]
8144 fn a_function_anything_could_call_is_emitted_without_being_called() {
8145 let text =
8146 ir("int nobody_here_calls_it(void) { return 1; }\nint main(void) { return 0; }\n");
8147 assert!(text.contains("func @nobody_here_calls_it"), "{text}");
8148 }
8149
8150 #[test]
8157 fn a_classification_c_has_an_operator_for_is_that_operator() {
8158 for (builtin, operator) in [
8159 ("__builtin_isgreater", "binary >"),
8160 ("__builtin_isgreaterequal", "binary >="),
8161 ("__builtin_isless", "binary <"),
8162 ("__builtin_islessequal", "binary <="),
8163 ] {
8164 let source = format!("int f(double x, double y) {{ return {builtin}(x, y); }}\n");
8165 let text = tast(&source);
8166 assert!(text.contains(&format!("{operator} : int")), "for {builtin}:\n{text}");
8167 }
8168 }
8169
8170 #[test]
8179 fn the_classification_builtins_are_comparisons_and_not_calls() {
8180 let text = body("int f(double x, double y) { return __builtin_isunordered(x, y); }\n");
8181 assert_eq!(
8182 text,
8183 "block0(%0: f64, %1: f64):\n %2 = fcmp uno %0, %1\n %3 = zext.i32 \
8184 %2\n return %3\n"
8185 );
8186
8187 let text = body("int f(double x, double y) { return __builtin_islessgreater(x, y); }\n");
8189 assert!(text.contains("fcmp one %0, %1"), "{text}");
8190
8191 let text = body("int f(double x) { return __builtin_isnan(x); }\n");
8192 assert!(text.contains("fcmp uno %0, %0"), "{text}");
8193
8194 let text = body("int f(double x) { return __builtin_isinf(x); }\n");
8195 assert!(text.contains("fconst.f64 0x7ff0000000000000"), "{text}");
8196 assert!(text.contains("fconst.f64 0xfff0000000000000"), "{text}");
8197 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
8198 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
8199 assert!(text.contains("%5 = or %3, %4"), "{text}");
8200
8201 let text = body("int f(double x) { return __builtin_isfinite(x); }\n");
8204 assert!(text.contains("%3 = fcmp olt %2, %0"), "{text}");
8205 assert!(text.contains("%4 = fcmp olt %0, %1"), "{text}");
8206 assert!(text.contains("%5 = and %3, %4"), "{text}");
8207
8208 let text = body("int f(double x) { return __builtin_signbit(x); }\n");
8209 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
8210 assert!(text.contains("icmp slt %1, %2"), "{text}");
8211
8212 let text = body("int f(long double x) { return __builtin_signbitl(x); }\n");
8216 assert!(text.contains("load.i16"), "{text}");
8217 assert!(text.contains("icmp slt"), "{text}");
8218 assert!(!text.contains("i80"), "{text}");
8219
8220 let text = body("double g(void);\nint f(void) { return __builtin_isnan(g()); }\n");
8223 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
8224 }
8225
8226 #[test]
8233 fn a_classification_spelling_that_names_a_width_converts_before_it_asks() {
8234 let text = ir(concat!(
8235 "int a = __builtin_isinff(1e300);\n",
8236 "int b = __builtin_isinf(1e300);\n",
8237 "int c = __builtin_isnan(0.0);\n",
8241 "int d = __builtin_signbit(-0.0);\n",
8242 "int e = __builtin_islessgreater(1.0, 2.0);\n",
8243 ));
8244 assert!(text.contains("global @a : i32 = 1,"), "{text}");
8245 assert!(text.contains("global @b : i32 = 0,"), "{text}");
8246 assert!(text.contains("global @c : i32 = 0,"), "{text}");
8247 assert!(text.contains("global @d : i32 = 1,"), "{text}");
8248 assert!(text.contains("global @e : i32 = 1,"), "{text}");
8249 }
8250
8251 #[test]
8253 fn a_classification_builtin_refuses_an_argument_that_is_not_floating_point() {
8254 let mut opts = options();
8255 opts.emit = EmitKind::Ir;
8256 let source = concat!(
8257 "int a(int x) { return __builtin_isnan(x); }\n",
8258 "int b(int x, int y) { return __builtin_isunordered(x, y); }\n",
8259 "int c(double x) { return __builtin_isnan(x, x); }\n",
8260 );
8261 let messages = run(&opts, source).messages;
8262 assert_eq!(
8263 messages,
8264 [
8265 "/main.c:1:23: error: non-floating-point argument in call to function \
8266 '__builtin_isnan' [E0685]",
8267 "/main.c:2:30: error: non-floating-point arguments in call to function \
8268 '__builtin_isunordered' [E0685]",
8269 "/main.c:3:26: error: too many arguments to function '__builtin_isnan' [E0511]",
8270 ]
8271 );
8272 }
8273
8274 #[test]
8283 fn the_last_three_classification_builtins_are_comparisons_and_not_calls() {
8284 let text = body("int f(double x) { return __builtin_isnormal(x); }\n");
8285 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
8289 assert!(text.contains("%2 = iconst.i64 9223372036854775807"), "{text}");
8290 assert!(text.contains("%3 = and %1, %2"), "{text}");
8291 assert!(text.contains("%4 = iconst.i64 4503599627370496"), "{text}");
8292 assert!(text.contains("%5 = iconst.i64 9218868437227405312"), "{text}");
8293 assert!(text.contains("%6 = icmp uge %3, %4"), "{text}");
8294 assert!(text.contains("%7 = icmp ult %3, %5"), "{text}");
8295 assert!(text.contains("%8 = and %6, %7"), "{text}");
8296
8297 let text = body("int f(long double x) { return __builtin_isnormal(x); }\n");
8302 assert!(text.contains("fconst.f80 0x18000000000000000"), "{text}");
8303 assert!(text.contains("fconst.f80 0x7fff8000000000000000"), "{text}");
8304 assert!(text.contains("fcmp oge"), "{text}");
8305 assert!(text.contains("fcmp olt"), "{text}");
8306
8307 let text = body("int f(double x) { return __builtin_isinf_sign(x); }\n");
8308 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
8309 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
8310 assert!(text.contains("%7 = sub %5, %6"), "{text}");
8311
8312 let text = body("int f(double x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n");
8313 assert!(text.contains("fcmp uno %0, %0"), "{text}");
8314 assert!(text.contains("fcmp oeq %0, %6"), "{text}");
8315 assert_eq!(text.matches(" = zext.i32 ").count(), 4, "{text}");
8319 assert_eq!(text.matches(" = xor ").count(), 4, "{text}");
8320 assert!(!text.contains("call"), "{text}");
8321
8322 let text = body(concat!(
8325 "double g(void);\n",
8326 "int f(void) { return __builtin_fpclassify(0, 1, 2, 3, 4, g()); }\n",
8327 ));
8328 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
8329 }
8330
8331 #[test]
8338 fn the_last_three_classification_builtins_fold_where_their_operand_is_a_constant() {
8339 let text = ir(concat!(
8340 "int a = __builtin_isnormal(1.0);\n",
8341 "int b = __builtin_isnormal(0.0);\n",
8342 "int c = __builtin_isnormal(1.0 / 0.0);\n",
8343 "int d = __builtin_isinf_sign(-1.0 / 0.0);\n",
8344 "int e = __builtin_isinf_sign(1.0);\n",
8345 "int g = __builtin_fpclassify(0, 1, 2, 3, 4, 0.0);\n",
8346 "int h = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0);\n",
8347 "int i = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0 / 0.0);\n",
8348 ));
8349 assert!(text.contains("global @a : i32 = 1,"), "{text}");
8350 assert!(text.contains("global @b : i32 = 0,"), "{text}");
8351 assert!(text.contains("global @c : i32 = 0,"), "{text}");
8352 assert!(text.contains("global @d : i32 = -1,"), "{text}");
8353 assert!(text.contains("global @e : i32 = 0,"), "{text}");
8354 assert!(text.contains("global @g : i32 = 4,"), "{text}");
8355 assert!(text.contains("global @h : i32 = 2,"), "{text}");
8356 assert!(text.contains("global @i : i32 = 1,"), "{text}");
8357 }
8358
8359 #[test]
8365 fn fpclassify_refuses_an_answer_that_is_not_an_integer_constant() {
8366 let mut opts = options();
8367 opts.emit = EmitKind::Ir;
8368 let source = concat!(
8369 "int a(double x, int n) { return __builtin_fpclassify(0, 1, n, 3, 4, x); }\n",
8370 "int b(double x) { return __builtin_fpclassify(0, 1, 2, 3, x); }\n",
8371 "int c(int x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n",
8372 );
8373 let messages = run(&opts, source).messages;
8374 assert_eq!(
8375 messages,
8376 [
8377 "/main.c:1:60: error: non-const integer argument 3 in call to function \
8378 '__builtin_fpclassify' [E0687]",
8379 "/main.c:2:26: error: too few arguments to function '__builtin_fpclassify' \
8380 [E0511]",
8381 "/main.c:3:23: error: non-floating-point argument in call to function \
8382 '__builtin_fpclassify' [E0685]",
8383 ]
8384 );
8385 }
8386
8387 #[test]
8395 fn a_builtin_whose_answer_is_a_constant_is_one_and_not_a_call() {
8396 let text = ir(concat!(
8397 "double a = __builtin_inf();\n",
8398 "float b = __builtin_huge_valf();\n",
8399 "long double c = __builtin_infl();\n",
8400 "double d = __builtin_huge_val();\n",
8401 ));
8402 assert!(text.contains("global @a : f64 = 0x7ff0000000000000,"), "{text}");
8403 assert!(text.contains("global @b : f32 = 0x7f800000,"), "{text}");
8404 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
8405 assert!(text.contains("global @d : f64 = 0x7ff0000000000000,"), "{text}");
8406 assert!(!text.contains("call"), "{text}");
8407 }
8408
8409 #[test]
8418 fn a_nan_is_written_with_the_payload_the_program_asked_for() {
8419 let text = ir(concat!(
8420 "double a = __builtin_nan(\"\");\n",
8421 "double b = __builtin_nan(\"0x1\");\n",
8422 "double c = __builtin_nan(\"010\");\n",
8424 "double d = __builtin_nans(\"\");\n",
8425 "double e = __builtin_nans(\"0x1\");\n",
8426 "float f = __builtin_nanf(\"0x1\");\n",
8427 "float g = __builtin_nansf(\"\");\n",
8428 "long double h = __builtin_nansl(\"\");\n",
8429 ));
8430 assert!(text.contains("global @a : f64 = 0x7ff8000000000000,"), "{text}");
8431 assert!(text.contains("global @b : f64 = 0x7ff8000000000001,"), "{text}");
8432 assert!(text.contains("global @c : f64 = 0x7ff8000000000008,"), "{text}");
8433 assert!(text.contains("global @d : f64 = 0x7ff4000000000000,"), "{text}");
8434 assert!(text.contains("global @e : f64 = 0x7ff0000000000001,"), "{text}");
8435 assert!(text.contains("global @f : f32 = 0x7fc00001,"), "{text}");
8436 assert!(text.contains("global @g : f32 = 0x7fa00000,"), "{text}");
8437 assert!(text.contains("f80 0x7fffa000000000000000"), "{text}");
8438
8439 let text = ir(concat!(
8442 "double f(const char *p) { return __builtin_nan(p); }\n",
8443 "double g(void) { return __builtin_nans(\"1x\"); }\n",
8444 ));
8445 assert_eq!(text.matches("call @nan(").count(), 1, "{text}");
8446 assert_eq!(text.matches("call @nans(").count(), 1, "{text}");
8447 }
8448
8449 #[test]
8457 fn the_length_and_the_order_of_a_string_literal_are_known_here() {
8458 let text = ir(concat!(
8459 "unsigned long a = __builtin_strlen(\"hello\");\n",
8460 "unsigned long b = __builtin_strlen(\"a\\0bc\");\n",
8461 "int c = __builtin_strcmp(\"X\", \"X\\376\") < 0;\n",
8462 "int d = __builtin_strcmp(\"abc\", \"abc\");\n",
8463 "int e = __builtin_strcmp(\"abc\", \"ab\") > 0;\n",
8464 ));
8465 assert!(text.contains("global @a : i64 = 5,"), "{text}");
8466 assert!(text.contains("global @b : i64 = 1,"), "{text}");
8467 assert!(text.contains("global @c : i32 = 1,"), "{text}");
8468 assert!(text.contains("global @d : i32 = 0,"), "{text}");
8469 assert!(text.contains("global @e : i32 = 1,"), "{text}");
8470 assert!(!text.contains("call"), "{text}");
8471
8472 let text = ir("unsigned long f(const char *p) { return __builtin_strlen(p); }\n");
8474 assert!(text.contains("call @strlen("), "{text}");
8475 }
8476
8477 #[test]
8484 fn a_sign_builtin_is_a_mask_over_the_bits_and_not_a_call() {
8485 let text = body("double f(double x) { return __builtin_fabs(x); }\n");
8486 assert!(text.contains("bitcast.i64 %0"), "{text}");
8487 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
8488 assert!(text.contains("and %1, %2"), "{text}");
8489 assert!(text.contains("bitcast.f64 %3"), "{text}");
8490 assert!(!text.contains("call"), "{text}");
8491
8492 let text = body("double f(double x, double y) { return __builtin_copysign(x, y); }\n");
8493 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
8494 assert!(text.contains("%8 = or %4, %7"), "{text}");
8495 assert!(!text.contains("call"), "{text}");
8496
8497 let text = body("long double f(long double x) { return __builtin_fabsl(x); }\n");
8500 assert!(text.contains("iconst.i16 32767"), "{text}");
8501 assert!(text.contains("load.f80"), "{text}");
8502 assert!(!text.contains("call"), "{text}");
8503
8504 let text = body("double f(float x) { return __builtin_fabs(x); }\n");
8507 assert!(text.contains("fpext.f64 %0"), "{text}");
8508 assert!(text.contains("bitcast.i64 %1"), "{text}");
8509 }
8510
8511 #[test]
8518 fn a_shuffle_picks_each_lane_by_the_low_bits_of_the_mask() {
8519 let text = body(concat!(
8520 "typedef int v2 __attribute__((vector_size(8)));\n",
8521 "void f(v2 *v, v2 m) { *v = __builtin_shuffle(*v, m); }\n",
8522 ));
8523 assert!(text.contains("memcpy"), "{text}");
8524 assert_eq!(text.matches("iconst.i32 1\n").count(), 2, "{text}");
8525 assert_eq!(text.matches(" = and ").count(), 2, "{text}");
8526 assert!(!text.contains("call"), "{text}");
8527
8528 let text = body(concat!(
8531 "typedef char v4 __attribute__((vector_size(4)));\n",
8532 "v4 f(v4 a, v4 b, v4 m) { return __builtin_shuffle(a, b, m); }\n",
8533 ));
8534 assert_eq!(text.matches("iconst.i32 7\n").count(), 4, "{text}");
8535 assert!(text.contains("zext.i32"), "{text}");
8536 assert!(!text.contains("call"), "{text}");
8537 }
8538
8539 #[test]
8543 fn the_arguments_a_function_was_called_with_are_saved_on_the_way_in() {
8544 let text =
8545 mir("void *f(int a, double b) { (void)a; (void)b; return __builtin_apply_args(); }\n");
8546 assert!(text.matches("x64.mov_mr_64").count() >= 7, "{text}");
8548 assert!(text.matches("x64.movaps_mr").count() >= 8, "{text}");
8549 for reg in ["$rdi", "$rsi", "$rdx", "$rcx", "$r8", "$r9", "$xmm0", "$xmm7"] {
8550 assert!(text.contains(reg), "{reg} is not saved in\n{text}");
8551 }
8552
8553 let text = mir("int f(int a) { return a; }\n");
8555 assert!(!text.contains("movaps_mr"), "{text}");
8556 }
8557
8558 #[test]
8562 fn a_call_built_from_saved_arguments_loads_every_argument_register() {
8563 let text = mir(concat!(
8564 "void *g(void *args, void (*h)()) {\n",
8565 " return __builtin_apply(h, args, 64);\n",
8566 "}\n",
8567 ));
8568 assert!(text.matches("x64.mov_rm_64").count() >= 7, "{text}");
8569 assert!(text.matches("x64.movaps_rm").count() >= 8, "{text}");
8570 assert!(text.contains("call"), "{text}");
8571 assert!(text.matches("x64.movaps_mr").count() >= 2, "{text}");
8573
8574 let mut opts = options();
8576 opts.emit = EmitKind::Ir;
8577 let result = run(
8578 &opts,
8579 "void *g(void *a, void (*h)(), int n) { return __builtin_apply(h, a, n); }\n",
8580 );
8581 assert!(result.failed(), "{:?}", result.messages);
8582 assert!(
8583 result
8584 .messages
8585 .iter()
8586 .any(|m| m.contains("the size given to '__builtin_apply' is a constant")),
8587 "{:?}",
8588 result.messages
8589 );
8590 }
8591
8592 #[test]
8594 fn a_shuffle_refuses_what_gcc_refuses() {
8595 let mut opts = options();
8596 opts.emit = EmitKind::Ir;
8597 let source = concat!(
8598 "typedef int v4 __attribute__((vector_size(16)));\n",
8599 "typedef float f4 __attribute__((vector_size(16)));\n",
8600 "typedef short s8 __attribute__((vector_size(16)));\n",
8601 "typedef long long l4 __attribute__((vector_size(32)));\n",
8602 "void a(v4 x, f4 m) { __builtin_shuffle(x, m); }\n",
8603 "void b(int x, v4 m) { __builtin_shuffle(x, m); }\n",
8604 "void c(v4 x, f4 y, v4 m) { __builtin_shuffle(x, y, m); }\n",
8605 "void d(v4 x, s8 m) { __builtin_shuffle(x, m); }\n",
8606 "void e(f4 x, l4 m) { __builtin_shuffle(x, m); }\n",
8607 "void g(v4 x) { __builtin_shuffle(x); }\n",
8608 );
8609 let messages = run(&opts, source).messages;
8610 let wanted = [
8611 "last argument must be an integer vector [E0715]",
8612 "arguments must be vectors [E0715]",
8613 "argument vectors must be of the same type [E0715]",
8614 "number of elements of the argument vector(s) and the mask vector should be the same \
8615 [E0715]",
8616 "argument vector(s) inner type must have the same size as inner type of the mask \
8617 [E0715]",
8618 "too few arguments to function '__builtin_shuffle' [E0511]",
8619 ];
8620 assert_eq!(messages.len(), wanted.len(), "{messages:?}");
8621 for (message, wanted) in messages.iter().zip(wanted) {
8622 assert!(message.ends_with(wanted), "{message}");
8623 }
8624 }
8625
8626 #[test]
8635 fn the_plain_math_names_are_the_same_mask_and_not_a_call() {
8636 let text =
8637 body(concat!("double fabs(double x);\n", "double f(double x) { return fabs(x); }\n",));
8638 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
8639 assert!(!text.contains("call"), "{text}");
8640
8641 let text =
8642 body(concat!("float fabsf(float x);\n", "float f(float x) { return fabsf(x); }\n",));
8643 assert!(text.contains("bitcast.i32 %0"), "{text}");
8644 assert!(!text.contains("call"), "{text}");
8645
8646 let text = body(concat!(
8647 "double copysign(double x, double y);\n",
8648 "double f(double x, double y) { return copysign(x, y); }\n",
8649 ));
8650 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
8651 assert!(!text.contains("call"), "{text}");
8652
8653 let text = body(concat!(
8654 "float copysignf(float x, float y);\n",
8655 "float f(float x, float y) { return copysignf(x, y); }\n",
8656 ));
8657 assert!(!text.contains("call"), "{text}");
8658
8659 let text = ir(concat!(
8663 "long double fabsl(long double x);\n",
8664 "long double f(long double x) { return fabsl(x); }\n",
8665 ));
8666 assert!(text.contains("call @fabsl"), "{text}");
8667 }
8668
8669 #[test]
8677 fn a_plain_math_name_the_program_took_is_the_programs_own_function() {
8678 let taken = concat!(
8679 "static double fabs(double b) { return 7; }\n",
8680 "double f(double x) { return fabs(x); }\n",
8681 );
8682 assert!(ir(taken).contains("call @fabs"), "a static definition is the program's own");
8683
8684 let retyped = concat!("int fabs(int b);\n", "int f(int x) { return fabs(x); }\n");
8685 assert!(ir(retyped).contains("call @fabs"), "another type is another function");
8686
8687 let plain = concat!("double fabs(double b);\n", "double f(double x) { return fabs(x); }\n");
8688 let mut opts = options();
8689 opts.emit = EmitKind::Ir;
8690 assert!(!run(&opts, plain).text().contains("call @fabs"), "the library's by default");
8691
8692 opts.builtins = false;
8693 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin");
8694
8695 opts.builtins = true;
8696 opts.no_builtin = vec!["fabs".to_owned()];
8697 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin-fabs");
8698 let one = concat!(
8699 "double copysign(double a, double b);\n",
8700 "double f(double x) { return copysign(x, 1.0); }\n",
8701 );
8702 assert!(!run(&opts, one).text().contains("call @copysign"), "one name and not the family");
8703
8704 opts.no_builtin = Vec::new();
8706 opts.builtins = false;
8707 let prefixed = "double f(double x) { return __builtin_fabs(x); }\n";
8708 assert!(!run(&opts, prefixed).text().contains("call @fabs"), "the prefix is not a library");
8709 }
8710
8711 #[test]
8720 fn the_sign_builtins_answer_a_zero_and_a_nan_the_way_the_bits_say() {
8721 let text = ir(concat!(
8722 "double a = __builtin_fabs(-3.5);\n",
8723 "double b = __builtin_copysign(1.0, -0.0);\n",
8724 "double c = __builtin_copysign(0.0, -2.0);\n",
8725 "double d = __builtin_copysign(-__builtin_nan(\"\"), 1.0);\n",
8727 "double e = __builtin_fabs(-__builtin_nan(\"0x1\"));\n",
8728 "float g = __builtin_copysignf(-0.0f, 2.0f);\n",
8729 "long double h = __builtin_copysignl(1.0L, -1.0L);\n",
8730 "long double i = __builtin_fabsl(-__builtin_infl());\n",
8731 ));
8732 assert!(text.contains("global @a : f64 = 0x400c000000000000,"), "{text}");
8733 assert!(text.contains("global @b : f64 = 0xbff0000000000000,"), "{text}");
8734 assert!(text.contains("global @c : f64 = 0x8000000000000000,"), "{text}");
8735 assert!(text.contains("global @d : f64 = 0x7ff8000000000000,"), "{text}");
8736 assert!(text.contains("global @e : f64 = 0x7ff8000000000001,"), "{text}");
8737 assert!(text.contains("global @g : f32 = 0x0,"), "{text}");
8738 assert!(text.contains("f80 0xbfff8000000000000000"), "{text}");
8739 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
8740 }
8741
8742 #[test]
8749 fn the_sign_of_a_long_double_is_in_the_word_at_the_top_of_it() {
8750 for source in [
8751 "int f(long double x) { return __builtin_signbit(x); }\n",
8752 "long double f(long double x) { return __builtin_fabsl(x); }\n",
8753 "long double f(long double x, long double y) { return __builtin_copysignl(x, y); }\n",
8754 "int f(long double x) { return __builtin_isnormal(x); }\n",
8755 ] {
8756 let text = body(source);
8757 assert!(!text.contains("i80"), "{text}");
8758 assert!(text.contains("i16"), "{text}");
8759 }
8760 }
8761
8762 #[test]
8770 fn the_complex_builtins_are_the_halves_of_the_value_and_not_a_call() {
8771 let text = body("double f(_Complex double z) { return __builtin_creal(z); }\n");
8772 assert!(!text.contains("call"), "{text}");
8773 let text = body("double f(_Complex double z) { return __builtin_cimag(z); }\n");
8774 assert!(!text.contains("call"), "{text}");
8775
8776 let text = body("_Complex double f(_Complex double z) { return __builtin_conj(z); }\n");
8779 assert_eq!(text.matches("fneg").count(), 1, "{text}");
8780 assert!(!text.contains("call"), "{text}");
8781 let negated = body("_Complex double f(_Complex double z) { return -z; }\n");
8782 assert_eq!(negated.matches("fneg").count(), 2, "{negated}");
8783
8784 let written = body("_Complex double f(_Complex double z) { return ~z; }\n");
8787 assert_eq!(written, text, "the name and the operator are the same thing");
8788
8789 let text = body(concat!(
8791 "double creal(_Complex double z);\n",
8792 "double f(_Complex double z) { return creal(z); }\n",
8793 ));
8794 assert!(!text.contains("call"), "{text}");
8795 let text = body(concat!(
8796 "_Complex float conjf(_Complex float z);\n",
8797 "_Complex float f(_Complex float z) { return conjf(z); }\n",
8798 ));
8799 assert_eq!(text.matches("fneg").count(), 1, "{text}");
8800 assert!(!text.contains("call"), "{text}");
8801
8802 let taken = concat!(
8805 "static double creal(_Complex double z) { return 7; }\n",
8806 "double f(_Complex double z) { return creal(z); }\n",
8807 );
8808 assert!(ir(taken).contains("call @creal"), "a static definition is the program's own");
8809 let retyped = concat!("int cimag(int z);\n", "int f(int z) { return cimag(z); }\n");
8810 assert!(ir(retyped).contains("call @cimag"), "another type is another function");
8811 let plain = concat!(
8812 "double cimag(_Complex double z);\n",
8813 "double f(_Complex double z) { return cimag(z); }\n",
8814 );
8815 let mut opts = options();
8816 opts.emit = EmitKind::Ir;
8817 opts.builtins = false;
8818 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin");
8819 opts.builtins = true;
8820 opts.no_builtin = vec!["cimag".to_owned()];
8821 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin-cimag");
8822
8823 let text = ir(concat!(
8825 "double a = __builtin_creal(1.5 + 2.5i);\n",
8826 "double b = __builtin_cimag(1.5 + 2.5i);\n",
8827 "_Complex double c = __builtin_conj(1.5 + 2.5i);\n",
8828 ));
8829 assert!(text.contains("global @a : f64 = 0x3ff8000000000000,"), "{text}");
8830 assert!(text.contains("global @b : f64 = 0x4004000000000000,"), "{text}");
8831 assert!(
8832 text.contains("{ f64 0x3ff8000000000000, f64 0xc004000000000000 }"),
8833 "the conjugate of a constant is the constant with the second half negated: {text}"
8834 );
8835 assert!(!text.contains("call"), "{text}");
8836 }
8837
8838 #[test]
8846 fn a_math_library_builtin_of_a_constant_is_the_answer_and_not_a_call() {
8847 let text = ir(concat!(
8848 "double a = __builtin_ceil(1.5);\n",
8849 "double b = __builtin_floor(1.5);\n",
8850 "double c = __builtin_trunc(-1.5);\n",
8851 "double d = __builtin_round(2.5);\n",
8854 "double e = __builtin_ceil(-0.5);\n",
8856 "double f = __builtin_fmax(1.0, 2.0);\n",
8857 "double g = __builtin_fmin(1.0, 2.0);\n",
8858 "float h = __builtin_ceilf(1.25f);\n",
8859 "double ceil(double x);\n",
8862 "double i = ceil(2.25);\n",
8863 ));
8864 assert!(text.contains("global @a : f64 = 0x4000000000000000,"), "{text}");
8865 assert!(text.contains("global @b : f64 = 0x3ff0000000000000,"), "{text}");
8866 assert!(text.contains("global @c : f64 = 0xbff0000000000000,"), "{text}");
8867 assert!(text.contains("global @d : f64 = 0x4008000000000000,"), "{text}");
8868 assert!(text.contains("global @e : f64 = 0x8000000000000000,"), "{text}");
8869 assert!(text.contains("global @f : f64 = 0x4000000000000000,"), "{text}");
8870 assert!(text.contains("global @g : f64 = 0x3ff0000000000000,"), "{text}");
8871 assert!(text.contains("global @h : f32 = 0x40000000,"), "{text}");
8872 assert!(text.contains("global @i : f64 = 0x4008000000000000,"), "{text}");
8873 assert!(!text.contains("call"), "{text}");
8874 }
8875
8876 #[test]
8884 fn a_math_library_builtin_of_anything_else_is_a_call_to_the_library() {
8885 let text = ir(concat!(
8886 "double f(double x) { return __builtin_ceil(x); }\n",
8887 "float g(float x) { return __builtin_floorf(x); }\n",
8888 "double h(double x, double y) { return __builtin_fmax(x, y); }\n",
8889 ));
8890 assert!(text.contains("call @ceil("), "{text}");
8891 assert!(text.contains("call @floorf("), "{text}");
8892 assert!(text.contains("call @fmax("), "{text}");
8893
8894 let text = ir(concat!(
8898 "double f(void) { return __builtin_rint(2.5); }\n",
8899 "double g(void) { return __builtin_nearbyint(2.5); }\n",
8900 ));
8901 assert!(text.contains("call @rint("), "{text}");
8902 assert!(text.contains("call @nearbyint("), "{text}");
8903
8904 let text = ir("double f(void) { return __builtin_fmin(__builtin_nan(\"\"), 1.0); }\n");
8907 assert!(text.contains("call @fmin("), "{text}");
8908
8909 let plain = concat!("double ceil(double x);\n", "double f(void) { return ceil(2.25); }\n");
8912 let mut opts = options();
8913 opts.emit = EmitKind::Ir;
8914 opts.no_builtin = vec!["ceil".to_owned()];
8915 assert!(run(&opts, plain).text().contains("call @ceil("), "-fno-builtin-ceil");
8916 }
8917
8918 #[test]
8925 fn a_constexpr_object_is_a_constant_wherever_one_is_required() {
8926 let text = ir(concat!(
8927 "constexpr int side = 4;\n",
8928 "constexpr int wider = side + 1;\n",
8929 "constexpr double half = 1.5;\n",
8930 "struct point { int x; int y; };\n",
8931 "constexpr struct point origin = { 5, 6 };\n",
8932 "int square[side * side];\n",
8933 "int rectangle[wider];\n",
8934 "int rounded[(int)half * 2];\n",
8935 "int across[origin.y];\n",
8936 "enum named { four = side };\n",
8937 "int e = four;\n",
8938 ));
8939 assert!(text.contains("global @square : bytes 64 ="), "{text}");
8940 assert!(text.contains("global @rectangle : bytes 20 ="), "{text}");
8941 assert!(text.contains("global @rounded : bytes 8 ="), "{text}");
8942 assert!(text.contains("global @across : bytes 24 ="), "{text}");
8943 assert!(text.contains("global @e : i32 = 4,"), "{text}");
8944
8945 let mut opts = options();
8948 opts.emit = EmitKind::Ir;
8949 let konst = "const int n = 1;\nint a[n];\n";
8950 let message = "/main.c:2:5: error: variably modified 'a' at file scope [E0538]";
8951 assert_eq!(run(&opts, konst).messages, [message]);
8952
8953 let subscript = "constexpr int t[3] = { 1, 2, 3 };\nint a[t[1]];\n";
8955 assert_eq!(run(&opts, subscript).messages, [message]);
8956
8957 let address = "constexpr int c = 3;\nint *p = &c;\n";
8959 let warning = "/main.c:2:6: warning: initialization discards 'const' qualifier from \
8960 pointer target type [E0514]";
8961 assert_eq!(run(&opts, address).messages, [warning]);
8962 }
8963
8964 #[test]
8973 fn a_member_whose_size_was_refused_is_not_a_flexible_array_member() {
8974 let mut opts = options();
8975 opts.emit = EmitKind::Ir;
8976
8977 let alone = "int k;\nextern struct D { int a[k]; } ed;\n";
8978 let message = "/main.c:2:23: error: variably modified 'a' at file scope [E0538]";
8979 assert_eq!(run(&opts, alone).messages, [message]);
8980
8981 let first = "int k;\nextern struct E { int a[k]; int b; } ee;\n";
8983 assert_eq!(run(&opts, first).messages, [message]);
8984
8985 let negative = "struct F { int a[-1]; };\n";
8988 let refused = "/main.c:1:18: error: size of array 'a' is negative [E0536]";
8989 assert_eq!(run(&opts, negative).messages, [refused]);
8990
8991 let flexible = "struct G { int a[]; };\n";
8994 let named = "/main.c:1:16: error: flexible array member in a struct with no named \
8995 members [E0554]";
8996 assert_eq!(run(&opts, flexible).messages, [named]);
8997 }
8998
8999 #[test]
9014 fn a_pointer_to_an_array_gains_a_qualifier_the_same_way_a_pointer_to_anything_else_does() {
9015 let mut opts = options();
9016 opts.emit = EmitKind::Ir;
9017 let prefix = "typedef unsigned int B[4];\nstruct H { B category[2]; };\n";
9018
9019 let adding = format!("{prefix}const B *f(struct H *h) {{ return &h->category[0]; }}\n");
9021 assert_eq!(run(&opts, &adding).messages, [] as [String; 0]);
9022
9023 let plain = concat!(
9026 "const unsigned int (*f(unsigned int (*p)[4]))[4] { return p; }\n",
9027 "const unsigned int (*g(unsigned int (*p)[2][3]))[2][3] { return p; }\n",
9028 );
9029 assert_eq!(run(&opts, plain).messages, [] as [String; 0]);
9030
9031 let dropping = format!("{prefix}B *f(const B *p) {{ return p; }}\n");
9034 let warning = "/main.c:3:27: warning: return discards 'const' qualifier from pointer target type \
9035 [E0514]";
9036 assert_eq!(run(&opts, &dropping).messages, [warning]);
9037
9038 let wrong = "const unsigned int (*f(unsigned short (*p)[4]))[4] { return p; }\n";
9041 let error = "/main.c:1:61: error: returning 'unsigned short (*)[4]' from a function with \
9042 incompatible return type 'const unsigned int (*)[4]' [E0512]";
9043 assert_eq!(run(&opts, wrong).messages, [error]);
9044 }
9045
9046 #[test]
9055 fn an_old_style_definition_takes_its_types_from_the_declarations_under_its_list() {
9056 let mut opts = options();
9059 opts.std = Std::C17;
9060 let source = concat!(
9061 "int add(a, b)\n",
9062 "int a;\n",
9063 "int b;\n",
9064 "{ return a + b; }\n",
9065 "int promoted(c)\n",
9066 "char c;\n",
9067 "{ return c; }\n",
9068 "int narrow(char);\n",
9069 "int narrow(c)\n",
9070 "char c;\n",
9071 "{ return c; }\n",
9072 "int first(a)\n",
9073 "int a[4];\n",
9074 "{ return a[0]; }\n",
9075 );
9076 let result = run(&opts, source);
9077 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
9078 let text = result.text();
9079 assert!(text.contains("add : int(int, int) function external defined"), "{text}");
9080 assert!(text.contains("promoted : int(int) function external defined"), "{text}");
9081 assert!(text.contains("c : char object automatic defined"), "{text}");
9083 assert!(text.contains("narrow : int(char) function external defined"), "{text}");
9084 assert!(text.contains("first : int(int *) function external defined"), "{text}");
9086 }
9087
9088 #[test]
9095 fn the_two_halves_of_an_old_style_parameter_list_have_to_agree() {
9096 let mut opts = options();
9097 opts.std = Std::C17;
9098 for (source, message) in [
9099 ("int f(a, a)\nint a;\n{ return a; }\n", "1:10: error: multiple parameters named 'a'"),
9100 (
9101 "int f(a)\nint a;\nint b;\n{ return a; }\n",
9102 "3:5: error: declaration for parameter 'b' but no such parameter",
9103 ),
9104 ("int f(a)\nint a;\nint a;\n{ return a; }\n", "3:5: error: redefinition of parameter"),
9105 ("int f(a)\nint a = 1;\n{ return a; }\n", "2:5: error: parameter 'a' is initialized"),
9106 (
9107 "int f(a)\nstatic int a;\n{ return a; }\n",
9108 "2:12: error: storage class specified for parameter 'a'",
9109 ),
9110 (
9111 "int f(char);\nint f(a)\nshort a;\n{ return a; }\n",
9112 "2:7: error: argument 'a' doesn't match prototype",
9113 ),
9114 ] {
9115 let result = run(&opts, source);
9116 assert!(result.failed(), "expected this to fail:\n{source}");
9117 assert!(result.messages[0].contains(message), "{:?}", result.messages);
9118 }
9119
9120 let implicit = "int f(a, b)\nint a;\n{ return a + b; }\n";
9123 let mut older = options();
9124 older.std = Std::C89;
9125 assert!(!run(&older, implicit).failed(), "{:?}", run(&older, implicit).messages);
9126 let result = run(&opts, implicit);
9127 assert!(
9128 result.messages[0].contains("1:10: error: type of 'b' defaults to 'int'"),
9129 "{:?}",
9130 result.messages
9131 );
9132
9133 let mut newer = options();
9137 newer.std = Std::C23;
9138 let plain = "int f(a)\nint a;\n{ return a; }\n";
9139 let result = run(&newer, plain);
9140 assert!(!result.failed(), "{:?}", result.messages);
9141 assert_eq!(
9142 result.messages,
9143 ["/main.c:1:5: warning: old-style function definition [E0412]"]
9144 );
9145 assert!(run(&opts, plain).messages.is_empty(), "and nothing to say in the dialects before");
9146 }
9147
9148 #[test]
9155 fn the_obsolete_designators_are_taken_and_are_pedantic_warnings() {
9156 let array = "int a[8] = { [3] 7 };\n";
9157 let member = "struct s { int x; } v = { x: 7 };\n";
9158 for source in [array, member] {
9159 let result = run(&options(), source);
9160 assert!(!result.failed(), "{:?}", result.messages);
9161 assert!(result.messages.is_empty(), "nothing to say: {:?}", result.messages);
9162 }
9163
9164 let mut asked = options();
9165 asked.pedantic = true;
9166 assert_eq!(
9167 run(&asked, array).messages,
9168 ["/main.c:1:18: warning: obsolete designator, write `[i] =` instead [E0415]"]
9169 );
9170 assert_eq!(
9171 run(&asked, member).messages,
9172 ["/main.c:1:27: warning: obsolete designator, write `.field =` instead [E0413]"]
9173 );
9174 }
9175
9176 #[test]
9183 fn a_type_is_refused_when_it_passes_the_largest_object_and_not_before() {
9184 let text = ir(concat!(
9185 "struct huge_struct { short buf[(1L << 62) - 256]; int a, b, c, d; };\n",
9186 "struct brim { char buf[9223372036854775807L]; };\n",
9187 "struct bitty { char buf[9223372036854775800L]; int x : 1; };\n",
9188 "unsigned long h = sizeof(struct huge_struct);\n",
9189 "unsigned long b = sizeof(struct brim);\n",
9190 "unsigned long y = sizeof(struct bitty);\n",
9191 ));
9192 assert!(text.contains("global @h : i64 = 9223372036854775312,"), "{text}");
9193 assert!(text.contains("global @b : i64 = 9223372036854775807,"), "{text}");
9194 assert!(text.contains("global @y : i64 = 9223372036854775804,"), "{text}");
9195
9196 let mut opts = options();
9197 opts.emit = EmitKind::Ir;
9198 let over = "struct over { char buf[9223372036854775800L]; char x[8]; };\n";
9199 let message = "/main.c:1:1: error: type 'struct over' is too large [E0560]";
9200 assert_eq!(run(&opts, over).messages, [message]);
9201 let array = "struct wide { short buf[1L << 62]; };\n";
9202 let message = "/main.c:1:25: error: size of array 'buf' exceeds \
9203 maximum object size '9223372036854775807' [E0537]";
9204 assert_eq!(run(&opts, array).messages[0], message);
9205 }
9206
9207 fn compile_bytes(source: &[u8]) -> Compiled {
9212 let mut opts = options();
9213 opts.emit = EmitKind::Ir;
9214 let mut fs = MemoryFileSystem::new();
9215 fs.insert("/main.c", source.to_vec());
9216 compile(&opts, "/main.c", &fs)
9217 }
9218
9219 #[test]
9226 fn a_byte_that_is_not_a_character_is_kept_in_a_literal_and_refused_outside_one() {
9227 let mut source = b"char s[] = \"a".to_vec();
9228 source.push(0xff);
9229 source.extend_from_slice(b"b\";\nchar c = '");
9230 source.push(0xff);
9231 source.extend_from_slice(b"';\n");
9232 let result = compile_bytes(&source);
9233 assert_eq!(result.messages, Vec::<String>::new(), "a raw byte in a literal is that byte");
9234 assert!(result.text().contains(r#"bytes "a\ffb\00""#), "{}", result.text());
9235 assert!(result.text().contains("global @c : i8 = -1,"), "{}", result.text());
9237
9238 let mut stray = b"int a".to_vec();
9239 stray.push(0xff);
9240 stray.extend_from_slice(b" = 1;\n");
9241 let result = compile_bytes(&stray);
9242 assert!(
9243 result.messages.iter().any(|m| m.contains("source is not valid UTF-8 here")),
9244 "{:?}",
9245 result.messages
9246 );
9247 }
9248
9249 #[test]
9250 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
9251 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
9252 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
9253 let expected = "\
9254func @add(i32, i32) -> i32, linkage(external) {
9255block0(%0: i32, %1: i32):
9256 %2 = add.nsw %0, %1
9257 return %2
9258}
9259";
9260 assert!(text.contains(expected), "{text}");
9261 }
9262
9263 #[test]
9264 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
9265 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
9266 assert!(!text.contains("alloca"), "{text}");
9267 assert!(!text.contains("load"), "{text}");
9268 assert!(!text.contains("store"), "{text}");
9269 }
9270
9271 #[test]
9272 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
9273 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
9274 let expected = "\
9275block0:
9276 %0 = alloca, size 4, align 4
9277 %1 = iconst.i32 1
9278 store %1 -> %0, align 4, tbaa !1
9279 %2 = call @g(%0) : (ptr) -> i32
9280 return %2
9281";
9282 assert_eq!(text, expected);
9283 }
9284
9285 #[test]
9286 fn a_loop_carries_what_it_changes_as_block_parameters() {
9287 let text = body(
9290 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
9291 return total;\n}\n",
9292 );
9293 assert!(!text.contains("alloca"), "{text}");
9294 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
9295 assert!(text.contains("jump block1("), "{text}");
9296 }
9297
9298 #[test]
9299 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
9300 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
9301 assert!(text.contains("icmp slt %0, %1"), "{text}");
9302 assert!(!text.contains("zext"), "{text}");
9303 }
9304
9305 #[test]
9306 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
9307 let text = body("int f(int a, int b) { return a && b; }\n");
9308 let expected = "\
9309block0(%0: i32, %1: i32):
9310 %2 = iconst.i32 0
9311 %3 = icmp ne %0, %2
9312 %4 = iconst.i1 0
9313 br_if %3, block1, block2(%4)
9314
9315block1:
9316 %5 = iconst.i32 0
9317 %6 = icmp ne %1, %5
9318 jump block2(%6)
9319
9320block2(%7: i1):
9321 %8 = zext.i32 %7
9322 return %8
9323";
9324 assert_eq!(text, expected);
9325 }
9326
9327 #[test]
9328 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
9329 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
9330 assert!(!text.contains("block3"), "{text}");
9333 assert!(!text.contains("iconst.i32 3"), "{text}");
9334 }
9335
9336 #[test]
9337 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
9338 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
9339 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
9340 assert!(body("int f(void) { }\n").contains("iconst.i32 0\n return"));
9343 }
9344
9345 #[test]
9346 fn a_structure_is_copied_rather_than_held_in_a_value() {
9347 let text = body(
9348 "struct point { int x, y; };\n\
9349 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
9350 );
9351 assert!(text.contains("memcpy"), "{text}");
9352 }
9353
9354 #[test]
9355 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
9356 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
9357 assert!(text.contains("memset"), "{text}");
9358 }
9359
9360 #[test]
9361 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
9362 let text = body(
9363 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
9364 default: r = 4; } return r; }\n",
9365 );
9366 let expected = "\
9367block0(%0: i32):
9368 %1 = iconst.i32 0
9369 switch %0, block1, [1 => block2, 2 => block3(%1)]
9370
9371block1:
9372 %2 = iconst.i32 4
9373 jump block4(%2)
9374
9375block2:
9376 %3 = iconst.i32 1
9377 jump block3(%3)
9378
9379block3(%4: i32):
9380 %5 = iconst.i32 2
9381 %6 = add.nsw %4, %5
9382 jump block4(%6)
9383
9384block4(%7: i32):
9385 return %7
9386";
9387 assert_eq!(text, expected);
9388 }
9389
9390 #[test]
9391 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
9392 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
9395 assert!(text.contains("%2 = sub %0, %1"), "{text}");
9396 assert!(text.contains("icmp ule"), "{text}");
9397 assert!(!text.contains("switch"), "{text}");
9398 }
9399
9400 #[test]
9401 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
9402 let text = body(
9403 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
9404 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
9405 );
9406 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
9409 assert!(text.contains("block5:\n jump block7("), "{text}");
9410 assert!(text.contains("block6:\n jump block8("), "{text}");
9411 }
9412
9413 #[test]
9414 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
9415 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
9416 }
9417
9418 #[test]
9419 fn a_label_a_loop_is_only_entered_through_builds_the_loop_around_it() {
9420 let text = body(
9425 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
9426 return n; }\n",
9427 );
9428 assert!(text.contains("switch %0, block1(%1), [1 => block2, 2 => block3(%1)]"), "{text}");
9431 assert!(text.contains("block3(%3: i32):\n %4 = iconst.i32 1"), "{text}");
9432 assert!(text.contains("block4:\n jump block3("), "{text}");
9433 }
9434
9435 #[test]
9436 fn a_goto_into_a_loop_body_enters_it_without_the_test() {
9437 let text = body("int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n");
9440 assert!(text.starts_with("block0(%0: i32, %1: i32):\n jump block1(%1)"), "{text}");
9441 assert!(text.contains("block1(%2: i32):\n %3 = iconst.i32 1"), "{text}");
9442 assert!(text.contains("br_if %6, block2, block3"), "{text}");
9443 }
9444
9445 #[test]
9446 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
9447 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
9448 assert!(!text.contains("alloca"), "{text}");
9452 assert!(text.contains("block2(%4: i32):\n return %4"), "{text}");
9453 assert_eq!(text.matches("jump block2(").count(), 2, "{text}");
9454 }
9455
9456 #[test]
9457 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
9458 let text =
9459 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
9460 assert!(!text.contains("alloca"), "{text}");
9461 assert!(text.contains("block1(%2: i32):"), "{text}");
9462 assert!(text.contains("jump block1(%5)"), "{text}");
9463 }
9464
9465 #[test]
9466 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
9467 assert_eq!(
9470 body("int f(int x) { return x; spare: return 0; }\n"),
9471 "block0(%0: i32):\n return %0\n"
9472 );
9473 }
9474
9475 #[test]
9476 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
9477 let text = body(
9478 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
9479 );
9480 assert_eq!(
9483 text,
9484 "\
9485block0(%0: ptr):
9486 %1 = load.i8 %0, align 1
9487 %2 = iconst.i8 3
9488 %3 = ashr %1, %2
9489 %4 = sext.i32 %3
9490 return %4
9491"
9492 );
9493 }
9494
9495 #[test]
9496 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
9497 let text =
9501 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
9502 assert_eq!(
9503 text,
9504 "\
9505block0(%0: ptr, %1: i32):
9506 %2 = iconst.i32 16777215
9507 %3 = and %1, %2
9508 %4 = trunc.i16 %3
9509 store %4 -> %0, align 2
9510 %5 = iconst.i32 16
9511 %6 = lshr %3, %5
9512 %7 = trunc.i8 %6
9513 %8 = iconst.i64 2
9514 %9 = ptr_add %0, %8
9515 store %7 -> %9, align 1
9516 return
9517"
9518 );
9519 }
9520
9521 #[test]
9522 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
9523 let text =
9524 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
9525 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
9528 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
9529 }
9530
9531 #[test]
9532 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
9533 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
9536 assert_eq!(text.matches("ashr").count(), 0, "{text}");
9537 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
9538 }
9539
9540 #[test]
9541 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
9542 let text = body(
9546 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
9547 );
9548 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
9549 }
9550
9551 #[test]
9552 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
9553 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
9556 assert!(
9557 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
9558 "{text}"
9559 );
9560 }
9561
9562 #[test]
9563 fn an_initialized_flexible_array_member_makes_the_object_larger_than_its_type() {
9564 let text = ir(concat!(
9569 "struct a { int i; int j[]; } x = { 1, { 2, 0, 2, 3 } };\n",
9570 "struct b { char c; char p[]; } y = { 'o', \"wx\" };\n",
9571 "struct c { char c; char p[]; } z = { '9', { 'e', 'b' } };\n",
9572 "char s[2] = \"hi\";\n",
9573 ));
9574 assert!(
9575 text.contains("global @x : bytes 20 = { i32 1, i32 2, i32 0, i32 2, i32 3 }"),
9576 "{text}"
9577 );
9578 assert!(text.contains("global @y : bytes 4 = { i8 111, bytes \"wx\\00\" }"), "{text}");
9579 assert!(text.contains("global @z : bytes 3 = { i8 57, i8 101, i8 98 }"), "{text}");
9580 assert!(text.contains("global @s : bytes 2 = { bytes \"hi\" }"), "{text}");
9583 }
9584
9585 #[test]
9586 fn a_definition_takes_a_parameter_it_left_unnamed() {
9587 let text = ir("int f(int a, int) { return a; }\n");
9591 assert!(text.contains("func @f(i32, i32) -> i32"), "{text}");
9592 assert!(text.contains("block0(%0: i32, %1: i32):"), "{text}");
9593
9594 let text = ir("int g(int, int n) { return n; }\n");
9597 assert!(text.contains("block0(%0: i32, %1: i32):\n return %1\n"), "{text}");
9598 }
9599
9600 #[test]
9601 fn an_assignment_of_a_structure_is_the_object_it_wrote() {
9602 let text = body(concat!(
9607 "struct s { int f; int g; };\n",
9608 "void h(struct s *a, struct s *c, struct s *d, struct s *e)\n",
9609 "{ *d = *e = a[0] = *c; }\n",
9610 ));
9611 assert_eq!(text.matches("memcpy").count(), 3, "{text}");
9612 assert!(text.contains("memcpy %8, %1, size 8, align 4\n"), "{text}");
9613 assert!(text.contains("memcpy %3, %8, size 8, align 4\n"), "{text}");
9614 assert!(text.contains("memcpy %2, %3, size 8, align 4\n"), "{text}");
9615 }
9616
9617 #[test]
9618 fn a_string_literal_stops_at_the_end_of_the_array_it_is_filling() {
9619 let mut opts = options();
9624 opts.emit = EmitKind::Ir;
9625 let result = run(
9626 &opts,
9627 concat!(
9628 "const char a[2][3] = { \"1234\", \"xyz\" };\n",
9629 "static const char b[3][5] = { \"12345\", \"678\", \"9\" };\n",
9630 "union u { struct { char x[4]; char y[4]; }; struct { char z[8]; }; };\n",
9631 "const union u c = { { \"1234\", \"567\" } };\n",
9632 ),
9633 );
9634 let text = result.text();
9635 assert_eq!(
9636 result.messages,
9637 ["/main.c:1:24: warning: initializer-string for array of 'const char' is too long \
9638 (5 chars into 3 available) [E0637]"]
9639 );
9640 assert!(text.contains("global @a : bytes 6 = { bytes \"123\", bytes \"xyz\" }"), "{text}");
9641 assert!(
9642 text.contains(
9643 "global @b : bytes 15 = { bytes \"12345\", bytes \"678\\00\", zero 1, \
9644 bytes \"9\\00\", zero 3 }"
9645 ),
9646 "{text}"
9647 );
9648 assert!(
9651 text.contains("global @c : bytes 8 = { bytes \"1234\", bytes \"567\\00\" }"),
9652 "{text}"
9653 );
9654 }
9655
9656 #[test]
9657 fn a_cast_of_a_record_to_its_own_type_is_the_object_that_was_cast() {
9658 let text = body(concat!(
9662 "struct s { int a, b; };\nstruct v { struct s s; int t; };\n",
9663 "void g(struct v *);\n",
9664 "void f(struct s *p) { struct v w = { (struct s)*p, 5 }; g(&w); }\n",
9665 ));
9666 assert_eq!(text.matches("memcpy").count(), 1, "{text}");
9667 }
9668
9669 #[test]
9670 fn a_compound_literal_read_in_a_static_initializer_lays_its_bytes_into_the_image() {
9671 let text = ir(concat!(
9676 "struct s { int x; };\n",
9677 "struct t { struct s s; int o; } a = { (struct s){ 2 }, 3 };\n",
9678 "int n = (int){ 7 };\n",
9679 "struct u { struct s p; struct s q; } b = { (struct s){ 1 }, (struct s){ } };\n",
9680 ));
9681 assert!(text.contains("global @a : bytes 8 = { i32 2, i32 3 }"), "{text}");
9682 assert!(text.contains("global @n : i32 = 7,"), "{text}");
9683 assert!(text.contains("global @b : bytes 8 = { i32 1, zero 4 }"), "{text}");
9686 }
9687
9688 #[test]
9689 fn the_address_of_a_compound_literal_asks_for_the_object_it_points_at() {
9690 let text = ir("struct s { int x; };\nstruct s *q = &(struct s){ 9 };\n");
9694 assert!(text.contains("global @.Lanon.0 : i32 = 9, align 4, linkage(internal)"), "{text}");
9695 assert!(text.contains("global @q : bytes 8 = { addr.8 @.Lanon.0 }"), "{text}");
9696 }
9697
9698 #[test]
9699 fn an_object_of_no_size_at_all_has_an_image_with_nothing_in_it() {
9700 let text = ir("unsigned char foo[1][0];\n");
9704 assert!(text.contains("global @foo : bytes 0 = {}, align 1"), "{text}");
9705 }
9706
9707 #[test]
9708 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
9709 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
9712 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
9713 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
9714 }
9715
9716 #[test]
9717 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
9718 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
9722 assert!(
9723 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
9724 "{text}"
9725 );
9726 }
9727
9728 #[test]
9729 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
9730 let text = body(
9735 "\
9736struct s { int a, b; };
9737struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
9738",
9739 );
9740 assert!(text.contains("block3(%7: ptr)"), "{text}");
9742 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
9743 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
9744 }
9745
9746 #[test]
9754 fn the_left_side_of_a_conditional_with_no_middle_is_evaluated_once() {
9755 let text = body("int f(int i) { return ++i ?: 10; }\n");
9756 assert!(text.contains("jump block3(%2)"), "the arm is the value that was tested: {text}");
9757 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
9758
9759 let text = body("long f(int i) { return ++i ?: 10L; }\n");
9762 assert!(text.contains("%5 = sext.i64 %2"), "the arm widens what was tested: {text}");
9763 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
9764
9765 let text = body("int g(void);\nint f(void) { return g() ?: 10; }\n");
9767 assert_eq!(text.matches("call @g").count(), 1, "called once: {text}");
9768
9769 let text = body("int f(int i) { return ++i ? ++i : 10; }\n");
9772 assert_eq!(text.matches("add.nsw").count(), 2, "incremented twice: {text}");
9773 }
9774
9775 #[test]
9776 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
9777 let text = ir("\
9781struct pair { int a, b; };
9782struct pair make(int a, int b);
9783struct pair twice(struct pair p) { return make(p.a, p.b); }
9784");
9785 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
9786 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
9787 }
9788
9789 #[test]
9790 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
9791 let text = ir("\
9795struct big { double v[8]; };
9796struct big grow(struct big b);
9797struct big twice(struct big b) { return grow(grow(b)); }
9798");
9799 assert!(
9800 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
9801 "{text}"
9802 );
9803 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
9804 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
9807 }
9808
9809 #[test]
9810 fn a_structure_passed_to_a_variadic_function_says_so_at_the_call() {
9811 let text = ir("\
9816struct big { double v[8]; };
9817struct pair { int a, b; };
9818int p(const char *, ...);
9819int f(struct big b, struct pair q) { return p(\"\", 1, b, q); }
9820");
9821 assert!(
9822 text.contains("call @p(%4, %5, %2 byval(64, align 8), %6) : (ptr, ...) -> i32"),
9823 "{text}"
9824 );
9825 }
9826
9827 #[test]
9828 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
9829 let body = body(
9832 "\
9833struct pair { int a, b; };
9834struct pair make(int a, int b);
9835int second(void) { return make(1, 2).b; }
9836",
9837 );
9838 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
9839 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
9840 }
9841
9842 #[test]
9843 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
9844 let source = "\
9848struct hfa { float x, y, z; };
9849int take(struct hfa h);
9850int give(struct hfa h) { return take(h); }
9851";
9852 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
9853 let mut opts = options();
9854 opts.emit = EmitKind::Ir;
9855 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
9856 let result = run(&opts, source);
9857 assert_eq!(result.messages, Vec::<String>::new());
9858 assert!(result.text().contains("func @take(f32, f32, f32) -> i32"), "{}", result.text());
9859 }
9860
9861 #[test]
9862 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
9863 let source = "\
9866int use(int *);
9867void f(int n) {
9868 {
9869 int a[n];
9870 use(a);
9871 }
9872 use(0);
9873}
9874";
9875 let body = body(source);
9876 assert!(body.contains("mul.nsw"), "{body}");
9877 assert!(body.contains("stacksave"), "{body}");
9878 assert!(body.contains("alloca %"), "{body}");
9879 assert!(body.contains("stackrestore"), "{body}");
9880 }
9881
9882 #[test]
9883 fn a_goto_out_of_the_scope_of_one_gives_its_stack_back_on_the_way() {
9884 let source = "\
9889int use(int *);
9890int f(int n) {
9891 {
9892 int a[n];
9893 if (use(a)) goto out;
9894 use(0);
9895 }
9896out:
9897 return 0;
9898}
9899";
9900 let body = body(source);
9901 assert_eq!(body.matches("stackrestore").count(), 2, "{body}");
9903 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
9904 assert!(after.starts_with(" %4\n jump block"), "{body}");
9905 }
9906
9907 #[test]
9908 fn a_goto_to_a_label_the_array_is_still_alive_at_leaves_the_stack_alone() {
9909 let source = "\
9913int use(int *);
9914int f(int n) {
9915 int a[n];
9916again:
9917 if (use(a)) goto again;
9918 return 0;
9919}
9920";
9921 let body = body(source);
9922 assert!(body.contains("stacksave"), "{body}");
9923 assert!(!body.contains("stackrestore"), "{body}");
9924 }
9925
9926 #[test]
9927 fn a_goto_back_to_a_label_in_front_of_one_gives_it_back_every_time_round() {
9928 let source = "\
9933int use(int *);
9934int f(int n) {
9935again:
9936 {
9937 int a[n];
9938 if (use(a)) goto again;
9939 }
9940 return 0;
9941}
9942";
9943 let body = body(source);
9944 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
9945 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
9946 assert!(after.starts_with(" %4\n jump block1\n"), "{body}");
9947 }
9948
9949 #[test]
9950 fn the_head_of_a_for_loop_is_a_scope_that_closes_where_the_loop_is_left() {
9951 let source = "\
9957int f(void);
9958void t(void) {
9959 int count = 10;
9960 for (; count--;) {
9961 int b[f()];
9962 int i;
9963 for (i = 0; i < f(); i++) {
9964 b[i] = count;
9965 }
9966 }
9967}
9968";
9969 let body = body(source);
9970 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
9974 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
9975 let next = after.split("\n\n").next().expect("the block the restore is in");
9978 assert!(next.contains("jump block1("), "{body}");
9979 }
9980
9981 #[test]
9982 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
9983 let source = "\
9986unsigned long f(int n) {
9987 int a[n];
9988 n = 0;
9989 return sizeof a;
9990}
9991";
9992 let body = body(source);
9993 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
9995 }
9996
9997 #[test]
9998 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
9999 let source = "\
10002int use(int);
10003int f(int x) {
10004 return ({
10005 int t = use(x);
10006 t * t;
10007 });
10008}
10009";
10010 let expected = "\
10011block0(%0: i32):
10012 %1 = call @use(%0) : (i32) -> i32
10013 %2 = mul.nsw %1, %1
10014 return %2
10015";
10016 assert_eq!(body(source), expected);
10017 }
10018
10019 #[test]
10020 fn a_comma_whose_value_is_an_object_names_the_object_the_right_side_named() {
10021 let source = "\
10025struct pair { int a, b; };
10026void bail(void);
10027int f(struct pair p) {
10028 return (bail(), p).b;
10029}
10030";
10031 let expected = "\
10032block0(%0: i64):
10033 %1 = alloca, size 8, align 4
10034 store %0 -> %1, align 4
10035 call @bail() : ()
10036 %2 = iconst.i64 4
10037 %3 = ptr_add %1, %2
10038 %4 = load.i32 %3, align 4, tbaa !1
10039 return %4
10040";
10041 assert_eq!(body(source), expected);
10042 }
10043
10044 #[test]
10045 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
10046 let source = "int f(int x) { return ({ return x; 0; }); }\n";
10050 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
10051 }
10052
10053 #[test]
10054 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
10055 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
10059 let expected = "\
10060block0(%0: ptr):
10061 %1 = va_arg.f64 %0
10062 %2 = va_arg.f64 %0
10063 %3 = fadd %1, %2
10064 return %3
10065";
10066 assert_eq!(body(source), expected);
10067 }
10068
10069 #[test]
10070 fn one_that_reads_a_structure_answers_where_the_object_is() {
10071 let source = "\
10085struct s { int a; long b; };
10086long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.b; }
10087";
10088 let expected = "\
10089block0(%0: ptr):
10090 %1 = alloca, size 16, align 16
10091 %2 = va_object %0, size 16, align 8, in(int 8 at 0, int 8 at 8)
10092 memcpy %1, %2, size 16, align 8
10093 %3 = iconst.i64 8
10094 %4 = ptr_add %1, %3
10095 %5 = load.i64 %4, align 8, tbaa !1
10096 return %5
10097";
10098 assert_eq!(body(source), expected);
10099 }
10100
10101 #[test]
10105 fn the_classification_says_which_registers_the_object_arrived_in() {
10106 let source = "\
10107struct s { double a; double b; };
10108double f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a; }
10109";
10110 assert!(
10111 body(source)
10112 .contains("va_object %0, size 16, align 8, in(float f64 at 0, float f64 at 8)"),
10113 "{}",
10114 body(source)
10115 );
10116
10117 let big = "\
10118struct s { long a[4]; };
10119long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a[0]; }
10120";
10121 assert!(body(big).contains("va_object %0, size 32, align 8\n"), "{}", body(big));
10122 }
10123
10124 #[test]
10125 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
10126 let source = "\
10130int f(int c) {
10131 void *p = c ? &&one : &&two;
10132 goto *p;
10133one:
10134 return 1;
10135two:
10136 return 2;
10137}
10138";
10139 let expected = "\
10140block0(%0: i32):
10141 %1 = iconst.i32 0
10142 %2 = icmp ne %0, %1
10143 br_if %2, block1, block2
10144
10145block1:
10146 %3 = block_addr block3
10147 jump block4(%3)
10148
10149block2:
10150 %4 = block_addr block5
10151 jump block4(%4)
10152
10153block3:
10154 %5 = iconst.i32 1
10155 return %5
10156
10157block4(%6: ptr):
10158 indirect_br %6, block3, block5
10159
10160block5:
10161 %7 = iconst.i32 2
10162 return %7
10163";
10164 assert_eq!(body(source), expected);
10165 }
10166
10167 fn dispatch(labels: usize) -> String {
10170 let mask = labels - 1;
10171 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
10172 for index in 0..labels {
10173 source.push_str(&format!(" &&a{index},"));
10174 }
10175 source.push_str(" };\n\tint w = n, x = n + 1, y = n + 2, z = n + 3;\n");
10176 source.push_str(&format!("\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
10177 for index in 0..labels {
10178 let step = match index % 4 {
10179 0 => "w += x;",
10180 1 => "x += y;",
10181 2 => "y += z;",
10182 _ => "z += w;",
10183 };
10184 source.push_str(&format!("a{index}:\n\t{step}\n"));
10185 source.push_str("\tif (--n <= 0) return w + x + y + z;\n");
10186 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
10187 }
10188 source.push_str("}\n");
10189 source
10190 }
10191
10192 fn in_front_of_the_jump(text: &str) -> usize {
10194 let (before, _) = text.split_once("\tjmp\t*%").expect("a jump through a register");
10195 before.lines().rev().take_while(|line| line.starts_with("\tmov")).count()
10196 }
10197
10198 #[test]
10208 fn a_jump_through_a_register_writes_what_it_carries_and_not_the_whole_table() {
10209 let small = in_front_of_the_jump(&asm(&dispatch(4)));
10210 let large = in_front_of_the_jump(&asm(&dispatch(32)));
10211 assert_eq!(small, large, "eight times the labels and the same values in hand");
10212 assert!(large <= 8, "the values the loop keeps, and not a set of them per label: {large}");
10213 }
10214
10215 fn crowded(labels: usize) -> String {
10218 const VALUES: usize = 24;
10219 let mask = labels - 1;
10220 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
10221 for index in 0..labels {
10222 source.push_str(&format!(" &&a{index},"));
10223 }
10224 source.push_str(" };\n\t");
10225 for value in 0..VALUES {
10226 source.push_str(&format!("int v{value} = n + {value}; "));
10227 }
10228 let sum: Vec<String> = (0..VALUES).map(|value| format!("v{value}")).collect();
10229 source.push_str(&format!("\n\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
10230 for index in 0..labels {
10231 let (to, from) = (index % VALUES, (index + 1) % VALUES);
10232 source.push_str(&format!("a{index}:\n\tv{to} += v{from};\n"));
10233 source.push_str(&format!("\tif (--n <= 0) return {};\n", sum.join(" + ")));
10234 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
10235 }
10236 source.push_str("}\n");
10237 source
10238 }
10239
10240 fn the_frame(text: &str) -> u64 {
10242 text.lines()
10243 .find_map(|line| {
10244 let (size, _) = line.strip_prefix("\tsubq\t$")?.split_once(", %rsp")?;
10245 size.parse().ok()
10246 })
10247 .expect("a function that opens a frame")
10248 }
10249
10250 #[test]
10259 fn a_frame_holds_what_is_wanted_at_once_and_not_a_slot_for_every_label() {
10260 let small = the_frame(&asm(&crowded(16)));
10261 let large = the_frame(&asm(&crowded(64)));
10262 assert_eq!(small, large, "four times the labels and the same values: {small}, {large}");
10263 }
10264
10265 #[test]
10274 fn a_template_that_names_its_own_registers_gets_the_ones_it_named() {
10275 let source = "void save(void *nlr) {
10276 __asm volatile (
10277 \"movq %%rsp, 32(%%rdi) \\n\"
10278 \"movq %%rbx, 40(%%rdi) \\n\"
10279 \"movq %%r12, 48(%%rdi) \\n\"
10280 : : \"D\" (nlr) : \"memory\");
10281}
10282";
10283 let text = asm(source);
10284 assert!(text.contains("\tmovq\t%rsp, 32(%rdi)\n"), "{text}");
10285 assert!(text.contains("\tmovq\t%rbx, 40(%rdi)\n"), "{text}");
10286 assert!(text.contains("\tmovq\t%r12, 48(%rdi)\n"), "{text}");
10287 }
10288
10289 #[test]
10290 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
10291 let source = "void **next(void);
10294void f(void) { goto *next(); }
10295";
10296 let expected = "\
10297block0:
10298 %0 = call @next() : () -> ptr
10299 unreachable
10300";
10301 assert_eq!(body(source), expected);
10302 }
10303
10304 #[test]
10305 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
10306 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
10309 let expected = "\
10310block0:
10311 inline_asm.volatile \"mfence\", \"\", \"memory\"()
10312 return
10313";
10314 assert_eq!(body(source), expected);
10315 }
10316
10317 #[test]
10318 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
10319 let source = "\
10322int f(int x, int y) {
10323 int r;
10324 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
10325 return r + y;
10326}
10327";
10328 let expected = "\
10329block0(%0: i32, %1: i32):
10330 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
10331 %4 = add.nsw %2, %3
10332 return %4
10333";
10334 assert_eq!(body(source), expected);
10335 }
10336
10337 #[test]
10338 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
10339 let source = "\
10344struct pair { int a, b; };
10345int f(int x) {
10346 int slot = x;
10347 struct pair p = { x, x };
10348 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
10349 return slot + p.a;
10350}
10351";
10352 let text = body(source);
10353 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
10354 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
10355 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
10356 }
10357
10358 #[test]
10359 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
10360 let source = "\
10365int f(int x) {
10366 int r = 7;
10367 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
10368 return r;
10369away:
10370 return r;
10371}
10372";
10373 let expected = "\
10374block0(%0: i32):
10375 %1 = iconst.i32 7
10376 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
10377
10378block1:
10379 return %2
10380
10381block2:
10382 return %1
10383";
10384 assert_eq!(body(source), expected);
10385 }
10386
10387 #[test]
10388 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
10389 let mut opts = options();
10393 opts.emit = EmitKind::Ir;
10394 for (source, expected) in [
10395 (
10396 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
10397 "output operand constraint lacks '='",
10398 ),
10399 (
10400 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
10401 "lvalue required in 'asm' statement",
10402 ),
10403 (
10404 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
10405 "read-only variable 'g' used as 'asm' output",
10406 ),
10407 (
10408 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
10409 "input operand constraint contains '='",
10410 ),
10411 (
10412 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
10413 "memory input 0 is not directly addressable",
10414 ),
10415 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
10416 (
10417 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
10418 "duplicate asm operand name 'a'",
10419 ),
10420 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
10421 ] {
10422 let result = run(&opts, source);
10423 assert!(result.failed(), "expected this to be reported:\n{source}");
10424 assert!(
10425 result.messages.iter().any(|m| m.contains(expected)),
10426 "{expected}\n{:?}",
10427 result.messages
10428 );
10429 }
10430 }
10431
10432 #[test]
10437 fn an_asm_at_file_scope_that_is_directives_becomes_the_objects_it_defines() {
10438 let text = ir(concat!(
10439 "__asm__(\n",
10440 " \".section .rodata\\n\"\n",
10441 " \".globl first\\n\"\n",
10442 " \".balign 8\\n\"\n",
10443 " \"first:\\n\"\n",
10444 " \".long 1\\n\"\n",
10445 " \".long 2\\n\"\n",
10446 " \".globl last\\n\"\n",
10447 " \"last:\\n\"\n",
10448 " \".quad last - first\\n\");\n",
10449 "extern const int first[];\n",
10450 "extern const long last;\n",
10451 ));
10452 assert!(text.contains("global @first : bytes 8 = { i32 1, i32 2 }, align 8"), "{text}");
10453 assert!(text.contains("global @last : i64 = 8"), "{text}");
10454 }
10455
10456 #[test]
10460 fn a_name_an_asm_at_file_scope_defined_is_not_undone_by_a_declaration_of_it() {
10461 let text = ir(concat!(
10462 "__asm__(\".data\\n.globl counter\\ncounter:\\n.long 7\\n\");\n",
10463 "extern int counter;\n",
10464 "int read(void) { return counter; }\n",
10465 ));
10466 assert!(text.contains("global @counter : i32 = 7"), "{text}");
10467 }
10468
10469 #[test]
10474 fn bytes_under_no_label_at_file_scope_are_a_global_in_front_of_the_label() {
10475 let text = ir(concat!(
10476 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n662:\\n",
10477 ".pushsection .data.ignore\\n.byte 7\\n.popsection\\n.byte 662b - 661b\\n\");\n",
10478 "extern unsigned char stuff[];\n",
10479 "int read(void) { return stuff[0]; }\n",
10480 ));
10481 let under = text.find("global @.Lasm.0 : i8 = 41").expect(&text);
10482 let named = text.find("global @stuff : i8 = 42").expect(&text);
10483 assert!(under < named, "the bytes under no label come first: {text}");
10484 assert!(text.contains("global @.Lasm.1 : i8 = 7, align 1, linkage(internal), section"));
10485 let after = text.find("global @.Lasm.2 : i8 = 1").expect(&text);
10490 assert!(named < after, "{text}");
10491 }
10492
10493 #[test]
10498 fn a_distance_from_here_at_file_scope_is_a_hole_naming_the_global_it_measures_to() {
10499 let text = ir(concat!(
10500 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n",
10501 ".pushsection .data.ignore\\n.long 661b - .\\n.popsection\\n\");\n",
10502 "extern unsigned char stuff[];\n",
10503 "int read(void) { return stuff[0]; }\n",
10504 ));
10505 assert!(text.contains("global @.Lasm.1 : bytes 4 = { away.4 @stuff }"), "{text}");
10509 }
10510
10511 #[test]
10516 fn a_set_at_file_scope_is_a_second_name_for_what_it_names() {
10517 let text = ir(concat!(
10518 "void base(void) {}\n",
10519 "__asm__(\".weak one\\n.set one, base\");\n",
10520 "__asm__(\".globl two\\n.set two, base\");\n",
10521 "__asm__(\".set three, base\");\n",
10522 "void three(void) {}\n",
10523 ));
10524 assert!(text.contains("alias @one = @base, linkage(weak)"), "{text}");
10525 assert!(text.contains("alias @two = @base"), "{text}");
10526 assert!(!text.contains("alias @three"), "a definition of the name wins: {text}");
10527 assert!(text.contains("func @three"), "{text}");
10528 }
10529
10530 #[test]
10535 fn a_set_of_a_name_this_file_does_not_define_says_so() {
10536 let messages = errors("__asm__(\".set here, elsewhere\");\n");
10537 assert!(
10538 messages
10539 .iter()
10540 .any(|m| m.contains("'here' is aliased to undefined symbol 'elsewhere'")
10541 && m.contains("E0697")),
10542 "{messages:?}"
10543 );
10544 }
10545
10546 #[test]
10549 fn an_incbin_at_file_scope_is_the_bytes_of_the_file_it_names() {
10550 let mut opts = options();
10551 opts.emit = EmitKind::Ir;
10552 let mut fs = MemoryFileSystem::new();
10553 fs.insert(
10554 "/main.c",
10555 b"__asm__(\".data\\n.globl blob\\nblob:\\n.incbin \\\"seed\\\"\\n\");\n".to_vec(),
10556 );
10557 fs.insert("seed", b"hi".to_vec());
10558 let result = compile(&opts, "/main.c", &fs);
10559 assert_eq!(result.messages, Vec::<String>::new());
10560 let text = result.text();
10561 assert!(text.contains("global @blob : bytes 2 = { bytes \"hi\" }"), "{text}");
10562 }
10563
10564 #[test]
10567 fn an_incbin_naming_a_file_that_is_not_there_says_which_file() {
10568 let messages = errors("__asm__(\".data\\nb:\\n.incbin \\\"nowhere\\\"\\n\");\n");
10569 assert!(
10570 messages
10571 .iter()
10572 .any(|m| m.contains("cannot open 'nowhere' for reading") && m.contains("E0702")),
10573 "{messages:?}"
10574 );
10575 }
10576
10577 #[test]
10581 fn a_directive_in_an_asm_at_file_scope_is_refused_rather_than_ignored() {
10582 let source = "__asm__(\".data\\n.set alias, 4\\n\");\n";
10583 let messages = errors(source);
10584 assert!(
10585 messages
10586 .iter()
10587 .any(|m| m.contains("not supported yet") && m.contains("in an `asm` at file scope")),
10588 "{source}\n{messages:?}"
10589 );
10590 }
10591
10592 #[test]
10602 fn a_naked_function_is_its_own_prologue_and_its_own_ending() {
10603 let text = asm(concat!(
10604 "unsigned nlr_push_tail(void *nlr);\n",
10605 "__attribute__((naked)) unsigned nlr_push(void *nlr) {\n",
10606 " __asm volatile(\n",
10607 " \"movq (%rsp), %rax\\n\"\n",
10608 " \"movq %rax, 16(%rdi)\\n\"\n",
10609 " \"movq %rbx, 40(%rdi)\\n\"\n",
10610 " \"jmp nlr_push_tail\\n\");\n",
10611 "}\n",
10612 ));
10613 assert!(text.contains("\tmovq\t(%rsp), %rax\n"), "{text}");
10614 assert!(text.contains("\tjmp\tnlr_push_tail\n"), "{text}");
10615 assert!(text.contains("\tud2\n"), "{text}");
10616 assert!(!text.contains("\tpushq\t"), "nothing is saved in front of it: {text}");
10617 assert!(!text.contains("\tret\n"), "the jump is where it ends: {text}");
10618 }
10619
10620 #[test]
10623 fn what_a_function_without_a_prologue_cannot_be_given_is_refused() {
10624 let mut opts = options();
10625 opts.emit = EmitKind::Asm;
10626 for (source, why) in [
10627 (
10628 "__attribute__((naked)) void f(void) { volatile long a[8]; a[0] = 1; }\n",
10629 "bytes of frame",
10630 ),
10631 (
10632 "__attribute__((naked)) void f(int n) { char a[n]; __asm(\"nop\" ::\"r\"(a)); }\n",
10633 "has no prologue to point a frame pointer at it with",
10634 ),
10635 ("void elsewhere(void); void f(void) { __asm(\"jmp elsewhere\"); }\n", "jumps out of"),
10636 ] {
10637 let result = run(&opts, source);
10638 assert!(result.failed(), "expected this to be refused:\n{source}");
10639 assert!(
10640 result.messages.iter().any(|message| message.contains(why)),
10641 "{:?}",
10642 result.messages
10643 );
10644 }
10645 }
10646
10647 #[test]
10648 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
10649 let mut opts = options();
10650 opts.emit = EmitKind::Ir;
10651 for source in [
10652 "int f(int n) { void *p = &&out; if (n) goto *p; { int a[n]; out: return 1; } }\n",
10653 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
10654 ] {
10655 let result = run(&opts, source);
10656 assert!(result.failed(), "expected this to be reported:\n{source}");
10657 assert!(
10658 result.messages.iter().any(|m| m.contains("not supported yet")),
10659 "{:?}",
10660 result.messages
10661 );
10662 }
10663 }
10664
10665 fn round_trip(source: &str) -> (String, String) {
10667 let printed = ir(source);
10668 let mut opts = options();
10669 opts.emit = EmitKind::Ir;
10670 let mut fs = MemoryFileSystem::new();
10671 fs.insert("/main.ir", printed.clone().into_bytes());
10672 let result = compile_ir(&opts, "/main.ir", &fs);
10673 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
10674 (printed, result.text().to_owned())
10675 }
10676
10677 #[test]
10678 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
10679 let (printed, again) = round_trip(
10683 "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",
10684 );
10685 assert_eq!(printed, again);
10686 }
10687
10688 #[test]
10689 fn ir_that_is_not_ir_says_which_line_stopped_it() {
10690 let mut opts = options();
10691 opts.emit = EmitKind::Ir;
10692 let mut fs = MemoryFileSystem::new();
10693 let text = "\
10694; ModuleID = 'a.c'
10695; format 0
10696target triple = \"x86_64-unknown-linux-gnu\"
10697target datalayout = \"e-p:64:64-i64:64-S128\"
10698
10699func @f(), linkage(external) {
10700block0:
10701 frobnicate
10702}
10703";
10704 fs.insert("/main.ir", text.as_bytes().to_vec());
10705 let result = compile_ir(&opts, "/main.ir", &fs);
10706 assert!(result.failed());
10707 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
10708 }
10709
10710 #[test]
10711 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
10712 let mut opts = options();
10715 opts.emit = EmitKind::Ir;
10716 let mut fs = MemoryFileSystem::new();
10717 let text = "\
10718; ModuleID = 'a.c'
10719; format 0
10720target triple = \"x86_64-unknown-linux-gnu\"
10721target datalayout = \"e-p:64:64-i64:64-S128\"
10722
10723func @f(), linkage(external) {
10724block0:
10725 %0 = iconst.i32 1
10726 return %0
10727}
10728";
10729 fs.insert("/main.ir", text.as_bytes().to_vec());
10730 let result = compile_ir(&opts, "/main.ir", &fs);
10731 assert!(result.failed());
10732 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
10733 }
10734
10735 #[test]
10736 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
10737 let mut fs = MemoryFileSystem::new();
10739 fs.insert("/main.ir", Vec::new());
10740 let result = compile_ir(&options(), "/main.ir", &fs);
10741 assert!(result.failed());
10742 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
10743 }
10744
10745 #[test]
10746 fn the_printed_ir_reads_back_as_the_same_module() {
10747 let text = ir("\
10750struct point { int x, y; };
10751static const char greeting[] = \"hi\";
10752int table[4] = { 1, 2, 3 };
10753int puts(const char *);
10754double half(double x) { return x / 2.0; }
10755int f(int n) {
10756 int total = 0;
10757 for (int i = 0; i < n; i++) {
10758 if (i == 3) continue;
10759 total += table[i];
10760 }
10761 switch (n) {
10762 case 0: total = 1;
10763 case 1: total++; break;
10764 default: total = -total;
10765 }
10766 struct point p = { total, 1 };
10767 int *q = &p.y;
10768 puts(greeting);
10769 return p.x + *q;
10770}
10771int dispatch(int c) {
10772 void *p = c ? &&one : &&two;
10773 goto *p;
10774one:
10775 return 1;
10776two:
10777 return 2;
10778}
10779int assembly(int x, int *p) {
10780 int r;
10781 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
10782 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
10783 return r;
10784away:
10785 return 0;
10786}
10787");
10788 let mut names = Interner::new();
10789 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
10790 assert_eq!(rucc_ir::print(&module, &names), text);
10791 }
10792
10793 #[test]
10794 fn what_save_temps_keeps_is_the_text_that_was_compiled_and_the_assembly_that_was_assembled() {
10795 let mut opts = options();
10799 opts.emit = EmitKind::Object;
10800 opts.save_temps = rucc_session::SaveTemps::Object;
10801 let result = run(&opts, "#define N 2\nint a[N];\n");
10802 assert_eq!(result.messages, Vec::<String>::new());
10803 let text = result.temps.preprocessed.expect("the preprocessed text");
10804 assert!(text.contains("int a[2];"), "{text}");
10805 assert!(text.starts_with("# 1 \"/main.c\""), "{text}");
10806 let asm = result.temps.assembly.expect("the assembly");
10807 assert!(asm.contains("a:"), "{asm}");
10808 assert!(matches!(result.artifact, Artifact::Object { .. }), "{:?}", result.artifact);
10809 }
10810
10811 #[test]
10812 fn nothing_is_kept_unless_the_flag_asked_for_it() {
10813 let mut opts = options();
10816 opts.emit = EmitKind::Object;
10817 assert_eq!(run(&opts, "int a;\n").temps, Temps::default());
10818 }
10819
10820 #[test]
10821 fn a_compilation_that_stops_before_the_back_end_keeps_the_text_and_no_assembly() {
10822 let mut opts = options();
10825 opts.emit = EmitKind::Ir;
10826 opts.save_temps = rucc_session::SaveTemps::Cwd;
10827 let result = run(&opts, "int a;\n");
10828 assert!(result.temps.preprocessed.is_some());
10829 assert_eq!(result.temps.assembly, None);
10830 }
10831
10832 fn span(from: u64, len: u64, held: rucc_debug::Held) -> rucc_debug::Span {
10834 rucc_debug::Span { from, len, held }
10835 }
10836
10837 #[test]
10838 fn two_stretches_that_meet_and_agree_come_out_as_one() {
10839 let one = span(0, 4, rucc_debug::Held::Reg(3));
10840 let two = span(4, 4, rucc_debug::Held::Reg(3));
10841 assert_eq!(settle(vec![two, one]), vec![span(0, 8, rucc_debug::Held::Reg(3))]);
10842 }
10843
10844 #[test]
10845 fn a_stretch_another_starts_inside_and_disagrees_with_ends_where_the_other_starts() {
10846 let one = span(0, 8, rucc_debug::Held::Reg(3));
10847 let two = span(4, 8, rucc_debug::Held::Reg(4));
10848 let settled = settle(vec![one, two]);
10851 assert_eq!(
10852 settled,
10853 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 8, rucc_debug::Held::Reg(4))]
10854 );
10855 }
10856
10857 #[test]
10858 fn a_stretch_cut_by_one_that_ends_first_does_not_come_back_after_it() {
10859 let one = span(0, 16, rucc_debug::Held::Reg(3));
10862 let two = span(4, 4, rucc_debug::Held::Reg(4));
10863 assert_eq!(
10864 settle(vec![one, two]),
10865 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 4, rucc_debug::Held::Reg(4))]
10866 );
10867 }
10868
10869 #[test]
10870 fn a_stretch_inside_another_that_agrees_with_it_cuts_nothing() {
10871 let one = span(0, 16, rucc_debug::Held::Reg(3));
10872 let two = span(4, 4, rucc_debug::Held::Reg(3));
10873 assert_eq!(settle(vec![one, two]), vec![span(0, 16, rucc_debug::Held::Reg(3))]);
10874 }
10875
10876 #[test]
10877 fn a_stretch_two_others_disagree_over_the_whole_of_says_nothing_at_all() {
10878 let one = span(0, 8, rucc_debug::Held::Reg(3));
10879 let two = span(0, 8, rucc_debug::Held::Frame(-16));
10880 assert_eq!(settle(vec![one, two]), Vec::new());
10881 }
10882
10883 #[test]
10884 fn stretches_with_a_gap_between_them_keep_the_gap() {
10885 let one = span(0, 4, rucc_debug::Held::Reg(3));
10886 let two = span(16, 4, rucc_debug::Held::Reg(3));
10887 assert_eq!(settle(vec![one, two]), vec![one, two]);
10888 }
10889
10890 fn extent(len: usize) -> rucc_object::Extent {
10892 rucc_object::Extent {
10893 name: "f".to_owned(),
10894 start: 0,
10895 len,
10896 align: 1,
10897 binding: rucc_object::Binding::Global,
10898 visibility: rucc_object::Visibility::Default,
10899 patch: None,
10900 landings: Vec::new(),
10901 }
10902 }
10903
10904 fn row(at: usize, lo: u32, hi: u32) -> rucc_asm::Row {
10906 let span = Span::new(lo, hi);
10907 rucc_asm::Row { at, span, inst: None }
10908 }
10909
10910 #[test]
10911 fn a_row_ends_where_the_next_address_begins() {
10912 let rows = [row(0, 0, 1), row(4, 1, 2), row(10, 2, 3)];
10913 assert_eq!(ends(&extent(16), &rows), vec![4, 10, 16]);
10914 }
10915
10916 #[test]
10917 fn rows_sharing_an_address_all_end_where_the_next_address_begins() {
10918 let rows = [row(0, 0, 1), row(4, 1, 2), row(4, 2, 3), row(4, 3, 4)];
10921 assert_eq!(ends(&extent(12), &rows), vec![4, 12, 12, 12]);
10922 }
10923
10924 #[test]
10925 fn the_rows_of_a_scope_that_are_next_to_each_other_come_out_as_one_stretch() {
10926 let rows = [row(0, 0, 4), row(4, 10, 14), row(8, 14, 18), row(12, 40, 44)];
10927 let ends = ends(&extent(16), &rows);
10928 let scope = Span::new(8, 20);
10929 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 8 }]);
10930 }
10931
10932 #[test]
10933 fn a_scope_the_back_end_split_in_two_comes_out_as_two_stretches() {
10934 let rows = [row(0, 10, 14), row(4, 40, 44), row(8, 14, 18)];
10935 let ends = ends(&extent(12), &rows);
10936 let scope = Span::new(8, 20);
10937 let over = spread(scope, &ends, &rows);
10938 assert_eq!(
10939 over,
10940 vec![rucc_debug::Reach { from: 0, len: 4 }, rucc_debug::Reach { from: 8, len: 4 }]
10941 );
10942 }
10943
10944 #[test]
10945 fn a_row_with_no_source_of_its_own_belongs_to_no_scope() {
10946 let rows = [rucc_asm::Row { at: 0, span: Span::DUMMY, inst: None }, row(4, 10, 14)];
10948 let ends = ends(&extent(8), &rows);
10949 let scope = Span::new(0, 20);
10950 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 4 }]);
10951 }
10952
10953 fn scope(parent: Option<usize>, lo: u32, hi: u32) -> crate::shapes::Scope {
10955 let span = Span::new(lo, hi);
10956 crate::shapes::Scope { parent, span }
10957 }
10958
10959 #[test]
10960 fn a_function_gets_the_scopes_its_own_locals_are_in_and_nothing_else() {
10961 let scopes = [scope(None, 0, 10), scope(None, 20, 30), scope(Some(1), 22, 26)];
10963 let rows = [row(0, 22, 24), row(4, 26, 28)];
10964 let (out, at) = nests(&[Some(2)], &scopes, &extent(8), &rows);
10965 assert_eq!(at.get(&1), Some(&0));
10968 assert_eq!(at.get(&2), Some(&1));
10969 assert_eq!(at.get(&0), None);
10970 assert_eq!(out.len(), 2);
10971 assert_eq!(out[0].parent, None);
10972 assert_eq!(out[1].parent, Some(0));
10973 assert_eq!(out[0].over, vec![rucc_debug::Reach { from: 0, len: 8 }]);
10974 assert_eq!(out[1].over, vec![rucc_debug::Reach { from: 0, len: 4 }]);
10975 }
10976
10977 #[test]
10978 fn a_local_written_straight_into_the_body_pulls_no_scope_in() {
10979 let scopes = [scope(None, 20, 30)];
10980 let rows = [row(0, 22, 24)];
10981 let (out, at) = nests(&[None], &scopes, &extent(4), &rows);
10982 assert_eq!(out, Vec::new());
10983 assert!(at.is_empty());
10984 }
10985
10986 #[test]
10987 fn a_scope_whose_code_all_went_away_is_still_one_of_the_functions_scopes() {
10988 let scopes = [scope(None, 20, 30)];
10992 let rows = [row(0, 40, 44)];
10993 let (out, at) = nests(&[Some(0)], &scopes, &extent(4), &rows);
10994 assert_eq!(at.get(&0), Some(&0));
10995 assert_eq!(out.len(), 1);
10996 assert_eq!(out[0].over, Vec::new());
10997 }
10998}