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 (globals, rucc_asm::aliases(module, names).map_err(refused)?)
1095 }
1096 _ => (rucc_asm::Globals::default(), Vec::new()),
1097 };
1098 let unwind = opts.unwinds();
1102 match opts.emit {
1103 EmitKind::Asm => {
1104 rucc_asm::print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
1105 .map(Artifact::Text)
1106 .map_err(refused)
1107 }
1108 EmitKind::Object | EmitKind::Archive | EmitKind::Executable => {
1112 if opts.save_temps.wanted() {
1113 let listing = rucc_asm::print(
1114 &funcs,
1115 &globals,
1116 &aliases,
1117 names,
1118 target,
1119 unwind,
1120 output(opts, target),
1121 );
1122 *assembly = Some(listing.map_err(refused)?);
1123 }
1124 let aarch64 = target.tuple.arch() == Arch::Aarch64;
1135 if aarch64 || globals.kept() || rucc_asm::kept(&funcs, names, target) {
1136 let print = if opts.debug_info { rucc_asm::print_marked } else { rucc_asm::print };
1140 let listing =
1141 print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
1142 .map_err(refused)?;
1143 let arch = target.tuple.arch();
1144 let read =
1145 rucc_asm::read_as(&listing, arch, target.object_format).map_err(|trouble| {
1146 let what = if aarch64 {
1147 "a unit for aarch64"
1148 } else if globals.kept() {
1149 "an `asm` at file scope"
1150 } else {
1151 "an `asm` template kept as text"
1152 };
1153 vec![unsupported(&format!(
1154 "{what}, whose listing the assembler stopped at on line {}: {}",
1155 trouble.line, trouble.why
1156 ))]
1157 })?;
1158 let info = if opts.debug_info {
1159 let assembled =
1160 placed(&read, &funcs, names, target).map_err(|why| vec![internal(&why)])?;
1161 describe(&assembled, &globals.image(), &funcs, origin, opts, target)
1162 .map_err(|why| vec![internal(&why)])?
1163 } else {
1164 rucc_object::Info::default()
1165 };
1166 let defines = rucc_object::assembled_defines(&read);
1167 let bytes =
1168 rucc_object::assembled_described(&read, target, &info).map_err(wrote)?;
1169 return Ok(Artifact::Object { bytes, defines });
1170 }
1171 let assembled = rucc_asm::assemble(&funcs, names, target, unwind, opts.debug_info)
1172 .map_err(refused)?;
1173 let data = globals.image();
1174 let info = if opts.debug_info {
1178 describe(&assembled, &data, &funcs, origin, opts, target)
1179 .map_err(|why| vec![internal(&why)])?
1180 } else {
1181 rucc_object::Info::default()
1182 };
1183 let text = assembled.text;
1184 let bytes =
1187 rucc_object::write(&text, &data, &aliases, target, output(opts, target), &info)
1188 .map_err(wrote)?;
1189 let defines = rucc_object::defines(&text, &data, &aliases, target).map_err(wrote)?;
1194 Ok(Artifact::Object { bytes, defines })
1195 }
1196 _ => Ok(Artifact::Text(rucc_mir::print(&funcs, names, target.regs))),
1197 }
1198}
1199
1200fn placed(
1212 read: &rucc_object::Assembled,
1213 funcs: &[rucc_mir::Func],
1214 names: &Interner,
1215 target: &TargetInfo,
1216) -> Result<rucc_asm::Assembled, String> {
1217 let at: HashMap<&str, &rucc_object::Name> =
1218 read.names.iter().map(|name| (name.name.as_str(), name)).collect();
1219 let offset = |name: &str| match at.get(name).map(|name| name.at) {
1220 Some(rucc_object::Held::In { part, offset }) => Some((part, offset)),
1221 _ => None,
1222 };
1223 let mut text = rucc_object::Text::default();
1224 let mut lines = Vec::with_capacity(funcs.len());
1225 let symbol = rucc_asm::Directives::of(target.object_format).symbol();
1229 for (which, func) in funcs.iter().enumerate() {
1230 let name = names.resolve(func.name);
1231 let Some((part, start)) = offset(&format!("{symbol}{name}")) else {
1232 return Err(format!("the listing has no label for the function '{name}'"));
1233 };
1234 let mut rows = Vec::with_capacity(func.inst_count() + 1);
1235 if !func.declared.is_dummy() {
1236 rows.push(rucc_asm::Row { at: 0, span: func.declared, inst: None });
1237 }
1238 for block in func.blocks() {
1239 for inst in func.insts(block) {
1240 let label = rucc_asm::mark(target, which, inst);
1241 let Some((held, here)) = offset(&label) else {
1242 return Err(format!("the listing has no label '{label}'"));
1243 };
1244 if held != part || here < start {
1245 return Err(format!("the label '{label}' is not inside '{name}'"));
1246 }
1247 let at = usize::try_from(here - start).map_err(|why| why.to_string())?;
1248 rows.push(rucc_asm::Row { at, span: func.span(inst), inst: Some(inst) });
1249 }
1250 }
1251 let size = at.get(format!("{symbol}{name}").as_str()).map_or(0, |name| name.size);
1254 let len = match offset(&rucc_asm::mark_end(target, which)) {
1255 Some((held, end)) if size == 0 && held == part && end >= start => end - start,
1256 _ => size,
1257 };
1258 text.funcs.push(rucc_object::Extent {
1259 name: name.to_owned(),
1260 start: usize::try_from(start).map_err(|why| why.to_string())?,
1261 len: usize::try_from(len).map_err(|why| why.to_string())?,
1262 align: func.align.unwrap_or(rucc_object::FUNC_ALIGN),
1263 binding: rucc_object::Binding::Global,
1264 visibility: rucc_object::Visibility::Default,
1265 patch: None,
1266 landings: Vec::new(),
1267 });
1268 lines.push(rows);
1269 }
1270 Ok(rucc_asm::Assembled { text, lines, frames: None })
1271}
1272
1273fn describe(
1292 assembled: &rucc_asm::Assembled,
1293 data: &rucc_object::Data,
1294 machine: &[rucc_mir::Func],
1295 origin: Origin<'_>,
1296 opts: &Options,
1297 target: &TargetInfo,
1298) -> Result<rucc_object::Info, String> {
1299 let rucc_asm::Assembled { text, lines, frames } = assembled;
1300 let rewrite = |path: &str| opts.prefix_map.debug.apply(path).into_owned();
1301 let mut files: Vec<String> = Vec::new();
1306 let mut funcs = Vec::with_capacity(text.funcs.len());
1307 for ((extent, rows), built) in text.funcs.iter().zip(lines).zip(machine) {
1308 let mut out: Vec<rucc_debug::Row> = Vec::with_capacity(rows.len());
1309 for row in rows {
1310 if row.span.is_dummy() {
1311 continue;
1312 }
1313 let Some(at) = origin.map.presumed(row.span.lo) else {
1314 continue;
1315 };
1316 let which = interned(&mut files, rewrite(at.name));
1317 let place = rucc_debug::Row {
1318 at: row.at as u64,
1319 file: which,
1320 line: at.line,
1321 column: at.column,
1322 };
1323 match out.last() {
1333 Some(last) if last.at == place.at => {}
1334 _ => out.push(place),
1335 }
1336 }
1337 if let Some(first) = out.first_mut() {
1344 first.at = 0;
1345 }
1346 let known = origin.meaning.funcs.get(&extent.name);
1352 let decl = known.map(|known| rucc_debug::Place {
1353 file: interned(&mut files, rewrite(&known.file)),
1354 line: known.line,
1355 });
1356 let mut sig = known.and_then(|known| known.sig.clone());
1365 let mut placed: Vec<(u32, i32)> = built.locals.clone();
1366 let mut spots = stretches(extent, rows, built, target);
1367 for &decl in &built.sharing {
1372 if !spots.iter().any(|(at, _)| *at == decl) {
1373 spots.push((decl, Vec::new()));
1374 }
1375 }
1376 if let (Some(sig), Some(known)) = (sig.as_mut(), known) {
1377 for (param, decl) in sig.params.iter_mut().zip(&known.params) {
1378 let Some(decl) = *decl else { continue };
1379 if let Some(which) = placed.iter().position(|&(at, _)| at == decl) {
1380 let at = rucc_debug::Held::Frame(i64::from(placed.remove(which).1));
1381 param.spot = Some(rucc_debug::Spot::Always(at));
1382 continue;
1383 }
1384 let Some(which) = spots.iter().position(|(at, _)| *at == decl) else { continue };
1388 param.spot = Some(rucc_debug::Spot::Over(spots.remove(which).1));
1389 }
1390 }
1391 let mut locals = Vec::with_capacity(placed.len() + spots.len());
1395 let mut wants: Vec<Option<usize>> = Vec::with_capacity(locals.capacity());
1399 for (decl, at) in placed {
1400 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1401 wants.push(named.scope);
1402 locals.push(rucc_debug::Local {
1403 name: named.name.clone(),
1404 ty: named.ty,
1405 decl: Some(rucc_debug::Place {
1406 file: interned(&mut files, rewrite(&named.file)),
1407 line: named.line,
1408 }),
1409 spot: rucc_debug::Spot::Always(rucc_debug::Held::Frame(i64::from(at))),
1410 scope: None,
1411 });
1412 }
1413 spots.sort_by_key(|(decl, _)| *decl);
1417 for (decl, spans) in spots {
1418 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1419 wants.push(named.scope);
1420 locals.push(rucc_debug::Local {
1421 name: named.name.clone(),
1422 ty: named.ty,
1423 decl: Some(rucc_debug::Place {
1424 file: interned(&mut files, rewrite(&named.file)),
1425 line: named.line,
1426 }),
1427 spot: rucc_debug::Spot::Over(spans),
1428 scope: None,
1429 });
1430 }
1431 let (scopes, at) = nests(&wants, &origin.meaning.scopes, extent, rows);
1436 for (local, want) in locals.iter_mut().zip(&wants) {
1437 local.scope = want.and_then(|want| at.get(&want).copied());
1438 }
1439 funcs.push(rucc_debug::Function {
1440 name: extent.name.clone(),
1441 len: extent.len as u64,
1442 rows: out,
1443 decl,
1444 sig,
1445 external: known.is_some_and(|known| known.external),
1446 locals,
1447 scopes,
1448 });
1449 }
1450 let mut globals = Vec::new();
1457 for object in &data.objects {
1458 let Some(held) = origin.meaning.objects.get(&object.name) else { continue };
1459 globals.push(rucc_debug::Global {
1460 name: object.name.clone(),
1461 ty: held.ty,
1462 decl: Some(rucc_debug::Place {
1463 file: interned(&mut files, rewrite(&held.file)),
1464 line: held.line,
1465 }),
1466 external: held.external,
1467 });
1468 }
1469 let unit = rucc_debug::Unit {
1470 name: rewrite(origin.name),
1471 dir: rewrite(opts.working_dir.as_deref().unwrap_or(".")),
1474 producer: format!("rucc {}", crate::VERSION),
1475 files,
1476 types: origin.meaning.types.clone(),
1477 funcs,
1478 globals,
1479 pointer: u8::try_from(target.pointer_width / 8).unwrap_or(8),
1480 frames: opts.unwinds() || frames.is_some(),
1485 mach_o: target.object_format == rucc_target::ObjectFormat::MachO,
1486 };
1487 let mut info = rucc_debug::write(&unit).map_err(|why| why.to_string())?;
1488 info.chunks.extend(frames.clone());
1489 Ok(info)
1490}
1491
1492fn stretches(
1503 extent: &rucc_object::Extent,
1504 rows: &[rucc_asm::Row],
1505 built: &rucc_mir::Func,
1506 target: &TargetInfo,
1507) -> Vec<(u32, Vec<rucc_debug::Span>)> {
1508 let (false, Some(regs)) = (built.kept.is_empty(), target.call_regs) else {
1511 return Vec::new();
1512 };
1513 let ends = ends(extent, rows);
1514 let mut bounds = vec![None; built.inst_count()];
1515 for (which, row) in rows.iter().enumerate() {
1516 let Some(inst) = row.inst else { continue };
1517 bounds[inst.index()] = Some((row.at as u64, ends[which]));
1518 }
1519 let mut spots: Vec<(u32, Vec<rucc_debug::Span>)> = Vec::new();
1520 for kept in &built.kept {
1521 let (Some((from, _)), Some((_, to))) = (bounds[kept.from.index()], bounds[kept.to.index()])
1522 else {
1523 continue;
1524 };
1525 if to <= from {
1526 continue;
1527 }
1528 let held = match kept.at {
1529 rucc_mir::Where::Reg { reg, class } => match regs.dwarf(class, reg) {
1532 Some(number) => rucc_debug::Held::Reg(number),
1533 None => continue,
1534 },
1535 rucc_mir::Where::Frame(at) => rucc_debug::Held::Frame(i64::from(at)),
1536 };
1537 let span = rucc_debug::Span { from, len: to - from, held };
1538 match spots.iter_mut().find(|(decl, _)| *decl == kept.decl) {
1539 Some((_, spans)) => spans.push(span),
1540 None => spots.push((kept.decl, vec![span])),
1541 }
1542 }
1543 for (_, spans) in &mut spots {
1544 *spans = settle(std::mem::take(spans));
1545 }
1546 spots.retain(|(_, spans)| !spans.is_empty());
1547 spots
1548}
1549
1550fn ends(extent: &rucc_object::Extent, rows: &[rucc_asm::Row]) -> Vec<u64> {
1560 let mut out = vec![extent.len as u64; rows.len()];
1561 let mut next = extent.len as u64;
1562 for which in (0..rows.len()).rev() {
1563 let at = rows[which].at as u64;
1564 out[which] = match next > at {
1567 true => next,
1568 false => out.get(which + 1).copied().unwrap_or(extent.len as u64),
1569 };
1570 next = next.min(at);
1571 }
1572 out
1573}
1574
1575fn nests(
1591 wants: &[Option<usize>],
1592 scopes: &[crate::shapes::Scope],
1593 extent: &rucc_object::Extent,
1594 rows: &[rucc_asm::Row],
1595) -> (Vec<rucc_debug::Scope>, HashMap<usize, usize>) {
1596 let mut needed: Vec<usize> = Vec::new();
1597 for &want in wants {
1598 let mut up = want;
1599 while let Some(which) = up {
1600 if needed.contains(&which) {
1601 break;
1602 }
1603 needed.push(which);
1604 up = scopes.get(which).and_then(|scope| scope.parent);
1605 }
1606 }
1607 needed.sort_unstable();
1610 let at: HashMap<usize, usize> =
1611 needed.iter().enumerate().map(|(which, &scope)| (scope, which)).collect();
1612 let ends = ends(extent, rows);
1613 let out = needed
1614 .iter()
1615 .map(|&which| {
1616 let scope = &scopes[which];
1617 rucc_debug::Scope {
1618 parent: scope.parent.and_then(|parent| at.get(&parent).copied()),
1619 over: spread(scope.span, &ends, rows),
1620 }
1621 })
1622 .collect();
1623 (out, at)
1624}
1625
1626fn spread(span: Span, ends: &[u64], rows: &[rucc_asm::Row]) -> Vec<rucc_debug::Reach> {
1634 let mut out: Vec<rucc_debug::Reach> = Vec::new();
1635 for (which, row) in rows.iter().enumerate() {
1636 if row.span.is_dummy() || row.span.lo < span.lo || row.span.hi > span.hi {
1637 continue;
1638 }
1639 let (from, to) = (row.at as u64, ends[which]);
1640 if to <= from {
1641 continue;
1642 }
1643 match out.last_mut() {
1644 Some(last) if last.from + last.len >= from => {
1645 last.len = to.saturating_sub(last.from).max(last.len);
1646 }
1647 _ => out.push(rucc_debug::Reach { from, len: to - from }),
1648 }
1649 }
1650 out
1651}
1652
1653fn settle(mut spans: Vec<rucc_debug::Span>) -> Vec<rucc_debug::Span> {
1670 spans.sort_by_key(|span| (span.from, span.len));
1671 for which in 0..spans.len() {
1672 let (from, end, held) =
1673 (spans[which].from, spans[which].from + spans[which].len, spans[which].held);
1674 let later = spans[which + 1..]
1675 .iter()
1676 .take_while(|later| later.from < end)
1677 .find(|later| later.from > from && later.held != held);
1678 if let Some(later) = later {
1679 spans[which].len = later.from - from;
1680 }
1681 }
1682 let mut edges: Vec<u64> =
1685 spans.iter().flat_map(|span| [span.from, span.from + span.len]).collect();
1686 edges.sort_unstable();
1687 edges.dedup();
1688 let mut out: Vec<rucc_debug::Span> = Vec::new();
1689 let mut first = 0;
1690 for pair in edges.windows(2) {
1691 let (from, to) = (pair[0], pair[1]);
1692 while spans.get(first).is_some_and(|span| span.from + span.len <= from) {
1696 first += 1;
1697 }
1698 let mut held = None;
1699 let mut agreed = true;
1700 for span in &spans[first..] {
1701 if span.from >= to {
1702 break;
1703 }
1704 if span.from > from || span.from + span.len < to {
1705 continue;
1706 }
1707 match held {
1708 None => held = Some(span.held),
1709 Some(seen) => agreed &= seen == span.held,
1710 }
1711 }
1712 let (Some(held), true) = (held, agreed) else { continue };
1713 match out.last_mut() {
1714 Some(last) if last.from + last.len == from && last.held == held => {
1715 last.len += to - from
1716 }
1717 _ => out.push(rucc_debug::Span { from, len: to - from, held }),
1718 }
1719 }
1720 out
1721}
1722
1723fn interned(files: &mut Vec<String>, name: String) -> usize {
1729 match files.iter().position(|have| *have == name) {
1730 Some(which) => which,
1731 None => {
1732 files.push(name);
1733 files.len() - 1
1734 }
1735 }
1736}
1737
1738fn output(opts: &Options, target: &TargetInfo) -> rucc_object::Output {
1750 let mut features = 0;
1751 if target.tuple.arch() == Arch::X86_64 {
1752 if opts.control.branch() {
1753 features |= rucc_object::Property::IBT;
1754 }
1755 if opts.control.ret() {
1756 features |= rucc_object::Property::SHSTK;
1757 }
1758 }
1759 rucc_object::Output {
1760 sections: rucc_object::Sections {
1761 functions: opts.function_sections,
1762 data: opts.data_sections,
1763 },
1764 property: rucc_object::Property { features },
1765 }
1766}
1767
1768fn wrote(why: rucc_object::Error) -> Vec<Diagnostic> {
1774 match why {
1775 rucc_object::Error::Format { .. } => vec![unsupported(&why.to_string())],
1776 rucc_object::Error::Refused { .. } => vec![internal(&why.to_string())],
1777 }
1778}
1779
1780fn refused(why: rucc_asm::Error) -> Vec<Diagnostic> {
1787 match why {
1788 rucc_asm::Error::Thread { .. }
1789 | rucc_asm::Error::IFunc { .. }
1790 | rucc_asm::Error::Frame { .. } => {
1791 vec![unsupported(&why.to_string())]
1792 }
1793 _ => vec![internal(&why.to_string())],
1794 }
1795}
1796
1797fn unsupported(message: &str) -> Diagnostic {
1803 unsupported_at(message, Span::DUMMY)
1804}
1805
1806fn unsupported_at(message: &str, span: Span) -> Diagnostic {
1812 Diagnostic::error(message.to_owned(), span)
1813 .with_code("E0653")
1814 .note("this construct is not lowered yet, see https://github.com/tamnd/rucc/issues", span)
1815}
1816
1817fn invalid(message: &str) -> Diagnostic {
1819 Diagnostic::error(message.to_owned(), Span::DUMMY).with_code("E0661")
1820}
1821
1822fn internal(message: &str) -> Diagnostic {
1824 Diagnostic::error(format!("internal error: {message}"), Span::DUMMY)
1825 .with_code("E0652")
1826 .note("this is a bug in rucc rather than in the program, please report it", Span::DUMMY)
1827}
1828
1829fn su_file(stack: &StackUsage, sources: &SourceMap, file: &str) -> String {
1836 let mut out = String::new();
1837 for row in stack.rows() {
1838 let span = row.span();
1839 let at = (!span.is_dummy()).then(|| sources.presumed(span.lo)).flatten();
1840 let (name, line, column) = at.map_or((file, 0, 0), |at| (at.name, at.line, at.column));
1841 out.push_str(&row.line(name, line, column));
1842 }
1843 out
1844}
1845
1846fn failure(message: String) -> Compiled {
1849 Compiled {
1850 artifact: Artifact::Nothing,
1851 messages: vec![format!("rucc: error: {message}")],
1852 errors: 1,
1853 fired: Fired::new(),
1854 pressure: Pressure::new(),
1855 lowerings: Lowerings::new(),
1856 dumps: Vec::new(),
1857 remarks: String::new(),
1858 deps: Vec::new(),
1859 temps: Temps::default(),
1860 timing: crate::trace::Timing::default(),
1861 stack_usage: String::new(),
1862 }
1863}
1864
1865#[cfg(test)]
1866mod tests {
1867 use rucc_session::{MemoryFileSystem, Std};
1868 use rucc_target::Triple;
1869
1870 use super::*;
1871
1872 fn options() -> Options {
1873 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1874 opts.emit = EmitKind::Tast;
1875 opts.frame_pointer = Some(false);
1878 opts
1879 }
1880
1881 fn run(opts: &Options, source: &str) -> Compiled {
1882 let mut fs = MemoryFileSystem::new();
1883 fs.insert("/main.c", source.to_owned().into_bytes());
1884 compile(opts, "/main.c", &fs)
1885 }
1886
1887 fn freestanding() -> Options {
1891 let mut opts = options();
1892 opts.hosted = false;
1893 opts.search.push_system(rucc_session::runtime::DIR);
1894 opts
1895 }
1896
1897 fn shipped(source: &str) -> String {
1899 let result = run(&freestanding(), source);
1900 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1901 result.text().to_owned()
1902 }
1903
1904 fn tast(source: &str) -> String {
1906 let result = run(&options(), source);
1907 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1908 result.text().to_owned()
1909 }
1910
1911 #[test]
1912 fn the_shipped_stdarg_declares_a_list_and_the_four_operators() {
1913 let text = shipped(concat!(
1914 "#include <stdarg.h>\n",
1915 "int sum(int n, ...) {\n",
1916 " va_list ap, copy;\n",
1917 " va_start(ap, n);\n",
1918 " va_copy(copy, ap);\n",
1919 " int total = va_arg(ap, int) + va_arg(copy, int);\n",
1920 " va_end(ap);\n",
1921 " va_end(copy);\n",
1922 " return total;\n",
1923 "}\n",
1924 ));
1925 assert!(text.contains("va-start"), "{text}");
1926 assert!(text.contains("va-copy"), "{text}");
1927 assert!(text.contains("va-arg"), "{text}");
1928 assert!(text.contains("va-end"), "{text}");
1929 }
1930
1931 #[test]
1935 fn stdarg_hands_out_the_type_alone_when_that_is_all_that_was_asked_for() {
1936 let text = shipped(concat!(
1937 "#define __need___va_list\n",
1938 "#include <stdarg.h>\n",
1939 "int vprint(const char *f, __gnuc_va_list ap);\n",
1940 "#ifdef va_start\n",
1941 "#error va_start should not be defined\n",
1942 "#endif\n",
1943 "#ifdef _VA_LIST_DEFINED\n",
1944 "#error va_list should not have been made\n",
1945 "#endif\n",
1946 ));
1947 assert!(text.contains("vprint"), "{text}");
1948 }
1949
1950 #[test]
1953 fn stddef_answers_one_piece_at_a_time_and_the_next_request_still_gets_through() {
1954 let text = shipped(concat!(
1955 "#define __need_size_t\n",
1956 "#include <stddef.h>\n",
1957 "#ifdef offsetof\n",
1958 "#error offsetof should not be defined yet\n",
1959 "#endif\n",
1960 "#define __need_ptrdiff_t\n",
1961 "#include <stddef.h>\n",
1962 "#include <stddef.h>\n",
1963 "size_t a;\n",
1964 "ptrdiff_t b;\n",
1965 "wchar_t c;\n",
1966 "max_align_t d;\n",
1967 "void *e = NULL;\n",
1968 "struct P { int x; long y; };\n",
1969 "size_t f = offsetof(struct P, y);\n",
1970 ));
1971 assert!(text.contains("decl #0 a : unsigned long"), "{text}");
1972 assert!(text.contains("decl #1 b : long"), "{text}");
1973 }
1974
1975 #[test]
1976 fn the_shipped_limits_and_float_are_the_targets_own_answers() {
1977 let text = shipped(concat!(
1978 "#include <limits.h>\n",
1979 "#include <float.h>\n",
1980 "int bits = CHAR_BIT;\n",
1981 "long big = LONG_MAX;\n",
1982 "int low = INT_MIN;\n",
1983 "int radix = FLT_RADIX;\n",
1984 "int digits = DBL_MANT_DIG;\n",
1985 ));
1986 assert!(text.contains("const 8 : int"), "{text}");
1987 assert!(text.contains("const 9223372036854775807 : long"), "{text}");
1988 assert!(text.contains("const 2 : int"), "{text}");
1989 assert!(text.contains("const 53 : int"), "{text}");
1990 }
1991
1992 #[test]
1996 fn the_shipped_stdint_writes_the_whole_set_when_there_is_no_library_to_defer_to() {
1997 let text = shipped(concat!(
1998 "#include <stdint.h>\n",
1999 "int64_t a = INT64_C(1);\n",
2000 "uint_least16_t b;\n",
2001 "intptr_t c;\n",
2002 "uintmax_t d = UINTMAX_MAX;\n",
2003 "int wide = sizeof(int_fast64_t);\n",
2004 ));
2005 assert!(text.contains("decl #0 a : long"), "{text}");
2006 assert!(text.contains("decl #1 b : unsigned short"), "{text}");
2007 assert!(text.contains("decl #2 c : long"), "{text}");
2008 }
2009
2010 #[test]
2021 fn the_shipped_mmintrin_defines_the_mmx_type_and_the_operations_over_it() {
2022 let text = shipped(concat!(
2023 "#include <mmintrin.h>\n",
2024 "__m64 add(__m64 a, __m64 b) { return _mm_add_pi16(a, b); }\n",
2025 "__m64 pack(__m64 a, __m64 b) { return _m_packsswb(a, b); }\n",
2026 "__m64 shift(__m64 a) { return _mm_srai_pi32(a, 3); }\n",
2027 "int low(__m64 a) { return _mm_cvtsi64_si32(a); }\n",
2028 "void done(void) { _mm_empty(); }\n",
2029 ));
2030 assert!(text.contains("add"), "{text}");
2031 assert!(text.contains("pack"), "{text}");
2032 assert!(text.contains("shift"), "{text}");
2033 }
2034
2035 #[test]
2040 fn the_shipped_mm_malloc_asks_for_aligned_memory_and_gives_it_back() {
2041 let text = shipped(concat!(
2042 "#include <mm_malloc.h>\n",
2043 "void *get(void) { return _mm_malloc(64, 16); }\n",
2044 "void put(void *p) { _mm_free(p); }\n",
2045 ));
2046 assert!(text.contains("get"), "{text}");
2047 assert!(text.contains("put"), "{text}");
2048 }
2049
2050 #[test]
2062 fn the_shipped_xmmintrin_defines_the_sse_type_and_the_operations_over_it() {
2063 let text = shipped(concat!(
2064 "#include <xmmintrin.h>\n",
2065 "__m128 add(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
2066 "__m128 one(__m128 a, __m128 b) { return _mm_max_ss(a, b); }\n",
2067 "__m128 mask(__m128 a, __m128 b) { return _mm_cmpnle_ps(a, b); }\n",
2068 "__m128 pick(__m128 a, __m128 b) { return _mm_shuffle_ps(a, b, _MM_SHUFFLE(0,1,2,3)); }\n",
2069 "int bits(__m128 a) { return _mm_movemask_ps(a); }\n",
2070 "int near(__m128 a) { return _mm_cvtss_si32(a); }\n",
2071 "__m128 wide(__m64 a) { return _mm_cvtpi16_ps(a); }\n",
2072 "void *room(void) { return _mm_malloc(64, 16); }\n",
2073 "void hint(const float *p) { _mm_prefetch(p, _MM_HINT_T0); _mm_sfence(); }\n",
2074 ));
2075 assert!(text.contains("add"), "{text}");
2076 assert!(text.contains("mask"), "{text}");
2077 assert!(text.contains("pick"), "{text}");
2078 assert!(text.contains("wide"), "{text}");
2079 }
2080
2081 #[test]
2088 fn the_shipped_xmmintrin_leaves_out_the_names_that_need_an_instruction() {
2089 let text = rucc_session::runtime::header("xmmintrin.h").expect("xmmintrin.h is shipped");
2090 for absent in [
2091 "_mm_sqrt_ps",
2092 "_mm_sqrt_ss",
2093 "_mm_rsqrt_ps",
2094 "_mm_rsqrt_ss",
2095 "_mm_getcsr",
2096 "_mm_setcsr",
2097 ] {
2098 let defined = text.contains(&format!("{absent}("));
2099 assert!(!defined, "{absent} is defined and the header says it is not");
2100 assert!(text.contains(absent), "{absent} is absent and unexplained");
2101 }
2102 }
2103
2104 #[test]
2105 fn the_shipped_emmintrin_defines_both_sse2_types_and_the_operations_over_them() {
2106 let text = shipped(concat!(
2107 "#include <emmintrin.h>\n",
2108 "__m128i add(__m128i a, __m128i b) { return _mm_add_epi64(a, b); }\n",
2109 "__m128i wide(__m128i a, __m128i b) { return _mm_mul_epu32(a, b); }\n",
2110 "__m128i pick(__m128i a) { return _mm_shuffle_epi32(a, _MM_SHUFFLE(0,1,2,3)); }\n",
2111 "__m128i up(__m128i a) { return _mm_slli_epi64(a, 13); }\n",
2112 "__m128i down(__m128i a) { return _mm_srli_si128(a, 3); }\n",
2113 "__m128i pack(__m128i a, __m128i b) { return _mm_packus_epi16(a, b); }\n",
2114 "int bits(__m128i a) { return _mm_movemask_epi8(a); }\n",
2115 "__m128d sum(__m128d a, __m128d b) { return _mm_add_sd(a, b); }\n",
2116 "__m128d mask(__m128d a, __m128d b) { return _mm_cmpunord_pd(a, b); }\n",
2117 "__m128i near(__m128d a) { return _mm_cvtpd_epi32(a); }\n",
2118 "__m128d over(__m128 a) { return _mm_cvtps_pd(a); }\n",
2119 "__m128i half(__m64 a) { return _mm_movpi64_epi64(a); }\n",
2120 "__m128i grab(void const *p) { return _mm_loadu_si128(p); }\n",
2121 "void wall(void) { _mm_lfence(); _mm_mfence(); }\n",
2122 ));
2123 assert!(text.contains("wide"), "{text}");
2124 assert!(text.contains("pack"), "{text}");
2125 assert!(text.contains("near"), "{text}");
2126 assert!(text.contains("half"), "{text}");
2127 }
2128
2129 #[test]
2133 fn the_shipped_immintrin_reaches_the_names_the_headers_under_it_define() {
2134 let text = shipped(concat!(
2135 "#include <immintrin.h>\n",
2136 "unsigned long long matching(unsigned char tag, unsigned char const *bucket) {\n",
2137 " __m128i const want = _mm_set1_epi8((char)tag);\n",
2138 " __m128i const chunk = _mm_loadu_si128((__m128i const *)(void const *)bucket);\n",
2139 " __m128i const same = _mm_cmpeq_epi8(chunk, want);\n",
2140 " return (unsigned long long)_mm_movemask_epi8(same);\n",
2141 "}\n",
2142 "__m64 narrow(__m64 a, __m64 b) { return _mm_add_pi32(a, b); }\n",
2143 "__m128 single(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
2144 ));
2145 assert!(text.contains("matching"), "{text}");
2146 assert!(text.contains("narrow"), "the MMX header is not reached: {text}");
2147 assert!(text.contains("single"), "the SSE header is not reached: {text}");
2148 }
2149
2150 #[test]
2154 fn the_shipped_x86intrin_reaches_the_fences_windows_headers_ask_it_for() {
2155 let text = shipped(concat!(
2156 "#include <x86intrin.h>\n",
2157 "void barriers(void *p) {\n",
2158 " _mm_lfence();\n",
2159 " _mm_sfence();\n",
2160 " _mm_mfence();\n",
2161 " _mm_pause();\n",
2162 " _mm_clflush(p);\n",
2163 "}\n",
2164 "__m128i wide(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
2165 ));
2166 assert!(text.contains("barriers"), "{text}");
2167 assert!(text.contains("wide"), "the SSE2 header is not reached: {text}");
2168 }
2169
2170 #[test]
2174 fn the_umbrella_and_the_header_under_it_can_both_be_included() {
2175 let text = shipped(concat!(
2176 "#include <immintrin.h>\n",
2177 "#include <emmintrin.h>\n",
2178 "#include <immintrin.h>\n",
2179 "#include <x86intrin.h>\n",
2180 "__m128i twice(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
2181 ));
2182 assert!(text.contains("twice"), "{text}");
2183 }
2184
2185 #[test]
2188 fn the_shipped_arm_neon_has_what_xxhash_asks_it_for() {
2189 let mut opts = freestanding();
2190 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2191 let source = concat!(
2192 "#include <arm_neon.h>\n",
2193 "uint64x2_t acc(uint64x2_t sum, const void *in, const void *key) {\n",
2194 " uint8x16_t data = vld1q_u8((const uint8_t *)in);\n",
2195 " uint8x16_t k = vld1q_u8((const uint8_t *)key);\n",
2196 " uint64x2_t mixed = vreinterpretq_u64_u8(veorq_u8(data, k));\n",
2197 " uint32x2_t lo = vmovn_u64(mixed);\n",
2198 " uint32x2_t hi = vshrn_n_u64(mixed, 32);\n",
2199 " return vmlal_u32(sum, lo, hi);\n",
2200 "}\n",
2201 "uint32x4x2_t pair(uint32x4_t a, uint32x4_t b) { return vzipq_u32(a, b); }\n",
2202 "uint32_t total(uint32x4_t a) { return vaddvq_u32(a); }\n",
2203 );
2204 let result = run(&opts, source);
2205 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2206 assert!(result.text().contains("pair"), "{}", result.text());
2207 assert!(result.text().contains("total"), "{}", result.text());
2208 }
2209
2210 #[test]
2212 fn the_shipped_arm_neon_refuses_another_target() {
2213 let result = run(&freestanding(), "#include <arm_neon.h>\n");
2214 let said = result.messages.join("\n");
2215 assert!(said.contains("arm_neon.h is for AArch64"), "{said}");
2216 }
2217
2218 #[test]
2222 fn the_shipped_emmintrin_leaves_out_the_two_square_roots() {
2223 let text = rucc_session::runtime::header("emmintrin.h").expect("emmintrin.h is shipped");
2224 for absent in ["_mm_sqrt_pd", "_mm_sqrt_sd"] {
2225 let defined = text.contains(&format!("{absent}("));
2226 assert!(!defined, "{absent} is defined and the header says it is not");
2227 assert!(text.contains(absent), "{absent} is absent and unexplained");
2228 }
2229 }
2230
2231 #[test]
2236 fn the_shipped_nmmintrin_is_one_instruction_per_step_under_sse4_2() {
2237 let mut opts = freestanding();
2238 opts.emit = EmitKind::Asm;
2239 let mut choices = rucc_target::Choices::new();
2240 choices.read("sse4.2").expect("gcc knows sse4.2");
2241 opts.isa = choices.over(opts.isa);
2242 let source = concat!(
2243 "#include <nmmintrin.h>\n",
2244 "unsigned b(unsigned c, unsigned char v) { return _mm_crc32_u8(c, v); }\n",
2245 "unsigned w(unsigned c, unsigned short v) { return _mm_crc32_u16(c, v); }\n",
2246 "unsigned l(unsigned c, unsigned v) { return _mm_crc32_u32(c, v); }\n",
2247 "unsigned long long q(unsigned long long c, unsigned long long v) {\n",
2248 " return _mm_crc32_u64(c, v);\n",
2249 "}\n",
2250 "int n(unsigned v) { return _mm_popcnt_u32(v); }\n",
2251 "long long m(unsigned long long v) { return _mm_popcnt_u64(v); }\n",
2252 );
2253 let result = run(&opts, source);
2254 assert_eq!(result.messages, Vec::<String>::new());
2255 let text = result.text();
2256 for step in ["crc32b", "crc32w", "crc32l", "crc32q", "popcntl", "popcntq"] {
2257 assert!(text.contains(step), "no {step} in:\n{text}");
2258 }
2259 }
2260
2261 #[test]
2264 fn the_shipped_smmintrin_refuses_a_caller_not_built_for_the_checksum() {
2265 let result = run(
2266 &freestanding(),
2267 "#include <immintrin.h>\nunsigned f(unsigned c) { return _mm_crc32_u32(c, 1); }\n",
2268 );
2269 let said = result.messages.join("\n");
2270 let refusal = "inlining failed in call to 'always_inline' '_mm_crc32_u32': target \
2271 specific option mismatch";
2272 assert!(said.contains(refusal), "{said}");
2273 }
2274
2275 #[test]
2280 fn a_function_built_for_sse4_2_calls_the_steps_without_a_flag() {
2281 let mut opts = freestanding();
2282 opts.emit = EmitKind::Asm;
2283 let source = concat!(
2284 "#include <nmmintrin.h>\n",
2285 "#if defined(__has_attribute) && __has_attribute (target)\n",
2286 "__attribute__((target(\"sse4.2\")))\n",
2287 "#endif\n",
2288 "unsigned l(unsigned c, unsigned v) { return _mm_crc32_u32(c, v); }\n",
2289 "__attribute__((target(\"popcnt\")))\n",
2290 "int n(unsigned v) { return _mm_popcnt_u32(v); }\n",
2291 );
2292 let result = run(&opts, source);
2293 assert_eq!(result.messages, Vec::<String>::new());
2294 let text = result.text();
2295 assert!(text.contains("crc32l") && text.contains("popcntl"), "{text}");
2296 let l = &text[text.find("\nl:").expect("l is defined")..];
2297 let l = &l[..l.find("ret").expect("l returns")];
2298 assert!(l.contains("crc32l") && !l.contains("call"), "{l}");
2299 }
2300
2301 #[test]
2308 fn the_shipped_avx512_headers_pass_postgres_probes() {
2309 let popcount = concat!(
2310 "#include <immintrin.h>\n",
2311 "#include <stdint.h>\n",
2312 "char buf[sizeof(__m512i)];\n",
2313 "#if defined(__has_attribute) && __has_attribute (target)\n",
2314 "__attribute__((target(\"avx512vpopcntdq,avx512bw\")))\n",
2315 "#endif\n",
2316 "int popcount_test(void)\n",
2317 "{\n",
2318 " int64_t popcnt = 0;\n",
2319 " __m512i accum = _mm512_setzero_si512();\n",
2320 " __m512i val = _mm512_maskz_loadu_epi8((__mmask64) 0xf0f0f0f0f0f0f0f0, (const __m512i *) buf);\n",
2321 " __m512i cnt = _mm512_popcnt_epi64(val);\n",
2322 " accum = _mm512_add_epi64(accum, cnt);\n",
2323 " popcnt = _mm512_reduce_add_epi64(accum);\n",
2324 " return (int) popcnt;\n",
2325 "}\n",
2326 );
2327 let pclmul = concat!(
2328 "#include <immintrin.h>\n",
2329 "__m512i x;\n",
2330 "__m512i y;\n",
2331 "#if defined(__has_attribute) && __has_attribute (target)\n",
2332 "__attribute__((target(\"vpclmulqdq,avx512vl\")))\n",
2333 "#endif\n",
2334 "int avx512_pclmul_test(void)\n",
2335 "{\n",
2336 " __m128i z;\n",
2337 " x = _mm512_xor_si512(_mm512_zextsi128_si512(_mm_cvtsi32_si128(0)), x);\n",
2338 " y = _mm512_clmulepi64_epi128(x, y, 0);\n",
2339 " z = _mm_ternarylogic_epi64(\n",
2340 " _mm512_castsi512_si128(y),\n",
2341 " _mm512_extracti32x4_epi32(y, 1),\n",
2342 " _mm512_extracti32x4_epi32(y, 2),\n",
2343 " 0x96);\n",
2344 " return _mm_crc32_u64(0, _mm_extract_epi64(z, 0));\n",
2345 "}\n",
2346 );
2347 let checks: [(&str, &str, &[&str]); 2] = [
2348 (popcount, "popcount_test", &["kmovq", "vmovdqu8", "vpopcntq", "vpaddq", "vshufi64x2"]),
2349 (pclmul, "avx512_pclmul_test", &["vpxorq", "vpclmulqdq", "vpternlogq", "crc32q"]),
2350 ];
2351 for (source, name, wanted) in checks {
2352 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
2353 let mut opts = freestanding();
2354 opts.emit = EmitKind::Asm;
2355 opts.opt_level = level;
2356 let result = run(&opts, source);
2357 assert_eq!(result.messages, Vec::<String>::new(), "{name} at {level:?}");
2358 let text = result.text();
2359 let start = text.find(&format!("\n{name}:")).expect("the probe is written out");
2360 let body = &text[start..];
2361 let body = &body[..body.find(".size").unwrap_or(body.len())];
2362 for instruction in wanted {
2363 assert!(
2364 body.contains(instruction),
2365 "no {instruction} at {level:?} in:\n{body}"
2366 );
2367 }
2368 assert!(!body.contains("call"), "a call left behind at {level:?} in:\n{body}");
2369 }
2370 }
2371 }
2372
2373 #[test]
2376 fn the_shipped_avx512_headers_refuse_a_caller_not_built_for_them() {
2377 let result = run(
2378 &freestanding(),
2379 "#include <immintrin.h>\n__m512i f(__m512i a) { return _mm512_popcnt_epi64(a); }\n",
2380 );
2381 let said = result.messages.join("\n");
2382 let refusal = "inlining failed in call to 'always_inline' '_mm512_popcnt_epi64': target \
2383 specific option mismatch";
2384 assert!(said.contains(refusal), "{said}");
2385 }
2386
2387 #[test]
2392 fn the_sse3_to_sse4_2_intrinsics_are_the_instructions_under_the_attribute() {
2393 let mut opts = freestanding();
2394 opts.emit = EmitKind::Asm;
2395 let source = concat!(
2396 "#include <immintrin.h>\n",
2397 "__attribute__((target(\"sse3\")))\n",
2398 "__m128i a(const __m128i *p) { return _mm_lddqu_si128(p); }\n",
2399 "__attribute__((target(\"sse3\")))\n",
2400 "__m128 b(__m128 x, __m128 y) { return _mm_hadd_ps(x, y); }\n",
2401 "__attribute__((target(\"ssse3\")))\n",
2402 "__m128i c(__m128i x, __m128i y) { return _mm_shuffle_epi8(_mm_abs_epi32(x), y); }\n",
2403 "__attribute__((target(\"ssse3\")))\n",
2404 "__m128i d(__m128i x, __m128i y) { return _mm_alignr_epi8(x, y, 5); }\n",
2405 "__attribute__((target(\"sse4.1\")))\n",
2406 "int e(__m128i x, __m128i y) {\n",
2407 " return _mm_extract_epi32(_mm_min_epi32(_mm_mullo_epi32(x, y), y), 2);\n",
2408 "}\n",
2409 "__attribute__((target(\"sse4.1\")))\n",
2410 "__m128 f(__m128 x) { return _mm_round_ps(x, _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); }\n",
2411 "__attribute__((target(\"sse4.1\")))\n",
2412 "__m128i g(__m128i x, __m128i y, __m128i m) { return _mm_blendv_epi8(x, y, m); }\n",
2413 "__attribute__((target(\"sse4.1\")))\n",
2414 "int h(__m128i x) { return _mm_testz_si128(x, x); }\n",
2415 "__attribute__((target(\"sse4.2\")))\n",
2416 "__m128i i(__m128i x, __m128i y) { return _mm_cmpgt_epi64(x, y); }\n",
2417 "__attribute__((target(\"sse4.2\")))\n",
2418 "int j(__m128i x, __m128i y) { return _mm_cmpistri(x, y, _SIDD_CMP_EQUAL_EACH); }\n",
2419 );
2420 let result = run(&opts, source);
2421 assert_eq!(result.messages, Vec::<String>::new());
2422 let text = result.text();
2423 for insn in [
2424 "lddqu",
2425 "haddps",
2426 "pabsd",
2427 "pshufb",
2428 "palignr $5,",
2429 "pmulld",
2430 "pminsd",
2431 "pextrd $2,",
2432 "roundps $8,",
2433 "pblendvb",
2434 "ptest",
2435 "pcmpgtq",
2436 "pcmpistri $8,",
2437 ] {
2438 assert!(text.contains(insn), "no {insn} in:\n{text}");
2439 }
2440 assert!(!text.contains("call"), "{text}");
2441 }
2442
2443 #[test]
2446 fn the_sse3_to_sse4_1_intrinsics_are_refused_a_caller_not_built_for_them() {
2447 let source = concat!(
2448 "#include <immintrin.h>\n",
2449 "__m128i f(__m128i x, __m128i y) { return _mm_shuffle_epi8(x, y); }\n",
2450 );
2451 let said = run(&freestanding(), source).messages.join("\n");
2452 let refusal = "inlining failed in call to 'always_inline' '_mm_shuffle_epi8': target \
2453 specific option mismatch";
2454 assert!(said.contains(refusal), "{said}");
2455
2456 let mut opts = freestanding();
2457 let mut choices = rucc_target::Choices::new();
2458 choices.read("ssse3").expect("gcc knows ssse3");
2459 opts.isa = choices.over(opts.isa);
2460 let result =
2461 run(&opts, &format!("{source}__m128 g(__m128 x) {{ return _mm_movehdup_ps(x); }}\n"));
2462 assert_eq!(result.messages, Vec::<String>::new());
2463 let result = run(
2464 &opts,
2465 "#include <immintrin.h>\n__m128i h(__m128i x) { return _mm_abs_epi8(_mm_cvtepi8_epi32(x)); }\n",
2466 );
2467 let said = result.messages.join("\n");
2468 assert!(said.contains("'_mm_cvtepi8_epi32': target specific option mismatch"), "{said}");
2469 }
2470
2471 #[test]
2474 fn a_target_string_gcc_does_not_know_is_refused() {
2475 for (string, name) in [("sse5", "sse5"), ("+crc", "+crc"), ("sse4.2,foo", "foo")] {
2476 let source =
2477 format!("__attribute__((target(\"{string}\"))) int f(void) {{ return 0; }}\n");
2478 let said = run(&freestanding(), &source).messages.join("\n");
2479 let wanted = format!("attribute 'target' argument '{name}' is unknown");
2480 assert!(said.contains(&wanted), "{string}: {said}");
2481 }
2482 }
2483
2484 #[test]
2487 fn an_aarch64_target_string_is_still_accepted() {
2488 let mut opts = freestanding();
2489 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2490 let source = "__attribute__((target(\"+crc\"))) int f(void) { return 0; }\n";
2491 let result = run(&opts, source);
2492 assert_eq!(result.messages, Vec::<String>::new());
2493 }
2494
2495 #[test]
2496 fn the_three_formality_headers_still_have_to_work() {
2497 let text = shipped(concat!(
2498 "#include <stdbool.h>\n",
2499 "#include <stdalign.h>\n",
2500 "#include <iso646.h>\n",
2501 "#include <stdnoreturn.h>\n",
2502 "int t = true and not false;\n",
2503 "_Alignas(16) char buf[16];\n",
2504 "int a = alignof(long);\n",
2505 ));
2506 assert!(text.contains("decl #0 t : int"), "{text}");
2507 assert!(text.contains("const 8 : unsigned long"), "{text}");
2508 }
2509
2510 #[test]
2518 fn every_shipped_header_can_be_included_twice() {
2519 let once: String = rucc_session::runtime::names()
2523 .iter()
2524 .filter(|name| !["arm_neon.h", "intrin.h", "setjmp.h", "vadefs.h"].contains(*name))
2525 .map(|name| format!("#include <{name}>\n"))
2526 .collect();
2527 let twice = once.repeat(2);
2528 assert_eq!(shipped(&format!("{once}int x;\n")), shipped(&format!("{twice}int x;\n")));
2529
2530 let mut opts = freestanding();
2531 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2532 let tree = |source: &str| {
2533 let result = run(&opts, source);
2534 assert_eq!(
2535 result.messages,
2536 Vec::<String>::new(),
2537 "expected this to compile:\n{source}"
2538 );
2539 result.text().to_owned()
2540 };
2541 let neon = "#include <arm_neon.h>\n";
2542 assert_eq!(tree(&format!("{neon}int x;\n")), tree(&format!("{neon}{neon}int x;\n")));
2543 }
2544
2545 #[test]
2546 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
2547 let fs = MemoryFileSystem::new();
2548 let result = compile(&options(), "/nope.c", &fs);
2549 assert!(result.failed());
2550 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
2551 assert!(result.text().is_empty());
2552 }
2553
2554 #[test]
2555 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
2556 let text = tast("int x = 1;\n");
2557 let expected = "\
2558decl #0 x : int object external static defined
2559 init
2560 +0
2561 const 1 : int
2562";
2563 assert_eq!(text, expected);
2564 }
2565
2566 #[test]
2567 fn the_macros_are_expanded_before_anything_is_parsed() {
2568 let text = tast("#define N 2\nint a[N];\n");
2572 assert!(text.starts_with("decl #0 a : int[2] object external static tentative"), "{text}");
2573 }
2574
2575 #[test]
2581 fn a_pragma_is_not_a_declaration_and_the_parse_walks_past_the_ones_it_does_not_read() {
2582 let text = tast(concat!(
2583 "#pragma pack(4)\n",
2584 "struct s { int a; };\n",
2585 "#pragma pack()\n",
2586 "int b;\n",
2587 "_Pragma(\"GCC visibility push(default)\") int c;\n",
2588 ));
2589 assert!(text.contains("decl #0 b : int"), "{text}");
2590 assert!(text.contains("decl #1 c : int"), "{text}");
2591 }
2592
2593 #[test]
2597 fn the_byte_swaps_and_the_bit_counts_of_a_constant_are_constants() {
2598 tast(concat!(
2599 "static const unsigned magic = __builtin_bswap32(0x11223344u);\n",
2600 "_Static_assert(__builtin_bswap16(0x1234) == 0x3412, \"16\");\n",
2601 "_Static_assert(__builtin_bswap32(0x11223344u) == 0x44332211u, \"32\");\n",
2602 "_Static_assert(__builtin_bswap64(0x0102030405060708ull) == 0x0807060504030201ull, \"64\");\n",
2603 "_Static_assert(__builtin_popcountll(-1ll) == 64 && __builtin_popcount(-1) == 32, \"ones\");\n",
2604 "_Static_assert(__builtin_parity(7) == 1 && __builtin_parity(3) == 0, \"parity\");\n",
2605 "_Static_assert(__builtin_ffs(0) == 0 && __builtin_ffs(8) == 4, \"ffs\");\n",
2606 "_Static_assert(__builtin_clrsb(0) == 31 && __builtin_clrsb(-1) == 31, \"clrsb\");\n",
2607 "_Static_assert(__builtin_clrsbl(1) == 62, \"clrsbl\");\n",
2608 "_Static_assert(__builtin_clz(1) == 31 && __builtin_clzl(1) == 63, \"clz\");\n",
2609 "_Static_assert(__builtin_ctzll(1ull << 40) == 40, \"ctz\");\n",
2610 "_Static_assert(__builtin_clz(0) == 32 && __builtin_ctzll(0) == 64, \"zero\");\n",
2611 ));
2612 }
2613
2614 #[test]
2622 fn the_layout_attributes_move_the_members_and_the_record_the_way_gcc_lays_them_out() {
2623 tast(concat!(
2624 "struct A { char c; int i; } __attribute__((packed));\n",
2625 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2626 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2627 "struct B { char c; int i; } __attribute__((aligned));\n",
2630 "_Static_assert(sizeof(struct B) == 16 && _Alignof(struct B) == 16, \"B\");\n",
2631 "struct C { char c; int i __attribute__((packed)); };\n",
2632 "_Static_assert(sizeof(struct C) == 5 && _Alignof(struct C) == 1, \"C\");\n",
2633 "_Static_assert(__builtin_offsetof(struct C, i) == 1, \"C.i\");\n",
2634 "struct D { char c; int i; } __attribute__((packed, aligned(4)));\n",
2635 "_Static_assert(sizeof(struct D) == 8 && _Alignof(struct D) == 4, \"D\");\n",
2636 "_Static_assert(__builtin_offsetof(struct D, i) == 1, \"D.i\");\n",
2637 "struct E { char c; _Alignas(8) int i; };\n",
2638 "_Static_assert(sizeof(struct E) == 16 && _Alignof(struct E) == 8, \"E\");\n",
2639 "_Static_assert(__builtin_offsetof(struct E, i) == 8, \"E.i\");\n",
2640 "struct F { char c; int i __attribute__((aligned(8))); };\n",
2641 "_Static_assert(sizeof(struct F) == 16 && _Alignof(struct F) == 8, \"F\");\n",
2642 "struct G { char c; short s; } __attribute__((aligned(2)));\n",
2645 "_Static_assert(sizeof(struct G) == 4 && _Alignof(struct G) == 2, \"G\");\n",
2646 "struct H { char c; int i; } __attribute__((aligned(2)));\n",
2647 "_Static_assert(sizeof(struct H) == 8 && _Alignof(struct H) == 4, \"H\");\n",
2648 "struct I { [[gnu::packed]] char c; int i; };\n",
2651 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2652 "struct J { char c; [[gnu::packed]] int i; };\n",
2653 "_Static_assert(sizeof(struct J) == 5 && _Alignof(struct J) == 1, \"J\");\n",
2654 "struct M { char c; int i : 5; int j : 20; } __attribute__((packed));\n",
2655 "_Static_assert(sizeof(struct M) == 5 && _Alignof(struct M) == 1, \"M\");\n",
2656 "struct N { char c; long long l; } __attribute__((aligned(32)));\n",
2657 "_Static_assert(sizeof(struct N) == 32 && _Alignof(struct N) == 32, \"N\");\n",
2658 "union L { char c; int i; } __attribute__((packed));\n",
2659 "_Static_assert(sizeof(union L) == 4 && _Alignof(union L) == 1, \"L\");\n",
2660 "struct O { char c; int i; } __attribute__((__packed__));\n",
2664 "_Static_assert(sizeof(struct O) == 5 && _Alignof(struct O) == 1, \"O\");\n",
2665 "struct P { char c; int i; } __attribute__((__aligned__(8)));\n",
2666 "_Static_assert(sizeof(struct P) == 8 && _Alignof(struct P) == 8, \"P\");\n",
2667 ));
2668 }
2669
2670 #[test]
2683 fn a_transparent_union_takes_the_member_a_value_fits_and_is_declared_either_way() {
2684 let text = tast(concat!(
2685 "struct one { int x; };\n",
2686 "struct two { long y; };\n",
2687 "typedef union { struct one *a; struct two *b; void *any; }\n",
2688 " __attribute__((__transparent_union__)) arg;\n",
2689 "int takes(arg v);\n",
2690 "int f(struct one *p, struct two *q, char *c) {\n",
2691 " return takes(p) + takes(q) + takes(c) + takes(0);\n",
2692 "}\n",
2693 "int takes(struct one *p);\n",
2695 "int (*as_a_member)(struct one *) = takes;\n",
2696 "int (*as_the_union)(arg) = takes;\n",
2697 ));
2698 assert!(text.contains("compound-literal"), "{text}");
2699 }
2700
2701 #[test]
2707 fn the_attribute_on_the_declarator_of_a_typedef_is_the_one_glibc_writes() {
2708 let text = tast(concat!(
2709 "struct sockaddr { int family; };\n",
2710 "struct sockaddr_in { int family; int addr; };\n",
2711 "typedef union { struct sockaddr *plain; struct sockaddr_in *inet; }\n",
2712 " addr_arg __attribute__((__transparent_union__));\n",
2713 "int bind_to(int fd, addr_arg where);\n",
2714 "int f(struct sockaddr_in *where) { return bind_to(0, where); }\n",
2715 ));
2716 assert!(text.contains("compound-literal"), "{text}");
2717 }
2718
2719 #[test]
2727 fn a_transparent_union_that_cannot_keep_the_promise_is_dropped_with_a_word_about_it() {
2728 let result = run(
2729 &options(),
2730 concat!(
2731 "union wider { int small; double large; } __attribute__((transparent_union));\n",
2732 "struct plain { int x; } __attribute__((transparent_union));\n",
2733 ),
2734 );
2735 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
2736 assert!(!result.failed(), "{:?}", result.messages);
2737 for message in &result.messages {
2738 assert!(message.contains("'transparent_union' attribute ignored"), "{message}");
2739 }
2740 assert!(result.messages[0].contains("first member"), "{:?}", result.messages);
2741 assert!(result.messages[1].contains("only a union"), "{:?}", result.messages);
2742 }
2743
2744 #[test]
2753 fn an_access_to_a_packed_member_says_the_alignment_the_layout_left_it() {
2754 let packed = body(concat!(
2755 "struct P { char c; int v; } __attribute__((packed));\n",
2756 "int f(struct P *p) { return p->v; }\n",
2757 ));
2758 assert!(packed.contains("load.i32 %2, align 1,"), "{packed}");
2759 let plain = body(concat!(
2761 "struct P { char c; int v; };\n",
2762 "int f(struct P *p) { return p->v; }\n",
2763 ));
2764 assert!(plain.contains("load.i32 %2, align 4,"), "{plain}");
2765 }
2766
2767 #[test]
2774 fn what_is_inside_a_packed_member_is_no_more_aligned_than_the_member_is() {
2775 let stepped = body(concat!(
2776 "struct P { char c; int v[4]; } __attribute__((packed));\n",
2777 "int f(struct P *p, int i) { return p->v[i]; }\n",
2778 ));
2779 assert!(stepped.contains(", align 1,"), "{stepped}");
2780 assert!(!stepped.contains(", align 4,"), "{stepped}");
2781 let nested = body(concat!(
2782 "struct Inner { int v; };\n",
2783 "struct P { char c; struct Inner in; } __attribute__((packed));\n",
2784 "int f(struct P *p) { return p->in.v; }\n",
2785 ));
2786 assert!(nested.contains(", align 1,"), "{nested}");
2787 assert!(!nested.contains(", align 4,"), "{nested}");
2788 }
2789
2790 #[test]
2806 fn an_access_through_a_typedef_that_lowered_its_alignment_says_the_one_the_typedef_asked_for() {
2807 let through = body(concat!(
2808 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2809 "unsigned int f(const void *p) { return *(const unalign32 *)p; }\n",
2810 ));
2811 assert!(through.contains("load.i32 %0, align 1,"), "{through}");
2812 let stepped = body(concat!(
2815 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2816 "unsigned int f(unalign32 *p, int i) { return p[i]; }\n",
2817 ));
2818 assert!(stepped.contains(", align 1,"), "{stepped}");
2819 assert!(!stepped.contains(", align 4,"), "{stepped}");
2820 let plain = body(concat!(
2823 "typedef unsigned int word;\n",
2824 "unsigned int f(const void *p) { return *(const word *)p; }\n",
2825 ));
2826 assert!(plain.contains("load.i32 %0, align 4,"), "{plain}");
2827 }
2828
2829 #[test]
2840 fn a_vector_read_through_a_typedef_that_lowered_its_alignment_comes_back_a_piece_at_a_time() {
2841 let prefix = concat!(
2842 "typedef long long v2di __attribute__((__vector_size__(16)));\n",
2843 "typedef long long v2di_u __attribute__((__vector_size__(16), __aligned__(1)));\n",
2844 );
2845 let loaded =
2846 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di_u *)p; }}"));
2847 assert_eq!(loaded.matches("align 1\n").count(), 2, "{loaded}");
2848 assert!(!loaded.contains("align 16"), "{loaded}");
2849 let stored = body(&format!("{prefix}void f(void *p, v2di b) {{ *(v2di_u *)p = b; }}"));
2852 assert!(stored.contains("memcpy %0, %3, size 16, align 1"), "{stored}");
2853 let aligned =
2855 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di *)p; }}"));
2856 assert!(aligned.contains("align 16"), "{aligned}");
2857 }
2858
2859 #[test]
2868 fn the_aligned_attribute_on_a_declaration_raises_what_that_one_object_is_aligned_to() {
2869 tast(concat!(
2870 "int v __attribute__((aligned(64)));\n",
2871 "_Static_assert(__alignof__(v) == 64, \"v\");\n",
2872 "__attribute__((aligned(32))) int w;\n",
2875 "_Static_assert(__alignof__(w) == 32, \"w\");\n",
2876 "[[gnu::aligned(16)]] int x;\n",
2877 "_Static_assert(__alignof__(x) == 16, \"x\");\n",
2878 "int y __attribute__((aligned(2)));\n",
2881 "_Static_assert(__alignof__(y) == 4, \"y\");\n",
2882 "void f(void) { int a __attribute__((aligned(128)));\n",
2884 "_Static_assert(__alignof__(a) == 128, \"a\"); (void)a; }\n",
2885 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2888 "void g(void) __attribute__((aligned(256)));\n",
2891 "void g(void) {}\n",
2892 "_Static_assert(__alignof__(g) == 256, \"g\");\n",
2893 ));
2894 }
2895
2896 #[test]
2900 fn what_a_declaration_asked_to_be_aligned_to_is_what_the_assembler_is_told() {
2901 let text = asm(concat!(
2902 "int v __attribute__((aligned(64)));\n",
2903 "void g(void) __attribute__((aligned(256)));\n",
2904 "void g(void) {}\n",
2905 "void plain(void) {}\n",
2906 ));
2907 assert!(text.contains("\t.p2align\t6\n\t.type\tv, @object\n"), "{text}");
2908 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2909 assert!(text.contains("\t.p2align\t4, 0x90\n\t.globl\tplain\n"), "{text}");
2910 }
2911
2912 #[test]
2918 fn the_alignment_the_command_line_asked_of_every_function_is_a_floor_under_all_of_them() {
2919 let source = concat!(
2920 "void g(void) __attribute__((aligned(256)));\n",
2921 "void g(void) {}\n",
2922 "void small(void) __attribute__((aligned(4)));\n",
2923 "void small(void) {}\n",
2924 "void plain(void) {}\n",
2925 );
2926 let listing = |align: Option<u32>| {
2927 let mut opts = options();
2928 opts.emit = EmitKind::Asm;
2929 opts.align_functions = align;
2930 let result = run(&opts, source);
2931 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
2932 result.text().to_owned()
2933 };
2934
2935 let text = listing(Some(32));
2936 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "the larger one wins: {text}");
2937 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tsmall\n"), "{text}");
2938 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tplain\n"), "{text}");
2939
2940 let text = listing(Some(8));
2943 assert!(text.contains("\t.p2align\t3, 0x90\n\t.globl\tplain\n"), "{text}");
2944 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2945 }
2946
2947 #[test]
2956 fn an_aligned_typedef_says_what_an_object_of_it_is_aligned_to_and_may_lower_it() {
2957 tast(concat!(
2958 "typedef int L __attribute__((aligned(2)));\n",
2959 "_Static_assert(__alignof__(L) == 2, \"L\");\n",
2960 "_Static_assert(_Alignof(L) == 2, \"L alignof\");\n",
2961 "_Static_assert(sizeof(L) == 4, \"L size\");\n",
2963 "struct T { char c; L x; };\n",
2964 "_Static_assert(sizeof(struct T) == 6, \"T\");\n",
2965 "_Static_assert(__builtin_offsetof(struct T, x) == 2, \"T.x\");\n",
2966 "typedef int H __attribute__((aligned(16)));\n",
2968 "_Static_assert(__alignof__(H) == 16, \"H\");\n",
2969 "_Static_assert(sizeof(H) == 4, \"H size\");\n",
2970 "struct U { char c; H x; };\n",
2971 "_Static_assert(sizeof(struct U) == 32, \"U\");\n",
2972 "_Static_assert(__builtin_offsetof(struct U, x) == 16, \"U.x\");\n",
2973 "typedef L M __attribute__((aligned(8)));\n",
2976 "_Static_assert(__alignof__(M) == 8, \"M\");\n",
2977 "typedef L N;\n",
2980 "_Static_assert(__alignof__(N) == 2, \"N\");\n",
2981 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2983 ));
2984 let text = asm(concat!(
2985 "typedef int L __attribute__((aligned(2)));\n",
2986 "typedef int H __attribute__((aligned(16)));\n",
2987 "L low;\n",
2988 "H high;\n",
2989 ));
2990 assert!(text.contains("\t.p2align\t1\n\t.type\tlow, @object\n"), "{text}");
2991 assert!(text.contains("\t.p2align\t4\n\t.type\thigh, @object\n"), "{text}");
2992 }
2993
2994 #[test]
3005 fn a_conditional_with_a_vector_in_each_arm_is_that_vector() {
3006 tast(concat!(
3007 "typedef long long v2di __attribute__((vector_size(16)));\n",
3008 "typedef long long m128i __attribute__((vector_size(16), may_alias));\n",
3009 "v2di id(v2di);\n",
3010 "v2di f(v2di x, v2di r, int c) { return c ? x : c > 1 ? id(x) : r; }\n",
3011 "m128i g(m128i x, v2di r, int c) { return c ? x : r; }\n",
3012 "_Static_assert(sizeof(1 ? (v2di){0} : (v2di){1}) == 16, \"whole\");\n",
3013 ));
3014 let refused = errors(concat!(
3015 "typedef long long v2di __attribute__((vector_size(16)));\n",
3016 "typedef int v4si __attribute__((vector_size(16)));\n",
3017 "v2di f(v2di x, v4si r, int c) { return c ? x : r; }\n",
3018 ));
3019 assert!(refused.iter().any(|m| m.contains("type mismatch in conditional")), "{refused:?}");
3020 }
3021
3022 #[test]
3023 fn the_vector_size_attribute_builds_a_type_of_lanes_and_measures_it_in_bytes() {
3024 tast(concat!(
3025 "typedef int __attribute__((vector_size(16))) v4si;\n",
3026 "_Static_assert(sizeof(v4si) == 16 && _Alignof(v4si) == 16, \"v4si\");\n",
3027 "typedef char __attribute__((vector_size(16))) v16qi;\n",
3028 "_Static_assert(sizeof(v16qi) == 16, \"v16qi\");\n",
3029 "typedef int __attribute__((vector_size(4))) v1si;\n",
3032 "_Static_assert(sizeof(v1si) == 4, \"v1si\");\n",
3033 "typedef float __attribute__((__vector_size__(8))) v2sf;\n",
3035 "_Static_assert(sizeof(v2sf) == 8, \"v2sf\");\n",
3036 "typedef short [[gnu::vector_size(8)]] v4hi;\n",
3037 "_Static_assert(sizeof(v4hi) == 8, \"v4hi\");\n",
3038 "v4si g;\n",
3041 "_Static_assert(sizeof(g[0]) == 4, \"lane\");\n",
3042 "_Static_assert(sizeof(g + g) == 16, \"whole\");\n",
3043 "_Static_assert(sizeof(g + 1) == 16, \"broadcast\");\n",
3046 "_Static_assert(sizeof(v4si[3]) == 48, \"array\");\n",
3048 ));
3049 }
3050
3051 #[test]
3061 fn a_vector_is_written_whole_into_an_array_of_them_and_named_by_a_type_name() {
3062 tast(concat!(
3063 "typedef int __attribute__((vector_size(8))) v2si;\n",
3064 "v2si table[] = { (v2si){ 1, 2 }, (v2si){ 3, 4 } };\n",
3065 "_Static_assert(sizeof(table) == 16, \"two of them and not eight lanes\");\n",
3066 "v2si written = (int __attribute__((vector_size(8)))){ 5, 6 };\n",
3068 "_Static_assert(sizeof((int __attribute__((vector_size(16)))){ 0 }) == 16, \"named\");\n",
3069 "v2si lanes[2] = { 1, 2, 3, 4 };\n",
3072 "_Static_assert(sizeof(lanes) == 16, \"still elided\");\n",
3073 ));
3074 }
3075
3076 #[test]
3084 fn a_lane_is_assignable_and_a_shift_takes_a_count_of_its_own_lane() {
3085 let result = run(
3086 &options(),
3087 concat!(
3088 "typedef int __attribute__((vector_size(16))) v4si;\n",
3089 "typedef unsigned __attribute__((vector_size(16))) v4ui;\n",
3090 "void write(v4si *out, v4ui a, v4si b, int n) {\n",
3091 " v4si v = { 1, 2, 3, 4 };\n",
3092 " v[0] = n;\n",
3093 " v[1] += n;\n",
3094 " v[2]++;\n",
3095 " *&v[3] = n;\n",
3096 " v4ui shifted = a >> b;\n",
3098 " shifted <<= b;\n",
3099 " *out = v + (v4si)shifted + (1 << b);\n",
3102 "}\n",
3103 "void refused(const v4si c) {\n",
3106 " c[0] = 1;\n",
3107 "}\n",
3108 ),
3109 );
3110 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
3111 assert!(result.messages[0].contains("assignment of read-only"), "{:?}", result.messages);
3112 }
3113
3114 #[test]
3126 fn a_record_that_asks_for_the_other_byte_order_swaps_every_scalar_it_holds() {
3127 let read = "int f(struct s *p) { return p->i; }\n";
3128 let big = "struct s { int i; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
3129 assert!(body(&format!("{big}{read}")).contains("bswap"), "{big}");
3130
3131 let armoured =
3132 "struct s { int i; } __attribute__((__scalar_storage_order__(\"big-endian\")));\n";
3133 assert!(body(&format!("{armoured}{read}")).contains("bswap"), "{armoured}");
3134
3135 let front = "struct __attribute__((scalar_storage_order(\"big-endian\"))) s { int i; };\n";
3136 assert!(body(&format!("{front}{read}")).contains("bswap"), "{front}");
3137
3138 let standard = "struct s { int i; } [[gnu::scalar_storage_order(\"big-endian\")]];\n";
3139 assert!(body(&format!("{standard}{read}")).contains("bswap"), "{standard}");
3140
3141 let same =
3142 "struct s { int i; } __attribute__((scalar_storage_order(\"little-endian\")));\n";
3143 assert!(!body(&format!("{same}{read}")).contains("bswap"), "{same}");
3144
3145 let byte = "struct s { char c; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
3147 let source = format!("{byte}int f(struct s *p) {{ return p->c; }}\n");
3148 assert!(!body(&source).contains("bswap"), "{byte}");
3149
3150 tast(concat!(
3151 "struct s { int i; short h; char c; }",
3152 " __attribute__((scalar_storage_order(\"big-endian\")));\n",
3153 "_Static_assert(sizeof(struct s) == 8 && _Alignof(struct s) == 4, \"s\");\n",
3154 "_Static_assert(__builtin_offsetof(struct s, h) == 4, \"s.h\");\n",
3155 "_Static_assert(__builtin_offsetof(struct s, c) == 6, \"s.c\");\n",
3156 ));
3157 }
3158
3159 #[test]
3165 fn a_bit_field_in_one_of_those_records_is_counted_from_the_top_of_its_bytes() {
3166 let members = "short i : 12; char c1 : 1; char c2 : 1; char c3 : 1; char c4 : 1;";
3167 let read = "int f(struct s *p) { return p->i; }\n";
3168 let plain = format!("struct s {{ {members} }};\n{read}");
3169 let reversed = format!(
3170 "struct s {{ {members} }} __attribute__((scalar_storage_order(\"big-endian\")));\n\
3171 {read}"
3172 );
3173 assert!(body(&plain).contains("shl"), "{}", body(&plain));
3174 assert!(!body(&plain).contains("bswap"), "{}", body(&plain));
3175 let built = body(&reversed);
3178 assert!(built.contains("bswap"), "{built}");
3179 assert!(!built.contains("shl"), "{built}");
3180 assert!(built.contains("ashr"), "{built}");
3181 }
3182
3183 #[test]
3190 fn the_address_of_a_scalar_stored_the_other_way_round_is_refused() {
3191 let opts = options();
3192 let record = "struct s { int i; int a[2]; struct in { int n; } w; }\n\
3193 __attribute__((scalar_storage_order(\"big-endian\")));\n";
3194 let taken = format!("{record}int *f(struct s *p) {{ return &p->i; }}\n");
3195 assert_eq!(
3196 run(&opts, &taken).messages,
3197 ["/main.c:3:30: error: cannot take address of scalar with reverse storage order \
3198 [E0712]"]
3199 );
3200 let element = format!("{record}int *f(struct s *p) {{ return &p->a[0]; }}\n");
3201 let messages = run(&opts, &element).messages;
3202 assert!(messages[0].contains("[E0712]"), "{messages:?}");
3203
3204 let whole = format!("{record}int *f(struct s *p) {{ return (int *) &p->w; }}\n");
3205 assert_eq!(run(&opts, &whole).messages, Vec::<String>::new(), "{whole}");
3206 }
3207
3208 #[test]
3212 fn a_storage_order_that_names_neither_end_is_refused_with_the_two_words_that_are_taken() {
3213 let opts = options();
3214 let wrong = "struct s { int i; } __attribute__((scalar_storage_order(\"middle\")));\n";
3215 assert_eq!(
3216 run(&opts, wrong).messages,
3217 ["/main.c:1:36: error: 'scalar_storage_order' argument must be one of \"big-endian\" \
3218 or \"little-endian\" [E0688]"]
3219 );
3220 let bare = "struct s { int i; } __attribute__((scalar_storage_order));\n";
3221 let messages = run(&opts, bare).messages;
3222 assert!(messages[0].contains("[E0688]"), "{messages:?}");
3223 }
3224
3225 #[test]
3231 fn ms_struct_and_gcc_struct_choose_the_bit_field_rule_for_one_record() {
3232 let source = concat!(
3233 "struct __attribute__((ms_struct)) m { unsigned x : 3; char c; };\n",
3234 "struct g { unsigned x : 3; char c; } __attribute__((__gcc_struct__));\n",
3235 "struct p { unsigned x : 3; char c; };\n",
3236 "typedef struct { char c; int : 20; } __attribute__((gcc_struct)) u;\n",
3237 "_Static_assert(sizeof(struct m) == 8 && __builtin_offsetof(struct m, c) == 4, \"m\");\n",
3238 "_Static_assert(sizeof(struct g) == 4 && __builtin_offsetof(struct g, c) == 1, \"g\");\n",
3239 "_Static_assert(sizeof(u) == 4 && _Alignof(u) == 1, \"u\");\n",
3240 );
3241 let windows = "_Static_assert(sizeof(struct p) == 8, \"p\");\n";
3242 let linux = "_Static_assert(sizeof(struct p) == 4, \"p\");\n";
3243
3244 let mut opts = options();
3245 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3246 let result = run(&opts, &format!("{source}{windows}"));
3247 assert_eq!(result.messages, Vec::<String>::new());
3248 let result = run(&options(), &format!("{source}{linux}"));
3249 assert_eq!(result.messages, Vec::<String>::new());
3250
3251 let mut opts = options();
3252 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3253 let ignored = concat!(
3254 "struct __attribute__((ms_struct)) m { unsigned x : 3; char c; };\n",
3255 "_Static_assert(sizeof(struct m) == 4, \"m\");\n",
3256 );
3257 assert_eq!(run(&opts, ignored).messages, Vec::<String>::new());
3258 }
3259
3260 #[test]
3262 fn a_record_that_asks_for_both_rules_gets_the_first() {
3263 let source = concat!(
3264 "struct __attribute__((gcc_struct, ms_struct)) s { unsigned x : 3; char c; };\n",
3265 "_Static_assert(sizeof(struct s) == 4, \"s\");\n",
3266 );
3267 assert_eq!(
3268 run(&options(), source).messages,
3269 ["/main.c:1:35: warning: 'ms_struct' incompatible attribute ignored [E0746]"]
3270 );
3271 let same = "struct __attribute__((ms_struct, ms_struct)) s { unsigned x : 3; char c; };\n";
3272 assert_eq!(run(&options(), same).messages, Vec::<String>::new());
3273 }
3274
3275 #[test]
3279 fn format_takes_the_archetypes_gcc_knows_on_the_target() {
3280 let source = concat!(
3281 "int a(const char *, ...) __attribute__((format(ms_printf, 1, 2)));\n",
3282 "int b(const char *, ...) __attribute__((__format__(__gnu_printf__, 1, 2)));\n",
3283 "__attribute__((format(ms_scanf, 1, 2))) int c(const char *, ...);\n",
3284 "int d(const char *, ...) __attribute__((format(gnu_scanf, 1, 2)));\n",
3285 "unsigned long e(char *, unsigned long, const char *, const void *)\n",
3286 " __attribute__((format(ms_strftime, 3, 0)));\n",
3287 "unsigned long f(char *, unsigned long, const char *, const void *)\n",
3288 " __attribute__((format(gnu_strftime, 3, 0)));\n",
3289 "int g(const char *, ...) __attribute__((format(printf, 1, 2)));\n",
3290 );
3291 let mut opts = options();
3292 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3293 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3294
3295 let linux = run(&options(), source).messages;
3296 assert_eq!(linux.len(), 3, "{linux:?}");
3297 assert!(
3298 linux[0]
3299 .ends_with("warning: 'ms_printf' is an unrecognized format function type [E0747]"),
3300 "{linux:?}"
3301 );
3302 assert!(linux[1].contains("'ms_scanf'"), "{linux:?}");
3303 assert!(linux[2].contains("'ms_strftime'"), "{linux:?}");
3304
3305 let bogus = "int h(const char *, ...) __attribute__((format(bogus, 1, 2)));\n";
3306 let messages = run(&opts, bogus).messages;
3307 assert_eq!(messages.len(), 1, "{messages:?}");
3308 assert!(messages[0].contains("'bogus' is an unrecognized format"), "{messages:?}");
3309 }
3310
3311 #[test]
3321 fn packing_is_what_decides_whether_a_bit_field_may_straddle_its_own_storage() {
3322 assert_eq!(bit_field_byte("struct s { int x : 12; char y : 6; };"), 2);
3324 assert_eq!(
3325 bit_field_byte("struct s { int x : 12; char y : 6; } __attribute__((packed));"),
3326 1
3327 );
3328 assert_eq!(
3329 bit_field_byte("struct s { int x : 12; __attribute__((packed)) char y : 6; };"),
3330 1
3331 );
3332 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { int x : 12; char y : 6; };"), 1);
3333 assert_eq!(bit_field_byte("struct s { char x; int y : 30; };"), 4);
3335 assert_eq!(bit_field_byte("struct s { char x; int y : 30; } __attribute__((packed));"), 1);
3336 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { char x; int y : 30; };"), 1);
3338 assert_eq!(bit_field_byte("#pragma pack(2)\nstruct s { char x; int y : 30; };"), 1);
3339 }
3340
3341 fn bit_field_byte(record: &str) -> u64 {
3343 let source = format!("{record}\nint f(struct s *p) {{ return p->y; }}\n");
3344 let body = body(&source);
3345 let Some((before, _)) = body.split_once("ptr_add") else { return 0 };
3346 let (_, constant) = before.rsplit_once("iconst.i64 ").expect("an offset constant");
3347 constant.lines().next().expect("a line").trim().parse().expect("a byte offset")
3348 }
3349
3350 #[test]
3356 fn an_attribute_among_the_specifiers_is_kept_beside_the_ones_written_in_front() {
3357 tast(concat!(
3358 "struct a { char c; __attribute__((aligned(8))) int i; };\n",
3359 "_Static_assert(sizeof(struct a) == 16 && _Alignof(struct a) == 8, \"a\");\n",
3360 "_Static_assert(__builtin_offsetof(struct a, i) == 8, \"a.i\");\n",
3361 "struct b { char c; __attribute__((packed)) int i; };\n",
3362 "_Static_assert(sizeof(struct b) == 5 && _Alignof(struct b) == 1, \"b\");\n",
3363 "_Static_assert(__builtin_offsetof(struct b, i) == 1, \"b.i\");\n",
3364 "typedef struct { char c; int i; } __attribute__((packed)) c;\n",
3365 "_Static_assert(sizeof(c) == 5 && _Alignof(c) == 1, \"c\");\n",
3366 ));
3367 }
3368
3369 #[test]
3375 fn pragma_pack_caps_every_member_and_is_read_where_the_body_closes() {
3376 tast(concat!(
3377 "#pragma pack(1)\n",
3378 "struct A { char c; int i; };\n",
3379 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
3380 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
3381 "#pragma pack()\n",
3382 "struct B { char c; int i; };\n",
3383 "_Static_assert(sizeof(struct B) == 8 && _Alignof(struct B) == 4, \"B\");\n",
3384 "#pragma pack(2)\n",
3385 "struct C { char c; int i; double d; };\n",
3386 "_Static_assert(sizeof(struct C) == 14 && _Alignof(struct C) == 2, \"C\");\n",
3387 "_Static_assert(__builtin_offsetof(struct C, d) == 6, \"C.d\");\n",
3388 "struct K { char c; int i __attribute__((aligned(8))); };\n",
3390 "_Static_assert(sizeof(struct K) == 6 && _Alignof(struct K) == 2, \"K\");\n",
3391 "_Static_assert(__builtin_offsetof(struct K, i) == 2, \"K.i\");\n",
3392 "struct J { char c; int i; } __attribute__((aligned(8)));\n",
3394 "_Static_assert(sizeof(struct J) == 8 && _Alignof(struct J) == 8, \"J\");\n",
3395 "#pragma pack()\n",
3396 "#pragma pack(push, 1)\n",
3397 "struct D { char c; short s; };\n",
3398 "_Static_assert(sizeof(struct D) == 3 && _Alignof(struct D) == 1, \"D\");\n",
3399 "#pragma pack(pop)\n",
3400 "struct E { char c; short s; };\n",
3401 "_Static_assert(sizeof(struct E) == 4 && _Alignof(struct E) == 2, \"E\");\n",
3402 "struct H { char c;\n",
3404 "#pragma pack(1)\n",
3405 " int i; };\n",
3406 "_Static_assert(sizeof(struct H) == 5 && _Alignof(struct H) == 1, \"H\");\n",
3407 "#pragma pack(1)\n",
3408 "struct I { char c;\n",
3409 "#pragma pack()\n",
3410 " int i; };\n",
3411 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
3412 "#pragma pack()\n",
3413 "#pragma pack(push, 8)\n",
3415 "#pragma pack(push, 1)\n",
3416 "struct P { char c; int i; };\n",
3417 "_Static_assert(sizeof(struct P) == 5 && _Alignof(struct P) == 1, \"P\");\n",
3418 "#pragma pack(pop)\n",
3419 "struct Q { char c; int i; };\n",
3420 "_Static_assert(sizeof(struct Q) == 8 && _Alignof(struct Q) == 4, \"Q\");\n",
3421 "#pragma pack(pop)\n",
3422 "#pragma pack(16)\n",
3424 "struct R { char c; int i; };\n",
3425 "_Static_assert(sizeof(struct R) == 8 && _Alignof(struct R) == 4, \"R\");\n",
3426 "#pragma pack()\n",
3427 "#pragma pack(1)\n",
3428 "struct S { char c; int i : 5; int j : 20; };\n",
3429 "_Static_assert(sizeof(struct S) == 5 && _Alignof(struct S) == 1, \"S\");\n",
3430 "union T { char c; int i; };\n",
3431 "_Static_assert(sizeof(union T) == 4 && _Alignof(union T) == 1, \"T\");\n",
3432 "#pragma pack()\n",
3433 ));
3434 }
3435
3436 #[test]
3440 fn a_pack_line_that_is_not_one_is_reported_in_the_words_gcc_uses() {
3441 let result = run(
3442 &options(),
3443 concat!(
3444 "#pragma pack 4\n",
3445 "#pragma pack(pop)\n",
3446 "#pragma pack(3)\n",
3447 "#pragma pack(1) junk\n",
3448 "#pragma pack(push, 1\n",
3449 "#pragma pack(x)\n",
3450 "#pragma pack(0)\n",
3453 "#pragma pack(push)\n",
3454 "struct s { char c; int i; };\n",
3455 "#pragma pack(pop)\n",
3456 "#pragma pack(pop, foo)\n",
3457 ),
3458 );
3459 let expected = [
3460 "missing `(` after `#pragma pack` - ignored",
3461 "`#pragma pack (pop)` encountered without matching `#pragma pack (push)`",
3462 "alignment must be a small power of two, not 3",
3463 "junk at end of `#pragma pack`",
3464 "malformed `#pragma pack(push[, id][, <n>])` - ignored",
3465 "unknown action `x` for `#pragma pack` - ignored",
3466 "`#pragma pack(pop, foo)` encountered without matching `#pragma pack(push, foo)`",
3467 ];
3468 assert_eq!(result.messages.len(), expected.len(), "{:?}", result.messages);
3469 for (message, want) in result.messages.iter().zip(expected) {
3470 assert!(message.contains(want), "expected {want:?} in {message:?}");
3471 }
3472 }
3473
3474 #[test]
3481 fn a_declaration_behind_an_empty_macro_is_not_eaten_by_the_pragma_above_it() {
3482 let result = run(
3483 &options(),
3484 concat!(
3485 "#pragma pack(push, 1)\n",
3486 "#pragma pack(pop)\n",
3487 "#define API\n",
3488 "API const char version[] = \"3.53.4\";\n",
3489 "const char *get(void) { return version; }\n",
3490 ),
3491 );
3492 assert!(result.messages.is_empty(), "{:?}", result.messages);
3493 }
3494
3495 #[test]
3499 fn the_wide_integer_answers_to_all_three_of_its_names() {
3500 let text = tast("__uint128_t a; __int128_t b; unsigned __int128 c;\n");
3501 assert!(text.contains("decl #0 a : unsigned __int128"), "{text}");
3502 assert!(text.contains("decl #1 b : __int128"), "{text}");
3503 assert!(text.contains("decl #2 c : unsigned __int128"), "{text}");
3504 }
3505
3506 #[test]
3507 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
3508 let text = tast("long f(int a, long b) { return a + b; }\n");
3512 assert!(text.contains("convert arithmetic"), "{text}");
3513 }
3514
3515 #[test]
3516 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
3517 for source in [
3518 "#error stop\n",
3519 "int f(void) { return 1 + ; }\n",
3520 "int f(void) { return undeclared; }\n",
3521 ] {
3522 let result = run(&options(), source);
3523 assert!(result.failed(), "expected this to fail:\n{source}");
3524 assert!(
3525 result.text().is_empty(),
3526 "a file that did not compile wrote a tree:\n{source}"
3527 );
3528 }
3529 }
3530
3531 #[test]
3532 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
3533 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
3537 assert_eq!(result.errors, 1, "{:?}", result.messages);
3538 }
3539
3540 #[test]
3541 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
3542 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
3546 assert_eq!(result.errors, 1, "{:?}", result.messages);
3547 }
3548
3549 #[test]
3550 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
3551 let source = "int f(void) { char c = 300; return c; }\n";
3552 let plain = run(&options(), source);
3553 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
3554 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
3555 assert!(!plain.text().is_empty(), "a warning is not a reason to write nothing");
3556
3557 let mut opts = options();
3558 opts.warnings_are_errors = true;
3559 let strict = run(&opts, source);
3560 assert!(strict.failed());
3561 assert!(strict.text().is_empty(), "and under -Werror it is a reason to write nothing");
3562 for message in &strict.messages {
3563 assert!(!message.contains("warning:"), "{message}");
3564 }
3565 }
3566
3567 #[test]
3568 fn w_drops_the_warning_before_werror_can_promote_it() {
3569 let source = "int f(void) { char c = 300; return c; }\n";
3570 let mut opts = options();
3571 opts.warnings = false;
3572 let quiet = run(&opts, source);
3573 assert_eq!(quiet.messages, Vec::<String>::new());
3574 assert_eq!(quiet.errors, 0);
3575 assert!(!quiet.text().is_empty(), "and the file still compiles");
3576
3577 opts.warnings_are_errors = true;
3580 let both = run(&opts, source);
3581 assert_eq!(both.messages, Vec::<String>::new());
3582 assert!(!both.failed(), "-w -Werror is not an error about a warning nobody saw");
3583 }
3584
3585 #[test]
3586 fn the_dialect_reaches_the_keywords_and_the_checking() {
3587 let source = "typeof(1) x;\n";
3590 let mut opts = options();
3591 opts.std = Std::C23;
3592 opts.gnu_extensions = false;
3593 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
3594
3595 opts.std = Std::C17;
3596 assert!(run(&opts, source).failed());
3597 }
3598
3599 #[test]
3600 fn asking_for_a_kind_that_is_not_written_yet_runs_the_front_end_and_writes_nothing() {
3601 let mut opts = options();
3602 opts.emit = EmitKind::Object;
3603 let result = run(&opts, "int x = 1;\n");
3604 assert!(!result.failed(), "{:?}", result.messages);
3605 assert!(result.text().is_empty());
3606 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
3609 }
3610
3611 fn mir(source: &str) -> String {
3613 let mut opts = options();
3614 opts.emit = EmitKind::MirFinal;
3615 let result = run(&opts, source);
3616 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3617 result.text().to_owned()
3618 }
3619
3620 #[test]
3626 fn a_function_goes_from_c_to_instructions_with_real_registers_in_them() {
3627 let text = mir("int add(int a, int b) { return a + b; }\n");
3628 assert!(text.starts_with("mfunc @add {"), "{text}");
3629 assert!(text.contains("x64.add_rr_32"), "{text}");
3630 assert!(text.contains("x64.ret"), "{text}");
3631 assert!(!text.contains('%'), "{text}");
3634 }
3635
3636 #[test]
3638 fn a_function_with_no_body_produces_no_machine_function() {
3639 let text = mir("int g(int);\nint f(int a) { return g(a); }\n");
3640 assert_eq!(text.matches("mfunc @").count(), 1, "{text}");
3641 assert!(text.contains("mfunc @f {"), "{text}");
3642 assert!(text.contains("x64.call"), "{text}");
3643 }
3644
3645 #[test]
3647 fn every_definition_in_the_file_is_generated_and_they_keep_their_order() {
3648 let text = mir("int a(int x) { return x; }\nint b(int x) { return x; }\n");
3649 let first = text.find("mfunc @a").expect("the first function");
3650 let second = text.find("mfunc @b").expect("the second function");
3651 assert!(first < second, "{text}");
3652 }
3653
3654 #[test]
3656 fn the_target_decides_which_convention_the_generated_code_follows() {
3657 let mut opts = options();
3658 opts.emit = EmitKind::MirFinal;
3659 let linux = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
3660 assert!(linux.contains("$rdi"), "{linux}");
3661
3662 opts.target = "x86_64-pc-windows-msvc".parse::<Triple>().unwrap();
3663 let windows = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
3664 assert!(windows.contains("$rcx"), "{windows}");
3665 assert!(!windows.contains("$rdi"), "{windows}");
3666 }
3667
3668 #[test]
3676 fn a_tagged_member_with_no_name_is_a_member_on_windows_and_nothing_on_linux() {
3677 let source = concat!(
3678 "struct S { union U { int i; void *p; }; unsigned long tymed; };\n",
3679 "int size(void) { return sizeof(struct S); }\n",
3680 "int f(struct S *s) { s->i = 1; return s->i; }\n",
3681 );
3682
3683 let mut opts = options();
3684 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3685 let windows = run(&opts, source);
3686 assert!(windows.messages.is_empty(), "{:?}", windows.messages);
3687
3688 let linux = run(&options(), source);
3689 assert_eq!(linux.messages.len(), 3, "{:?}", linux.messages);
3690 assert!(linux.messages[0].contains("does not declare anything"), "{:?}", linux.messages);
3691
3692 let mut opts = options();
3695 opts.ms_extensions = Some(true);
3696 let asked = run(&opts, source);
3697 assert!(asked.messages.is_empty(), "{:?}", asked.messages);
3698 }
3699
3700 #[test]
3702 fn a_target_this_has_no_back_end_for_is_reported_rather_than_generated() {
3703 let mut opts = options();
3704 opts.emit = EmitKind::MirFinal;
3705 opts.target = "riscv64-unknown-linux-gnu".parse::<Triple>().unwrap();
3706 let result = run(&opts, "int f(int a) { return a; }\n");
3707 assert!(result.failed());
3708 assert!(result.messages[0].contains("no back end for riscv64"), "{:?}", result.messages);
3709 assert!(result.text().is_empty());
3710 }
3711
3712 #[test]
3715 fn an_aarch64_target_is_written_as_aarch64_assembly() {
3716 let mut opts = options();
3717 opts.emit = EmitKind::Asm;
3718 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3719 let source = "int g(int);\nint f(int a, int b) { return g(a) + b; }\n";
3720 let result = run(&opts, source);
3721 assert!(!result.failed(), "{:?}", result.messages);
3722 let text = result.text();
3723 for line in ["stp x29, x30, [sp, #-16]!", "mov x29, sp", "bl g", "ldp x29, x30, [sp], #16"]
3724 {
3725 assert!(text.contains(line), "{line} is not in\n{text}");
3726 }
3727 assert!(!text.contains('%'), "{text}");
3728 }
3729
3730 #[test]
3733 fn an_aarch64_target_reaches_an_object_file() {
3734 let mut opts = options();
3735 opts.emit = EmitKind::Object;
3736 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3737 let source = concat!(
3738 "int g(int);\n",
3739 "int table[4] = {1, 2, 3, 4};\n",
3740 "int f(int a, int b) { return g(a) + table[b & 3]; }\n",
3741 );
3742 let result = run(&opts, source);
3743 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3744 let bytes = match result.artifact {
3745 Artifact::Object { bytes, defines } => {
3746 assert_eq!(defines, ["f", "table"]);
3747 bytes
3748 }
3749 other => panic!("expected an object, got {other:?}"),
3750 };
3751 assert_eq!(&bytes[..4], b"\x7fELF");
3752 assert_eq!(&bytes[18..20], &183u16.to_le_bytes(), "EM_AARCH64");
3753
3754 opts.debug_info = true;
3757 let result = run(&opts, source);
3758 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3759 let bytes = match result.artifact {
3760 Artifact::Object { bytes, .. } => bytes,
3761 other => panic!("expected an object, got {other:?}"),
3762 };
3763 let has = |name: &[u8]| bytes.windows(name.len()).any(|at| at == name);
3764 assert!(has(b".debug_line\0") && has(b".debug_info\0"));
3765 assert!(!has(b"rucc_row"), "a row label reached the symbol table");
3766 }
3767
3768 #[test]
3772 fn an_aarch64_object_keeps_the_landing_pads_of_its_cleanups() {
3773 let mut opts = options();
3774 opts.emit = EmitKind::Object;
3775 opts.exceptions = true;
3776 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3777 let source = concat!(
3778 "void g(void);\n",
3779 "void done(int *p);\n",
3780 "void f(void) { int a __attribute__((cleanup(done))) = 1; g(); }\n",
3781 );
3782 let result = run(&opts, source);
3783 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3784 let Artifact::Object { bytes, .. } = result.artifact else { panic!("an object") };
3785 let has = |name: &[u8]| bytes.windows(name.len()).any(|at| at == name);
3786 assert!(has(b".gcc_except_table\0"), "no call site table");
3787 assert!(has(b"zPLR\0"), "no header naming the personality routine");
3788 assert!(has(b"DW.ref.__gcc_personality_v0\0"));
3789 }
3790
3791 #[test]
3794 fn the_aarch64_vector_type_names_are_declared_on_that_target_and_nowhere_else() {
3795 let mut opts = options();
3796 opts.emit = EmitKind::Asm;
3797 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3798 let source = "typedef __Float32x4_t f4;\n__SVFloat32_t sv(__SVFloat32_t, __SVBool_t);\n\
3799 int n = sizeof(f4) + sizeof(__Int8x8_t);\n\
3800 int f(f4 v) { int __Uint8x16_t = 3; return v[1] + __Uint8x16_t; }\n";
3801 let result = run(&opts, source);
3802 assert!(!result.failed(), "{:?}", result.messages);
3803 assert!(result.text().contains(".long\t24"), "{}", result.text());
3804 opts.target = "x86_64-unknown-linux-gnu".parse::<Triple>().unwrap();
3805 let result = run(&opts, "typedef __Float32x4_t f4;\n");
3806 assert!(result.failed());
3807 let result = run(&opts, "int __Float32x4_t = 1;\n");
3808 assert!(!result.failed(), "{:?}", result.messages);
3809 }
3810
3811 #[test]
3814 fn an_aarch64_result_in_memory_is_reached_through_x8() {
3815 let mut opts = options();
3816 opts.emit = EmitKind::Asm;
3817 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3818 let source = "struct big { long a, b, c; };\nstruct big make(long v);\n\
3819 long f(long v) { return make(v).c; }\n\
3820 struct big g(long v) { struct big b = { v, v, v }; return b; }\n";
3821 let result = run(&opts, source);
3822 assert!(!result.failed(), "{:?}", result.messages);
3823 let text = result.text();
3824 assert!(text.contains("x8"), "{text}");
3825 assert!(text.contains("bl make"), "{text}");
3826 }
3827
3828 #[test]
3831 fn an_aarch64_remainder_is_a_division_and_a_multiply_subtract() {
3832 let mut opts = options();
3833 opts.emit = EmitKind::Asm;
3834 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3835 let source = "int s(int a, int b) { return a % b; }\n\
3836 unsigned long u(unsigned long a, unsigned long b) { return a % b; }\n";
3837 let result = run(&opts, source);
3838 assert!(!result.failed(), "{:?}", result.messages);
3839 let text = result.text();
3840 let at = |what: &str| text.find(what).unwrap_or_else(|| panic!("{what} is not in\n{text}"));
3841 assert!(at("sdiv w") < at("msub w"), "{text}");
3842 assert!(at("udiv x") < at("msub x"), "{text}");
3843 }
3844
3845 #[test]
3848 fn an_aarch64_jump_table_is_reached_with_adr() {
3849 let mut opts = options();
3850 opts.emit = EmitKind::Asm;
3851 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3852 let source = "int f(int x) { switch (x) { case 0: return 10; case 1: return 21; \
3853 case 2: return 32; case 3: return 43; case 4: return 54; case 5: return 65; \
3854 case 6: return 76; case 7: return 87; case 8: return 98; case 9: return 9; \
3855 case 10: return 19; case 11: return 29; default: return 0; } }\n";
3856 let result = run(&opts, source);
3857 assert!(!result.failed(), "{:?}", result.messages);
3858 let text = result.text();
3859 let at = |what: &str| text.find(what).unwrap_or_else(|| panic!("{what} is not in\n{text}"));
3860 assert!(at("adr x") < at("ldrsw x"), "{text}");
3861 assert!(at("ldrsw x") < at("br x"), "{text}");
3862 assert!(text.contains("_j0:"), "{text}");
3863 assert!(text.contains(".long"), "{text}");
3864 }
3865
3866 #[test]
3870 fn an_aarch64_va_start_writes_the_five_fields_of_its_list() {
3871 let mut opts = options();
3872 opts.emit = EmitKind::Asm;
3873 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3874 let source = "typedef __builtin_va_list va_list;\n\
3875 int f(int n, ...) { va_list ap; __builtin_va_start(ap, n); \
3876 int x = __builtin_va_arg(ap, int); double d = __builtin_va_arg(ap, double); \
3877 __builtin_va_end(ap); return x + (int)d; }\n";
3878 let result = run(&opts, source);
3879 assert!(!result.failed(), "{:?}", result.messages);
3880 let text = result.text();
3881 assert!(text.contains("#-56"), "{text}");
3882 assert!(text.contains("#-128"), "{text}");
3883 assert!(text.contains("#24]"), "{text}");
3884 assert!(text.contains("#28]"), "{text}");
3885 assert!(text.contains("str q"), "{text}");
3886 }
3887
3888 #[test]
3891 fn an_aarch64_long_double_is_a_quad_in_a_vector_register() {
3892 let mut opts = options();
3893 opts.emit = EmitKind::Asm;
3894 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3895 let source = "void f(long double *p, long double x) { *p = *p + x; }\n";
3896 let result = run(&opts, source);
3897 assert!(!result.failed(), "{:?}", result.messages);
3898 let text = result.text();
3899 assert!(text.contains("ldr q"), "{text}");
3900 assert!(text.contains("str q"), "{text}");
3901 assert!(text.contains("__addtf3"), "{text}");
3902 }
3903
3904 #[test]
3907 fn an_aarch64_thread_local_is_reached_through_tpidr_el0() {
3908 let mut opts = options();
3909 opts.emit = EmitKind::Asm;
3910 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3911 let source = "__thread int n;\nint *f(void) { return &n; }\n\
3912 void *g(void) { return __builtin_thread_pointer(); }\n";
3913 let result = run(&opts, source);
3914 assert!(!result.failed(), "{:?}", result.messages);
3915 let text = result.text();
3916 assert!(text.contains(":gottprel:n"), "{text}");
3917 assert!(text.contains(":gottprel_lo12:n]"), "{text}");
3918 assert_eq!(text.matches("mrs x").count(), 2, "{text}");
3919 assert!(text.contains("tpidr_el0"), "{text}");
3920 }
3921
3922 #[test]
3925 fn a_darwin_thread_local_is_reached_through_its_descriptor() {
3926 let source = "__thread int n = 5;\nint *f(void) { return &n; }\n";
3927 for (triple, wanted) in [
3928 ("aarch64-apple-darwin", &["_n@TLVPPAGE\n", "_n@TLVPPAGEOFF]\n", "\tblr x"][..]),
3929 ("x86_64-apple-darwin", &["_n@TLVP(%rip), %rdi\n", "\tcall\t*%"][..]),
3930 ] {
3931 let mut opts = options();
3932 opts.emit = EmitKind::Asm;
3933 opts.target = triple.parse::<Triple>().unwrap();
3934 let result = run(&opts, source);
3935 assert!(!result.failed(), "{triple}: {:?}", result.messages);
3936 let text = result.text();
3937 for want in wanted {
3938 assert!(text.contains(want), "{triple} wanted {want:?}:\n{text}");
3939 }
3940 assert!(text.contains("\n_n:\n\t.quad\t__tlv_bootstrap\n"), "{text}");
3941 assert!(!text.contains("tpidr_el0") && !text.contains("%fs"), "{text}");
3942 }
3943 }
3944
3945 #[test]
3947 fn the_thread_pointer_is_refused_on_darwin() {
3948 let mut opts = options();
3949 opts.emit = EmitKind::Asm;
3950 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3951 let result = run(&opts, "void *f(void) { return __builtin_thread_pointer(); }\n");
3952 assert!(result.failed());
3953 assert!(result.messages[0].contains("thread pointer"), "{:?}", result.messages);
3954 }
3955
3956 #[test]
3959 fn a_darwin_variadic_definition_saves_nothing_and_walks_the_stack() {
3960 let mut opts = options();
3961 opts.emit = EmitKind::Asm;
3962 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3963 let source = "int f(int n, ...) { __builtin_va_list ap; __builtin_va_start(ap, n);\n\
3964 int r = __builtin_va_arg(ap, int); __builtin_va_end(ap); return r; }\n";
3965 let result = run(&opts, source);
3966 assert!(!result.failed(), "{:?}", result.messages);
3967 let text = result.text();
3968 assert!(!text.contains("str q"), "{text}");
3969 assert!(!text.contains("x7"), "{text}");
3970 }
3971
3972 #[test]
3975 fn a_darwin_call_puts_its_variadic_arguments_in_memory() {
3976 let mut opts = options();
3977 opts.emit = EmitKind::Asm;
3978 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3979 let source = "int printf(const char *, ...);\n\
3980 int g(double x) { return printf(\"%d %f\", 7, x); }\n";
3981 let result = run(&opts, source);
3982 assert!(!result.failed(), "{:?}", result.messages);
3983 let text = result.text();
3984 assert!(text.contains("str d0, [sp, #8]"), "{text}");
3985 }
3986
3987 #[test]
3991 fn a_darwin_listing_is_one_apples_assembler_reads() {
3992 let mut opts = options();
3993 opts.emit = EmitKind::Asm;
3994 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3995 opts.common = Some(false);
3998 let source = "extern int ext;\n\
3999 int g[4];\n\
4000 int f(int i) { return g[i] + ext; }\n";
4001 let result = run(&opts, source);
4002 assert!(!result.failed(), "{:?}", result.messages);
4003 let text = result.text();
4004 assert!(text.contains(", _g@PAGE\n"), "{text}");
4005 assert!(text.contains(", _g@PAGEOFF\n"), "{text}");
4006 assert!(text.contains(", _ext@GOTPAGE\n"), "{text}");
4007 assert!(text.contains(", _ext@GOTPAGEOFF]\n"), "{text}");
4008 assert!(!text.contains(":lo12:"), "{text}");
4009 assert!(text.contains("\t.globl\t_g\n\t.zerofill\t__DATA,__bss,_g,16,2\n"), "{text}");
4010 }
4011
4012 #[test]
4018 fn a_signed_char_on_aarch64_is_widened_with_its_sign_before_it_is_added_to() {
4019 for target in ["aarch64-linux-gnu", "aarch64-apple-darwin"] {
4020 let mut opts = options();
4021 opts.emit = EmitKind::Asm;
4022 opts.target = target.parse::<Triple>().unwrap();
4023 let source = "int f(signed char *p) { return *p + 1; }\n\
4024 unsigned g(unsigned short *p) { return *p + 1u; }\n";
4025 let result = run(&opts, source);
4026 assert!(!result.failed(), "{:?}", result.messages);
4027 let text = result.text();
4028 let signed = text.contains("\tsxtb w") || text.contains("\tldrsb w");
4029 assert!(signed, "{target}: {text}");
4030 }
4031 }
4032
4033 #[test]
4045 fn a_construct_the_back_end_cannot_reach_yet_is_reported_against_its_function() {
4046 let mut opts = options();
4047 opts.emit = EmitKind::MirFinal;
4048 let source = "void a(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
4049 s; s.x = 1; v[0] = s.x; }\n\
4050 void b(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
4051 s; s.x = 1; v[0] = s.x; }\n";
4052 let result = run(&opts, source);
4053 assert!(result.failed());
4054 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
4055 assert!(result.messages[0].contains("cannot generate code for 'a'"), "{:?}", result);
4056 assert!(result.messages[0].contains("wants more alignment"), "{:?}", result);
4057 assert!(result.messages[1].contains("cannot generate code for 'b'"), "{:?}", result);
4058 assert!(result.text().is_empty());
4059 }
4060
4061 #[test]
4069 fn a_variable_length_array_walks_its_pages_where_every_page_of_the_frame_is_to_be_touched() {
4070 let mut opts = options();
4071 opts.emit = EmitKind::MirFinal;
4072 let source = "void a(int n) { int v[n]; v[0] = 1; }\n";
4073 let plain = run(&opts, source);
4074 assert!(!plain.failed(), "{:?}", plain.messages);
4075 assert!(!plain.text().contains("cmp_set_a_64"), "{}", plain.text());
4076
4077 opts.stack_clash = true;
4078 let result = run(&opts, source);
4079 assert!(!result.failed(), "{:?}", result.messages);
4080 assert!(result.text().contains("cmp_set_a_64"), "{}", result.text());
4081 assert!(result.text().contains("or_mi_8"), "{}", result.text());
4082 }
4083
4084 #[test]
4094 fn a_function_that_keeps_a_frame_pointer_on_windows_reaches_an_object_file() {
4095 let mut opts = options();
4096 opts.emit = EmitKind::Object;
4097 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
4098 let source = concat!(
4099 "void use(void *p);\n",
4100 "void array(int n) { int v[n]; v[0] = 1; use(v); }\n",
4101 "void taken(unsigned long n) { use(__builtin_alloca(n)); }\n",
4102 );
4103 let result = run(&opts, source);
4104 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
4105 let bytes = match result.artifact {
4106 Artifact::Object { bytes, .. } => bytes,
4107 other => panic!("expected an object, got {other:?}"),
4108 };
4109 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
4110
4111 let mut opts = options();
4114 opts.emit = EmitKind::Object;
4115 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
4116 }
4117
4118 #[test]
4129 fn the_address_of_a_function_this_file_only_declares_reaches_a_windows_object() {
4130 let source = concat!(
4131 "void other(void *p);\n",
4132 "void takes(void (*f)(void *));\n",
4133 "void (*held)(void *);\n",
4134 "void pass(void) { takes(other); }\n",
4135 "void keep(void) { held = other; }\n",
4136 "void call(void) { other(0); }\n",
4137 );
4138 let mut opts = options();
4139 opts.emit = EmitKind::Object;
4140 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
4141 let result = run(&opts, source);
4142 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
4143 let bytes = match result.artifact {
4144 Artifact::Object { bytes, .. } => bytes,
4145 other => panic!("expected an object, got {other:?}"),
4146 };
4147 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
4148
4149 let mut opts = options();
4152 opts.emit = EmitKind::Object;
4153 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
4154 }
4155
4156 #[test]
4160 fn a_pragma_comment_asks_the_windows_linker_for_a_library() {
4161 let source = concat!(
4162 "#pragma comment(lib, \"ws2_32\")\n",
4163 "#pragma comment(lib, \"my lib\")\n",
4164 "#pragma comment(lib, \"libz.a\")\n",
4165 "#pragma comment(linker, \"/include:x\")\n",
4166 "#pragma comment(user, \"nobody reads this\")\n",
4167 "int f(void) { return 0; }\n",
4168 );
4169 let wanted =
4170 " /DEFAULTLIB:ws2_32.lib /DEFAULTLIB:\"my lib.lib\" /DEFAULTLIB:libz.a /include:x";
4171 let mut opts = options();
4172 opts.emit = EmitKind::Object;
4173 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
4174 let result = run(&opts, source);
4175 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
4176 let Artifact::Object { bytes, .. } = result.artifact else { panic!("expected an object") };
4177 assert!(bytes.windows(wanted.len()).any(|at| at == wanted.as_bytes()), "no options");
4178
4179 opts.emit = EmitKind::Asm;
4180 let text = match run(&opts, source).artifact {
4181 Artifact::Text(text) => text,
4182 other => panic!("expected a listing, got {other:?}"),
4183 };
4184 assert!(text.contains("\t.ascii\t\" /DEFAULTLIB:\\\"my lib.lib\\\"\"\n"), "{text}");
4185
4186 let mut opts = options();
4187 opts.emit = EmitKind::Asm;
4188 let result = run(&opts, source);
4189 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
4190 let Artifact::Text(text) = result.artifact else { panic!("expected a listing") };
4191 assert!(!text.contains("DEFAULTLIB"), "{text}");
4192
4193 let result = run(&opts, "#pragma comment(lib)\nint f(void) { return 0; }\n");
4195 assert!(result.messages.iter().any(|m| m.contains("wants a string")), "{result:?}");
4196 }
4197
4198 #[test]
4212 fn an_opcode_with_no_name_in_the_rule_language_is_named_by_its_own_spelling() {
4213 let mut opts = options();
4214 opts.emit = EmitKind::MirFinal;
4215 let source =
4216 "long double f(int a) {\n __int128 wide = a;\n return (long double) wide;\n}\n";
4217 let result = run(&opts, source);
4218 assert!(result.failed());
4219 assert!(
4220 result.messages[0].contains("no rule lowers a `sext` producing a `i128`"),
4221 "{result:?}"
4222 );
4223 assert!(result.messages[0].contains(":2:"), "the line the widening is on: {result:?}");
4224 assert!(!result.messages[0].contains("this instruction"), "{result:?}");
4225 }
4226
4227 #[test]
4229 fn the_note_on_unfinished_work_points_at_the_issues_rather_than_at_the_plan() {
4230 let mut opts = options();
4231 opts.emit = EmitKind::MirFinal;
4232 let source = "long double f(int a) { __int128 wide = a; return (long double) wide; }\n";
4233 let result = run(&opts, source);
4234 assert!(result.failed());
4235 let note = result.messages.iter().find(|line| line.contains("note:")).expect("a note");
4236 assert!(note.contains("https://github.com/tamnd/rucc/issues"), "{note}");
4237 assert!(!note.contains("spec/17-milestones.md"), "{note}");
4238 }
4239
4240 #[test]
4242 fn the_frame_flags_on_the_command_line_reach_the_generated_frame() {
4243 let source = "int f(int a) { return a; }\n";
4244 assert!(!mir(source).contains("$rbp"), "a leaf needs no frame pointer when told so");
4245
4246 let mut opts = options();
4247 opts.emit = EmitKind::MirFinal;
4248 opts.frame_pointer = Some(true);
4249 let kept = run(&opts, source).text().to_owned();
4250 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
4251
4252 opts.frame_pointer = None;
4254 let kept = run(&opts, source).text().to_owned();
4255 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
4256 }
4257
4258 fn asm(source: &str) -> String {
4260 let mut opts = options();
4261 opts.emit = EmitKind::Asm;
4262 let result = run(&opts, source);
4263 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4264 result.text().to_owned()
4265 }
4266
4267 #[test]
4274 fn a_function_goes_from_c_to_assembly_an_assembler_would_take() {
4275 let text = asm("int add(int a, int b) { return a + b; }\n");
4276 assert!(text.contains("\t.globl\tadd\n"), "{text}");
4277 assert!(text.contains("\t.type\tadd, @function\n"), "{text}");
4278 assert!(text.contains("\nadd:\n"), "{text}");
4279 assert!(text.contains("\taddl\t"), "{text}");
4280 assert!(text.contains("\tret\n"), "{text}");
4281 assert!(text.contains("\t.size\tadd, .-add\n"), "{text}");
4282 assert!(text.contains(".note.GNU-stack"), "{text}");
4285 }
4286
4287 #[test]
4293 fn a_call_through_a_function_pointer_goes_through_the_register_it_is_in() {
4294 let text = asm("int g(int);\nint f(int (*p)(int), int a) { return p(a) + g(a); }\n");
4295 assert!(text.contains("\tcall\t*%"), "{text}");
4296 assert!(text.contains("\tcall\tg\n"), "{text}");
4297 assert!(text.contains("%rdi"), "{text}");
4301 }
4302
4303 #[test]
4307 fn the_address_of_a_global_is_read_from_the_instruction_pointer() {
4308 let text = asm("extern int counter;\nint f(void) { return counter; }\n");
4309 assert!(text.contains("\tmovl\tcounter(%rip), %eax\n"), "{text}");
4310 }
4311
4312 #[test]
4321 fn a_branch_on_a_comparison_jumps_on_the_opposite_of_what_it_compared() {
4322 let arms = "return 1; return 2;";
4323 let signed = [("==", "jne"), ("!=", "je"), ("<", "jge"), ("<=", "jg"), (">", "jle")];
4324 for (operator, jump) in signed.into_iter().chain([(">=", "jl")]) {
4325 let text = asm(&format!("int f(int a, int b) {{ if (a {operator} b) {arms} }}\n"));
4326 assert!(
4327 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
4328 "{operator}: {text}"
4329 );
4330 assert!(!text.contains("\tset"), "{operator}: {text}");
4331 assert!(!text.contains("\ttest"), "{operator}: {text}");
4332 }
4333 let unsigned = [("<", "jae"), ("<=", "ja"), (">", "jbe"), (">=", "jb")];
4334 for (operator, jump) in unsigned {
4335 let source =
4336 format!("int f(unsigned a, unsigned b) {{ if (a {operator} b) {arms} }}\n");
4337 let text = asm(&source);
4338 assert!(
4339 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
4340 "{operator}: {text}"
4341 );
4342 }
4343
4344 let text = asm("int f(int a) { if (a < 7) return 1; return 2; }\n");
4347 assert!(text.contains("\tcmpl\t$7, %edi\n\tjge\t"), "{text}");
4348 }
4349
4350 #[test]
4356 fn a_comparison_whose_answer_the_program_wanted_still_writes_a_byte() {
4357 let text = asm("int f(int a, int b) { return a < b; }\n");
4358 assert!(text.contains("\tsetl\t"), "{text}");
4359 }
4360
4361 fn optimized(source: &str) -> String {
4363 let mut opts = options();
4364 opts.emit = EmitKind::Asm;
4365 opts.opt_level = rucc_session::OptLevel::O2;
4366 let result = run(&opts, source);
4367 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4368 result.text().to_owned()
4369 }
4370
4371 #[test]
4373 fn opt_info_says_what_each_switch_became_and_a_forced_shape_is_what_it_says() {
4374 let arms: String = (0..40)
4375 .map(|k| format!("case {}: return g({k});", k * 17))
4376 .collect::<Vec<_>>()
4377 .join(" ");
4378 let source = format!("int g(int);\nint f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n");
4379 let said = |shape: Option<&str>| {
4380 let mut opts = options();
4381 opts.emit = EmitKind::Asm;
4382 opts.opt_level = rucc_session::OptLevel::O2;
4383 opts.opt_info = vec![String::new()];
4384 opts.switch_shape = shape.map(str::to_owned);
4385 let result = run(&opts, &source);
4386 assert_eq!(result.messages, Vec::<String>::new());
4387 let lines: Vec<String> = result
4388 .remarks
4389 .lines()
4390 .filter(|line| line.contains("[switch-lowering]"))
4391 .map(str::to_owned)
4392 .collect();
4393 assert_eq!(lines.len(), 1, "{}", result.remarks);
4394 lines[0].clone()
4395 };
4396 assert!(said(None).contains(": f: optimized: switch of 40 cases lowered as a tree;"));
4397 assert!(said(Some("table")).contains("lowered as a table;"));
4398 assert!(said(Some("walk")).contains("lowered as a walk;"));
4399 }
4400
4401 #[test]
4411 fn a_switch_whose_arms_are_a_function_of_the_label_is_a_range_check_and_arithmetic() {
4412 let arms: String =
4413 (0..16).map(|k| format!("case {k}: return {};", k + 1)).collect::<Vec<_>>().join(" ");
4414 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
4415 assert!(text.contains("\tcmpl\t$15, %edi\n\tja\t"), "{text}");
4416 assert!(text.contains("\taddl\t$1, %edi"), "{text}");
4417 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
4418 }
4419
4420 #[test]
4428 fn a_dense_switch_whose_arms_are_not_a_line_is_a_load_from_a_table() {
4429 let arms: String = (0..16)
4430 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
4431 .collect::<Vec<_>>()
4432 .join(" ");
4433 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
4434 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
4435 assert!(!text.contains("\tjmp\t*"), "{text}");
4436 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
4437 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
4438 let section = text[..text.find("CSWTCH.0:").unwrap_or(0)].rfind("\t.section\t.rodata");
4439 assert!(section.is_some(), "{text}");
4440 assert_eq!(table.matches("\t.long\t").count(), 16, "{text}");
4441 assert!(table.contains("\t.long\t100\n"), "{text}");
4442 }
4443
4444 #[test]
4450 fn a_switch_whose_arms_give_strings_is_a_table_of_how_far_away_they_are() {
4451 let text = optimized(
4452 "const char *f(int k) { switch (k) { case 0: return \"zero\"; \
4453 case 1: return \"one\"; case 2: return \"two\"; case 3: return \"three\"; } \
4454 return \"many\"; }\n",
4455 );
4456 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
4457 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
4458 assert!(!text.contains(".data.rel.ro"), "{text}");
4459 let at = text.find("CSWTCH.0:").expect("the table is in the output");
4460 assert!(text[..at].rfind("\t.section\t.rodata").is_some(), "{text}");
4461 let table = &text[at..];
4462 assert_eq!(table.matches(" - .\n").count(), 4, "{text}");
4463 assert!(table.contains("\t.long\t.Lstr.1+4 - .\n"), "{text}");
4464 }
4465
4466 #[test]
4472 fn a_table_at_os_has_cells_as_narrow_as_its_answers() {
4473 let arms: String = (0..16)
4474 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
4475 .collect::<Vec<_>>()
4476 .join(" ");
4477 let mut opts = options();
4478 opts.emit = EmitKind::Asm;
4479 opts.opt_level = rucc_session::OptLevel::Os;
4480 let result = run(&opts, &format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
4481 assert_eq!(result.messages, Vec::<String>::new());
4482 let text = result.text();
4483 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
4484 assert_eq!(table.matches("\t.byte\t").count(), 16, "{text}");
4485 assert!(text.contains("\tmovsbl\t"), "{text}");
4486 }
4487
4488 #[test]
4495 fn a_table_that_covers_its_operand_has_no_range_check() {
4496 let text = optimized(
4497 "int f(unsigned x) { switch (x & 3) { case 0: return 5; case 1: return 9; \
4498 case 2: return 2; case 3: return 7; } return -1; }\n",
4499 );
4500 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
4501 assert!(!text.contains("\tcmp"), "{text}");
4502 assert!(!text.contains("$-1"), "{text}");
4503 }
4504
4505 #[test]
4510 fn a_store_to_a_local_the_loop_just_read_is_a_conditional_move() {
4511 let text = optimized(
4512 "int f(const int *v, int n, int k) { int best[8] = {0}; \
4513 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; \
4514 return best[k & 7]; }\n",
4515 );
4516 assert!(text.contains("\tcmovgl"), "{text}");
4517 assert!(!text.contains("\tset"), "{text}");
4518 assert!(!text.contains("\ttestb"), "{text}");
4519 }
4520
4521 #[test]
4523 fn a_store_to_a_global_the_loop_just_read_keeps_its_branch() {
4524 let text = optimized(
4525 "int best[8]; void f(const int *v, int n) { \
4526 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; }\n",
4527 );
4528 assert!(!text.contains("\tcmov"), "{text}");
4529 }
4530
4531 #[test]
4539 fn a_conversion_from_a_constant_double_is_the_number_it_converts_to() {
4540 let text = optimized("int f(void) { double d = 2.75; return (int) d; }\n");
4541 assert!(text.contains("movl\t$2, %eax"), "{text}");
4542 assert!(!text.contains("cvttsd2si"), "{text}");
4543 }
4544
4545 #[test]
4553 fn a_slot_of_a_read_only_table_is_the_value_the_table_holds() {
4554 let text =
4555 optimized("static const int t[4] = {10, 20, 30, 40};\nint f(void) { return t[2]; }\n");
4556 assert!(text.contains("movl\t$30, %eax"), "{text}");
4557 assert!(!text.contains("t(%rip)"), "{text}");
4558 }
4559
4560 #[test]
4563 fn a_byte_of_a_read_only_string_is_the_byte_the_string_spells() {
4564 let text = optimized("static const char s[] = \"abc\";\nint f(void) { return s[1]; }\n");
4565 assert!(text.contains("movl\t$98, %eax"), "{text}");
4566 }
4567
4568 #[test]
4572 fn a_table_that_is_not_read_only_keeps_its_load() {
4573 let text = optimized(
4574 "static int t[4] = {10, 20, 30, 40};\nvoid g(int x) { t[2] = x; }\nint f(void) { return t[2]; }\n",
4575 );
4576 assert!(!text.contains("movl\t$30, %eax"), "{text}");
4577 }
4578
4579 #[test]
4586 fn a_call_guarded_by_a_condition_a_read_only_object_settles_is_not_emitted() {
4587 let text = optimized(
4588 "void link_error(void);\nconst double one = 1.0;\nint main(void) { if ((int) one != 1) link_error(); return 0; }\n",
4589 );
4590 assert!(!text.contains("call\tlink_error"), "{text}");
4591 }
4592
4593 #[test]
4595 fn a_cast_between_a_pointer_and_an_integer_leaves_the_value_where_it_is() {
4596 let text = asm("long f(void *p) { return (long)p; }\n");
4597 for line in text.lines().filter(|line| line.starts_with('\t') && !line.contains('.')) {
4602 let mnemonic = line.split_whitespace().next().unwrap_or("");
4603 assert!(matches!(mnemonic, "movq" | "ret"), "{line} in\n{text}");
4604 }
4605 }
4606
4607 #[test]
4611 fn an_argument_past_the_last_register_is_read_out_of_the_caller_s_stack() {
4612 let six = "long a, long b, long c, long d, long e, long f";
4613 let text = asm(&format!("long f({six}, long g, long h) {{ return g + h; }}\n"));
4614
4615 assert!(text.contains("\tmovq\t8(%rsp), "), "{text}");
4622 assert!(text.contains("\taddq\t16(%rsp), "), "{text}");
4623
4624 let narrow = asm(&format!("int f({six}, int g) {{ return g; }}\n"));
4628 assert!(narrow.contains("\tmovl\t8(%rsp), "), "{narrow}");
4629 let eight =
4630 "double a, double b, double c, double d, double e, double f, double g, double h";
4631 let float = asm(&format!("double f({eight}, double i) {{ return i; }}\n"));
4632 assert!(float.contains("\tmovsd\t8(%rsp), "), "{float}");
4633 }
4634
4635 #[test]
4638 fn a_call_writes_the_arguments_with_no_register_left_at_the_stack_pointer() {
4639 let six = "1, 2, 3, 4, 5, 6";
4640 let decl = "long g(long, long, long, long, long, long, long, long);\n";
4641 let text = asm(&format!("{decl}long f(void) {{ return g({six}, 7, 8); }}\n"));
4642
4643 assert!(text.contains("\tmovq\t%"), "{text}");
4644 assert!(text.contains(", (%rsp)\n"), "{text}");
4645 assert!(text.contains(", 8(%rsp)\n"), "{text}");
4646 assert!(text.contains("\tsubq\t$"), "{text}");
4648
4649 let narrow = "int g(int, int, int, int, int, int, int);\n";
4651 let text = asm(&format!("{narrow}int f(void) {{ return g({six}, 7); }}\n"));
4652 assert!(text.contains("\tmovl\t%"), "{text}");
4653 assert!(text.contains(", (%rsp)\n"), "{text}");
4654 }
4655
4656 #[test]
4659 fn a_variadic_call_counts_registers_and_not_arguments() {
4660 let nine = "1., 2., 3., 4., 5., 6., 7., 8., 9.";
4661 let decl = "int g(int, ...);\n";
4662 let text = asm(&format!("{decl}int f(void) {{ return g(0, {nine}); }}\n"));
4663
4664 assert!(text.contains("\tmovl\t$8, "), "eight registers, not nine: {text}");
4665 assert!(text.contains("\tmovsd\t%"), "{text}");
4666 assert!(text.contains(", (%rsp)\n"), "{text}");
4667 }
4668
4669 #[test]
4674 fn a_variadic_function_writes_the_argument_registers_it_was_handed_into_its_frame() {
4675 let body =
4676 "__builtin_va_list ap; __builtin_va_start(ap, n); __builtin_va_end(ap); return n;";
4677 let text = asm(&format!("int f(int n, ...) {{ {body} }}\n"));
4678
4679 let stores = |mnemonic: &str| text.matches(&format!("\t{mnemonic}\t%")).count();
4682 assert!(text.contains(", 8(%r"), "the second slot, not the first: {text}");
4683 assert!(!text.contains(", 0(%r"), "{text}");
4684 assert_eq!(stores("movaps"), 8, "every vector register: {text}");
4687 assert_eq!(stores("movsd"), 0, "and the whole of each one: {text}");
4688
4689 assert!(text.contains("\tsubq\t$"), "{text}");
4691 }
4692
4693 #[test]
4696 fn va_start_writes_the_four_fields_the_psabi_describes() {
4697 let start = "__builtin_va_list ap; __builtin_va_start(ap, d);";
4698 let params = "int a, int b, int c, double d";
4699 let text = asm(&format!("int f({params}, ...) {{ {start} return a; }}\n"));
4700
4701 assert!(text.contains(" movl $24, "), "{text}");
4705 assert!(text.contains(" movl $64, "), "{text}");
4706 assert!(text.contains(", 8(%r"), "{text}");
4710 assert!(text.contains(", 16(%r"), "{text}");
4711 let frame: u32 = text
4712 .lines()
4713 .find_map(|line| line.trim().strip_prefix("subq $")?.split(',').next()?.parse().ok())
4714 .expect("a variadic function takes a frame for the save area");
4715 let above = |line: &str| {
4716 let at: u32 = line.trim().strip_prefix("leaq ")?.split('(').next()?.parse().ok()?;
4717 Some(at > frame)
4718 };
4719 assert!(text.lines().filter_map(above).any(|it| it), "{frame}: {text}");
4720 }
4721
4722 #[test]
4725 fn va_arg_branches_on_whether_the_argument_is_still_in_the_save_area() {
4726 let read = "__builtin_va_list ap; __builtin_va_start(ap, n);";
4727 let ints = format!("int f(int n, ...) {{ {read} return __builtin_va_arg(ap, int); }}\n");
4728 let text = asm(&ints);
4729
4730 assert!(text.contains("$40, "), "{text}");
4733 assert!(text.contains(" cmpl "), "{text}");
4734 assert!(text.contains(" ja "), "{text}");
4738
4739 let arg = "__builtin_va_arg(ap, double)";
4740 let text = asm(&format!("double f(int n, ...) {{ {read} return {arg}; }}\n"));
4741 assert!(text.contains("$160, "), "the last vector slot: {text}");
4742 }
4743
4744 #[test]
4747 fn a_structure_assignment_is_a_move_for_each_word_of_it() {
4748 let decl = "struct pair { long a, b; };\n";
4749 let body = "struct pair p = *q; return p.a + p.b;";
4750 let text = asm(&format!("{decl}long f(struct pair *q) {{ {body} }}\n"));
4751
4752 assert!(!text.contains("memcpy"), "nothing calls the library: {text}");
4753 assert!(!text.contains("\tcall"), "{text}");
4754 assert!(text.matches("\tmovq\t").count() >= 4, "two words each way: {text}");
4756 }
4757
4758 #[test]
4761 fn how_wide_a_word_of_a_copy_is_follows_the_alignment() {
4762 let decl = "struct bytes { char a[8]; };\n";
4763 let body = "struct bytes p = *q; return p.a[0];";
4764 let text = asm(&format!("{decl}int f(struct bytes *q) {{ {body} }}\n"));
4765
4766 assert!(text.matches("\tmovb\t").count() >= 16, "a byte at a time: {text}");
4768 }
4769
4770 #[test]
4773 fn the_part_of_an_initialiser_that_names_nothing_is_stored_as_zero() {
4774 let decl = "struct wide { long a, b, c; };\n";
4775 let text = asm(&format!("{decl}long f(void) {{ struct wide w = {{ 7 }}; return w.c; }}\n"));
4776
4777 assert!(!text.contains("memset"), "nothing calls the library: {text}");
4778 assert!(text.contains("\tmovq\t$0, ") || text.contains("\txorl\t"), "the zero: {text}");
4783 }
4784
4785 #[test]
4788 fn a_copy_too_large_to_unroll_calls_the_runtime() {
4789 let decl = "struct huge { char a[4096]; };\n";
4790 let mut opts = options();
4791 opts.emit = EmitKind::Asm;
4792 let source = format!("{decl}void f(struct huge *p, struct huge *q) {{ *p = *q; }}\n");
4793 let result = run(&opts, &source);
4794 assert!(!result.failed(), "{:?}", result.messages);
4795 let text = result.text();
4796 assert!(text.contains("call") && text.contains("memcpy"), "{text}");
4797 assert!(text.contains("4096"), "the size travels: {text}");
4800 }
4801
4802 #[test]
4809 fn a_structure_too_large_to_unroll_is_copied_into_the_argument_area_by_the_runtime() {
4810 let decl = "struct huge { char a[4096]; };\nint take(struct huge);\n";
4811 let text = asm(&format!("{decl}int f(struct huge *p) {{ return take(*p); }}\n"));
4812
4813 let copy = text.find("call\tmemcpy").expect("the copy");
4814 let call = text.find("call\ttake").expect("the call");
4815 assert!(copy < call, "the copy comes first: {text}");
4816 assert!(text.contains("movq\t%rsp, %rdi"), "the destination: {text}");
4821 assert!(text.contains("$4096, %edx"), "the size: {text}");
4822 }
4823
4824 #[test]
4827 fn a_realigned_frame_reads_them_through_the_frame_pointer() {
4828 let six = "long a, long b, long c, long d, long e, long f";
4829 let body = "_Alignas(32) long wide[4]; wide[0] = g; return wide[0];";
4830 let text = asm(&format!("long f({six}, long g) {{ {body} }}\n"));
4831
4832 assert!(text.contains("\tandq\t$-32, %rsp"), "{text}");
4836 assert!(text.contains("\tmovq\t16(%rbp), "), "{text}");
4837 assert!(!text.contains("\tmovq\t16(%rsp), "), "{text}");
4838 }
4839
4840 #[test]
4842 fn the_target_decides_how_the_assembly_is_spelled() {
4843 let mut opts = options();
4844 opts.emit = EmitKind::Asm;
4845 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
4846 let text = run(&opts, "int f(void) { return 0; }\n").text().to_owned();
4847 assert!(text.contains("__TEXT,__text"), "{text}");
4848 assert!(text.contains("\n_f:\n"), "{text}");
4849 assert!(!text.contains(".note.GNU-stack"), "{text}");
4850 }
4851
4852 fn obj(source: &str) -> Vec<u8> {
4854 let mut opts = options();
4855 opts.emit = EmitKind::Object;
4856 let result = run(&opts, source);
4857 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4858 match result.artifact {
4859 Artifact::Object { bytes, .. } => bytes,
4860 other => panic!("expected an object, got {other:?}"),
4861 }
4862 }
4863
4864 #[test]
4870 fn a_function_goes_from_c_to_an_object_a_linker_would_take() {
4871 let bytes = obj("int add(int a, int b) { return a + b; }\n");
4872 assert_eq!(&bytes[..4], b"\x7fELF", "an object file starts by saying it is one");
4873 let text = asm("int add(int a, int b) { return a + b; }\n");
4874 assert!(
4875 text.contains("\taddl\t"),
4876 "and the listing of it is the same instructions:\n{text}"
4877 );
4878 }
4879
4880 #[test]
4882 fn a_variable_goes_from_c_to_the_section_it_belongs_in() {
4883 let text = asm("int counter = 42;\nstatic int hidden;\nconst int fixed = 7;\n");
4884 assert!(text.contains("\t.data\n\t.globl\tcounter\n"), "{text}");
4885 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
4886 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
4887 assert!(text.contains("\t.bss\n\t.p2align\t2\n"), "{text}");
4890 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
4891 assert!(!text.contains(".globl\thidden"), "{text}");
4892 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4895 }
4896
4897 #[test]
4904 fn a_bit_field_initializer_writes_every_byte_of_the_value_and_not_only_the_ones_that_are_set() {
4905 let text = asm("struct s { unsigned f : 20; } x = { 0x12300 };\n");
4906 assert!(text.contains("\t.data\n"), "there is something to write: {text}");
4907 assert!(text.contains("\nx:\n\t.ascii\t\"\\000#\\001\"\n"), "and it is the value: {text}");
4908
4909 let text = asm("struct s { unsigned a : 8; unsigned b : 8; } x = { 0, 3 };\n");
4912 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\003\"\n"), "{text}");
4913
4914 let text = asm("struct s { unsigned long long f : 40; } x = { 0x100000 };\n");
4917 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\000\\020\"\n\t.space\t5\n"), "{text}");
4918
4919 let text = asm("struct s { unsigned f : 20; } x = { 0 };\n");
4921 assert!(text.contains("\t.bss\n"), "an object of zeroes is zeroes: {text}");
4922 assert!(text.contains("\nx:\n\t.space\t4\n"), "{text}");
4923 }
4924
4925 #[test]
4927 fn a_string_literal_is_a_variable_with_a_name_no_program_could_write() {
4928 let text = asm("const char *f(void) { return \"hi\"; }\n");
4929 assert!(text.contains("\t.ascii\t\"hi\\000\"\n"), "{text}");
4930 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4931 let label = text
4932 .lines()
4933 .find(|line| line.starts_with(".Lstr"))
4934 .unwrap_or_else(|| panic!("a label for the literal in\n{text}"));
4935 assert!(!text.contains(&format!(".globl\t{}", label.trim_end_matches(':'))), "{text}");
4936 }
4937
4938 #[test]
4940 fn an_address_in_an_initializer_is_left_to_the_linker() {
4941 let source = "int counter;\nint *p = &counter;\n";
4942 let text = asm(source);
4943 assert!(text.contains("\np:\n\t.quad\tcounter\n"), "{text}");
4944 let bytes = obj(source);
4947 assert!(bytes.windows(8).any(|w| w == b"counter\0"), "the object has to name it");
4948 }
4949
4950 #[test]
4959 fn a_constant_holding_an_address_goes_in_the_section_the_loader_may_write_once() {
4960 let text = asm("static void a(void) {}\nstatic void b(void) {}\n\
4963 struct m { void (*x)(void); void (*y)(void); };\n\
4964 const struct m t = { a, b };\n");
4965 assert!(text.contains("\t.section\t.data.rel.ro.local,\"aw\",@progbits\n"), "{text}");
4966 assert!(text.contains("\nt:\n\t.quad\ta\n\t.quad\tb\n"), "{text}");
4967
4968 let text =
4971 asm("void a(void);\nstruct m { void (*x)(void); };\nconst struct m t = { a };\n");
4972 assert!(text.contains("\t.section\t.data.rel.ro,\"aw\",@progbits\n"), "{text}");
4973
4974 let text = asm("const int fixed = 7;\n");
4976 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4977 }
4978
4979 #[test]
4986 fn a_thread_local_variable_is_storage_a_thread_gets_a_copy_of_and_an_offset_into_it() {
4987 let text = asm("_Thread_local int x = 1;\nint read(void) { return x; }\n");
4988 assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
4991 assert!(text.contains("\t.type\tx, @tls_object\n"), "{text}");
4992 assert!(text.contains("x@GOTTPOFF(%rip)"), "{text}");
4995 assert!(text.contains("%fs:0"), "{text}");
4996 }
4997
4998 #[test]
5004 fn the_address_of_this_thread_s_own_storage_is_read_out_of_the_segment_register() {
5005 let text = asm("void *here(void) { return __builtin_thread_pointer(); }\n");
5006 assert!(text.contains("movq\t%fs:0, "), "{text}");
5007 assert!(!text.contains("GOTTPOFF"), "{text}");
5009 }
5010
5011 #[test]
5022 fn a_prefetch_is_one_of_four_instructions_and_the_locality_is_what_picks() {
5023 for (locality, wanted) in
5024 [(0, "prefetchnta"), (1, "prefetcht2"), (2, "prefetcht1"), (3, "prefetcht0")]
5025 {
5026 let source =
5027 format!("void warm(void *p) {{ __builtin_prefetch(p, 0, {locality}); }}\n");
5028 let text = asm(&source);
5029 assert!(text.contains(&format!("\t{wanted}\t")), "locality {locality}: {text}");
5030 }
5031 let text = asm("void warm(void *p) { __builtin_prefetch(p); }\n");
5033 assert!(text.contains("\tprefetcht0\t"), "{text}");
5034 let text = asm("void warm(void *p) { __builtin_prefetch(p, 1); }\n");
5037 assert!(text.contains("\tprefetcht0\t"), "{text}");
5038 assert!(!text.contains("prefetchw"), "{text}");
5039 }
5040
5041 #[test]
5044 fn an_aarch64_prefetch_is_a_prfm_that_says_the_locality_and_whether_it_writes() {
5045 let mut opts = options();
5046 opts.emit = EmitKind::Asm;
5047 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
5048 for (write, kind) in [(0, "pld"), (1, "pst")] {
5049 for (locality, wanted) in [(0, "l1strm"), (1, "l3keep"), (2, "l2keep"), (3, "l1keep")] {
5050 let source = format!(
5051 "void warm(void *p) {{ __builtin_prefetch(p, {write}, {locality}); }}\n"
5052 );
5053 let result = run(&opts, &source);
5054 assert_eq!(result.messages, Vec::<String>::new(), "{source}");
5055 let text = result.text();
5056 assert!(text.contains("prfm"), "{source}{text}");
5057 assert!(text.contains(&format!("{kind}{wanted}, [x0]")), "{source}{text}");
5058 }
5059 }
5060 }
5061
5062 #[test]
5073 fn a_trap_is_the_instruction_the_machine_has_no_meaning_for() {
5074 let text = asm("void stop(void) { __builtin_trap(); }\n");
5075 assert!(text.contains("\tud2\n"), "{text}");
5076 assert!(!text.contains("\tcall"), "a stop is not a call to anything: {text}");
5077
5078 let text = asm("int stop(int a) { __builtin_trap(); return a + 1; }\n");
5079 assert!(text.contains("\tud2\n"), "{text}");
5080 assert!(text.contains("\taddl\t"), "the block goes on after a stop: {text}");
5081 }
5082
5083 #[test]
5087 fn cpu_init_is_a_call_to_the_libgcc_function_that_fills_in_the_model() {
5088 let text = asm("void start(void) { __builtin_cpu_init(); }\n");
5089 assert!(text.contains("\tcall\t__cpu_indicator_init"), "{text}");
5090 assert!(!text.contains("__builtin_cpu_init"), "{text}");
5091 }
5092
5093 #[test]
5096 fn the_time_stamp_counter_is_a_call_into_the_builtins_archive() {
5097 let text = asm("unsigned long long f(void) { return __builtin_ia32_rdtsc(); }\n");
5098 assert!(text.contains("\tcall\t__rucc_ia32_rdtsc"), "{text}");
5099 assert!(!text.contains("__builtin_ia32_rdtsc"), "{text}");
5100 let text =
5101 asm("unsigned long long f(unsigned int *aux) { return __builtin_ia32_rdtscp(aux); }\n");
5102 assert!(text.contains("\tcall\t__rucc_ia32_rdtscp"), "{text}");
5103 assert!(!text.contains("__builtin_ia32_rdtscp"), "{text}");
5104 }
5105
5106 #[test]
5114 fn cpu_supports_is_a_bit_of_the_words_libgcc_fills_in() {
5115 let text = asm("int f(void) { return __builtin_cpu_supports(\"sse4.2\"); }\n");
5116 assert!(text.contains("__cpu_model"), "{text}");
5117 assert!(text.contains("12(%"), "the fourth word of the model: {text}");
5118 assert!(text.contains("$256"), "{text}");
5119 assert!(!text.contains("\tcall"), "the answer is a read and not a call: {text}");
5120
5121 let text = asm("int f(void) { return __builtin_cpu_supports(\"vpclmulqdq\"); }\n");
5122 assert!(text.contains("__cpu_features2"), "{text}");
5123 assert!(text.contains("$2,"), "{text}");
5124
5125 let text = asm("int f(void) { return __builtin_cpu_supports(\"xsave\"); }\n");
5126 assert!(text.contains("__cpu_features2"), "{text}");
5127 assert!(text.contains("4(%"), "the second word of the second object: {text}");
5128 assert!(text.contains("$131072"), "{text}");
5129
5130 let text = asm("int f(void) { return __builtin_cpu_supports(\"avx512vbmi2\"); }\n");
5131 assert!(text.contains("set"), "the top bit is answered as a one: {text}");
5132 }
5133
5134 #[test]
5137 fn cpu_is_compares_one_word_of_the_model_with_a_number() {
5138 let text = asm("int f(void) { return __builtin_cpu_is(\"amd\"); }\n");
5139 assert!(text.contains("__cpu_model"), "{text}");
5140 assert!(text.contains("$2,"), "{text}");
5141
5142 let text = asm("int f(void) { return __builtin_cpu_is(\"znver4\"); }\n");
5143 assert!(text.contains("8(%"), "the subtype is the third word: {text}");
5144 assert!(text.contains("$29,"), "{text}");
5145 }
5146
5147 #[test]
5151 fn a_cpu_builtin_takes_a_name_it_knows_written_as_a_literal() {
5152 let mut opts = options();
5153 opts.emit = EmitKind::Ir;
5154 for (source, wanted) in [
5155 (
5156 "int f(const char *s) { return __builtin_cpu_supports(s); }\n",
5157 "parameter to builtin must be a string constant or literal",
5158 ),
5159 (
5160 "int f(void) { return __builtin_cpu_supports(\"sse5\"); }\n",
5161 "parameter to builtin not valid: sse5",
5162 ),
5163 (
5164 "int f(void) { return __builtin_cpu_is(\"sse\"); }\n",
5165 "parameter to builtin not valid: sse",
5166 ),
5167 ] {
5168 let result = run(&opts, source);
5169 assert!(
5170 result.messages.iter().any(|m| m.contains(wanted)),
5171 "{source}{:?}",
5172 result.messages
5173 );
5174 }
5175 let text = asm("int f(void) { return __builtin_cpu_supports((const char *)\"avx2\"); }\n");
5177 assert!(text.contains("$1024"), "{text}");
5178
5179 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
5180 for source in [
5181 "void f(void) { __builtin_cpu_init(); }\n",
5182 "int f(void) { return __builtin_cpu_supports(\"sse4.2\"); }\n",
5183 ] {
5184 let result = run(&opts, source);
5185 assert!(
5186 result.messages.iter().any(|m| m.contains("only available on x86-64")),
5187 "{source}{:?}",
5188 result.messages
5189 );
5190 }
5191 }
5192
5193 #[test]
5205 fn assume_aligned_is_its_first_argument_and_keeps_the_rest() {
5206 let text = asm("void *aligned(char *p) { return __builtin_assume_aligned(p, 16); }\n");
5207 assert!(!text.contains("assume_aligned"), "{text}");
5208 assert!(!text.contains("\tcall"), "nothing is called for an alignment fact: {text}");
5209
5210 let source = "unsigned long width(void);\n\
5211 void *aligned(char *p) { return __builtin_assume_aligned(p, width()); }\n";
5212 let text = asm(source);
5213 assert!(!text.contains("assume_aligned"), "{text}");
5214 assert!(text.contains("width"), "the argument that is not the answer still runs: {text}");
5215 }
5216
5217 #[test]
5227 fn the_frame_address_is_the_frame_pointer_after_walking_that_many_links() {
5228 let text = asm("void *here(void) { return __builtin_frame_address(0); }\n");
5229 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
5230 assert!(text.contains("movq\t%rbp, %rax"), "{text}");
5231 assert!(!text.contains("\tcall"), "a frame address is not a call to anything: {text}");
5232
5233 let walk = |depth: u32| {
5234 let source = format!("void *up(void) {{ return __builtin_frame_address({depth}); }}\n");
5235 asm(&source).matches("movq\t(%r").count()
5236 };
5237 assert_eq!(walk(1), 1, "one link is one load");
5238 assert_eq!(walk(3), 3, "three links are three loads");
5239 }
5240
5241 #[test]
5251 fn the_return_address_is_one_word_above_the_frame_the_walk_ended_at() {
5252 let text = asm("void *back(void) { return __builtin_return_address(0); }\n");
5253 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
5254 assert!(text.contains("movq\t8(%rbp), %rax"), "{text}");
5255 assert!(!text.contains("\tcall"), "a return address is not a call to anything: {text}");
5256
5257 let text = asm("void *back(void) { return __builtin_return_address(2); }\n");
5258 assert_eq!(text.matches("movq\t(%r").count(), 2, "two links are two loads: {text}");
5259 assert!(text.contains("movq\t8(%r"), "and the answer is above the last of them: {text}");
5260 }
5261
5262 #[test]
5273 fn a_depth_that_is_not_a_small_constant_is_refused() {
5274 let mut opts = options();
5275 opts.emit = EmitKind::Ir;
5276 for source in [
5277 "void *up(int n) { return __builtin_return_address(n); }\n",
5278 "void *up(void) { return __builtin_frame_address(1000); }\n",
5279 ] {
5280 let messages = run(&opts, source).messages;
5281 let named = messages.iter().any(|m| m.contains("E0705"));
5282 assert!(named, "expected a refusal in {messages:?}");
5283 }
5284 }
5285
5286 #[test]
5298 fn an_alloca_takes_the_bytes_off_the_stack_pointer_and_answers_where_they_are() {
5299 let text =
5300 asm("void use(void *p); void f(unsigned long n) { use(__builtin_alloca(n)); }\n");
5301 assert!(text.contains("andq\t$-16"), "the size is rounded up to sixteen: {text}");
5302 assert!(text.contains("subq\t%rdi, %rsp"), "and taken off the stack pointer: {text}");
5303 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
5304
5305 let plain = concat!(
5308 "extern void *alloca(__SIZE_TYPE__);\n",
5309 "void use(void *p);\n",
5310 "void f(unsigned long n) { use(alloca(n)); }\n",
5311 );
5312 let text = asm(plain);
5313 assert!(text.contains("subq\t%rdi, %rsp"), "the plain name is the same bytes: {text}");
5314 assert_eq!(text.matches("\tcall").count(), 1, "and is not a call either: {text}");
5315
5316 let own = concat!(
5319 "static void *alloca(unsigned long n) { return 0; }\n",
5320 "void *f(unsigned long n) { return alloca(n); }\n",
5321 );
5322 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
5323 }
5324
5325 #[test]
5340 fn a_builtin_the_program_never_declared_is_the_builtin_rather_than_the_one_c89_wrote_down() {
5341 let mut opts = options();
5345 opts.permissive = true;
5346 let undeclared = "void use(void *p);
5347void f(unsigned long n) { use(alloca(n)); }
5348";
5349 assert_eq!(
5350 run(&opts, undeclared).messages,
5351 [
5352 "/main.c:2:31: warning: implicit declaration of function 'alloca' [E0521]",
5353 "/main.c:2:31: warning: incompatible implicit declaration of built-in function \
5354 'alloca' [E0713]",
5355 ]
5356 );
5357
5358 opts.emit = EmitKind::Asm;
5359 let text = run(&opts, undeclared).text().to_owned();
5360 assert!(text.contains("subq\t%rdi, %rsp"), "the bytes come off the stack: {text}");
5361 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
5362
5363 let string = "unsigned long f(void) { return strlen(\"abc\"); }\n";
5366 let text = run(&opts, string).text().to_owned();
5367 assert!(text.contains("call\tstrlen"), "strlen is still a call: {text}");
5368
5369 let own = concat!(
5372 "static void *alloca(unsigned long n) { return 0; }\n",
5373 "void *f(unsigned long n) { return alloca(n); }\n",
5374 );
5375 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
5376 }
5377
5378 #[test]
5388 fn the_bytes_an_alloca_took_are_still_there_at_the_end_of_the_block_that_took_them() {
5389 let inner = "{ use(__builtin_alloca(n)); }";
5390 for body in [inner.to_owned(), format!("int a[n]; {inner} use(a);")] {
5391 let source = format!("void use(void *p);\nvoid f(unsigned long n) {{ {body} }}\n");
5392 let text = asm(&source);
5393 for line in text.lines().filter(|line| line.trim_end().ends_with(", %rsp")) {
5397 let taking = line.contains("subq");
5398 let leaving = line.contains("%rbp");
5399 assert!(taking || leaving, "nothing puts the stack back: {line} in {text}");
5400 }
5401 }
5402 }
5403
5404 #[test]
5406 fn the_object_and_the_listing_are_two_spellings_of_one_compilation() {
5407 let source = "int callee(void); int g(void) { return callee(); }\n";
5411 let bytes = obj(source);
5412 assert!(
5413 bytes.windows(7).any(|w| w == b"callee\0"),
5414 "the object has to name the callee for the linker to find it"
5415 );
5416 let text = asm(source);
5417 assert!(text.contains("\tcall\tcallee\n"), "{text}");
5418 }
5419
5420 #[test]
5426 fn compiling_for_an_executable_produces_an_object_and_not_a_dump() {
5427 let mut opts = options();
5428 opts.emit = EmitKind::Executable;
5430 let result = run(&opts, "int main(void) { return 0; }\n");
5431 assert_eq!(result.messages, Vec::<String>::new());
5432 match result.artifact {
5433 Artifact::Object { bytes, .. } => assert_eq!(&bytes[..4], b"\x7fELF"),
5434 other => panic!("expected an object, got {other:?}"),
5435 }
5436 }
5437
5438 #[test]
5440 fn a_platform_with_no_object_writer_is_said_so_rather_than_written_as_elf() {
5441 let mut opts = options();
5442 opts.emit = EmitKind::Object;
5443 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
5444 let result = run(&opts, "int f(void) { return 0; }\n");
5445 assert!(result.failed(), "an object nobody can read is worse than a message");
5446 assert!(
5447 result.messages.iter().any(|m| m.contains("no object writer")),
5448 "{:?}",
5449 result.messages
5450 );
5451 }
5452
5453 fn ir(source: &str) -> String {
5455 let mut opts = options();
5456 opts.emit = EmitKind::Ir;
5457 let result = run(&opts, source);
5458 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5459 result.text().to_owned()
5460 }
5461
5462 fn errors(source: &str) -> Vec<String> {
5464 let mut opts = options();
5465 opts.emit = EmitKind::Ir;
5466 let result = run(&opts, source);
5467 assert!(result.failed(), "expected this to be refused:\n{source}");
5468 result.messages
5469 }
5470
5471 fn body(source: &str) -> String {
5473 let text = ir(source);
5474 let (_, rest) = text.split_once("{\n").expect("a function definition");
5475 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
5476 body.to_owned()
5477 }
5478
5479 #[test]
5487 fn gnu89_inline_is_what_decides_whether_a_bare_inline_definition_reaches_the_module() {
5488 let source = "inline int f(int x) { return x + 1; }\n";
5489 let with = |flag: bool| {
5490 let mut opts = options();
5491 opts.emit = EmitKind::Ir;
5492 opts.gnu89_inline = flag;
5493 let result = run(&opts, source);
5494 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5495 result.text().to_owned()
5496 };
5497
5498 assert!(!with(false).contains("block0"), "no body: {}", with(false));
5501
5502 assert!(with(true).contains("block0"), "a body: {}", with(true));
5505 }
5506
5507 #[test]
5514 fn an_access_through_a_type_names_the_type_it_went_through() {
5515 let source = "\
5516struct s { int a; float b; };\n\
5517union u { int i; float f; };\n\
5518int scalar(int *p) { return *p; }\n\
5519float member(struct s *p) { p->a = 1; return p->b; }\n\
5520int element(int *a, long i) { return a[i]; }\n\
5521float through_a_union(union u *p) { p->i = 1; return p->f; }\n";
5522 let text = ir(source);
5523 assert!(text.contains(r#"!0 = tbaa "char""#), "the root: {text}");
5524 assert!(text.contains(r#"tbaa "int", parent !0"#), "int under it: {text}");
5525 assert!(text.contains(r#"tbaa "float", parent !0"#), "float under it: {text}");
5526 let named = text.lines().filter(|line| line.contains(", tbaa !")).count();
5529 assert_eq!(named, 6, "six accesses: {text}");
5530 }
5531
5532 #[test]
5539 fn turning_strict_aliasing_off_leaves_the_type_off_every_access() {
5540 let source = "int punned(float *f, int *i) { *i = 1; *f = 2.0f; return *i; }\n";
5541 let mut opts = options();
5542 opts.emit = EmitKind::Ir;
5543 opts.strict_aliasing = false;
5544 let result = run(&opts, source);
5545 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5546 let text = result.text().to_owned();
5547 assert!(!text.contains("tbaa"), "not even the root: {text}");
5548 }
5549
5550 #[test]
5556 fn instrumenting_functions_calls_the_hooks_around_every_body_but_the_hooks() {
5557 let source = concat!(
5558 "#define NOCHK __attribute__((no_instrument_function))\n",
5559 "void __cyg_profile_func_enter(void *, void *) NOCHK;\n",
5560 "void __cyg_profile_func_exit(void *, void *) NOCHK;\n",
5561 "int calls;\n",
5562 "int pick(int x) { if (x) return 1; return 2; }\n",
5563 "void quiet(void) NOCHK;\n",
5564 "void quiet(void) { calls++; }\n",
5565 "void __cyg_profile_func_enter(void *fn, void *site) { calls++; }\n",
5566 "void __cyg_profile_func_exit(void *fn, void *site) { calls--; }\n",
5567 );
5568 let mut opts = options();
5569 opts.emit = EmitKind::Ir;
5570 opts.instrument_functions = true;
5571 let result = run(&opts, source);
5572 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5573 let text = result.text().to_owned();
5574 let body = |name: &str| -> String {
5575 let open = format!("func @{name}(");
5576 let start = text.find(&open).unwrap_or_else(|| panic!("no {name}: {text}"));
5577 let rest = &text[start..];
5578 rest[..rest.find("\n}").unwrap_or(rest.len())].to_owned()
5579 };
5580 let pick = body("pick");
5581 assert_eq!(pick.matches("call @__cyg_profile_func_enter(").count(), 1, "{pick}");
5582 assert_eq!(pick.matches("call @__cyg_profile_func_exit(").count(), 2, "{pick}");
5583 assert!(pick.contains("return_address"), "{pick}");
5584 assert!(pick.contains("global_addr @pick"), "{pick}");
5585 for quiet in ["quiet", "__cyg_profile_func_enter", "__cyg_profile_func_exit"] {
5586 assert!(!body(quiet).contains("call "), "{quiet} is left alone: {text}");
5587 }
5588
5589 opts.instrument_functions = false;
5590 let result = run(&opts, source);
5591 assert!(!result.text().contains("call @__cyg_profile"), "off unless asked for");
5592 }
5593
5594 #[test]
5599 fn calls_that_owe_the_same_handlers_share_one_landing_pad() {
5600 let source = concat!(
5601 "void done(int *p);\n",
5602 "void work(int);\n",
5603 "void f(void) {\n",
5604 " int a __attribute__((cleanup(done))) = 1;\n",
5605 " work(1); work(2);\n",
5606 " { int b __attribute__((cleanup(done))) = 2; work(3); work(4); }\n",
5607 " work(5); work(6);\n",
5608 "}\n",
5609 );
5610 let mut opts = options();
5611 opts.emit = EmitKind::Ir;
5612 opts.exceptions = true;
5613 let result = run(&opts, source);
5614 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5615 let text = result.text();
5616 assert_eq!(text.matches("= unwound").count(), 8, "every call has its edge: {text}");
5619 assert_eq!(text.matches("= landing").count(), 2, "one pad for a, one for b and a: {text}");
5620 }
5621
5622 #[test]
5626 fn a_goto_that_runs_handlers_gives_each_one_an_edge_to_what_is_still_owed() {
5627 let source = concat!(
5628 "void done(int *p);\n",
5629 "void f(int n) {\n",
5630 " int a __attribute__((cleanup(done))) = 1;\n",
5631 " { int b __attribute__((cleanup(done))) = 2;\n",
5632 " { int c __attribute__((cleanup(done))) = 3; if (n) goto out; }\n",
5633 " }\n",
5634 "out:\n",
5635 " return;\n",
5636 "}\n",
5637 );
5638 let mut opts = options();
5639 opts.emit = EmitKind::Ir;
5640 opts.exceptions = true;
5641 let result = run(&opts, source);
5642 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5643 let text = result.text();
5644 assert_eq!(text.matches("= unwound").count(), 5, "{text}");
5647 assert_eq!(text.matches("= landing").count(), 2, "{text}");
5648 }
5649
5650 #[test]
5656 fn a_call_an_unwind_would_leave_a_cleanup_behind_gets_a_landing_pad_under_exceptions() {
5657 let source = concat!(
5658 "void done(int *p);\n",
5659 "void work(void);\n",
5660 "void calls(void) { int x __attribute__((cleanup(done))) = 1; work(); }\n",
5661 "int quiet(int y) { int x __attribute__((cleanup(done))) = y; return x + 1; }\n",
5662 "void after(void) { { int x __attribute__((cleanup(done))) = 1; } work(); }\n",
5663 );
5664 let mut opts = options();
5665 opts.emit = EmitKind::Ir;
5666 let result = run(&opts, source);
5667 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5668 assert!(!result.text().contains("landing"), "{}", result.text());
5669
5670 opts.exceptions = true;
5671 let result = run(&opts, source);
5672 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5673 let text = result.text();
5674 assert_eq!(text.matches("= landing").count(), 1, "only the call in calls: {text}");
5675 assert!(text.contains("_Unwind_Resume"), "{text}");
5676
5677 opts.emit = EmitKind::Asm;
5678 let result = run(&opts, source);
5679 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5680 let text = result.text();
5681 assert!(text.contains(".cfi_personality 0x9b,DW.ref.__gcc_personality_v0"), "{text}");
5682 assert!(text.contains(".cfi_lsda 0x1b,.LLSDA_calls"), "{text}");
5683 assert!(text.contains(".gcc_except_table"), "{text}");
5684 assert_eq!(text.matches(".cfi_lsda").count(), 1, "{text}");
5685
5686 opts.emit = EmitKind::Object;
5687 let result = run(&opts, source);
5688 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5689 let bytes = result.artifact.bytes();
5690 let has = |what: &[u8]| bytes.windows(what.len()).any(|window| window == what);
5691 assert!(has(b".gcc_except_table\0"), "the call site table has a section");
5692 assert!(has(b"zPLR\0"), "a header naming the personality routine");
5693 assert!(has(b"zR\0"), "and the plain one for the functions with no pad");
5694 assert!(has(b"DW.ref.__gcc_personality_v0\0"), "the pointer the header reads through");
5695
5696 opts.emit = EmitKind::Ir;
5699 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
5700 let result = run(&opts, source);
5701 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
5702 assert!(result.messages[0].contains("landing pad"), "{:?}", result.messages);
5703 assert!(result.messages[0].contains(":3:"), "the call in calls: {:?}", result.messages);
5704 }
5705
5706 #[test]
5712 fn an_asm_at_file_scope_with_an_instruction_in_it_is_assembled() {
5713 let source = concat!(
5714 "extern void vide(void);\n",
5715 "__asm__(\".text;.globl _us;_us:;movl $0x1234ABCD, %eax;ret\");\n",
5716 "__asm__(\"vide: ret\");\n",
5717 "unsigned short _us(void);\n",
5718 "int main(void) { vide(); return _us() == 0xABCD ? 0 : 1; }\n",
5719 );
5720 let mut opts = options();
5721 opts.emit = EmitKind::Ir;
5722 let result = run(&opts, source);
5723 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5724 assert_eq!(result.text().matches("module asm ").count(), 2, "{}", result.text());
5725
5726 opts.emit = EmitKind::Asm;
5727 let result = run(&opts, source);
5728 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5729 let text = result.text();
5730 assert!(text.contains("#APP\nvide: ret\n#NO_APP\n"), "{text}");
5731 let main = text.find("main:").expect("main");
5732 assert!(text.find("#NO_APP").expect("the markers") < main, "templates first: {text}");
5733
5734 opts.emit = EmitKind::Object;
5735 let result = run(&opts, source);
5736 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5737 let (bytes, defines) = match result.artifact {
5738 Artifact::Object { bytes, defines } => (bytes, defines),
5739 other => panic!("expected an object, got {other:?}"),
5740 };
5741 assert!(defines.iter().any(|name| name == "_us"), "{defines:?}");
5742 let us = [0xb8, 0xcd, 0xab, 0x34, 0x12, 0xc3];
5744 assert!(bytes.windows(us.len()).any(|window| window == us), "the template's bytes");
5745
5746 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
5748 opts.emit = EmitKind::Ir;
5749 let result = run(&opts, source);
5750 assert!(!result.messages.is_empty(), "refused on Mach-O");
5751 assert!(result.messages[0].contains("the instruction 'movl'"), "{:?}", result.messages);
5752 }
5753
5754 #[test]
5762 fn a_bare_return_from_a_function_that_promised_a_value_gives_back_a_zero() {
5763 let mut opts = options();
5764 opts.emit = EmitKind::Ir;
5765 opts.std = Std::C89;
5766 let compiled = |source: &str| {
5767 let result = run(&opts, source);
5768 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
5769 result.text().to_owned()
5770 };
5771
5772 let text = compiled("int f(int x) { if (x) return; return 3; }\n");
5773 assert!(text.contains("iconst.i32 0\n return"), "zero goes back: {text}");
5774 assert!(!text.contains("unreachable"), "the branch that reached it is kept: {text}");
5775
5776 let text = compiled("double f(int x) { if (x) return; return 1.0; }\n");
5778 assert!(text.contains("fconst.f64 0x0\n return"), "a float zero goes back: {text}");
5779 }
5780
5781 #[test]
5789 fn a_call_to_a_name_nothing_declared_declares_it_as_c89_said_to() {
5790 let mut opts = options();
5791 opts.emit = EmitKind::Ir;
5792 opts.std = Std::C89;
5793 let compiled = |source: &str| {
5794 let result = run(&opts, source);
5795 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
5796 result.text().to_owned()
5797 };
5798
5799 let text = compiled("int f(void) { return g(); }\n");
5801 assert!(text.contains("call @g"), "the call is to the name that was written: {text}");
5802 assert!(text.contains("i32"), "and it gives back an int: {text}");
5803
5804 let text = compiled("int f(char c) { return g(c); }\n");
5807 assert!(text.contains("sext.i32"), "the argument is promoted: {text}");
5808
5809 let mut opts = options();
5812 opts.std = Std::C89;
5813 let said = run(&opts, "int f(void) { return h; }\n").messages.join("\n");
5814 assert!(said.contains("'h' undeclared"), "not a call, so not declared: {said}");
5815 }
5816
5817 #[test]
5827 fn a_name_called_before_it_is_defined_still_gets_its_definition() {
5828 let mut opts = options();
5829 opts.emit = EmitKind::Ir;
5830 opts.std = Std::C89;
5831 let text = run(&opts, "int f(void) { return dummy(); }\ndummy () { return 7; }\n")
5832 .text()
5833 .to_owned();
5834 assert!(text.contains("func @f()"), "the caller is there: {text}");
5835 assert!(text.contains("func @dummy"), "and so is what it calls: {text}");
5836 assert!(text.contains("iconst.i32 7"), "with the body it was given: {text}");
5837 }
5838
5839 #[test]
5847 fn an_old_style_parameter_is_converted_from_what_the_call_promoted_it_to() {
5848 let mut opts = options();
5849 opts.emit = EmitKind::Ir;
5850 opts.std = Std::C89;
5851 let compiled = |source: &str| run(&opts, source).text().to_owned();
5852
5853 let text = compiled("f (c) unsigned char c; { return c; }\n");
5854 assert!(text.contains("func @f(i32"), "an int arrives: {text}");
5855 assert!(text.contains("trunc.i8"), "and is cut down to what was declared: {text}");
5856 assert!(text.contains("zext.i32"), "then read back unsigned: {text}");
5857
5858 let text = compiled("f (s) short s; { return s; }\n");
5860 assert!(text.contains("trunc.i16"), "cut down: {text}");
5861 assert!(text.contains("sext.i32"), "and read back signed: {text}");
5862
5863 let text = compiled("f (x) float x; { return x * 2; }\n");
5866 assert!(text.contains("func @f(f64"), "a double arrives: {text}");
5867 assert!(text.contains("fptrunc.f32"), "and is narrowed to the float: {text}");
5868
5869 let text = compiled("int f(unsigned char c) { return c; }\n");
5872 assert!(text.contains("func @f(i8)"), "the declared type arrives: {text}");
5873 assert!(!text.contains("trunc"), "so there is nothing to cut down: {text}");
5874 }
5875
5876 #[test]
5885 fn the_rules_gcc_promoted_are_decided_by_the_dialect_and_by_fpermissive() {
5886 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
5888 let cases = [
5889 ("static counted;\n", ["", "error", "warning", "error"]),
5890 ("int f(void) { return g(); }\n", ["", "error", "warning", "error"]),
5891 ("int f(x) { return x; }\n", ["", "error", "warning", "error"]),
5892 ("int *p;\nvoid h(void) { p = 1; }\n", ["warning", "error", "warning", "error"]),
5893 (
5894 "char *q;\nint *r;\nvoid k(void) { r = q; }\n",
5895 ["warning", "error", "warning", "error"],
5896 ),
5897 ("int f(void) { return; }\n", ["", "error", "warning", "error"]),
5898 ("void g(void) { return 1; }\n", ["warning", "error", "warning", "error"]),
5899 ];
5900
5901 for (source, wanted) in cases {
5902 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
5903 let mut opts = options();
5904 opts.std = std;
5905 opts.permissive = permissive;
5906 let said = run(&opts, source).messages.join("\n");
5907 let severity = if said.contains(": error: ") {
5908 "error"
5909 } else if said.contains(": warning: ") {
5910 "warning"
5911 } else {
5912 ""
5913 };
5914 let how = if permissive { " -fpermissive" } else { "" };
5915 assert_eq!(
5916 severity,
5917 wanted,
5918 "under -std={}{how}, {source} was answered with `{said}`",
5919 std.as_str()
5920 );
5921 if wanted.is_empty() {
5922 assert!(said.is_empty(), "nothing to say, but said `{said}`");
5923 }
5924 }
5925 }
5926 }
5927
5928 #[test]
5937 fn the_three_variadic_builtins_answer_a_bad_list_the_way_a_call_answers_a_bad_argument() {
5938 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
5939 let cases = [
5940 (
5941 "int f(int n, ...) { char *p; return __builtin_va_arg(p, int); }\n",
5942 "first argument to 'va_arg' not of type 'va_list'",
5943 ["error", "error", "error", "error"],
5944 ),
5945 (
5946 "void f(int n, ...) { char *p; __builtin_va_start(p, n); }\n",
5947 "passing argument 1 of '__builtin_va_start' from incompatible pointer type",
5948 ["warning", "error", "warning", "error"],
5949 ),
5950 (
5951 "void f(int n, ...) { int x; __builtin_va_end(x); }\n",
5952 "passing argument 1 of '__builtin_va_end' makes pointer from integer without a \
5953 cast",
5954 ["warning", "error", "warning", "error"],
5955 ),
5956 (
5957 "void f(int n, ...) { __builtin_va_list a; char *p; __builtin_va_copy(a, p); }\n",
5958 "passing argument 2 of '__builtin_va_copy' from incompatible pointer type",
5959 ["warning", "error", "warning", "error"],
5960 ),
5961 ];
5962
5963 for (source, message, wanted) in cases {
5964 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
5965 let mut opts = options();
5966 opts.std = std;
5967 opts.permissive = permissive;
5968 let said = run(&opts, source).messages.join("\n");
5969 let how = if permissive { " -fpermissive" } else { "" };
5970 assert!(
5971 said.contains(&format!(": {wanted}: {message}")),
5972 "under -std={}{how}, {source} was answered with `{said}`",
5973 std.as_str()
5974 );
5975 }
5976 }
5977 }
5978
5979 fn safe_ir(tier: rucc_session::Safety, source: &str) -> String {
5981 let mut opts = options();
5982 opts.emit = EmitKind::Ir;
5983 opts.safety = tier;
5984 let result = run(&opts, source);
5985 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5986 result.text().to_owned()
5987 }
5988
5989 const READS_THROUGH_A_POINTER: &str = "int read(int *p) { return p[1]; }\n";
5990
5991 fn padded_ir(padding: Padding, source: &str) -> String {
5993 let mut opts = options();
5994 opts.emit = EmitKind::Ir;
5995 opts.safety = rucc_session::Safety::Detect;
5996 opts.padding = padding;
5997 let result = run(&opts, source);
5998 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5999 result.text().to_owned()
6000 }
6001
6002 const FILLS_A_RECORD_A_MEMBER_AT_A_TIME: &str = "struct padded { char tag; int value; };\n\
6003 void fill(struct padded *p) { p->tag = 1; p->value = 2; }\n";
6004
6005 #[test]
6006 fn a_record_filled_a_member_at_a_time_comes_out_whole_when_padding_does_not_participate() {
6007 let text = padded_ir(Padding::Ignored, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
6011 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
6012 }
6013
6014 #[test]
6015 fn a_store_says_only_what_it_wrote_when_padding_does_participate() {
6016 let text = padded_ir(Padding::Tracked, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
6019 assert!(!text.contains("owns"), "{text}");
6020 }
6021
6022 #[test]
6023 fn a_member_of_a_union_owns_nothing_after_it() {
6024 let text = padded_ir(
6028 Padding::Ignored,
6029 "union u { char tag; long wide; };\nvoid fill(union u *p) { p->tag = 1; }\n",
6030 );
6031 assert!(!text.contains("owns"), "{text}");
6032 }
6033
6034 #[test]
6035 fn an_inner_records_trailing_padding_reaches_the_outer_records() {
6036 let text = padded_ir(
6041 Padding::Ignored,
6042 "struct inner { char c; };\n\
6043 struct outer { struct inner in; int x; };\n\
6044 void fill(struct outer *p) { p->in.c = 1; p->x = 2; }\n",
6045 );
6046 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
6047 }
6048
6049 #[test]
6050 fn a_build_that_did_not_ask_for_the_monitor_is_compiled_the_way_it_always_was() {
6051 let text = ir(READS_THROUGH_A_POINTER);
6055 assert!(!text.contains("check_"), "{text}");
6056 assert!(!text.contains("cap_of"), "{text}");
6057 }
6058
6059 #[test]
6060 fn asking_for_a_tier_puts_the_checks_in_before_the_optimizer_sees_them() {
6061 let text = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
6062 assert!(text.contains("cap_of"), "{text}");
6063 assert!(text.contains("check_bounds"), "{text}");
6064 assert!(text.contains("check_live"), "{text}");
6065 assert!(text.contains("check_deriv"), "{text}");
6067 assert!(text.contains("check_type"), "{text}");
6069 }
6070
6071 #[test]
6072 fn the_three_tiers_that_are_not_off_all_check_the_same_accesses_so_far() {
6073 let detect = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
6077 for tier in [rucc_session::Safety::Enforce, rucc_session::Safety::Kernel] {
6078 assert_eq!(safe_ir(tier, READS_THROUGH_A_POINTER), detect, "{tier}");
6079 }
6080 }
6081
6082 fn summary(tier: rucc_session::Safety, source: &str) -> String {
6084 let mut opts = options();
6085 opts.emit = EmitKind::SafetySummary;
6086 opts.safety = tier;
6087 let result = run(&opts, source);
6088 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
6089 result.text().to_owned()
6090 }
6091
6092 #[test]
6093 fn the_summary_counts_the_checks_that_went_in_and_the_ones_still_standing() {
6094 let text = summary(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
6095 assert!(text.contains("\"tier\": \"detect\""), "{text}");
6096 assert!(
6098 text.contains("\"bounds\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"),
6099 "{text}"
6100 );
6101 assert!(
6102 text.contains(
6103 "\"derivation\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"
6104 ),
6105 "{text}"
6106 );
6107 }
6108
6109 #[test]
6110 fn a_build_without_the_monitor_summarises_as_a_build_with_no_checks_in_it() {
6111 let text = summary(rucc_session::Safety::Off, READS_THROUGH_A_POINTER);
6115 assert!(text.contains("\"tier\": \"off\""), "{text}");
6116 assert!(
6117 text.contains("\"bounds\": { \"emitted\": 0, \"remaining\": 0, \"discharged\": 0 }"),
6118 "{text}"
6119 );
6120 }
6121
6122 #[test]
6123 fn a_call_the_boundary_models_is_counted_apart_from_one_it_does_not() {
6124 let text = summary(
6125 rucc_session::Safety::Detect,
6126 "void *memcpy(void *, const void *, unsigned long);\n\
6127 int puts(const char *);\n\
6128 void f(char *d, char *s) { memcpy(d, s, 4); puts(d); }\n",
6129 );
6130 assert!(text.contains("\"interposed\": 1"), "{text}");
6131 assert!(text.contains("\"puts\""), "{text}");
6132 assert!(!text.contains("__rucc_wrap_memcpy\""), "{text}");
6136 }
6137
6138 #[test]
6139 fn an_address_taken_of_a_library_function_is_counted_the_way_a_call_to_one_is() {
6140 let text = summary(
6145 rucc_session::Safety::Detect,
6146 "void *memcpy(void *, const void *, unsigned long);\n\
6147 int puts(const char *);\n\
6148 void *table[2] = { (void *)memcpy, (void *)puts };\n\
6149 void *f(int i) { return table[i]; }\n",
6150 );
6151 assert!(text.contains("\"interposed\": 1"), "{text}");
6152 assert!(text.contains("\"puts\""), "{text}");
6153 assert!(!text.contains("\"memcpy\""), "{text}");
6154 }
6155
6156 #[test]
6157 fn the_two_directions_a_pointer_crosses_the_boundary_are_counted_apart() {
6158 let text = summary(
6162 rucc_session::Safety::Detect,
6163 "void *notes_open(void);\n\
6164 char *f(char *p) { char *q = notes_open(); return q ? q : p; }\n",
6165 );
6166 assert!(text.contains("\"crossings\": { \"entered\": 1, \"returned\": 1 }"), "{text}");
6167 assert!(text.contains("\"notes_open\""), "{text}");
6168 }
6169
6170 #[test]
6171 fn a_static_function_nobody_takes_the_address_of_is_not_a_crossing() {
6172 let text = summary(
6175 rucc_session::Safety::Detect,
6176 "static int len(const char *p) { return p ? 1 : 0; }\n\
6177 int f(void) { return len(\"x\"); }\n",
6178 );
6179 assert!(text.contains("\"crossings\": { \"entered\": 0, \"returned\": 0 }"), "{text}");
6180 }
6181
6182 fn granules(source: &str) -> String {
6184 let mut opts = options();
6185 opts.emit = EmitKind::TypeGranules;
6186 let result = run(&opts, source);
6187 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
6188 result.text().to_owned()
6189 }
6190
6191 #[test]
6192 fn the_granule_report_names_every_record_and_both_keyings() {
6193 let text = granules(
6194 "struct hot { char *p; int a; int b; };\n\
6195 int f(struct hot *h) { return h->a; }\n",
6196 );
6197 assert!(text.contains("struct hot"), "{text}");
6198 assert!(text.contains("every type distinct"), "{text}");
6201 assert!(text.contains("every pointer one type"), "{text}");
6202 assert!(text.contains("budget"), "{text}");
6203 }
6204
6205 #[test]
6206 fn a_record_nothing_uses_is_still_measured() {
6207 let text = granules("struct unused { long a; double b; };\nint f(void) { return 0; }\n");
6210 assert!(text.contains("struct unused"), "{text}");
6211 }
6212
6213 #[test]
6214 fn the_granule_report_stops_before_anything_is_lowered() {
6215 let text = granules(
6219 "struct wide { long double d; };\n\
6220 long double f(long double x) { return x * x; }\n",
6221 );
6222 assert!(text.contains("struct wide"), "{text}");
6223 }
6224
6225 #[test]
6226 fn a_witness_reaches_the_assembler_as_a_call_to_the_runtime() {
6227 let text = safe_asm(rucc_session::Safety::Detect, "char *f(char *p) { return p; }\n");
6230 assert!(text.contains("\tcall\t__rucc_cap_witness\n"), "{text}");
6231 }
6232
6233 #[test]
6234 fn a_pointer_turned_into_an_integer_is_on_the_trust_set() {
6235 let text = summary(
6236 rucc_session::Safety::Detect,
6237 "unsigned long f(int *p) { return (unsigned long) p; }\n",
6238 );
6239 assert!(text.contains("\"exposed\": 1"), "{text}");
6240 }
6241
6242 fn safe_asm(tier: rucc_session::Safety, source: &str) -> String {
6244 let mut opts = options();
6245 opts.emit = EmitKind::Asm;
6246 opts.safety = tier;
6247 let result = run(&opts, source);
6248 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
6249 result.text().to_owned()
6250 }
6251
6252 #[test]
6253 fn a_check_reaches_the_assembler_as_a_call_to_the_runtime() {
6254 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
6255 assert!(text.contains("\tcall\t__rucc_check_bounds\n"), "{text}");
6256 assert!(text.contains("\tcall\t__rucc_check_live\n"), "{text}");
6257 assert!(text.contains("\tcall\t__rucc_check_deriv\n"), "{text}");
6258 assert!(text.contains("\tcall\t__rucc_check_typed_init\n"), "{text}");
6261 }
6262
6263 #[test]
6264 fn every_check_that_reached_the_assembler_has_a_row_describing_it() {
6265 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
6271 let section = format!("\t.section\t{},", rucc_safety::SECTION);
6272 assert_eq!(text.matches(§ion).count(), 4, "{text}");
6273 for index in 0..4 {
6274 let name = format!("__rucc_safety_desc_{index}");
6275 assert!(text.contains(&format!("{name}:\n")), "{text}");
6278 assert!(text.contains(&format!("{name}(%rip)")), "{text}");
6279 }
6280 assert!(!text.contains("__rucc_safety_desc_4"), "{text}");
6281 }
6282
6283 #[test]
6291 fn builtin_constant_p_is_folded_where_it_is_written_rather_than_called() {
6292 let text = ir(concat!(
6293 "int g;\n",
6294 "int a = __builtin_constant_p(1);\n",
6295 "int b = __builtin_constant_p(g);\n",
6296 "int c = __builtin_constant_p(\"abc\");\n",
6297 "int d = __builtin_constant_p(&g);\n",
6298 "int e = __builtin_constant_p(1.5);\n",
6299 "int h = __builtin_choose_expr(__builtin_constant_p(3), 11, 22);\n",
6300 ));
6301 assert!(text.contains("global @a : i32 = 1,"), "{text}");
6302 assert!(text.contains("global @b : i32 = 0,"), "{text}");
6303 assert!(text.contains("global @c : i32 = 1,"), "{text}");
6304 assert!(text.contains("global @d : i32 = 0,"), "{text}");
6305 assert!(text.contains("global @e : i32 = 1,"), "{text}");
6306 assert!(text.contains("global @h : i32 = 11,"), "{text}");
6307 assert!(!text.contains("__builtin_constant_p"), "it is not a call to anything:\n{text}");
6308
6309 let text = body("int f(void) { int i = 0; __builtin_constant_p(i++); return i; }\n");
6313 assert_eq!(text, "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 0\n return %0\n");
6314 }
6315
6316 #[test]
6320 fn a_pure_function_that_returns_through_memory_is_still_called() {
6321 let text = optimized(concat!(
6322 "struct four { long a, b, c, d; };\n",
6323 "__attribute__((pure)) struct four made(int);\n",
6324 "__attribute__((const)) struct four counted(int);\n",
6325 "long f(int x) { return made(x).c + counted(x).d; }\n",
6326 ));
6327 assert!(text.contains("call\tmade"), "{text}");
6328 assert!(text.contains("call\tcounted"), "{text}");
6329 }
6330
6331 #[test]
6336 fn a_condition_on_a_builtin_constant_p_waits_for_the_optimizer() {
6337 let text = optimized(concat!(
6338 "void g(void);\n",
6339 "void f(void) { int size = sizeof(int); __builtin_constant_p(size) ? (void)0 : g(); }\n",
6340 "void h(void) { int size = sizeof(int); if (!__builtin_constant_p(size)) g(); }\n",
6341 ));
6342 assert!(!text.contains("call\tg") && !text.contains("jmp\tg"), "{text}");
6343 }
6344
6345 #[test]
6349 fn a_call_to_a_function_that_falls_off_its_end_comes_back() {
6350 let text = optimized(concat!(
6351 "int calls;\n",
6352 "__attribute__((noinline)) static int no_answer(int x) { calls += x; }\n",
6353 "void after(void);\n",
6354 "void f(void) { no_answer(1); after(); }\n",
6355 ));
6356 assert!(text.contains("call\tafter") || text.contains("jmp\tafter"), "{text}");
6357 }
6358
6359 #[test]
6368 fn a_call_to_a_library_builtin_reaches_the_library_function() {
6369 let text = body("void f(void) { __builtin_abort(); }\n");
6370 assert_eq!(text, "block0:\n call @abort() : ()\n return\n");
6371
6372 let text = ir("int f(const char *s) { return __builtin_puts(s) + __builtin_strlen(s); }\n");
6375 assert!(text.contains("call @puts(%0) : (ptr) -> i32"), "{text}");
6376 assert!(text.contains("call @strlen(%0) : (ptr) -> i64"), "{text}");
6377 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
6378 }
6379
6380 #[test]
6393 fn a_chk_builtin_reaches_the_checking_function_and_keeps_the_size() {
6394 let text = ir(concat!(
6395 "char d[8];\n",
6396 "void f(const char *s, unsigned long n) {\n",
6397 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
6398 " __builtin___strcpy_chk(d, s, __builtin_object_size(d, 1));\n",
6399 " __builtin___memset_chk(d, 0, n, 8);\n",
6400 "}\n",
6401 ));
6402 assert!(text.contains("call @__memcpy_chk("), "{text}");
6403 assert!(text.contains("call @__strcpy_chk("), "{text}");
6404 assert!(text.contains("call @__memset_chk("), "{text}");
6405 assert!(text.contains("iconst.i64 8"), "the object size reaches the call: {text}");
6406 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
6407 }
6408
6409 #[test]
6417 fn a_checking_call_whose_size_says_nothing_is_known_is_the_plain_library_call() {
6418 let text = ir(concat!(
6419 "extern char *p;\n",
6420 "char d[8];\n",
6421 "void f(const char *s, unsigned long n) {\n",
6422 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
6423 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
6424 " __builtin___strcpy_chk(p, s, __builtin_object_size(p, 0));\n",
6425 " __builtin___stpncpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
6426 " __builtin___sprintf_chk(p, 1, __builtin_object_size(p, 0), s);\n",
6427 "}\n",
6428 ));
6429
6430 assert!(
6432 text.contains("call @__memcpy_chk(%2, %0, %1, %3) : (ptr, ptr, i64, i64)"),
6433 "{text}"
6434 );
6435
6436 assert!(text.contains("call @memcpy(%6, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
6439 assert!(text.contains("call @strcpy(%10, %0) : (ptr, ptr) -> ptr"), "{text}");
6440 assert!(text.contains("call @stpncpy(%14, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
6441
6442 assert!(text.contains("call @__sprintf_chk("), "{text}");
6445
6446 let asm = asm(concat!(
6449 "void f(char *p, const char *s, unsigned long n) {\n",
6450 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
6451 "}\n",
6452 ));
6453 assert!(asm.contains("call\tmemcpy"), "{asm}");
6454 assert!(!asm.contains("$-1"), "the size that went away leaves no instruction:\n{asm}");
6455 }
6456
6457 #[test]
6465 fn the_v_spellings_of_the_chk_family_take_the_list_a_va_list_parameter_holds() {
6466 let text = ir(concat!(
6467 "char d[64];\n",
6468 "int f(const char *fmt, ...) {\n",
6469 " __builtin_va_list ap;\n",
6470 " __builtin_va_start(ap, fmt);\n",
6471 " int n = __builtin___vsprintf_chk(d, 1, __builtin_object_size(d, 0), fmt, ap);\n",
6472 " __builtin_va_end(ap);\n",
6473 " return n;\n",
6474 "}\n",
6475 ));
6476 assert!(text.contains("call @__vsprintf_chk("), "{text}");
6477 assert!(text.contains("iconst.i64 64"), "the object size reaches the call: {text}");
6478 }
6479
6480 #[test]
6491 fn the_absolute_value_family_is_the_magnitude_and_not_a_call() {
6492 let text = body(concat!(
6493 "long long llabs(long long);\n",
6494 "long long f(long long x) { return llabs(x); }\n",
6495 ));
6496 assert!(text.contains("%1 = iconst.i64 63"), "{text}");
6497 assert!(text.contains("%2 = ashr %0, %1"), "{text}");
6498 assert!(text.contains("%3 = xor %0, %2"), "{text}");
6499 assert!(text.contains("%4 = sub %3, %2"), "{text}");
6500 assert!(!text.contains("call"), "the call does not happen:\n{text}");
6501
6502 let text = body("int abs(int);\nint f(int x) { return abs(x); }\n");
6505 assert!(text.contains("iconst.i32 31"), "{text}");
6506 let text = body("long labs(long);\nlong f(long x) { return labs(x); }\n");
6507 assert!(text.contains("iconst.i64 63"), "{text}");
6508
6509 let text = body("long long f(long long x) { return __builtin_llabs(x); }\n");
6512 assert!(!text.contains("call"), "{text}");
6513
6514 let text = ir(concat!(
6516 "long long llabs(long long b);\n",
6517 "long long g(long long x) { return llabs(x); }\n",
6518 "long long llabs(long long b) { return 7; }\n",
6519 ));
6520 assert!(!text.contains("call @llabs"), "{text}");
6521 }
6522
6523 #[test]
6530 fn a_byte_swap_is_arithmetic_and_not_a_call() {
6531 let text = body("unsigned f(unsigned x) { return __builtin_bswap32(x); }\n");
6532 assert_eq!(text, "block0(%0: i32):\n %1 = bswap %0\n return %1\n");
6533
6534 let text = body("unsigned f(unsigned char c) { return __builtin_bswap32(c); }\n");
6537 assert!(text.contains("zext.i32 %0"), "widened first: {text}");
6538 assert!(text.contains("bswap %1"), "and swapped at four bytes: {text}");
6539 }
6540
6541 #[test]
6547 fn the_byte_swaps_reverse_at_the_width_their_name_says() {
6548 for (name, ty, width) in [
6549 ("__builtin_bswap16", "unsigned short", "i16"),
6550 ("__builtin_bswap32", "unsigned", "i32"),
6551 ("__builtin_bswap64", "unsigned long long", "i64"),
6552 ] {
6553 let source = format!("{ty} f({ty} x) {{ return {name}(x); }}\n");
6554 let text = body(&source);
6555 assert_eq!(
6556 text,
6557 format!("block0(%0: {width}):\n %1 = bswap %0\n return %1\n"),
6558 "{name}"
6559 );
6560 }
6561 }
6562
6563 #[test]
6570 fn the_bit_counts_are_instructions_and_not_calls() {
6571 let text = body("int f(unsigned x) { return __builtin_clz(x); }\n");
6572 assert_eq!(text, "block0(%0: i32):\n %1 = ctlz %0\n return %1\n");
6573
6574 let text = body("int f(unsigned x) { return __builtin_ctz(x); }\n");
6575 assert_eq!(text, "block0(%0: i32):\n %1 = cttz %0\n return %1\n");
6576
6577 let text = body("int f(unsigned x) { return __builtin_popcount(x); }\n");
6578 assert_eq!(text, "block0(%0: i32):\n %1 = ctpop %0\n return %1\n");
6579 }
6580
6581 #[test]
6590 fn the_bit_counts_ask_about_the_width_their_name_says() {
6591 let text = body("int f(unsigned long long x) { return __builtin_clzll(x); }\n");
6592 assert!(text.starts_with("block0(%0: i64):"), "counted at eight bytes: {text}");
6593 assert!(text.contains("%1 = ctlz %0"), "{text}");
6594 assert!(text.contains("trunc.i32 %1"), "and answered in an int: {text}");
6595
6596 let text = body("int f(unsigned long long x) { return __builtin_clz(x); }\n");
6599 assert!(text.contains("trunc.i32 %0"), "narrowed to what was asked about: {text}");
6600 assert!(text.contains("ctlz %1"), "and counted there: {text}");
6601
6602 let text = body("int f(unsigned long x) { return __builtin_popcountl(x); }\n");
6603 assert!(text.contains("%1 = ctpop %0"), "{text}");
6604 assert!(!text.contains("call"), "{text}");
6605 }
6606
6607 #[test]
6612 fn a_parity_is_the_low_bit_of_the_set_bit_count() {
6613 let text = body("int f(unsigned x) { return __builtin_parity(x); }\n");
6614 assert!(text.contains("%1 = ctpop %0"), "{text}");
6615 assert!(text.contains("iconst.i32 1"), "{text}");
6616 assert!(text.contains("and %1, %2"), "the low bit of it: {text}");
6617 }
6618
6619 #[test]
6625 fn the_first_set_bit_is_one_based_and_zero_for_a_zero() {
6626 let text = body("int f(int x) { return __builtin_ffs(x); }\n");
6627 assert!(text.contains("%1 = cttz %0"), "{text}");
6628 assert!(text.contains("%4 = add %1, %2"), "one more than the count: {text}");
6629 assert!(text.contains("%5 = icmp ne %0, %3"), "whether there was a bit at all: {text}");
6630 assert!(text.contains("%7 = sub %3, %6"), "spread to a mask: {text}");
6631 assert!(text.contains("%8 = and %4, %7"), "and kept only then: {text}");
6632 assert!(!text.contains("br_if"), "no branch: {text}");
6633 }
6634
6635 #[test]
6645 fn the_redundant_sign_bit_count_is_instructions_and_not_a_call() {
6646 let text = body("int f(int x) { return __builtin_clrsb(x); }\n");
6647 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
6648 assert!(text.contains("%2 = ashr %0, %1"), "the sign over every bit: {text}");
6649 assert!(text.contains("%3 = xor %0, %2"), "folded onto it: {text}");
6650 assert!(text.contains("%5 = shl %3, %4"), "one less than the count: {text}");
6651 assert!(text.contains("%6 = or %5, %4"), "with something to count at zero: {text}");
6652 assert!(text.contains("%7 = ctlz %6"), "{text}");
6653 assert!(!text.contains("call"), "{text}");
6654 assert!(!text.contains("br_if"), "no branch: {text}");
6655 }
6656
6657 #[test]
6663 fn the_unsigned_absolute_value_family_answers_in_the_unsigned_type() {
6664 let text = body("unsigned f(int x) { return __builtin_uabs(x); }\n");
6665 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
6666 assert!(text.contains("%4 = sub %3, %2"), "{text}");
6667 assert!(!text.contains("call"), "nothing declares uabs, so a call would not link: {text}");
6668
6669 let text = body("unsigned long long f(long long x) { return __builtin_ullabs(x); }\n");
6670 assert!(text.contains("iconst.i64 63"), "at the width the name says: {text}");
6671
6672 let text = body("int f(int x) { return __builtin_uabs(x) > 2147483647u; }\n");
6675 assert!(text.contains("icmp ugt"), "compared unsigned: {text}");
6676 }
6677
6678 #[test]
6686 fn the_widest_absolute_value_is_whichever_type_the_target_makes_intmax_t() {
6687 let text = body("long f(long x) { return __builtin_imaxabs(x); }\n");
6688 assert!(text.contains("iconst.i64 63"), "{text}");
6689 assert!(text.contains("%4 = sub %3, %2"), "{text}");
6690 assert!(!text.contains("call"), "{text}");
6691
6692 let text = body("unsigned long f(long x) { return __builtin_umaxabs(x); }\n");
6693 assert!(text.contains("iconst.i64 63"), "{text}");
6694 assert!(!text.contains("call"), "{text}");
6695 }
6696
6697 #[test]
6705 fn an_overflow_predicate_writes_nothing_and_answers_the_bit_the_check_would() {
6706 let text =
6707 body("int f(int a, int b) { return __builtin_add_overflow_p(a, b, (int) 0); }\n");
6708 assert!(text.contains("%2, %3 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
6709 assert!(!text.contains("store"), "nothing is written: {text}");
6710 assert!(!text.contains("call"), "{text}");
6711
6712 let text =
6715 body("int f(int a, int b) { return __builtin_mul_overflow_p(a, b, (long long) 0); }\n");
6716 assert!(text.contains("smul_overflow.(i64, i1)"), "{text}");
6717 assert!(!text.contains("store"), "{text}");
6718
6719 let text = body(concat!(
6722 "int g(void);\n",
6723 "int f(int a, int b) { return __builtin_sub_overflow_p(a, b, g()); }\n",
6724 ));
6725 assert!(!text.contains("call @g"), "the third argument is not evaluated: {text}");
6726 }
6727
6728 #[test]
6738 fn an_overflow_check_is_arithmetic_and_not_a_call() {
6739 let text =
6740 body("int f(int a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
6741 assert!(text.contains("%3, %4 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
6742 assert!(text.contains("store %3 -> %2"), "{text}");
6743 assert!(!text.contains("call"), "{text}");
6744
6745 let text =
6746 body("int f(int a, int b, int *r) { return __builtin_sub_overflow(a, b, r); }\n");
6747 assert!(text.contains("ssub_overflow.(i32, i1) %0, %1"), "{text}");
6748
6749 let text =
6750 body("int f(int a, int b, int *r) { return __builtin_mul_overflow(a, b, r); }\n");
6751 assert!(text.contains("smul_overflow.(i32, i1) %0, %1"), "{text}");
6752
6753 let text = body(
6756 "int f(unsigned a, unsigned b, unsigned *r) { return __builtin_add_overflow(a, b, r); }\n",
6757 );
6758 assert!(text.contains("uadd_overflow.(i32, i1) %0, %1"), "{text}");
6759 }
6760
6761 #[test]
6769 fn an_overflow_check_is_done_at_a_type_that_holds_every_operand() {
6770 let text = body(
6771 "int f(unsigned a, int b, long long *r) { return __builtin_add_overflow(a, b, r); }\n",
6772 );
6773 assert!(text.contains("%3 = zext.i64 %0"), "the unsigned operand keeps its value: {text}");
6774 assert!(text.contains("%4 = sext.i64 %1"), "and so does the signed one: {text}");
6775 assert!(text.contains("sadd_overflow.(i64, i1) %3, %4"), "{text}");
6776
6777 let text = body(
6780 "int f(long long a, long long b, long long *r) { return __builtin_mul_overflow(a, b, r); }\n",
6781 );
6782 assert!(text.contains("smul_overflow.(i64, i1) %0, %1"), "{text}");
6783 assert!(!text.contains("sext."), "{text}");
6784 assert!(!text.contains("zext.i64"), "{text}");
6786 }
6787
6788 #[test]
6796 fn an_overflow_check_writes_the_wrapped_answer_whether_or_not_it_fit() {
6797 let text =
6798 body("int f(int a, int b, char *r) { return __builtin_sub_overflow(a, b, r); }\n");
6799 assert!(text.contains("%3, %4 = ssub_overflow.(i32, i1) %0, %1"), "{text}");
6800 assert!(text.contains("%5 = trunc.i8 %3"), "narrowed to where it goes: {text}");
6801 assert!(text.contains("%6 = sext.i32 %5"), "and back: {text}");
6802 assert!(text.contains("%7 = icmp ne %6, %3"), "which is whether it fit: {text}");
6803 assert!(text.contains("store %5 -> %2"), "the narrowed value is stored either way: {text}");
6804 assert!(text.contains("%8 = or %4, %7"), "and either bit is an overflow: {text}");
6805 }
6806
6807 #[test]
6814 fn a_call_needing_more_than_the_widest_type_still_compiles() {
6815 for name in ["add", "sub", "mul"] {
6816 let source = format!(
6817 "int f(unsigned __int128 a, long long b, __int128 *r) {{\n \
6818 return __builtin_{name}_overflow(a, b, r);\n}}\n"
6819 );
6820 let mut opts = options();
6821 opts.emit = EmitKind::MirFinal;
6822 assert!(!run(&opts, &source).failed(), "{name} was refused or stopped the back end");
6823 }
6824 }
6825
6826 #[test]
6829 fn an_overflow_check_over_something_that_is_not_an_integer_says_so() {
6830 let messages =
6831 errors("int f(double a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
6832 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
6833
6834 let messages =
6835 errors("int f(int a, int b, double *r) { return __builtin_add_overflow(a, b, r); }\n");
6836 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
6837 }
6838
6839 #[test]
6850 fn an_ordered_access_is_ordered_in_the_ir() {
6851 let text = body("int f(int *p) { return __atomic_load_n(p, 0); }\n");
6852 assert!(text.contains("atomic_load.i32 %0, align 4, relaxed"), "{text}");
6853
6854 let text = body("long f(long *p) { return __atomic_load_n(p, 2); }\n");
6855 assert!(text.contains("atomic_load.i64 %0, align 8, acquire"), "{text}");
6856
6857 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
6858 assert!(text.contains("atomic_store %1 -> %0, align 4, release"), "{text}");
6859
6860 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
6861 assert!(text.contains("atomic_store %1 -> %0, align 4, seq_cst"), "{text}");
6862
6863 let text = body("void f(char *p, int v) { __atomic_store_n(p, v, 0); }\n");
6866 assert!(text.contains("trunc.i8 %1"), "{text}");
6867 assert!(text.contains("atomic_store %2 -> %0, align 1, relaxed"), "{text}");
6868 }
6869
6870 #[test]
6879 fn an_ordered_access_is_the_plain_instruction_on_this_machine() {
6880 let text = asm("int f(int *p) { return __atomic_load_n(p, 5); }\n");
6881 assert!(text.contains("movl\t(%rdi), %eax"), "{text}");
6882 assert!(!text.contains("mfence"), "a load needs no barrier here: {text}");
6883
6884 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
6885 assert!(text.contains("movl\t%esi, (%rdi)"), "{text}");
6886 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
6887
6888 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
6889 let (before, after) = text.split_once("mfence").expect("a barrier: {text}");
6890 assert!(before.contains("movl\t%esi, (%rdi)"), "the store comes first: {text}");
6891 assert!(!after.contains("movl"), "and nothing else is between them: {text}");
6892 }
6893
6894 #[test]
6904 fn a_barrier_is_one_instruction_at_the_strongest_ordering_and_none_below_it() {
6905 assert!(asm("void f(void) { __atomic_thread_fence(5); }\n").contains("mfence"));
6906 assert!(asm("void f(void) { __sync_synchronize(); }\n").contains("mfence"));
6907
6908 for weaker in ["1", "2", "3", "4"] {
6909 let source = format!("void f(void) {{ __atomic_thread_fence({weaker}); }}\n");
6910 assert!(!asm(&source).contains("mfence"), "{weaker} costs nothing here");
6911 }
6912 }
6913
6914 #[test]
6924 fn the_three_x86_fences_are_the_barrier_the_strongest_ordering_gives() {
6925 for name in ["__builtin_ia32_sfence", "__builtin_ia32_lfence", "__builtin_ia32_mfence"] {
6926 let source = format!("void f(void) {{ {name}(); }}\n");
6927 assert!(asm(&source).contains("mfence"), "{name} is a barrier");
6928 let text = body(&source);
6929 assert!(text.contains("fence seq_cst"), "{name}: {text}");
6930 }
6931
6932 let result = run(&options(), "void f(void) { __builtin_ia32_sfence(1); }\n");
6933 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
6934 assert!(result.messages[0].contains("too many arguments"), "{:?}", result.messages);
6935 }
6936
6937 #[test]
6943 fn a_compare_and_exchange_is_one_instruction_answering_two_things() {
6944 let text =
6947 body("int f(int *p, int e, int d) { return __sync_val_compare_and_swap(p, e, d); }\n");
6948 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
6949 assert!(text.contains("return %3"), "the value it found: {text}");
6950
6951 let text =
6952 body("int f(int *p, int e, int d) { return __sync_bool_compare_and_swap(p, e, d); }\n");
6953 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
6954 assert!(text.contains("zext.i32 %4"), "whether it happened: {text}");
6955
6956 let text = body(
6959 "int f(int *p, int *e, int d) { return __atomic_compare_exchange_n(p, e, d, 0, 4, 2); }\n",
6960 );
6961 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
6962 assert!(text.contains("%4, %5 = cmpxchg.(i32, i1) %0, %3, %2, align 4, acq_rel"), "{text}");
6963 assert!(text.contains("br_if %5, block2, block1"), "{text}");
6964 assert!(text.contains("store %4 -> %1, align 4"), "{text}");
6965
6966 let text = body(
6969 "int f(int *p, int *e, int *d) { return __atomic_compare_exchange(p, e, d, 0, 5, 5); }\n",
6970 );
6971 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
6972 assert!(text.contains("%4 = load.i32 %2, align 4"), "{text}");
6973 assert!(text.contains("%5, %6 = cmpxchg.(i32, i1) %0, %3, %4, align 4, seq_cst"), "{text}");
6974 }
6975
6976 #[test]
6983 fn a_compare_and_exchange_is_a_locked_instruction_at_the_width_of_the_object() {
6984 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
6985 for (ty, suffix, reg) in widths {
6986 let source = format!(
6987 "int f({ty} *p, {ty} e, {ty} d) {{ return __sync_bool_compare_and_swap(p, e, d); }}\n"
6988 );
6989 let text = asm(&source);
6990 assert!(text.contains("\tlock\n"), "{ty}: {text}");
6991 assert!(text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
6992 assert!(text.contains("sete\t"), "{ty}: {text}");
6993 }
6994 let source =
6995 "int f(long *p, long e, long d) { return __sync_bool_compare_and_swap(p, e, d); }\n";
6996 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
6997
6998 for order in ["0", "2", "3", "4", "5"] {
7002 let call = format!("__atomic_compare_exchange_n(p, e, d, 0, {order}, 0)");
7003 let source = format!("int f(int *p, int *e, int d) {{ return {call}; }}\n");
7004 let text = asm(&source);
7005 assert!(text.contains("cmpxchgl\t"), "{order}: {text}");
7006 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
7007 }
7008 }
7009
7010 #[test]
7022 fn a_read_modify_write_is_one_instruction_and_the_arithmetic_a_name_asks_for() {
7023 let text = body("int f(int *p, int v) { return __atomic_fetch_add(p, v, 5); }\n");
7024 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
7025 assert!(text.contains("return %2"), "the value that was there: {text}");
7026
7027 let text = body("int f(int *p, int v) { return __atomic_add_fetch(p, v, 5); }\n");
7028 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
7029 assert!(text.contains("%3 = add %2, %1"), "and the value afterwards: {text}");
7030
7031 let text = body("int f(int *p, int v) { return __atomic_sub_fetch(p, v, 5); }\n");
7032 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
7033 assert!(text.contains("%3 = sub %2, %1"), "{text}");
7034
7035 let text = body("int f(int *p, int v) { return __sync_fetch_and_sub(p, v); }\n");
7037 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
7038
7039 let text = body("int f(int *p, int v) { return __atomic_exchange_n(p, v, 5); }\n");
7042 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, seq_cst"), "{text}");
7043
7044 let text = body("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
7045 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, acquire"), "{text}");
7046
7047 let text = body("void f(int *p) { __sync_lock_release(p); }\n");
7050 assert!(text.contains("release"), "{text}");
7051 assert!(text.contains("%1 = iconst.i32 0"), "{text}");
7052
7053 let text = body("void f(int *p, int guard) { __sync_lock_release(p, guard); }\n");
7057 assert!(text.contains("%2 = iconst.i32 0"), "{text}");
7058 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
7059
7060 let text = body("int f(int *p, int v) { return __atomic_fetch_and(p, v, 5); }\n");
7063 assert!(text.contains("%2 = atomic_rmw.i32 and %0, %1, align 4, seq_cst"), "{text}");
7064
7065 let text = body("int f(int *p, int v) { return __sync_or_and_fetch(p, v); }\n");
7066 assert!(text.contains("%2 = atomic_rmw.i32 or %0, %1, align 4, seq_cst"), "{text}");
7067 assert!(text.contains("%3 = or %2, %1"), "and the value afterwards: {text}");
7068
7069 let text = body("int f(int *p, int v) { return __atomic_nand_fetch(p, v, 5); }\n");
7072 assert!(text.contains("%2 = atomic_rmw.i32 nand %0, %1, align 4, seq_cst"), "{text}");
7073 assert!(text.contains("%3 = and %2, %1"), "{text}");
7074 assert!(text.contains("%4 = iconst.i32 -1"), "{text}");
7075 assert!(text.contains("%5 = xor %3, %4"), "{text}");
7076 }
7077
7078 #[test]
7089 fn a_bitwise_read_modify_write_is_a_loop_around_the_compare_and_exchange() {
7090 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
7091 for (ty, suffix, reg) in widths {
7092 for (name, call, insn) in [
7093 ("and", "__atomic_fetch_and(p, v, 5)", "and"),
7094 ("or", "__sync_fetch_and_or(p, v)", "or"),
7095 ("xor", "__atomic_xor_fetch(p, v, 5)", "xor"),
7096 ] {
7097 let source = format!("{ty} f({ty} *p, {ty} v) {{ return {call}; }}\n");
7098 let text = asm(&source);
7099 assert!(text.contains("\tlock\n"), "{ty} {name}: {text}");
7100 assert!(
7101 text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")),
7102 "{ty} {name}: {text}"
7103 );
7104 assert!(text.contains(&format!("{insn}{suffix}\t")), "{ty} {name}: {text}");
7105 assert!(!text.contains("\txadd"), "{ty} {name} is not an add: {text}");
7107 assert!(!text.contains("\txchg"), "{ty} {name} is not an exchange: {text}");
7108 }
7109 }
7110 let source = "long f(long *p, long v) { return __atomic_fetch_or(p, v, 5); }\n";
7111 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
7112
7113 let text = asm("int f(int *p, int v) { return __sync_fetch_and_nand(p, v); }\n");
7117 assert!(text.contains("cmpxchgl\t"), "{text}");
7118 assert!(text.contains("andl\t"), "{text}");
7119 assert!(text.contains("notl\t"), "{text}");
7120 }
7121
7122 #[test]
7131 fn an_access_through_a_second_pointer_is_the_same_access_and_one_more() {
7132 let text = body("void f(int *p, int *r) { __atomic_load(p, r, 5); }\n");
7133 assert!(text.contains("%2 = atomic_load.i32 %0, align 4, seq_cst"), "{text}");
7134 assert!(text.contains("store %2 -> %1, align 4"), "and out through the place: {text}");
7135
7136 let text = body("void f(int *p, int *v) { __atomic_store(p, v, 3); }\n");
7137 assert!(text.contains("%2 = load.i32 %1, align 4"), "in through the place: {text}");
7138 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
7139
7140 let text = body("void f(int *p, int *v, int *r) { __atomic_exchange(p, v, r, 5); }\n");
7143 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
7144 assert!(text.contains("%4 = atomic_rmw.i32 xchg %0, %3, align 4, seq_cst"), "{text}");
7145 assert!(text.contains("store %4 -> %2, align 4"), "{text}");
7146 }
7147
7148 #[test]
7159 fn a_flag_is_an_exchange_of_one_byte_and_a_store_of_a_zero_over_the_same_byte() {
7160 for pointer in ["char", "int", "void"] {
7161 let source = format!("int f({pointer} *p) {{ return __atomic_test_and_set(p, 5); }}\n");
7162 let text = body(&source);
7163 assert!(text.contains("%1 = iconst.i8 1"), "{pointer}: {text}");
7164 assert!(
7165 text.contains("%2 = atomic_rmw.i8 xchg %0, %1, align 1, seq_cst"),
7166 "{pointer}: {text}"
7167 );
7168 assert!(text.contains("%4 = icmp ne %2, %3"), "{pointer}: {text}");
7169
7170 let source = format!("void f({pointer} *p) {{ __atomic_clear(p, 3); }}\n");
7171 let text = body(&source);
7172 assert!(text.contains("atomic_store %2 -> %0, align 1, release"), "{pointer}: {text}");
7173 }
7174
7175 let text = asm("int f(int *p) { return __atomic_test_and_set(p, 5); }\n");
7178 assert!(text.contains("xchgb\t%al, (%rdi)"), "{text}");
7179 assert!(text.contains("setne\t"), "{text}");
7180 }
7181
7182 #[test]
7190 fn a_read_modify_write_is_an_exchange_or_a_locked_add_at_the_width_of_the_object() {
7191 let widths = [("char", "b", "%sil"), ("short", "w", "%si"), ("int", "l", "%esi")];
7192 for (ty, suffix, reg) in widths {
7193 let source =
7194 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_fetch_add(p, v, 5); }}\n");
7195 let text = asm(&source);
7196 assert!(text.contains("\tlock\n"), "{ty}: {text}");
7197 assert!(text.contains(&format!("xadd{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
7198
7199 let source =
7200 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_exchange_n(p, v, 5); }}\n");
7201 let text = asm(&source);
7202 assert!(text.contains(&format!("xchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
7203 assert!(!text.contains("\tlock\n"), "an exchange is locked already: {ty}: {text}");
7204 }
7205 let source = "long f(long *p, long v) { return __atomic_fetch_add(p, v, 5); }\n";
7206 assert!(asm(source).contains("xaddq\t%rsi, (%rdi)"), "{}", asm(source));
7207
7208 let source = "int f(int *p, int v) { return __atomic_fetch_sub(p, v, 5); }\n";
7211 let text = asm(source);
7212 assert!(text.contains("negl\t"), "{text}");
7213 assert!(text.contains("xaddl\t"), "{text}");
7214
7215 for order in ["0", "2", "3", "4", "5"] {
7218 let source =
7219 format!("int f(int *p, int v) {{ return __atomic_fetch_add(p, v, {order}); }}\n");
7220 let text = asm(&source);
7221 assert!(text.contains("xaddl\t"), "{order}: {text}");
7222 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
7223 }
7224
7225 let text = asm("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
7229 assert!(text.contains("xchgl\t%esi, (%rdi)"), "{text}");
7230 let text = asm("void f(int *p) { __sync_lock_release(p); }\n");
7237 assert!(text.contains("xorl\t%eax, %eax"), "{text}");
7238 assert!(text.contains("movl\t%eax, (%rdi)"), "{text}");
7239 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
7240 }
7241
7242 #[test]
7254 fn the_lock_free_questions_are_answered_as_constants() {
7255 for size in ["1", "2", "4", "8"] {
7256 let source =
7257 format!("int f(void) {{ return __atomic_always_lock_free({size}, 0); }}\n");
7258 let text = asm(&source);
7259 assert!(text.contains("movb\t$1, %al"), "{size} bytes is lock free: {text}");
7260 assert!(!text.contains("call"), "and is not a call: {text}");
7261 }
7262 for size in ["3", "16", "sizeof(long double)"] {
7263 let source = format!("int f(void) {{ return __atomic_is_lock_free({size}, 0); }}\n");
7264 let text = asm(&source);
7265 assert!(text.contains("movb\t$0, %al"), "{size} bytes is not: {text}");
7266 assert!(!text.contains("call"), "and is not a call either: {text}");
7267 }
7268
7269 let text = asm("int f(int n) { return __atomic_is_lock_free(n, 0); }\n");
7273 assert!(text.contains("movb\t$0, %al"), "a size nobody knows is not lock free: {text}");
7274 let text = asm("int f(int *p) { return __atomic_always_lock_free(8, p); }\n");
7275 assert!(text.contains("movb\t$0, %al"), "eight bytes at four is not: {text}");
7276 let text = asm("int f(long *p) { return __atomic_always_lock_free(8, p); }\n");
7277 assert!(text.contains("movb\t$1, %al"), "and at eight it is: {text}");
7278 }
7279
7280 #[test]
7292 fn a_memory_order_an_operation_cannot_carry_is_read_as_the_strongest() {
7293 let mut opts = options();
7294 opts.emit = EmitKind::Ir;
7295
7296 let acquire_store = run(&opts, "void f(int *p, int v) { __atomic_store_n(p, v, 2); }\n");
7297 assert!(acquire_store.text().contains("seq_cst"), "{:?}", acquire_store.text());
7298 assert!(acquire_store.messages[0].contains("[W0333]"), "{:?}", acquire_store.messages);
7299
7300 let nonsense = run(&opts, "int f(int *p) { return __atomic_load_n(p, 99); }\n");
7301 assert!(nonsense.text().contains("seq_cst"), "{:?}", nonsense.text());
7302 assert!(nonsense.messages[0].contains("[W0333]"), "{:?}", nonsense.messages);
7303
7304 let computed = run(&opts, "int f(int *p, int n) { return __atomic_load_n(p, n); }\n");
7305 assert!(computed.text().contains("seq_cst"), "{:?}", computed.text());
7306 assert_eq!(computed.messages, Vec::<String>::new(), "a computed order is not a mistake");
7307 }
7308
7309 #[test]
7321 fn a_conversion_between_a_float_and_the_widest_unsigned_integer_is_written_without_a_branch() {
7322 let text = asm("double f(unsigned long long x) { return (double)x; }\n");
7323 assert!(text.contains("cvtsi2sdq"), "the signed conversion is what runs: {text}");
7324 assert!(text.contains("shrq"), "with the value halved first: {text}");
7325 assert!(text.contains("addsd"), "and doubled after: {text}");
7326 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
7327
7328 let text = asm("unsigned long long f(double d) { return (unsigned long long)d; }\n");
7329 assert!(text.contains("cvttsd2siq"), "the signed conversion is what runs: {text}");
7330 assert!(text.contains("subsd"), "with half the range taken off first: {text}");
7331 assert!(text.contains("shlq\t$63"), "and the top bit put back: {text}");
7332 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
7333 }
7334
7335 #[test]
7346 fn a_plain_name_the_program_took_is_the_programs_own_function() {
7347 let taken = concat!(
7348 "static long long llabs(long long b) { return 7; }\n",
7349 "long long f(long long x) { return llabs(x); }\n",
7350 );
7351 assert!(ir(taken).contains("call @llabs"), "a static definition is the program's own");
7352
7353 let retyped = concat!("int llabs(int b);\n", "int f(int x) { return llabs(x); }\n",);
7354 assert!(ir(retyped).contains("call @llabs"), "another type is another function");
7355
7356 let plain = concat!(
7357 "long long llabs(long long b);\n",
7358 "long long f(long long x) { return llabs(x); }\n",
7359 );
7360 let mut opts = options();
7361 opts.emit = EmitKind::Ir;
7362 assert!(!run(&opts, plain).text().contains("call @llabs"), "the library's by default");
7363
7364 opts.builtins = false;
7365 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin");
7366
7367 opts.builtins = true;
7368 opts.no_builtin = vec!["llabs".to_owned()];
7369 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin-llabs");
7370 let one = "long labs(long b);\nlong f(long x) { return labs(x); }\n";
7371 assert!(!run(&opts, one).text().contains("call @labs"), "one name and not the family");
7372
7373 opts.no_builtin = Vec::new();
7376 opts.builtins = false;
7377 let prefixed = "long long f(long long x) { return __builtin_llabs(x); }\n";
7378 assert!(!run(&opts, prefixed).text().contains("call @llabs"), "the prefix is a promise");
7379 }
7380
7381 #[test]
7394 fn the_hint_builtins_are_their_first_argument_and_the_hint_leaves_no_trace() {
7395 let text = ir(concat!(
7396 "long a = __builtin_expect(7, 1);\n",
7397 "long b = __builtin_expect_with_probability(9, 1, 0.9);\n",
7398 "unsigned long c = sizeof(__builtin_expect((char)1, 1));\n",
7399 ));
7400 assert!(text.contains("global @a : i64 = 7,"), "{text}");
7401 assert!(text.contains("global @b : i64 = 9,"), "{text}");
7402 assert!(text.contains("global @c : i64 = 8,"), "{text}");
7403 assert!(!text.contains("__builtin_expect"), "it is not a call to anything:\n{text}");
7404
7405 let text = body("long f(char c) { return __builtin_expect(c, 1); }\n");
7408 assert!(text.contains("sext"), "{text}");
7409
7410 let one = "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 1\n %2 = sext.i64 %1\n return %0\n";
7414 assert_eq!(body("int f(void) { int i = 0; __builtin_expect(1, i++); return i; }\n"), one);
7415 let source = "int g(void) { int i = 0; __builtin_expect_with_probability(1, i++, 0.5); return i; }\n";
7416 assert_eq!(body(source), one);
7417
7418 let kept = body("int f(int n) { int i = 0; __builtin_expect(n, i++); return i; }\n");
7423 assert!(kept.contains("add.nsw"), "the hint still runs: {kept}");
7424 assert!(kept.ends_with("return %3\n"), "and the answer is what it left behind: {kept}");
7425 let both = "int g(int n) { int i = 0; __builtin_expect_with_probability(n, i++, 0.5); return i; }\n";
7426 assert!(body(both).contains("add.nsw"), "and so does the one with three arguments");
7427 }
7428
7429 #[test]
7441 fn a_promise_that_control_does_not_arrive_writes_no_instruction() {
7442 let promised = "int f(int x) { if (x) return 1; __builtin_unreachable(); }\n";
7443 let text = ir(promised);
7444 assert!(text.contains(" unreachable_hint\n"), "{text}");
7445 assert!(!text.contains("call"), "it is not a call to anything:\n{text}");
7446
7447 let after = body("int g(int x) { __builtin_unreachable(); return x; }\n");
7451 assert!(after.contains("return"), "{after}");
7452
7453 let text = asm(promised);
7456 let mine = text.split_once("\nf:\n").expect("a definition").1;
7457 let mine = mine.split_once("\t.size").expect("a definition").0;
7458 let plain = asm("int f(int x) { if (x) return 1; }\n");
7459 let plain = plain.split_once("\nf:\n").expect("a definition").1;
7460 let plain = plain.split_once("\t.size").expect("a definition").0;
7461 assert_eq!(mine, plain);
7462 let last = mine.lines().rfind(|line| !line.trim_start().starts_with('.'));
7465 assert_eq!(last.map(str::trim), Some("ret"), "{mine}");
7466 assert!(!mine.contains("ud2"), "{mine}");
7467 }
7468
7469 #[test]
7476 fn a_library_builtin_is_diagnosed_under_the_name_the_program_wrote() {
7477 let mut opts = options();
7478 opts.emit = EmitKind::Ir;
7479 let messages = run(&opts, "void f(void) { __builtin_abort(1); }\n").messages;
7480 assert!(
7481 messages.iter().any(|m| m.contains("__builtin_abort")),
7482 "expected the written name in {messages:?}"
7483 );
7484 }
7485
7486 #[test]
7494 fn a_builtin_nothing_lowers_is_refused_by_name() {
7495 let mut opts = options();
7496 opts.emit = EmitKind::Ir;
7497 let builtin = "__atomic_signal_fence";
7498 let source = format!("int counter;\nint f(void) {{ return ({builtin}(5), 0); }}\n");
7499 let messages = run(&opts, &source).messages;
7500 let named = messages.iter().any(|m| m.contains(builtin) && m.contains("E0686"));
7501 assert!(named, "expected {builtin} to be refused by name in {messages:?}");
7502 }
7503
7504 #[test]
7513 fn what_is_refused_is_the_call_and_not_the_name() {
7514 let text = ir(concat!(
7515 "void __atomic_signal_fence(int order) { (void)order; }\n",
7516 "void f(void) { __atomic_signal_fence(5); }\n",
7517 ));
7518 assert!(text.contains("call @__atomic_signal_fence"), "{text}");
7519 }
7520
7521 #[test]
7530 fn the_object_size_of_an_address_is_what_the_layout_leaves_in_front_of_it() {
7531 let text = ir(concat!(
7532 "struct S { char a[8]; int n; char b[12]; };\n",
7533 "char g[32];\n",
7534 "struct S gs;\n",
7535 "unsigned long whole = __builtin_object_size(g, 0);\n",
7536 "unsigned long moved = __builtin_object_size(g + 4, 0);\n",
7537 "unsigned long back = __builtin_object_size(g + 30 - 2, 0);\n",
7538 "unsigned long outer = __builtin_object_size(gs.a, 0);\n",
7539 "unsigned long inner = __builtin_object_size(gs.a, 1);\n",
7540 "unsigned long scalar = __builtin_object_size(&gs.n, 1);\n",
7541 "unsigned long after = __builtin_object_size(&gs.n, 0);\n",
7542 "unsigned long into = __builtin_object_size(&gs.b[2], 1);\n",
7543 "unsigned long text = __builtin_object_size(\"hello\", 0);\n",
7544 "unsigned long dyn = __builtin_dynamic_object_size(gs.b, 1);\n",
7545 ));
7546 for (name, size) in [
7547 ("whole", 32),
7548 ("moved", 28),
7549 ("back", 4),
7550 ("outer", 24),
7551 ("inner", 8),
7552 ("scalar", 4),
7553 ("after", 16),
7554 ("into", 10),
7555 ("text", 6),
7556 ("dyn", 12),
7557 ] {
7558 let said = format!("global @{name} : i64 = {size},");
7559 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
7560 }
7561 }
7562
7563 #[test]
7571 fn the_object_behind_an_address_can_be_one_with_automatic_storage() {
7572 let text = body(concat!(
7573 "struct S { char a[8]; int n; char b[12]; };\n",
7574 "unsigned long f(void) {\n",
7575 " char loc[20];\n",
7576 " struct S ls;\n",
7577 " return __builtin_object_size(loc + 3, 0) + __builtin_object_size(ls.b + 2, 1);\n",
7578 "}\n",
7579 ));
7580 assert!(text.contains("iconst.i64 17"), "twenty bytes with three used: {text}");
7581 assert!(text.contains("iconst.i64 10"), "twelve bytes with two used: {text}");
7582 }
7583
7584 #[test]
7594 fn an_address_with_no_object_in_sight_answers_at_the_end_of_the_range_its_kind_asks_for() {
7595 let text = ir(concat!(
7596 "struct T { int n; char f[]; };\n",
7597 "extern char *p;\n",
7598 "extern struct T *t;\n",
7599 "unsigned long largest = __builtin_object_size(p, 0);\n",
7600 "unsigned long nearest = __builtin_object_size(p, 1);\n",
7601 "unsigned long least = __builtin_object_size(p, 2);\n",
7602 "unsigned long tight = __builtin_object_size(p, 3);\n",
7603 "unsigned long flex = __builtin_object_size(t->f, 1);\n",
7604 "int says = __builtin_object_size(p, 0) == (unsigned long)-1;\n",
7605 ));
7606 for name in ["largest", "nearest", "flex"] {
7607 let said = format!("global @{name} : i64 = -1,");
7611 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
7612 }
7613 for name in ["least", "tight"] {
7614 let said = format!("global @{name} : i64 = 0,");
7615 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
7616 }
7617 assert!(text.contains("global @says : i32 = 1,"), "{text}");
7618 }
7619
7620 #[test]
7627 fn the_address_an_object_size_is_asked_about_is_not_evaluated() {
7628 let text = body(concat!(
7629 "extern char *side(void);\n",
7630 "unsigned long f(void) { return __builtin_object_size(side(), 0); }\n",
7631 ));
7632 assert!(!text.contains("call"), "nothing is called: {text}");
7633 }
7634
7635 #[test]
7640 fn a_kind_that_is_not_one_of_the_four_is_refused() {
7641 for source in [
7642 "extern char *p;\nextern int k;\nunsigned long f(void) ".to_owned()
7643 + "{ return __builtin_object_size(p, k); }\n",
7644 "extern char *p;\nunsigned long f(void) { return __builtin_object_size(p, 4); }\n"
7645 .to_owned(),
7646 "extern char *p;\nunsigned long f(void) ".to_owned()
7647 + "{ return __builtin_dynamic_object_size(p, -1); }\n",
7648 ] {
7649 let messages = errors(&source);
7650 let named = messages.iter().any(|m| m.contains("E0709") && m.contains("0 to 3"));
7651 assert!(named, "expected a complaint about the kind in {messages:?}");
7652 }
7653 }
7654
7655 #[test]
7661 fn the_pair_that_saves_a_place_lowers_to_the_two_markers() {
7662 let text = ir(concat!(
7663 "void *buf[5];\n",
7664 "int f(void) {\n",
7665 " if (__builtin_setjmp(buf)) return 2;\n",
7666 " return 1;\n",
7667 "}\n",
7668 "void g(void) { __builtin_longjmp(buf, 1); }\n",
7669 ));
7670 assert!(text.contains("= setjmp_marker.i32 %0\n"), "the save answers a value: {text}");
7671 assert!(text.contains(" longjmp_marker %0\n"), "the restore answers nothing: {text}");
7672 assert!(!text.contains("call @"), "neither of them is a call: {text}");
7673 }
7674
7675 #[test]
7683 fn a_local_of_a_function_that_saves_a_place_gets_a_slot() {
7684 let text = ir(concat!(
7685 "void *buf[5];\n",
7686 "int f(int x) { int a = 0; if (__builtin_setjmp(buf)) return a; a = 1; return x; }\n",
7687 "int g(int x) { int a = 0; if (x) return a; a = 1; return x; }\n",
7688 ));
7689 let (saves, plain) = text.split_once("func @g").expect("both functions");
7690 assert_eq!(saves.matches("= alloca").count(), 2, "the parameter and the local: {text}");
7691 assert!(saves.contains("store %9 -> %2"), "the local is written through: {text}");
7692 assert!(!plain.contains("alloca"), "nothing in the plain one needs a slot: {text}");
7693 }
7694
7695 #[test]
7704 fn a_value_live_across_sigsetjmp_keeps_its_spill_slot() {
7705 each_spill_slot_written_once(&across("int __sigsetjmp(sigjmp_buf, int);\n", "__sigsetjmp"));
7706 }
7707
7708 #[test]
7711 fn a_value_live_across_a_returns_twice_call_keeps_its_spill_slot() {
7712 let declared = "int save_here(sigjmp_buf, int) __attribute__((__returns_twice__));\n";
7713 each_spill_slot_written_once(&across(declared, "save_here"));
7714 }
7715
7716 #[test]
7724 fn a_value_live_across_setjmp_shares_its_slot_with_nothing_in_the_first_arm() {
7725 let source = concat!(
7726 "typedef long jmp_buf[25];\n",
7727 "int _setjmp(jmp_buf);\n",
7728 "void longjmp(jmp_buf, int) __attribute__((noreturn));\n",
7729 "int printf(const char *, ...);\n",
7730 "static jmp_buf *stack;\n",
7731 "static volatile long long sink;\n",
7732 "static int cells[64];\n",
7733 "static volatile int seed_in = 3;\n",
7734 "static void work(void) { longjmp(*stack, 1); }\n",
7735 "int main(void) {\n",
7736 " int seed = seed_in;\n",
7737 " int v = seed * 2;\n",
7738 " jmp_buf buf;\n",
7739 " if (_setjmp(buf) == 0) {\n",
7740 " stack = &buf;\n",
7741 " int *p = &cells[seed + 3];\n",
7742 " int a = v + 8;\n",
7743 " int b = seed * 2005;\n",
7744 " int *q = &cells[v + 1];\n",
7745 " work();\n",
7746 " sink = *p + a + b + *q;\n",
7747 " } else {\n",
7748 " printf(\"%d\\n\", v);\n",
7749 " }\n",
7750 " return 0;\n",
7751 "}\n",
7752 );
7753 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
7754 let mut opts = options();
7755 opts.emit = EmitKind::Asm;
7756 opts.opt_level = level;
7757 let result = run(&opts, source);
7758 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
7759 let text = result.text();
7760 let body = text.split_once("\nmain:\n").expect("the function").1;
7761 let lines: Vec<&str> = body.lines().map(str::trim).collect();
7762 let save = lines.iter().position(|l| *l == "call\t_setjmp").expect("the save");
7763 let jump = lines[save..]
7764 .iter()
7765 .position(|l| *l == "call\twork" || *l == "call\tlongjmp")
7766 .map(|at| save + at)
7767 .unwrap_or_else(|| panic!("the call that jumps back at {level:?}:\n{text}"));
7768 let printf = lines.iter().position(|l| *l == "call\tprintf").expect("the handler");
7769 let slot = lines[jump..printf]
7771 .iter()
7772 .rev()
7773 .find_map(|l| l.strip_suffix(", %rsi").or_else(|| l.strip_suffix(", %esi")))
7774 .and_then(|l| l.split_once('\t'))
7775 .map(|(_, place)| place)
7776 .filter(|place| place.ends_with("(%rsp)") || place.ends_with("(%rbp)"))
7777 .unwrap_or_else(|| panic!("the handler reads v from a slot at {level:?}:\n{text}"));
7778 let writes = |l: &&str| {
7779 !l.starts_with("cmp") && !l.starts_with("test") && l.ends_with(&format!(", {slot}"))
7780 };
7781 assert!(
7782 lines[..save].iter().any(writes),
7783 "{slot} is written before the save at {level:?}:\n{text}"
7784 );
7785 assert!(
7786 !lines[save..jump].iter().any(writes),
7787 "{slot} is written again before the jump at {level:?}:\n{text}"
7788 );
7789 }
7790 }
7791
7792 fn across(declared: &str, save: &str) -> String {
7795 asm(&format!(
7796 "typedef long sigjmp_buf[25];\n{declared}int id(int);\nvoid thrower(int);\n\
7797 int work(int n) {{\n\
7798 \x20 int v0 = id(n), v1 = id(n + 1), v2 = id(n + 2), v3 = id(n + 3), v4 = id(n + 4);\n\
7799 \x20 sigjmp_buf b;\n\
7800 \x20 if ({save}(b, 0) == 0) {{\n\
7801 \x20 int w0 = id(v0 + v1), w1 = id(v1 + v2), w2 = id(v2 + v3);\n\
7802 \x20 int w3 = id(v3 + v4), w4 = id(v4 + v0);\n\
7803 \x20 thrower(n);\n\
7804 \x20 return w0 ^ w1 ^ w2 ^ w3 ^ w4;\n\
7805 \x20 }}\n\
7806 \x20 return v0 + v1 + v2 + v3 + v4;\n\
7807 }}\n"
7808 ))
7809 }
7810
7811 fn each_spill_slot_written_once(text: &str) {
7813 let mut stored = Vec::new();
7814 for line in text.lines().map(str::trim) {
7815 let Some(operands) = line.strip_prefix("movq\t%") else { continue };
7816 if let Some((_, place)) = operands.split_once(", ") {
7817 if place.ends_with("(%rsp)") {
7818 assert!(!stored.contains(&place), "{place} is written twice:\n{text}");
7819 stored.push(place);
7820 }
7821 }
7822 }
7823 assert!(!stored.is_empty(), "something should have been spilled:\n{text}");
7824 }
7825
7826 #[test]
7835 fn the_save_writes_four_words_and_carries_on_in_a_new_block() {
7836 let text =
7837 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
7838 let body = text.split_once("\nf:\n").expect("the function").1;
7839 assert!(body.contains("\tmovq\t%rsp, %rbp\n"), "a frame pointer whatever: {text}");
7840 assert!(body.contains("\tsubq\t$8, %rsp\n"), "no red zone: {text}");
7841 assert!(body.contains("\tmovq\t%rbp, (%rax)\n"), "the frame pointer: {text}");
7842 assert!(body.contains("\tmovq\t%rsp, 16(%rax)\n"), "the stack pointer: {text}");
7843 assert!(body.contains("\tleaq\t.Lf_1(%rip), %rcx\n"), "where to come back to: {text}");
7844 assert!(body.contains("\tmovq\t%rcx, 8(%rax)\n"), "and that goes in the buffer: {text}");
7845 let back = body.split_once(".Lf_1:\n").expect("the block control comes back to").1;
7846 assert!(back.starts_with("\tmovq\t(%rsp), %rax\n"), "the answer is read back: {text}");
7847 }
7848
7849 #[test]
7857 fn a_save_destroys_every_register_the_allocator_hands_out() {
7858 let text =
7859 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
7860 for reg in ["%rbx", "%r12", "%r13", "%r14", "%r15"] {
7861 assert!(text.contains(&format!("\tpushq\t{reg}\n")), "{reg} is saved: {text}");
7862 assert!(text.contains(&format!("\tpopq\t{reg}\n")), "{reg} is restored: {text}");
7863 }
7864 }
7865
7866 #[test]
7873 fn the_restore_puts_the_frame_back_before_it_jumps() {
7874 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
7875 let mut opts = options();
7876 opts.emit = EmitKind::Asm;
7877 opts.opt_level = level;
7878 let source = "void *buf[5];\nvoid g(void) { __builtin_longjmp(buf, 1); }\n";
7879 let result = run(&opts, source);
7880 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
7881 let text = result.text().to_owned();
7882 let jump = text.find("\tjmp\t*%").unwrap_or_else(|| panic!("an indirect jump: {text}"));
7883 let stack = text.find(", %rsp\n").unwrap_or_else(|| panic!("the stack back: {text}"));
7884 let frame = text.find(", %rbp\n").unwrap_or_else(|| panic!("the frame back: {text}"));
7885 assert!(stack < jump, "the stack goes back first at {level:?}: {text}");
7886 assert!(frame < jump, "and so does the frame at {level:?}: {text}");
7887 }
7888 }
7889
7890 #[test]
7896 fn a_longjmp_whose_second_argument_is_not_one_is_turned_down() {
7897 for source in [
7898 "void *buf[5];\nvoid f(void) { __builtin_longjmp(buf, 0); }\n",
7899 "void *buf[5];\nextern int v;\nvoid f(void) { __builtin_longjmp(buf, v); }\n",
7900 ] {
7901 let messages = errors(source);
7902 let named = messages.iter().any(|m| m.contains("E0710"));
7903 assert!(named, "expected a complaint about the value in {messages:?}");
7904 }
7905 }
7906
7907 #[test]
7912 fn a_static_function_nothing_refers_to_is_not_emitted() {
7913 let text = ir("static int dropped(void) { return 1; }\n\
7914 static int kept(void) { return 2; }\n\
7915 int main(void) { return kept(); }\n");
7916 assert!(text.contains("func @kept"), "{text}");
7917 assert!(!text.contains("dropped"), "{text}");
7918 }
7919
7920 #[test]
7926 fn two_static_functions_that_only_call_each_other_are_both_dropped() {
7927 let text = ir("static int ping(void);\n\
7928 static int pong(void) { return ping(); }\n\
7929 static int ping(void) { return pong(); }\n\
7930 int main(void) { return 0; }\n");
7931 assert!(!text.contains("ping"), "{text}");
7932 assert!(!text.contains("pong"), "{text}");
7933 }
7934
7935 #[test]
7941 fn naming_a_static_function_anywhere_keeps_it() {
7942 let text = ir("static int by_address(void) { return 1; }\n\
7943 static int in_an_image(void) { return 2; }\n\
7944 static int deeper(void) { return 3; }\n\
7945 static int reaches_deeper(void) { return deeper(); }\n\
7946 static int (*table[1])(void) = {in_an_image};\n\
7947 int main(void) {\n\
7948 int (*p)(void) = by_address;\n\
7949 return p() + table[0]() + reaches_deeper();\n\
7950 }\n");
7951 for kept in ["by_address", "in_an_image", "deeper", "reaches_deeper"] {
7952 assert!(text.contains(&format!("func @{kept}")), "expected {kept} in:\n{text}");
7953 }
7954 }
7955
7956 #[test]
7962 fn an_attribute_keeps_a_static_function_nothing_refers_to() {
7963 for attribute in ["used", "retain", "constructor", "destructor", "__used__"] {
7964 let source = format!(
7965 "__attribute__(({attribute})) static int kept(void) {{ return 1; }}\n\
7966 int main(void) {{ return 0; }}\n"
7967 );
7968 let text = ir(&source);
7969 assert!(text.contains("func @kept"), "for {attribute}:\n{text}");
7970 }
7971 }
7972
7973 #[test]
7976 fn nonnull_is_answered_yes_and_taken_with_or_without_operands() {
7977 let text = ir("#if !__has_attribute(nonnull) || !__has_attribute(__nonnull__)\n\
7978 #error nonnull\n\
7979 #endif\n\
7980 __attribute__((nonnull)) int first(char *p);\n\
7981 int both(char *a, int n, char *b) __attribute__((__nonnull__(1, 3)));\n\
7982 int both(char *a, int n, char *b) { return first(a) + n + (b != 0); }\n");
7983 assert!(text.contains("func @both"), "{text}");
7984 }
7985
7986 #[test]
7989 fn a_function_anything_could_call_is_emitted_without_being_called() {
7990 let text =
7991 ir("int nobody_here_calls_it(void) { return 1; }\nint main(void) { return 0; }\n");
7992 assert!(text.contains("func @nobody_here_calls_it"), "{text}");
7993 }
7994
7995 #[test]
8002 fn a_classification_c_has_an_operator_for_is_that_operator() {
8003 for (builtin, operator) in [
8004 ("__builtin_isgreater", "binary >"),
8005 ("__builtin_isgreaterequal", "binary >="),
8006 ("__builtin_isless", "binary <"),
8007 ("__builtin_islessequal", "binary <="),
8008 ] {
8009 let source = format!("int f(double x, double y) {{ return {builtin}(x, y); }}\n");
8010 let text = tast(&source);
8011 assert!(text.contains(&format!("{operator} : int")), "for {builtin}:\n{text}");
8012 }
8013 }
8014
8015 #[test]
8024 fn the_classification_builtins_are_comparisons_and_not_calls() {
8025 let text = body("int f(double x, double y) { return __builtin_isunordered(x, y); }\n");
8026 assert_eq!(
8027 text,
8028 "block0(%0: f64, %1: f64):\n %2 = fcmp uno %0, %1\n %3 = zext.i32 \
8029 %2\n return %3\n"
8030 );
8031
8032 let text = body("int f(double x, double y) { return __builtin_islessgreater(x, y); }\n");
8034 assert!(text.contains("fcmp one %0, %1"), "{text}");
8035
8036 let text = body("int f(double x) { return __builtin_isnan(x); }\n");
8037 assert!(text.contains("fcmp uno %0, %0"), "{text}");
8038
8039 let text = body("int f(double x) { return __builtin_isinf(x); }\n");
8040 assert!(text.contains("fconst.f64 0x7ff0000000000000"), "{text}");
8041 assert!(text.contains("fconst.f64 0xfff0000000000000"), "{text}");
8042 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
8043 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
8044 assert!(text.contains("%5 = or %3, %4"), "{text}");
8045
8046 let text = body("int f(double x) { return __builtin_isfinite(x); }\n");
8049 assert!(text.contains("%3 = fcmp olt %2, %0"), "{text}");
8050 assert!(text.contains("%4 = fcmp olt %0, %1"), "{text}");
8051 assert!(text.contains("%5 = and %3, %4"), "{text}");
8052
8053 let text = body("int f(double x) { return __builtin_signbit(x); }\n");
8054 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
8055 assert!(text.contains("icmp slt %1, %2"), "{text}");
8056
8057 let text = body("int f(long double x) { return __builtin_signbitl(x); }\n");
8061 assert!(text.contains("load.i16"), "{text}");
8062 assert!(text.contains("icmp slt"), "{text}");
8063 assert!(!text.contains("i80"), "{text}");
8064
8065 let text = body("double g(void);\nint f(void) { return __builtin_isnan(g()); }\n");
8068 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
8069 }
8070
8071 #[test]
8078 fn a_classification_spelling_that_names_a_width_converts_before_it_asks() {
8079 let text = ir(concat!(
8080 "int a = __builtin_isinff(1e300);\n",
8081 "int b = __builtin_isinf(1e300);\n",
8082 "int c = __builtin_isnan(0.0);\n",
8086 "int d = __builtin_signbit(-0.0);\n",
8087 "int e = __builtin_islessgreater(1.0, 2.0);\n",
8088 ));
8089 assert!(text.contains("global @a : i32 = 1,"), "{text}");
8090 assert!(text.contains("global @b : i32 = 0,"), "{text}");
8091 assert!(text.contains("global @c : i32 = 0,"), "{text}");
8092 assert!(text.contains("global @d : i32 = 1,"), "{text}");
8093 assert!(text.contains("global @e : i32 = 1,"), "{text}");
8094 }
8095
8096 #[test]
8098 fn a_classification_builtin_refuses_an_argument_that_is_not_floating_point() {
8099 let mut opts = options();
8100 opts.emit = EmitKind::Ir;
8101 let source = concat!(
8102 "int a(int x) { return __builtin_isnan(x); }\n",
8103 "int b(int x, int y) { return __builtin_isunordered(x, y); }\n",
8104 "int c(double x) { return __builtin_isnan(x, x); }\n",
8105 );
8106 let messages = run(&opts, source).messages;
8107 assert_eq!(
8108 messages,
8109 [
8110 "/main.c:1:23: error: non-floating-point argument in call to function \
8111 '__builtin_isnan' [E0685]",
8112 "/main.c:2:30: error: non-floating-point arguments in call to function \
8113 '__builtin_isunordered' [E0685]",
8114 "/main.c:3:26: error: too many arguments to function '__builtin_isnan' [E0511]",
8115 ]
8116 );
8117 }
8118
8119 #[test]
8128 fn the_last_three_classification_builtins_are_comparisons_and_not_calls() {
8129 let text = body("int f(double x) { return __builtin_isnormal(x); }\n");
8130 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
8134 assert!(text.contains("%2 = iconst.i64 9223372036854775807"), "{text}");
8135 assert!(text.contains("%3 = and %1, %2"), "{text}");
8136 assert!(text.contains("%4 = iconst.i64 4503599627370496"), "{text}");
8137 assert!(text.contains("%5 = iconst.i64 9218868437227405312"), "{text}");
8138 assert!(text.contains("%6 = icmp uge %3, %4"), "{text}");
8139 assert!(text.contains("%7 = icmp ult %3, %5"), "{text}");
8140 assert!(text.contains("%8 = and %6, %7"), "{text}");
8141
8142 let text = body("int f(long double x) { return __builtin_isnormal(x); }\n");
8147 assert!(text.contains("fconst.f80 0x18000000000000000"), "{text}");
8148 assert!(text.contains("fconst.f80 0x7fff8000000000000000"), "{text}");
8149 assert!(text.contains("fcmp oge"), "{text}");
8150 assert!(text.contains("fcmp olt"), "{text}");
8151
8152 let text = body("int f(double x) { return __builtin_isinf_sign(x); }\n");
8153 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
8154 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
8155 assert!(text.contains("%7 = sub %5, %6"), "{text}");
8156
8157 let text = body("int f(double x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n");
8158 assert!(text.contains("fcmp uno %0, %0"), "{text}");
8159 assert!(text.contains("fcmp oeq %0, %6"), "{text}");
8160 assert_eq!(text.matches(" = zext.i32 ").count(), 4, "{text}");
8164 assert_eq!(text.matches(" = xor ").count(), 4, "{text}");
8165 assert!(!text.contains("call"), "{text}");
8166
8167 let text = body(concat!(
8170 "double g(void);\n",
8171 "int f(void) { return __builtin_fpclassify(0, 1, 2, 3, 4, g()); }\n",
8172 ));
8173 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
8174 }
8175
8176 #[test]
8183 fn the_last_three_classification_builtins_fold_where_their_operand_is_a_constant() {
8184 let text = ir(concat!(
8185 "int a = __builtin_isnormal(1.0);\n",
8186 "int b = __builtin_isnormal(0.0);\n",
8187 "int c = __builtin_isnormal(1.0 / 0.0);\n",
8188 "int d = __builtin_isinf_sign(-1.0 / 0.0);\n",
8189 "int e = __builtin_isinf_sign(1.0);\n",
8190 "int g = __builtin_fpclassify(0, 1, 2, 3, 4, 0.0);\n",
8191 "int h = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0);\n",
8192 "int i = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0 / 0.0);\n",
8193 ));
8194 assert!(text.contains("global @a : i32 = 1,"), "{text}");
8195 assert!(text.contains("global @b : i32 = 0,"), "{text}");
8196 assert!(text.contains("global @c : i32 = 0,"), "{text}");
8197 assert!(text.contains("global @d : i32 = -1,"), "{text}");
8198 assert!(text.contains("global @e : i32 = 0,"), "{text}");
8199 assert!(text.contains("global @g : i32 = 4,"), "{text}");
8200 assert!(text.contains("global @h : i32 = 2,"), "{text}");
8201 assert!(text.contains("global @i : i32 = 1,"), "{text}");
8202 }
8203
8204 #[test]
8210 fn fpclassify_refuses_an_answer_that_is_not_an_integer_constant() {
8211 let mut opts = options();
8212 opts.emit = EmitKind::Ir;
8213 let source = concat!(
8214 "int a(double x, int n) { return __builtin_fpclassify(0, 1, n, 3, 4, x); }\n",
8215 "int b(double x) { return __builtin_fpclassify(0, 1, 2, 3, x); }\n",
8216 "int c(int x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n",
8217 );
8218 let messages = run(&opts, source).messages;
8219 assert_eq!(
8220 messages,
8221 [
8222 "/main.c:1:60: error: non-const integer argument 3 in call to function \
8223 '__builtin_fpclassify' [E0687]",
8224 "/main.c:2:26: error: too few arguments to function '__builtin_fpclassify' \
8225 [E0511]",
8226 "/main.c:3:23: error: non-floating-point argument in call to function \
8227 '__builtin_fpclassify' [E0685]",
8228 ]
8229 );
8230 }
8231
8232 #[test]
8240 fn a_builtin_whose_answer_is_a_constant_is_one_and_not_a_call() {
8241 let text = ir(concat!(
8242 "double a = __builtin_inf();\n",
8243 "float b = __builtin_huge_valf();\n",
8244 "long double c = __builtin_infl();\n",
8245 "double d = __builtin_huge_val();\n",
8246 ));
8247 assert!(text.contains("global @a : f64 = 0x7ff0000000000000,"), "{text}");
8248 assert!(text.contains("global @b : f32 = 0x7f800000,"), "{text}");
8249 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
8250 assert!(text.contains("global @d : f64 = 0x7ff0000000000000,"), "{text}");
8251 assert!(!text.contains("call"), "{text}");
8252 }
8253
8254 #[test]
8263 fn a_nan_is_written_with_the_payload_the_program_asked_for() {
8264 let text = ir(concat!(
8265 "double a = __builtin_nan(\"\");\n",
8266 "double b = __builtin_nan(\"0x1\");\n",
8267 "double c = __builtin_nan(\"010\");\n",
8269 "double d = __builtin_nans(\"\");\n",
8270 "double e = __builtin_nans(\"0x1\");\n",
8271 "float f = __builtin_nanf(\"0x1\");\n",
8272 "float g = __builtin_nansf(\"\");\n",
8273 "long double h = __builtin_nansl(\"\");\n",
8274 ));
8275 assert!(text.contains("global @a : f64 = 0x7ff8000000000000,"), "{text}");
8276 assert!(text.contains("global @b : f64 = 0x7ff8000000000001,"), "{text}");
8277 assert!(text.contains("global @c : f64 = 0x7ff8000000000008,"), "{text}");
8278 assert!(text.contains("global @d : f64 = 0x7ff4000000000000,"), "{text}");
8279 assert!(text.contains("global @e : f64 = 0x7ff0000000000001,"), "{text}");
8280 assert!(text.contains("global @f : f32 = 0x7fc00001,"), "{text}");
8281 assert!(text.contains("global @g : f32 = 0x7fa00000,"), "{text}");
8282 assert!(text.contains("f80 0x7fffa000000000000000"), "{text}");
8283
8284 let text = ir(concat!(
8287 "double f(const char *p) { return __builtin_nan(p); }\n",
8288 "double g(void) { return __builtin_nans(\"1x\"); }\n",
8289 ));
8290 assert_eq!(text.matches("call @nan(").count(), 1, "{text}");
8291 assert_eq!(text.matches("call @nans(").count(), 1, "{text}");
8292 }
8293
8294 #[test]
8302 fn the_length_and_the_order_of_a_string_literal_are_known_here() {
8303 let text = ir(concat!(
8304 "unsigned long a = __builtin_strlen(\"hello\");\n",
8305 "unsigned long b = __builtin_strlen(\"a\\0bc\");\n",
8306 "int c = __builtin_strcmp(\"X\", \"X\\376\") < 0;\n",
8307 "int d = __builtin_strcmp(\"abc\", \"abc\");\n",
8308 "int e = __builtin_strcmp(\"abc\", \"ab\") > 0;\n",
8309 ));
8310 assert!(text.contains("global @a : i64 = 5,"), "{text}");
8311 assert!(text.contains("global @b : i64 = 1,"), "{text}");
8312 assert!(text.contains("global @c : i32 = 1,"), "{text}");
8313 assert!(text.contains("global @d : i32 = 0,"), "{text}");
8314 assert!(text.contains("global @e : i32 = 1,"), "{text}");
8315 assert!(!text.contains("call"), "{text}");
8316
8317 let text = ir("unsigned long f(const char *p) { return __builtin_strlen(p); }\n");
8319 assert!(text.contains("call @strlen("), "{text}");
8320 }
8321
8322 #[test]
8329 fn a_sign_builtin_is_a_mask_over_the_bits_and_not_a_call() {
8330 let text = body("double f(double x) { return __builtin_fabs(x); }\n");
8331 assert!(text.contains("bitcast.i64 %0"), "{text}");
8332 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
8333 assert!(text.contains("and %1, %2"), "{text}");
8334 assert!(text.contains("bitcast.f64 %3"), "{text}");
8335 assert!(!text.contains("call"), "{text}");
8336
8337 let text = body("double f(double x, double y) { return __builtin_copysign(x, y); }\n");
8338 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
8339 assert!(text.contains("%8 = or %4, %7"), "{text}");
8340 assert!(!text.contains("call"), "{text}");
8341
8342 let text = body("long double f(long double x) { return __builtin_fabsl(x); }\n");
8345 assert!(text.contains("iconst.i16 32767"), "{text}");
8346 assert!(text.contains("load.f80"), "{text}");
8347 assert!(!text.contains("call"), "{text}");
8348
8349 let text = body("double f(float x) { return __builtin_fabs(x); }\n");
8352 assert!(text.contains("fpext.f64 %0"), "{text}");
8353 assert!(text.contains("bitcast.i64 %1"), "{text}");
8354 }
8355
8356 #[test]
8363 fn a_shuffle_picks_each_lane_by_the_low_bits_of_the_mask() {
8364 let text = body(concat!(
8365 "typedef int v2 __attribute__((vector_size(8)));\n",
8366 "void f(v2 *v, v2 m) { *v = __builtin_shuffle(*v, m); }\n",
8367 ));
8368 assert!(text.contains("memcpy"), "{text}");
8369 assert_eq!(text.matches("iconst.i32 1\n").count(), 2, "{text}");
8370 assert_eq!(text.matches(" = and ").count(), 2, "{text}");
8371 assert!(!text.contains("call"), "{text}");
8372
8373 let text = body(concat!(
8376 "typedef char v4 __attribute__((vector_size(4)));\n",
8377 "v4 f(v4 a, v4 b, v4 m) { return __builtin_shuffle(a, b, m); }\n",
8378 ));
8379 assert_eq!(text.matches("iconst.i32 7\n").count(), 4, "{text}");
8380 assert!(text.contains("zext.i32"), "{text}");
8381 assert!(!text.contains("call"), "{text}");
8382 }
8383
8384 #[test]
8388 fn the_arguments_a_function_was_called_with_are_saved_on_the_way_in() {
8389 let text =
8390 mir("void *f(int a, double b) { (void)a; (void)b; return __builtin_apply_args(); }\n");
8391 assert!(text.matches("x64.mov_mr_64").count() >= 7, "{text}");
8393 assert!(text.matches("x64.movaps_mr").count() >= 8, "{text}");
8394 for reg in ["$rdi", "$rsi", "$rdx", "$rcx", "$r8", "$r9", "$xmm0", "$xmm7"] {
8395 assert!(text.contains(reg), "{reg} is not saved in\n{text}");
8396 }
8397
8398 let text = mir("int f(int a) { return a; }\n");
8400 assert!(!text.contains("movaps_mr"), "{text}");
8401 }
8402
8403 #[test]
8407 fn a_call_built_from_saved_arguments_loads_every_argument_register() {
8408 let text = mir(concat!(
8409 "void *g(void *args, void (*h)()) {\n",
8410 " return __builtin_apply(h, args, 64);\n",
8411 "}\n",
8412 ));
8413 assert!(text.matches("x64.mov_rm_64").count() >= 7, "{text}");
8414 assert!(text.matches("x64.movaps_rm").count() >= 8, "{text}");
8415 assert!(text.contains("call"), "{text}");
8416 assert!(text.matches("x64.movaps_mr").count() >= 2, "{text}");
8418
8419 let mut opts = options();
8421 opts.emit = EmitKind::Ir;
8422 let result = run(
8423 &opts,
8424 "void *g(void *a, void (*h)(), int n) { return __builtin_apply(h, a, n); }\n",
8425 );
8426 assert!(result.failed(), "{:?}", result.messages);
8427 assert!(
8428 result
8429 .messages
8430 .iter()
8431 .any(|m| m.contains("the size given to '__builtin_apply' is a constant")),
8432 "{:?}",
8433 result.messages
8434 );
8435 }
8436
8437 #[test]
8439 fn a_shuffle_refuses_what_gcc_refuses() {
8440 let mut opts = options();
8441 opts.emit = EmitKind::Ir;
8442 let source = concat!(
8443 "typedef int v4 __attribute__((vector_size(16)));\n",
8444 "typedef float f4 __attribute__((vector_size(16)));\n",
8445 "typedef short s8 __attribute__((vector_size(16)));\n",
8446 "typedef long long l4 __attribute__((vector_size(32)));\n",
8447 "void a(v4 x, f4 m) { __builtin_shuffle(x, m); }\n",
8448 "void b(int x, v4 m) { __builtin_shuffle(x, m); }\n",
8449 "void c(v4 x, f4 y, v4 m) { __builtin_shuffle(x, y, m); }\n",
8450 "void d(v4 x, s8 m) { __builtin_shuffle(x, m); }\n",
8451 "void e(f4 x, l4 m) { __builtin_shuffle(x, m); }\n",
8452 "void g(v4 x) { __builtin_shuffle(x); }\n",
8453 );
8454 let messages = run(&opts, source).messages;
8455 let wanted = [
8456 "last argument must be an integer vector [E0715]",
8457 "arguments must be vectors [E0715]",
8458 "argument vectors must be of the same type [E0715]",
8459 "number of elements of the argument vector(s) and the mask vector should be the same \
8460 [E0715]",
8461 "argument vector(s) inner type must have the same size as inner type of the mask \
8462 [E0715]",
8463 "too few arguments to function '__builtin_shuffle' [E0511]",
8464 ];
8465 assert_eq!(messages.len(), wanted.len(), "{messages:?}");
8466 for (message, wanted) in messages.iter().zip(wanted) {
8467 assert!(message.ends_with(wanted), "{message}");
8468 }
8469 }
8470
8471 #[test]
8480 fn the_plain_math_names_are_the_same_mask_and_not_a_call() {
8481 let text =
8482 body(concat!("double fabs(double x);\n", "double f(double x) { return fabs(x); }\n",));
8483 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
8484 assert!(!text.contains("call"), "{text}");
8485
8486 let text =
8487 body(concat!("float fabsf(float x);\n", "float f(float x) { return fabsf(x); }\n",));
8488 assert!(text.contains("bitcast.i32 %0"), "{text}");
8489 assert!(!text.contains("call"), "{text}");
8490
8491 let text = body(concat!(
8492 "double copysign(double x, double y);\n",
8493 "double f(double x, double y) { return copysign(x, y); }\n",
8494 ));
8495 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
8496 assert!(!text.contains("call"), "{text}");
8497
8498 let text = body(concat!(
8499 "float copysignf(float x, float y);\n",
8500 "float f(float x, float y) { return copysignf(x, y); }\n",
8501 ));
8502 assert!(!text.contains("call"), "{text}");
8503
8504 let text = ir(concat!(
8508 "long double fabsl(long double x);\n",
8509 "long double f(long double x) { return fabsl(x); }\n",
8510 ));
8511 assert!(text.contains("call @fabsl"), "{text}");
8512 }
8513
8514 #[test]
8522 fn a_plain_math_name_the_program_took_is_the_programs_own_function() {
8523 let taken = concat!(
8524 "static double fabs(double b) { return 7; }\n",
8525 "double f(double x) { return fabs(x); }\n",
8526 );
8527 assert!(ir(taken).contains("call @fabs"), "a static definition is the program's own");
8528
8529 let retyped = concat!("int fabs(int b);\n", "int f(int x) { return fabs(x); }\n");
8530 assert!(ir(retyped).contains("call @fabs"), "another type is another function");
8531
8532 let plain = concat!("double fabs(double b);\n", "double f(double x) { return fabs(x); }\n");
8533 let mut opts = options();
8534 opts.emit = EmitKind::Ir;
8535 assert!(!run(&opts, plain).text().contains("call @fabs"), "the library's by default");
8536
8537 opts.builtins = false;
8538 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin");
8539
8540 opts.builtins = true;
8541 opts.no_builtin = vec!["fabs".to_owned()];
8542 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin-fabs");
8543 let one = concat!(
8544 "double copysign(double a, double b);\n",
8545 "double f(double x) { return copysign(x, 1.0); }\n",
8546 );
8547 assert!(!run(&opts, one).text().contains("call @copysign"), "one name and not the family");
8548
8549 opts.no_builtin = Vec::new();
8551 opts.builtins = false;
8552 let prefixed = "double f(double x) { return __builtin_fabs(x); }\n";
8553 assert!(!run(&opts, prefixed).text().contains("call @fabs"), "the prefix is not a library");
8554 }
8555
8556 #[test]
8565 fn the_sign_builtins_answer_a_zero_and_a_nan_the_way_the_bits_say() {
8566 let text = ir(concat!(
8567 "double a = __builtin_fabs(-3.5);\n",
8568 "double b = __builtin_copysign(1.0, -0.0);\n",
8569 "double c = __builtin_copysign(0.0, -2.0);\n",
8570 "double d = __builtin_copysign(-__builtin_nan(\"\"), 1.0);\n",
8572 "double e = __builtin_fabs(-__builtin_nan(\"0x1\"));\n",
8573 "float g = __builtin_copysignf(-0.0f, 2.0f);\n",
8574 "long double h = __builtin_copysignl(1.0L, -1.0L);\n",
8575 "long double i = __builtin_fabsl(-__builtin_infl());\n",
8576 ));
8577 assert!(text.contains("global @a : f64 = 0x400c000000000000,"), "{text}");
8578 assert!(text.contains("global @b : f64 = 0xbff0000000000000,"), "{text}");
8579 assert!(text.contains("global @c : f64 = 0x8000000000000000,"), "{text}");
8580 assert!(text.contains("global @d : f64 = 0x7ff8000000000000,"), "{text}");
8581 assert!(text.contains("global @e : f64 = 0x7ff8000000000001,"), "{text}");
8582 assert!(text.contains("global @g : f32 = 0x0,"), "{text}");
8583 assert!(text.contains("f80 0xbfff8000000000000000"), "{text}");
8584 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
8585 }
8586
8587 #[test]
8594 fn the_sign_of_a_long_double_is_in_the_word_at_the_top_of_it() {
8595 for source in [
8596 "int f(long double x) { return __builtin_signbit(x); }\n",
8597 "long double f(long double x) { return __builtin_fabsl(x); }\n",
8598 "long double f(long double x, long double y) { return __builtin_copysignl(x, y); }\n",
8599 "int f(long double x) { return __builtin_isnormal(x); }\n",
8600 ] {
8601 let text = body(source);
8602 assert!(!text.contains("i80"), "{text}");
8603 assert!(text.contains("i16"), "{text}");
8604 }
8605 }
8606
8607 #[test]
8615 fn the_complex_builtins_are_the_halves_of_the_value_and_not_a_call() {
8616 let text = body("double f(_Complex double z) { return __builtin_creal(z); }\n");
8617 assert!(!text.contains("call"), "{text}");
8618 let text = body("double f(_Complex double z) { return __builtin_cimag(z); }\n");
8619 assert!(!text.contains("call"), "{text}");
8620
8621 let text = body("_Complex double f(_Complex double z) { return __builtin_conj(z); }\n");
8624 assert_eq!(text.matches("fneg").count(), 1, "{text}");
8625 assert!(!text.contains("call"), "{text}");
8626 let negated = body("_Complex double f(_Complex double z) { return -z; }\n");
8627 assert_eq!(negated.matches("fneg").count(), 2, "{negated}");
8628
8629 let written = body("_Complex double f(_Complex double z) { return ~z; }\n");
8632 assert_eq!(written, text, "the name and the operator are the same thing");
8633
8634 let text = body(concat!(
8636 "double creal(_Complex double z);\n",
8637 "double f(_Complex double z) { return creal(z); }\n",
8638 ));
8639 assert!(!text.contains("call"), "{text}");
8640 let text = body(concat!(
8641 "_Complex float conjf(_Complex float z);\n",
8642 "_Complex float f(_Complex float z) { return conjf(z); }\n",
8643 ));
8644 assert_eq!(text.matches("fneg").count(), 1, "{text}");
8645 assert!(!text.contains("call"), "{text}");
8646
8647 let taken = concat!(
8650 "static double creal(_Complex double z) { return 7; }\n",
8651 "double f(_Complex double z) { return creal(z); }\n",
8652 );
8653 assert!(ir(taken).contains("call @creal"), "a static definition is the program's own");
8654 let retyped = concat!("int cimag(int z);\n", "int f(int z) { return cimag(z); }\n");
8655 assert!(ir(retyped).contains("call @cimag"), "another type is another function");
8656 let plain = concat!(
8657 "double cimag(_Complex double z);\n",
8658 "double f(_Complex double z) { return cimag(z); }\n",
8659 );
8660 let mut opts = options();
8661 opts.emit = EmitKind::Ir;
8662 opts.builtins = false;
8663 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin");
8664 opts.builtins = true;
8665 opts.no_builtin = vec!["cimag".to_owned()];
8666 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin-cimag");
8667
8668 let text = ir(concat!(
8670 "double a = __builtin_creal(1.5 + 2.5i);\n",
8671 "double b = __builtin_cimag(1.5 + 2.5i);\n",
8672 "_Complex double c = __builtin_conj(1.5 + 2.5i);\n",
8673 ));
8674 assert!(text.contains("global @a : f64 = 0x3ff8000000000000,"), "{text}");
8675 assert!(text.contains("global @b : f64 = 0x4004000000000000,"), "{text}");
8676 assert!(
8677 text.contains("{ f64 0x3ff8000000000000, f64 0xc004000000000000 }"),
8678 "the conjugate of a constant is the constant with the second half negated: {text}"
8679 );
8680 assert!(!text.contains("call"), "{text}");
8681 }
8682
8683 #[test]
8691 fn a_math_library_builtin_of_a_constant_is_the_answer_and_not_a_call() {
8692 let text = ir(concat!(
8693 "double a = __builtin_ceil(1.5);\n",
8694 "double b = __builtin_floor(1.5);\n",
8695 "double c = __builtin_trunc(-1.5);\n",
8696 "double d = __builtin_round(2.5);\n",
8699 "double e = __builtin_ceil(-0.5);\n",
8701 "double f = __builtin_fmax(1.0, 2.0);\n",
8702 "double g = __builtin_fmin(1.0, 2.0);\n",
8703 "float h = __builtin_ceilf(1.25f);\n",
8704 "double ceil(double x);\n",
8707 "double i = ceil(2.25);\n",
8708 ));
8709 assert!(text.contains("global @a : f64 = 0x4000000000000000,"), "{text}");
8710 assert!(text.contains("global @b : f64 = 0x3ff0000000000000,"), "{text}");
8711 assert!(text.contains("global @c : f64 = 0xbff0000000000000,"), "{text}");
8712 assert!(text.contains("global @d : f64 = 0x4008000000000000,"), "{text}");
8713 assert!(text.contains("global @e : f64 = 0x8000000000000000,"), "{text}");
8714 assert!(text.contains("global @f : f64 = 0x4000000000000000,"), "{text}");
8715 assert!(text.contains("global @g : f64 = 0x3ff0000000000000,"), "{text}");
8716 assert!(text.contains("global @h : f32 = 0x40000000,"), "{text}");
8717 assert!(text.contains("global @i : f64 = 0x4008000000000000,"), "{text}");
8718 assert!(!text.contains("call"), "{text}");
8719 }
8720
8721 #[test]
8729 fn a_math_library_builtin_of_anything_else_is_a_call_to_the_library() {
8730 let text = ir(concat!(
8731 "double f(double x) { return __builtin_ceil(x); }\n",
8732 "float g(float x) { return __builtin_floorf(x); }\n",
8733 "double h(double x, double y) { return __builtin_fmax(x, y); }\n",
8734 ));
8735 assert!(text.contains("call @ceil("), "{text}");
8736 assert!(text.contains("call @floorf("), "{text}");
8737 assert!(text.contains("call @fmax("), "{text}");
8738
8739 let text = ir(concat!(
8743 "double f(void) { return __builtin_rint(2.5); }\n",
8744 "double g(void) { return __builtin_nearbyint(2.5); }\n",
8745 ));
8746 assert!(text.contains("call @rint("), "{text}");
8747 assert!(text.contains("call @nearbyint("), "{text}");
8748
8749 let text = ir("double f(void) { return __builtin_fmin(__builtin_nan(\"\"), 1.0); }\n");
8752 assert!(text.contains("call @fmin("), "{text}");
8753
8754 let plain = concat!("double ceil(double x);\n", "double f(void) { return ceil(2.25); }\n");
8757 let mut opts = options();
8758 opts.emit = EmitKind::Ir;
8759 opts.no_builtin = vec!["ceil".to_owned()];
8760 assert!(run(&opts, plain).text().contains("call @ceil("), "-fno-builtin-ceil");
8761 }
8762
8763 #[test]
8770 fn a_constexpr_object_is_a_constant_wherever_one_is_required() {
8771 let text = ir(concat!(
8772 "constexpr int side = 4;\n",
8773 "constexpr int wider = side + 1;\n",
8774 "constexpr double half = 1.5;\n",
8775 "struct point { int x; int y; };\n",
8776 "constexpr struct point origin = { 5, 6 };\n",
8777 "int square[side * side];\n",
8778 "int rectangle[wider];\n",
8779 "int rounded[(int)half * 2];\n",
8780 "int across[origin.y];\n",
8781 "enum named { four = side };\n",
8782 "int e = four;\n",
8783 ));
8784 assert!(text.contains("global @square : bytes 64 ="), "{text}");
8785 assert!(text.contains("global @rectangle : bytes 20 ="), "{text}");
8786 assert!(text.contains("global @rounded : bytes 8 ="), "{text}");
8787 assert!(text.contains("global @across : bytes 24 ="), "{text}");
8788 assert!(text.contains("global @e : i32 = 4,"), "{text}");
8789
8790 let mut opts = options();
8793 opts.emit = EmitKind::Ir;
8794 let konst = "const int n = 1;\nint a[n];\n";
8795 let message = "/main.c:2:5: error: variably modified 'a' at file scope [E0538]";
8796 assert_eq!(run(&opts, konst).messages, [message]);
8797
8798 let subscript = "constexpr int t[3] = { 1, 2, 3 };\nint a[t[1]];\n";
8800 assert_eq!(run(&opts, subscript).messages, [message]);
8801
8802 let address = "constexpr int c = 3;\nint *p = &c;\n";
8804 let warning = "/main.c:2:6: warning: initialization discards 'const' qualifier from \
8805 pointer target type [E0514]";
8806 assert_eq!(run(&opts, address).messages, [warning]);
8807 }
8808
8809 #[test]
8818 fn a_member_whose_size_was_refused_is_not_a_flexible_array_member() {
8819 let mut opts = options();
8820 opts.emit = EmitKind::Ir;
8821
8822 let alone = "int k;\nextern struct D { int a[k]; } ed;\n";
8823 let message = "/main.c:2:23: error: variably modified 'a' at file scope [E0538]";
8824 assert_eq!(run(&opts, alone).messages, [message]);
8825
8826 let first = "int k;\nextern struct E { int a[k]; int b; } ee;\n";
8828 assert_eq!(run(&opts, first).messages, [message]);
8829
8830 let negative = "struct F { int a[-1]; };\n";
8833 let refused = "/main.c:1:18: error: size of array 'a' is negative [E0536]";
8834 assert_eq!(run(&opts, negative).messages, [refused]);
8835
8836 let flexible = "struct G { int a[]; };\n";
8839 let named = "/main.c:1:16: error: flexible array member in a struct with no named \
8840 members [E0554]";
8841 assert_eq!(run(&opts, flexible).messages, [named]);
8842 }
8843
8844 #[test]
8859 fn a_pointer_to_an_array_gains_a_qualifier_the_same_way_a_pointer_to_anything_else_does() {
8860 let mut opts = options();
8861 opts.emit = EmitKind::Ir;
8862 let prefix = "typedef unsigned int B[4];\nstruct H { B category[2]; };\n";
8863
8864 let adding = format!("{prefix}const B *f(struct H *h) {{ return &h->category[0]; }}\n");
8866 assert_eq!(run(&opts, &adding).messages, [] as [String; 0]);
8867
8868 let plain = concat!(
8871 "const unsigned int (*f(unsigned int (*p)[4]))[4] { return p; }\n",
8872 "const unsigned int (*g(unsigned int (*p)[2][3]))[2][3] { return p; }\n",
8873 );
8874 assert_eq!(run(&opts, plain).messages, [] as [String; 0]);
8875
8876 let dropping = format!("{prefix}B *f(const B *p) {{ return p; }}\n");
8879 let warning = "/main.c:3:27: warning: return discards 'const' qualifier from pointer target type \
8880 [E0514]";
8881 assert_eq!(run(&opts, &dropping).messages, [warning]);
8882
8883 let wrong = "const unsigned int (*f(unsigned short (*p)[4]))[4] { return p; }\n";
8886 let error = "/main.c:1:61: error: returning 'unsigned short (*)[4]' from a function with \
8887 incompatible return type 'const unsigned int (*)[4]' [E0512]";
8888 assert_eq!(run(&opts, wrong).messages, [error]);
8889 }
8890
8891 #[test]
8900 fn an_old_style_definition_takes_its_types_from_the_declarations_under_its_list() {
8901 let mut opts = options();
8904 opts.std = Std::C17;
8905 let source = concat!(
8906 "int add(a, b)\n",
8907 "int a;\n",
8908 "int b;\n",
8909 "{ return a + b; }\n",
8910 "int promoted(c)\n",
8911 "char c;\n",
8912 "{ return c; }\n",
8913 "int narrow(char);\n",
8914 "int narrow(c)\n",
8915 "char c;\n",
8916 "{ return c; }\n",
8917 "int first(a)\n",
8918 "int a[4];\n",
8919 "{ return a[0]; }\n",
8920 );
8921 let result = run(&opts, source);
8922 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
8923 let text = result.text();
8924 assert!(text.contains("add : int(int, int) function external defined"), "{text}");
8925 assert!(text.contains("promoted : int(int) function external defined"), "{text}");
8926 assert!(text.contains("c : char object automatic defined"), "{text}");
8928 assert!(text.contains("narrow : int(char) function external defined"), "{text}");
8929 assert!(text.contains("first : int(int *) function external defined"), "{text}");
8931 }
8932
8933 #[test]
8940 fn the_two_halves_of_an_old_style_parameter_list_have_to_agree() {
8941 let mut opts = options();
8942 opts.std = Std::C17;
8943 for (source, message) in [
8944 ("int f(a, a)\nint a;\n{ return a; }\n", "1:10: error: multiple parameters named 'a'"),
8945 (
8946 "int f(a)\nint a;\nint b;\n{ return a; }\n",
8947 "3:5: error: declaration for parameter 'b' but no such parameter",
8948 ),
8949 ("int f(a)\nint a;\nint a;\n{ return a; }\n", "3:5: error: redefinition of parameter"),
8950 ("int f(a)\nint a = 1;\n{ return a; }\n", "2:5: error: parameter 'a' is initialized"),
8951 (
8952 "int f(a)\nstatic int a;\n{ return a; }\n",
8953 "2:12: error: storage class specified for parameter 'a'",
8954 ),
8955 (
8956 "int f(char);\nint f(a)\nshort a;\n{ return a; }\n",
8957 "2:7: error: argument 'a' doesn't match prototype",
8958 ),
8959 ] {
8960 let result = run(&opts, source);
8961 assert!(result.failed(), "expected this to fail:\n{source}");
8962 assert!(result.messages[0].contains(message), "{:?}", result.messages);
8963 }
8964
8965 let implicit = "int f(a, b)\nint a;\n{ return a + b; }\n";
8968 let mut older = options();
8969 older.std = Std::C89;
8970 assert!(!run(&older, implicit).failed(), "{:?}", run(&older, implicit).messages);
8971 let result = run(&opts, implicit);
8972 assert!(
8973 result.messages[0].contains("1:10: error: type of 'b' defaults to 'int'"),
8974 "{:?}",
8975 result.messages
8976 );
8977
8978 let mut newer = options();
8982 newer.std = Std::C23;
8983 let plain = "int f(a)\nint a;\n{ return a; }\n";
8984 let result = run(&newer, plain);
8985 assert!(!result.failed(), "{:?}", result.messages);
8986 assert_eq!(
8987 result.messages,
8988 ["/main.c:1:5: warning: old-style function definition [E0412]"]
8989 );
8990 assert!(run(&opts, plain).messages.is_empty(), "and nothing to say in the dialects before");
8991 }
8992
8993 #[test]
9000 fn the_obsolete_designators_are_taken_and_are_pedantic_warnings() {
9001 let array = "int a[8] = { [3] 7 };\n";
9002 let member = "struct s { int x; } v = { x: 7 };\n";
9003 for source in [array, member] {
9004 let result = run(&options(), source);
9005 assert!(!result.failed(), "{:?}", result.messages);
9006 assert!(result.messages.is_empty(), "nothing to say: {:?}", result.messages);
9007 }
9008
9009 let mut asked = options();
9010 asked.pedantic = true;
9011 assert_eq!(
9012 run(&asked, array).messages,
9013 ["/main.c:1:18: warning: obsolete designator, write `[i] =` instead [E0415]"]
9014 );
9015 assert_eq!(
9016 run(&asked, member).messages,
9017 ["/main.c:1:27: warning: obsolete designator, write `.field =` instead [E0413]"]
9018 );
9019 }
9020
9021 #[test]
9028 fn a_type_is_refused_when_it_passes_the_largest_object_and_not_before() {
9029 let text = ir(concat!(
9030 "struct huge_struct { short buf[(1L << 62) - 256]; int a, b, c, d; };\n",
9031 "struct brim { char buf[9223372036854775807L]; };\n",
9032 "struct bitty { char buf[9223372036854775800L]; int x : 1; };\n",
9033 "unsigned long h = sizeof(struct huge_struct);\n",
9034 "unsigned long b = sizeof(struct brim);\n",
9035 "unsigned long y = sizeof(struct bitty);\n",
9036 ));
9037 assert!(text.contains("global @h : i64 = 9223372036854775312,"), "{text}");
9038 assert!(text.contains("global @b : i64 = 9223372036854775807,"), "{text}");
9039 assert!(text.contains("global @y : i64 = 9223372036854775804,"), "{text}");
9040
9041 let mut opts = options();
9042 opts.emit = EmitKind::Ir;
9043 let over = "struct over { char buf[9223372036854775800L]; char x[8]; };\n";
9044 let message = "/main.c:1:1: error: type 'struct over' is too large [E0560]";
9045 assert_eq!(run(&opts, over).messages, [message]);
9046 let array = "struct wide { short buf[1L << 62]; };\n";
9047 let message = "/main.c:1:25: error: size of array 'buf' exceeds \
9048 maximum object size '9223372036854775807' [E0537]";
9049 assert_eq!(run(&opts, array).messages[0], message);
9050 }
9051
9052 fn compile_bytes(source: &[u8]) -> Compiled {
9057 let mut opts = options();
9058 opts.emit = EmitKind::Ir;
9059 let mut fs = MemoryFileSystem::new();
9060 fs.insert("/main.c", source.to_vec());
9061 compile(&opts, "/main.c", &fs)
9062 }
9063
9064 #[test]
9071 fn a_byte_that_is_not_a_character_is_kept_in_a_literal_and_refused_outside_one() {
9072 let mut source = b"char s[] = \"a".to_vec();
9073 source.push(0xff);
9074 source.extend_from_slice(b"b\";\nchar c = '");
9075 source.push(0xff);
9076 source.extend_from_slice(b"';\n");
9077 let result = compile_bytes(&source);
9078 assert_eq!(result.messages, Vec::<String>::new(), "a raw byte in a literal is that byte");
9079 assert!(result.text().contains(r#"bytes "a\ffb\00""#), "{}", result.text());
9080 assert!(result.text().contains("global @c : i8 = -1,"), "{}", result.text());
9082
9083 let mut stray = b"int a".to_vec();
9084 stray.push(0xff);
9085 stray.extend_from_slice(b" = 1;\n");
9086 let result = compile_bytes(&stray);
9087 assert!(
9088 result.messages.iter().any(|m| m.contains("source is not valid UTF-8 here")),
9089 "{:?}",
9090 result.messages
9091 );
9092 }
9093
9094 #[test]
9095 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
9096 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
9097 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
9098 let expected = "\
9099func @add(i32, i32) -> i32, linkage(external) {
9100block0(%0: i32, %1: i32):
9101 %2 = add.nsw %0, %1
9102 return %2
9103}
9104";
9105 assert!(text.contains(expected), "{text}");
9106 }
9107
9108 #[test]
9109 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
9110 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
9111 assert!(!text.contains("alloca"), "{text}");
9112 assert!(!text.contains("load"), "{text}");
9113 assert!(!text.contains("store"), "{text}");
9114 }
9115
9116 #[test]
9117 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
9118 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
9119 let expected = "\
9120block0:
9121 %0 = alloca, size 4, align 4
9122 %1 = iconst.i32 1
9123 store %1 -> %0, align 4, tbaa !1
9124 %2 = call @g(%0) : (ptr) -> i32
9125 return %2
9126";
9127 assert_eq!(text, expected);
9128 }
9129
9130 #[test]
9131 fn a_loop_carries_what_it_changes_as_block_parameters() {
9132 let text = body(
9135 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
9136 return total;\n}\n",
9137 );
9138 assert!(!text.contains("alloca"), "{text}");
9139 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
9140 assert!(text.contains("jump block1("), "{text}");
9141 }
9142
9143 #[test]
9144 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
9145 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
9146 assert!(text.contains("icmp slt %0, %1"), "{text}");
9147 assert!(!text.contains("zext"), "{text}");
9148 }
9149
9150 #[test]
9151 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
9152 let text = body("int f(int a, int b) { return a && b; }\n");
9153 let expected = "\
9154block0(%0: i32, %1: i32):
9155 %2 = iconst.i32 0
9156 %3 = icmp ne %0, %2
9157 %4 = iconst.i1 0
9158 br_if %3, block1, block2(%4)
9159
9160block1:
9161 %5 = iconst.i32 0
9162 %6 = icmp ne %1, %5
9163 jump block2(%6)
9164
9165block2(%7: i1):
9166 %8 = zext.i32 %7
9167 return %8
9168";
9169 assert_eq!(text, expected);
9170 }
9171
9172 #[test]
9173 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
9174 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
9175 assert!(!text.contains("block3"), "{text}");
9178 assert!(!text.contains("iconst.i32 3"), "{text}");
9179 }
9180
9181 #[test]
9182 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
9183 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
9184 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
9185 assert!(body("int f(void) { }\n").contains("iconst.i32 0\n return"));
9188 }
9189
9190 #[test]
9191 fn a_structure_is_copied_rather_than_held_in_a_value() {
9192 let text = body(
9193 "struct point { int x, y; };\n\
9194 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
9195 );
9196 assert!(text.contains("memcpy"), "{text}");
9197 }
9198
9199 #[test]
9200 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
9201 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
9202 assert!(text.contains("memset"), "{text}");
9203 }
9204
9205 #[test]
9206 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
9207 let text = body(
9208 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
9209 default: r = 4; } return r; }\n",
9210 );
9211 let expected = "\
9212block0(%0: i32):
9213 %1 = iconst.i32 0
9214 switch %0, block1, [1 => block2, 2 => block3(%1)]
9215
9216block1:
9217 %2 = iconst.i32 4
9218 jump block4(%2)
9219
9220block2:
9221 %3 = iconst.i32 1
9222 jump block3(%3)
9223
9224block3(%4: i32):
9225 %5 = iconst.i32 2
9226 %6 = add.nsw %4, %5
9227 jump block4(%6)
9228
9229block4(%7: i32):
9230 return %7
9231";
9232 assert_eq!(text, expected);
9233 }
9234
9235 #[test]
9236 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
9237 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
9240 assert!(text.contains("%2 = sub %0, %1"), "{text}");
9241 assert!(text.contains("icmp ule"), "{text}");
9242 assert!(!text.contains("switch"), "{text}");
9243 }
9244
9245 #[test]
9246 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
9247 let text = body(
9248 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
9249 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
9250 );
9251 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
9254 assert!(text.contains("block5:\n jump block7("), "{text}");
9255 assert!(text.contains("block6:\n jump block8("), "{text}");
9256 }
9257
9258 #[test]
9259 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
9260 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
9261 }
9262
9263 #[test]
9264 fn a_label_a_loop_is_only_entered_through_builds_the_loop_around_it() {
9265 let text = body(
9270 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
9271 return n; }\n",
9272 );
9273 assert!(text.contains("switch %0, block1(%1), [1 => block2, 2 => block3(%1)]"), "{text}");
9276 assert!(text.contains("block3(%3: i32):\n %4 = iconst.i32 1"), "{text}");
9277 assert!(text.contains("block4:\n jump block3("), "{text}");
9278 }
9279
9280 #[test]
9281 fn a_goto_into_a_loop_body_enters_it_without_the_test() {
9282 let text = body("int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n");
9285 assert!(text.starts_with("block0(%0: i32, %1: i32):\n jump block1(%1)"), "{text}");
9286 assert!(text.contains("block1(%2: i32):\n %3 = iconst.i32 1"), "{text}");
9287 assert!(text.contains("br_if %6, block2, block3"), "{text}");
9288 }
9289
9290 #[test]
9291 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
9292 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
9293 assert!(!text.contains("alloca"), "{text}");
9297 assert!(text.contains("block2(%4: i32):\n return %4"), "{text}");
9298 assert_eq!(text.matches("jump block2(").count(), 2, "{text}");
9299 }
9300
9301 #[test]
9302 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
9303 let text =
9304 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
9305 assert!(!text.contains("alloca"), "{text}");
9306 assert!(text.contains("block1(%2: i32):"), "{text}");
9307 assert!(text.contains("jump block1(%5)"), "{text}");
9308 }
9309
9310 #[test]
9311 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
9312 assert_eq!(
9315 body("int f(int x) { return x; spare: return 0; }\n"),
9316 "block0(%0: i32):\n return %0\n"
9317 );
9318 }
9319
9320 #[test]
9321 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
9322 let text = body(
9323 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
9324 );
9325 assert_eq!(
9328 text,
9329 "\
9330block0(%0: ptr):
9331 %1 = load.i8 %0, align 1
9332 %2 = iconst.i8 3
9333 %3 = ashr %1, %2
9334 %4 = sext.i32 %3
9335 return %4
9336"
9337 );
9338 }
9339
9340 #[test]
9341 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
9342 let text =
9346 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
9347 assert_eq!(
9348 text,
9349 "\
9350block0(%0: ptr, %1: i32):
9351 %2 = iconst.i32 16777215
9352 %3 = and %1, %2
9353 %4 = trunc.i16 %3
9354 store %4 -> %0, align 2
9355 %5 = iconst.i32 16
9356 %6 = lshr %3, %5
9357 %7 = trunc.i8 %6
9358 %8 = iconst.i64 2
9359 %9 = ptr_add %0, %8
9360 store %7 -> %9, align 1
9361 return
9362"
9363 );
9364 }
9365
9366 #[test]
9367 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
9368 let text =
9369 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
9370 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
9373 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
9374 }
9375
9376 #[test]
9377 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
9378 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
9381 assert_eq!(text.matches("ashr").count(), 0, "{text}");
9382 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
9383 }
9384
9385 #[test]
9386 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
9387 let text = body(
9391 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
9392 );
9393 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
9394 }
9395
9396 #[test]
9397 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
9398 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
9401 assert!(
9402 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
9403 "{text}"
9404 );
9405 }
9406
9407 #[test]
9408 fn an_initialized_flexible_array_member_makes_the_object_larger_than_its_type() {
9409 let text = ir(concat!(
9414 "struct a { int i; int j[]; } x = { 1, { 2, 0, 2, 3 } };\n",
9415 "struct b { char c; char p[]; } y = { 'o', \"wx\" };\n",
9416 "struct c { char c; char p[]; } z = { '9', { 'e', 'b' } };\n",
9417 "char s[2] = \"hi\";\n",
9418 ));
9419 assert!(
9420 text.contains("global @x : bytes 20 = { i32 1, i32 2, i32 0, i32 2, i32 3 }"),
9421 "{text}"
9422 );
9423 assert!(text.contains("global @y : bytes 4 = { i8 111, bytes \"wx\\00\" }"), "{text}");
9424 assert!(text.contains("global @z : bytes 3 = { i8 57, i8 101, i8 98 }"), "{text}");
9425 assert!(text.contains("global @s : bytes 2 = { bytes \"hi\" }"), "{text}");
9428 }
9429
9430 #[test]
9431 fn a_definition_takes_a_parameter_it_left_unnamed() {
9432 let text = ir("int f(int a, int) { return a; }\n");
9436 assert!(text.contains("func @f(i32, i32) -> i32"), "{text}");
9437 assert!(text.contains("block0(%0: i32, %1: i32):"), "{text}");
9438
9439 let text = ir("int g(int, int n) { return n; }\n");
9442 assert!(text.contains("block0(%0: i32, %1: i32):\n return %1\n"), "{text}");
9443 }
9444
9445 #[test]
9446 fn an_assignment_of_a_structure_is_the_object_it_wrote() {
9447 let text = body(concat!(
9452 "struct s { int f; int g; };\n",
9453 "void h(struct s *a, struct s *c, struct s *d, struct s *e)\n",
9454 "{ *d = *e = a[0] = *c; }\n",
9455 ));
9456 assert_eq!(text.matches("memcpy").count(), 3, "{text}");
9457 assert!(text.contains("memcpy %8, %1, size 8, align 4\n"), "{text}");
9458 assert!(text.contains("memcpy %3, %8, size 8, align 4\n"), "{text}");
9459 assert!(text.contains("memcpy %2, %3, size 8, align 4\n"), "{text}");
9460 }
9461
9462 #[test]
9463 fn a_string_literal_stops_at_the_end_of_the_array_it_is_filling() {
9464 let mut opts = options();
9469 opts.emit = EmitKind::Ir;
9470 let result = run(
9471 &opts,
9472 concat!(
9473 "const char a[2][3] = { \"1234\", \"xyz\" };\n",
9474 "static const char b[3][5] = { \"12345\", \"678\", \"9\" };\n",
9475 "union u { struct { char x[4]; char y[4]; }; struct { char z[8]; }; };\n",
9476 "const union u c = { { \"1234\", \"567\" } };\n",
9477 ),
9478 );
9479 let text = result.text();
9480 assert_eq!(
9481 result.messages,
9482 ["/main.c:1:24: warning: initializer-string for array of 'const char' is too long \
9483 (5 chars into 3 available) [E0637]"]
9484 );
9485 assert!(text.contains("global @a : bytes 6 = { bytes \"123\", bytes \"xyz\" }"), "{text}");
9486 assert!(
9487 text.contains(
9488 "global @b : bytes 15 = { bytes \"12345\", bytes \"678\\00\", zero 1, \
9489 bytes \"9\\00\", zero 3 }"
9490 ),
9491 "{text}"
9492 );
9493 assert!(
9496 text.contains("global @c : bytes 8 = { bytes \"1234\", bytes \"567\\00\" }"),
9497 "{text}"
9498 );
9499 }
9500
9501 #[test]
9502 fn a_cast_of_a_record_to_its_own_type_is_the_object_that_was_cast() {
9503 let text = body(concat!(
9507 "struct s { int a, b; };\nstruct v { struct s s; int t; };\n",
9508 "void g(struct v *);\n",
9509 "void f(struct s *p) { struct v w = { (struct s)*p, 5 }; g(&w); }\n",
9510 ));
9511 assert_eq!(text.matches("memcpy").count(), 1, "{text}");
9512 }
9513
9514 #[test]
9515 fn a_compound_literal_read_in_a_static_initializer_lays_its_bytes_into_the_image() {
9516 let text = ir(concat!(
9521 "struct s { int x; };\n",
9522 "struct t { struct s s; int o; } a = { (struct s){ 2 }, 3 };\n",
9523 "int n = (int){ 7 };\n",
9524 "struct u { struct s p; struct s q; } b = { (struct s){ 1 }, (struct s){ } };\n",
9525 ));
9526 assert!(text.contains("global @a : bytes 8 = { i32 2, i32 3 }"), "{text}");
9527 assert!(text.contains("global @n : i32 = 7,"), "{text}");
9528 assert!(text.contains("global @b : bytes 8 = { i32 1, zero 4 }"), "{text}");
9531 }
9532
9533 #[test]
9534 fn the_address_of_a_compound_literal_asks_for_the_object_it_points_at() {
9535 let text = ir("struct s { int x; };\nstruct s *q = &(struct s){ 9 };\n");
9539 assert!(text.contains("global @.Lanon.0 : i32 = 9, align 4, linkage(internal)"), "{text}");
9540 assert!(text.contains("global @q : bytes 8 = { addr.8 @.Lanon.0 }"), "{text}");
9541 }
9542
9543 #[test]
9544 fn an_object_of_no_size_at_all_has_an_image_with_nothing_in_it() {
9545 let text = ir("unsigned char foo[1][0];\n");
9549 assert!(text.contains("global @foo : bytes 0 = {}, align 1"), "{text}");
9550 }
9551
9552 #[test]
9553 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
9554 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
9557 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
9558 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
9559 }
9560
9561 #[test]
9562 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
9563 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
9567 assert!(
9568 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
9569 "{text}"
9570 );
9571 }
9572
9573 #[test]
9574 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
9575 let text = body(
9580 "\
9581struct s { int a, b; };
9582struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
9583",
9584 );
9585 assert!(text.contains("block3(%7: ptr)"), "{text}");
9587 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
9588 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
9589 }
9590
9591 #[test]
9599 fn the_left_side_of_a_conditional_with_no_middle_is_evaluated_once() {
9600 let text = body("int f(int i) { return ++i ?: 10; }\n");
9601 assert!(text.contains("jump block3(%2)"), "the arm is the value that was tested: {text}");
9602 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
9603
9604 let text = body("long f(int i) { return ++i ?: 10L; }\n");
9607 assert!(text.contains("%5 = sext.i64 %2"), "the arm widens what was tested: {text}");
9608 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
9609
9610 let text = body("int g(void);\nint f(void) { return g() ?: 10; }\n");
9612 assert_eq!(text.matches("call @g").count(), 1, "called once: {text}");
9613
9614 let text = body("int f(int i) { return ++i ? ++i : 10; }\n");
9617 assert_eq!(text.matches("add.nsw").count(), 2, "incremented twice: {text}");
9618 }
9619
9620 #[test]
9621 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
9622 let text = ir("\
9626struct pair { int a, b; };
9627struct pair make(int a, int b);
9628struct pair twice(struct pair p) { return make(p.a, p.b); }
9629");
9630 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
9631 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
9632 }
9633
9634 #[test]
9635 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
9636 let text = ir("\
9640struct big { double v[8]; };
9641struct big grow(struct big b);
9642struct big twice(struct big b) { return grow(grow(b)); }
9643");
9644 assert!(
9645 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
9646 "{text}"
9647 );
9648 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
9649 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
9652 }
9653
9654 #[test]
9655 fn a_structure_passed_to_a_variadic_function_says_so_at_the_call() {
9656 let text = ir("\
9661struct big { double v[8]; };
9662struct pair { int a, b; };
9663int p(const char *, ...);
9664int f(struct big b, struct pair q) { return p(\"\", 1, b, q); }
9665");
9666 assert!(
9667 text.contains("call @p(%4, %5, %2 byval(64, align 8), %6) : (ptr, ...) -> i32"),
9668 "{text}"
9669 );
9670 }
9671
9672 #[test]
9673 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
9674 let body = body(
9677 "\
9678struct pair { int a, b; };
9679struct pair make(int a, int b);
9680int second(void) { return make(1, 2).b; }
9681",
9682 );
9683 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
9684 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
9685 }
9686
9687 #[test]
9688 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
9689 let source = "\
9693struct hfa { float x, y, z; };
9694int take(struct hfa h);
9695int give(struct hfa h) { return take(h); }
9696";
9697 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
9698 let mut opts = options();
9699 opts.emit = EmitKind::Ir;
9700 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
9701 let result = run(&opts, source);
9702 assert_eq!(result.messages, Vec::<String>::new());
9703 assert!(result.text().contains("func @take(f32, f32, f32) -> i32"), "{}", result.text());
9704 }
9705
9706 #[test]
9707 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
9708 let source = "\
9711int use(int *);
9712void f(int n) {
9713 {
9714 int a[n];
9715 use(a);
9716 }
9717 use(0);
9718}
9719";
9720 let body = body(source);
9721 assert!(body.contains("mul.nsw"), "{body}");
9722 assert!(body.contains("stacksave"), "{body}");
9723 assert!(body.contains("alloca %"), "{body}");
9724 assert!(body.contains("stackrestore"), "{body}");
9725 }
9726
9727 #[test]
9728 fn a_goto_out_of_the_scope_of_one_gives_its_stack_back_on_the_way() {
9729 let source = "\
9734int use(int *);
9735int f(int n) {
9736 {
9737 int a[n];
9738 if (use(a)) goto out;
9739 use(0);
9740 }
9741out:
9742 return 0;
9743}
9744";
9745 let body = body(source);
9746 assert_eq!(body.matches("stackrestore").count(), 2, "{body}");
9748 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
9749 assert!(after.starts_with(" %4\n jump block"), "{body}");
9750 }
9751
9752 #[test]
9753 fn a_goto_to_a_label_the_array_is_still_alive_at_leaves_the_stack_alone() {
9754 let source = "\
9758int use(int *);
9759int f(int n) {
9760 int a[n];
9761again:
9762 if (use(a)) goto again;
9763 return 0;
9764}
9765";
9766 let body = body(source);
9767 assert!(body.contains("stacksave"), "{body}");
9768 assert!(!body.contains("stackrestore"), "{body}");
9769 }
9770
9771 #[test]
9772 fn a_goto_back_to_a_label_in_front_of_one_gives_it_back_every_time_round() {
9773 let source = "\
9778int use(int *);
9779int f(int n) {
9780again:
9781 {
9782 int a[n];
9783 if (use(a)) goto again;
9784 }
9785 return 0;
9786}
9787";
9788 let body = body(source);
9789 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
9790 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
9791 assert!(after.starts_with(" %4\n jump block1\n"), "{body}");
9792 }
9793
9794 #[test]
9795 fn the_head_of_a_for_loop_is_a_scope_that_closes_where_the_loop_is_left() {
9796 let source = "\
9802int f(void);
9803void t(void) {
9804 int count = 10;
9805 for (; count--;) {
9806 int b[f()];
9807 int i;
9808 for (i = 0; i < f(); i++) {
9809 b[i] = count;
9810 }
9811 }
9812}
9813";
9814 let body = body(source);
9815 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
9819 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
9820 let next = after.split("\n\n").next().expect("the block the restore is in");
9823 assert!(next.contains("jump block1("), "{body}");
9824 }
9825
9826 #[test]
9827 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
9828 let source = "\
9831unsigned long f(int n) {
9832 int a[n];
9833 n = 0;
9834 return sizeof a;
9835}
9836";
9837 let body = body(source);
9838 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
9840 }
9841
9842 #[test]
9843 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
9844 let source = "\
9847int use(int);
9848int f(int x) {
9849 return ({
9850 int t = use(x);
9851 t * t;
9852 });
9853}
9854";
9855 let expected = "\
9856block0(%0: i32):
9857 %1 = call @use(%0) : (i32) -> i32
9858 %2 = mul.nsw %1, %1
9859 return %2
9860";
9861 assert_eq!(body(source), expected);
9862 }
9863
9864 #[test]
9865 fn a_comma_whose_value_is_an_object_names_the_object_the_right_side_named() {
9866 let source = "\
9870struct pair { int a, b; };
9871void bail(void);
9872int f(struct pair p) {
9873 return (bail(), p).b;
9874}
9875";
9876 let expected = "\
9877block0(%0: i64):
9878 %1 = alloca, size 8, align 4
9879 store %0 -> %1, align 4
9880 call @bail() : ()
9881 %2 = iconst.i64 4
9882 %3 = ptr_add %1, %2
9883 %4 = load.i32 %3, align 4, tbaa !1
9884 return %4
9885";
9886 assert_eq!(body(source), expected);
9887 }
9888
9889 #[test]
9890 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
9891 let source = "int f(int x) { return ({ return x; 0; }); }\n";
9895 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
9896 }
9897
9898 #[test]
9899 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
9900 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
9904 let expected = "\
9905block0(%0: ptr):
9906 %1 = va_arg.f64 %0
9907 %2 = va_arg.f64 %0
9908 %3 = fadd %1, %2
9909 return %3
9910";
9911 assert_eq!(body(source), expected);
9912 }
9913
9914 #[test]
9915 fn one_that_reads_a_structure_answers_where_the_object_is() {
9916 let source = "\
9930struct s { int a; long b; };
9931long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.b; }
9932";
9933 let expected = "\
9934block0(%0: ptr):
9935 %1 = alloca, size 16, align 16
9936 %2 = va_object %0, size 16, align 8, in(int 8 at 0, int 8 at 8)
9937 memcpy %1, %2, size 16, align 8
9938 %3 = iconst.i64 8
9939 %4 = ptr_add %1, %3
9940 %5 = load.i64 %4, align 8, tbaa !1
9941 return %5
9942";
9943 assert_eq!(body(source), expected);
9944 }
9945
9946 #[test]
9950 fn the_classification_says_which_registers_the_object_arrived_in() {
9951 let source = "\
9952struct s { double a; double b; };
9953double f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a; }
9954";
9955 assert!(
9956 body(source)
9957 .contains("va_object %0, size 16, align 8, in(float f64 at 0, float f64 at 8)"),
9958 "{}",
9959 body(source)
9960 );
9961
9962 let big = "\
9963struct s { long a[4]; };
9964long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a[0]; }
9965";
9966 assert!(body(big).contains("va_object %0, size 32, align 8\n"), "{}", body(big));
9967 }
9968
9969 #[test]
9970 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
9971 let source = "\
9975int f(int c) {
9976 void *p = c ? &&one : &&two;
9977 goto *p;
9978one:
9979 return 1;
9980two:
9981 return 2;
9982}
9983";
9984 let expected = "\
9985block0(%0: i32):
9986 %1 = iconst.i32 0
9987 %2 = icmp ne %0, %1
9988 br_if %2, block1, block2
9989
9990block1:
9991 %3 = block_addr block3
9992 jump block4(%3)
9993
9994block2:
9995 %4 = block_addr block5
9996 jump block4(%4)
9997
9998block3:
9999 %5 = iconst.i32 1
10000 return %5
10001
10002block4(%6: ptr):
10003 indirect_br %6, block3, block5
10004
10005block5:
10006 %7 = iconst.i32 2
10007 return %7
10008";
10009 assert_eq!(body(source), expected);
10010 }
10011
10012 fn dispatch(labels: usize) -> String {
10015 let mask = labels - 1;
10016 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
10017 for index in 0..labels {
10018 source.push_str(&format!(" &&a{index},"));
10019 }
10020 source.push_str(" };\n\tint w = n, x = n + 1, y = n + 2, z = n + 3;\n");
10021 source.push_str(&format!("\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
10022 for index in 0..labels {
10023 let step = match index % 4 {
10024 0 => "w += x;",
10025 1 => "x += y;",
10026 2 => "y += z;",
10027 _ => "z += w;",
10028 };
10029 source.push_str(&format!("a{index}:\n\t{step}\n"));
10030 source.push_str("\tif (--n <= 0) return w + x + y + z;\n");
10031 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
10032 }
10033 source.push_str("}\n");
10034 source
10035 }
10036
10037 fn in_front_of_the_jump(text: &str) -> usize {
10039 let (before, _) = text.split_once("\tjmp\t*%").expect("a jump through a register");
10040 before.lines().rev().take_while(|line| line.starts_with("\tmov")).count()
10041 }
10042
10043 #[test]
10053 fn a_jump_through_a_register_writes_what_it_carries_and_not_the_whole_table() {
10054 let small = in_front_of_the_jump(&asm(&dispatch(4)));
10055 let large = in_front_of_the_jump(&asm(&dispatch(32)));
10056 assert_eq!(small, large, "eight times the labels and the same values in hand");
10057 assert!(large <= 8, "the values the loop keeps, and not a set of them per label: {large}");
10058 }
10059
10060 fn crowded(labels: usize) -> String {
10063 const VALUES: usize = 24;
10064 let mask = labels - 1;
10065 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
10066 for index in 0..labels {
10067 source.push_str(&format!(" &&a{index},"));
10068 }
10069 source.push_str(" };\n\t");
10070 for value in 0..VALUES {
10071 source.push_str(&format!("int v{value} = n + {value}; "));
10072 }
10073 let sum: Vec<String> = (0..VALUES).map(|value| format!("v{value}")).collect();
10074 source.push_str(&format!("\n\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
10075 for index in 0..labels {
10076 let (to, from) = (index % VALUES, (index + 1) % VALUES);
10077 source.push_str(&format!("a{index}:\n\tv{to} += v{from};\n"));
10078 source.push_str(&format!("\tif (--n <= 0) return {};\n", sum.join(" + ")));
10079 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
10080 }
10081 source.push_str("}\n");
10082 source
10083 }
10084
10085 fn the_frame(text: &str) -> u64 {
10087 text.lines()
10088 .find_map(|line| {
10089 let (size, _) = line.strip_prefix("\tsubq\t$")?.split_once(", %rsp")?;
10090 size.parse().ok()
10091 })
10092 .expect("a function that opens a frame")
10093 }
10094
10095 #[test]
10104 fn a_frame_holds_what_is_wanted_at_once_and_not_a_slot_for_every_label() {
10105 let small = the_frame(&asm(&crowded(16)));
10106 let large = the_frame(&asm(&crowded(64)));
10107 assert_eq!(small, large, "four times the labels and the same values: {small}, {large}");
10108 }
10109
10110 #[test]
10119 fn a_template_that_names_its_own_registers_gets_the_ones_it_named() {
10120 let source = "void save(void *nlr) {
10121 __asm volatile (
10122 \"movq %%rsp, 32(%%rdi) \\n\"
10123 \"movq %%rbx, 40(%%rdi) \\n\"
10124 \"movq %%r12, 48(%%rdi) \\n\"
10125 : : \"D\" (nlr) : \"memory\");
10126}
10127";
10128 let text = asm(source);
10129 assert!(text.contains("\tmovq\t%rsp, 32(%rdi)\n"), "{text}");
10130 assert!(text.contains("\tmovq\t%rbx, 40(%rdi)\n"), "{text}");
10131 assert!(text.contains("\tmovq\t%r12, 48(%rdi)\n"), "{text}");
10132 }
10133
10134 #[test]
10135 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
10136 let source = "void **next(void);
10139void f(void) { goto *next(); }
10140";
10141 let expected = "\
10142block0:
10143 %0 = call @next() : () -> ptr
10144 unreachable
10145";
10146 assert_eq!(body(source), expected);
10147 }
10148
10149 #[test]
10150 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
10151 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
10154 let expected = "\
10155block0:
10156 inline_asm.volatile \"mfence\", \"\", \"memory\"()
10157 return
10158";
10159 assert_eq!(body(source), expected);
10160 }
10161
10162 #[test]
10163 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
10164 let source = "\
10167int f(int x, int y) {
10168 int r;
10169 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
10170 return r + y;
10171}
10172";
10173 let expected = "\
10174block0(%0: i32, %1: i32):
10175 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
10176 %4 = add.nsw %2, %3
10177 return %4
10178";
10179 assert_eq!(body(source), expected);
10180 }
10181
10182 #[test]
10183 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
10184 let source = "\
10189struct pair { int a, b; };
10190int f(int x) {
10191 int slot = x;
10192 struct pair p = { x, x };
10193 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
10194 return slot + p.a;
10195}
10196";
10197 let text = body(source);
10198 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
10199 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
10200 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
10201 }
10202
10203 #[test]
10204 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
10205 let source = "\
10210int f(int x) {
10211 int r = 7;
10212 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
10213 return r;
10214away:
10215 return r;
10216}
10217";
10218 let expected = "\
10219block0(%0: i32):
10220 %1 = iconst.i32 7
10221 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
10222
10223block1:
10224 return %2
10225
10226block2:
10227 return %1
10228";
10229 assert_eq!(body(source), expected);
10230 }
10231
10232 #[test]
10233 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
10234 let mut opts = options();
10238 opts.emit = EmitKind::Ir;
10239 for (source, expected) in [
10240 (
10241 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
10242 "output operand constraint lacks '='",
10243 ),
10244 (
10245 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
10246 "lvalue required in 'asm' statement",
10247 ),
10248 (
10249 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
10250 "read-only variable 'g' used as 'asm' output",
10251 ),
10252 (
10253 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
10254 "input operand constraint contains '='",
10255 ),
10256 (
10257 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
10258 "memory input 0 is not directly addressable",
10259 ),
10260 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
10261 (
10262 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
10263 "duplicate asm operand name 'a'",
10264 ),
10265 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
10266 ] {
10267 let result = run(&opts, source);
10268 assert!(result.failed(), "expected this to be reported:\n{source}");
10269 assert!(
10270 result.messages.iter().any(|m| m.contains(expected)),
10271 "{expected}\n{:?}",
10272 result.messages
10273 );
10274 }
10275 }
10276
10277 #[test]
10282 fn an_asm_at_file_scope_that_is_directives_becomes_the_objects_it_defines() {
10283 let text = ir(concat!(
10284 "__asm__(\n",
10285 " \".section .rodata\\n\"\n",
10286 " \".globl first\\n\"\n",
10287 " \".balign 8\\n\"\n",
10288 " \"first:\\n\"\n",
10289 " \".long 1\\n\"\n",
10290 " \".long 2\\n\"\n",
10291 " \".globl last\\n\"\n",
10292 " \"last:\\n\"\n",
10293 " \".quad last - first\\n\");\n",
10294 "extern const int first[];\n",
10295 "extern const long last;\n",
10296 ));
10297 assert!(text.contains("global @first : bytes 8 = { i32 1, i32 2 }, align 8"), "{text}");
10298 assert!(text.contains("global @last : i64 = 8"), "{text}");
10299 }
10300
10301 #[test]
10305 fn a_name_an_asm_at_file_scope_defined_is_not_undone_by_a_declaration_of_it() {
10306 let text = ir(concat!(
10307 "__asm__(\".data\\n.globl counter\\ncounter:\\n.long 7\\n\");\n",
10308 "extern int counter;\n",
10309 "int read(void) { return counter; }\n",
10310 ));
10311 assert!(text.contains("global @counter : i32 = 7"), "{text}");
10312 }
10313
10314 #[test]
10319 fn bytes_under_no_label_at_file_scope_are_a_global_in_front_of_the_label() {
10320 let text = ir(concat!(
10321 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n662:\\n",
10322 ".pushsection .data.ignore\\n.byte 7\\n.popsection\\n.byte 662b - 661b\\n\");\n",
10323 "extern unsigned char stuff[];\n",
10324 "int read(void) { return stuff[0]; }\n",
10325 ));
10326 let under = text.find("global @.Lasm.0 : i8 = 41").expect(&text);
10327 let named = text.find("global @stuff : i8 = 42").expect(&text);
10328 assert!(under < named, "the bytes under no label come first: {text}");
10329 assert!(text.contains("global @.Lasm.1 : i8 = 7, align 1, linkage(internal), section"));
10330 let after = text.find("global @.Lasm.2 : i8 = 1").expect(&text);
10335 assert!(named < after, "{text}");
10336 }
10337
10338 #[test]
10343 fn a_distance_from_here_at_file_scope_is_a_hole_naming_the_global_it_measures_to() {
10344 let text = ir(concat!(
10345 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n",
10346 ".pushsection .data.ignore\\n.long 661b - .\\n.popsection\\n\");\n",
10347 "extern unsigned char stuff[];\n",
10348 "int read(void) { return stuff[0]; }\n",
10349 ));
10350 assert!(text.contains("global @.Lasm.1 : bytes 4 = { away.4 @stuff }"), "{text}");
10354 }
10355
10356 #[test]
10361 fn a_set_at_file_scope_is_a_second_name_for_what_it_names() {
10362 let text = ir(concat!(
10363 "void base(void) {}\n",
10364 "__asm__(\".weak one\\n.set one, base\");\n",
10365 "__asm__(\".globl two\\n.set two, base\");\n",
10366 "__asm__(\".set three, base\");\n",
10367 "void three(void) {}\n",
10368 ));
10369 assert!(text.contains("alias @one = @base, linkage(weak)"), "{text}");
10370 assert!(text.contains("alias @two = @base"), "{text}");
10371 assert!(!text.contains("alias @three"), "a definition of the name wins: {text}");
10372 assert!(text.contains("func @three"), "{text}");
10373 }
10374
10375 #[test]
10380 fn a_set_of_a_name_this_file_does_not_define_says_so() {
10381 let messages = errors("__asm__(\".set here, elsewhere\");\n");
10382 assert!(
10383 messages
10384 .iter()
10385 .any(|m| m.contains("'here' is aliased to undefined symbol 'elsewhere'")
10386 && m.contains("E0697")),
10387 "{messages:?}"
10388 );
10389 }
10390
10391 #[test]
10394 fn an_incbin_at_file_scope_is_the_bytes_of_the_file_it_names() {
10395 let mut opts = options();
10396 opts.emit = EmitKind::Ir;
10397 let mut fs = MemoryFileSystem::new();
10398 fs.insert(
10399 "/main.c",
10400 b"__asm__(\".data\\n.globl blob\\nblob:\\n.incbin \\\"seed\\\"\\n\");\n".to_vec(),
10401 );
10402 fs.insert("seed", b"hi".to_vec());
10403 let result = compile(&opts, "/main.c", &fs);
10404 assert_eq!(result.messages, Vec::<String>::new());
10405 let text = result.text();
10406 assert!(text.contains("global @blob : bytes 2 = { bytes \"hi\" }"), "{text}");
10407 }
10408
10409 #[test]
10412 fn an_incbin_naming_a_file_that_is_not_there_says_which_file() {
10413 let messages = errors("__asm__(\".data\\nb:\\n.incbin \\\"nowhere\\\"\\n\");\n");
10414 assert!(
10415 messages
10416 .iter()
10417 .any(|m| m.contains("cannot open 'nowhere' for reading") && m.contains("E0702")),
10418 "{messages:?}"
10419 );
10420 }
10421
10422 #[test]
10426 fn a_directive_in_an_asm_at_file_scope_is_refused_rather_than_ignored() {
10427 let source = "__asm__(\".data\\n.set alias, 4\\n\");\n";
10428 let messages = errors(source);
10429 assert!(
10430 messages
10431 .iter()
10432 .any(|m| m.contains("not supported yet") && m.contains("in an `asm` at file scope")),
10433 "{source}\n{messages:?}"
10434 );
10435 }
10436
10437 #[test]
10447 fn a_naked_function_is_its_own_prologue_and_its_own_ending() {
10448 let text = asm(concat!(
10449 "unsigned nlr_push_tail(void *nlr);\n",
10450 "__attribute__((naked)) unsigned nlr_push(void *nlr) {\n",
10451 " __asm volatile(\n",
10452 " \"movq (%rsp), %rax\\n\"\n",
10453 " \"movq %rax, 16(%rdi)\\n\"\n",
10454 " \"movq %rbx, 40(%rdi)\\n\"\n",
10455 " \"jmp nlr_push_tail\\n\");\n",
10456 "}\n",
10457 ));
10458 assert!(text.contains("\tmovq\t(%rsp), %rax\n"), "{text}");
10459 assert!(text.contains("\tjmp\tnlr_push_tail\n"), "{text}");
10460 assert!(text.contains("\tud2\n"), "{text}");
10461 assert!(!text.contains("\tpushq\t"), "nothing is saved in front of it: {text}");
10462 assert!(!text.contains("\tret\n"), "the jump is where it ends: {text}");
10463 }
10464
10465 #[test]
10468 fn what_a_function_without_a_prologue_cannot_be_given_is_refused() {
10469 let mut opts = options();
10470 opts.emit = EmitKind::Asm;
10471 for (source, why) in [
10472 (
10473 "__attribute__((naked)) void f(void) { volatile long a[8]; a[0] = 1; }\n",
10474 "bytes of frame",
10475 ),
10476 (
10477 "__attribute__((naked)) void f(int n) { char a[n]; __asm(\"nop\" ::\"r\"(a)); }\n",
10478 "has no prologue to point a frame pointer at it with",
10479 ),
10480 ("void elsewhere(void); void f(void) { __asm(\"jmp elsewhere\"); }\n", "jumps out of"),
10481 ] {
10482 let result = run(&opts, source);
10483 assert!(result.failed(), "expected this to be refused:\n{source}");
10484 assert!(
10485 result.messages.iter().any(|message| message.contains(why)),
10486 "{:?}",
10487 result.messages
10488 );
10489 }
10490 }
10491
10492 #[test]
10493 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
10494 let mut opts = options();
10495 opts.emit = EmitKind::Ir;
10496 for source in [
10497 "int f(int n) { void *p = &&out; if (n) goto *p; { int a[n]; out: return 1; } }\n",
10498 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
10499 ] {
10500 let result = run(&opts, source);
10501 assert!(result.failed(), "expected this to be reported:\n{source}");
10502 assert!(
10503 result.messages.iter().any(|m| m.contains("not supported yet")),
10504 "{:?}",
10505 result.messages
10506 );
10507 }
10508 }
10509
10510 fn round_trip(source: &str) -> (String, String) {
10512 let printed = ir(source);
10513 let mut opts = options();
10514 opts.emit = EmitKind::Ir;
10515 let mut fs = MemoryFileSystem::new();
10516 fs.insert("/main.ir", printed.clone().into_bytes());
10517 let result = compile_ir(&opts, "/main.ir", &fs);
10518 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
10519 (printed, result.text().to_owned())
10520 }
10521
10522 #[test]
10523 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
10524 let (printed, again) = round_trip(
10528 "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",
10529 );
10530 assert_eq!(printed, again);
10531 }
10532
10533 #[test]
10534 fn ir_that_is_not_ir_says_which_line_stopped_it() {
10535 let mut opts = options();
10536 opts.emit = EmitKind::Ir;
10537 let mut fs = MemoryFileSystem::new();
10538 let text = "\
10539; ModuleID = 'a.c'
10540; format 0
10541target triple = \"x86_64-unknown-linux-gnu\"
10542target datalayout = \"e-p:64:64-i64:64-S128\"
10543
10544func @f(), linkage(external) {
10545block0:
10546 frobnicate
10547}
10548";
10549 fs.insert("/main.ir", text.as_bytes().to_vec());
10550 let result = compile_ir(&opts, "/main.ir", &fs);
10551 assert!(result.failed());
10552 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
10553 }
10554
10555 #[test]
10556 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
10557 let mut opts = options();
10560 opts.emit = EmitKind::Ir;
10561 let mut fs = MemoryFileSystem::new();
10562 let text = "\
10563; ModuleID = 'a.c'
10564; format 0
10565target triple = \"x86_64-unknown-linux-gnu\"
10566target datalayout = \"e-p:64:64-i64:64-S128\"
10567
10568func @f(), linkage(external) {
10569block0:
10570 %0 = iconst.i32 1
10571 return %0
10572}
10573";
10574 fs.insert("/main.ir", text.as_bytes().to_vec());
10575 let result = compile_ir(&opts, "/main.ir", &fs);
10576 assert!(result.failed());
10577 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
10578 }
10579
10580 #[test]
10581 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
10582 let mut fs = MemoryFileSystem::new();
10584 fs.insert("/main.ir", Vec::new());
10585 let result = compile_ir(&options(), "/main.ir", &fs);
10586 assert!(result.failed());
10587 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
10588 }
10589
10590 #[test]
10591 fn the_printed_ir_reads_back_as_the_same_module() {
10592 let text = ir("\
10595struct point { int x, y; };
10596static const char greeting[] = \"hi\";
10597int table[4] = { 1, 2, 3 };
10598int puts(const char *);
10599double half(double x) { return x / 2.0; }
10600int f(int n) {
10601 int total = 0;
10602 for (int i = 0; i < n; i++) {
10603 if (i == 3) continue;
10604 total += table[i];
10605 }
10606 switch (n) {
10607 case 0: total = 1;
10608 case 1: total++; break;
10609 default: total = -total;
10610 }
10611 struct point p = { total, 1 };
10612 int *q = &p.y;
10613 puts(greeting);
10614 return p.x + *q;
10615}
10616int dispatch(int c) {
10617 void *p = c ? &&one : &&two;
10618 goto *p;
10619one:
10620 return 1;
10621two:
10622 return 2;
10623}
10624int assembly(int x, int *p) {
10625 int r;
10626 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
10627 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
10628 return r;
10629away:
10630 return 0;
10631}
10632");
10633 let mut names = Interner::new();
10634 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
10635 assert_eq!(rucc_ir::print(&module, &names), text);
10636 }
10637
10638 #[test]
10639 fn what_save_temps_keeps_is_the_text_that_was_compiled_and_the_assembly_that_was_assembled() {
10640 let mut opts = options();
10644 opts.emit = EmitKind::Object;
10645 opts.save_temps = rucc_session::SaveTemps::Object;
10646 let result = run(&opts, "#define N 2\nint a[N];\n");
10647 assert_eq!(result.messages, Vec::<String>::new());
10648 let text = result.temps.preprocessed.expect("the preprocessed text");
10649 assert!(text.contains("int a[2];"), "{text}");
10650 assert!(text.starts_with("# 1 \"/main.c\""), "{text}");
10651 let asm = result.temps.assembly.expect("the assembly");
10652 assert!(asm.contains("a:"), "{asm}");
10653 assert!(matches!(result.artifact, Artifact::Object { .. }), "{:?}", result.artifact);
10654 }
10655
10656 #[test]
10657 fn nothing_is_kept_unless_the_flag_asked_for_it() {
10658 let mut opts = options();
10661 opts.emit = EmitKind::Object;
10662 assert_eq!(run(&opts, "int a;\n").temps, Temps::default());
10663 }
10664
10665 #[test]
10666 fn a_compilation_that_stops_before_the_back_end_keeps_the_text_and_no_assembly() {
10667 let mut opts = options();
10670 opts.emit = EmitKind::Ir;
10671 opts.save_temps = rucc_session::SaveTemps::Cwd;
10672 let result = run(&opts, "int a;\n");
10673 assert!(result.temps.preprocessed.is_some());
10674 assert_eq!(result.temps.assembly, None);
10675 }
10676
10677 fn span(from: u64, len: u64, held: rucc_debug::Held) -> rucc_debug::Span {
10679 rucc_debug::Span { from, len, held }
10680 }
10681
10682 #[test]
10683 fn two_stretches_that_meet_and_agree_come_out_as_one() {
10684 let one = span(0, 4, rucc_debug::Held::Reg(3));
10685 let two = span(4, 4, rucc_debug::Held::Reg(3));
10686 assert_eq!(settle(vec![two, one]), vec![span(0, 8, rucc_debug::Held::Reg(3))]);
10687 }
10688
10689 #[test]
10690 fn a_stretch_another_starts_inside_and_disagrees_with_ends_where_the_other_starts() {
10691 let one = span(0, 8, rucc_debug::Held::Reg(3));
10692 let two = span(4, 8, rucc_debug::Held::Reg(4));
10693 let settled = settle(vec![one, two]);
10696 assert_eq!(
10697 settled,
10698 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 8, rucc_debug::Held::Reg(4))]
10699 );
10700 }
10701
10702 #[test]
10703 fn a_stretch_cut_by_one_that_ends_first_does_not_come_back_after_it() {
10704 let one = span(0, 16, rucc_debug::Held::Reg(3));
10707 let two = span(4, 4, rucc_debug::Held::Reg(4));
10708 assert_eq!(
10709 settle(vec![one, two]),
10710 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 4, rucc_debug::Held::Reg(4))]
10711 );
10712 }
10713
10714 #[test]
10715 fn a_stretch_inside_another_that_agrees_with_it_cuts_nothing() {
10716 let one = span(0, 16, rucc_debug::Held::Reg(3));
10717 let two = span(4, 4, rucc_debug::Held::Reg(3));
10718 assert_eq!(settle(vec![one, two]), vec![span(0, 16, rucc_debug::Held::Reg(3))]);
10719 }
10720
10721 #[test]
10722 fn a_stretch_two_others_disagree_over_the_whole_of_says_nothing_at_all() {
10723 let one = span(0, 8, rucc_debug::Held::Reg(3));
10724 let two = span(0, 8, rucc_debug::Held::Frame(-16));
10725 assert_eq!(settle(vec![one, two]), Vec::new());
10726 }
10727
10728 #[test]
10729 fn stretches_with_a_gap_between_them_keep_the_gap() {
10730 let one = span(0, 4, rucc_debug::Held::Reg(3));
10731 let two = span(16, 4, rucc_debug::Held::Reg(3));
10732 assert_eq!(settle(vec![one, two]), vec![one, two]);
10733 }
10734
10735 fn extent(len: usize) -> rucc_object::Extent {
10737 rucc_object::Extent {
10738 name: "f".to_owned(),
10739 start: 0,
10740 len,
10741 align: 1,
10742 binding: rucc_object::Binding::Global,
10743 visibility: rucc_object::Visibility::Default,
10744 patch: None,
10745 landings: Vec::new(),
10746 }
10747 }
10748
10749 fn row(at: usize, lo: u32, hi: u32) -> rucc_asm::Row {
10751 let span = Span::new(lo, hi);
10752 rucc_asm::Row { at, span, inst: None }
10753 }
10754
10755 #[test]
10756 fn a_row_ends_where_the_next_address_begins() {
10757 let rows = [row(0, 0, 1), row(4, 1, 2), row(10, 2, 3)];
10758 assert_eq!(ends(&extent(16), &rows), vec![4, 10, 16]);
10759 }
10760
10761 #[test]
10762 fn rows_sharing_an_address_all_end_where_the_next_address_begins() {
10763 let rows = [row(0, 0, 1), row(4, 1, 2), row(4, 2, 3), row(4, 3, 4)];
10766 assert_eq!(ends(&extent(12), &rows), vec![4, 12, 12, 12]);
10767 }
10768
10769 #[test]
10770 fn the_rows_of_a_scope_that_are_next_to_each_other_come_out_as_one_stretch() {
10771 let rows = [row(0, 0, 4), row(4, 10, 14), row(8, 14, 18), row(12, 40, 44)];
10772 let ends = ends(&extent(16), &rows);
10773 let scope = Span::new(8, 20);
10774 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 8 }]);
10775 }
10776
10777 #[test]
10778 fn a_scope_the_back_end_split_in_two_comes_out_as_two_stretches() {
10779 let rows = [row(0, 10, 14), row(4, 40, 44), row(8, 14, 18)];
10780 let ends = ends(&extent(12), &rows);
10781 let scope = Span::new(8, 20);
10782 let over = spread(scope, &ends, &rows);
10783 assert_eq!(
10784 over,
10785 vec![rucc_debug::Reach { from: 0, len: 4 }, rucc_debug::Reach { from: 8, len: 4 }]
10786 );
10787 }
10788
10789 #[test]
10790 fn a_row_with_no_source_of_its_own_belongs_to_no_scope() {
10791 let rows = [rucc_asm::Row { at: 0, span: Span::DUMMY, inst: None }, row(4, 10, 14)];
10793 let ends = ends(&extent(8), &rows);
10794 let scope = Span::new(0, 20);
10795 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 4 }]);
10796 }
10797
10798 fn scope(parent: Option<usize>, lo: u32, hi: u32) -> crate::shapes::Scope {
10800 let span = Span::new(lo, hi);
10801 crate::shapes::Scope { parent, span }
10802 }
10803
10804 #[test]
10805 fn a_function_gets_the_scopes_its_own_locals_are_in_and_nothing_else() {
10806 let scopes = [scope(None, 0, 10), scope(None, 20, 30), scope(Some(1), 22, 26)];
10808 let rows = [row(0, 22, 24), row(4, 26, 28)];
10809 let (out, at) = nests(&[Some(2)], &scopes, &extent(8), &rows);
10810 assert_eq!(at.get(&1), Some(&0));
10813 assert_eq!(at.get(&2), Some(&1));
10814 assert_eq!(at.get(&0), None);
10815 assert_eq!(out.len(), 2);
10816 assert_eq!(out[0].parent, None);
10817 assert_eq!(out[1].parent, Some(0));
10818 assert_eq!(out[0].over, vec![rucc_debug::Reach { from: 0, len: 8 }]);
10819 assert_eq!(out[1].over, vec![rucc_debug::Reach { from: 0, len: 4 }]);
10820 }
10821
10822 #[test]
10823 fn a_local_written_straight_into_the_body_pulls_no_scope_in() {
10824 let scopes = [scope(None, 20, 30)];
10825 let rows = [row(0, 22, 24)];
10826 let (out, at) = nests(&[None], &scopes, &extent(4), &rows);
10827 assert_eq!(out, Vec::new());
10828 assert!(at.is_empty());
10829 }
10830
10831 #[test]
10832 fn a_scope_whose_code_all_went_away_is_still_one_of_the_functions_scopes() {
10833 let scopes = [scope(None, 20, 30)];
10837 let rows = [row(0, 40, 44)];
10838 let (out, at) = nests(&[Some(0)], &scopes, &extent(4), &rows);
10839 assert_eq!(at.get(&0), Some(&0));
10840 assert_eq!(out.len(), 1);
10841 assert_eq!(out[0].over, Vec::new());
10842 }
10843}