1use std::collections::HashMap;
14use std::path::Path;
15
16use rucc_base::{Interner, Symbol};
17use rucc_codegen::coverage::Fired;
18use rucc_codegen::elsewhere::Elsewhere;
19use rucc_codegen::lowering::Lowerings;
20use rucc_codegen::pipeline::{self, Machine, Recording};
21use rucc_codegen::pressure::Pressure;
22use rucc_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 keywords = Keywords::new(&mut sess.interner, opts.std, opts.gnu_extensions);
193 let mut diagnostics: Vec<Diagnostic> = Vec::new();
194 let mut fired = Fired::new();
196 let mut pressure = Pressure::new();
198 let mut lowerings = Lowerings::asked(opts.lowering_dump.is_some());
199 let mut stack = StackUsage::new();
203 let mut dumps = Vec::new();
205 let mut remarks = String::new();
206 let mut passes = Vec::new();
208 let mut temps = Temps::default();
210
211 let bytes = match fs.read(Path::new(name)) {
212 Ok(bytes) => bytes,
213 Err(e) => return failure(format!("{name}: {e}")),
214 };
215 let Ok(file) = sess.sources.add_shared(crate::phase::source_name(name), bytes, None) else {
216 return failure(format!("{name}: the source map has no room left for this file"));
217 };
218 clock.lap("read");
219
220 let mut pp = rucc_pp::Preprocessor::with_prefix_map(opts.prefix_map.macros.clone());
224 let predef = rucc_pp::Predef::for_options(opts);
225 let expanded: Vec<PpToken> = {
226 let mut tokens = Vec::new();
227 {
232 let mut cx =
233 rucc_pp::Context::new(&mut sess.interner, &mut sess.sources, fs, &opts.search);
234 cx.lex = rucc_lex::Options::for_dialect(opts.std, opts.gnu_extensions);
235 cx.pedantic = opts.pedantic;
236 if pp.predefine(&sess.target, &predef, &mut cx).is_err() {
237 return failure(format!(
238 "{name}: the source map has no room for the built in macros"
239 ));
240 }
241 if pp.preinclude(&opts.preincludes, &mut tokens, &mut cx).is_err() {
242 return failure(format!("{name}: the source map has no room for the command line"));
243 }
244 tokens.append(&mut pp.run(file, &mut cx));
245 }
246 if opts.save_temps.wanted() {
247 temps.preprocessed = Some(rucc_pp::print(
248 file,
249 &tokens,
250 pp.line_directives(),
251 &sess.sources,
252 &sess.interner,
253 rucc_pp::PrintOptions { line_markers: opts.line_markers },
254 ));
255 }
256 tokens.iter().map(|token| token.to_pp()).collect()
257 };
258 diagnostics.extend(pp.take_diagnostics());
259 let deps = pp.dependencies().to_vec();
262 clock.lap("preprocess");
263
264 let cx = Convert {
267 keywords: &keywords,
268 interner: &sess.interner,
269 target: &sess.target,
270 std: opts.std,
271 gnu: opts.gnu_extensions,
272 pedantic: opts.pedantic,
273 };
274 let (tokens, complaints) = convert(&expanded, &cx);
275 diagnostics.extend(complaints);
276 clock.lap("convert");
277
278 let type_names: Vec<Symbol> =
280 sess.target.type_names().iter().filter_map(|&(name, _)| sess.interner.find(name)).collect();
281 let parsed = rucc_parse::parse(
282 &tokens,
283 rucc_parse::Context {
284 interner: &sess.interner,
285 std: opts.std,
286 gnu: opts.gnu_extensions,
287 pedantic: opts.pedantic,
288 error_limit: opts.error_limit as usize,
289 type_names: &type_names,
290 },
291 );
292 clock.lap("parse");
293 let parse_failed = parsed.diagnostics.iter().any(|d| d.severity.is_fatal());
294 diagnostics.extend(parsed.diagnostics);
295
296 let mut artifact = Artifact::Nothing;
297 let mut instrumented = Instrumented::default();
300 if !parse_failed {
301 let mut checker = Checker::new(
302 &parsed.ast,
303 CheckContext {
304 names: &sess.interner,
305 target: &sess.target,
306 std: opts.std,
307 gnu: opts.gnu_extensions,
308 pedantic: opts.pedantic,
309 permissive: opts.permissive,
310 gnu89_inline: opts.gnu89_inline,
311 error_limit: opts.error_limit as usize,
312 builtins: opts.builtins && opts.hosted,
315 no_builtin: &opts.no_builtin,
316 short_enums: opts.short_enums,
317 ms_extensions: sess.ms_extensions(),
318 trapping_math: opts.trapping_math,
319 isa: opts.isa,
320 },
321 );
322 checker.check_unit();
323 let checked = checker.finish();
324 clock.lap("check");
325 if !checked.failed() {
326 match opts.emit {
327 EmitKind::Tast => {
328 artifact = Artifact::Text(rucc_sema::print(
329 &checked.tast,
330 &checked.types,
331 &sess.interner,
332 ));
333 }
334 EmitKind::TypeGranules => {
338 artifact = Artifact::Text(rucc_types::granule_report(
339 &checked.types,
340 &sess.interner,
341 &sess.target,
342 ));
343 }
344 EmitKind::Ir
345 | EmitKind::MirFinal
346 | EmitKind::Asm
347 | EmitKind::Object
348 | EmitKind::Archive
349 | EmitKind::Executable
350 | EmitKind::SafetySummary => {
351 let mut read = |named: &str| {
356 fs.read(Path::new(named))
357 .map(|bytes| bytes.as_slice().to_vec())
358 .map_err(|why| why.to_string())
359 };
360 let meaning = if opts.debug_info {
366 crate::shapes::collect(
367 &checked.tast,
368 &checked.types,
369 &sess.target,
370 &sess.interner,
371 &sess.sources,
372 )
373 } else {
374 crate::shapes::Meaning::default()
375 };
376 let mut lowered = rucc_lower::lower(
377 crate::phase::source_name(name),
378 rucc_lower::Context {
379 tast: &checked.tast,
380 types: &checked.types,
381 target: &sess.target,
382 names: &mut sess.interner,
383 visibility: match opts.visibility {
384 Visibility::Default => IrVisibility::Default,
385 Visibility::Hidden => IrVisibility::Hidden,
386 Visibility::Protected => IrVisibility::Protected,
387 },
388 protector: match opts.protector {
389 Protector::None => LowerProtector::None,
390 Protector::Buffers => LowerProtector::Buffers,
391 Protector::Strong => LowerProtector::Strong,
392 Protector::All => LowerProtector::All,
393 },
394 wrapping: rucc_lower::Wrapping {
395 signed: opts.wrapping.signed,
396 pointer: opts.wrapping.pointer,
397 trap: opts.wrapping.trap,
398 },
399 aliasing: opts.strict_aliasing,
400 padding: opts.padding == Padding::Ignored,
401 contract: match opts.fp_contract {
402 Contract::Off => FpContract::Off,
403 Contract::On => FpContract::On,
404 Contract::Fast => FpContract::Fast,
405 },
406 align: opts.align_functions,
407 instrument: opts.instrument_functions,
408 exceptions: opts.exceptions,
409 read: &mut read,
410 },
411 );
412 clock.lap("lower");
416 let failed = lowered.diagnostics.iter().any(|d| d.severity.is_fatal());
417 if !failed {
418 if let Err(errors) = clock
423 .time("verify", || rucc_ir::verify(&lowered.module, &sess.interner))
424 {
425 for error in errors {
426 diagnostics.push(internal(&format!("invalid IR, {error}")));
427 }
428 } else if let Err(complaints) = clock
429 .time("instrument", || {
430 instrument(&mut lowered.module, &mut sess.interner, opts)
431 })
432 .map(|done| instrumented = done)
433 {
434 diagnostics.extend(complaints);
435 } else if let Err(complaints) = clock
436 .time("optimize", || {
437 optimize(
438 &mut lowered.module,
439 &mut sess.interner,
440 &sess.target,
441 opts,
442 name,
443 &mut dumps,
444 &mut remarks,
445 )
446 })
447 .map(|times| passes = times)
448 {
449 diagnostics.extend(complaints);
450 } else if opts.emit == EmitKind::SafetySummary {
451 artifact = Artifact::Text(
456 rucc_safety::summarize(
457 &lowered.module,
458 &sess.interner,
459 name,
460 opts.safety.as_str(),
461 instrumented.checks,
462 instrumented.interposed,
463 instrumented.crossings,
464 )
465 .render(),
466 );
467 } else if opts.emit == EmitKind::Ir {
468 artifact =
473 Artifact::Text(rucc_ir::print(&lowered.module, &sess.interner));
474 } else {
475 let made = clock.time("generate", || {
478 generate(
479 &mut lowered.module,
480 &mut sess.interner,
481 &sess.target,
482 opts,
483 &mut Recording {
484 fired: &mut fired,
485 pressure: &mut pressure,
486 lowerings: &mut lowerings,
487 stack: &mut stack,
488 },
489 &mut temps.assembly,
490 Origin { map: &sess.sources, name, meaning: &meaning },
491 )
492 });
493 match made {
494 Ok(made) => artifact = made,
495 Err(complaints) => diagnostics.extend(complaints),
496 }
497 }
498 }
499 diagnostics.extend(lowered.diagnostics);
500 }
501 EmitKind::SyntaxOnly => {}
503 _ => {}
504 }
505 }
506 diagnostics.extend(checked.diagnostics);
507 }
508 let mut wants = rucc_opt::Wants::none();
511 for spec in &opts.opt_info {
512 let _ = wants.add(spec);
514 }
515 if wants.wants(rucc_opt::stats::Kind::Optimized) {
516 remarks.push_str(&lowerings.remarks(name));
517 }
518
519 let mut messages = Vec::with_capacity(diagnostics.len());
520 let mut errors = 0;
521 for diag in &diagnostics {
522 if rucc_diag::dropped(diag, &sess.sources, opts.warnings, opts.system_header_warnings) {
528 continue;
529 }
530 if diag.severity.is_fatal()
531 || (diag.severity == Severity::Warning && opts.warnings_are_errors)
532 {
533 errors += 1;
534 }
535 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
536 }
537 if errors > 0 {
538 artifact = Artifact::Nothing;
540 }
541 let stack_usage = if opts.stack_usage && errors == 0 {
544 su_file(&stack, &sess.sources, crate::phase::source_name(name))
545 } else {
546 String::new()
547 };
548 clock.passes(passes);
551 let timing = clock.finish();
552 Compiled {
553 artifact,
554 messages,
555 errors,
556 fired,
557 pressure,
558 lowerings,
559 dumps,
560 remarks,
561 deps,
562 temps,
563 timing,
564 stack_usage,
565 }
566}
567
568#[must_use]
578pub fn compile_ir(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
579 let mut sess = Session::new(opts.clone());
580 if opts.emit != EmitKind::Ir {
581 return failure(format!(
582 "{name}: an input of IR can only be emitted as IR, and `--emit={}` asks for what \
583 the C in front of it became",
584 opts.emit.as_str()
585 ));
586 }
587 let bytes = match fs.read(Path::new(name)) {
588 Ok(bytes) => bytes,
589 Err(e) => return failure(format!("{name}: {e}")),
590 };
591 let Ok(text) = std::str::from_utf8(bytes.as_slice()) else {
592 return failure(format!("{name}: this is not text, so it is not IR"));
593 };
594
595 let module = match rucc_ir::parse(text, &mut sess.interner) {
596 Ok(module) => module,
597 Err(error) => {
598 return failure(format!("{name}:{}: {}", error.line, error.message));
599 }
600 };
601 let mut diagnostics: Vec<Diagnostic> = Vec::new();
602 if let Err(errors) = rucc_ir::verify(&module, &sess.interner) {
603 for error in errors {
604 diagnostics.push(invalid(&format!("invalid IR, {error}")));
605 }
606 }
607 let mut messages = Vec::with_capacity(diagnostics.len());
608 for diag in &diagnostics {
609 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
610 }
611 let errors = u32::try_from(messages.len()).unwrap_or(u32::MAX);
612 let artifact = if errors > 0 {
613 Artifact::Nothing
614 } else {
615 Artifact::Text(rucc_ir::print(&module, &sess.interner))
616 };
617 Compiled {
619 artifact,
620 messages,
621 errors,
622 fired: Fired::new(),
623 pressure: Pressure::new(),
624 lowerings: Lowerings::new(),
625 dumps: Vec::new(),
626 remarks: String::new(),
627 deps: Vec::new(),
628 temps: Temps::default(),
629 timing: crate::trace::Timing::default(),
630 stack_usage: String::new(),
631 }
632}
633
634fn instrument(
657 module: &mut rucc_ir::Module,
658 names: &mut Interner,
659 opts: &Options,
660) -> Result<Instrumented, Vec<Diagnostic>> {
661 if !opts.safety.instruments() {
662 return Ok(Instrumented::default());
663 }
664 let mut checks = rucc_safety::run(module, opts.subobject, opts.promise, opts.races);
665 checks.freed = rucc_safety::ending::checks(module, names);
673 let interposed = rucc_safety::redirect(module, names);
678 let crossings = rucc_safety::witness(module, names);
681 match rucc_ir::verify(module, names) {
682 Ok(()) => Ok(Instrumented { checks, interposed, crossings }),
683 Err(errors) => Err(errors
684 .iter()
685 .map(|e| internal(&format!("invalid IR after check insertion, {e}")))
686 .collect()),
687 }
688}
689
690#[derive(Clone, Copy, Debug, Default)]
696struct Instrumented {
697 checks: rucc_safety::Counts,
699 interposed: usize,
701 crossings: rucc_safety::Sites,
703}
704
705fn optimize(
719 module: &mut rucc_ir::Module,
720 names: &mut Interner,
721 target: &TargetInfo,
722 opts: &Options,
723 file: &str,
724 dumps: &mut Vec<rucc_opt::Dump>,
725 remarks: &mut String,
726) -> Result<Vec<(&'static str, std::time::Duration)>, Vec<Diagnostic>> {
727 let mut settings = rucc_opt::Options::for_level(opts.opt_level);
728 settings.interposition = match opts.interposition {
734 true => replaceable(target, opts),
735 false => IrPic::Executable,
736 };
737 settings.toggles.clone_from(&opts.passes);
738 settings.builtins = opts.builtins && opts.hosted;
743 settings.no_builtin.clone_from(&opts.no_builtin);
744 settings.isa = opts.isa;
747 settings.fuel = opts.pass_fuel.iter().cloned().collect();
748 settings.global_fuel = opts.pass_fuel_global;
749 settings.verify |= opts.verify_each;
750 for (on, spec) in &opts.pass_gates {
751 if let Err(why) = settings.gates.add(*on, spec) {
754 return Err(vec![internal(&why)]);
755 }
756 }
757 for spec in &opts.dump_ir {
758 if let Err(why) = settings.dumps.add(spec) {
761 return Err(vec![internal(&why)]);
762 }
763 }
764 let mut wants = rucc_opt::Wants::none();
765 for spec in &opts.opt_info {
766 if let Err(why) = wants.add(spec) {
769 return Err(vec![internal(&why)]);
770 }
771 }
772 let report = rucc_opt::run(module, names, &settings);
773 remarks.push_str(&rucc_opt::optinfo::render(file, &report, names, wants));
774 dumps.extend(report.dumps);
775 match report.broke.is_empty() {
776 true => Ok(report.time),
777 false => Err(report.broke.iter().map(|why| internal(why)).collect()),
778 }
779}
780
781fn replaceable(target: &TargetInfo, opts: &Options) -> IrPic {
815 match (target.tuple.os().object_format(), opts.pic) {
816 (Some(ObjectFormat::Elf), Pic::Library) => IrPic::Library,
817 _ => IrPic::Executable,
818 }
819}
820
821#[derive(Clone, Copy)]
828struct Origin<'a> {
829 map: &'a SourceMap,
831 name: &'a str,
833 meaning: &'a crate::shapes::Meaning,
835}
836
837fn generate(
838 module: &mut rucc_ir::Module,
839 names: &mut Interner,
840 target: &TargetInfo,
841 opts: &Options,
842 recording: &mut Recording<'_>,
843 assembly: &mut Option<String>,
844 origin: Origin<'_>,
845) -> Result<Artifact, Vec<Diagnostic>> {
846 let Some(machine) = Machine::for_target(target) else {
847 return Err(vec![unsupported(&format!(
848 "there is no back end for {} in this compiler yet, so there is nothing to generate",
849 target.tuple
850 ))]);
851 };
852 if opts.protector != Protector::None && machine.conv.guard.is_none() {
857 return Err(vec![unsupported(&format!(
858 "{} is not supported for {} yet, because the stack protector on that target is not \
859 the one this compiler writes",
860 opts.protector, target.tuple
861 ))]);
862 }
863 if opts.control.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
869 return Err(vec![unsupported(&format!(
870 "-fcf-protection={} is not supported for {} yet, because what says a file was built \
871 for it there is not the note this compiler writes",
872 opts.control, target.tuple
873 ))]);
874 }
875 let profile = match machine.conv.trace {
881 Some(trace) => opts.profile.then(|| opts.hook.early(trace.fentry)),
882 None if opts.profile => {
883 return Err(vec![unsupported(&format!(
884 "-pg is not supported for {} yet, because the profiler's hook on that target is \
885 not the one this compiler calls",
886 target.tuple
887 ))]);
888 }
889 None => None,
890 };
891 if opts.patchable.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
896 return Err(vec![unsupported(&format!(
897 "-fpatchable-function-entry= is not supported for {} yet, because what records where \
898 the room is there is not the section this compiler writes",
899 target.tuple
900 ))]);
901 }
902 let flags = pipeline::Flags {
903 frame_pointer: opts.keeps_frame_pointer(),
904 red_zone: opts.red_zone,
905 stack_clash: opts.stack_clash,
906 landing: opts.control.branch(),
907 profile: match profile {
908 None => pipeline::Profile::No,
909 Some(true) => pipeline::Profile::Early,
910 Some(false) => pipeline::Profile::Late,
911 },
912 patch: pipeline::Room { after: opts.patchable.after(), before: opts.patchable.before },
913 reorder: opts.reorder_blocks.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
920 reuse: opts.stack_reuse.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
925 schedule: opts.schedule_insns.unwrap_or_else(|| opts.opt_level.schedules()),
930 align_loops: opts.align_loops.unwrap_or(false),
936 accurate: opts.cycle_accurate_model,
938 verify: opts.verify_each,
941 allocator: if opts.backtracking.unwrap_or_else(|| opts.opt_level.runs_optimizer()) {
945 pipeline::Allocator::Backtracking
946 } else {
947 pipeline::Allocator::Single
948 },
949 goal: Goal::for_size(opts.opt_level.is_size()),
954 switch: opts.switch_shape.as_deref().and_then(rucc_codegen::switch::Force::named),
956 sibling: opts.sibling_calls.unwrap_or_else(|| opts.opt_level.sibling_calls()),
958 };
959
960 if opts.safety.instruments() {
969 rucc_opt::heap::annotate(module, names);
979 rucc_safety::handover::arrange(module);
986 rucc_safety::lower(module, names);
987 if let Err(errors) = rucc_ir::verify(module, names) {
988 return Err(errors
989 .iter()
990 .map(|e| internal(&format!("invalid IR after check lowering, {e}")))
991 .collect());
992 }
993 }
994
995 let copies = target.tuple.arch() == Arch::X86_64;
1006 let elsewhere = Elsewhere::of(module, replaceable(target, opts), target.object_format, copies);
1007
1008 let mut funcs = Vec::new();
1009 let mut complaints = Vec::new();
1010 for id in module.funcs() {
1011 if module[id].is_declaration() {
1012 continue;
1013 }
1014 match pipeline::compile_recording(
1015 &mut module[id],
1016 names,
1017 &machine,
1018 &elsewhere,
1019 flags,
1020 recording,
1021 ) {
1022 Ok(func) => funcs.push(func),
1023 Err(why) => {
1024 let name = names.resolve(module[id].name).to_owned();
1025 let span = why.inst().map_or(Span::DUMMY, |inst| module[id].span(inst));
1028 let said = format!("cannot generate code for '{name}': {why}");
1029 complaints.push(unsupported_at(&said, span));
1030 }
1031 }
1032 }
1033 if !complaints.is_empty() {
1034 return Err(complaints);
1035 }
1036 let (globals, aliases) = match opts.emit {
1042 EmitKind::Asm | EmitKind::Object | EmitKind::Archive | EmitKind::Executable => (
1043 rucc_asm::globals(module, names, target.object_format).map_err(refused)?,
1044 rucc_asm::aliases(module, names).map_err(refused)?,
1045 ),
1046 _ => (rucc_asm::Globals::default(), Vec::new()),
1047 };
1048 let unwind = opts.unwinds();
1052 match opts.emit {
1053 EmitKind::Asm => {
1054 rucc_asm::print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
1055 .map(Artifact::Text)
1056 .map_err(refused)
1057 }
1058 EmitKind::Object | EmitKind::Archive | EmitKind::Executable => {
1062 if opts.save_temps.wanted() {
1063 let listing = rucc_asm::print(
1064 &funcs,
1065 &globals,
1066 &aliases,
1067 names,
1068 target,
1069 unwind,
1070 output(opts, target),
1071 );
1072 *assembly = Some(listing.map_err(refused)?);
1073 }
1074 let aarch64 = target.tuple.arch() == Arch::Aarch64;
1085 if aarch64 || globals.kept() || rucc_asm::kept(&funcs, names, target) {
1086 if funcs.iter().any(|func| !func.landings.is_empty()) {
1090 return Err(vec![unsupported(
1091 "a cleanup that runs during an unwind, in a unit whose listing is read \
1092 back by the assembler",
1093 )]);
1094 }
1095 let print = if opts.debug_info { rucc_asm::print_marked } else { rucc_asm::print };
1096 let listing =
1097 print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
1098 .map_err(refused)?;
1099 let arch = target.tuple.arch();
1100 let read =
1101 rucc_asm::read_as(&listing, arch, target.object_format).map_err(|trouble| {
1102 let what = if aarch64 {
1103 "a unit for aarch64"
1104 } else if globals.kept() {
1105 "an `asm` at file scope"
1106 } else {
1107 "an `asm` template kept as text"
1108 };
1109 vec![unsupported(&format!(
1110 "{what}, whose listing the assembler stopped at on line {}: {}",
1111 trouble.line, trouble.why
1112 ))]
1113 })?;
1114 let info = if opts.debug_info {
1115 let assembled =
1116 placed(&read, &funcs, names, target).map_err(|why| vec![internal(&why)])?;
1117 describe(&assembled, &globals.image(), &funcs, origin, opts, target)
1118 .map_err(|why| vec![internal(&why)])?
1119 } else {
1120 rucc_object::Info::default()
1121 };
1122 let defines = rucc_object::assembled_defines(&read);
1123 let bytes =
1124 rucc_object::assembled_described(&read, target, &info).map_err(wrote)?;
1125 return Ok(Artifact::Object { bytes, defines });
1126 }
1127 let assembled = rucc_asm::assemble(&funcs, names, target, unwind, opts.debug_info)
1128 .map_err(refused)?;
1129 let data = globals.image();
1130 let info = if opts.debug_info {
1134 describe(&assembled, &data, &funcs, origin, opts, target)
1135 .map_err(|why| vec![internal(&why)])?
1136 } else {
1137 rucc_object::Info::default()
1138 };
1139 let text = assembled.text;
1140 let bytes =
1143 rucc_object::write(&text, &data, &aliases, target, output(opts, target), &info)
1144 .map_err(wrote)?;
1145 let defines = rucc_object::defines(&text, &data, &aliases, target).map_err(wrote)?;
1150 Ok(Artifact::Object { bytes, defines })
1151 }
1152 _ => Ok(Artifact::Text(rucc_mir::print(&funcs, names, target.regs))),
1153 }
1154}
1155
1156fn placed(
1168 read: &rucc_object::Assembled,
1169 funcs: &[rucc_mir::Func],
1170 names: &Interner,
1171 target: &TargetInfo,
1172) -> Result<rucc_asm::Assembled, String> {
1173 let at: HashMap<&str, &rucc_object::Name> =
1174 read.names.iter().map(|name| (name.name.as_str(), name)).collect();
1175 let offset = |name: &str| match at.get(name).map(|name| name.at) {
1176 Some(rucc_object::Held::In { part, offset }) => Some((part, offset)),
1177 _ => None,
1178 };
1179 let mut text = rucc_object::Text::default();
1180 let mut lines = Vec::with_capacity(funcs.len());
1181 let symbol = rucc_asm::Directives::of(target.object_format).symbol();
1185 for (which, func) in funcs.iter().enumerate() {
1186 let name = names.resolve(func.name);
1187 let Some((part, start)) = offset(&format!("{symbol}{name}")) else {
1188 return Err(format!("the listing has no label for the function '{name}'"));
1189 };
1190 let mut rows = Vec::with_capacity(func.inst_count() + 1);
1191 if !func.declared.is_dummy() {
1192 rows.push(rucc_asm::Row { at: 0, span: func.declared, inst: None });
1193 }
1194 for block in func.blocks() {
1195 for inst in func.insts(block) {
1196 let label = rucc_asm::mark(target, which, inst);
1197 let Some((held, here)) = offset(&label) else {
1198 return Err(format!("the listing has no label '{label}'"));
1199 };
1200 if held != part || here < start {
1201 return Err(format!("the label '{label}' is not inside '{name}'"));
1202 }
1203 let at = usize::try_from(here - start).map_err(|why| why.to_string())?;
1204 rows.push(rucc_asm::Row { at, span: func.span(inst), inst: Some(inst) });
1205 }
1206 }
1207 let size = at.get(format!("{symbol}{name}").as_str()).map_or(0, |name| name.size);
1210 let len = match offset(&rucc_asm::mark_end(target, which)) {
1211 Some((held, end)) if size == 0 && held == part && end >= start => end - start,
1212 _ => size,
1213 };
1214 text.funcs.push(rucc_object::Extent {
1215 name: name.to_owned(),
1216 start: usize::try_from(start).map_err(|why| why.to_string())?,
1217 len: usize::try_from(len).map_err(|why| why.to_string())?,
1218 align: func.align.unwrap_or(rucc_object::FUNC_ALIGN),
1219 binding: rucc_object::Binding::Global,
1220 visibility: rucc_object::Visibility::Default,
1221 patch: None,
1222 landings: Vec::new(),
1223 });
1224 lines.push(rows);
1225 }
1226 Ok(rucc_asm::Assembled { text, lines, frames: None })
1227}
1228
1229fn describe(
1248 assembled: &rucc_asm::Assembled,
1249 data: &rucc_object::Data,
1250 machine: &[rucc_mir::Func],
1251 origin: Origin<'_>,
1252 opts: &Options,
1253 target: &TargetInfo,
1254) -> Result<rucc_object::Info, String> {
1255 let rucc_asm::Assembled { text, lines, frames } = assembled;
1256 let rewrite = |path: &str| opts.prefix_map.debug.apply(path).into_owned();
1257 let mut files: Vec<String> = Vec::new();
1262 let mut funcs = Vec::with_capacity(text.funcs.len());
1263 for ((extent, rows), built) in text.funcs.iter().zip(lines).zip(machine) {
1264 let mut out: Vec<rucc_debug::Row> = Vec::with_capacity(rows.len());
1265 for row in rows {
1266 if row.span.is_dummy() {
1267 continue;
1268 }
1269 let Some(at) = origin.map.presumed(row.span.lo) else {
1270 continue;
1271 };
1272 let which = interned(&mut files, rewrite(at.name));
1273 let place = rucc_debug::Row {
1274 at: row.at as u64,
1275 file: which,
1276 line: at.line,
1277 column: at.column,
1278 };
1279 match out.last() {
1289 Some(last) if last.at == place.at => {}
1290 _ => out.push(place),
1291 }
1292 }
1293 if let Some(first) = out.first_mut() {
1300 first.at = 0;
1301 }
1302 let known = origin.meaning.funcs.get(&extent.name);
1308 let decl = known.map(|known| rucc_debug::Place {
1309 file: interned(&mut files, rewrite(&known.file)),
1310 line: known.line,
1311 });
1312 let mut sig = known.and_then(|known| known.sig.clone());
1321 let mut placed: Vec<(u32, i32)> = built.locals.clone();
1322 let mut spots = stretches(extent, rows, built, target);
1323 for &decl in &built.sharing {
1328 if !spots.iter().any(|(at, _)| *at == decl) {
1329 spots.push((decl, Vec::new()));
1330 }
1331 }
1332 if let (Some(sig), Some(known)) = (sig.as_mut(), known) {
1333 for (param, decl) in sig.params.iter_mut().zip(&known.params) {
1334 let Some(decl) = *decl else { continue };
1335 if let Some(which) = placed.iter().position(|&(at, _)| at == decl) {
1336 let at = rucc_debug::Held::Frame(i64::from(placed.remove(which).1));
1337 param.spot = Some(rucc_debug::Spot::Always(at));
1338 continue;
1339 }
1340 let Some(which) = spots.iter().position(|(at, _)| *at == decl) else { continue };
1344 param.spot = Some(rucc_debug::Spot::Over(spots.remove(which).1));
1345 }
1346 }
1347 let mut locals = Vec::with_capacity(placed.len() + spots.len());
1351 let mut wants: Vec<Option<usize>> = Vec::with_capacity(locals.capacity());
1355 for (decl, at) in placed {
1356 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1357 wants.push(named.scope);
1358 locals.push(rucc_debug::Local {
1359 name: named.name.clone(),
1360 ty: named.ty,
1361 decl: Some(rucc_debug::Place {
1362 file: interned(&mut files, rewrite(&named.file)),
1363 line: named.line,
1364 }),
1365 spot: rucc_debug::Spot::Always(rucc_debug::Held::Frame(i64::from(at))),
1366 scope: None,
1367 });
1368 }
1369 spots.sort_by_key(|(decl, _)| *decl);
1373 for (decl, spans) in spots {
1374 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1375 wants.push(named.scope);
1376 locals.push(rucc_debug::Local {
1377 name: named.name.clone(),
1378 ty: named.ty,
1379 decl: Some(rucc_debug::Place {
1380 file: interned(&mut files, rewrite(&named.file)),
1381 line: named.line,
1382 }),
1383 spot: rucc_debug::Spot::Over(spans),
1384 scope: None,
1385 });
1386 }
1387 let (scopes, at) = nests(&wants, &origin.meaning.scopes, extent, rows);
1392 for (local, want) in locals.iter_mut().zip(&wants) {
1393 local.scope = want.and_then(|want| at.get(&want).copied());
1394 }
1395 funcs.push(rucc_debug::Function {
1396 name: extent.name.clone(),
1397 len: extent.len as u64,
1398 rows: out,
1399 decl,
1400 sig,
1401 external: known.is_some_and(|known| known.external),
1402 locals,
1403 scopes,
1404 });
1405 }
1406 let mut globals = Vec::new();
1413 for object in &data.objects {
1414 let Some(held) = origin.meaning.objects.get(&object.name) else { continue };
1415 globals.push(rucc_debug::Global {
1416 name: object.name.clone(),
1417 ty: held.ty,
1418 decl: Some(rucc_debug::Place {
1419 file: interned(&mut files, rewrite(&held.file)),
1420 line: held.line,
1421 }),
1422 external: held.external,
1423 });
1424 }
1425 let unit = rucc_debug::Unit {
1426 name: rewrite(origin.name),
1427 dir: rewrite(opts.working_dir.as_deref().unwrap_or(".")),
1430 producer: format!("rucc {}", crate::VERSION),
1431 files,
1432 types: origin.meaning.types.clone(),
1433 funcs,
1434 globals,
1435 pointer: u8::try_from(target.pointer_width / 8).unwrap_or(8),
1436 frames: opts.unwinds() || frames.is_some(),
1441 mach_o: target.object_format == rucc_target::ObjectFormat::MachO,
1442 };
1443 let mut info = rucc_debug::write(&unit).map_err(|why| why.to_string())?;
1444 info.chunks.extend(frames.clone());
1445 Ok(info)
1446}
1447
1448fn stretches(
1459 extent: &rucc_object::Extent,
1460 rows: &[rucc_asm::Row],
1461 built: &rucc_mir::Func,
1462 target: &TargetInfo,
1463) -> Vec<(u32, Vec<rucc_debug::Span>)> {
1464 let (false, Some(regs)) = (built.kept.is_empty(), target.call_regs) else {
1467 return Vec::new();
1468 };
1469 let ends = ends(extent, rows);
1470 let mut bounds = vec![None; built.inst_count()];
1471 for (which, row) in rows.iter().enumerate() {
1472 let Some(inst) = row.inst else { continue };
1473 bounds[inst.index()] = Some((row.at as u64, ends[which]));
1474 }
1475 let mut spots: Vec<(u32, Vec<rucc_debug::Span>)> = Vec::new();
1476 for kept in &built.kept {
1477 let (Some((from, _)), Some((_, to))) = (bounds[kept.from.index()], bounds[kept.to.index()])
1478 else {
1479 continue;
1480 };
1481 if to <= from {
1482 continue;
1483 }
1484 let held = match kept.at {
1485 rucc_mir::Where::Reg { reg, class } => match regs.dwarf(class, reg) {
1488 Some(number) => rucc_debug::Held::Reg(number),
1489 None => continue,
1490 },
1491 rucc_mir::Where::Frame(at) => rucc_debug::Held::Frame(i64::from(at)),
1492 };
1493 let span = rucc_debug::Span { from, len: to - from, held };
1494 match spots.iter_mut().find(|(decl, _)| *decl == kept.decl) {
1495 Some((_, spans)) => spans.push(span),
1496 None => spots.push((kept.decl, vec![span])),
1497 }
1498 }
1499 for (_, spans) in &mut spots {
1500 *spans = settle(std::mem::take(spans));
1501 }
1502 spots.retain(|(_, spans)| !spans.is_empty());
1503 spots
1504}
1505
1506fn ends(extent: &rucc_object::Extent, rows: &[rucc_asm::Row]) -> Vec<u64> {
1516 let mut out = vec![extent.len as u64; rows.len()];
1517 let mut next = extent.len as u64;
1518 for which in (0..rows.len()).rev() {
1519 let at = rows[which].at as u64;
1520 out[which] = match next > at {
1523 true => next,
1524 false => out.get(which + 1).copied().unwrap_or(extent.len as u64),
1525 };
1526 next = next.min(at);
1527 }
1528 out
1529}
1530
1531fn nests(
1547 wants: &[Option<usize>],
1548 scopes: &[crate::shapes::Scope],
1549 extent: &rucc_object::Extent,
1550 rows: &[rucc_asm::Row],
1551) -> (Vec<rucc_debug::Scope>, HashMap<usize, usize>) {
1552 let mut needed: Vec<usize> = Vec::new();
1553 for &want in wants {
1554 let mut up = want;
1555 while let Some(which) = up {
1556 if needed.contains(&which) {
1557 break;
1558 }
1559 needed.push(which);
1560 up = scopes.get(which).and_then(|scope| scope.parent);
1561 }
1562 }
1563 needed.sort_unstable();
1566 let at: HashMap<usize, usize> =
1567 needed.iter().enumerate().map(|(which, &scope)| (scope, which)).collect();
1568 let ends = ends(extent, rows);
1569 let out = needed
1570 .iter()
1571 .map(|&which| {
1572 let scope = &scopes[which];
1573 rucc_debug::Scope {
1574 parent: scope.parent.and_then(|parent| at.get(&parent).copied()),
1575 over: spread(scope.span, &ends, rows),
1576 }
1577 })
1578 .collect();
1579 (out, at)
1580}
1581
1582fn spread(span: Span, ends: &[u64], rows: &[rucc_asm::Row]) -> Vec<rucc_debug::Reach> {
1590 let mut out: Vec<rucc_debug::Reach> = Vec::new();
1591 for (which, row) in rows.iter().enumerate() {
1592 if row.span.is_dummy() || row.span.lo < span.lo || row.span.hi > span.hi {
1593 continue;
1594 }
1595 let (from, to) = (row.at as u64, ends[which]);
1596 if to <= from {
1597 continue;
1598 }
1599 match out.last_mut() {
1600 Some(last) if last.from + last.len >= from => {
1601 last.len = to.saturating_sub(last.from).max(last.len);
1602 }
1603 _ => out.push(rucc_debug::Reach { from, len: to - from }),
1604 }
1605 }
1606 out
1607}
1608
1609fn settle(mut spans: Vec<rucc_debug::Span>) -> Vec<rucc_debug::Span> {
1626 spans.sort_by_key(|span| (span.from, span.len));
1627 for which in 0..spans.len() {
1628 let (from, end, held) =
1629 (spans[which].from, spans[which].from + spans[which].len, spans[which].held);
1630 let later = spans[which + 1..]
1631 .iter()
1632 .take_while(|later| later.from < end)
1633 .find(|later| later.from > from && later.held != held);
1634 if let Some(later) = later {
1635 spans[which].len = later.from - from;
1636 }
1637 }
1638 let mut edges: Vec<u64> =
1641 spans.iter().flat_map(|span| [span.from, span.from + span.len]).collect();
1642 edges.sort_unstable();
1643 edges.dedup();
1644 let mut out: Vec<rucc_debug::Span> = Vec::new();
1645 let mut first = 0;
1646 for pair in edges.windows(2) {
1647 let (from, to) = (pair[0], pair[1]);
1648 while spans.get(first).is_some_and(|span| span.from + span.len <= from) {
1652 first += 1;
1653 }
1654 let mut held = None;
1655 let mut agreed = true;
1656 for span in &spans[first..] {
1657 if span.from >= to {
1658 break;
1659 }
1660 if span.from > from || span.from + span.len < to {
1661 continue;
1662 }
1663 match held {
1664 None => held = Some(span.held),
1665 Some(seen) => agreed &= seen == span.held,
1666 }
1667 }
1668 let (Some(held), true) = (held, agreed) else { continue };
1669 match out.last_mut() {
1670 Some(last) if last.from + last.len == from && last.held == held => {
1671 last.len += to - from
1672 }
1673 _ => out.push(rucc_debug::Span { from, len: to - from, held }),
1674 }
1675 }
1676 out
1677}
1678
1679fn interned(files: &mut Vec<String>, name: String) -> usize {
1685 match files.iter().position(|have| *have == name) {
1686 Some(which) => which,
1687 None => {
1688 files.push(name);
1689 files.len() - 1
1690 }
1691 }
1692}
1693
1694fn output(opts: &Options, target: &TargetInfo) -> rucc_object::Output {
1706 let mut features = 0;
1707 if target.tuple.arch() == Arch::X86_64 {
1708 if opts.control.branch() {
1709 features |= rucc_object::Property::IBT;
1710 }
1711 if opts.control.ret() {
1712 features |= rucc_object::Property::SHSTK;
1713 }
1714 }
1715 rucc_object::Output {
1716 sections: rucc_object::Sections {
1717 functions: opts.function_sections,
1718 data: opts.data_sections,
1719 },
1720 property: rucc_object::Property { features },
1721 }
1722}
1723
1724fn wrote(why: rucc_object::Error) -> Vec<Diagnostic> {
1730 match why {
1731 rucc_object::Error::Format { .. } => vec![unsupported(&why.to_string())],
1732 rucc_object::Error::Refused { .. } => vec![internal(&why.to_string())],
1733 }
1734}
1735
1736fn refused(why: rucc_asm::Error) -> Vec<Diagnostic> {
1743 match why {
1744 rucc_asm::Error::Thread { .. }
1745 | rucc_asm::Error::IFunc { .. }
1746 | rucc_asm::Error::Frame { .. } => {
1747 vec![unsupported(&why.to_string())]
1748 }
1749 _ => vec![internal(&why.to_string())],
1750 }
1751}
1752
1753fn unsupported(message: &str) -> Diagnostic {
1759 unsupported_at(message, Span::DUMMY)
1760}
1761
1762fn unsupported_at(message: &str, span: Span) -> Diagnostic {
1768 Diagnostic::error(message.to_owned(), span)
1769 .with_code("E0653")
1770 .note("this construct is not lowered yet, see https://github.com/tamnd/rucc/issues", span)
1771}
1772
1773fn invalid(message: &str) -> Diagnostic {
1775 Diagnostic::error(message.to_owned(), Span::DUMMY).with_code("E0661")
1776}
1777
1778fn internal(message: &str) -> Diagnostic {
1780 Diagnostic::error(format!("internal error: {message}"), Span::DUMMY)
1781 .with_code("E0652")
1782 .note("this is a bug in rucc rather than in the program, please report it", Span::DUMMY)
1783}
1784
1785fn su_file(stack: &StackUsage, sources: &SourceMap, file: &str) -> String {
1792 let mut out = String::new();
1793 for row in stack.rows() {
1794 let span = row.span();
1795 let at = (!span.is_dummy()).then(|| sources.presumed(span.lo)).flatten();
1796 let (name, line, column) = at.map_or((file, 0, 0), |at| (at.name, at.line, at.column));
1797 out.push_str(&row.line(name, line, column));
1798 }
1799 out
1800}
1801
1802fn failure(message: String) -> Compiled {
1805 Compiled {
1806 artifact: Artifact::Nothing,
1807 messages: vec![format!("rucc: error: {message}")],
1808 errors: 1,
1809 fired: Fired::new(),
1810 pressure: Pressure::new(),
1811 lowerings: Lowerings::new(),
1812 dumps: Vec::new(),
1813 remarks: String::new(),
1814 deps: Vec::new(),
1815 temps: Temps::default(),
1816 timing: crate::trace::Timing::default(),
1817 stack_usage: String::new(),
1818 }
1819}
1820
1821#[cfg(test)]
1822mod tests {
1823 use rucc_session::{MemoryFileSystem, Std};
1824 use rucc_target::Triple;
1825
1826 use super::*;
1827
1828 fn options() -> Options {
1829 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1830 opts.emit = EmitKind::Tast;
1831 opts.frame_pointer = Some(false);
1834 opts
1835 }
1836
1837 fn run(opts: &Options, source: &str) -> Compiled {
1838 let mut fs = MemoryFileSystem::new();
1839 fs.insert("/main.c", source.to_owned().into_bytes());
1840 compile(opts, "/main.c", &fs)
1841 }
1842
1843 fn freestanding() -> Options {
1847 let mut opts = options();
1848 opts.hosted = false;
1849 opts.search.push_system(rucc_session::runtime::DIR);
1850 opts
1851 }
1852
1853 fn shipped(source: &str) -> String {
1855 let result = run(&freestanding(), source);
1856 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1857 result.text().to_owned()
1858 }
1859
1860 fn tast(source: &str) -> String {
1862 let result = run(&options(), source);
1863 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1864 result.text().to_owned()
1865 }
1866
1867 #[test]
1868 fn the_shipped_stdarg_declares_a_list_and_the_four_operators() {
1869 let text = shipped(concat!(
1870 "#include <stdarg.h>\n",
1871 "int sum(int n, ...) {\n",
1872 " va_list ap, copy;\n",
1873 " va_start(ap, n);\n",
1874 " va_copy(copy, ap);\n",
1875 " int total = va_arg(ap, int) + va_arg(copy, int);\n",
1876 " va_end(ap);\n",
1877 " va_end(copy);\n",
1878 " return total;\n",
1879 "}\n",
1880 ));
1881 assert!(text.contains("va-start"), "{text}");
1882 assert!(text.contains("va-copy"), "{text}");
1883 assert!(text.contains("va-arg"), "{text}");
1884 assert!(text.contains("va-end"), "{text}");
1885 }
1886
1887 #[test]
1891 fn stdarg_hands_out_the_type_alone_when_that_is_all_that_was_asked_for() {
1892 let text = shipped(concat!(
1893 "#define __need___va_list\n",
1894 "#include <stdarg.h>\n",
1895 "int vprint(const char *f, __gnuc_va_list ap);\n",
1896 "#ifdef va_start\n",
1897 "#error va_start should not be defined\n",
1898 "#endif\n",
1899 "#ifdef _VA_LIST_DEFINED\n",
1900 "#error va_list should not have been made\n",
1901 "#endif\n",
1902 ));
1903 assert!(text.contains("vprint"), "{text}");
1904 }
1905
1906 #[test]
1909 fn stddef_answers_one_piece_at_a_time_and_the_next_request_still_gets_through() {
1910 let text = shipped(concat!(
1911 "#define __need_size_t\n",
1912 "#include <stddef.h>\n",
1913 "#ifdef offsetof\n",
1914 "#error offsetof should not be defined yet\n",
1915 "#endif\n",
1916 "#define __need_ptrdiff_t\n",
1917 "#include <stddef.h>\n",
1918 "#include <stddef.h>\n",
1919 "size_t a;\n",
1920 "ptrdiff_t b;\n",
1921 "wchar_t c;\n",
1922 "max_align_t d;\n",
1923 "void *e = NULL;\n",
1924 "struct P { int x; long y; };\n",
1925 "size_t f = offsetof(struct P, y);\n",
1926 ));
1927 assert!(text.contains("decl #0 a : unsigned long"), "{text}");
1928 assert!(text.contains("decl #1 b : long"), "{text}");
1929 }
1930
1931 #[test]
1932 fn the_shipped_limits_and_float_are_the_targets_own_answers() {
1933 let text = shipped(concat!(
1934 "#include <limits.h>\n",
1935 "#include <float.h>\n",
1936 "int bits = CHAR_BIT;\n",
1937 "long big = LONG_MAX;\n",
1938 "int low = INT_MIN;\n",
1939 "int radix = FLT_RADIX;\n",
1940 "int digits = DBL_MANT_DIG;\n",
1941 ));
1942 assert!(text.contains("const 8 : int"), "{text}");
1943 assert!(text.contains("const 9223372036854775807 : long"), "{text}");
1944 assert!(text.contains("const 2 : int"), "{text}");
1945 assert!(text.contains("const 53 : int"), "{text}");
1946 }
1947
1948 #[test]
1952 fn the_shipped_stdint_writes_the_whole_set_when_there_is_no_library_to_defer_to() {
1953 let text = shipped(concat!(
1954 "#include <stdint.h>\n",
1955 "int64_t a = INT64_C(1);\n",
1956 "uint_least16_t b;\n",
1957 "intptr_t c;\n",
1958 "uintmax_t d = UINTMAX_MAX;\n",
1959 "int wide = sizeof(int_fast64_t);\n",
1960 ));
1961 assert!(text.contains("decl #0 a : long"), "{text}");
1962 assert!(text.contains("decl #1 b : unsigned short"), "{text}");
1963 assert!(text.contains("decl #2 c : long"), "{text}");
1964 }
1965
1966 #[test]
1977 fn the_shipped_mmintrin_defines_the_mmx_type_and_the_operations_over_it() {
1978 let text = shipped(concat!(
1979 "#include <mmintrin.h>\n",
1980 "__m64 add(__m64 a, __m64 b) { return _mm_add_pi16(a, b); }\n",
1981 "__m64 pack(__m64 a, __m64 b) { return _m_packsswb(a, b); }\n",
1982 "__m64 shift(__m64 a) { return _mm_srai_pi32(a, 3); }\n",
1983 "int low(__m64 a) { return _mm_cvtsi64_si32(a); }\n",
1984 "void done(void) { _mm_empty(); }\n",
1985 ));
1986 assert!(text.contains("add"), "{text}");
1987 assert!(text.contains("pack"), "{text}");
1988 assert!(text.contains("shift"), "{text}");
1989 }
1990
1991 #[test]
1996 fn the_shipped_mm_malloc_asks_for_aligned_memory_and_gives_it_back() {
1997 let text = shipped(concat!(
1998 "#include <mm_malloc.h>\n",
1999 "void *get(void) { return _mm_malloc(64, 16); }\n",
2000 "void put(void *p) { _mm_free(p); }\n",
2001 ));
2002 assert!(text.contains("get"), "{text}");
2003 assert!(text.contains("put"), "{text}");
2004 }
2005
2006 #[test]
2018 fn the_shipped_xmmintrin_defines_the_sse_type_and_the_operations_over_it() {
2019 let text = shipped(concat!(
2020 "#include <xmmintrin.h>\n",
2021 "__m128 add(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
2022 "__m128 one(__m128 a, __m128 b) { return _mm_max_ss(a, b); }\n",
2023 "__m128 mask(__m128 a, __m128 b) { return _mm_cmpnle_ps(a, b); }\n",
2024 "__m128 pick(__m128 a, __m128 b) { return _mm_shuffle_ps(a, b, _MM_SHUFFLE(0,1,2,3)); }\n",
2025 "int bits(__m128 a) { return _mm_movemask_ps(a); }\n",
2026 "int near(__m128 a) { return _mm_cvtss_si32(a); }\n",
2027 "__m128 wide(__m64 a) { return _mm_cvtpi16_ps(a); }\n",
2028 "void *room(void) { return _mm_malloc(64, 16); }\n",
2029 "void hint(const float *p) { _mm_prefetch(p, _MM_HINT_T0); _mm_sfence(); }\n",
2030 ));
2031 assert!(text.contains("add"), "{text}");
2032 assert!(text.contains("mask"), "{text}");
2033 assert!(text.contains("pick"), "{text}");
2034 assert!(text.contains("wide"), "{text}");
2035 }
2036
2037 #[test]
2044 fn the_shipped_xmmintrin_leaves_out_the_names_that_need_an_instruction() {
2045 let text = rucc_session::runtime::header("xmmintrin.h").expect("xmmintrin.h is shipped");
2046 for absent in [
2047 "_mm_sqrt_ps",
2048 "_mm_sqrt_ss",
2049 "_mm_rsqrt_ps",
2050 "_mm_rsqrt_ss",
2051 "_mm_getcsr",
2052 "_mm_setcsr",
2053 ] {
2054 let defined = text.contains(&format!("{absent}("));
2055 assert!(!defined, "{absent} is defined and the header says it is not");
2056 assert!(text.contains(absent), "{absent} is absent and unexplained");
2057 }
2058 }
2059
2060 #[test]
2061 fn the_shipped_emmintrin_defines_both_sse2_types_and_the_operations_over_them() {
2062 let text = shipped(concat!(
2063 "#include <emmintrin.h>\n",
2064 "__m128i add(__m128i a, __m128i b) { return _mm_add_epi64(a, b); }\n",
2065 "__m128i wide(__m128i a, __m128i b) { return _mm_mul_epu32(a, b); }\n",
2066 "__m128i pick(__m128i a) { return _mm_shuffle_epi32(a, _MM_SHUFFLE(0,1,2,3)); }\n",
2067 "__m128i up(__m128i a) { return _mm_slli_epi64(a, 13); }\n",
2068 "__m128i down(__m128i a) { return _mm_srli_si128(a, 3); }\n",
2069 "__m128i pack(__m128i a, __m128i b) { return _mm_packus_epi16(a, b); }\n",
2070 "int bits(__m128i a) { return _mm_movemask_epi8(a); }\n",
2071 "__m128d sum(__m128d a, __m128d b) { return _mm_add_sd(a, b); }\n",
2072 "__m128d mask(__m128d a, __m128d b) { return _mm_cmpunord_pd(a, b); }\n",
2073 "__m128i near(__m128d a) { return _mm_cvtpd_epi32(a); }\n",
2074 "__m128d over(__m128 a) { return _mm_cvtps_pd(a); }\n",
2075 "__m128i half(__m64 a) { return _mm_movpi64_epi64(a); }\n",
2076 "__m128i grab(void const *p) { return _mm_loadu_si128(p); }\n",
2077 "void wall(void) { _mm_lfence(); _mm_mfence(); }\n",
2078 ));
2079 assert!(text.contains("wide"), "{text}");
2080 assert!(text.contains("pack"), "{text}");
2081 assert!(text.contains("near"), "{text}");
2082 assert!(text.contains("half"), "{text}");
2083 }
2084
2085 #[test]
2089 fn the_shipped_immintrin_reaches_the_names_the_headers_under_it_define() {
2090 let text = shipped(concat!(
2091 "#include <immintrin.h>\n",
2092 "unsigned long long matching(unsigned char tag, unsigned char const *bucket) {\n",
2093 " __m128i const want = _mm_set1_epi8((char)tag);\n",
2094 " __m128i const chunk = _mm_loadu_si128((__m128i const *)(void const *)bucket);\n",
2095 " __m128i const same = _mm_cmpeq_epi8(chunk, want);\n",
2096 " return (unsigned long long)_mm_movemask_epi8(same);\n",
2097 "}\n",
2098 "__m64 narrow(__m64 a, __m64 b) { return _mm_add_pi32(a, b); }\n",
2099 "__m128 single(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
2100 ));
2101 assert!(text.contains("matching"), "{text}");
2102 assert!(text.contains("narrow"), "the MMX header is not reached: {text}");
2103 assert!(text.contains("single"), "the SSE header is not reached: {text}");
2104 }
2105
2106 #[test]
2110 fn the_shipped_x86intrin_reaches_the_fences_windows_headers_ask_it_for() {
2111 let text = shipped(concat!(
2112 "#include <x86intrin.h>\n",
2113 "void barriers(void *p) {\n",
2114 " _mm_lfence();\n",
2115 " _mm_sfence();\n",
2116 " _mm_mfence();\n",
2117 " _mm_pause();\n",
2118 " _mm_clflush(p);\n",
2119 "}\n",
2120 "__m128i wide(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
2121 ));
2122 assert!(text.contains("barriers"), "{text}");
2123 assert!(text.contains("wide"), "the SSE2 header is not reached: {text}");
2124 }
2125
2126 #[test]
2130 fn the_umbrella_and_the_header_under_it_can_both_be_included() {
2131 let text = shipped(concat!(
2132 "#include <immintrin.h>\n",
2133 "#include <emmintrin.h>\n",
2134 "#include <immintrin.h>\n",
2135 "#include <x86intrin.h>\n",
2136 "__m128i twice(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
2137 ));
2138 assert!(text.contains("twice"), "{text}");
2139 }
2140
2141 #[test]
2144 fn the_shipped_arm_neon_has_what_xxhash_asks_it_for() {
2145 let mut opts = freestanding();
2146 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2147 let source = concat!(
2148 "#include <arm_neon.h>\n",
2149 "uint64x2_t acc(uint64x2_t sum, const void *in, const void *key) {\n",
2150 " uint8x16_t data = vld1q_u8((const uint8_t *)in);\n",
2151 " uint8x16_t k = vld1q_u8((const uint8_t *)key);\n",
2152 " uint64x2_t mixed = vreinterpretq_u64_u8(veorq_u8(data, k));\n",
2153 " uint32x2_t lo = vmovn_u64(mixed);\n",
2154 " uint32x2_t hi = vshrn_n_u64(mixed, 32);\n",
2155 " return vmlal_u32(sum, lo, hi);\n",
2156 "}\n",
2157 "uint32x4x2_t pair(uint32x4_t a, uint32x4_t b) { return vzipq_u32(a, b); }\n",
2158 "uint32_t total(uint32x4_t a) { return vaddvq_u32(a); }\n",
2159 );
2160 let result = run(&opts, source);
2161 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2162 assert!(result.text().contains("pair"), "{}", result.text());
2163 assert!(result.text().contains("total"), "{}", result.text());
2164 }
2165
2166 #[test]
2168 fn the_shipped_arm_neon_refuses_another_target() {
2169 let result = run(&freestanding(), "#include <arm_neon.h>\n");
2170 let said = result.messages.join("\n");
2171 assert!(said.contains("arm_neon.h is for AArch64"), "{said}");
2172 }
2173
2174 #[test]
2178 fn the_shipped_emmintrin_leaves_out_the_two_square_roots() {
2179 let text = rucc_session::runtime::header("emmintrin.h").expect("emmintrin.h is shipped");
2180 for absent in ["_mm_sqrt_pd", "_mm_sqrt_sd"] {
2181 let defined = text.contains(&format!("{absent}("));
2182 assert!(!defined, "{absent} is defined and the header says it is not");
2183 assert!(text.contains(absent), "{absent} is absent and unexplained");
2184 }
2185 }
2186
2187 #[test]
2192 fn the_shipped_nmmintrin_is_one_instruction_per_step_under_sse4_2() {
2193 let mut opts = freestanding();
2194 opts.emit = EmitKind::Asm;
2195 let mut choices = rucc_target::Choices::new();
2196 choices.read("sse4.2").expect("gcc knows sse4.2");
2197 opts.isa = choices.over(opts.isa);
2198 let source = concat!(
2199 "#include <nmmintrin.h>\n",
2200 "unsigned b(unsigned c, unsigned char v) { return _mm_crc32_u8(c, v); }\n",
2201 "unsigned w(unsigned c, unsigned short v) { return _mm_crc32_u16(c, v); }\n",
2202 "unsigned l(unsigned c, unsigned v) { return _mm_crc32_u32(c, v); }\n",
2203 "unsigned long long q(unsigned long long c, unsigned long long v) {\n",
2204 " return _mm_crc32_u64(c, v);\n",
2205 "}\n",
2206 "int n(unsigned v) { return _mm_popcnt_u32(v); }\n",
2207 "long long m(unsigned long long v) { return _mm_popcnt_u64(v); }\n",
2208 );
2209 let result = run(&opts, source);
2210 assert_eq!(result.messages, Vec::<String>::new());
2211 let text = result.text();
2212 for step in ["crc32b", "crc32w", "crc32l", "crc32q", "popcntl", "popcntq"] {
2213 assert!(text.contains(step), "no {step} in:\n{text}");
2214 }
2215 }
2216
2217 #[test]
2220 fn the_shipped_smmintrin_refuses_a_caller_not_built_for_the_checksum() {
2221 let result = run(
2222 &freestanding(),
2223 "#include <immintrin.h>\nunsigned f(unsigned c) { return _mm_crc32_u32(c, 1); }\n",
2224 );
2225 let said = result.messages.join("\n");
2226 let refusal = "inlining failed in call to 'always_inline' '_mm_crc32_u32': target \
2227 specific option mismatch";
2228 assert!(said.contains(refusal), "{said}");
2229 }
2230
2231 #[test]
2236 fn a_function_built_for_sse4_2_calls_the_steps_without_a_flag() {
2237 let mut opts = freestanding();
2238 opts.emit = EmitKind::Asm;
2239 let source = concat!(
2240 "#include <nmmintrin.h>\n",
2241 "#if defined(__has_attribute) && __has_attribute (target)\n",
2242 "__attribute__((target(\"sse4.2\")))\n",
2243 "#endif\n",
2244 "unsigned l(unsigned c, unsigned v) { return _mm_crc32_u32(c, v); }\n",
2245 "__attribute__((target(\"popcnt\")))\n",
2246 "int n(unsigned v) { return _mm_popcnt_u32(v); }\n",
2247 );
2248 let result = run(&opts, source);
2249 assert_eq!(result.messages, Vec::<String>::new());
2250 let text = result.text();
2251 assert!(text.contains("crc32l") && text.contains("popcntl"), "{text}");
2252 let l = &text[text.find("\nl:").expect("l is defined")..];
2253 let l = &l[..l.find("ret").expect("l returns")];
2254 assert!(l.contains("crc32l") && !l.contains("call"), "{l}");
2255 }
2256
2257 #[test]
2264 fn the_shipped_avx512_headers_pass_postgres_probes() {
2265 let popcount = concat!(
2266 "#include <immintrin.h>\n",
2267 "#include <stdint.h>\n",
2268 "char buf[sizeof(__m512i)];\n",
2269 "#if defined(__has_attribute) && __has_attribute (target)\n",
2270 "__attribute__((target(\"avx512vpopcntdq,avx512bw\")))\n",
2271 "#endif\n",
2272 "int popcount_test(void)\n",
2273 "{\n",
2274 " int64_t popcnt = 0;\n",
2275 " __m512i accum = _mm512_setzero_si512();\n",
2276 " __m512i val = _mm512_maskz_loadu_epi8((__mmask64) 0xf0f0f0f0f0f0f0f0, (const __m512i *) buf);\n",
2277 " __m512i cnt = _mm512_popcnt_epi64(val);\n",
2278 " accum = _mm512_add_epi64(accum, cnt);\n",
2279 " popcnt = _mm512_reduce_add_epi64(accum);\n",
2280 " return (int) popcnt;\n",
2281 "}\n",
2282 );
2283 let pclmul = concat!(
2284 "#include <immintrin.h>\n",
2285 "__m512i x;\n",
2286 "__m512i y;\n",
2287 "#if defined(__has_attribute) && __has_attribute (target)\n",
2288 "__attribute__((target(\"vpclmulqdq,avx512vl\")))\n",
2289 "#endif\n",
2290 "int avx512_pclmul_test(void)\n",
2291 "{\n",
2292 " __m128i z;\n",
2293 " x = _mm512_xor_si512(_mm512_zextsi128_si512(_mm_cvtsi32_si128(0)), x);\n",
2294 " y = _mm512_clmulepi64_epi128(x, y, 0);\n",
2295 " z = _mm_ternarylogic_epi64(\n",
2296 " _mm512_castsi512_si128(y),\n",
2297 " _mm512_extracti32x4_epi32(y, 1),\n",
2298 " _mm512_extracti32x4_epi32(y, 2),\n",
2299 " 0x96);\n",
2300 " return _mm_crc32_u64(0, _mm_extract_epi64(z, 0));\n",
2301 "}\n",
2302 );
2303 let checks: [(&str, &str, &[&str]); 2] = [
2304 (popcount, "popcount_test", &["kmovq", "vmovdqu8", "vpopcntq", "vpaddq", "vshufi64x2"]),
2305 (pclmul, "avx512_pclmul_test", &["vpxorq", "vpclmulqdq", "vpternlogq", "crc32q"]),
2306 ];
2307 for (source, name, wanted) in checks {
2308 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
2309 let mut opts = freestanding();
2310 opts.emit = EmitKind::Asm;
2311 opts.opt_level = level;
2312 let result = run(&opts, source);
2313 assert_eq!(result.messages, Vec::<String>::new(), "{name} at {level:?}");
2314 let text = result.text();
2315 let start = text.find(&format!("\n{name}:")).expect("the probe is written out");
2316 let body = &text[start..];
2317 let body = &body[..body.find(".size").unwrap_or(body.len())];
2318 for instruction in wanted {
2319 assert!(
2320 body.contains(instruction),
2321 "no {instruction} at {level:?} in:\n{body}"
2322 );
2323 }
2324 assert!(!body.contains("call"), "a call left behind at {level:?} in:\n{body}");
2325 }
2326 }
2327 }
2328
2329 #[test]
2332 fn the_shipped_avx512_headers_refuse_a_caller_not_built_for_them() {
2333 let result = run(
2334 &freestanding(),
2335 "#include <immintrin.h>\n__m512i f(__m512i a) { return _mm512_popcnt_epi64(a); }\n",
2336 );
2337 let said = result.messages.join("\n");
2338 let refusal = "inlining failed in call to 'always_inline' '_mm512_popcnt_epi64': target \
2339 specific option mismatch";
2340 assert!(said.contains(refusal), "{said}");
2341 }
2342
2343 #[test]
2348 fn the_sse3_to_sse4_2_intrinsics_are_the_instructions_under_the_attribute() {
2349 let mut opts = freestanding();
2350 opts.emit = EmitKind::Asm;
2351 let source = concat!(
2352 "#include <immintrin.h>\n",
2353 "__attribute__((target(\"sse3\")))\n",
2354 "__m128i a(const __m128i *p) { return _mm_lddqu_si128(p); }\n",
2355 "__attribute__((target(\"sse3\")))\n",
2356 "__m128 b(__m128 x, __m128 y) { return _mm_hadd_ps(x, y); }\n",
2357 "__attribute__((target(\"ssse3\")))\n",
2358 "__m128i c(__m128i x, __m128i y) { return _mm_shuffle_epi8(_mm_abs_epi32(x), y); }\n",
2359 "__attribute__((target(\"ssse3\")))\n",
2360 "__m128i d(__m128i x, __m128i y) { return _mm_alignr_epi8(x, y, 5); }\n",
2361 "__attribute__((target(\"sse4.1\")))\n",
2362 "int e(__m128i x, __m128i y) {\n",
2363 " return _mm_extract_epi32(_mm_min_epi32(_mm_mullo_epi32(x, y), y), 2);\n",
2364 "}\n",
2365 "__attribute__((target(\"sse4.1\")))\n",
2366 "__m128 f(__m128 x) { return _mm_round_ps(x, _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); }\n",
2367 "__attribute__((target(\"sse4.1\")))\n",
2368 "__m128i g(__m128i x, __m128i y, __m128i m) { return _mm_blendv_epi8(x, y, m); }\n",
2369 "__attribute__((target(\"sse4.1\")))\n",
2370 "int h(__m128i x) { return _mm_testz_si128(x, x); }\n",
2371 "__attribute__((target(\"sse4.2\")))\n",
2372 "__m128i i(__m128i x, __m128i y) { return _mm_cmpgt_epi64(x, y); }\n",
2373 "__attribute__((target(\"sse4.2\")))\n",
2374 "int j(__m128i x, __m128i y) { return _mm_cmpistri(x, y, _SIDD_CMP_EQUAL_EACH); }\n",
2375 );
2376 let result = run(&opts, source);
2377 assert_eq!(result.messages, Vec::<String>::new());
2378 let text = result.text();
2379 for insn in [
2380 "lddqu",
2381 "haddps",
2382 "pabsd",
2383 "pshufb",
2384 "palignr $5,",
2385 "pmulld",
2386 "pminsd",
2387 "pextrd $2,",
2388 "roundps $8,",
2389 "pblendvb",
2390 "ptest",
2391 "pcmpgtq",
2392 "pcmpistri $8,",
2393 ] {
2394 assert!(text.contains(insn), "no {insn} in:\n{text}");
2395 }
2396 assert!(!text.contains("call"), "{text}");
2397 }
2398
2399 #[test]
2402 fn the_sse3_to_sse4_1_intrinsics_are_refused_a_caller_not_built_for_them() {
2403 let source = concat!(
2404 "#include <immintrin.h>\n",
2405 "__m128i f(__m128i x, __m128i y) { return _mm_shuffle_epi8(x, y); }\n",
2406 );
2407 let said = run(&freestanding(), source).messages.join("\n");
2408 let refusal = "inlining failed in call to 'always_inline' '_mm_shuffle_epi8': target \
2409 specific option mismatch";
2410 assert!(said.contains(refusal), "{said}");
2411
2412 let mut opts = freestanding();
2413 let mut choices = rucc_target::Choices::new();
2414 choices.read("ssse3").expect("gcc knows ssse3");
2415 opts.isa = choices.over(opts.isa);
2416 let result =
2417 run(&opts, &format!("{source}__m128 g(__m128 x) {{ return _mm_movehdup_ps(x); }}\n"));
2418 assert_eq!(result.messages, Vec::<String>::new());
2419 let result = run(
2420 &opts,
2421 "#include <immintrin.h>\n__m128i h(__m128i x) { return _mm_abs_epi8(_mm_cvtepi8_epi32(x)); }\n",
2422 );
2423 let said = result.messages.join("\n");
2424 assert!(said.contains("'_mm_cvtepi8_epi32': target specific option mismatch"), "{said}");
2425 }
2426
2427 #[test]
2430 fn a_target_string_gcc_does_not_know_is_refused() {
2431 for (string, name) in [("sse5", "sse5"), ("+crc", "+crc"), ("sse4.2,foo", "foo")] {
2432 let source =
2433 format!("__attribute__((target(\"{string}\"))) int f(void) {{ return 0; }}\n");
2434 let said = run(&freestanding(), &source).messages.join("\n");
2435 let wanted = format!("attribute 'target' argument '{name}' is unknown");
2436 assert!(said.contains(&wanted), "{string}: {said}");
2437 }
2438 }
2439
2440 #[test]
2443 fn an_aarch64_target_string_is_still_accepted() {
2444 let mut opts = freestanding();
2445 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2446 let source = "__attribute__((target(\"+crc\"))) int f(void) { return 0; }\n";
2447 let result = run(&opts, source);
2448 assert_eq!(result.messages, Vec::<String>::new());
2449 }
2450
2451 #[test]
2452 fn the_three_formality_headers_still_have_to_work() {
2453 let text = shipped(concat!(
2454 "#include <stdbool.h>\n",
2455 "#include <stdalign.h>\n",
2456 "#include <iso646.h>\n",
2457 "#include <stdnoreturn.h>\n",
2458 "int t = true and not false;\n",
2459 "_Alignas(16) char buf[16];\n",
2460 "int a = alignof(long);\n",
2461 ));
2462 assert!(text.contains("decl #0 t : int"), "{text}");
2463 assert!(text.contains("const 8 : unsigned long"), "{text}");
2464 }
2465
2466 #[test]
2474 fn every_shipped_header_can_be_included_twice() {
2475 let once: String = rucc_session::runtime::names()
2478 .iter()
2479 .filter(|name| **name != "arm_neon.h")
2480 .map(|name| format!("#include <{name}>\n"))
2481 .collect();
2482 let twice = once.repeat(2);
2483 assert_eq!(shipped(&format!("{once}int x;\n")), shipped(&format!("{twice}int x;\n")));
2484
2485 let mut opts = freestanding();
2486 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2487 let tree = |source: &str| {
2488 let result = run(&opts, source);
2489 assert_eq!(
2490 result.messages,
2491 Vec::<String>::new(),
2492 "expected this to compile:\n{source}"
2493 );
2494 result.text().to_owned()
2495 };
2496 let neon = "#include <arm_neon.h>\n";
2497 assert_eq!(tree(&format!("{neon}int x;\n")), tree(&format!("{neon}{neon}int x;\n")));
2498 }
2499
2500 #[test]
2501 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
2502 let fs = MemoryFileSystem::new();
2503 let result = compile(&options(), "/nope.c", &fs);
2504 assert!(result.failed());
2505 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
2506 assert!(result.text().is_empty());
2507 }
2508
2509 #[test]
2510 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
2511 let text = tast("int x = 1;\n");
2512 let expected = "\
2513decl #0 x : int object external static defined
2514 init
2515 +0
2516 const 1 : int
2517";
2518 assert_eq!(text, expected);
2519 }
2520
2521 #[test]
2522 fn the_macros_are_expanded_before_anything_is_parsed() {
2523 let text = tast("#define N 2\nint a[N];\n");
2527 assert!(text.starts_with("decl #0 a : int[2] object external static tentative"), "{text}");
2528 }
2529
2530 #[test]
2536 fn a_pragma_is_not_a_declaration_and_the_parse_walks_past_the_ones_it_does_not_read() {
2537 let text = tast(concat!(
2538 "#pragma pack(4)\n",
2539 "struct s { int a; };\n",
2540 "#pragma pack()\n",
2541 "int b;\n",
2542 "_Pragma(\"GCC visibility push(default)\") int c;\n",
2543 ));
2544 assert!(text.contains("decl #0 b : int"), "{text}");
2545 assert!(text.contains("decl #1 c : int"), "{text}");
2546 }
2547
2548 #[test]
2552 fn the_byte_swaps_and_the_bit_counts_of_a_constant_are_constants() {
2553 tast(concat!(
2554 "static const unsigned magic = __builtin_bswap32(0x11223344u);\n",
2555 "_Static_assert(__builtin_bswap16(0x1234) == 0x3412, \"16\");\n",
2556 "_Static_assert(__builtin_bswap32(0x11223344u) == 0x44332211u, \"32\");\n",
2557 "_Static_assert(__builtin_bswap64(0x0102030405060708ull) == 0x0807060504030201ull, \"64\");\n",
2558 "_Static_assert(__builtin_popcountll(-1ll) == 64 && __builtin_popcount(-1) == 32, \"ones\");\n",
2559 "_Static_assert(__builtin_parity(7) == 1 && __builtin_parity(3) == 0, \"parity\");\n",
2560 "_Static_assert(__builtin_ffs(0) == 0 && __builtin_ffs(8) == 4, \"ffs\");\n",
2561 "_Static_assert(__builtin_clrsb(0) == 31 && __builtin_clrsb(-1) == 31, \"clrsb\");\n",
2562 "_Static_assert(__builtin_clrsbl(1) == 62, \"clrsbl\");\n",
2563 "_Static_assert(__builtin_clz(1) == 31 && __builtin_clzl(1) == 63, \"clz\");\n",
2564 "_Static_assert(__builtin_ctzll(1ull << 40) == 40, \"ctz\");\n",
2565 "_Static_assert(__builtin_clz(0) == 32 && __builtin_ctzll(0) == 64, \"zero\");\n",
2566 ));
2567 }
2568
2569 #[test]
2577 fn the_layout_attributes_move_the_members_and_the_record_the_way_gcc_lays_them_out() {
2578 tast(concat!(
2579 "struct A { char c; int i; } __attribute__((packed));\n",
2580 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2581 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2582 "struct B { char c; int i; } __attribute__((aligned));\n",
2585 "_Static_assert(sizeof(struct B) == 16 && _Alignof(struct B) == 16, \"B\");\n",
2586 "struct C { char c; int i __attribute__((packed)); };\n",
2587 "_Static_assert(sizeof(struct C) == 5 && _Alignof(struct C) == 1, \"C\");\n",
2588 "_Static_assert(__builtin_offsetof(struct C, i) == 1, \"C.i\");\n",
2589 "struct D { char c; int i; } __attribute__((packed, aligned(4)));\n",
2590 "_Static_assert(sizeof(struct D) == 8 && _Alignof(struct D) == 4, \"D\");\n",
2591 "_Static_assert(__builtin_offsetof(struct D, i) == 1, \"D.i\");\n",
2592 "struct E { char c; _Alignas(8) int i; };\n",
2593 "_Static_assert(sizeof(struct E) == 16 && _Alignof(struct E) == 8, \"E\");\n",
2594 "_Static_assert(__builtin_offsetof(struct E, i) == 8, \"E.i\");\n",
2595 "struct F { char c; int i __attribute__((aligned(8))); };\n",
2596 "_Static_assert(sizeof(struct F) == 16 && _Alignof(struct F) == 8, \"F\");\n",
2597 "struct G { char c; short s; } __attribute__((aligned(2)));\n",
2600 "_Static_assert(sizeof(struct G) == 4 && _Alignof(struct G) == 2, \"G\");\n",
2601 "struct H { char c; int i; } __attribute__((aligned(2)));\n",
2602 "_Static_assert(sizeof(struct H) == 8 && _Alignof(struct H) == 4, \"H\");\n",
2603 "struct I { [[gnu::packed]] char c; int i; };\n",
2606 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2607 "struct J { char c; [[gnu::packed]] int i; };\n",
2608 "_Static_assert(sizeof(struct J) == 5 && _Alignof(struct J) == 1, \"J\");\n",
2609 "struct M { char c; int i : 5; int j : 20; } __attribute__((packed));\n",
2610 "_Static_assert(sizeof(struct M) == 5 && _Alignof(struct M) == 1, \"M\");\n",
2611 "struct N { char c; long long l; } __attribute__((aligned(32)));\n",
2612 "_Static_assert(sizeof(struct N) == 32 && _Alignof(struct N) == 32, \"N\");\n",
2613 "union L { char c; int i; } __attribute__((packed));\n",
2614 "_Static_assert(sizeof(union L) == 4 && _Alignof(union L) == 1, \"L\");\n",
2615 "struct O { char c; int i; } __attribute__((__packed__));\n",
2619 "_Static_assert(sizeof(struct O) == 5 && _Alignof(struct O) == 1, \"O\");\n",
2620 "struct P { char c; int i; } __attribute__((__aligned__(8)));\n",
2621 "_Static_assert(sizeof(struct P) == 8 && _Alignof(struct P) == 8, \"P\");\n",
2622 ));
2623 }
2624
2625 #[test]
2638 fn a_transparent_union_takes_the_member_a_value_fits_and_is_declared_either_way() {
2639 let text = tast(concat!(
2640 "struct one { int x; };\n",
2641 "struct two { long y; };\n",
2642 "typedef union { struct one *a; struct two *b; void *any; }\n",
2643 " __attribute__((__transparent_union__)) arg;\n",
2644 "int takes(arg v);\n",
2645 "int f(struct one *p, struct two *q, char *c) {\n",
2646 " return takes(p) + takes(q) + takes(c) + takes(0);\n",
2647 "}\n",
2648 "int takes(struct one *p);\n",
2650 "int (*as_a_member)(struct one *) = takes;\n",
2651 "int (*as_the_union)(arg) = takes;\n",
2652 ));
2653 assert!(text.contains("compound-literal"), "{text}");
2654 }
2655
2656 #[test]
2662 fn the_attribute_on_the_declarator_of_a_typedef_is_the_one_glibc_writes() {
2663 let text = tast(concat!(
2664 "struct sockaddr { int family; };\n",
2665 "struct sockaddr_in { int family; int addr; };\n",
2666 "typedef union { struct sockaddr *plain; struct sockaddr_in *inet; }\n",
2667 " addr_arg __attribute__((__transparent_union__));\n",
2668 "int bind_to(int fd, addr_arg where);\n",
2669 "int f(struct sockaddr_in *where) { return bind_to(0, where); }\n",
2670 ));
2671 assert!(text.contains("compound-literal"), "{text}");
2672 }
2673
2674 #[test]
2682 fn a_transparent_union_that_cannot_keep_the_promise_is_dropped_with_a_word_about_it() {
2683 let result = run(
2684 &options(),
2685 concat!(
2686 "union wider { int small; double large; } __attribute__((transparent_union));\n",
2687 "struct plain { int x; } __attribute__((transparent_union));\n",
2688 ),
2689 );
2690 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
2691 assert!(!result.failed(), "{:?}", result.messages);
2692 for message in &result.messages {
2693 assert!(message.contains("'transparent_union' attribute ignored"), "{message}");
2694 }
2695 assert!(result.messages[0].contains("first member"), "{:?}", result.messages);
2696 assert!(result.messages[1].contains("only a union"), "{:?}", result.messages);
2697 }
2698
2699 #[test]
2708 fn an_access_to_a_packed_member_says_the_alignment_the_layout_left_it() {
2709 let packed = body(concat!(
2710 "struct P { char c; int v; } __attribute__((packed));\n",
2711 "int f(struct P *p) { return p->v; }\n",
2712 ));
2713 assert!(packed.contains("load.i32 %2, align 1,"), "{packed}");
2714 let plain = body(concat!(
2716 "struct P { char c; int v; };\n",
2717 "int f(struct P *p) { return p->v; }\n",
2718 ));
2719 assert!(plain.contains("load.i32 %2, align 4,"), "{plain}");
2720 }
2721
2722 #[test]
2729 fn what_is_inside_a_packed_member_is_no_more_aligned_than_the_member_is() {
2730 let stepped = body(concat!(
2731 "struct P { char c; int v[4]; } __attribute__((packed));\n",
2732 "int f(struct P *p, int i) { return p->v[i]; }\n",
2733 ));
2734 assert!(stepped.contains(", align 1,"), "{stepped}");
2735 assert!(!stepped.contains(", align 4,"), "{stepped}");
2736 let nested = body(concat!(
2737 "struct Inner { int v; };\n",
2738 "struct P { char c; struct Inner in; } __attribute__((packed));\n",
2739 "int f(struct P *p) { return p->in.v; }\n",
2740 ));
2741 assert!(nested.contains(", align 1,"), "{nested}");
2742 assert!(!nested.contains(", align 4,"), "{nested}");
2743 }
2744
2745 #[test]
2761 fn an_access_through_a_typedef_that_lowered_its_alignment_says_the_one_the_typedef_asked_for() {
2762 let through = body(concat!(
2763 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2764 "unsigned int f(const void *p) { return *(const unalign32 *)p; }\n",
2765 ));
2766 assert!(through.contains("load.i32 %0, align 1,"), "{through}");
2767 let stepped = body(concat!(
2770 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2771 "unsigned int f(unalign32 *p, int i) { return p[i]; }\n",
2772 ));
2773 assert!(stepped.contains(", align 1,"), "{stepped}");
2774 assert!(!stepped.contains(", align 4,"), "{stepped}");
2775 let plain = body(concat!(
2778 "typedef unsigned int word;\n",
2779 "unsigned int f(const void *p) { return *(const word *)p; }\n",
2780 ));
2781 assert!(plain.contains("load.i32 %0, align 4,"), "{plain}");
2782 }
2783
2784 #[test]
2795 fn a_vector_read_through_a_typedef_that_lowered_its_alignment_comes_back_a_piece_at_a_time() {
2796 let prefix = concat!(
2797 "typedef long long v2di __attribute__((__vector_size__(16)));\n",
2798 "typedef long long v2di_u __attribute__((__vector_size__(16), __aligned__(1)));\n",
2799 );
2800 let loaded =
2801 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di_u *)p; }}"));
2802 assert_eq!(loaded.matches("align 1\n").count(), 2, "{loaded}");
2803 assert!(!loaded.contains("align 16"), "{loaded}");
2804 let stored = body(&format!("{prefix}void f(void *p, v2di b) {{ *(v2di_u *)p = b; }}"));
2807 assert!(stored.contains("memcpy %0, %3, size 16, align 1"), "{stored}");
2808 let aligned =
2810 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di *)p; }}"));
2811 assert!(aligned.contains("align 16"), "{aligned}");
2812 }
2813
2814 #[test]
2823 fn the_aligned_attribute_on_a_declaration_raises_what_that_one_object_is_aligned_to() {
2824 tast(concat!(
2825 "int v __attribute__((aligned(64)));\n",
2826 "_Static_assert(__alignof__(v) == 64, \"v\");\n",
2827 "__attribute__((aligned(32))) int w;\n",
2830 "_Static_assert(__alignof__(w) == 32, \"w\");\n",
2831 "[[gnu::aligned(16)]] int x;\n",
2832 "_Static_assert(__alignof__(x) == 16, \"x\");\n",
2833 "int y __attribute__((aligned(2)));\n",
2836 "_Static_assert(__alignof__(y) == 4, \"y\");\n",
2837 "void f(void) { int a __attribute__((aligned(128)));\n",
2839 "_Static_assert(__alignof__(a) == 128, \"a\"); (void)a; }\n",
2840 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2843 "void g(void) __attribute__((aligned(256)));\n",
2846 "void g(void) {}\n",
2847 "_Static_assert(__alignof__(g) == 256, \"g\");\n",
2848 ));
2849 }
2850
2851 #[test]
2855 fn what_a_declaration_asked_to_be_aligned_to_is_what_the_assembler_is_told() {
2856 let text = asm(concat!(
2857 "int v __attribute__((aligned(64)));\n",
2858 "void g(void) __attribute__((aligned(256)));\n",
2859 "void g(void) {}\n",
2860 "void plain(void) {}\n",
2861 ));
2862 assert!(text.contains("\t.p2align\t6\n\t.type\tv, @object\n"), "{text}");
2863 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2864 assert!(text.contains("\t.p2align\t4, 0x90\n\t.globl\tplain\n"), "{text}");
2865 }
2866
2867 #[test]
2873 fn the_alignment_the_command_line_asked_of_every_function_is_a_floor_under_all_of_them() {
2874 let source = concat!(
2875 "void g(void) __attribute__((aligned(256)));\n",
2876 "void g(void) {}\n",
2877 "void small(void) __attribute__((aligned(4)));\n",
2878 "void small(void) {}\n",
2879 "void plain(void) {}\n",
2880 );
2881 let listing = |align: Option<u32>| {
2882 let mut opts = options();
2883 opts.emit = EmitKind::Asm;
2884 opts.align_functions = align;
2885 let result = run(&opts, source);
2886 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
2887 result.text().to_owned()
2888 };
2889
2890 let text = listing(Some(32));
2891 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "the larger one wins: {text}");
2892 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tsmall\n"), "{text}");
2893 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tplain\n"), "{text}");
2894
2895 let text = listing(Some(8));
2898 assert!(text.contains("\t.p2align\t3, 0x90\n\t.globl\tplain\n"), "{text}");
2899 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2900 }
2901
2902 #[test]
2911 fn an_aligned_typedef_says_what_an_object_of_it_is_aligned_to_and_may_lower_it() {
2912 tast(concat!(
2913 "typedef int L __attribute__((aligned(2)));\n",
2914 "_Static_assert(__alignof__(L) == 2, \"L\");\n",
2915 "_Static_assert(_Alignof(L) == 2, \"L alignof\");\n",
2916 "_Static_assert(sizeof(L) == 4, \"L size\");\n",
2918 "struct T { char c; L x; };\n",
2919 "_Static_assert(sizeof(struct T) == 6, \"T\");\n",
2920 "_Static_assert(__builtin_offsetof(struct T, x) == 2, \"T.x\");\n",
2921 "typedef int H __attribute__((aligned(16)));\n",
2923 "_Static_assert(__alignof__(H) == 16, \"H\");\n",
2924 "_Static_assert(sizeof(H) == 4, \"H size\");\n",
2925 "struct U { char c; H x; };\n",
2926 "_Static_assert(sizeof(struct U) == 32, \"U\");\n",
2927 "_Static_assert(__builtin_offsetof(struct U, x) == 16, \"U.x\");\n",
2928 "typedef L M __attribute__((aligned(8)));\n",
2931 "_Static_assert(__alignof__(M) == 8, \"M\");\n",
2932 "typedef L N;\n",
2935 "_Static_assert(__alignof__(N) == 2, \"N\");\n",
2936 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2938 ));
2939 let text = asm(concat!(
2940 "typedef int L __attribute__((aligned(2)));\n",
2941 "typedef int H __attribute__((aligned(16)));\n",
2942 "L low;\n",
2943 "H high;\n",
2944 ));
2945 assert!(text.contains("\t.p2align\t1\n\t.type\tlow, @object\n"), "{text}");
2946 assert!(text.contains("\t.p2align\t4\n\t.type\thigh, @object\n"), "{text}");
2947 }
2948
2949 #[test]
2957 fn the_vector_size_attribute_builds_a_type_of_lanes_and_measures_it_in_bytes() {
2958 tast(concat!(
2959 "typedef int __attribute__((vector_size(16))) v4si;\n",
2960 "_Static_assert(sizeof(v4si) == 16 && _Alignof(v4si) == 16, \"v4si\");\n",
2961 "typedef char __attribute__((vector_size(16))) v16qi;\n",
2962 "_Static_assert(sizeof(v16qi) == 16, \"v16qi\");\n",
2963 "typedef int __attribute__((vector_size(4))) v1si;\n",
2966 "_Static_assert(sizeof(v1si) == 4, \"v1si\");\n",
2967 "typedef float __attribute__((__vector_size__(8))) v2sf;\n",
2969 "_Static_assert(sizeof(v2sf) == 8, \"v2sf\");\n",
2970 "typedef short [[gnu::vector_size(8)]] v4hi;\n",
2971 "_Static_assert(sizeof(v4hi) == 8, \"v4hi\");\n",
2972 "v4si g;\n",
2975 "_Static_assert(sizeof(g[0]) == 4, \"lane\");\n",
2976 "_Static_assert(sizeof(g + g) == 16, \"whole\");\n",
2977 "_Static_assert(sizeof(g + 1) == 16, \"broadcast\");\n",
2980 "_Static_assert(sizeof(v4si[3]) == 48, \"array\");\n",
2982 ));
2983 }
2984
2985 #[test]
2995 fn a_vector_is_written_whole_into_an_array_of_them_and_named_by_a_type_name() {
2996 tast(concat!(
2997 "typedef int __attribute__((vector_size(8))) v2si;\n",
2998 "v2si table[] = { (v2si){ 1, 2 }, (v2si){ 3, 4 } };\n",
2999 "_Static_assert(sizeof(table) == 16, \"two of them and not eight lanes\");\n",
3000 "v2si written = (int __attribute__((vector_size(8)))){ 5, 6 };\n",
3002 "_Static_assert(sizeof((int __attribute__((vector_size(16)))){ 0 }) == 16, \"named\");\n",
3003 "v2si lanes[2] = { 1, 2, 3, 4 };\n",
3006 "_Static_assert(sizeof(lanes) == 16, \"still elided\");\n",
3007 ));
3008 }
3009
3010 #[test]
3018 fn a_lane_is_assignable_and_a_shift_takes_a_count_of_its_own_lane() {
3019 let result = run(
3020 &options(),
3021 concat!(
3022 "typedef int __attribute__((vector_size(16))) v4si;\n",
3023 "typedef unsigned __attribute__((vector_size(16))) v4ui;\n",
3024 "void write(v4si *out, v4ui a, v4si b, int n) {\n",
3025 " v4si v = { 1, 2, 3, 4 };\n",
3026 " v[0] = n;\n",
3027 " v[1] += n;\n",
3028 " v[2]++;\n",
3029 " *&v[3] = n;\n",
3030 " v4ui shifted = a >> b;\n",
3032 " shifted <<= b;\n",
3033 " *out = v + (v4si)shifted + (1 << b);\n",
3036 "}\n",
3037 "void refused(const v4si c) {\n",
3040 " c[0] = 1;\n",
3041 "}\n",
3042 ),
3043 );
3044 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
3045 assert!(result.messages[0].contains("assignment of read-only"), "{:?}", result.messages);
3046 }
3047
3048 #[test]
3060 fn a_record_that_asks_for_the_other_byte_order_swaps_every_scalar_it_holds() {
3061 let read = "int f(struct s *p) { return p->i; }\n";
3062 let big = "struct s { int i; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
3063 assert!(body(&format!("{big}{read}")).contains("bswap"), "{big}");
3064
3065 let armoured =
3066 "struct s { int i; } __attribute__((__scalar_storage_order__(\"big-endian\")));\n";
3067 assert!(body(&format!("{armoured}{read}")).contains("bswap"), "{armoured}");
3068
3069 let front = "struct __attribute__((scalar_storage_order(\"big-endian\"))) s { int i; };\n";
3070 assert!(body(&format!("{front}{read}")).contains("bswap"), "{front}");
3071
3072 let standard = "struct s { int i; } [[gnu::scalar_storage_order(\"big-endian\")]];\n";
3073 assert!(body(&format!("{standard}{read}")).contains("bswap"), "{standard}");
3074
3075 let same =
3076 "struct s { int i; } __attribute__((scalar_storage_order(\"little-endian\")));\n";
3077 assert!(!body(&format!("{same}{read}")).contains("bswap"), "{same}");
3078
3079 let byte = "struct s { char c; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
3081 let source = format!("{byte}int f(struct s *p) {{ return p->c; }}\n");
3082 assert!(!body(&source).contains("bswap"), "{byte}");
3083
3084 tast(concat!(
3085 "struct s { int i; short h; char c; }",
3086 " __attribute__((scalar_storage_order(\"big-endian\")));\n",
3087 "_Static_assert(sizeof(struct s) == 8 && _Alignof(struct s) == 4, \"s\");\n",
3088 "_Static_assert(__builtin_offsetof(struct s, h) == 4, \"s.h\");\n",
3089 "_Static_assert(__builtin_offsetof(struct s, c) == 6, \"s.c\");\n",
3090 ));
3091 }
3092
3093 #[test]
3099 fn a_bit_field_in_one_of_those_records_is_counted_from_the_top_of_its_bytes() {
3100 let members = "short i : 12; char c1 : 1; char c2 : 1; char c3 : 1; char c4 : 1;";
3101 let read = "int f(struct s *p) { return p->i; }\n";
3102 let plain = format!("struct s {{ {members} }};\n{read}");
3103 let reversed = format!(
3104 "struct s {{ {members} }} __attribute__((scalar_storage_order(\"big-endian\")));\n\
3105 {read}"
3106 );
3107 assert!(body(&plain).contains("shl"), "{}", body(&plain));
3108 assert!(!body(&plain).contains("bswap"), "{}", body(&plain));
3109 let built = body(&reversed);
3112 assert!(built.contains("bswap"), "{built}");
3113 assert!(!built.contains("shl"), "{built}");
3114 assert!(built.contains("ashr"), "{built}");
3115 }
3116
3117 #[test]
3124 fn the_address_of_a_scalar_stored_the_other_way_round_is_refused() {
3125 let opts = options();
3126 let record = "struct s { int i; int a[2]; struct in { int n; } w; }\n\
3127 __attribute__((scalar_storage_order(\"big-endian\")));\n";
3128 let taken = format!("{record}int *f(struct s *p) {{ return &p->i; }}\n");
3129 assert_eq!(
3130 run(&opts, &taken).messages,
3131 ["/main.c:3:30: error: cannot take address of scalar with reverse storage order \
3132 [E0712]"]
3133 );
3134 let element = format!("{record}int *f(struct s *p) {{ return &p->a[0]; }}\n");
3135 let messages = run(&opts, &element).messages;
3136 assert!(messages[0].contains("[E0712]"), "{messages:?}");
3137
3138 let whole = format!("{record}int *f(struct s *p) {{ return (int *) &p->w; }}\n");
3139 assert_eq!(run(&opts, &whole).messages, Vec::<String>::new(), "{whole}");
3140 }
3141
3142 #[test]
3146 fn a_storage_order_that_names_neither_end_is_refused_with_the_two_words_that_are_taken() {
3147 let opts = options();
3148 let wrong = "struct s { int i; } __attribute__((scalar_storage_order(\"middle\")));\n";
3149 assert_eq!(
3150 run(&opts, wrong).messages,
3151 ["/main.c:1:36: error: 'scalar_storage_order' argument must be one of \"big-endian\" \
3152 or \"little-endian\" [E0688]"]
3153 );
3154 let bare = "struct s { int i; } __attribute__((scalar_storage_order));\n";
3155 let messages = run(&opts, bare).messages;
3156 assert!(messages[0].contains("[E0688]"), "{messages:?}");
3157 }
3158
3159 #[test]
3169 fn packing_is_what_decides_whether_a_bit_field_may_straddle_its_own_storage() {
3170 assert_eq!(bit_field_byte("struct s { int x : 12; char y : 6; };"), 2);
3172 assert_eq!(
3173 bit_field_byte("struct s { int x : 12; char y : 6; } __attribute__((packed));"),
3174 1
3175 );
3176 assert_eq!(
3177 bit_field_byte("struct s { int x : 12; __attribute__((packed)) char y : 6; };"),
3178 1
3179 );
3180 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { int x : 12; char y : 6; };"), 1);
3181 assert_eq!(bit_field_byte("struct s { char x; int y : 30; };"), 4);
3183 assert_eq!(bit_field_byte("struct s { char x; int y : 30; } __attribute__((packed));"), 1);
3184 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { char x; int y : 30; };"), 1);
3186 assert_eq!(bit_field_byte("#pragma pack(2)\nstruct s { char x; int y : 30; };"), 1);
3187 }
3188
3189 fn bit_field_byte(record: &str) -> u64 {
3191 let source = format!("{record}\nint f(struct s *p) {{ return p->y; }}\n");
3192 let body = body(&source);
3193 let Some((before, _)) = body.split_once("ptr_add") else { return 0 };
3194 let (_, constant) = before.rsplit_once("iconst.i64 ").expect("an offset constant");
3195 constant.lines().next().expect("a line").trim().parse().expect("a byte offset")
3196 }
3197
3198 #[test]
3204 fn an_attribute_among_the_specifiers_is_kept_beside_the_ones_written_in_front() {
3205 tast(concat!(
3206 "struct a { char c; __attribute__((aligned(8))) int i; };\n",
3207 "_Static_assert(sizeof(struct a) == 16 && _Alignof(struct a) == 8, \"a\");\n",
3208 "_Static_assert(__builtin_offsetof(struct a, i) == 8, \"a.i\");\n",
3209 "struct b { char c; __attribute__((packed)) int i; };\n",
3210 "_Static_assert(sizeof(struct b) == 5 && _Alignof(struct b) == 1, \"b\");\n",
3211 "_Static_assert(__builtin_offsetof(struct b, i) == 1, \"b.i\");\n",
3212 "typedef struct { char c; int i; } __attribute__((packed)) c;\n",
3213 "_Static_assert(sizeof(c) == 5 && _Alignof(c) == 1, \"c\");\n",
3214 ));
3215 }
3216
3217 #[test]
3223 fn pragma_pack_caps_every_member_and_is_read_where_the_body_closes() {
3224 tast(concat!(
3225 "#pragma pack(1)\n",
3226 "struct A { char c; int i; };\n",
3227 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
3228 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
3229 "#pragma pack()\n",
3230 "struct B { char c; int i; };\n",
3231 "_Static_assert(sizeof(struct B) == 8 && _Alignof(struct B) == 4, \"B\");\n",
3232 "#pragma pack(2)\n",
3233 "struct C { char c; int i; double d; };\n",
3234 "_Static_assert(sizeof(struct C) == 14 && _Alignof(struct C) == 2, \"C\");\n",
3235 "_Static_assert(__builtin_offsetof(struct C, d) == 6, \"C.d\");\n",
3236 "struct K { char c; int i __attribute__((aligned(8))); };\n",
3238 "_Static_assert(sizeof(struct K) == 6 && _Alignof(struct K) == 2, \"K\");\n",
3239 "_Static_assert(__builtin_offsetof(struct K, i) == 2, \"K.i\");\n",
3240 "struct J { char c; int i; } __attribute__((aligned(8)));\n",
3242 "_Static_assert(sizeof(struct J) == 8 && _Alignof(struct J) == 8, \"J\");\n",
3243 "#pragma pack()\n",
3244 "#pragma pack(push, 1)\n",
3245 "struct D { char c; short s; };\n",
3246 "_Static_assert(sizeof(struct D) == 3 && _Alignof(struct D) == 1, \"D\");\n",
3247 "#pragma pack(pop)\n",
3248 "struct E { char c; short s; };\n",
3249 "_Static_assert(sizeof(struct E) == 4 && _Alignof(struct E) == 2, \"E\");\n",
3250 "struct H { char c;\n",
3252 "#pragma pack(1)\n",
3253 " int i; };\n",
3254 "_Static_assert(sizeof(struct H) == 5 && _Alignof(struct H) == 1, \"H\");\n",
3255 "#pragma pack(1)\n",
3256 "struct I { char c;\n",
3257 "#pragma pack()\n",
3258 " int i; };\n",
3259 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
3260 "#pragma pack()\n",
3261 "#pragma pack(push, 8)\n",
3263 "#pragma pack(push, 1)\n",
3264 "struct P { char c; int i; };\n",
3265 "_Static_assert(sizeof(struct P) == 5 && _Alignof(struct P) == 1, \"P\");\n",
3266 "#pragma pack(pop)\n",
3267 "struct Q { char c; int i; };\n",
3268 "_Static_assert(sizeof(struct Q) == 8 && _Alignof(struct Q) == 4, \"Q\");\n",
3269 "#pragma pack(pop)\n",
3270 "#pragma pack(16)\n",
3272 "struct R { char c; int i; };\n",
3273 "_Static_assert(sizeof(struct R) == 8 && _Alignof(struct R) == 4, \"R\");\n",
3274 "#pragma pack()\n",
3275 "#pragma pack(1)\n",
3276 "struct S { char c; int i : 5; int j : 20; };\n",
3277 "_Static_assert(sizeof(struct S) == 5 && _Alignof(struct S) == 1, \"S\");\n",
3278 "union T { char c; int i; };\n",
3279 "_Static_assert(sizeof(union T) == 4 && _Alignof(union T) == 1, \"T\");\n",
3280 "#pragma pack()\n",
3281 ));
3282 }
3283
3284 #[test]
3288 fn a_pack_line_that_is_not_one_is_reported_in_the_words_gcc_uses() {
3289 let result = run(
3290 &options(),
3291 concat!(
3292 "#pragma pack 4\n",
3293 "#pragma pack(pop)\n",
3294 "#pragma pack(3)\n",
3295 "#pragma pack(1) junk\n",
3296 "#pragma pack(push, 1\n",
3297 "#pragma pack(x)\n",
3298 "#pragma pack(0)\n",
3301 "#pragma pack(push)\n",
3302 "struct s { char c; int i; };\n",
3303 "#pragma pack(pop)\n",
3304 "#pragma pack(pop, foo)\n",
3305 ),
3306 );
3307 let expected = [
3308 "missing `(` after `#pragma pack` - ignored",
3309 "`#pragma pack (pop)` encountered without matching `#pragma pack (push)`",
3310 "alignment must be a small power of two, not 3",
3311 "junk at end of `#pragma pack`",
3312 "malformed `#pragma pack(push[, id][, <n>])` - ignored",
3313 "unknown action `x` for `#pragma pack` - ignored",
3314 "`#pragma pack(pop, foo)` encountered without matching `#pragma pack(push, foo)`",
3315 ];
3316 assert_eq!(result.messages.len(), expected.len(), "{:?}", result.messages);
3317 for (message, want) in result.messages.iter().zip(expected) {
3318 assert!(message.contains(want), "expected {want:?} in {message:?}");
3319 }
3320 }
3321
3322 #[test]
3329 fn a_declaration_behind_an_empty_macro_is_not_eaten_by_the_pragma_above_it() {
3330 let result = run(
3331 &options(),
3332 concat!(
3333 "#pragma pack(push, 1)\n",
3334 "#pragma pack(pop)\n",
3335 "#define API\n",
3336 "API const char version[] = \"3.53.4\";\n",
3337 "const char *get(void) { return version; }\n",
3338 ),
3339 );
3340 assert!(result.messages.is_empty(), "{:?}", result.messages);
3341 }
3342
3343 #[test]
3347 fn the_wide_integer_answers_to_all_three_of_its_names() {
3348 let text = tast("__uint128_t a; __int128_t b; unsigned __int128 c;\n");
3349 assert!(text.contains("decl #0 a : unsigned __int128"), "{text}");
3350 assert!(text.contains("decl #1 b : __int128"), "{text}");
3351 assert!(text.contains("decl #2 c : unsigned __int128"), "{text}");
3352 }
3353
3354 #[test]
3355 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
3356 let text = tast("long f(int a, long b) { return a + b; }\n");
3360 assert!(text.contains("convert arithmetic"), "{text}");
3361 }
3362
3363 #[test]
3364 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
3365 for source in [
3366 "#error stop\n",
3367 "int f(void) { return 1 + ; }\n",
3368 "int f(void) { return undeclared; }\n",
3369 ] {
3370 let result = run(&options(), source);
3371 assert!(result.failed(), "expected this to fail:\n{source}");
3372 assert!(
3373 result.text().is_empty(),
3374 "a file that did not compile wrote a tree:\n{source}"
3375 );
3376 }
3377 }
3378
3379 #[test]
3380 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
3381 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
3385 assert_eq!(result.errors, 1, "{:?}", result.messages);
3386 }
3387
3388 #[test]
3389 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
3390 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
3394 assert_eq!(result.errors, 1, "{:?}", result.messages);
3395 }
3396
3397 #[test]
3398 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
3399 let source = "int f(void) { char c = 300; return c; }\n";
3400 let plain = run(&options(), source);
3401 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
3402 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
3403 assert!(!plain.text().is_empty(), "a warning is not a reason to write nothing");
3404
3405 let mut opts = options();
3406 opts.warnings_are_errors = true;
3407 let strict = run(&opts, source);
3408 assert!(strict.failed());
3409 assert!(strict.text().is_empty(), "and under -Werror it is a reason to write nothing");
3410 for message in &strict.messages {
3411 assert!(!message.contains("warning:"), "{message}");
3412 }
3413 }
3414
3415 #[test]
3416 fn w_drops_the_warning_before_werror_can_promote_it() {
3417 let source = "int f(void) { char c = 300; return c; }\n";
3418 let mut opts = options();
3419 opts.warnings = false;
3420 let quiet = run(&opts, source);
3421 assert_eq!(quiet.messages, Vec::<String>::new());
3422 assert_eq!(quiet.errors, 0);
3423 assert!(!quiet.text().is_empty(), "and the file still compiles");
3424
3425 opts.warnings_are_errors = true;
3428 let both = run(&opts, source);
3429 assert_eq!(both.messages, Vec::<String>::new());
3430 assert!(!both.failed(), "-w -Werror is not an error about a warning nobody saw");
3431 }
3432
3433 #[test]
3434 fn the_dialect_reaches_the_keywords_and_the_checking() {
3435 let source = "typeof(1) x;\n";
3438 let mut opts = options();
3439 opts.std = Std::C23;
3440 opts.gnu_extensions = false;
3441 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
3442
3443 opts.std = Std::C17;
3444 assert!(run(&opts, source).failed());
3445 }
3446
3447 #[test]
3448 fn asking_for_a_kind_that_is_not_written_yet_runs_the_front_end_and_writes_nothing() {
3449 let mut opts = options();
3450 opts.emit = EmitKind::Object;
3451 let result = run(&opts, "int x = 1;\n");
3452 assert!(!result.failed(), "{:?}", result.messages);
3453 assert!(result.text().is_empty());
3454 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
3457 }
3458
3459 fn mir(source: &str) -> String {
3461 let mut opts = options();
3462 opts.emit = EmitKind::MirFinal;
3463 let result = run(&opts, source);
3464 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3465 result.text().to_owned()
3466 }
3467
3468 #[test]
3474 fn a_function_goes_from_c_to_instructions_with_real_registers_in_them() {
3475 let text = mir("int add(int a, int b) { return a + b; }\n");
3476 assert!(text.starts_with("mfunc @add {"), "{text}");
3477 assert!(text.contains("x64.add_rr_32"), "{text}");
3478 assert!(text.contains("x64.ret"), "{text}");
3479 assert!(!text.contains('%'), "{text}");
3482 }
3483
3484 #[test]
3486 fn a_function_with_no_body_produces_no_machine_function() {
3487 let text = mir("int g(int);\nint f(int a) { return g(a); }\n");
3488 assert_eq!(text.matches("mfunc @").count(), 1, "{text}");
3489 assert!(text.contains("mfunc @f {"), "{text}");
3490 assert!(text.contains("x64.call"), "{text}");
3491 }
3492
3493 #[test]
3495 fn every_definition_in_the_file_is_generated_and_they_keep_their_order() {
3496 let text = mir("int a(int x) { return x; }\nint b(int x) { return x; }\n");
3497 let first = text.find("mfunc @a").expect("the first function");
3498 let second = text.find("mfunc @b").expect("the second function");
3499 assert!(first < second, "{text}");
3500 }
3501
3502 #[test]
3504 fn the_target_decides_which_convention_the_generated_code_follows() {
3505 let mut opts = options();
3506 opts.emit = EmitKind::MirFinal;
3507 let linux = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
3508 assert!(linux.contains("$rdi"), "{linux}");
3509
3510 opts.target = "x86_64-pc-windows-msvc".parse::<Triple>().unwrap();
3511 let windows = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
3512 assert!(windows.contains("$rcx"), "{windows}");
3513 assert!(!windows.contains("$rdi"), "{windows}");
3514 }
3515
3516 #[test]
3524 fn a_tagged_member_with_no_name_is_a_member_on_windows_and_nothing_on_linux() {
3525 let source = concat!(
3526 "struct S { union U { int i; void *p; }; unsigned long tymed; };\n",
3527 "int size(void) { return sizeof(struct S); }\n",
3528 "int f(struct S *s) { s->i = 1; return s->i; }\n",
3529 );
3530
3531 let mut opts = options();
3532 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3533 let windows = run(&opts, source);
3534 assert!(windows.messages.is_empty(), "{:?}", windows.messages);
3535
3536 let linux = run(&options(), source);
3537 assert_eq!(linux.messages.len(), 3, "{:?}", linux.messages);
3538 assert!(linux.messages[0].contains("does not declare anything"), "{:?}", linux.messages);
3539
3540 let mut opts = options();
3543 opts.ms_extensions = Some(true);
3544 let asked = run(&opts, source);
3545 assert!(asked.messages.is_empty(), "{:?}", asked.messages);
3546 }
3547
3548 #[test]
3550 fn a_target_this_has_no_back_end_for_is_reported_rather_than_generated() {
3551 let mut opts = options();
3552 opts.emit = EmitKind::MirFinal;
3553 opts.target = "riscv64-unknown-linux-gnu".parse::<Triple>().unwrap();
3554 let result = run(&opts, "int f(int a) { return a; }\n");
3555 assert!(result.failed());
3556 assert!(result.messages[0].contains("no back end for riscv64"), "{:?}", result.messages);
3557 assert!(result.text().is_empty());
3558 }
3559
3560 #[test]
3563 fn an_aarch64_target_is_written_as_aarch64_assembly() {
3564 let mut opts = options();
3565 opts.emit = EmitKind::Asm;
3566 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3567 let source = "int g(int);\nint f(int a, int b) { return g(a) + b; }\n";
3568 let result = run(&opts, source);
3569 assert!(!result.failed(), "{:?}", result.messages);
3570 let text = result.text();
3571 for line in ["stp x29, x30, [sp, #-16]!", "mov x29, sp", "bl g", "ldp x29, x30, [sp], #16"]
3572 {
3573 assert!(text.contains(line), "{line} is not in\n{text}");
3574 }
3575 assert!(!text.contains('%'), "{text}");
3576 }
3577
3578 #[test]
3581 fn an_aarch64_target_reaches_an_object_file() {
3582 let mut opts = options();
3583 opts.emit = EmitKind::Object;
3584 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3585 let source = concat!(
3586 "int g(int);\n",
3587 "int table[4] = {1, 2, 3, 4};\n",
3588 "int f(int a, int b) { return g(a) + table[b & 3]; }\n",
3589 );
3590 let result = run(&opts, source);
3591 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3592 let bytes = match result.artifact {
3593 Artifact::Object { bytes, defines } => {
3594 assert_eq!(defines, ["f", "table"]);
3595 bytes
3596 }
3597 other => panic!("expected an object, got {other:?}"),
3598 };
3599 assert_eq!(&bytes[..4], b"\x7fELF");
3600 assert_eq!(&bytes[18..20], &183u16.to_le_bytes(), "EM_AARCH64");
3601
3602 opts.debug_info = true;
3605 let result = run(&opts, source);
3606 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3607 let bytes = match result.artifact {
3608 Artifact::Object { bytes, .. } => bytes,
3609 other => panic!("expected an object, got {other:?}"),
3610 };
3611 let has = |name: &[u8]| bytes.windows(name.len()).any(|at| at == name);
3612 assert!(has(b".debug_line\0") && has(b".debug_info\0"));
3613 assert!(!has(b"rucc_row"), "a row label reached the symbol table");
3614 }
3615
3616 #[test]
3619 fn the_aarch64_vector_type_names_are_declared_on_that_target_and_nowhere_else() {
3620 let mut opts = options();
3621 opts.emit = EmitKind::Asm;
3622 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3623 let source = "typedef __Float32x4_t f4;\n__SVFloat32_t sv(__SVFloat32_t, __SVBool_t);\n\
3624 int n = sizeof(f4) + sizeof(__Int8x8_t);\n\
3625 int f(f4 v) { int __Uint8x16_t = 3; return v[1] + __Uint8x16_t; }\n";
3626 let result = run(&opts, source);
3627 assert!(!result.failed(), "{:?}", result.messages);
3628 assert!(result.text().contains(".long\t24"), "{}", result.text());
3629 opts.target = "x86_64-unknown-linux-gnu".parse::<Triple>().unwrap();
3630 let result = run(&opts, "typedef __Float32x4_t f4;\n");
3631 assert!(result.failed());
3632 let result = run(&opts, "int __Float32x4_t = 1;\n");
3633 assert!(!result.failed(), "{:?}", result.messages);
3634 }
3635
3636 #[test]
3639 fn an_aarch64_result_in_memory_is_reached_through_x8() {
3640 let mut opts = options();
3641 opts.emit = EmitKind::Asm;
3642 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3643 let source = "struct big { long a, b, c; };\nstruct big make(long v);\n\
3644 long f(long v) { return make(v).c; }\n\
3645 struct big g(long v) { struct big b = { v, v, v }; return b; }\n";
3646 let result = run(&opts, source);
3647 assert!(!result.failed(), "{:?}", result.messages);
3648 let text = result.text();
3649 assert!(text.contains("x8"), "{text}");
3650 assert!(text.contains("bl make"), "{text}");
3651 }
3652
3653 #[test]
3656 fn an_aarch64_remainder_is_a_division_and_a_multiply_subtract() {
3657 let mut opts = options();
3658 opts.emit = EmitKind::Asm;
3659 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3660 let source = "int s(int a, int b) { return a % b; }\n\
3661 unsigned long u(unsigned long a, unsigned long b) { return a % b; }\n";
3662 let result = run(&opts, source);
3663 assert!(!result.failed(), "{:?}", result.messages);
3664 let text = result.text();
3665 let at = |what: &str| text.find(what).unwrap_or_else(|| panic!("{what} is not in\n{text}"));
3666 assert!(at("sdiv w") < at("msub w"), "{text}");
3667 assert!(at("udiv x") < at("msub x"), "{text}");
3668 }
3669
3670 #[test]
3673 fn an_aarch64_jump_table_is_reached_with_adr() {
3674 let mut opts = options();
3675 opts.emit = EmitKind::Asm;
3676 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3677 let source = "int f(int x) { switch (x) { case 0: return 10; case 1: return 21; \
3678 case 2: return 32; case 3: return 43; case 4: return 54; case 5: return 65; \
3679 case 6: return 76; case 7: return 87; case 8: return 98; case 9: return 9; \
3680 case 10: return 19; case 11: return 29; default: return 0; } }\n";
3681 let result = run(&opts, source);
3682 assert!(!result.failed(), "{:?}", result.messages);
3683 let text = result.text();
3684 let at = |what: &str| text.find(what).unwrap_or_else(|| panic!("{what} is not in\n{text}"));
3685 assert!(at("adr x") < at("ldrsw x"), "{text}");
3686 assert!(at("ldrsw x") < at("br x"), "{text}");
3687 assert!(text.contains("_j0:"), "{text}");
3688 assert!(text.contains(".long"), "{text}");
3689 }
3690
3691 #[test]
3695 fn an_aarch64_va_start_writes_the_five_fields_of_its_list() {
3696 let mut opts = options();
3697 opts.emit = EmitKind::Asm;
3698 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3699 let source = "typedef __builtin_va_list va_list;\n\
3700 int f(int n, ...) { va_list ap; __builtin_va_start(ap, n); \
3701 int x = __builtin_va_arg(ap, int); double d = __builtin_va_arg(ap, double); \
3702 __builtin_va_end(ap); return x + (int)d; }\n";
3703 let result = run(&opts, source);
3704 assert!(!result.failed(), "{:?}", result.messages);
3705 let text = result.text();
3706 assert!(text.contains("#-56"), "{text}");
3707 assert!(text.contains("#-128"), "{text}");
3708 assert!(text.contains("#24]"), "{text}");
3709 assert!(text.contains("#28]"), "{text}");
3710 assert!(text.contains("str q"), "{text}");
3711 }
3712
3713 #[test]
3716 fn an_aarch64_long_double_is_a_quad_in_a_vector_register() {
3717 let mut opts = options();
3718 opts.emit = EmitKind::Asm;
3719 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3720 let source = "void f(long double *p, long double x) { *p = *p + x; }\n";
3721 let result = run(&opts, source);
3722 assert!(!result.failed(), "{:?}", result.messages);
3723 let text = result.text();
3724 assert!(text.contains("ldr q"), "{text}");
3725 assert!(text.contains("str q"), "{text}");
3726 assert!(text.contains("__addtf3"), "{text}");
3727 }
3728
3729 #[test]
3732 fn an_aarch64_thread_local_is_reached_through_tpidr_el0() {
3733 let mut opts = options();
3734 opts.emit = EmitKind::Asm;
3735 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3736 let source = "__thread int n;\nint *f(void) { return &n; }\n\
3737 void *g(void) { return __builtin_thread_pointer(); }\n";
3738 let result = run(&opts, source);
3739 assert!(!result.failed(), "{:?}", result.messages);
3740 let text = result.text();
3741 assert!(text.contains(":gottprel:n"), "{text}");
3742 assert!(text.contains(":gottprel_lo12:n]"), "{text}");
3743 assert_eq!(text.matches("mrs x").count(), 2, "{text}");
3744 assert!(text.contains("tpidr_el0"), "{text}");
3745 }
3746
3747 #[test]
3750 fn a_darwin_thread_local_is_reached_through_its_descriptor() {
3751 let source = "__thread int n = 5;\nint *f(void) { return &n; }\n";
3752 for (triple, wanted) in [
3753 ("aarch64-apple-darwin", &["_n@TLVPPAGE\n", "_n@TLVPPAGEOFF]\n", "\tblr x"][..]),
3754 ("x86_64-apple-darwin", &["_n@TLVP(%rip), %rdi\n", "\tcall\t*%"][..]),
3755 ] {
3756 let mut opts = options();
3757 opts.emit = EmitKind::Asm;
3758 opts.target = triple.parse::<Triple>().unwrap();
3759 let result = run(&opts, source);
3760 assert!(!result.failed(), "{triple}: {:?}", result.messages);
3761 let text = result.text();
3762 for want in wanted {
3763 assert!(text.contains(want), "{triple} wanted {want:?}:\n{text}");
3764 }
3765 assert!(text.contains("\n_n:\n\t.quad\t__tlv_bootstrap\n"), "{text}");
3766 assert!(!text.contains("tpidr_el0") && !text.contains("%fs"), "{text}");
3767 }
3768 }
3769
3770 #[test]
3772 fn the_thread_pointer_is_refused_on_darwin() {
3773 let mut opts = options();
3774 opts.emit = EmitKind::Asm;
3775 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3776 let result = run(&opts, "void *f(void) { return __builtin_thread_pointer(); }\n");
3777 assert!(result.failed());
3778 assert!(result.messages[0].contains("thread pointer"), "{:?}", result.messages);
3779 }
3780
3781 #[test]
3784 fn a_darwin_variadic_definition_saves_nothing_and_walks_the_stack() {
3785 let mut opts = options();
3786 opts.emit = EmitKind::Asm;
3787 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3788 let source = "int f(int n, ...) { __builtin_va_list ap; __builtin_va_start(ap, n);\n\
3789 int r = __builtin_va_arg(ap, int); __builtin_va_end(ap); return r; }\n";
3790 let result = run(&opts, source);
3791 assert!(!result.failed(), "{:?}", result.messages);
3792 let text = result.text();
3793 assert!(!text.contains("str q"), "{text}");
3794 assert!(!text.contains("x7"), "{text}");
3795 }
3796
3797 #[test]
3800 fn a_darwin_call_puts_its_variadic_arguments_in_memory() {
3801 let mut opts = options();
3802 opts.emit = EmitKind::Asm;
3803 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3804 let source = "int printf(const char *, ...);\n\
3805 int g(double x) { return printf(\"%d %f\", 7, x); }\n";
3806 let result = run(&opts, source);
3807 assert!(!result.failed(), "{:?}", result.messages);
3808 let text = result.text();
3809 assert!(text.contains("str d0, [sp, #8]"), "{text}");
3810 }
3811
3812 #[test]
3816 fn a_darwin_listing_is_one_apples_assembler_reads() {
3817 let mut opts = options();
3818 opts.emit = EmitKind::Asm;
3819 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3820 let source = "extern int ext;\n\
3821 int g[4];\n\
3822 int f(int i) { return g[i] + ext; }\n";
3823 let result = run(&opts, source);
3824 assert!(!result.failed(), "{:?}", result.messages);
3825 let text = result.text();
3826 assert!(text.contains(", _g@PAGE\n"), "{text}");
3827 assert!(text.contains(", _g@PAGEOFF\n"), "{text}");
3828 assert!(text.contains(", _ext@GOTPAGE\n"), "{text}");
3829 assert!(text.contains(", _ext@GOTPAGEOFF]\n"), "{text}");
3830 assert!(!text.contains(":lo12:"), "{text}");
3831 assert!(text.contains("\t.globl\t_g\n\t.zerofill\t__DATA,__bss,_g,16,2\n"), "{text}");
3832 }
3833
3834 #[test]
3840 fn a_signed_char_on_aarch64_is_widened_with_its_sign_before_it_is_added_to() {
3841 for target in ["aarch64-linux-gnu", "aarch64-apple-darwin"] {
3842 let mut opts = options();
3843 opts.emit = EmitKind::Asm;
3844 opts.target = target.parse::<Triple>().unwrap();
3845 let source = "int f(signed char *p) { return *p + 1; }\n\
3846 unsigned g(unsigned short *p) { return *p + 1u; }\n";
3847 let result = run(&opts, source);
3848 assert!(!result.failed(), "{:?}", result.messages);
3849 let text = result.text();
3850 let signed = text.contains("\tsxtb w") || text.contains("\tldrsb w");
3851 assert!(signed, "{target}: {text}");
3852 }
3853 }
3854
3855 #[test]
3867 fn a_construct_the_back_end_cannot_reach_yet_is_reported_against_its_function() {
3868 let mut opts = options();
3869 opts.emit = EmitKind::MirFinal;
3870 let source = "void a(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
3871 s; s.x = 1; v[0] = s.x; }\n\
3872 void b(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
3873 s; s.x = 1; v[0] = s.x; }\n";
3874 let result = run(&opts, source);
3875 assert!(result.failed());
3876 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
3877 assert!(result.messages[0].contains("cannot generate code for 'a'"), "{:?}", result);
3878 assert!(result.messages[0].contains("wants more alignment"), "{:?}", result);
3879 assert!(result.messages[1].contains("cannot generate code for 'b'"), "{:?}", result);
3880 assert!(result.text().is_empty());
3881 }
3882
3883 #[test]
3891 fn a_variable_length_array_walks_its_pages_where_every_page_of_the_frame_is_to_be_touched() {
3892 let mut opts = options();
3893 opts.emit = EmitKind::MirFinal;
3894 let source = "void a(int n) { int v[n]; v[0] = 1; }\n";
3895 let plain = run(&opts, source);
3896 assert!(!plain.failed(), "{:?}", plain.messages);
3897 assert!(!plain.text().contains("cmp_set_a_64"), "{}", plain.text());
3898
3899 opts.stack_clash = true;
3900 let result = run(&opts, source);
3901 assert!(!result.failed(), "{:?}", result.messages);
3902 assert!(result.text().contains("cmp_set_a_64"), "{}", result.text());
3903 assert!(result.text().contains("or_mi_8"), "{}", result.text());
3904 }
3905
3906 #[test]
3916 fn a_function_that_keeps_a_frame_pointer_on_windows_reaches_an_object_file() {
3917 let mut opts = options();
3918 opts.emit = EmitKind::Object;
3919 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3920 let source = concat!(
3921 "void use(void *p);\n",
3922 "void array(int n) { int v[n]; v[0] = 1; use(v); }\n",
3923 "void taken(unsigned long n) { use(__builtin_alloca(n)); }\n",
3924 );
3925 let result = run(&opts, source);
3926 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3927 let bytes = match result.artifact {
3928 Artifact::Object { bytes, .. } => bytes,
3929 other => panic!("expected an object, got {other:?}"),
3930 };
3931 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3932
3933 let mut opts = options();
3936 opts.emit = EmitKind::Object;
3937 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3938 }
3939
3940 #[test]
3951 fn the_address_of_a_function_this_file_only_declares_reaches_a_windows_object() {
3952 let source = concat!(
3953 "void other(void *p);\n",
3954 "void takes(void (*f)(void *));\n",
3955 "void (*held)(void *);\n",
3956 "void pass(void) { takes(other); }\n",
3957 "void keep(void) { held = other; }\n",
3958 "void call(void) { other(0); }\n",
3959 );
3960 let mut opts = options();
3961 opts.emit = EmitKind::Object;
3962 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3963 let result = run(&opts, source);
3964 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3965 let bytes = match result.artifact {
3966 Artifact::Object { bytes, .. } => bytes,
3967 other => panic!("expected an object, got {other:?}"),
3968 };
3969 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3970
3971 let mut opts = options();
3974 opts.emit = EmitKind::Object;
3975 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3976 }
3977
3978 #[test]
3992 fn an_opcode_with_no_name_in_the_rule_language_is_named_by_its_own_spelling() {
3993 let mut opts = options();
3994 opts.emit = EmitKind::MirFinal;
3995 let source =
3996 "long double f(int a) {\n __int128 wide = a;\n return (long double) wide;\n}\n";
3997 let result = run(&opts, source);
3998 assert!(result.failed());
3999 assert!(
4000 result.messages[0].contains("no rule lowers a `sext` producing a `i128`"),
4001 "{result:?}"
4002 );
4003 assert!(result.messages[0].contains(":2:"), "the line the widening is on: {result:?}");
4004 assert!(!result.messages[0].contains("this instruction"), "{result:?}");
4005 }
4006
4007 #[test]
4009 fn the_note_on_unfinished_work_points_at_the_issues_rather_than_at_the_plan() {
4010 let mut opts = options();
4011 opts.emit = EmitKind::MirFinal;
4012 let source = "long double f(int a) { __int128 wide = a; return (long double) wide; }\n";
4013 let result = run(&opts, source);
4014 assert!(result.failed());
4015 let note = result.messages.iter().find(|line| line.contains("note:")).expect("a note");
4016 assert!(note.contains("https://github.com/tamnd/rucc/issues"), "{note}");
4017 assert!(!note.contains("spec/17-milestones.md"), "{note}");
4018 }
4019
4020 #[test]
4022 fn the_frame_flags_on_the_command_line_reach_the_generated_frame() {
4023 let source = "int f(int a) { return a; }\n";
4024 assert!(!mir(source).contains("$rbp"), "a leaf needs no frame pointer when told so");
4025
4026 let mut opts = options();
4027 opts.emit = EmitKind::MirFinal;
4028 opts.frame_pointer = Some(true);
4029 let kept = run(&opts, source).text().to_owned();
4030 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
4031
4032 opts.frame_pointer = None;
4034 let kept = run(&opts, source).text().to_owned();
4035 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
4036 }
4037
4038 fn asm(source: &str) -> String {
4040 let mut opts = options();
4041 opts.emit = EmitKind::Asm;
4042 let result = run(&opts, source);
4043 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4044 result.text().to_owned()
4045 }
4046
4047 #[test]
4054 fn a_function_goes_from_c_to_assembly_an_assembler_would_take() {
4055 let text = asm("int add(int a, int b) { return a + b; }\n");
4056 assert!(text.contains("\t.globl\tadd\n"), "{text}");
4057 assert!(text.contains("\t.type\tadd, @function\n"), "{text}");
4058 assert!(text.contains("\nadd:\n"), "{text}");
4059 assert!(text.contains("\taddl\t"), "{text}");
4060 assert!(text.contains("\tret\n"), "{text}");
4061 assert!(text.contains("\t.size\tadd, .-add\n"), "{text}");
4062 assert!(text.contains(".note.GNU-stack"), "{text}");
4065 }
4066
4067 #[test]
4073 fn a_call_through_a_function_pointer_goes_through_the_register_it_is_in() {
4074 let text = asm("int g(int);\nint f(int (*p)(int), int a) { return p(a) + g(a); }\n");
4075 assert!(text.contains("\tcall\t*%"), "{text}");
4076 assert!(text.contains("\tcall\tg\n"), "{text}");
4077 assert!(text.contains("%rdi"), "{text}");
4081 }
4082
4083 #[test]
4087 fn the_address_of_a_global_is_read_from_the_instruction_pointer() {
4088 let text = asm("extern int counter;\nint f(void) { return counter; }\n");
4089 assert!(text.contains("\tmovl\tcounter(%rip), %eax\n"), "{text}");
4090 }
4091
4092 #[test]
4101 fn a_branch_on_a_comparison_jumps_on_the_opposite_of_what_it_compared() {
4102 let arms = "return 1; return 2;";
4103 let signed = [("==", "jne"), ("!=", "je"), ("<", "jge"), ("<=", "jg"), (">", "jle")];
4104 for (operator, jump) in signed.into_iter().chain([(">=", "jl")]) {
4105 let text = asm(&format!("int f(int a, int b) {{ if (a {operator} b) {arms} }}\n"));
4106 assert!(
4107 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
4108 "{operator}: {text}"
4109 );
4110 assert!(!text.contains("\tset"), "{operator}: {text}");
4111 assert!(!text.contains("\ttest"), "{operator}: {text}");
4112 }
4113 let unsigned = [("<", "jae"), ("<=", "ja"), (">", "jbe"), (">=", "jb")];
4114 for (operator, jump) in unsigned {
4115 let source =
4116 format!("int f(unsigned a, unsigned b) {{ if (a {operator} b) {arms} }}\n");
4117 let text = asm(&source);
4118 assert!(
4119 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
4120 "{operator}: {text}"
4121 );
4122 }
4123
4124 let text = asm("int f(int a) { if (a < 7) return 1; return 2; }\n");
4127 assert!(text.contains("\tcmpl\t$7, %edi\n\tjge\t"), "{text}");
4128 }
4129
4130 #[test]
4136 fn a_comparison_whose_answer_the_program_wanted_still_writes_a_byte() {
4137 let text = asm("int f(int a, int b) { return a < b; }\n");
4138 assert!(text.contains("\tsetl\t"), "{text}");
4139 }
4140
4141 fn optimized(source: &str) -> String {
4143 let mut opts = options();
4144 opts.emit = EmitKind::Asm;
4145 opts.opt_level = rucc_session::OptLevel::O2;
4146 let result = run(&opts, source);
4147 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4148 result.text().to_owned()
4149 }
4150
4151 #[test]
4153 fn opt_info_says_what_each_switch_became_and_a_forced_shape_is_what_it_says() {
4154 let arms: String = (0..40)
4155 .map(|k| format!("case {}: return g({k});", k * 17))
4156 .collect::<Vec<_>>()
4157 .join(" ");
4158 let source = format!("int g(int);\nint f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n");
4159 let said = |shape: Option<&str>| {
4160 let mut opts = options();
4161 opts.emit = EmitKind::Asm;
4162 opts.opt_level = rucc_session::OptLevel::O2;
4163 opts.opt_info = vec![String::new()];
4164 opts.switch_shape = shape.map(str::to_owned);
4165 let result = run(&opts, &source);
4166 assert_eq!(result.messages, Vec::<String>::new());
4167 let lines: Vec<String> = result
4168 .remarks
4169 .lines()
4170 .filter(|line| line.contains("[switch-lowering]"))
4171 .map(str::to_owned)
4172 .collect();
4173 assert_eq!(lines.len(), 1, "{}", result.remarks);
4174 lines[0].clone()
4175 };
4176 assert!(said(None).contains(": f: optimized: switch of 40 cases lowered as a tree;"));
4177 assert!(said(Some("table")).contains("lowered as a table;"));
4178 assert!(said(Some("walk")).contains("lowered as a walk;"));
4179 }
4180
4181 #[test]
4191 fn a_switch_whose_arms_are_a_function_of_the_label_is_a_range_check_and_arithmetic() {
4192 let arms: String =
4193 (0..16).map(|k| format!("case {k}: return {};", k + 1)).collect::<Vec<_>>().join(" ");
4194 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
4195 assert!(text.contains("\tcmpl\t$15, %edi\n\tja\t"), "{text}");
4196 assert!(text.contains("\taddl\t$1, %edi"), "{text}");
4197 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
4198 }
4199
4200 #[test]
4208 fn a_dense_switch_whose_arms_are_not_a_line_is_a_load_from_a_table() {
4209 let arms: String = (0..16)
4210 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
4211 .collect::<Vec<_>>()
4212 .join(" ");
4213 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
4214 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
4215 assert!(!text.contains("\tjmp\t*"), "{text}");
4216 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
4217 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
4218 let section = text[..text.find("CSWTCH.0:").unwrap_or(0)].rfind("\t.section\t.rodata");
4219 assert!(section.is_some(), "{text}");
4220 assert_eq!(table.matches("\t.long\t").count(), 16, "{text}");
4221 assert!(table.contains("\t.long\t100\n"), "{text}");
4222 }
4223
4224 #[test]
4230 fn a_switch_whose_arms_give_strings_is_a_table_of_how_far_away_they_are() {
4231 let text = optimized(
4232 "const char *f(int k) { switch (k) { case 0: return \"zero\"; \
4233 case 1: return \"one\"; case 2: return \"two\"; case 3: return \"three\"; } \
4234 return \"many\"; }\n",
4235 );
4236 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
4237 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
4238 assert!(!text.contains(".data.rel.ro"), "{text}");
4239 let at = text.find("CSWTCH.0:").expect("the table is in the output");
4240 assert!(text[..at].rfind("\t.section\t.rodata").is_some(), "{text}");
4241 let table = &text[at..];
4242 assert_eq!(table.matches(" - .\n").count(), 4, "{text}");
4243 assert!(table.contains("\t.long\t.Lstr.1+4 - .\n"), "{text}");
4244 }
4245
4246 #[test]
4252 fn a_table_at_os_has_cells_as_narrow_as_its_answers() {
4253 let arms: String = (0..16)
4254 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
4255 .collect::<Vec<_>>()
4256 .join(" ");
4257 let mut opts = options();
4258 opts.emit = EmitKind::Asm;
4259 opts.opt_level = rucc_session::OptLevel::Os;
4260 let result = run(&opts, &format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
4261 assert_eq!(result.messages, Vec::<String>::new());
4262 let text = result.text();
4263 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
4264 assert_eq!(table.matches("\t.byte\t").count(), 16, "{text}");
4265 assert!(text.contains("\tmovsbl\t"), "{text}");
4266 }
4267
4268 #[test]
4275 fn a_table_that_covers_its_operand_has_no_range_check() {
4276 let text = optimized(
4277 "int f(unsigned x) { switch (x & 3) { case 0: return 5; case 1: return 9; \
4278 case 2: return 2; case 3: return 7; } return -1; }\n",
4279 );
4280 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
4281 assert!(!text.contains("\tcmp"), "{text}");
4282 assert!(!text.contains("$-1"), "{text}");
4283 }
4284
4285 #[test]
4290 fn a_store_to_a_local_the_loop_just_read_is_a_conditional_move() {
4291 let text = optimized(
4292 "int f(const int *v, int n, int k) { int best[8] = {0}; \
4293 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; \
4294 return best[k & 7]; }\n",
4295 );
4296 assert!(text.contains("\tcmovgl"), "{text}");
4297 assert!(!text.contains("\tset"), "{text}");
4298 assert!(!text.contains("\ttestb"), "{text}");
4299 }
4300
4301 #[test]
4303 fn a_store_to_a_global_the_loop_just_read_keeps_its_branch() {
4304 let text = optimized(
4305 "int best[8]; void f(const int *v, int n) { \
4306 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; }\n",
4307 );
4308 assert!(!text.contains("\tcmov"), "{text}");
4309 }
4310
4311 #[test]
4319 fn a_conversion_from_a_constant_double_is_the_number_it_converts_to() {
4320 let text = optimized("int f(void) { double d = 2.75; return (int) d; }\n");
4321 assert!(text.contains("movl\t$2, %eax"), "{text}");
4322 assert!(!text.contains("cvttsd2si"), "{text}");
4323 }
4324
4325 #[test]
4333 fn a_slot_of_a_read_only_table_is_the_value_the_table_holds() {
4334 let text =
4335 optimized("static const int t[4] = {10, 20, 30, 40};\nint f(void) { return t[2]; }\n");
4336 assert!(text.contains("movl\t$30, %eax"), "{text}");
4337 assert!(!text.contains("t(%rip)"), "{text}");
4338 }
4339
4340 #[test]
4343 fn a_byte_of_a_read_only_string_is_the_byte_the_string_spells() {
4344 let text = optimized("static const char s[] = \"abc\";\nint f(void) { return s[1]; }\n");
4345 assert!(text.contains("movl\t$98, %eax"), "{text}");
4346 }
4347
4348 #[test]
4352 fn a_table_that_is_not_read_only_keeps_its_load() {
4353 let text = optimized(
4354 "static int t[4] = {10, 20, 30, 40};\nvoid g(int x) { t[2] = x; }\nint f(void) { return t[2]; }\n",
4355 );
4356 assert!(!text.contains("movl\t$30, %eax"), "{text}");
4357 }
4358
4359 #[test]
4366 fn a_call_guarded_by_a_condition_a_read_only_object_settles_is_not_emitted() {
4367 let text = optimized(
4368 "void link_error(void);\nconst double one = 1.0;\nint main(void) { if ((int) one != 1) link_error(); return 0; }\n",
4369 );
4370 assert!(!text.contains("call\tlink_error"), "{text}");
4371 }
4372
4373 #[test]
4375 fn a_cast_between_a_pointer_and_an_integer_leaves_the_value_where_it_is() {
4376 let text = asm("long f(void *p) { return (long)p; }\n");
4377 for line in text.lines().filter(|line| line.starts_with('\t') && !line.contains('.')) {
4382 let mnemonic = line.split_whitespace().next().unwrap_or("");
4383 assert!(matches!(mnemonic, "movq" | "ret"), "{line} in\n{text}");
4384 }
4385 }
4386
4387 #[test]
4391 fn an_argument_past_the_last_register_is_read_out_of_the_caller_s_stack() {
4392 let six = "long a, long b, long c, long d, long e, long f";
4393 let text = asm(&format!("long f({six}, long g, long h) {{ return g + h; }}\n"));
4394
4395 assert!(text.contains("\tmovq\t8(%rsp), "), "{text}");
4402 assert!(text.contains("\taddq\t16(%rsp), "), "{text}");
4403
4404 let narrow = asm(&format!("int f({six}, int g) {{ return g; }}\n"));
4408 assert!(narrow.contains("\tmovl\t8(%rsp), "), "{narrow}");
4409 let eight =
4410 "double a, double b, double c, double d, double e, double f, double g, double h";
4411 let float = asm(&format!("double f({eight}, double i) {{ return i; }}\n"));
4412 assert!(float.contains("\tmovsd\t8(%rsp), "), "{float}");
4413 }
4414
4415 #[test]
4418 fn a_call_writes_the_arguments_with_no_register_left_at_the_stack_pointer() {
4419 let six = "1, 2, 3, 4, 5, 6";
4420 let decl = "long g(long, long, long, long, long, long, long, long);\n";
4421 let text = asm(&format!("{decl}long f(void) {{ return g({six}, 7, 8); }}\n"));
4422
4423 assert!(text.contains("\tmovq\t%"), "{text}");
4424 assert!(text.contains(", (%rsp)\n"), "{text}");
4425 assert!(text.contains(", 8(%rsp)\n"), "{text}");
4426 assert!(text.contains("\tsubq\t$"), "{text}");
4428
4429 let narrow = "int g(int, int, int, int, int, int, int);\n";
4431 let text = asm(&format!("{narrow}int f(void) {{ return g({six}, 7); }}\n"));
4432 assert!(text.contains("\tmovl\t%"), "{text}");
4433 assert!(text.contains(", (%rsp)\n"), "{text}");
4434 }
4435
4436 #[test]
4439 fn a_variadic_call_counts_registers_and_not_arguments() {
4440 let nine = "1., 2., 3., 4., 5., 6., 7., 8., 9.";
4441 let decl = "int g(int, ...);\n";
4442 let text = asm(&format!("{decl}int f(void) {{ return g(0, {nine}); }}\n"));
4443
4444 assert!(text.contains("\tmovl\t$8, "), "eight registers, not nine: {text}");
4445 assert!(text.contains("\tmovsd\t%"), "{text}");
4446 assert!(text.contains(", (%rsp)\n"), "{text}");
4447 }
4448
4449 #[test]
4454 fn a_variadic_function_writes_the_argument_registers_it_was_handed_into_its_frame() {
4455 let body =
4456 "__builtin_va_list ap; __builtin_va_start(ap, n); __builtin_va_end(ap); return n;";
4457 let text = asm(&format!("int f(int n, ...) {{ {body} }}\n"));
4458
4459 let stores = |mnemonic: &str| text.matches(&format!("\t{mnemonic}\t%")).count();
4462 assert!(text.contains(", 8(%r"), "the second slot, not the first: {text}");
4463 assert!(!text.contains(", 0(%r"), "{text}");
4464 assert_eq!(stores("movaps"), 8, "every vector register: {text}");
4467 assert_eq!(stores("movsd"), 0, "and the whole of each one: {text}");
4468
4469 assert!(text.contains("\tsubq\t$"), "{text}");
4471 }
4472
4473 #[test]
4476 fn va_start_writes_the_four_fields_the_psabi_describes() {
4477 let start = "__builtin_va_list ap; __builtin_va_start(ap, d);";
4478 let params = "int a, int b, int c, double d";
4479 let text = asm(&format!("int f({params}, ...) {{ {start} return a; }}\n"));
4480
4481 assert!(text.contains(" movl $24, "), "{text}");
4485 assert!(text.contains(" movl $64, "), "{text}");
4486 assert!(text.contains(", 8(%r"), "{text}");
4490 assert!(text.contains(", 16(%r"), "{text}");
4491 let frame: u32 = text
4492 .lines()
4493 .find_map(|line| line.trim().strip_prefix("subq $")?.split(',').next()?.parse().ok())
4494 .expect("a variadic function takes a frame for the save area");
4495 let above = |line: &str| {
4496 let at: u32 = line.trim().strip_prefix("leaq ")?.split('(').next()?.parse().ok()?;
4497 Some(at > frame)
4498 };
4499 assert!(text.lines().filter_map(above).any(|it| it), "{frame}: {text}");
4500 }
4501
4502 #[test]
4505 fn va_arg_branches_on_whether_the_argument_is_still_in_the_save_area() {
4506 let read = "__builtin_va_list ap; __builtin_va_start(ap, n);";
4507 let ints = format!("int f(int n, ...) {{ {read} return __builtin_va_arg(ap, int); }}\n");
4508 let text = asm(&ints);
4509
4510 assert!(text.contains("$40, "), "{text}");
4513 assert!(text.contains(" cmpl "), "{text}");
4514 assert!(text.contains(" ja "), "{text}");
4518
4519 let arg = "__builtin_va_arg(ap, double)";
4520 let text = asm(&format!("double f(int n, ...) {{ {read} return {arg}; }}\n"));
4521 assert!(text.contains("$160, "), "the last vector slot: {text}");
4522 }
4523
4524 #[test]
4527 fn a_structure_assignment_is_a_move_for_each_word_of_it() {
4528 let decl = "struct pair { long a, b; };\n";
4529 let body = "struct pair p = *q; return p.a + p.b;";
4530 let text = asm(&format!("{decl}long f(struct pair *q) {{ {body} }}\n"));
4531
4532 assert!(!text.contains("memcpy"), "nothing calls the library: {text}");
4533 assert!(!text.contains("\tcall"), "{text}");
4534 assert!(text.matches("\tmovq\t").count() >= 4, "two words each way: {text}");
4536 }
4537
4538 #[test]
4541 fn how_wide_a_word_of_a_copy_is_follows_the_alignment() {
4542 let decl = "struct bytes { char a[8]; };\n";
4543 let body = "struct bytes p = *q; return p.a[0];";
4544 let text = asm(&format!("{decl}int f(struct bytes *q) {{ {body} }}\n"));
4545
4546 assert!(text.matches("\tmovb\t").count() >= 16, "a byte at a time: {text}");
4548 }
4549
4550 #[test]
4553 fn the_part_of_an_initialiser_that_names_nothing_is_stored_as_zero() {
4554 let decl = "struct wide { long a, b, c; };\n";
4555 let text = asm(&format!("{decl}long f(void) {{ struct wide w = {{ 7 }}; return w.c; }}\n"));
4556
4557 assert!(!text.contains("memset"), "nothing calls the library: {text}");
4558 assert!(text.contains("\tmovq\t$0, ") || text.contains("\txorl\t"), "the zero: {text}");
4563 }
4564
4565 #[test]
4568 fn a_copy_too_large_to_unroll_calls_the_runtime() {
4569 let decl = "struct huge { char a[4096]; };\n";
4570 let mut opts = options();
4571 opts.emit = EmitKind::Asm;
4572 let source = format!("{decl}void f(struct huge *p, struct huge *q) {{ *p = *q; }}\n");
4573 let result = run(&opts, &source);
4574 assert!(!result.failed(), "{:?}", result.messages);
4575 let text = result.text();
4576 assert!(text.contains("call") && text.contains("memcpy"), "{text}");
4577 assert!(text.contains("4096"), "the size travels: {text}");
4580 }
4581
4582 #[test]
4589 fn a_structure_too_large_to_unroll_is_copied_into_the_argument_area_by_the_runtime() {
4590 let decl = "struct huge { char a[4096]; };\nint take(struct huge);\n";
4591 let text = asm(&format!("{decl}int f(struct huge *p) {{ return take(*p); }}\n"));
4592
4593 let copy = text.find("call\tmemcpy").expect("the copy");
4594 let call = text.find("call\ttake").expect("the call");
4595 assert!(copy < call, "the copy comes first: {text}");
4596 assert!(text.contains("movq\t%rsp, %rdi"), "the destination: {text}");
4601 assert!(text.contains("$4096, %edx"), "the size: {text}");
4602 }
4603
4604 #[test]
4607 fn a_realigned_frame_reads_them_through_the_frame_pointer() {
4608 let six = "long a, long b, long c, long d, long e, long f";
4609 let body = "_Alignas(32) long wide[4]; wide[0] = g; return wide[0];";
4610 let text = asm(&format!("long f({six}, long g) {{ {body} }}\n"));
4611
4612 assert!(text.contains("\tandq\t$-32, %rsp"), "{text}");
4616 assert!(text.contains("\tmovq\t16(%rbp), "), "{text}");
4617 assert!(!text.contains("\tmovq\t16(%rsp), "), "{text}");
4618 }
4619
4620 #[test]
4622 fn the_target_decides_how_the_assembly_is_spelled() {
4623 let mut opts = options();
4624 opts.emit = EmitKind::Asm;
4625 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
4626 let text = run(&opts, "int f(void) { return 0; }\n").text().to_owned();
4627 assert!(text.contains("__TEXT,__text"), "{text}");
4628 assert!(text.contains("\n_f:\n"), "{text}");
4629 assert!(!text.contains(".note.GNU-stack"), "{text}");
4630 }
4631
4632 fn obj(source: &str) -> Vec<u8> {
4634 let mut opts = options();
4635 opts.emit = EmitKind::Object;
4636 let result = run(&opts, source);
4637 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4638 match result.artifact {
4639 Artifact::Object { bytes, .. } => bytes,
4640 other => panic!("expected an object, got {other:?}"),
4641 }
4642 }
4643
4644 #[test]
4650 fn a_function_goes_from_c_to_an_object_a_linker_would_take() {
4651 let bytes = obj("int add(int a, int b) { return a + b; }\n");
4652 assert_eq!(&bytes[..4], b"\x7fELF", "an object file starts by saying it is one");
4653 let text = asm("int add(int a, int b) { return a + b; }\n");
4654 assert!(
4655 text.contains("\taddl\t"),
4656 "and the listing of it is the same instructions:\n{text}"
4657 );
4658 }
4659
4660 #[test]
4662 fn a_variable_goes_from_c_to_the_section_it_belongs_in() {
4663 let text = asm("int counter = 42;\nstatic int hidden;\nconst int fixed = 7;\n");
4664 assert!(text.contains("\t.data\n\t.globl\tcounter\n"), "{text}");
4665 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
4666 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
4667 assert!(text.contains("\t.bss\n\t.p2align\t2\n"), "{text}");
4670 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
4671 assert!(!text.contains(".globl\thidden"), "{text}");
4672 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4675 }
4676
4677 #[test]
4684 fn a_bit_field_initializer_writes_every_byte_of_the_value_and_not_only_the_ones_that_are_set() {
4685 let text = asm("struct s { unsigned f : 20; } x = { 0x12300 };\n");
4686 assert!(text.contains("\t.data\n"), "there is something to write: {text}");
4687 assert!(text.contains("\nx:\n\t.ascii\t\"\\000#\\001\"\n"), "and it is the value: {text}");
4688
4689 let text = asm("struct s { unsigned a : 8; unsigned b : 8; } x = { 0, 3 };\n");
4692 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\003\"\n"), "{text}");
4693
4694 let text = asm("struct s { unsigned long long f : 40; } x = { 0x100000 };\n");
4697 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\000\\020\"\n\t.space\t5\n"), "{text}");
4698
4699 let text = asm("struct s { unsigned f : 20; } x = { 0 };\n");
4701 assert!(text.contains("\t.bss\n"), "an object of zeroes is zeroes: {text}");
4702 assert!(text.contains("\nx:\n\t.space\t4\n"), "{text}");
4703 }
4704
4705 #[test]
4707 fn a_string_literal_is_a_variable_with_a_name_no_program_could_write() {
4708 let text = asm("const char *f(void) { return \"hi\"; }\n");
4709 assert!(text.contains("\t.ascii\t\"hi\\000\"\n"), "{text}");
4710 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4711 let label = text
4712 .lines()
4713 .find(|line| line.starts_with(".Lstr"))
4714 .unwrap_or_else(|| panic!("a label for the literal in\n{text}"));
4715 assert!(!text.contains(&format!(".globl\t{}", label.trim_end_matches(':'))), "{text}");
4716 }
4717
4718 #[test]
4720 fn an_address_in_an_initializer_is_left_to_the_linker() {
4721 let source = "int counter;\nint *p = &counter;\n";
4722 let text = asm(source);
4723 assert!(text.contains("\np:\n\t.quad\tcounter\n"), "{text}");
4724 let bytes = obj(source);
4727 assert!(bytes.windows(8).any(|w| w == b"counter\0"), "the object has to name it");
4728 }
4729
4730 #[test]
4739 fn a_constant_holding_an_address_goes_in_the_section_the_loader_may_write_once() {
4740 let text = asm("static void a(void) {}\nstatic void b(void) {}\n\
4743 struct m { void (*x)(void); void (*y)(void); };\n\
4744 const struct m t = { a, b };\n");
4745 assert!(text.contains("\t.section\t.data.rel.ro.local,\"aw\",@progbits\n"), "{text}");
4746 assert!(text.contains("\nt:\n\t.quad\ta\n\t.quad\tb\n"), "{text}");
4747
4748 let text =
4751 asm("void a(void);\nstruct m { void (*x)(void); };\nconst struct m t = { a };\n");
4752 assert!(text.contains("\t.section\t.data.rel.ro,\"aw\",@progbits\n"), "{text}");
4753
4754 let text = asm("const int fixed = 7;\n");
4756 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4757 }
4758
4759 #[test]
4766 fn a_thread_local_variable_is_storage_a_thread_gets_a_copy_of_and_an_offset_into_it() {
4767 let text = asm("_Thread_local int x = 1;\nint read(void) { return x; }\n");
4768 assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
4771 assert!(text.contains("\t.type\tx, @tls_object\n"), "{text}");
4772 assert!(text.contains("x@GOTTPOFF(%rip)"), "{text}");
4775 assert!(text.contains("%fs:0"), "{text}");
4776 }
4777
4778 #[test]
4784 fn the_address_of_this_thread_s_own_storage_is_read_out_of_the_segment_register() {
4785 let text = asm("void *here(void) { return __builtin_thread_pointer(); }\n");
4786 assert!(text.contains("movq\t%fs:0, "), "{text}");
4787 assert!(!text.contains("GOTTPOFF"), "{text}");
4789 }
4790
4791 #[test]
4802 fn a_prefetch_is_one_of_four_instructions_and_the_locality_is_what_picks() {
4803 for (locality, wanted) in
4804 [(0, "prefetchnta"), (1, "prefetcht2"), (2, "prefetcht1"), (3, "prefetcht0")]
4805 {
4806 let source =
4807 format!("void warm(void *p) {{ __builtin_prefetch(p, 0, {locality}); }}\n");
4808 let text = asm(&source);
4809 assert!(text.contains(&format!("\t{wanted}\t")), "locality {locality}: {text}");
4810 }
4811 let text = asm("void warm(void *p) { __builtin_prefetch(p); }\n");
4813 assert!(text.contains("\tprefetcht0\t"), "{text}");
4814 let text = asm("void warm(void *p) { __builtin_prefetch(p, 1); }\n");
4817 assert!(text.contains("\tprefetcht0\t"), "{text}");
4818 assert!(!text.contains("prefetchw"), "{text}");
4819 }
4820
4821 #[test]
4824 fn an_aarch64_prefetch_is_a_prfm_that_says_the_locality_and_whether_it_writes() {
4825 let mut opts = options();
4826 opts.emit = EmitKind::Asm;
4827 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
4828 for (write, kind) in [(0, "pld"), (1, "pst")] {
4829 for (locality, wanted) in [(0, "l1strm"), (1, "l3keep"), (2, "l2keep"), (3, "l1keep")] {
4830 let source = format!(
4831 "void warm(void *p) {{ __builtin_prefetch(p, {write}, {locality}); }}\n"
4832 );
4833 let result = run(&opts, &source);
4834 assert_eq!(result.messages, Vec::<String>::new(), "{source}");
4835 let text = result.text();
4836 assert!(text.contains("prfm"), "{source}{text}");
4837 assert!(text.contains(&format!("{kind}{wanted}, [x0]")), "{source}{text}");
4838 }
4839 }
4840 }
4841
4842 #[test]
4853 fn a_trap_is_the_instruction_the_machine_has_no_meaning_for() {
4854 let text = asm("void stop(void) { __builtin_trap(); }\n");
4855 assert!(text.contains("\tud2\n"), "{text}");
4856 assert!(!text.contains("\tcall"), "a stop is not a call to anything: {text}");
4857
4858 let text = asm("int stop(int a) { __builtin_trap(); return a + 1; }\n");
4859 assert!(text.contains("\tud2\n"), "{text}");
4860 assert!(text.contains("\taddl\t"), "the block goes on after a stop: {text}");
4861 }
4862
4863 #[test]
4867 fn cpu_init_is_a_call_to_the_libgcc_function_that_fills_in_the_model() {
4868 let text = asm("void start(void) { __builtin_cpu_init(); }\n");
4869 assert!(text.contains("\tcall\t__cpu_indicator_init"), "{text}");
4870 assert!(!text.contains("__builtin_cpu_init"), "{text}");
4871 }
4872
4873 #[test]
4881 fn cpu_supports_is_a_bit_of_the_words_libgcc_fills_in() {
4882 let text = asm("int f(void) { return __builtin_cpu_supports(\"sse4.2\"); }\n");
4883 assert!(text.contains("__cpu_model"), "{text}");
4884 assert!(text.contains("12(%"), "the fourth word of the model: {text}");
4885 assert!(text.contains("$256"), "{text}");
4886 assert!(!text.contains("\tcall"), "the answer is a read and not a call: {text}");
4887
4888 let text = asm("int f(void) { return __builtin_cpu_supports(\"vpclmulqdq\"); }\n");
4889 assert!(text.contains("__cpu_features2"), "{text}");
4890 assert!(text.contains("$2,"), "{text}");
4891
4892 let text = asm("int f(void) { return __builtin_cpu_supports(\"xsave\"); }\n");
4893 assert!(text.contains("__cpu_features2"), "{text}");
4894 assert!(text.contains("4(%"), "the second word of the second object: {text}");
4895 assert!(text.contains("$131072"), "{text}");
4896
4897 let text = asm("int f(void) { return __builtin_cpu_supports(\"avx512vbmi2\"); }\n");
4898 assert!(text.contains("set"), "the top bit is answered as a one: {text}");
4899 }
4900
4901 #[test]
4904 fn cpu_is_compares_one_word_of_the_model_with_a_number() {
4905 let text = asm("int f(void) { return __builtin_cpu_is(\"amd\"); }\n");
4906 assert!(text.contains("__cpu_model"), "{text}");
4907 assert!(text.contains("$2,"), "{text}");
4908
4909 let text = asm("int f(void) { return __builtin_cpu_is(\"znver4\"); }\n");
4910 assert!(text.contains("8(%"), "the subtype is the third word: {text}");
4911 assert!(text.contains("$29,"), "{text}");
4912 }
4913
4914 #[test]
4918 fn a_cpu_builtin_takes_a_name_it_knows_written_as_a_literal() {
4919 let mut opts = options();
4920 opts.emit = EmitKind::Ir;
4921 for (source, wanted) in [
4922 (
4923 "int f(const char *s) { return __builtin_cpu_supports(s); }\n",
4924 "parameter to builtin must be a string constant or literal",
4925 ),
4926 (
4927 "int f(void) { return __builtin_cpu_supports(\"sse5\"); }\n",
4928 "parameter to builtin not valid: sse5",
4929 ),
4930 (
4931 "int f(void) { return __builtin_cpu_is(\"sse\"); }\n",
4932 "parameter to builtin not valid: sse",
4933 ),
4934 ] {
4935 let result = run(&opts, source);
4936 assert!(
4937 result.messages.iter().any(|m| m.contains(wanted)),
4938 "{source}{:?}",
4939 result.messages
4940 );
4941 }
4942 let text = asm("int f(void) { return __builtin_cpu_supports((const char *)\"avx2\"); }\n");
4944 assert!(text.contains("$1024"), "{text}");
4945
4946 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
4947 for source in [
4948 "void f(void) { __builtin_cpu_init(); }\n",
4949 "int f(void) { return __builtin_cpu_supports(\"sse4.2\"); }\n",
4950 ] {
4951 let result = run(&opts, source);
4952 assert!(
4953 result.messages.iter().any(|m| m.contains("only available on x86-64")),
4954 "{source}{:?}",
4955 result.messages
4956 );
4957 }
4958 }
4959
4960 #[test]
4972 fn assume_aligned_is_its_first_argument_and_keeps_the_rest() {
4973 let text = asm("void *aligned(char *p) { return __builtin_assume_aligned(p, 16); }\n");
4974 assert!(!text.contains("assume_aligned"), "{text}");
4975 assert!(!text.contains("\tcall"), "nothing is called for an alignment fact: {text}");
4976
4977 let source = "unsigned long width(void);\n\
4978 void *aligned(char *p) { return __builtin_assume_aligned(p, width()); }\n";
4979 let text = asm(source);
4980 assert!(!text.contains("assume_aligned"), "{text}");
4981 assert!(text.contains("width"), "the argument that is not the answer still runs: {text}");
4982 }
4983
4984 #[test]
4994 fn the_frame_address_is_the_frame_pointer_after_walking_that_many_links() {
4995 let text = asm("void *here(void) { return __builtin_frame_address(0); }\n");
4996 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
4997 assert!(text.contains("movq\t%rbp, %rax"), "{text}");
4998 assert!(!text.contains("\tcall"), "a frame address is not a call to anything: {text}");
4999
5000 let walk = |depth: u32| {
5001 let source = format!("void *up(void) {{ return __builtin_frame_address({depth}); }}\n");
5002 asm(&source).matches("movq\t(%r").count()
5003 };
5004 assert_eq!(walk(1), 1, "one link is one load");
5005 assert_eq!(walk(3), 3, "three links are three loads");
5006 }
5007
5008 #[test]
5018 fn the_return_address_is_one_word_above_the_frame_the_walk_ended_at() {
5019 let text = asm("void *back(void) { return __builtin_return_address(0); }\n");
5020 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
5021 assert!(text.contains("movq\t8(%rbp), %rax"), "{text}");
5022 assert!(!text.contains("\tcall"), "a return address is not a call to anything: {text}");
5023
5024 let text = asm("void *back(void) { return __builtin_return_address(2); }\n");
5025 assert_eq!(text.matches("movq\t(%r").count(), 2, "two links are two loads: {text}");
5026 assert!(text.contains("movq\t8(%r"), "and the answer is above the last of them: {text}");
5027 }
5028
5029 #[test]
5040 fn a_depth_that_is_not_a_small_constant_is_refused() {
5041 let mut opts = options();
5042 opts.emit = EmitKind::Ir;
5043 for source in [
5044 "void *up(int n) { return __builtin_return_address(n); }\n",
5045 "void *up(void) { return __builtin_frame_address(1000); }\n",
5046 ] {
5047 let messages = run(&opts, source).messages;
5048 let named = messages.iter().any(|m| m.contains("E0705"));
5049 assert!(named, "expected a refusal in {messages:?}");
5050 }
5051 }
5052
5053 #[test]
5065 fn an_alloca_takes_the_bytes_off_the_stack_pointer_and_answers_where_they_are() {
5066 let text =
5067 asm("void use(void *p); void f(unsigned long n) { use(__builtin_alloca(n)); }\n");
5068 assert!(text.contains("andq\t$-16"), "the size is rounded up to sixteen: {text}");
5069 assert!(text.contains("subq\t%rdi, %rsp"), "and taken off the stack pointer: {text}");
5070 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
5071
5072 let plain = concat!(
5075 "extern void *alloca(__SIZE_TYPE__);\n",
5076 "void use(void *p);\n",
5077 "void f(unsigned long n) { use(alloca(n)); }\n",
5078 );
5079 let text = asm(plain);
5080 assert!(text.contains("subq\t%rdi, %rsp"), "the plain name is the same bytes: {text}");
5081 assert_eq!(text.matches("\tcall").count(), 1, "and is not a call either: {text}");
5082
5083 let own = concat!(
5086 "static void *alloca(unsigned long n) { return 0; }\n",
5087 "void *f(unsigned long n) { return alloca(n); }\n",
5088 );
5089 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
5090 }
5091
5092 #[test]
5107 fn a_builtin_the_program_never_declared_is_the_builtin_rather_than_the_one_c89_wrote_down() {
5108 let mut opts = options();
5112 opts.permissive = true;
5113 let undeclared = "void use(void *p);
5114void f(unsigned long n) { use(alloca(n)); }
5115";
5116 assert_eq!(
5117 run(&opts, undeclared).messages,
5118 [
5119 "/main.c:2:31: warning: implicit declaration of function 'alloca' [E0521]",
5120 "/main.c:2:31: warning: incompatible implicit declaration of built-in function \
5121 'alloca' [E0713]",
5122 ]
5123 );
5124
5125 opts.emit = EmitKind::Asm;
5126 let text = run(&opts, undeclared).text().to_owned();
5127 assert!(text.contains("subq\t%rdi, %rsp"), "the bytes come off the stack: {text}");
5128 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
5129
5130 let string = "unsigned long f(void) { return strlen(\"abc\"); }\n";
5133 let text = run(&opts, string).text().to_owned();
5134 assert!(text.contains("call\tstrlen"), "strlen is still a call: {text}");
5135
5136 let own = concat!(
5139 "static void *alloca(unsigned long n) { return 0; }\n",
5140 "void *f(unsigned long n) { return alloca(n); }\n",
5141 );
5142 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
5143 }
5144
5145 #[test]
5155 fn the_bytes_an_alloca_took_are_still_there_at_the_end_of_the_block_that_took_them() {
5156 let inner = "{ use(__builtin_alloca(n)); }";
5157 for body in [inner.to_owned(), format!("int a[n]; {inner} use(a);")] {
5158 let source = format!("void use(void *p);\nvoid f(unsigned long n) {{ {body} }}\n");
5159 let text = asm(&source);
5160 for line in text.lines().filter(|line| line.trim_end().ends_with(", %rsp")) {
5164 let taking = line.contains("subq");
5165 let leaving = line.contains("%rbp");
5166 assert!(taking || leaving, "nothing puts the stack back: {line} in {text}");
5167 }
5168 }
5169 }
5170
5171 #[test]
5173 fn the_object_and_the_listing_are_two_spellings_of_one_compilation() {
5174 let source = "int callee(void); int g(void) { return callee(); }\n";
5178 let bytes = obj(source);
5179 assert!(
5180 bytes.windows(7).any(|w| w == b"callee\0"),
5181 "the object has to name the callee for the linker to find it"
5182 );
5183 let text = asm(source);
5184 assert!(text.contains("\tcall\tcallee\n"), "{text}");
5185 }
5186
5187 #[test]
5193 fn compiling_for_an_executable_produces_an_object_and_not_a_dump() {
5194 let mut opts = options();
5195 opts.emit = EmitKind::Executable;
5197 let result = run(&opts, "int main(void) { return 0; }\n");
5198 assert_eq!(result.messages, Vec::<String>::new());
5199 match result.artifact {
5200 Artifact::Object { bytes, .. } => assert_eq!(&bytes[..4], b"\x7fELF"),
5201 other => panic!("expected an object, got {other:?}"),
5202 }
5203 }
5204
5205 #[test]
5207 fn a_platform_with_no_object_writer_is_said_so_rather_than_written_as_elf() {
5208 let mut opts = options();
5209 opts.emit = EmitKind::Object;
5210 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
5211 let result = run(&opts, "int f(void) { return 0; }\n");
5212 assert!(result.failed(), "an object nobody can read is worse than a message");
5213 assert!(
5214 result.messages.iter().any(|m| m.contains("no object writer")),
5215 "{:?}",
5216 result.messages
5217 );
5218 }
5219
5220 fn ir(source: &str) -> String {
5222 let mut opts = options();
5223 opts.emit = EmitKind::Ir;
5224 let result = run(&opts, source);
5225 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5226 result.text().to_owned()
5227 }
5228
5229 fn errors(source: &str) -> Vec<String> {
5231 let mut opts = options();
5232 opts.emit = EmitKind::Ir;
5233 let result = run(&opts, source);
5234 assert!(result.failed(), "expected this to be refused:\n{source}");
5235 result.messages
5236 }
5237
5238 fn body(source: &str) -> String {
5240 let text = ir(source);
5241 let (_, rest) = text.split_once("{\n").expect("a function definition");
5242 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
5243 body.to_owned()
5244 }
5245
5246 #[test]
5254 fn gnu89_inline_is_what_decides_whether_a_bare_inline_definition_reaches_the_module() {
5255 let source = "inline int f(int x) { return x + 1; }\n";
5256 let with = |flag: bool| {
5257 let mut opts = options();
5258 opts.emit = EmitKind::Ir;
5259 opts.gnu89_inline = flag;
5260 let result = run(&opts, source);
5261 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5262 result.text().to_owned()
5263 };
5264
5265 assert!(!with(false).contains("block0"), "no body: {}", with(false));
5268
5269 assert!(with(true).contains("block0"), "a body: {}", with(true));
5272 }
5273
5274 #[test]
5281 fn an_access_through_a_type_names_the_type_it_went_through() {
5282 let source = "\
5283struct s { int a; float b; };\n\
5284union u { int i; float f; };\n\
5285int scalar(int *p) { return *p; }\n\
5286float member(struct s *p) { p->a = 1; return p->b; }\n\
5287int element(int *a, long i) { return a[i]; }\n\
5288float through_a_union(union u *p) { p->i = 1; return p->f; }\n";
5289 let text = ir(source);
5290 assert!(text.contains(r#"!0 = tbaa "char""#), "the root: {text}");
5291 assert!(text.contains(r#"tbaa "int", parent !0"#), "int under it: {text}");
5292 assert!(text.contains(r#"tbaa "float", parent !0"#), "float under it: {text}");
5293 let named = text.lines().filter(|line| line.contains(", tbaa !")).count();
5296 assert_eq!(named, 6, "six accesses: {text}");
5297 }
5298
5299 #[test]
5306 fn turning_strict_aliasing_off_leaves_the_type_off_every_access() {
5307 let source = "int punned(float *f, int *i) { *i = 1; *f = 2.0f; return *i; }\n";
5308 let mut opts = options();
5309 opts.emit = EmitKind::Ir;
5310 opts.strict_aliasing = false;
5311 let result = run(&opts, source);
5312 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5313 let text = result.text().to_owned();
5314 assert!(!text.contains("tbaa"), "not even the root: {text}");
5315 }
5316
5317 #[test]
5323 fn instrumenting_functions_calls_the_hooks_around_every_body_but_the_hooks() {
5324 let source = concat!(
5325 "#define NOCHK __attribute__((no_instrument_function))\n",
5326 "void __cyg_profile_func_enter(void *, void *) NOCHK;\n",
5327 "void __cyg_profile_func_exit(void *, void *) NOCHK;\n",
5328 "int calls;\n",
5329 "int pick(int x) { if (x) return 1; return 2; }\n",
5330 "void quiet(void) NOCHK;\n",
5331 "void quiet(void) { calls++; }\n",
5332 "void __cyg_profile_func_enter(void *fn, void *site) { calls++; }\n",
5333 "void __cyg_profile_func_exit(void *fn, void *site) { calls--; }\n",
5334 );
5335 let mut opts = options();
5336 opts.emit = EmitKind::Ir;
5337 opts.instrument_functions = true;
5338 let result = run(&opts, source);
5339 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5340 let text = result.text().to_owned();
5341 let body = |name: &str| -> String {
5342 let open = format!("func @{name}(");
5343 let start = text.find(&open).unwrap_or_else(|| panic!("no {name}: {text}"));
5344 let rest = &text[start..];
5345 rest[..rest.find("\n}").unwrap_or(rest.len())].to_owned()
5346 };
5347 let pick = body("pick");
5348 assert_eq!(pick.matches("call @__cyg_profile_func_enter(").count(), 1, "{pick}");
5349 assert_eq!(pick.matches("call @__cyg_profile_func_exit(").count(), 2, "{pick}");
5350 assert!(pick.contains("return_address"), "{pick}");
5351 assert!(pick.contains("global_addr @pick"), "{pick}");
5352 for quiet in ["quiet", "__cyg_profile_func_enter", "__cyg_profile_func_exit"] {
5353 assert!(!body(quiet).contains("call "), "{quiet} is left alone: {text}");
5354 }
5355
5356 opts.instrument_functions = false;
5357 let result = run(&opts, source);
5358 assert!(!result.text().contains("call @__cyg_profile"), "off unless asked for");
5359 }
5360
5361 #[test]
5366 fn calls_that_owe_the_same_handlers_share_one_landing_pad() {
5367 let source = concat!(
5368 "void done(int *p);\n",
5369 "void work(int);\n",
5370 "void f(void) {\n",
5371 " int a __attribute__((cleanup(done))) = 1;\n",
5372 " work(1); work(2);\n",
5373 " { int b __attribute__((cleanup(done))) = 2; work(3); work(4); }\n",
5374 " work(5); work(6);\n",
5375 "}\n",
5376 );
5377 let mut opts = options();
5378 opts.emit = EmitKind::Ir;
5379 opts.exceptions = true;
5380 let result = run(&opts, source);
5381 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5382 let text = result.text();
5383 assert_eq!(text.matches("= unwound").count(), 8, "every call has its edge: {text}");
5386 assert_eq!(text.matches("= landing").count(), 2, "one pad for a, one for b and a: {text}");
5387 }
5388
5389 #[test]
5393 fn a_goto_that_runs_handlers_gives_each_one_an_edge_to_what_is_still_owed() {
5394 let source = concat!(
5395 "void done(int *p);\n",
5396 "void f(int n) {\n",
5397 " int a __attribute__((cleanup(done))) = 1;\n",
5398 " { int b __attribute__((cleanup(done))) = 2;\n",
5399 " { int c __attribute__((cleanup(done))) = 3; if (n) goto out; }\n",
5400 " }\n",
5401 "out:\n",
5402 " return;\n",
5403 "}\n",
5404 );
5405 let mut opts = options();
5406 opts.emit = EmitKind::Ir;
5407 opts.exceptions = true;
5408 let result = run(&opts, source);
5409 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5410 let text = result.text();
5411 assert_eq!(text.matches("= unwound").count(), 5, "{text}");
5414 assert_eq!(text.matches("= landing").count(), 2, "{text}");
5415 }
5416
5417 #[test]
5423 fn a_call_an_unwind_would_leave_a_cleanup_behind_gets_a_landing_pad_under_exceptions() {
5424 let source = concat!(
5425 "void done(int *p);\n",
5426 "void work(void);\n",
5427 "void calls(void) { int x __attribute__((cleanup(done))) = 1; work(); }\n",
5428 "int quiet(int y) { int x __attribute__((cleanup(done))) = y; return x + 1; }\n",
5429 "void after(void) { { int x __attribute__((cleanup(done))) = 1; } work(); }\n",
5430 );
5431 let mut opts = options();
5432 opts.emit = EmitKind::Ir;
5433 let result = run(&opts, source);
5434 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5435 assert!(!result.text().contains("landing"), "{}", result.text());
5436
5437 opts.exceptions = true;
5438 let result = run(&opts, source);
5439 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5440 let text = result.text();
5441 assert_eq!(text.matches("= landing").count(), 1, "only the call in calls: {text}");
5442 assert!(text.contains("_Unwind_Resume"), "{text}");
5443
5444 opts.emit = EmitKind::Asm;
5445 let result = run(&opts, source);
5446 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5447 let text = result.text();
5448 assert!(text.contains(".cfi_personality 0x9b,DW.ref.__gcc_personality_v0"), "{text}");
5449 assert!(text.contains(".cfi_lsda 0x1b,.LLSDA_calls"), "{text}");
5450 assert!(text.contains(".gcc_except_table"), "{text}");
5451 assert_eq!(text.matches(".cfi_lsda").count(), 1, "{text}");
5452
5453 opts.emit = EmitKind::Object;
5454 let result = run(&opts, source);
5455 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5456 let bytes = result.artifact.bytes();
5457 let has = |what: &[u8]| bytes.windows(what.len()).any(|window| window == what);
5458 assert!(has(b".gcc_except_table\0"), "the call site table has a section");
5459 assert!(has(b"zPLR\0"), "a header naming the personality routine");
5460 assert!(has(b"zR\0"), "and the plain one for the functions with no pad");
5461 assert!(has(b"DW.ref.__gcc_personality_v0\0"), "the pointer the header reads through");
5462
5463 opts.emit = EmitKind::Ir;
5464 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
5465 let result = run(&opts, source);
5466 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
5467 assert!(result.messages[0].contains("landing pad"), "{:?}", result.messages);
5468 assert!(result.messages[0].contains(":3:"), "the call in calls: {:?}", result.messages);
5469 }
5470
5471 #[test]
5477 fn an_asm_at_file_scope_with_an_instruction_in_it_is_assembled() {
5478 let source = concat!(
5479 "extern void vide(void);\n",
5480 "__asm__(\".text;.globl _us;_us:;movl $0x1234ABCD, %eax;ret\");\n",
5481 "__asm__(\"vide: ret\");\n",
5482 "unsigned short _us(void);\n",
5483 "int main(void) { vide(); return _us() == 0xABCD ? 0 : 1; }\n",
5484 );
5485 let mut opts = options();
5486 opts.emit = EmitKind::Ir;
5487 let result = run(&opts, source);
5488 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5489 assert_eq!(result.text().matches("module asm ").count(), 2, "{}", result.text());
5490
5491 opts.emit = EmitKind::Asm;
5492 let result = run(&opts, source);
5493 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5494 let text = result.text();
5495 assert!(text.contains("#APP\nvide: ret\n#NO_APP\n"), "{text}");
5496 let main = text.find("main:").expect("main");
5497 assert!(text.find("#NO_APP").expect("the markers") < main, "templates first: {text}");
5498
5499 opts.emit = EmitKind::Object;
5500 let result = run(&opts, source);
5501 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5502 let (bytes, defines) = match result.artifact {
5503 Artifact::Object { bytes, defines } => (bytes, defines),
5504 other => panic!("expected an object, got {other:?}"),
5505 };
5506 assert!(defines.iter().any(|name| name == "_us"), "{defines:?}");
5507 let us = [0xb8, 0xcd, 0xab, 0x34, 0x12, 0xc3];
5509 assert!(bytes.windows(us.len()).any(|window| window == us), "the template's bytes");
5510
5511 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
5513 opts.emit = EmitKind::Ir;
5514 let result = run(&opts, source);
5515 assert!(!result.messages.is_empty(), "refused on Mach-O");
5516 assert!(result.messages[0].contains("the instruction 'movl'"), "{:?}", result.messages);
5517 }
5518
5519 #[test]
5527 fn a_bare_return_from_a_function_that_promised_a_value_gives_back_a_zero() {
5528 let mut opts = options();
5529 opts.emit = EmitKind::Ir;
5530 opts.std = Std::C89;
5531 let compiled = |source: &str| {
5532 let result = run(&opts, source);
5533 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
5534 result.text().to_owned()
5535 };
5536
5537 let text = compiled("int f(int x) { if (x) return; return 3; }\n");
5538 assert!(text.contains("iconst.i32 0\n return"), "zero goes back: {text}");
5539 assert!(!text.contains("unreachable"), "the branch that reached it is kept: {text}");
5540
5541 let text = compiled("double f(int x) { if (x) return; return 1.0; }\n");
5543 assert!(text.contains("fconst.f64 0x0\n return"), "a float zero goes back: {text}");
5544 }
5545
5546 #[test]
5554 fn a_call_to_a_name_nothing_declared_declares_it_as_c89_said_to() {
5555 let mut opts = options();
5556 opts.emit = EmitKind::Ir;
5557 opts.std = Std::C89;
5558 let compiled = |source: &str| {
5559 let result = run(&opts, source);
5560 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
5561 result.text().to_owned()
5562 };
5563
5564 let text = compiled("int f(void) { return g(); }\n");
5566 assert!(text.contains("call @g"), "the call is to the name that was written: {text}");
5567 assert!(text.contains("i32"), "and it gives back an int: {text}");
5568
5569 let text = compiled("int f(char c) { return g(c); }\n");
5572 assert!(text.contains("sext.i32"), "the argument is promoted: {text}");
5573
5574 let mut opts = options();
5577 opts.std = Std::C89;
5578 let said = run(&opts, "int f(void) { return h; }\n").messages.join("\n");
5579 assert!(said.contains("'h' undeclared"), "not a call, so not declared: {said}");
5580 }
5581
5582 #[test]
5592 fn a_name_called_before_it_is_defined_still_gets_its_definition() {
5593 let mut opts = options();
5594 opts.emit = EmitKind::Ir;
5595 opts.std = Std::C89;
5596 let text = run(&opts, "int f(void) { return dummy(); }\ndummy () { return 7; }\n")
5597 .text()
5598 .to_owned();
5599 assert!(text.contains("func @f()"), "the caller is there: {text}");
5600 assert!(text.contains("func @dummy"), "and so is what it calls: {text}");
5601 assert!(text.contains("iconst.i32 7"), "with the body it was given: {text}");
5602 }
5603
5604 #[test]
5612 fn an_old_style_parameter_is_converted_from_what_the_call_promoted_it_to() {
5613 let mut opts = options();
5614 opts.emit = EmitKind::Ir;
5615 opts.std = Std::C89;
5616 let compiled = |source: &str| run(&opts, source).text().to_owned();
5617
5618 let text = compiled("f (c) unsigned char c; { return c; }\n");
5619 assert!(text.contains("func @f(i32"), "an int arrives: {text}");
5620 assert!(text.contains("trunc.i8"), "and is cut down to what was declared: {text}");
5621 assert!(text.contains("zext.i32"), "then read back unsigned: {text}");
5622
5623 let text = compiled("f (s) short s; { return s; }\n");
5625 assert!(text.contains("trunc.i16"), "cut down: {text}");
5626 assert!(text.contains("sext.i32"), "and read back signed: {text}");
5627
5628 let text = compiled("f (x) float x; { return x * 2; }\n");
5631 assert!(text.contains("func @f(f64"), "a double arrives: {text}");
5632 assert!(text.contains("fptrunc.f32"), "and is narrowed to the float: {text}");
5633
5634 let text = compiled("int f(unsigned char c) { return c; }\n");
5637 assert!(text.contains("func @f(i8)"), "the declared type arrives: {text}");
5638 assert!(!text.contains("trunc"), "so there is nothing to cut down: {text}");
5639 }
5640
5641 #[test]
5650 fn the_rules_gcc_promoted_are_decided_by_the_dialect_and_by_fpermissive() {
5651 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
5653 let cases = [
5654 ("static counted;\n", ["", "error", "warning", "error"]),
5655 ("int f(void) { return g(); }\n", ["", "error", "warning", "error"]),
5656 ("int f(x) { return x; }\n", ["", "error", "warning", "error"]),
5657 ("int *p;\nvoid h(void) { p = 1; }\n", ["warning", "error", "warning", "error"]),
5658 (
5659 "char *q;\nint *r;\nvoid k(void) { r = q; }\n",
5660 ["warning", "error", "warning", "error"],
5661 ),
5662 ("int f(void) { return; }\n", ["", "error", "warning", "error"]),
5663 ("void g(void) { return 1; }\n", ["warning", "error", "warning", "error"]),
5664 ];
5665
5666 for (source, wanted) in cases {
5667 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
5668 let mut opts = options();
5669 opts.std = std;
5670 opts.permissive = permissive;
5671 let said = run(&opts, source).messages.join("\n");
5672 let severity = if said.contains(": error: ") {
5673 "error"
5674 } else if said.contains(": warning: ") {
5675 "warning"
5676 } else {
5677 ""
5678 };
5679 let how = if permissive { " -fpermissive" } else { "" };
5680 assert_eq!(
5681 severity,
5682 wanted,
5683 "under -std={}{how}, {source} was answered with `{said}`",
5684 std.as_str()
5685 );
5686 if wanted.is_empty() {
5687 assert!(said.is_empty(), "nothing to say, but said `{said}`");
5688 }
5689 }
5690 }
5691 }
5692
5693 #[test]
5702 fn the_three_variadic_builtins_answer_a_bad_list_the_way_a_call_answers_a_bad_argument() {
5703 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
5704 let cases = [
5705 (
5706 "int f(int n, ...) { char *p; return __builtin_va_arg(p, int); }\n",
5707 "first argument to 'va_arg' not of type 'va_list'",
5708 ["error", "error", "error", "error"],
5709 ),
5710 (
5711 "void f(int n, ...) { char *p; __builtin_va_start(p, n); }\n",
5712 "passing argument 1 of '__builtin_va_start' from incompatible pointer type",
5713 ["warning", "error", "warning", "error"],
5714 ),
5715 (
5716 "void f(int n, ...) { int x; __builtin_va_end(x); }\n",
5717 "passing argument 1 of '__builtin_va_end' makes pointer from integer without a \
5718 cast",
5719 ["warning", "error", "warning", "error"],
5720 ),
5721 (
5722 "void f(int n, ...) { __builtin_va_list a; char *p; __builtin_va_copy(a, p); }\n",
5723 "passing argument 2 of '__builtin_va_copy' from incompatible pointer type",
5724 ["warning", "error", "warning", "error"],
5725 ),
5726 ];
5727
5728 for (source, message, wanted) in cases {
5729 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
5730 let mut opts = options();
5731 opts.std = std;
5732 opts.permissive = permissive;
5733 let said = run(&opts, source).messages.join("\n");
5734 let how = if permissive { " -fpermissive" } else { "" };
5735 assert!(
5736 said.contains(&format!(": {wanted}: {message}")),
5737 "under -std={}{how}, {source} was answered with `{said}`",
5738 std.as_str()
5739 );
5740 }
5741 }
5742 }
5743
5744 fn safe_ir(tier: rucc_session::Safety, source: &str) -> String {
5746 let mut opts = options();
5747 opts.emit = EmitKind::Ir;
5748 opts.safety = tier;
5749 let result = run(&opts, source);
5750 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5751 result.text().to_owned()
5752 }
5753
5754 const READS_THROUGH_A_POINTER: &str = "int read(int *p) { return p[1]; }\n";
5755
5756 fn padded_ir(padding: Padding, source: &str) -> String {
5758 let mut opts = options();
5759 opts.emit = EmitKind::Ir;
5760 opts.safety = rucc_session::Safety::Detect;
5761 opts.padding = padding;
5762 let result = run(&opts, source);
5763 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5764 result.text().to_owned()
5765 }
5766
5767 const FILLS_A_RECORD_A_MEMBER_AT_A_TIME: &str = "struct padded { char tag; int value; };\n\
5768 void fill(struct padded *p) { p->tag = 1; p->value = 2; }\n";
5769
5770 #[test]
5771 fn a_record_filled_a_member_at_a_time_comes_out_whole_when_padding_does_not_participate() {
5772 let text = padded_ir(Padding::Ignored, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
5776 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
5777 }
5778
5779 #[test]
5780 fn a_store_says_only_what_it_wrote_when_padding_does_participate() {
5781 let text = padded_ir(Padding::Tracked, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
5784 assert!(!text.contains("owns"), "{text}");
5785 }
5786
5787 #[test]
5788 fn a_member_of_a_union_owns_nothing_after_it() {
5789 let text = padded_ir(
5793 Padding::Ignored,
5794 "union u { char tag; long wide; };\nvoid fill(union u *p) { p->tag = 1; }\n",
5795 );
5796 assert!(!text.contains("owns"), "{text}");
5797 }
5798
5799 #[test]
5800 fn an_inner_records_trailing_padding_reaches_the_outer_records() {
5801 let text = padded_ir(
5806 Padding::Ignored,
5807 "struct inner { char c; };\n\
5808 struct outer { struct inner in; int x; };\n\
5809 void fill(struct outer *p) { p->in.c = 1; p->x = 2; }\n",
5810 );
5811 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
5812 }
5813
5814 #[test]
5815 fn a_build_that_did_not_ask_for_the_monitor_is_compiled_the_way_it_always_was() {
5816 let text = ir(READS_THROUGH_A_POINTER);
5820 assert!(!text.contains("check_"), "{text}");
5821 assert!(!text.contains("cap_of"), "{text}");
5822 }
5823
5824 #[test]
5825 fn asking_for_a_tier_puts_the_checks_in_before_the_optimizer_sees_them() {
5826 let text = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5827 assert!(text.contains("cap_of"), "{text}");
5828 assert!(text.contains("check_bounds"), "{text}");
5829 assert!(text.contains("check_live"), "{text}");
5830 assert!(text.contains("check_deriv"), "{text}");
5832 assert!(text.contains("check_type"), "{text}");
5834 }
5835
5836 #[test]
5837 fn the_three_tiers_that_are_not_off_all_check_the_same_accesses_so_far() {
5838 let detect = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5842 for tier in [rucc_session::Safety::Enforce, rucc_session::Safety::Kernel] {
5843 assert_eq!(safe_ir(tier, READS_THROUGH_A_POINTER), detect, "{tier}");
5844 }
5845 }
5846
5847 fn summary(tier: rucc_session::Safety, source: &str) -> String {
5849 let mut opts = options();
5850 opts.emit = EmitKind::SafetySummary;
5851 opts.safety = tier;
5852 let result = run(&opts, source);
5853 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5854 result.text().to_owned()
5855 }
5856
5857 #[test]
5858 fn the_summary_counts_the_checks_that_went_in_and_the_ones_still_standing() {
5859 let text = summary(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5860 assert!(text.contains("\"tier\": \"detect\""), "{text}");
5861 assert!(
5863 text.contains("\"bounds\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"),
5864 "{text}"
5865 );
5866 assert!(
5867 text.contains(
5868 "\"derivation\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"
5869 ),
5870 "{text}"
5871 );
5872 }
5873
5874 #[test]
5875 fn a_build_without_the_monitor_summarises_as_a_build_with_no_checks_in_it() {
5876 let text = summary(rucc_session::Safety::Off, READS_THROUGH_A_POINTER);
5880 assert!(text.contains("\"tier\": \"off\""), "{text}");
5881 assert!(
5882 text.contains("\"bounds\": { \"emitted\": 0, \"remaining\": 0, \"discharged\": 0 }"),
5883 "{text}"
5884 );
5885 }
5886
5887 #[test]
5888 fn a_call_the_boundary_models_is_counted_apart_from_one_it_does_not() {
5889 let text = summary(
5890 rucc_session::Safety::Detect,
5891 "void *memcpy(void *, const void *, unsigned long);\n\
5892 int puts(const char *);\n\
5893 void f(char *d, char *s) { memcpy(d, s, 4); puts(d); }\n",
5894 );
5895 assert!(text.contains("\"interposed\": 1"), "{text}");
5896 assert!(text.contains("\"puts\""), "{text}");
5897 assert!(!text.contains("__rucc_wrap_memcpy\""), "{text}");
5901 }
5902
5903 #[test]
5904 fn an_address_taken_of_a_library_function_is_counted_the_way_a_call_to_one_is() {
5905 let text = summary(
5910 rucc_session::Safety::Detect,
5911 "void *memcpy(void *, const void *, unsigned long);\n\
5912 int puts(const char *);\n\
5913 void *table[2] = { (void *)memcpy, (void *)puts };\n\
5914 void *f(int i) { return table[i]; }\n",
5915 );
5916 assert!(text.contains("\"interposed\": 1"), "{text}");
5917 assert!(text.contains("\"puts\""), "{text}");
5918 assert!(!text.contains("\"memcpy\""), "{text}");
5919 }
5920
5921 #[test]
5922 fn the_two_directions_a_pointer_crosses_the_boundary_are_counted_apart() {
5923 let text = summary(
5927 rucc_session::Safety::Detect,
5928 "void *notes_open(void);\n\
5929 char *f(char *p) { char *q = notes_open(); return q ? q : p; }\n",
5930 );
5931 assert!(text.contains("\"crossings\": { \"entered\": 1, \"returned\": 1 }"), "{text}");
5932 assert!(text.contains("\"notes_open\""), "{text}");
5933 }
5934
5935 #[test]
5936 fn a_static_function_nobody_takes_the_address_of_is_not_a_crossing() {
5937 let text = summary(
5940 rucc_session::Safety::Detect,
5941 "static int len(const char *p) { return p ? 1 : 0; }\n\
5942 int f(void) { return len(\"x\"); }\n",
5943 );
5944 assert!(text.contains("\"crossings\": { \"entered\": 0, \"returned\": 0 }"), "{text}");
5945 }
5946
5947 fn granules(source: &str) -> String {
5949 let mut opts = options();
5950 opts.emit = EmitKind::TypeGranules;
5951 let result = run(&opts, source);
5952 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5953 result.text().to_owned()
5954 }
5955
5956 #[test]
5957 fn the_granule_report_names_every_record_and_both_keyings() {
5958 let text = granules(
5959 "struct hot { char *p; int a; int b; };\n\
5960 int f(struct hot *h) { return h->a; }\n",
5961 );
5962 assert!(text.contains("struct hot"), "{text}");
5963 assert!(text.contains("every type distinct"), "{text}");
5966 assert!(text.contains("every pointer one type"), "{text}");
5967 assert!(text.contains("budget"), "{text}");
5968 }
5969
5970 #[test]
5971 fn a_record_nothing_uses_is_still_measured() {
5972 let text = granules("struct unused { long a; double b; };\nint f(void) { return 0; }\n");
5975 assert!(text.contains("struct unused"), "{text}");
5976 }
5977
5978 #[test]
5979 fn the_granule_report_stops_before_anything_is_lowered() {
5980 let text = granules(
5984 "struct wide { long double d; };\n\
5985 long double f(long double x) { return x * x; }\n",
5986 );
5987 assert!(text.contains("struct wide"), "{text}");
5988 }
5989
5990 #[test]
5991 fn a_witness_reaches_the_assembler_as_a_call_to_the_runtime() {
5992 let text = safe_asm(rucc_session::Safety::Detect, "char *f(char *p) { return p; }\n");
5995 assert!(text.contains("\tcall\t__rucc_cap_witness\n"), "{text}");
5996 }
5997
5998 #[test]
5999 fn a_pointer_turned_into_an_integer_is_on_the_trust_set() {
6000 let text = summary(
6001 rucc_session::Safety::Detect,
6002 "unsigned long f(int *p) { return (unsigned long) p; }\n",
6003 );
6004 assert!(text.contains("\"exposed\": 1"), "{text}");
6005 }
6006
6007 fn safe_asm(tier: rucc_session::Safety, source: &str) -> String {
6009 let mut opts = options();
6010 opts.emit = EmitKind::Asm;
6011 opts.safety = tier;
6012 let result = run(&opts, source);
6013 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
6014 result.text().to_owned()
6015 }
6016
6017 #[test]
6018 fn a_check_reaches_the_assembler_as_a_call_to_the_runtime() {
6019 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
6020 assert!(text.contains("\tcall\t__rucc_check_bounds\n"), "{text}");
6021 assert!(text.contains("\tcall\t__rucc_check_live\n"), "{text}");
6022 assert!(text.contains("\tcall\t__rucc_check_deriv\n"), "{text}");
6023 assert!(text.contains("\tcall\t__rucc_check_typed_init\n"), "{text}");
6026 }
6027
6028 #[test]
6029 fn every_check_that_reached_the_assembler_has_a_row_describing_it() {
6030 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
6036 let section = format!("\t.section\t{},", rucc_safety::SECTION);
6037 assert_eq!(text.matches(§ion).count(), 4, "{text}");
6038 for index in 0..4 {
6039 let name = format!("__rucc_safety_desc_{index}");
6040 assert!(text.contains(&format!("{name}:\n")), "{text}");
6043 assert!(text.contains(&format!("{name}(%rip)")), "{text}");
6044 }
6045 assert!(!text.contains("__rucc_safety_desc_4"), "{text}");
6046 }
6047
6048 #[test]
6056 fn builtin_constant_p_is_folded_where_it_is_written_rather_than_called() {
6057 let text = ir(concat!(
6058 "int g;\n",
6059 "int a = __builtin_constant_p(1);\n",
6060 "int b = __builtin_constant_p(g);\n",
6061 "int c = __builtin_constant_p(\"abc\");\n",
6062 "int d = __builtin_constant_p(&g);\n",
6063 "int e = __builtin_constant_p(1.5);\n",
6064 "int h = __builtin_choose_expr(__builtin_constant_p(3), 11, 22);\n",
6065 ));
6066 assert!(text.contains("global @a : i32 = 1,"), "{text}");
6067 assert!(text.contains("global @b : i32 = 0,"), "{text}");
6068 assert!(text.contains("global @c : i32 = 1,"), "{text}");
6069 assert!(text.contains("global @d : i32 = 0,"), "{text}");
6070 assert!(text.contains("global @e : i32 = 1,"), "{text}");
6071 assert!(text.contains("global @h : i32 = 11,"), "{text}");
6072 assert!(!text.contains("__builtin_constant_p"), "it is not a call to anything:\n{text}");
6073
6074 let text = body("int f(void) { int i = 0; __builtin_constant_p(i++); return i; }\n");
6078 assert_eq!(text, "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 0\n return %0\n");
6079 }
6080
6081 #[test]
6090 fn a_call_to_a_library_builtin_reaches_the_library_function() {
6091 let text = body("void f(void) { __builtin_abort(); }\n");
6092 assert_eq!(text, "block0:\n call @abort() : ()\n return\n");
6093
6094 let text = ir("int f(const char *s) { return __builtin_puts(s) + __builtin_strlen(s); }\n");
6097 assert!(text.contains("call @puts(%0) : (ptr) -> i32"), "{text}");
6098 assert!(text.contains("call @strlen(%0) : (ptr) -> i64"), "{text}");
6099 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
6100 }
6101
6102 #[test]
6115 fn a_chk_builtin_reaches_the_checking_function_and_keeps_the_size() {
6116 let text = ir(concat!(
6117 "char d[8];\n",
6118 "void f(const char *s, unsigned long n) {\n",
6119 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
6120 " __builtin___strcpy_chk(d, s, __builtin_object_size(d, 1));\n",
6121 " __builtin___memset_chk(d, 0, n, 8);\n",
6122 "}\n",
6123 ));
6124 assert!(text.contains("call @__memcpy_chk("), "{text}");
6125 assert!(text.contains("call @__strcpy_chk("), "{text}");
6126 assert!(text.contains("call @__memset_chk("), "{text}");
6127 assert!(text.contains("iconst.i64 8"), "the object size reaches the call: {text}");
6128 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
6129 }
6130
6131 #[test]
6139 fn a_checking_call_whose_size_says_nothing_is_known_is_the_plain_library_call() {
6140 let text = ir(concat!(
6141 "extern char *p;\n",
6142 "char d[8];\n",
6143 "void f(const char *s, unsigned long n) {\n",
6144 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
6145 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
6146 " __builtin___strcpy_chk(p, s, __builtin_object_size(p, 0));\n",
6147 " __builtin___stpncpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
6148 " __builtin___sprintf_chk(p, 1, __builtin_object_size(p, 0), s);\n",
6149 "}\n",
6150 ));
6151
6152 assert!(
6154 text.contains("call @__memcpy_chk(%2, %0, %1, %3) : (ptr, ptr, i64, i64)"),
6155 "{text}"
6156 );
6157
6158 assert!(text.contains("call @memcpy(%6, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
6161 assert!(text.contains("call @strcpy(%10, %0) : (ptr, ptr) -> ptr"), "{text}");
6162 assert!(text.contains("call @stpncpy(%14, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
6163
6164 assert!(text.contains("call @__sprintf_chk("), "{text}");
6167
6168 let asm = asm(concat!(
6171 "void f(char *p, const char *s, unsigned long n) {\n",
6172 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
6173 "}\n",
6174 ));
6175 assert!(asm.contains("call\tmemcpy"), "{asm}");
6176 assert!(!asm.contains("$-1"), "the size that went away leaves no instruction:\n{asm}");
6177 }
6178
6179 #[test]
6187 fn the_v_spellings_of_the_chk_family_take_the_list_a_va_list_parameter_holds() {
6188 let text = ir(concat!(
6189 "char d[64];\n",
6190 "int f(const char *fmt, ...) {\n",
6191 " __builtin_va_list ap;\n",
6192 " __builtin_va_start(ap, fmt);\n",
6193 " int n = __builtin___vsprintf_chk(d, 1, __builtin_object_size(d, 0), fmt, ap);\n",
6194 " __builtin_va_end(ap);\n",
6195 " return n;\n",
6196 "}\n",
6197 ));
6198 assert!(text.contains("call @__vsprintf_chk("), "{text}");
6199 assert!(text.contains("iconst.i64 64"), "the object size reaches the call: {text}");
6200 }
6201
6202 #[test]
6213 fn the_absolute_value_family_is_the_magnitude_and_not_a_call() {
6214 let text = body(concat!(
6215 "long long llabs(long long);\n",
6216 "long long f(long long x) { return llabs(x); }\n",
6217 ));
6218 assert!(text.contains("%1 = iconst.i64 63"), "{text}");
6219 assert!(text.contains("%2 = ashr %0, %1"), "{text}");
6220 assert!(text.contains("%3 = xor %0, %2"), "{text}");
6221 assert!(text.contains("%4 = sub %3, %2"), "{text}");
6222 assert!(!text.contains("call"), "the call does not happen:\n{text}");
6223
6224 let text = body("int abs(int);\nint f(int x) { return abs(x); }\n");
6227 assert!(text.contains("iconst.i32 31"), "{text}");
6228 let text = body("long labs(long);\nlong f(long x) { return labs(x); }\n");
6229 assert!(text.contains("iconst.i64 63"), "{text}");
6230
6231 let text = body("long long f(long long x) { return __builtin_llabs(x); }\n");
6234 assert!(!text.contains("call"), "{text}");
6235
6236 let text = ir(concat!(
6238 "long long llabs(long long b);\n",
6239 "long long g(long long x) { return llabs(x); }\n",
6240 "long long llabs(long long b) { return 7; }\n",
6241 ));
6242 assert!(!text.contains("call @llabs"), "{text}");
6243 }
6244
6245 #[test]
6252 fn a_byte_swap_is_arithmetic_and_not_a_call() {
6253 let text = body("unsigned f(unsigned x) { return __builtin_bswap32(x); }\n");
6254 assert_eq!(text, "block0(%0: i32):\n %1 = bswap %0\n return %1\n");
6255
6256 let text = body("unsigned f(unsigned char c) { return __builtin_bswap32(c); }\n");
6259 assert!(text.contains("zext.i32 %0"), "widened first: {text}");
6260 assert!(text.contains("bswap %1"), "and swapped at four bytes: {text}");
6261 }
6262
6263 #[test]
6269 fn the_byte_swaps_reverse_at_the_width_their_name_says() {
6270 for (name, ty, width) in [
6271 ("__builtin_bswap16", "unsigned short", "i16"),
6272 ("__builtin_bswap32", "unsigned", "i32"),
6273 ("__builtin_bswap64", "unsigned long long", "i64"),
6274 ] {
6275 let source = format!("{ty} f({ty} x) {{ return {name}(x); }}\n");
6276 let text = body(&source);
6277 assert_eq!(
6278 text,
6279 format!("block0(%0: {width}):\n %1 = bswap %0\n return %1\n"),
6280 "{name}"
6281 );
6282 }
6283 }
6284
6285 #[test]
6292 fn the_bit_counts_are_instructions_and_not_calls() {
6293 let text = body("int f(unsigned x) { return __builtin_clz(x); }\n");
6294 assert_eq!(text, "block0(%0: i32):\n %1 = ctlz %0\n return %1\n");
6295
6296 let text = body("int f(unsigned x) { return __builtin_ctz(x); }\n");
6297 assert_eq!(text, "block0(%0: i32):\n %1 = cttz %0\n return %1\n");
6298
6299 let text = body("int f(unsigned x) { return __builtin_popcount(x); }\n");
6300 assert_eq!(text, "block0(%0: i32):\n %1 = ctpop %0\n return %1\n");
6301 }
6302
6303 #[test]
6312 fn the_bit_counts_ask_about_the_width_their_name_says() {
6313 let text = body("int f(unsigned long long x) { return __builtin_clzll(x); }\n");
6314 assert!(text.starts_with("block0(%0: i64):"), "counted at eight bytes: {text}");
6315 assert!(text.contains("%1 = ctlz %0"), "{text}");
6316 assert!(text.contains("trunc.i32 %1"), "and answered in an int: {text}");
6317
6318 let text = body("int f(unsigned long long x) { return __builtin_clz(x); }\n");
6321 assert!(text.contains("trunc.i32 %0"), "narrowed to what was asked about: {text}");
6322 assert!(text.contains("ctlz %1"), "and counted there: {text}");
6323
6324 let text = body("int f(unsigned long x) { return __builtin_popcountl(x); }\n");
6325 assert!(text.contains("%1 = ctpop %0"), "{text}");
6326 assert!(!text.contains("call"), "{text}");
6327 }
6328
6329 #[test]
6334 fn a_parity_is_the_low_bit_of_the_set_bit_count() {
6335 let text = body("int f(unsigned x) { return __builtin_parity(x); }\n");
6336 assert!(text.contains("%1 = ctpop %0"), "{text}");
6337 assert!(text.contains("iconst.i32 1"), "{text}");
6338 assert!(text.contains("and %1, %2"), "the low bit of it: {text}");
6339 }
6340
6341 #[test]
6347 fn the_first_set_bit_is_one_based_and_zero_for_a_zero() {
6348 let text = body("int f(int x) { return __builtin_ffs(x); }\n");
6349 assert!(text.contains("%1 = cttz %0"), "{text}");
6350 assert!(text.contains("%4 = add %1, %2"), "one more than the count: {text}");
6351 assert!(text.contains("%5 = icmp ne %0, %3"), "whether there was a bit at all: {text}");
6352 assert!(text.contains("%7 = sub %3, %6"), "spread to a mask: {text}");
6353 assert!(text.contains("%8 = and %4, %7"), "and kept only then: {text}");
6354 assert!(!text.contains("br_if"), "no branch: {text}");
6355 }
6356
6357 #[test]
6367 fn the_redundant_sign_bit_count_is_instructions_and_not_a_call() {
6368 let text = body("int f(int x) { return __builtin_clrsb(x); }\n");
6369 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
6370 assert!(text.contains("%2 = ashr %0, %1"), "the sign over every bit: {text}");
6371 assert!(text.contains("%3 = xor %0, %2"), "folded onto it: {text}");
6372 assert!(text.contains("%5 = shl %3, %4"), "one less than the count: {text}");
6373 assert!(text.contains("%6 = or %5, %4"), "with something to count at zero: {text}");
6374 assert!(text.contains("%7 = ctlz %6"), "{text}");
6375 assert!(!text.contains("call"), "{text}");
6376 assert!(!text.contains("br_if"), "no branch: {text}");
6377 }
6378
6379 #[test]
6385 fn the_unsigned_absolute_value_family_answers_in_the_unsigned_type() {
6386 let text = body("unsigned f(int x) { return __builtin_uabs(x); }\n");
6387 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
6388 assert!(text.contains("%4 = sub %3, %2"), "{text}");
6389 assert!(!text.contains("call"), "nothing declares uabs, so a call would not link: {text}");
6390
6391 let text = body("unsigned long long f(long long x) { return __builtin_ullabs(x); }\n");
6392 assert!(text.contains("iconst.i64 63"), "at the width the name says: {text}");
6393
6394 let text = body("int f(int x) { return __builtin_uabs(x) > 2147483647u; }\n");
6397 assert!(text.contains("icmp ugt"), "compared unsigned: {text}");
6398 }
6399
6400 #[test]
6408 fn the_widest_absolute_value_is_whichever_type_the_target_makes_intmax_t() {
6409 let text = body("long f(long x) { return __builtin_imaxabs(x); }\n");
6410 assert!(text.contains("iconst.i64 63"), "{text}");
6411 assert!(text.contains("%4 = sub %3, %2"), "{text}");
6412 assert!(!text.contains("call"), "{text}");
6413
6414 let text = body("unsigned long f(long x) { return __builtin_umaxabs(x); }\n");
6415 assert!(text.contains("iconst.i64 63"), "{text}");
6416 assert!(!text.contains("call"), "{text}");
6417 }
6418
6419 #[test]
6427 fn an_overflow_predicate_writes_nothing_and_answers_the_bit_the_check_would() {
6428 let text =
6429 body("int f(int a, int b) { return __builtin_add_overflow_p(a, b, (int) 0); }\n");
6430 assert!(text.contains("%2, %3 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
6431 assert!(!text.contains("store"), "nothing is written: {text}");
6432 assert!(!text.contains("call"), "{text}");
6433
6434 let text =
6437 body("int f(int a, int b) { return __builtin_mul_overflow_p(a, b, (long long) 0); }\n");
6438 assert!(text.contains("smul_overflow.(i64, i1)"), "{text}");
6439 assert!(!text.contains("store"), "{text}");
6440
6441 let text = body(concat!(
6444 "int g(void);\n",
6445 "int f(int a, int b) { return __builtin_sub_overflow_p(a, b, g()); }\n",
6446 ));
6447 assert!(!text.contains("call @g"), "the third argument is not evaluated: {text}");
6448 }
6449
6450 #[test]
6460 fn an_overflow_check_is_arithmetic_and_not_a_call() {
6461 let text =
6462 body("int f(int a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
6463 assert!(text.contains("%3, %4 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
6464 assert!(text.contains("store %3 -> %2"), "{text}");
6465 assert!(!text.contains("call"), "{text}");
6466
6467 let text =
6468 body("int f(int a, int b, int *r) { return __builtin_sub_overflow(a, b, r); }\n");
6469 assert!(text.contains("ssub_overflow.(i32, i1) %0, %1"), "{text}");
6470
6471 let text =
6472 body("int f(int a, int b, int *r) { return __builtin_mul_overflow(a, b, r); }\n");
6473 assert!(text.contains("smul_overflow.(i32, i1) %0, %1"), "{text}");
6474
6475 let text = body(
6478 "int f(unsigned a, unsigned b, unsigned *r) { return __builtin_add_overflow(a, b, r); }\n",
6479 );
6480 assert!(text.contains("uadd_overflow.(i32, i1) %0, %1"), "{text}");
6481 }
6482
6483 #[test]
6491 fn an_overflow_check_is_done_at_a_type_that_holds_every_operand() {
6492 let text = body(
6493 "int f(unsigned a, int b, long long *r) { return __builtin_add_overflow(a, b, r); }\n",
6494 );
6495 assert!(text.contains("%3 = zext.i64 %0"), "the unsigned operand keeps its value: {text}");
6496 assert!(text.contains("%4 = sext.i64 %1"), "and so does the signed one: {text}");
6497 assert!(text.contains("sadd_overflow.(i64, i1) %3, %4"), "{text}");
6498
6499 let text = body(
6502 "int f(long long a, long long b, long long *r) { return __builtin_mul_overflow(a, b, r); }\n",
6503 );
6504 assert!(text.contains("smul_overflow.(i64, i1) %0, %1"), "{text}");
6505 assert!(!text.contains("sext."), "{text}");
6506 assert!(!text.contains("zext.i64"), "{text}");
6508 }
6509
6510 #[test]
6518 fn an_overflow_check_writes_the_wrapped_answer_whether_or_not_it_fit() {
6519 let text =
6520 body("int f(int a, int b, char *r) { return __builtin_sub_overflow(a, b, r); }\n");
6521 assert!(text.contains("%3, %4 = ssub_overflow.(i32, i1) %0, %1"), "{text}");
6522 assert!(text.contains("%5 = trunc.i8 %3"), "narrowed to where it goes: {text}");
6523 assert!(text.contains("%6 = sext.i32 %5"), "and back: {text}");
6524 assert!(text.contains("%7 = icmp ne %6, %3"), "which is whether it fit: {text}");
6525 assert!(text.contains("store %5 -> %2"), "the narrowed value is stored either way: {text}");
6526 assert!(text.contains("%8 = or %4, %7"), "and either bit is an overflow: {text}");
6527 }
6528
6529 #[test]
6536 fn a_call_needing_more_than_the_widest_type_still_compiles() {
6537 for name in ["add", "sub", "mul"] {
6538 let source = format!(
6539 "int f(unsigned __int128 a, long long b, __int128 *r) {{\n \
6540 return __builtin_{name}_overflow(a, b, r);\n}}\n"
6541 );
6542 let mut opts = options();
6543 opts.emit = EmitKind::MirFinal;
6544 assert!(!run(&opts, &source).failed(), "{name} was refused or stopped the back end");
6545 }
6546 }
6547
6548 #[test]
6551 fn an_overflow_check_over_something_that_is_not_an_integer_says_so() {
6552 let messages =
6553 errors("int f(double a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
6554 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
6555
6556 let messages =
6557 errors("int f(int a, int b, double *r) { return __builtin_add_overflow(a, b, r); }\n");
6558 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
6559 }
6560
6561 #[test]
6572 fn an_ordered_access_is_ordered_in_the_ir() {
6573 let text = body("int f(int *p) { return __atomic_load_n(p, 0); }\n");
6574 assert!(text.contains("atomic_load.i32 %0, align 4, relaxed"), "{text}");
6575
6576 let text = body("long f(long *p) { return __atomic_load_n(p, 2); }\n");
6577 assert!(text.contains("atomic_load.i64 %0, align 8, acquire"), "{text}");
6578
6579 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
6580 assert!(text.contains("atomic_store %1 -> %0, align 4, release"), "{text}");
6581
6582 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
6583 assert!(text.contains("atomic_store %1 -> %0, align 4, seq_cst"), "{text}");
6584
6585 let text = body("void f(char *p, int v) { __atomic_store_n(p, v, 0); }\n");
6588 assert!(text.contains("trunc.i8 %1"), "{text}");
6589 assert!(text.contains("atomic_store %2 -> %0, align 1, relaxed"), "{text}");
6590 }
6591
6592 #[test]
6601 fn an_ordered_access_is_the_plain_instruction_on_this_machine() {
6602 let text = asm("int f(int *p) { return __atomic_load_n(p, 5); }\n");
6603 assert!(text.contains("movl\t(%rdi), %eax"), "{text}");
6604 assert!(!text.contains("mfence"), "a load needs no barrier here: {text}");
6605
6606 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
6607 assert!(text.contains("movl\t%esi, (%rdi)"), "{text}");
6608 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
6609
6610 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
6611 let (before, after) = text.split_once("mfence").expect("a barrier: {text}");
6612 assert!(before.contains("movl\t%esi, (%rdi)"), "the store comes first: {text}");
6613 assert!(!after.contains("movl"), "and nothing else is between them: {text}");
6614 }
6615
6616 #[test]
6626 fn a_barrier_is_one_instruction_at_the_strongest_ordering_and_none_below_it() {
6627 assert!(asm("void f(void) { __atomic_thread_fence(5); }\n").contains("mfence"));
6628 assert!(asm("void f(void) { __sync_synchronize(); }\n").contains("mfence"));
6629
6630 for weaker in ["1", "2", "3", "4"] {
6631 let source = format!("void f(void) {{ __atomic_thread_fence({weaker}); }}\n");
6632 assert!(!asm(&source).contains("mfence"), "{weaker} costs nothing here");
6633 }
6634 }
6635
6636 #[test]
6646 fn the_three_x86_fences_are_the_barrier_the_strongest_ordering_gives() {
6647 for name in ["__builtin_ia32_sfence", "__builtin_ia32_lfence", "__builtin_ia32_mfence"] {
6648 let source = format!("void f(void) {{ {name}(); }}\n");
6649 assert!(asm(&source).contains("mfence"), "{name} is a barrier");
6650 let text = body(&source);
6651 assert!(text.contains("fence seq_cst"), "{name}: {text}");
6652 }
6653
6654 let result = run(&options(), "void f(void) { __builtin_ia32_sfence(1); }\n");
6655 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
6656 assert!(result.messages[0].contains("too many arguments"), "{:?}", result.messages);
6657 }
6658
6659 #[test]
6665 fn a_compare_and_exchange_is_one_instruction_answering_two_things() {
6666 let text =
6669 body("int f(int *p, int e, int d) { return __sync_val_compare_and_swap(p, e, d); }\n");
6670 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
6671 assert!(text.contains("return %3"), "the value it found: {text}");
6672
6673 let text =
6674 body("int f(int *p, int e, int d) { return __sync_bool_compare_and_swap(p, e, d); }\n");
6675 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
6676 assert!(text.contains("zext.i32 %4"), "whether it happened: {text}");
6677
6678 let text = body(
6681 "int f(int *p, int *e, int d) { return __atomic_compare_exchange_n(p, e, d, 0, 4, 2); }\n",
6682 );
6683 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
6684 assert!(text.contains("%4, %5 = cmpxchg.(i32, i1) %0, %3, %2, align 4, acq_rel"), "{text}");
6685 assert!(text.contains("br_if %5, block2, block1"), "{text}");
6686 assert!(text.contains("store %4 -> %1, align 4"), "{text}");
6687
6688 let text = body(
6691 "int f(int *p, int *e, int *d) { return __atomic_compare_exchange(p, e, d, 0, 5, 5); }\n",
6692 );
6693 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
6694 assert!(text.contains("%4 = load.i32 %2, align 4"), "{text}");
6695 assert!(text.contains("%5, %6 = cmpxchg.(i32, i1) %0, %3, %4, align 4, seq_cst"), "{text}");
6696 }
6697
6698 #[test]
6705 fn a_compare_and_exchange_is_a_locked_instruction_at_the_width_of_the_object() {
6706 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
6707 for (ty, suffix, reg) in widths {
6708 let source = format!(
6709 "int f({ty} *p, {ty} e, {ty} d) {{ return __sync_bool_compare_and_swap(p, e, d); }}\n"
6710 );
6711 let text = asm(&source);
6712 assert!(text.contains("\tlock\n"), "{ty}: {text}");
6713 assert!(text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
6714 assert!(text.contains("sete\t"), "{ty}: {text}");
6715 }
6716 let source =
6717 "int f(long *p, long e, long d) { return __sync_bool_compare_and_swap(p, e, d); }\n";
6718 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
6719
6720 for order in ["0", "2", "3", "4", "5"] {
6724 let call = format!("__atomic_compare_exchange_n(p, e, d, 0, {order}, 0)");
6725 let source = format!("int f(int *p, int *e, int d) {{ return {call}; }}\n");
6726 let text = asm(&source);
6727 assert!(text.contains("cmpxchgl\t"), "{order}: {text}");
6728 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
6729 }
6730 }
6731
6732 #[test]
6744 fn a_read_modify_write_is_one_instruction_and_the_arithmetic_a_name_asks_for() {
6745 let text = body("int f(int *p, int v) { return __atomic_fetch_add(p, v, 5); }\n");
6746 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
6747 assert!(text.contains("return %2"), "the value that was there: {text}");
6748
6749 let text = body("int f(int *p, int v) { return __atomic_add_fetch(p, v, 5); }\n");
6750 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
6751 assert!(text.contains("%3 = add %2, %1"), "and the value afterwards: {text}");
6752
6753 let text = body("int f(int *p, int v) { return __atomic_sub_fetch(p, v, 5); }\n");
6754 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
6755 assert!(text.contains("%3 = sub %2, %1"), "{text}");
6756
6757 let text = body("int f(int *p, int v) { return __sync_fetch_and_sub(p, v); }\n");
6759 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
6760
6761 let text = body("int f(int *p, int v) { return __atomic_exchange_n(p, v, 5); }\n");
6764 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, seq_cst"), "{text}");
6765
6766 let text = body("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
6767 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, acquire"), "{text}");
6768
6769 let text = body("void f(int *p) { __sync_lock_release(p); }\n");
6772 assert!(text.contains("release"), "{text}");
6773 assert!(text.contains("%1 = iconst.i32 0"), "{text}");
6774
6775 let text = body("void f(int *p, int guard) { __sync_lock_release(p, guard); }\n");
6779 assert!(text.contains("%2 = iconst.i32 0"), "{text}");
6780 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
6781
6782 let text = body("int f(int *p, int v) { return __atomic_fetch_and(p, v, 5); }\n");
6785 assert!(text.contains("%2 = atomic_rmw.i32 and %0, %1, align 4, seq_cst"), "{text}");
6786
6787 let text = body("int f(int *p, int v) { return __sync_or_and_fetch(p, v); }\n");
6788 assert!(text.contains("%2 = atomic_rmw.i32 or %0, %1, align 4, seq_cst"), "{text}");
6789 assert!(text.contains("%3 = or %2, %1"), "and the value afterwards: {text}");
6790
6791 let text = body("int f(int *p, int v) { return __atomic_nand_fetch(p, v, 5); }\n");
6794 assert!(text.contains("%2 = atomic_rmw.i32 nand %0, %1, align 4, seq_cst"), "{text}");
6795 assert!(text.contains("%3 = and %2, %1"), "{text}");
6796 assert!(text.contains("%4 = iconst.i32 -1"), "{text}");
6797 assert!(text.contains("%5 = xor %3, %4"), "{text}");
6798 }
6799
6800 #[test]
6811 fn a_bitwise_read_modify_write_is_a_loop_around_the_compare_and_exchange() {
6812 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
6813 for (ty, suffix, reg) in widths {
6814 for (name, call, insn) in [
6815 ("and", "__atomic_fetch_and(p, v, 5)", "and"),
6816 ("or", "__sync_fetch_and_or(p, v)", "or"),
6817 ("xor", "__atomic_xor_fetch(p, v, 5)", "xor"),
6818 ] {
6819 let source = format!("{ty} f({ty} *p, {ty} v) {{ return {call}; }}\n");
6820 let text = asm(&source);
6821 assert!(text.contains("\tlock\n"), "{ty} {name}: {text}");
6822 assert!(
6823 text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")),
6824 "{ty} {name}: {text}"
6825 );
6826 assert!(text.contains(&format!("{insn}{suffix}\t")), "{ty} {name}: {text}");
6827 assert!(!text.contains("\txadd"), "{ty} {name} is not an add: {text}");
6829 assert!(!text.contains("\txchg"), "{ty} {name} is not an exchange: {text}");
6830 }
6831 }
6832 let source = "long f(long *p, long v) { return __atomic_fetch_or(p, v, 5); }\n";
6833 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
6834
6835 let text = asm("int f(int *p, int v) { return __sync_fetch_and_nand(p, v); }\n");
6839 assert!(text.contains("cmpxchgl\t"), "{text}");
6840 assert!(text.contains("andl\t"), "{text}");
6841 assert!(text.contains("notl\t"), "{text}");
6842 }
6843
6844 #[test]
6853 fn an_access_through_a_second_pointer_is_the_same_access_and_one_more() {
6854 let text = body("void f(int *p, int *r) { __atomic_load(p, r, 5); }\n");
6855 assert!(text.contains("%2 = atomic_load.i32 %0, align 4, seq_cst"), "{text}");
6856 assert!(text.contains("store %2 -> %1, align 4"), "and out through the place: {text}");
6857
6858 let text = body("void f(int *p, int *v) { __atomic_store(p, v, 3); }\n");
6859 assert!(text.contains("%2 = load.i32 %1, align 4"), "in through the place: {text}");
6860 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
6861
6862 let text = body("void f(int *p, int *v, int *r) { __atomic_exchange(p, v, r, 5); }\n");
6865 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
6866 assert!(text.contains("%4 = atomic_rmw.i32 xchg %0, %3, align 4, seq_cst"), "{text}");
6867 assert!(text.contains("store %4 -> %2, align 4"), "{text}");
6868 }
6869
6870 #[test]
6881 fn a_flag_is_an_exchange_of_one_byte_and_a_store_of_a_zero_over_the_same_byte() {
6882 for pointer in ["char", "int", "void"] {
6883 let source = format!("int f({pointer} *p) {{ return __atomic_test_and_set(p, 5); }}\n");
6884 let text = body(&source);
6885 assert!(text.contains("%1 = iconst.i8 1"), "{pointer}: {text}");
6886 assert!(
6887 text.contains("%2 = atomic_rmw.i8 xchg %0, %1, align 1, seq_cst"),
6888 "{pointer}: {text}"
6889 );
6890 assert!(text.contains("%4 = icmp ne %2, %3"), "{pointer}: {text}");
6891
6892 let source = format!("void f({pointer} *p) {{ __atomic_clear(p, 3); }}\n");
6893 let text = body(&source);
6894 assert!(text.contains("atomic_store %2 -> %0, align 1, release"), "{pointer}: {text}");
6895 }
6896
6897 let text = asm("int f(int *p) { return __atomic_test_and_set(p, 5); }\n");
6900 assert!(text.contains("xchgb\t%al, (%rdi)"), "{text}");
6901 assert!(text.contains("setne\t"), "{text}");
6902 }
6903
6904 #[test]
6912 fn a_read_modify_write_is_an_exchange_or_a_locked_add_at_the_width_of_the_object() {
6913 let widths = [("char", "b", "%sil"), ("short", "w", "%si"), ("int", "l", "%esi")];
6914 for (ty, suffix, reg) in widths {
6915 let source =
6916 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_fetch_add(p, v, 5); }}\n");
6917 let text = asm(&source);
6918 assert!(text.contains("\tlock\n"), "{ty}: {text}");
6919 assert!(text.contains(&format!("xadd{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
6920
6921 let source =
6922 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_exchange_n(p, v, 5); }}\n");
6923 let text = asm(&source);
6924 assert!(text.contains(&format!("xchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
6925 assert!(!text.contains("\tlock\n"), "an exchange is locked already: {ty}: {text}");
6926 }
6927 let source = "long f(long *p, long v) { return __atomic_fetch_add(p, v, 5); }\n";
6928 assert!(asm(source).contains("xaddq\t%rsi, (%rdi)"), "{}", asm(source));
6929
6930 let source = "int f(int *p, int v) { return __atomic_fetch_sub(p, v, 5); }\n";
6933 let text = asm(source);
6934 assert!(text.contains("negl\t"), "{text}");
6935 assert!(text.contains("xaddl\t"), "{text}");
6936
6937 for order in ["0", "2", "3", "4", "5"] {
6940 let source =
6941 format!("int f(int *p, int v) {{ return __atomic_fetch_add(p, v, {order}); }}\n");
6942 let text = asm(&source);
6943 assert!(text.contains("xaddl\t"), "{order}: {text}");
6944 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
6945 }
6946
6947 let text = asm("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
6951 assert!(text.contains("xchgl\t%esi, (%rdi)"), "{text}");
6952 let text = asm("void f(int *p) { __sync_lock_release(p); }\n");
6959 assert!(text.contains("xorl\t%eax, %eax"), "{text}");
6960 assert!(text.contains("movl\t%eax, (%rdi)"), "{text}");
6961 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
6962 }
6963
6964 #[test]
6976 fn the_lock_free_questions_are_answered_as_constants() {
6977 for size in ["1", "2", "4", "8"] {
6978 let source =
6979 format!("int f(void) {{ return __atomic_always_lock_free({size}, 0); }}\n");
6980 let text = asm(&source);
6981 assert!(text.contains("movb\t$1, %al"), "{size} bytes is lock free: {text}");
6982 assert!(!text.contains("call"), "and is not a call: {text}");
6983 }
6984 for size in ["3", "16", "sizeof(long double)"] {
6985 let source = format!("int f(void) {{ return __atomic_is_lock_free({size}, 0); }}\n");
6986 let text = asm(&source);
6987 assert!(text.contains("movb\t$0, %al"), "{size} bytes is not: {text}");
6988 assert!(!text.contains("call"), "and is not a call either: {text}");
6989 }
6990
6991 let text = asm("int f(int n) { return __atomic_is_lock_free(n, 0); }\n");
6995 assert!(text.contains("movb\t$0, %al"), "a size nobody knows is not lock free: {text}");
6996 let text = asm("int f(int *p) { return __atomic_always_lock_free(8, p); }\n");
6997 assert!(text.contains("movb\t$0, %al"), "eight bytes at four is not: {text}");
6998 let text = asm("int f(long *p) { return __atomic_always_lock_free(8, p); }\n");
6999 assert!(text.contains("movb\t$1, %al"), "and at eight it is: {text}");
7000 }
7001
7002 #[test]
7014 fn a_memory_order_an_operation_cannot_carry_is_read_as_the_strongest() {
7015 let mut opts = options();
7016 opts.emit = EmitKind::Ir;
7017
7018 let acquire_store = run(&opts, "void f(int *p, int v) { __atomic_store_n(p, v, 2); }\n");
7019 assert!(acquire_store.text().contains("seq_cst"), "{:?}", acquire_store.text());
7020 assert!(acquire_store.messages[0].contains("[W0333]"), "{:?}", acquire_store.messages);
7021
7022 let nonsense = run(&opts, "int f(int *p) { return __atomic_load_n(p, 99); }\n");
7023 assert!(nonsense.text().contains("seq_cst"), "{:?}", nonsense.text());
7024 assert!(nonsense.messages[0].contains("[W0333]"), "{:?}", nonsense.messages);
7025
7026 let computed = run(&opts, "int f(int *p, int n) { return __atomic_load_n(p, n); }\n");
7027 assert!(computed.text().contains("seq_cst"), "{:?}", computed.text());
7028 assert_eq!(computed.messages, Vec::<String>::new(), "a computed order is not a mistake");
7029 }
7030
7031 #[test]
7043 fn a_conversion_between_a_float_and_the_widest_unsigned_integer_is_written_without_a_branch() {
7044 let text = asm("double f(unsigned long long x) { return (double)x; }\n");
7045 assert!(text.contains("cvtsi2sdq"), "the signed conversion is what runs: {text}");
7046 assert!(text.contains("shrq"), "with the value halved first: {text}");
7047 assert!(text.contains("addsd"), "and doubled after: {text}");
7048 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
7049
7050 let text = asm("unsigned long long f(double d) { return (unsigned long long)d; }\n");
7051 assert!(text.contains("cvttsd2siq"), "the signed conversion is what runs: {text}");
7052 assert!(text.contains("subsd"), "with half the range taken off first: {text}");
7053 assert!(text.contains("shlq\t$63"), "and the top bit put back: {text}");
7054 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
7055 }
7056
7057 #[test]
7068 fn a_plain_name_the_program_took_is_the_programs_own_function() {
7069 let taken = concat!(
7070 "static long long llabs(long long b) { return 7; }\n",
7071 "long long f(long long x) { return llabs(x); }\n",
7072 );
7073 assert!(ir(taken).contains("call @llabs"), "a static definition is the program's own");
7074
7075 let retyped = concat!("int llabs(int b);\n", "int f(int x) { return llabs(x); }\n",);
7076 assert!(ir(retyped).contains("call @llabs"), "another type is another function");
7077
7078 let plain = concat!(
7079 "long long llabs(long long b);\n",
7080 "long long f(long long x) { return llabs(x); }\n",
7081 );
7082 let mut opts = options();
7083 opts.emit = EmitKind::Ir;
7084 assert!(!run(&opts, plain).text().contains("call @llabs"), "the library's by default");
7085
7086 opts.builtins = false;
7087 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin");
7088
7089 opts.builtins = true;
7090 opts.no_builtin = vec!["llabs".to_owned()];
7091 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin-llabs");
7092 let one = "long labs(long b);\nlong f(long x) { return labs(x); }\n";
7093 assert!(!run(&opts, one).text().contains("call @labs"), "one name and not the family");
7094
7095 opts.no_builtin = Vec::new();
7098 opts.builtins = false;
7099 let prefixed = "long long f(long long x) { return __builtin_llabs(x); }\n";
7100 assert!(!run(&opts, prefixed).text().contains("call @llabs"), "the prefix is a promise");
7101 }
7102
7103 #[test]
7116 fn the_hint_builtins_are_their_first_argument_and_the_hint_leaves_no_trace() {
7117 let text = ir(concat!(
7118 "long a = __builtin_expect(7, 1);\n",
7119 "long b = __builtin_expect_with_probability(9, 1, 0.9);\n",
7120 "unsigned long c = sizeof(__builtin_expect((char)1, 1));\n",
7121 ));
7122 assert!(text.contains("global @a : i64 = 7,"), "{text}");
7123 assert!(text.contains("global @b : i64 = 9,"), "{text}");
7124 assert!(text.contains("global @c : i64 = 8,"), "{text}");
7125 assert!(!text.contains("__builtin_expect"), "it is not a call to anything:\n{text}");
7126
7127 let text = body("long f(char c) { return __builtin_expect(c, 1); }\n");
7130 assert!(text.contains("sext"), "{text}");
7131
7132 let one = "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 1\n %2 = sext.i64 %1\n return %0\n";
7136 assert_eq!(body("int f(void) { int i = 0; __builtin_expect(1, i++); return i; }\n"), one);
7137 let source = "int g(void) { int i = 0; __builtin_expect_with_probability(1, i++, 0.5); return i; }\n";
7138 assert_eq!(body(source), one);
7139
7140 let kept = body("int f(int n) { int i = 0; __builtin_expect(n, i++); return i; }\n");
7145 assert!(kept.contains("add.nsw"), "the hint still runs: {kept}");
7146 assert!(kept.ends_with("return %3\n"), "and the answer is what it left behind: {kept}");
7147 let both = "int g(int n) { int i = 0; __builtin_expect_with_probability(n, i++, 0.5); return i; }\n";
7148 assert!(body(both).contains("add.nsw"), "and so does the one with three arguments");
7149 }
7150
7151 #[test]
7163 fn a_promise_that_control_does_not_arrive_writes_no_instruction() {
7164 let promised = "int f(int x) { if (x) return 1; __builtin_unreachable(); }\n";
7165 let text = ir(promised);
7166 assert!(text.contains(" unreachable_hint\n"), "{text}");
7167 assert!(!text.contains("call"), "it is not a call to anything:\n{text}");
7168
7169 let after = body("int g(int x) { __builtin_unreachable(); return x; }\n");
7173 assert!(after.contains("return"), "{after}");
7174
7175 let text = asm(promised);
7178 let mine = text.split_once("\nf:\n").expect("a definition").1;
7179 let mine = mine.split_once("\t.size").expect("a definition").0;
7180 let plain = asm("int f(int x) { if (x) return 1; }\n");
7181 let plain = plain.split_once("\nf:\n").expect("a definition").1;
7182 let plain = plain.split_once("\t.size").expect("a definition").0;
7183 assert_eq!(mine, plain);
7184 let last = mine.lines().rfind(|line| !line.trim_start().starts_with('.'));
7187 assert_eq!(last.map(str::trim), Some("ret"), "{mine}");
7188 assert!(!mine.contains("ud2"), "{mine}");
7189 }
7190
7191 #[test]
7198 fn a_library_builtin_is_diagnosed_under_the_name_the_program_wrote() {
7199 let mut opts = options();
7200 opts.emit = EmitKind::Ir;
7201 let messages = run(&opts, "void f(void) { __builtin_abort(1); }\n").messages;
7202 assert!(
7203 messages.iter().any(|m| m.contains("__builtin_abort")),
7204 "expected the written name in {messages:?}"
7205 );
7206 }
7207
7208 #[test]
7216 fn a_builtin_nothing_lowers_is_refused_by_name() {
7217 let mut opts = options();
7218 opts.emit = EmitKind::Ir;
7219 let builtin = "__atomic_signal_fence";
7220 let source = format!("int counter;\nint f(void) {{ return ({builtin}(5), 0); }}\n");
7221 let messages = run(&opts, &source).messages;
7222 let named = messages.iter().any(|m| m.contains(builtin) && m.contains("E0686"));
7223 assert!(named, "expected {builtin} to be refused by name in {messages:?}");
7224 }
7225
7226 #[test]
7235 fn what_is_refused_is_the_call_and_not_the_name() {
7236 let text = ir(concat!(
7237 "void __atomic_signal_fence(int order) { (void)order; }\n",
7238 "void f(void) { __atomic_signal_fence(5); }\n",
7239 ));
7240 assert!(text.contains("call @__atomic_signal_fence"), "{text}");
7241 }
7242
7243 #[test]
7252 fn the_object_size_of_an_address_is_what_the_layout_leaves_in_front_of_it() {
7253 let text = ir(concat!(
7254 "struct S { char a[8]; int n; char b[12]; };\n",
7255 "char g[32];\n",
7256 "struct S gs;\n",
7257 "unsigned long whole = __builtin_object_size(g, 0);\n",
7258 "unsigned long moved = __builtin_object_size(g + 4, 0);\n",
7259 "unsigned long back = __builtin_object_size(g + 30 - 2, 0);\n",
7260 "unsigned long outer = __builtin_object_size(gs.a, 0);\n",
7261 "unsigned long inner = __builtin_object_size(gs.a, 1);\n",
7262 "unsigned long scalar = __builtin_object_size(&gs.n, 1);\n",
7263 "unsigned long after = __builtin_object_size(&gs.n, 0);\n",
7264 "unsigned long into = __builtin_object_size(&gs.b[2], 1);\n",
7265 "unsigned long text = __builtin_object_size(\"hello\", 0);\n",
7266 "unsigned long dyn = __builtin_dynamic_object_size(gs.b, 1);\n",
7267 ));
7268 for (name, size) in [
7269 ("whole", 32),
7270 ("moved", 28),
7271 ("back", 4),
7272 ("outer", 24),
7273 ("inner", 8),
7274 ("scalar", 4),
7275 ("after", 16),
7276 ("into", 10),
7277 ("text", 6),
7278 ("dyn", 12),
7279 ] {
7280 let said = format!("global @{name} : i64 = {size},");
7281 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
7282 }
7283 }
7284
7285 #[test]
7293 fn the_object_behind_an_address_can_be_one_with_automatic_storage() {
7294 let text = body(concat!(
7295 "struct S { char a[8]; int n; char b[12]; };\n",
7296 "unsigned long f(void) {\n",
7297 " char loc[20];\n",
7298 " struct S ls;\n",
7299 " return __builtin_object_size(loc + 3, 0) + __builtin_object_size(ls.b + 2, 1);\n",
7300 "}\n",
7301 ));
7302 assert!(text.contains("iconst.i64 17"), "twenty bytes with three used: {text}");
7303 assert!(text.contains("iconst.i64 10"), "twelve bytes with two used: {text}");
7304 }
7305
7306 #[test]
7316 fn an_address_with_no_object_in_sight_answers_at_the_end_of_the_range_its_kind_asks_for() {
7317 let text = ir(concat!(
7318 "struct T { int n; char f[]; };\n",
7319 "extern char *p;\n",
7320 "extern struct T *t;\n",
7321 "unsigned long largest = __builtin_object_size(p, 0);\n",
7322 "unsigned long nearest = __builtin_object_size(p, 1);\n",
7323 "unsigned long least = __builtin_object_size(p, 2);\n",
7324 "unsigned long tight = __builtin_object_size(p, 3);\n",
7325 "unsigned long flex = __builtin_object_size(t->f, 1);\n",
7326 "int says = __builtin_object_size(p, 0) == (unsigned long)-1;\n",
7327 ));
7328 for name in ["largest", "nearest", "flex"] {
7329 let said = format!("global @{name} : i64 = -1,");
7333 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
7334 }
7335 for name in ["least", "tight"] {
7336 let said = format!("global @{name} : i64 = 0,");
7337 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
7338 }
7339 assert!(text.contains("global @says : i32 = 1,"), "{text}");
7340 }
7341
7342 #[test]
7349 fn the_address_an_object_size_is_asked_about_is_not_evaluated() {
7350 let text = body(concat!(
7351 "extern char *side(void);\n",
7352 "unsigned long f(void) { return __builtin_object_size(side(), 0); }\n",
7353 ));
7354 assert!(!text.contains("call"), "nothing is called: {text}");
7355 }
7356
7357 #[test]
7362 fn a_kind_that_is_not_one_of_the_four_is_refused() {
7363 for source in [
7364 "extern char *p;\nextern int k;\nunsigned long f(void) ".to_owned()
7365 + "{ return __builtin_object_size(p, k); }\n",
7366 "extern char *p;\nunsigned long f(void) { return __builtin_object_size(p, 4); }\n"
7367 .to_owned(),
7368 "extern char *p;\nunsigned long f(void) ".to_owned()
7369 + "{ return __builtin_dynamic_object_size(p, -1); }\n",
7370 ] {
7371 let messages = errors(&source);
7372 let named = messages.iter().any(|m| m.contains("E0709") && m.contains("0 to 3"));
7373 assert!(named, "expected a complaint about the kind in {messages:?}");
7374 }
7375 }
7376
7377 #[test]
7383 fn the_pair_that_saves_a_place_lowers_to_the_two_markers() {
7384 let text = ir(concat!(
7385 "void *buf[5];\n",
7386 "int f(void) {\n",
7387 " if (__builtin_setjmp(buf)) return 2;\n",
7388 " return 1;\n",
7389 "}\n",
7390 "void g(void) { __builtin_longjmp(buf, 1); }\n",
7391 ));
7392 assert!(text.contains("= setjmp_marker.i32 %0\n"), "the save answers a value: {text}");
7393 assert!(text.contains(" longjmp_marker %0\n"), "the restore answers nothing: {text}");
7394 assert!(!text.contains("call @"), "neither of them is a call: {text}");
7395 }
7396
7397 #[test]
7405 fn a_local_of_a_function_that_saves_a_place_gets_a_slot() {
7406 let text = ir(concat!(
7407 "void *buf[5];\n",
7408 "int f(int x) { int a = 0; if (__builtin_setjmp(buf)) return a; a = 1; return x; }\n",
7409 "int g(int x) { int a = 0; if (x) return a; a = 1; return x; }\n",
7410 ));
7411 let (saves, plain) = text.split_once("func @g").expect("both functions");
7412 assert_eq!(saves.matches("= alloca").count(), 2, "the parameter and the local: {text}");
7413 assert!(saves.contains("store %9 -> %2"), "the local is written through: {text}");
7414 assert!(!plain.contains("alloca"), "nothing in the plain one needs a slot: {text}");
7415 }
7416
7417 #[test]
7426 fn a_value_live_across_sigsetjmp_keeps_its_spill_slot() {
7427 each_spill_slot_written_once(&across("int __sigsetjmp(sigjmp_buf, int);\n", "__sigsetjmp"));
7428 }
7429
7430 #[test]
7433 fn a_value_live_across_a_returns_twice_call_keeps_its_spill_slot() {
7434 let declared = "int save_here(sigjmp_buf, int) __attribute__((__returns_twice__));\n";
7435 each_spill_slot_written_once(&across(declared, "save_here"));
7436 }
7437
7438 #[test]
7446 fn a_value_live_across_setjmp_shares_its_slot_with_nothing_in_the_first_arm() {
7447 let source = concat!(
7448 "typedef long jmp_buf[25];\n",
7449 "int _setjmp(jmp_buf);\n",
7450 "void longjmp(jmp_buf, int) __attribute__((noreturn));\n",
7451 "int printf(const char *, ...);\n",
7452 "static jmp_buf *stack;\n",
7453 "static volatile long long sink;\n",
7454 "static int cells[64];\n",
7455 "static volatile int seed_in = 3;\n",
7456 "static void work(void) { longjmp(*stack, 1); }\n",
7457 "int main(void) {\n",
7458 " int seed = seed_in;\n",
7459 " int v = seed * 2;\n",
7460 " jmp_buf buf;\n",
7461 " if (_setjmp(buf) == 0) {\n",
7462 " stack = &buf;\n",
7463 " int *p = &cells[seed + 3];\n",
7464 " int a = v + 8;\n",
7465 " int b = seed * 2005;\n",
7466 " int *q = &cells[v + 1];\n",
7467 " work();\n",
7468 " sink = *p + a + b + *q;\n",
7469 " } else {\n",
7470 " printf(\"%d\\n\", v);\n",
7471 " }\n",
7472 " return 0;\n",
7473 "}\n",
7474 );
7475 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
7476 let mut opts = options();
7477 opts.emit = EmitKind::Asm;
7478 opts.opt_level = level;
7479 let result = run(&opts, source);
7480 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
7481 let text = result.text();
7482 let body = text.split_once("\nmain:\n").expect("the function").1;
7483 let lines: Vec<&str> = body.lines().map(str::trim).collect();
7484 let save = lines.iter().position(|l| *l == "call\t_setjmp").expect("the save");
7485 let jump = lines[save..]
7486 .iter()
7487 .position(|l| *l == "call\twork" || *l == "call\tlongjmp")
7488 .map(|at| save + at)
7489 .unwrap_or_else(|| panic!("the call that jumps back at {level:?}:\n{text}"));
7490 let printf = lines.iter().position(|l| *l == "call\tprintf").expect("the handler");
7491 let slot = lines[jump..printf]
7493 .iter()
7494 .rev()
7495 .find_map(|l| l.strip_suffix(", %rsi").or_else(|| l.strip_suffix(", %esi")))
7496 .and_then(|l| l.split_once('\t'))
7497 .map(|(_, place)| place)
7498 .filter(|place| place.ends_with("(%rsp)") || place.ends_with("(%rbp)"))
7499 .unwrap_or_else(|| panic!("the handler reads v from a slot at {level:?}:\n{text}"));
7500 let writes = |l: &&str| {
7501 !l.starts_with("cmp") && !l.starts_with("test") && l.ends_with(&format!(", {slot}"))
7502 };
7503 assert!(
7504 lines[..save].iter().any(writes),
7505 "{slot} is written before the save at {level:?}:\n{text}"
7506 );
7507 assert!(
7508 !lines[save..jump].iter().any(writes),
7509 "{slot} is written again before the jump at {level:?}:\n{text}"
7510 );
7511 }
7512 }
7513
7514 fn across(declared: &str, save: &str) -> String {
7517 asm(&format!(
7518 "typedef long sigjmp_buf[25];\n{declared}int id(int);\nvoid thrower(int);\n\
7519 int work(int n) {{\n\
7520 \x20 int v0 = id(n), v1 = id(n + 1), v2 = id(n + 2), v3 = id(n + 3), v4 = id(n + 4);\n\
7521 \x20 sigjmp_buf b;\n\
7522 \x20 if ({save}(b, 0) == 0) {{\n\
7523 \x20 int w0 = id(v0 + v1), w1 = id(v1 + v2), w2 = id(v2 + v3);\n\
7524 \x20 int w3 = id(v3 + v4), w4 = id(v4 + v0);\n\
7525 \x20 thrower(n);\n\
7526 \x20 return w0 ^ w1 ^ w2 ^ w3 ^ w4;\n\
7527 \x20 }}\n\
7528 \x20 return v0 + v1 + v2 + v3 + v4;\n\
7529 }}\n"
7530 ))
7531 }
7532
7533 fn each_spill_slot_written_once(text: &str) {
7535 let mut stored = Vec::new();
7536 for line in text.lines().map(str::trim) {
7537 let Some(operands) = line.strip_prefix("movq\t%") else { continue };
7538 if let Some((_, place)) = operands.split_once(", ") {
7539 if place.ends_with("(%rsp)") {
7540 assert!(!stored.contains(&place), "{place} is written twice:\n{text}");
7541 stored.push(place);
7542 }
7543 }
7544 }
7545 assert!(!stored.is_empty(), "something should have been spilled:\n{text}");
7546 }
7547
7548 #[test]
7557 fn the_save_writes_four_words_and_carries_on_in_a_new_block() {
7558 let text =
7559 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
7560 let body = text.split_once("\nf:\n").expect("the function").1;
7561 assert!(body.contains("\tmovq\t%rsp, %rbp\n"), "a frame pointer whatever: {text}");
7562 assert!(body.contains("\tsubq\t$8, %rsp\n"), "no red zone: {text}");
7563 assert!(body.contains("\tmovq\t%rbp, (%rax)\n"), "the frame pointer: {text}");
7564 assert!(body.contains("\tmovq\t%rsp, 16(%rax)\n"), "the stack pointer: {text}");
7565 assert!(body.contains("\tleaq\t.Lf_1(%rip), %rcx\n"), "where to come back to: {text}");
7566 assert!(body.contains("\tmovq\t%rcx, 8(%rax)\n"), "and that goes in the buffer: {text}");
7567 let back = body.split_once(".Lf_1:\n").expect("the block control comes back to").1;
7568 assert!(back.starts_with("\tmovq\t(%rsp), %rax\n"), "the answer is read back: {text}");
7569 }
7570
7571 #[test]
7579 fn a_save_destroys_every_register_the_allocator_hands_out() {
7580 let text =
7581 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
7582 for reg in ["%rbx", "%r12", "%r13", "%r14", "%r15"] {
7583 assert!(text.contains(&format!("\tpushq\t{reg}\n")), "{reg} is saved: {text}");
7584 assert!(text.contains(&format!("\tpopq\t{reg}\n")), "{reg} is restored: {text}");
7585 }
7586 }
7587
7588 #[test]
7595 fn the_restore_puts_the_frame_back_before_it_jumps() {
7596 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
7597 let mut opts = options();
7598 opts.emit = EmitKind::Asm;
7599 opts.opt_level = level;
7600 let source = "void *buf[5];\nvoid g(void) { __builtin_longjmp(buf, 1); }\n";
7601 let result = run(&opts, source);
7602 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
7603 let text = result.text().to_owned();
7604 let jump = text.find("\tjmp\t*%").unwrap_or_else(|| panic!("an indirect jump: {text}"));
7605 let stack = text.find(", %rsp\n").unwrap_or_else(|| panic!("the stack back: {text}"));
7606 let frame = text.find(", %rbp\n").unwrap_or_else(|| panic!("the frame back: {text}"));
7607 assert!(stack < jump, "the stack goes back first at {level:?}: {text}");
7608 assert!(frame < jump, "and so does the frame at {level:?}: {text}");
7609 }
7610 }
7611
7612 #[test]
7618 fn a_longjmp_whose_second_argument_is_not_one_is_turned_down() {
7619 for source in [
7620 "void *buf[5];\nvoid f(void) { __builtin_longjmp(buf, 0); }\n",
7621 "void *buf[5];\nextern int v;\nvoid f(void) { __builtin_longjmp(buf, v); }\n",
7622 ] {
7623 let messages = errors(source);
7624 let named = messages.iter().any(|m| m.contains("E0710"));
7625 assert!(named, "expected a complaint about the value in {messages:?}");
7626 }
7627 }
7628
7629 #[test]
7634 fn a_static_function_nothing_refers_to_is_not_emitted() {
7635 let text = ir("static int dropped(void) { return 1; }\n\
7636 static int kept(void) { return 2; }\n\
7637 int main(void) { return kept(); }\n");
7638 assert!(text.contains("func @kept"), "{text}");
7639 assert!(!text.contains("dropped"), "{text}");
7640 }
7641
7642 #[test]
7648 fn two_static_functions_that_only_call_each_other_are_both_dropped() {
7649 let text = ir("static int ping(void);\n\
7650 static int pong(void) { return ping(); }\n\
7651 static int ping(void) { return pong(); }\n\
7652 int main(void) { return 0; }\n");
7653 assert!(!text.contains("ping"), "{text}");
7654 assert!(!text.contains("pong"), "{text}");
7655 }
7656
7657 #[test]
7663 fn naming_a_static_function_anywhere_keeps_it() {
7664 let text = ir("static int by_address(void) { return 1; }\n\
7665 static int in_an_image(void) { return 2; }\n\
7666 static int deeper(void) { return 3; }\n\
7667 static int reaches_deeper(void) { return deeper(); }\n\
7668 static int (*table[1])(void) = {in_an_image};\n\
7669 int main(void) {\n\
7670 int (*p)(void) = by_address;\n\
7671 return p() + table[0]() + reaches_deeper();\n\
7672 }\n");
7673 for kept in ["by_address", "in_an_image", "deeper", "reaches_deeper"] {
7674 assert!(text.contains(&format!("func @{kept}")), "expected {kept} in:\n{text}");
7675 }
7676 }
7677
7678 #[test]
7684 fn an_attribute_keeps_a_static_function_nothing_refers_to() {
7685 for attribute in ["used", "retain", "constructor", "destructor", "__used__"] {
7686 let source = format!(
7687 "__attribute__(({attribute})) static int kept(void) {{ return 1; }}\n\
7688 int main(void) {{ return 0; }}\n"
7689 );
7690 let text = ir(&source);
7691 assert!(text.contains("func @kept"), "for {attribute}:\n{text}");
7692 }
7693 }
7694
7695 #[test]
7698 fn nonnull_is_answered_yes_and_taken_with_or_without_operands() {
7699 let text = ir("#if !__has_attribute(nonnull) || !__has_attribute(__nonnull__)\n\
7700 #error nonnull\n\
7701 #endif\n\
7702 __attribute__((nonnull)) int first(char *p);\n\
7703 int both(char *a, int n, char *b) __attribute__((__nonnull__(1, 3)));\n\
7704 int both(char *a, int n, char *b) { return first(a) + n + (b != 0); }\n");
7705 assert!(text.contains("func @both"), "{text}");
7706 }
7707
7708 #[test]
7711 fn a_function_anything_could_call_is_emitted_without_being_called() {
7712 let text =
7713 ir("int nobody_here_calls_it(void) { return 1; }\nint main(void) { return 0; }\n");
7714 assert!(text.contains("func @nobody_here_calls_it"), "{text}");
7715 }
7716
7717 #[test]
7724 fn a_classification_c_has_an_operator_for_is_that_operator() {
7725 for (builtin, operator) in [
7726 ("__builtin_isgreater", "binary >"),
7727 ("__builtin_isgreaterequal", "binary >="),
7728 ("__builtin_isless", "binary <"),
7729 ("__builtin_islessequal", "binary <="),
7730 ] {
7731 let source = format!("int f(double x, double y) {{ return {builtin}(x, y); }}\n");
7732 let text = tast(&source);
7733 assert!(text.contains(&format!("{operator} : int")), "for {builtin}:\n{text}");
7734 }
7735 }
7736
7737 #[test]
7746 fn the_classification_builtins_are_comparisons_and_not_calls() {
7747 let text = body("int f(double x, double y) { return __builtin_isunordered(x, y); }\n");
7748 assert_eq!(
7749 text,
7750 "block0(%0: f64, %1: f64):\n %2 = fcmp uno %0, %1\n %3 = zext.i32 \
7751 %2\n return %3\n"
7752 );
7753
7754 let text = body("int f(double x, double y) { return __builtin_islessgreater(x, y); }\n");
7756 assert!(text.contains("fcmp one %0, %1"), "{text}");
7757
7758 let text = body("int f(double x) { return __builtin_isnan(x); }\n");
7759 assert!(text.contains("fcmp uno %0, %0"), "{text}");
7760
7761 let text = body("int f(double x) { return __builtin_isinf(x); }\n");
7762 assert!(text.contains("fconst.f64 0x7ff0000000000000"), "{text}");
7763 assert!(text.contains("fconst.f64 0xfff0000000000000"), "{text}");
7764 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
7765 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
7766 assert!(text.contains("%5 = or %3, %4"), "{text}");
7767
7768 let text = body("int f(double x) { return __builtin_isfinite(x); }\n");
7771 assert!(text.contains("%3 = fcmp olt %2, %0"), "{text}");
7772 assert!(text.contains("%4 = fcmp olt %0, %1"), "{text}");
7773 assert!(text.contains("%5 = and %3, %4"), "{text}");
7774
7775 let text = body("int f(double x) { return __builtin_signbit(x); }\n");
7776 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
7777 assert!(text.contains("icmp slt %1, %2"), "{text}");
7778
7779 let text = body("int f(long double x) { return __builtin_signbitl(x); }\n");
7783 assert!(text.contains("load.i16"), "{text}");
7784 assert!(text.contains("icmp slt"), "{text}");
7785 assert!(!text.contains("i80"), "{text}");
7786
7787 let text = body("double g(void);\nint f(void) { return __builtin_isnan(g()); }\n");
7790 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
7791 }
7792
7793 #[test]
7800 fn a_classification_spelling_that_names_a_width_converts_before_it_asks() {
7801 let text = ir(concat!(
7802 "int a = __builtin_isinff(1e300);\n",
7803 "int b = __builtin_isinf(1e300);\n",
7804 "int c = __builtin_isnan(0.0);\n",
7808 "int d = __builtin_signbit(-0.0);\n",
7809 "int e = __builtin_islessgreater(1.0, 2.0);\n",
7810 ));
7811 assert!(text.contains("global @a : i32 = 1,"), "{text}");
7812 assert!(text.contains("global @b : i32 = 0,"), "{text}");
7813 assert!(text.contains("global @c : i32 = 0,"), "{text}");
7814 assert!(text.contains("global @d : i32 = 1,"), "{text}");
7815 assert!(text.contains("global @e : i32 = 1,"), "{text}");
7816 }
7817
7818 #[test]
7820 fn a_classification_builtin_refuses_an_argument_that_is_not_floating_point() {
7821 let mut opts = options();
7822 opts.emit = EmitKind::Ir;
7823 let source = concat!(
7824 "int a(int x) { return __builtin_isnan(x); }\n",
7825 "int b(int x, int y) { return __builtin_isunordered(x, y); }\n",
7826 "int c(double x) { return __builtin_isnan(x, x); }\n",
7827 );
7828 let messages = run(&opts, source).messages;
7829 assert_eq!(
7830 messages,
7831 [
7832 "/main.c:1:23: error: non-floating-point argument in call to function \
7833 '__builtin_isnan' [E0685]",
7834 "/main.c:2:30: error: non-floating-point arguments in call to function \
7835 '__builtin_isunordered' [E0685]",
7836 "/main.c:3:26: error: too many arguments to function '__builtin_isnan' [E0511]",
7837 ]
7838 );
7839 }
7840
7841 #[test]
7850 fn the_last_three_classification_builtins_are_comparisons_and_not_calls() {
7851 let text = body("int f(double x) { return __builtin_isnormal(x); }\n");
7852 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
7856 assert!(text.contains("%2 = iconst.i64 9223372036854775807"), "{text}");
7857 assert!(text.contains("%3 = and %1, %2"), "{text}");
7858 assert!(text.contains("%4 = iconst.i64 4503599627370496"), "{text}");
7859 assert!(text.contains("%5 = iconst.i64 9218868437227405312"), "{text}");
7860 assert!(text.contains("%6 = icmp uge %3, %4"), "{text}");
7861 assert!(text.contains("%7 = icmp ult %3, %5"), "{text}");
7862 assert!(text.contains("%8 = and %6, %7"), "{text}");
7863
7864 let text = body("int f(long double x) { return __builtin_isnormal(x); }\n");
7869 assert!(text.contains("fconst.f80 0x18000000000000000"), "{text}");
7870 assert!(text.contains("fconst.f80 0x7fff8000000000000000"), "{text}");
7871 assert!(text.contains("fcmp oge"), "{text}");
7872 assert!(text.contains("fcmp olt"), "{text}");
7873
7874 let text = body("int f(double x) { return __builtin_isinf_sign(x); }\n");
7875 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
7876 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
7877 assert!(text.contains("%7 = sub %5, %6"), "{text}");
7878
7879 let text = body("int f(double x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n");
7880 assert!(text.contains("fcmp uno %0, %0"), "{text}");
7881 assert!(text.contains("fcmp oeq %0, %6"), "{text}");
7882 assert_eq!(text.matches(" = zext.i32 ").count(), 4, "{text}");
7886 assert_eq!(text.matches(" = xor ").count(), 4, "{text}");
7887 assert!(!text.contains("call"), "{text}");
7888
7889 let text = body(concat!(
7892 "double g(void);\n",
7893 "int f(void) { return __builtin_fpclassify(0, 1, 2, 3, 4, g()); }\n",
7894 ));
7895 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
7896 }
7897
7898 #[test]
7905 fn the_last_three_classification_builtins_fold_where_their_operand_is_a_constant() {
7906 let text = ir(concat!(
7907 "int a = __builtin_isnormal(1.0);\n",
7908 "int b = __builtin_isnormal(0.0);\n",
7909 "int c = __builtin_isnormal(1.0 / 0.0);\n",
7910 "int d = __builtin_isinf_sign(-1.0 / 0.0);\n",
7911 "int e = __builtin_isinf_sign(1.0);\n",
7912 "int g = __builtin_fpclassify(0, 1, 2, 3, 4, 0.0);\n",
7913 "int h = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0);\n",
7914 "int i = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0 / 0.0);\n",
7915 ));
7916 assert!(text.contains("global @a : i32 = 1,"), "{text}");
7917 assert!(text.contains("global @b : i32 = 0,"), "{text}");
7918 assert!(text.contains("global @c : i32 = 0,"), "{text}");
7919 assert!(text.contains("global @d : i32 = -1,"), "{text}");
7920 assert!(text.contains("global @e : i32 = 0,"), "{text}");
7921 assert!(text.contains("global @g : i32 = 4,"), "{text}");
7922 assert!(text.contains("global @h : i32 = 2,"), "{text}");
7923 assert!(text.contains("global @i : i32 = 1,"), "{text}");
7924 }
7925
7926 #[test]
7932 fn fpclassify_refuses_an_answer_that_is_not_an_integer_constant() {
7933 let mut opts = options();
7934 opts.emit = EmitKind::Ir;
7935 let source = concat!(
7936 "int a(double x, int n) { return __builtin_fpclassify(0, 1, n, 3, 4, x); }\n",
7937 "int b(double x) { return __builtin_fpclassify(0, 1, 2, 3, x); }\n",
7938 "int c(int x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n",
7939 );
7940 let messages = run(&opts, source).messages;
7941 assert_eq!(
7942 messages,
7943 [
7944 "/main.c:1:60: error: non-const integer argument 3 in call to function \
7945 '__builtin_fpclassify' [E0687]",
7946 "/main.c:2:26: error: too few arguments to function '__builtin_fpclassify' \
7947 [E0511]",
7948 "/main.c:3:23: error: non-floating-point argument in call to function \
7949 '__builtin_fpclassify' [E0685]",
7950 ]
7951 );
7952 }
7953
7954 #[test]
7962 fn a_builtin_whose_answer_is_a_constant_is_one_and_not_a_call() {
7963 let text = ir(concat!(
7964 "double a = __builtin_inf();\n",
7965 "float b = __builtin_huge_valf();\n",
7966 "long double c = __builtin_infl();\n",
7967 "double d = __builtin_huge_val();\n",
7968 ));
7969 assert!(text.contains("global @a : f64 = 0x7ff0000000000000,"), "{text}");
7970 assert!(text.contains("global @b : f32 = 0x7f800000,"), "{text}");
7971 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
7972 assert!(text.contains("global @d : f64 = 0x7ff0000000000000,"), "{text}");
7973 assert!(!text.contains("call"), "{text}");
7974 }
7975
7976 #[test]
7985 fn a_nan_is_written_with_the_payload_the_program_asked_for() {
7986 let text = ir(concat!(
7987 "double a = __builtin_nan(\"\");\n",
7988 "double b = __builtin_nan(\"0x1\");\n",
7989 "double c = __builtin_nan(\"010\");\n",
7991 "double d = __builtin_nans(\"\");\n",
7992 "double e = __builtin_nans(\"0x1\");\n",
7993 "float f = __builtin_nanf(\"0x1\");\n",
7994 "float g = __builtin_nansf(\"\");\n",
7995 "long double h = __builtin_nansl(\"\");\n",
7996 ));
7997 assert!(text.contains("global @a : f64 = 0x7ff8000000000000,"), "{text}");
7998 assert!(text.contains("global @b : f64 = 0x7ff8000000000001,"), "{text}");
7999 assert!(text.contains("global @c : f64 = 0x7ff8000000000008,"), "{text}");
8000 assert!(text.contains("global @d : f64 = 0x7ff4000000000000,"), "{text}");
8001 assert!(text.contains("global @e : f64 = 0x7ff0000000000001,"), "{text}");
8002 assert!(text.contains("global @f : f32 = 0x7fc00001,"), "{text}");
8003 assert!(text.contains("global @g : f32 = 0x7fa00000,"), "{text}");
8004 assert!(text.contains("f80 0x7fffa000000000000000"), "{text}");
8005
8006 let text = ir(concat!(
8009 "double f(const char *p) { return __builtin_nan(p); }\n",
8010 "double g(void) { return __builtin_nans(\"1x\"); }\n",
8011 ));
8012 assert_eq!(text.matches("call @nan(").count(), 1, "{text}");
8013 assert_eq!(text.matches("call @nans(").count(), 1, "{text}");
8014 }
8015
8016 #[test]
8024 fn the_length_and_the_order_of_a_string_literal_are_known_here() {
8025 let text = ir(concat!(
8026 "unsigned long a = __builtin_strlen(\"hello\");\n",
8027 "unsigned long b = __builtin_strlen(\"a\\0bc\");\n",
8028 "int c = __builtin_strcmp(\"X\", \"X\\376\") < 0;\n",
8029 "int d = __builtin_strcmp(\"abc\", \"abc\");\n",
8030 "int e = __builtin_strcmp(\"abc\", \"ab\") > 0;\n",
8031 ));
8032 assert!(text.contains("global @a : i64 = 5,"), "{text}");
8033 assert!(text.contains("global @b : i64 = 1,"), "{text}");
8034 assert!(text.contains("global @c : i32 = 1,"), "{text}");
8035 assert!(text.contains("global @d : i32 = 0,"), "{text}");
8036 assert!(text.contains("global @e : i32 = 1,"), "{text}");
8037 assert!(!text.contains("call"), "{text}");
8038
8039 let text = ir("unsigned long f(const char *p) { return __builtin_strlen(p); }\n");
8041 assert!(text.contains("call @strlen("), "{text}");
8042 }
8043
8044 #[test]
8051 fn a_sign_builtin_is_a_mask_over_the_bits_and_not_a_call() {
8052 let text = body("double f(double x) { return __builtin_fabs(x); }\n");
8053 assert!(text.contains("bitcast.i64 %0"), "{text}");
8054 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
8055 assert!(text.contains("and %1, %2"), "{text}");
8056 assert!(text.contains("bitcast.f64 %3"), "{text}");
8057 assert!(!text.contains("call"), "{text}");
8058
8059 let text = body("double f(double x, double y) { return __builtin_copysign(x, y); }\n");
8060 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
8061 assert!(text.contains("%8 = or %4, %7"), "{text}");
8062 assert!(!text.contains("call"), "{text}");
8063
8064 let text = body("long double f(long double x) { return __builtin_fabsl(x); }\n");
8067 assert!(text.contains("iconst.i16 32767"), "{text}");
8068 assert!(text.contains("load.f80"), "{text}");
8069 assert!(!text.contains("call"), "{text}");
8070
8071 let text = body("double f(float x) { return __builtin_fabs(x); }\n");
8074 assert!(text.contains("fpext.f64 %0"), "{text}");
8075 assert!(text.contains("bitcast.i64 %1"), "{text}");
8076 }
8077
8078 #[test]
8085 fn a_shuffle_picks_each_lane_by_the_low_bits_of_the_mask() {
8086 let text = body(concat!(
8087 "typedef int v2 __attribute__((vector_size(8)));\n",
8088 "void f(v2 *v, v2 m) { *v = __builtin_shuffle(*v, m); }\n",
8089 ));
8090 assert!(text.contains("memcpy"), "{text}");
8091 assert_eq!(text.matches("iconst.i32 1\n").count(), 2, "{text}");
8092 assert_eq!(text.matches(" = and ").count(), 2, "{text}");
8093 assert!(!text.contains("call"), "{text}");
8094
8095 let text = body(concat!(
8098 "typedef char v4 __attribute__((vector_size(4)));\n",
8099 "v4 f(v4 a, v4 b, v4 m) { return __builtin_shuffle(a, b, m); }\n",
8100 ));
8101 assert_eq!(text.matches("iconst.i32 7\n").count(), 4, "{text}");
8102 assert!(text.contains("zext.i32"), "{text}");
8103 assert!(!text.contains("call"), "{text}");
8104 }
8105
8106 #[test]
8110 fn the_arguments_a_function_was_called_with_are_saved_on_the_way_in() {
8111 let text =
8112 mir("void *f(int a, double b) { (void)a; (void)b; return __builtin_apply_args(); }\n");
8113 assert!(text.matches("x64.mov_mr_64").count() >= 7, "{text}");
8115 assert!(text.matches("x64.movaps_mr").count() >= 8, "{text}");
8116 for reg in ["$rdi", "$rsi", "$rdx", "$rcx", "$r8", "$r9", "$xmm0", "$xmm7"] {
8117 assert!(text.contains(reg), "{reg} is not saved in\n{text}");
8118 }
8119
8120 let text = mir("int f(int a) { return a; }\n");
8122 assert!(!text.contains("movaps_mr"), "{text}");
8123 }
8124
8125 #[test]
8129 fn a_call_built_from_saved_arguments_loads_every_argument_register() {
8130 let text = mir(concat!(
8131 "void *g(void *args, void (*h)()) {\n",
8132 " return __builtin_apply(h, args, 64);\n",
8133 "}\n",
8134 ));
8135 assert!(text.matches("x64.mov_rm_64").count() >= 7, "{text}");
8136 assert!(text.matches("x64.movaps_rm").count() >= 8, "{text}");
8137 assert!(text.contains("call"), "{text}");
8138 assert!(text.matches("x64.movaps_mr").count() >= 2, "{text}");
8140
8141 let mut opts = options();
8143 opts.emit = EmitKind::Ir;
8144 let result = run(
8145 &opts,
8146 "void *g(void *a, void (*h)(), int n) { return __builtin_apply(h, a, n); }\n",
8147 );
8148 assert!(result.failed(), "{:?}", result.messages);
8149 assert!(
8150 result
8151 .messages
8152 .iter()
8153 .any(|m| m.contains("the size given to '__builtin_apply' is a constant")),
8154 "{:?}",
8155 result.messages
8156 );
8157 }
8158
8159 #[test]
8161 fn a_shuffle_refuses_what_gcc_refuses() {
8162 let mut opts = options();
8163 opts.emit = EmitKind::Ir;
8164 let source = concat!(
8165 "typedef int v4 __attribute__((vector_size(16)));\n",
8166 "typedef float f4 __attribute__((vector_size(16)));\n",
8167 "typedef short s8 __attribute__((vector_size(16)));\n",
8168 "typedef long long l4 __attribute__((vector_size(32)));\n",
8169 "void a(v4 x, f4 m) { __builtin_shuffle(x, m); }\n",
8170 "void b(int x, v4 m) { __builtin_shuffle(x, m); }\n",
8171 "void c(v4 x, f4 y, v4 m) { __builtin_shuffle(x, y, m); }\n",
8172 "void d(v4 x, s8 m) { __builtin_shuffle(x, m); }\n",
8173 "void e(f4 x, l4 m) { __builtin_shuffle(x, m); }\n",
8174 "void g(v4 x) { __builtin_shuffle(x); }\n",
8175 );
8176 let messages = run(&opts, source).messages;
8177 let wanted = [
8178 "last argument must be an integer vector [E0715]",
8179 "arguments must be vectors [E0715]",
8180 "argument vectors must be of the same type [E0715]",
8181 "number of elements of the argument vector(s) and the mask vector should be the same \
8182 [E0715]",
8183 "argument vector(s) inner type must have the same size as inner type of the mask \
8184 [E0715]",
8185 "too few arguments to function '__builtin_shuffle' [E0511]",
8186 ];
8187 assert_eq!(messages.len(), wanted.len(), "{messages:?}");
8188 for (message, wanted) in messages.iter().zip(wanted) {
8189 assert!(message.ends_with(wanted), "{message}");
8190 }
8191 }
8192
8193 #[test]
8202 fn the_plain_math_names_are_the_same_mask_and_not_a_call() {
8203 let text =
8204 body(concat!("double fabs(double x);\n", "double f(double x) { return fabs(x); }\n",));
8205 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
8206 assert!(!text.contains("call"), "{text}");
8207
8208 let text =
8209 body(concat!("float fabsf(float x);\n", "float f(float x) { return fabsf(x); }\n",));
8210 assert!(text.contains("bitcast.i32 %0"), "{text}");
8211 assert!(!text.contains("call"), "{text}");
8212
8213 let text = body(concat!(
8214 "double copysign(double x, double y);\n",
8215 "double f(double x, double y) { return copysign(x, y); }\n",
8216 ));
8217 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
8218 assert!(!text.contains("call"), "{text}");
8219
8220 let text = body(concat!(
8221 "float copysignf(float x, float y);\n",
8222 "float f(float x, float y) { return copysignf(x, y); }\n",
8223 ));
8224 assert!(!text.contains("call"), "{text}");
8225
8226 let text = ir(concat!(
8230 "long double fabsl(long double x);\n",
8231 "long double f(long double x) { return fabsl(x); }\n",
8232 ));
8233 assert!(text.contains("call @fabsl"), "{text}");
8234 }
8235
8236 #[test]
8244 fn a_plain_math_name_the_program_took_is_the_programs_own_function() {
8245 let taken = concat!(
8246 "static double fabs(double b) { return 7; }\n",
8247 "double f(double x) { return fabs(x); }\n",
8248 );
8249 assert!(ir(taken).contains("call @fabs"), "a static definition is the program's own");
8250
8251 let retyped = concat!("int fabs(int b);\n", "int f(int x) { return fabs(x); }\n");
8252 assert!(ir(retyped).contains("call @fabs"), "another type is another function");
8253
8254 let plain = concat!("double fabs(double b);\n", "double f(double x) { return fabs(x); }\n");
8255 let mut opts = options();
8256 opts.emit = EmitKind::Ir;
8257 assert!(!run(&opts, plain).text().contains("call @fabs"), "the library's by default");
8258
8259 opts.builtins = false;
8260 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin");
8261
8262 opts.builtins = true;
8263 opts.no_builtin = vec!["fabs".to_owned()];
8264 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin-fabs");
8265 let one = concat!(
8266 "double copysign(double a, double b);\n",
8267 "double f(double x) { return copysign(x, 1.0); }\n",
8268 );
8269 assert!(!run(&opts, one).text().contains("call @copysign"), "one name and not the family");
8270
8271 opts.no_builtin = Vec::new();
8273 opts.builtins = false;
8274 let prefixed = "double f(double x) { return __builtin_fabs(x); }\n";
8275 assert!(!run(&opts, prefixed).text().contains("call @fabs"), "the prefix is not a library");
8276 }
8277
8278 #[test]
8287 fn the_sign_builtins_answer_a_zero_and_a_nan_the_way_the_bits_say() {
8288 let text = ir(concat!(
8289 "double a = __builtin_fabs(-3.5);\n",
8290 "double b = __builtin_copysign(1.0, -0.0);\n",
8291 "double c = __builtin_copysign(0.0, -2.0);\n",
8292 "double d = __builtin_copysign(-__builtin_nan(\"\"), 1.0);\n",
8294 "double e = __builtin_fabs(-__builtin_nan(\"0x1\"));\n",
8295 "float g = __builtin_copysignf(-0.0f, 2.0f);\n",
8296 "long double h = __builtin_copysignl(1.0L, -1.0L);\n",
8297 "long double i = __builtin_fabsl(-__builtin_infl());\n",
8298 ));
8299 assert!(text.contains("global @a : f64 = 0x400c000000000000,"), "{text}");
8300 assert!(text.contains("global @b : f64 = 0xbff0000000000000,"), "{text}");
8301 assert!(text.contains("global @c : f64 = 0x8000000000000000,"), "{text}");
8302 assert!(text.contains("global @d : f64 = 0x7ff8000000000000,"), "{text}");
8303 assert!(text.contains("global @e : f64 = 0x7ff8000000000001,"), "{text}");
8304 assert!(text.contains("global @g : f32 = 0x0,"), "{text}");
8305 assert!(text.contains("f80 0xbfff8000000000000000"), "{text}");
8306 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
8307 }
8308
8309 #[test]
8316 fn the_sign_of_a_long_double_is_in_the_word_at_the_top_of_it() {
8317 for source in [
8318 "int f(long double x) { return __builtin_signbit(x); }\n",
8319 "long double f(long double x) { return __builtin_fabsl(x); }\n",
8320 "long double f(long double x, long double y) { return __builtin_copysignl(x, y); }\n",
8321 "int f(long double x) { return __builtin_isnormal(x); }\n",
8322 ] {
8323 let text = body(source);
8324 assert!(!text.contains("i80"), "{text}");
8325 assert!(text.contains("i16"), "{text}");
8326 }
8327 }
8328
8329 #[test]
8337 fn the_complex_builtins_are_the_halves_of_the_value_and_not_a_call() {
8338 let text = body("double f(_Complex double z) { return __builtin_creal(z); }\n");
8339 assert!(!text.contains("call"), "{text}");
8340 let text = body("double f(_Complex double z) { return __builtin_cimag(z); }\n");
8341 assert!(!text.contains("call"), "{text}");
8342
8343 let text = body("_Complex double f(_Complex double z) { return __builtin_conj(z); }\n");
8346 assert_eq!(text.matches("fneg").count(), 1, "{text}");
8347 assert!(!text.contains("call"), "{text}");
8348 let negated = body("_Complex double f(_Complex double z) { return -z; }\n");
8349 assert_eq!(negated.matches("fneg").count(), 2, "{negated}");
8350
8351 let written = body("_Complex double f(_Complex double z) { return ~z; }\n");
8354 assert_eq!(written, text, "the name and the operator are the same thing");
8355
8356 let text = body(concat!(
8358 "double creal(_Complex double z);\n",
8359 "double f(_Complex double z) { return creal(z); }\n",
8360 ));
8361 assert!(!text.contains("call"), "{text}");
8362 let text = body(concat!(
8363 "_Complex float conjf(_Complex float z);\n",
8364 "_Complex float f(_Complex float z) { return conjf(z); }\n",
8365 ));
8366 assert_eq!(text.matches("fneg").count(), 1, "{text}");
8367 assert!(!text.contains("call"), "{text}");
8368
8369 let taken = concat!(
8372 "static double creal(_Complex double z) { return 7; }\n",
8373 "double f(_Complex double z) { return creal(z); }\n",
8374 );
8375 assert!(ir(taken).contains("call @creal"), "a static definition is the program's own");
8376 let retyped = concat!("int cimag(int z);\n", "int f(int z) { return cimag(z); }\n");
8377 assert!(ir(retyped).contains("call @cimag"), "another type is another function");
8378 let plain = concat!(
8379 "double cimag(_Complex double z);\n",
8380 "double f(_Complex double z) { return cimag(z); }\n",
8381 );
8382 let mut opts = options();
8383 opts.emit = EmitKind::Ir;
8384 opts.builtins = false;
8385 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin");
8386 opts.builtins = true;
8387 opts.no_builtin = vec!["cimag".to_owned()];
8388 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin-cimag");
8389
8390 let text = ir(concat!(
8392 "double a = __builtin_creal(1.5 + 2.5i);\n",
8393 "double b = __builtin_cimag(1.5 + 2.5i);\n",
8394 "_Complex double c = __builtin_conj(1.5 + 2.5i);\n",
8395 ));
8396 assert!(text.contains("global @a : f64 = 0x3ff8000000000000,"), "{text}");
8397 assert!(text.contains("global @b : f64 = 0x4004000000000000,"), "{text}");
8398 assert!(
8399 text.contains("{ f64 0x3ff8000000000000, f64 0xc004000000000000 }"),
8400 "the conjugate of a constant is the constant with the second half negated: {text}"
8401 );
8402 assert!(!text.contains("call"), "{text}");
8403 }
8404
8405 #[test]
8413 fn a_math_library_builtin_of_a_constant_is_the_answer_and_not_a_call() {
8414 let text = ir(concat!(
8415 "double a = __builtin_ceil(1.5);\n",
8416 "double b = __builtin_floor(1.5);\n",
8417 "double c = __builtin_trunc(-1.5);\n",
8418 "double d = __builtin_round(2.5);\n",
8421 "double e = __builtin_ceil(-0.5);\n",
8423 "double f = __builtin_fmax(1.0, 2.0);\n",
8424 "double g = __builtin_fmin(1.0, 2.0);\n",
8425 "float h = __builtin_ceilf(1.25f);\n",
8426 "double ceil(double x);\n",
8429 "double i = ceil(2.25);\n",
8430 ));
8431 assert!(text.contains("global @a : f64 = 0x4000000000000000,"), "{text}");
8432 assert!(text.contains("global @b : f64 = 0x3ff0000000000000,"), "{text}");
8433 assert!(text.contains("global @c : f64 = 0xbff0000000000000,"), "{text}");
8434 assert!(text.contains("global @d : f64 = 0x4008000000000000,"), "{text}");
8435 assert!(text.contains("global @e : f64 = 0x8000000000000000,"), "{text}");
8436 assert!(text.contains("global @f : f64 = 0x4000000000000000,"), "{text}");
8437 assert!(text.contains("global @g : f64 = 0x3ff0000000000000,"), "{text}");
8438 assert!(text.contains("global @h : f32 = 0x40000000,"), "{text}");
8439 assert!(text.contains("global @i : f64 = 0x4008000000000000,"), "{text}");
8440 assert!(!text.contains("call"), "{text}");
8441 }
8442
8443 #[test]
8451 fn a_math_library_builtin_of_anything_else_is_a_call_to_the_library() {
8452 let text = ir(concat!(
8453 "double f(double x) { return __builtin_ceil(x); }\n",
8454 "float g(float x) { return __builtin_floorf(x); }\n",
8455 "double h(double x, double y) { return __builtin_fmax(x, y); }\n",
8456 ));
8457 assert!(text.contains("call @ceil("), "{text}");
8458 assert!(text.contains("call @floorf("), "{text}");
8459 assert!(text.contains("call @fmax("), "{text}");
8460
8461 let text = ir(concat!(
8465 "double f(void) { return __builtin_rint(2.5); }\n",
8466 "double g(void) { return __builtin_nearbyint(2.5); }\n",
8467 ));
8468 assert!(text.contains("call @rint("), "{text}");
8469 assert!(text.contains("call @nearbyint("), "{text}");
8470
8471 let text = ir("double f(void) { return __builtin_fmin(__builtin_nan(\"\"), 1.0); }\n");
8474 assert!(text.contains("call @fmin("), "{text}");
8475
8476 let plain = concat!("double ceil(double x);\n", "double f(void) { return ceil(2.25); }\n");
8479 let mut opts = options();
8480 opts.emit = EmitKind::Ir;
8481 opts.no_builtin = vec!["ceil".to_owned()];
8482 assert!(run(&opts, plain).text().contains("call @ceil("), "-fno-builtin-ceil");
8483 }
8484
8485 #[test]
8492 fn a_constexpr_object_is_a_constant_wherever_one_is_required() {
8493 let text = ir(concat!(
8494 "constexpr int side = 4;\n",
8495 "constexpr int wider = side + 1;\n",
8496 "constexpr double half = 1.5;\n",
8497 "struct point { int x; int y; };\n",
8498 "constexpr struct point origin = { 5, 6 };\n",
8499 "int square[side * side];\n",
8500 "int rectangle[wider];\n",
8501 "int rounded[(int)half * 2];\n",
8502 "int across[origin.y];\n",
8503 "enum named { four = side };\n",
8504 "int e = four;\n",
8505 ));
8506 assert!(text.contains("global @square : bytes 64 ="), "{text}");
8507 assert!(text.contains("global @rectangle : bytes 20 ="), "{text}");
8508 assert!(text.contains("global @rounded : bytes 8 ="), "{text}");
8509 assert!(text.contains("global @across : bytes 24 ="), "{text}");
8510 assert!(text.contains("global @e : i32 = 4,"), "{text}");
8511
8512 let mut opts = options();
8515 opts.emit = EmitKind::Ir;
8516 let konst = "const int n = 1;\nint a[n];\n";
8517 let message = "/main.c:2:5: error: variably modified 'a' at file scope [E0538]";
8518 assert_eq!(run(&opts, konst).messages, [message]);
8519
8520 let subscript = "constexpr int t[3] = { 1, 2, 3 };\nint a[t[1]];\n";
8522 assert_eq!(run(&opts, subscript).messages, [message]);
8523
8524 let address = "constexpr int c = 3;\nint *p = &c;\n";
8526 let warning = "/main.c:2:6: warning: initialization discards 'const' qualifier from \
8527 pointer target type [E0514]";
8528 assert_eq!(run(&opts, address).messages, [warning]);
8529 }
8530
8531 #[test]
8540 fn a_member_whose_size_was_refused_is_not_a_flexible_array_member() {
8541 let mut opts = options();
8542 opts.emit = EmitKind::Ir;
8543
8544 let alone = "int k;\nextern struct D { int a[k]; } ed;\n";
8545 let message = "/main.c:2:23: error: variably modified 'a' at file scope [E0538]";
8546 assert_eq!(run(&opts, alone).messages, [message]);
8547
8548 let first = "int k;\nextern struct E { int a[k]; int b; } ee;\n";
8550 assert_eq!(run(&opts, first).messages, [message]);
8551
8552 let negative = "struct F { int a[-1]; };\n";
8555 let refused = "/main.c:1:18: error: size of array 'a' is negative [E0536]";
8556 assert_eq!(run(&opts, negative).messages, [refused]);
8557
8558 let flexible = "struct G { int a[]; };\n";
8561 let named = "/main.c:1:16: error: flexible array member in a struct with no named \
8562 members [E0554]";
8563 assert_eq!(run(&opts, flexible).messages, [named]);
8564 }
8565
8566 #[test]
8581 fn a_pointer_to_an_array_gains_a_qualifier_the_same_way_a_pointer_to_anything_else_does() {
8582 let mut opts = options();
8583 opts.emit = EmitKind::Ir;
8584 let prefix = "typedef unsigned int B[4];\nstruct H { B category[2]; };\n";
8585
8586 let adding = format!("{prefix}const B *f(struct H *h) {{ return &h->category[0]; }}\n");
8588 assert_eq!(run(&opts, &adding).messages, [] as [String; 0]);
8589
8590 let plain = concat!(
8593 "const unsigned int (*f(unsigned int (*p)[4]))[4] { return p; }\n",
8594 "const unsigned int (*g(unsigned int (*p)[2][3]))[2][3] { return p; }\n",
8595 );
8596 assert_eq!(run(&opts, plain).messages, [] as [String; 0]);
8597
8598 let dropping = format!("{prefix}B *f(const B *p) {{ return p; }}\n");
8601 let warning = "/main.c:3:27: warning: return discards 'const' qualifier from pointer target type \
8602 [E0514]";
8603 assert_eq!(run(&opts, &dropping).messages, [warning]);
8604
8605 let wrong = "const unsigned int (*f(unsigned short (*p)[4]))[4] { return p; }\n";
8608 let error = "/main.c:1:61: error: returning 'unsigned short (*)[4]' from a function with \
8609 incompatible return type 'const unsigned int (*)[4]' [E0512]";
8610 assert_eq!(run(&opts, wrong).messages, [error]);
8611 }
8612
8613 #[test]
8622 fn an_old_style_definition_takes_its_types_from_the_declarations_under_its_list() {
8623 let mut opts = options();
8626 opts.std = Std::C17;
8627 let source = concat!(
8628 "int add(a, b)\n",
8629 "int a;\n",
8630 "int b;\n",
8631 "{ return a + b; }\n",
8632 "int promoted(c)\n",
8633 "char c;\n",
8634 "{ return c; }\n",
8635 "int narrow(char);\n",
8636 "int narrow(c)\n",
8637 "char c;\n",
8638 "{ return c; }\n",
8639 "int first(a)\n",
8640 "int a[4];\n",
8641 "{ return a[0]; }\n",
8642 );
8643 let result = run(&opts, source);
8644 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
8645 let text = result.text();
8646 assert!(text.contains("add : int(int, int) function external defined"), "{text}");
8647 assert!(text.contains("promoted : int(int) function external defined"), "{text}");
8648 assert!(text.contains("c : char object automatic defined"), "{text}");
8650 assert!(text.contains("narrow : int(char) function external defined"), "{text}");
8651 assert!(text.contains("first : int(int *) function external defined"), "{text}");
8653 }
8654
8655 #[test]
8662 fn the_two_halves_of_an_old_style_parameter_list_have_to_agree() {
8663 let mut opts = options();
8664 opts.std = Std::C17;
8665 for (source, message) in [
8666 ("int f(a, a)\nint a;\n{ return a; }\n", "1:10: error: multiple parameters named 'a'"),
8667 (
8668 "int f(a)\nint a;\nint b;\n{ return a; }\n",
8669 "3:5: error: declaration for parameter 'b' but no such parameter",
8670 ),
8671 ("int f(a)\nint a;\nint a;\n{ return a; }\n", "3:5: error: redefinition of parameter"),
8672 ("int f(a)\nint a = 1;\n{ return a; }\n", "2:5: error: parameter 'a' is initialized"),
8673 (
8674 "int f(a)\nstatic int a;\n{ return a; }\n",
8675 "2:12: error: storage class specified for parameter 'a'",
8676 ),
8677 (
8678 "int f(char);\nint f(a)\nshort a;\n{ return a; }\n",
8679 "2:7: error: argument 'a' doesn't match prototype",
8680 ),
8681 ] {
8682 let result = run(&opts, source);
8683 assert!(result.failed(), "expected this to fail:\n{source}");
8684 assert!(result.messages[0].contains(message), "{:?}", result.messages);
8685 }
8686
8687 let implicit = "int f(a, b)\nint a;\n{ return a + b; }\n";
8690 let mut older = options();
8691 older.std = Std::C89;
8692 assert!(!run(&older, implicit).failed(), "{:?}", run(&older, implicit).messages);
8693 let result = run(&opts, implicit);
8694 assert!(
8695 result.messages[0].contains("1:10: error: type of 'b' defaults to 'int'"),
8696 "{:?}",
8697 result.messages
8698 );
8699
8700 let mut newer = options();
8704 newer.std = Std::C23;
8705 let plain = "int f(a)\nint a;\n{ return a; }\n";
8706 let result = run(&newer, plain);
8707 assert!(!result.failed(), "{:?}", result.messages);
8708 assert_eq!(
8709 result.messages,
8710 ["/main.c:1:5: warning: old-style function definition [E0412]"]
8711 );
8712 assert!(run(&opts, plain).messages.is_empty(), "and nothing to say in the dialects before");
8713 }
8714
8715 #[test]
8722 fn the_obsolete_designators_are_taken_and_are_pedantic_warnings() {
8723 let array = "int a[8] = { [3] 7 };\n";
8724 let member = "struct s { int x; } v = { x: 7 };\n";
8725 for source in [array, member] {
8726 let result = run(&options(), source);
8727 assert!(!result.failed(), "{:?}", result.messages);
8728 assert!(result.messages.is_empty(), "nothing to say: {:?}", result.messages);
8729 }
8730
8731 let mut asked = options();
8732 asked.pedantic = true;
8733 assert_eq!(
8734 run(&asked, array).messages,
8735 ["/main.c:1:18: warning: obsolete designator, write `[i] =` instead [E0415]"]
8736 );
8737 assert_eq!(
8738 run(&asked, member).messages,
8739 ["/main.c:1:27: warning: obsolete designator, write `.field =` instead [E0413]"]
8740 );
8741 }
8742
8743 #[test]
8750 fn a_type_is_refused_when_it_passes_the_largest_object_and_not_before() {
8751 let text = ir(concat!(
8752 "struct huge_struct { short buf[(1L << 62) - 256]; int a, b, c, d; };\n",
8753 "struct brim { char buf[9223372036854775807L]; };\n",
8754 "struct bitty { char buf[9223372036854775800L]; int x : 1; };\n",
8755 "unsigned long h = sizeof(struct huge_struct);\n",
8756 "unsigned long b = sizeof(struct brim);\n",
8757 "unsigned long y = sizeof(struct bitty);\n",
8758 ));
8759 assert!(text.contains("global @h : i64 = 9223372036854775312,"), "{text}");
8760 assert!(text.contains("global @b : i64 = 9223372036854775807,"), "{text}");
8761 assert!(text.contains("global @y : i64 = 9223372036854775804,"), "{text}");
8762
8763 let mut opts = options();
8764 opts.emit = EmitKind::Ir;
8765 let over = "struct over { char buf[9223372036854775800L]; char x[8]; };\n";
8766 let message = "/main.c:1:1: error: type 'struct over' is too large [E0560]";
8767 assert_eq!(run(&opts, over).messages, [message]);
8768 let array = "struct wide { short buf[1L << 62]; };\n";
8769 let message = "/main.c:1:25: error: size of array 'buf' exceeds \
8770 maximum object size '9223372036854775807' [E0537]";
8771 assert_eq!(run(&opts, array).messages[0], message);
8772 }
8773
8774 fn compile_bytes(source: &[u8]) -> Compiled {
8779 let mut opts = options();
8780 opts.emit = EmitKind::Ir;
8781 let mut fs = MemoryFileSystem::new();
8782 fs.insert("/main.c", source.to_vec());
8783 compile(&opts, "/main.c", &fs)
8784 }
8785
8786 #[test]
8793 fn a_byte_that_is_not_a_character_is_kept_in_a_literal_and_refused_outside_one() {
8794 let mut source = b"char s[] = \"a".to_vec();
8795 source.push(0xff);
8796 source.extend_from_slice(b"b\";\nchar c = '");
8797 source.push(0xff);
8798 source.extend_from_slice(b"';\n");
8799 let result = compile_bytes(&source);
8800 assert_eq!(result.messages, Vec::<String>::new(), "a raw byte in a literal is that byte");
8801 assert!(result.text().contains(r#"bytes "a\ffb\00""#), "{}", result.text());
8802 assert!(result.text().contains("global @c : i8 = -1,"), "{}", result.text());
8804
8805 let mut stray = b"int a".to_vec();
8806 stray.push(0xff);
8807 stray.extend_from_slice(b" = 1;\n");
8808 let result = compile_bytes(&stray);
8809 assert!(
8810 result.messages.iter().any(|m| m.contains("source is not valid UTF-8 here")),
8811 "{:?}",
8812 result.messages
8813 );
8814 }
8815
8816 #[test]
8817 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
8818 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
8819 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
8820 let expected = "\
8821func @add(i32, i32) -> i32, linkage(external) {
8822block0(%0: i32, %1: i32):
8823 %2 = add.nsw %0, %1
8824 return %2
8825}
8826";
8827 assert!(text.contains(expected), "{text}");
8828 }
8829
8830 #[test]
8831 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
8832 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
8833 assert!(!text.contains("alloca"), "{text}");
8834 assert!(!text.contains("load"), "{text}");
8835 assert!(!text.contains("store"), "{text}");
8836 }
8837
8838 #[test]
8839 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
8840 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
8841 let expected = "\
8842block0:
8843 %0 = alloca, size 4, align 4
8844 %1 = iconst.i32 1
8845 store %1 -> %0, align 4, tbaa !1
8846 %2 = call @g(%0) : (ptr) -> i32
8847 return %2
8848";
8849 assert_eq!(text, expected);
8850 }
8851
8852 #[test]
8853 fn a_loop_carries_what_it_changes_as_block_parameters() {
8854 let text = body(
8857 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
8858 return total;\n}\n",
8859 );
8860 assert!(!text.contains("alloca"), "{text}");
8861 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
8862 assert!(text.contains("jump block1("), "{text}");
8863 }
8864
8865 #[test]
8866 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
8867 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
8868 assert!(text.contains("icmp slt %0, %1"), "{text}");
8869 assert!(!text.contains("zext"), "{text}");
8870 }
8871
8872 #[test]
8873 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
8874 let text = body("int f(int a, int b) { return a && b; }\n");
8875 let expected = "\
8876block0(%0: i32, %1: i32):
8877 %2 = iconst.i32 0
8878 %3 = icmp ne %0, %2
8879 %4 = iconst.i1 0
8880 br_if %3, block1, block2(%4)
8881
8882block1:
8883 %5 = iconst.i32 0
8884 %6 = icmp ne %1, %5
8885 jump block2(%6)
8886
8887block2(%7: i1):
8888 %8 = zext.i32 %7
8889 return %8
8890";
8891 assert_eq!(text, expected);
8892 }
8893
8894 #[test]
8895 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
8896 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
8897 assert!(!text.contains("block3"), "{text}");
8900 assert!(!text.contains("iconst.i32 3"), "{text}");
8901 }
8902
8903 #[test]
8904 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
8905 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
8906 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
8907 assert!(body("int f(void) { }\n").contains("unreachable"));
8908 }
8909
8910 #[test]
8911 fn a_structure_is_copied_rather_than_held_in_a_value() {
8912 let text = body(
8913 "struct point { int x, y; };\n\
8914 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
8915 );
8916 assert!(text.contains("memcpy"), "{text}");
8917 }
8918
8919 #[test]
8920 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
8921 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
8922 assert!(text.contains("memset"), "{text}");
8923 }
8924
8925 #[test]
8926 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
8927 let text = body(
8928 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
8929 default: r = 4; } return r; }\n",
8930 );
8931 let expected = "\
8932block0(%0: i32):
8933 %1 = iconst.i32 0
8934 switch %0, block1, [1 => block2, 2 => block3(%1)]
8935
8936block1:
8937 %2 = iconst.i32 4
8938 jump block4(%2)
8939
8940block2:
8941 %3 = iconst.i32 1
8942 jump block3(%3)
8943
8944block3(%4: i32):
8945 %5 = iconst.i32 2
8946 %6 = add.nsw %4, %5
8947 jump block4(%6)
8948
8949block4(%7: i32):
8950 return %7
8951";
8952 assert_eq!(text, expected);
8953 }
8954
8955 #[test]
8956 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
8957 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
8960 assert!(text.contains("%2 = sub %0, %1"), "{text}");
8961 assert!(text.contains("icmp ule"), "{text}");
8962 assert!(!text.contains("switch"), "{text}");
8963 }
8964
8965 #[test]
8966 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
8967 let text = body(
8968 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
8969 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
8970 );
8971 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
8974 assert!(text.contains("block5:\n jump block7("), "{text}");
8975 assert!(text.contains("block6:\n jump block8("), "{text}");
8976 }
8977
8978 #[test]
8979 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
8980 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
8981 }
8982
8983 #[test]
8984 fn a_label_a_loop_is_only_entered_through_builds_the_loop_around_it() {
8985 let text = body(
8990 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
8991 return n; }\n",
8992 );
8993 assert!(text.contains("switch %0, block1(%1), [1 => block2, 2 => block3(%1)]"), "{text}");
8996 assert!(text.contains("block3(%3: i32):\n %4 = iconst.i32 1"), "{text}");
8997 assert!(text.contains("block4:\n jump block3("), "{text}");
8998 }
8999
9000 #[test]
9001 fn a_goto_into_a_loop_body_enters_it_without_the_test() {
9002 let text = body("int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n");
9005 assert!(text.starts_with("block0(%0: i32, %1: i32):\n jump block1(%1)"), "{text}");
9006 assert!(text.contains("block1(%2: i32):\n %3 = iconst.i32 1"), "{text}");
9007 assert!(text.contains("br_if %6, block2, block3"), "{text}");
9008 }
9009
9010 #[test]
9011 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
9012 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
9013 assert!(!text.contains("alloca"), "{text}");
9017 assert!(text.contains("block2(%4: i32):\n return %4"), "{text}");
9018 assert_eq!(text.matches("jump block2(").count(), 2, "{text}");
9019 }
9020
9021 #[test]
9022 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
9023 let text =
9024 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
9025 assert!(!text.contains("alloca"), "{text}");
9026 assert!(text.contains("block1(%2: i32):"), "{text}");
9027 assert!(text.contains("jump block1(%5)"), "{text}");
9028 }
9029
9030 #[test]
9031 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
9032 assert_eq!(
9035 body("int f(int x) { return x; spare: return 0; }\n"),
9036 "block0(%0: i32):\n return %0\n"
9037 );
9038 }
9039
9040 #[test]
9041 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
9042 let text = body(
9043 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
9044 );
9045 assert_eq!(
9048 text,
9049 "\
9050block0(%0: ptr):
9051 %1 = load.i8 %0, align 1
9052 %2 = iconst.i8 3
9053 %3 = ashr %1, %2
9054 %4 = sext.i32 %3
9055 return %4
9056"
9057 );
9058 }
9059
9060 #[test]
9061 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
9062 let text =
9066 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
9067 assert_eq!(
9068 text,
9069 "\
9070block0(%0: ptr, %1: i32):
9071 %2 = iconst.i32 16777215
9072 %3 = and %1, %2
9073 %4 = trunc.i16 %3
9074 store %4 -> %0, align 2
9075 %5 = iconst.i32 16
9076 %6 = lshr %3, %5
9077 %7 = trunc.i8 %6
9078 %8 = iconst.i64 2
9079 %9 = ptr_add %0, %8
9080 store %7 -> %9, align 1
9081 return
9082"
9083 );
9084 }
9085
9086 #[test]
9087 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
9088 let text =
9089 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
9090 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
9093 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
9094 }
9095
9096 #[test]
9097 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
9098 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
9101 assert_eq!(text.matches("ashr").count(), 0, "{text}");
9102 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
9103 }
9104
9105 #[test]
9106 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
9107 let text = body(
9111 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
9112 );
9113 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
9114 }
9115
9116 #[test]
9117 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
9118 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
9121 assert!(
9122 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
9123 "{text}"
9124 );
9125 }
9126
9127 #[test]
9128 fn an_initialized_flexible_array_member_makes_the_object_larger_than_its_type() {
9129 let text = ir(concat!(
9134 "struct a { int i; int j[]; } x = { 1, { 2, 0, 2, 3 } };\n",
9135 "struct b { char c; char p[]; } y = { 'o', \"wx\" };\n",
9136 "struct c { char c; char p[]; } z = { '9', { 'e', 'b' } };\n",
9137 "char s[2] = \"hi\";\n",
9138 ));
9139 assert!(
9140 text.contains("global @x : bytes 20 = { i32 1, i32 2, i32 0, i32 2, i32 3 }"),
9141 "{text}"
9142 );
9143 assert!(text.contains("global @y : bytes 4 = { i8 111, bytes \"wx\\00\" }"), "{text}");
9144 assert!(text.contains("global @z : bytes 3 = { i8 57, i8 101, i8 98 }"), "{text}");
9145 assert!(text.contains("global @s : bytes 2 = { bytes \"hi\" }"), "{text}");
9148 }
9149
9150 #[test]
9151 fn a_definition_takes_a_parameter_it_left_unnamed() {
9152 let text = ir("int f(int a, int) { return a; }\n");
9156 assert!(text.contains("func @f(i32, i32) -> i32"), "{text}");
9157 assert!(text.contains("block0(%0: i32, %1: i32):"), "{text}");
9158
9159 let text = ir("int g(int, int n) { return n; }\n");
9162 assert!(text.contains("block0(%0: i32, %1: i32):\n return %1\n"), "{text}");
9163 }
9164
9165 #[test]
9166 fn an_assignment_of_a_structure_is_the_object_it_wrote() {
9167 let text = body(concat!(
9172 "struct s { int f; int g; };\n",
9173 "void h(struct s *a, struct s *c, struct s *d, struct s *e)\n",
9174 "{ *d = *e = a[0] = *c; }\n",
9175 ));
9176 assert_eq!(text.matches("memcpy").count(), 3, "{text}");
9177 assert!(text.contains("memcpy %8, %1, size 8, align 4\n"), "{text}");
9178 assert!(text.contains("memcpy %3, %8, size 8, align 4\n"), "{text}");
9179 assert!(text.contains("memcpy %2, %3, size 8, align 4\n"), "{text}");
9180 }
9181
9182 #[test]
9183 fn a_string_literal_stops_at_the_end_of_the_array_it_is_filling() {
9184 let mut opts = options();
9189 opts.emit = EmitKind::Ir;
9190 let result = run(
9191 &opts,
9192 concat!(
9193 "const char a[2][3] = { \"1234\", \"xyz\" };\n",
9194 "static const char b[3][5] = { \"12345\", \"678\", \"9\" };\n",
9195 "union u { struct { char x[4]; char y[4]; }; struct { char z[8]; }; };\n",
9196 "const union u c = { { \"1234\", \"567\" } };\n",
9197 ),
9198 );
9199 let text = result.text();
9200 assert_eq!(
9201 result.messages,
9202 ["/main.c:1:24: warning: initializer-string for array of 'const char' is too long \
9203 (5 chars into 3 available) [E0637]"]
9204 );
9205 assert!(text.contains("global @a : bytes 6 = { bytes \"123\", bytes \"xyz\" }"), "{text}");
9206 assert!(
9207 text.contains(
9208 "global @b : bytes 15 = { bytes \"12345\", bytes \"678\\00\", zero 1, \
9209 bytes \"9\\00\", zero 3 }"
9210 ),
9211 "{text}"
9212 );
9213 assert!(
9216 text.contains("global @c : bytes 8 = { bytes \"1234\", bytes \"567\\00\" }"),
9217 "{text}"
9218 );
9219 }
9220
9221 #[test]
9222 fn a_cast_of_a_record_to_its_own_type_is_the_object_that_was_cast() {
9223 let text = body(concat!(
9227 "struct s { int a, b; };\nstruct v { struct s s; int t; };\n",
9228 "void g(struct v *);\n",
9229 "void f(struct s *p) { struct v w = { (struct s)*p, 5 }; g(&w); }\n",
9230 ));
9231 assert_eq!(text.matches("memcpy").count(), 1, "{text}");
9232 }
9233
9234 #[test]
9235 fn a_compound_literal_read_in_a_static_initializer_lays_its_bytes_into_the_image() {
9236 let text = ir(concat!(
9241 "struct s { int x; };\n",
9242 "struct t { struct s s; int o; } a = { (struct s){ 2 }, 3 };\n",
9243 "int n = (int){ 7 };\n",
9244 "struct u { struct s p; struct s q; } b = { (struct s){ 1 }, (struct s){ } };\n",
9245 ));
9246 assert!(text.contains("global @a : bytes 8 = { i32 2, i32 3 }"), "{text}");
9247 assert!(text.contains("global @n : i32 = 7,"), "{text}");
9248 assert!(text.contains("global @b : bytes 8 = { i32 1, zero 4 }"), "{text}");
9251 }
9252
9253 #[test]
9254 fn the_address_of_a_compound_literal_asks_for_the_object_it_points_at() {
9255 let text = ir("struct s { int x; };\nstruct s *q = &(struct s){ 9 };\n");
9259 assert!(text.contains("global @.Lanon.0 : i32 = 9, align 4, linkage(internal)"), "{text}");
9260 assert!(text.contains("global @q : bytes 8 = { addr.8 @.Lanon.0 }"), "{text}");
9261 }
9262
9263 #[test]
9264 fn an_object_of_no_size_at_all_has_an_image_with_nothing_in_it() {
9265 let text = ir("unsigned char foo[1][0];\n");
9269 assert!(text.contains("global @foo : bytes 0 = {}, align 1"), "{text}");
9270 }
9271
9272 #[test]
9273 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
9274 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
9277 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
9278 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
9279 }
9280
9281 #[test]
9282 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
9283 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
9287 assert!(
9288 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
9289 "{text}"
9290 );
9291 }
9292
9293 #[test]
9294 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
9295 let text = body(
9300 "\
9301struct s { int a, b; };
9302struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
9303",
9304 );
9305 assert!(text.contains("block3(%7: ptr)"), "{text}");
9307 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
9308 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
9309 }
9310
9311 #[test]
9319 fn the_left_side_of_a_conditional_with_no_middle_is_evaluated_once() {
9320 let text = body("int f(int i) { return ++i ?: 10; }\n");
9321 assert!(text.contains("jump block3(%2)"), "the arm is the value that was tested: {text}");
9322 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
9323
9324 let text = body("long f(int i) { return ++i ?: 10L; }\n");
9327 assert!(text.contains("%5 = sext.i64 %2"), "the arm widens what was tested: {text}");
9328 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
9329
9330 let text = body("int g(void);\nint f(void) { return g() ?: 10; }\n");
9332 assert_eq!(text.matches("call @g").count(), 1, "called once: {text}");
9333
9334 let text = body("int f(int i) { return ++i ? ++i : 10; }\n");
9337 assert_eq!(text.matches("add.nsw").count(), 2, "incremented twice: {text}");
9338 }
9339
9340 #[test]
9341 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
9342 let text = ir("\
9346struct pair { int a, b; };
9347struct pair make(int a, int b);
9348struct pair twice(struct pair p) { return make(p.a, p.b); }
9349");
9350 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
9351 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
9352 }
9353
9354 #[test]
9355 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
9356 let text = ir("\
9360struct big { double v[8]; };
9361struct big grow(struct big b);
9362struct big twice(struct big b) { return grow(grow(b)); }
9363");
9364 assert!(
9365 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
9366 "{text}"
9367 );
9368 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
9369 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
9372 }
9373
9374 #[test]
9375 fn a_structure_passed_to_a_variadic_function_says_so_at_the_call() {
9376 let text = ir("\
9381struct big { double v[8]; };
9382struct pair { int a, b; };
9383int p(const char *, ...);
9384int f(struct big b, struct pair q) { return p(\"\", 1, b, q); }
9385");
9386 assert!(
9387 text.contains("call @p(%4, %5, %2 byval(64, align 8), %6) : (ptr, ...) -> i32"),
9388 "{text}"
9389 );
9390 }
9391
9392 #[test]
9393 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
9394 let body = body(
9397 "\
9398struct pair { int a, b; };
9399struct pair make(int a, int b);
9400int second(void) { return make(1, 2).b; }
9401",
9402 );
9403 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
9404 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
9405 }
9406
9407 #[test]
9408 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
9409 let source = "\
9413struct hfa { float x, y, z; };
9414int take(struct hfa h);
9415int give(struct hfa h) { return take(h); }
9416";
9417 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
9418 let mut opts = options();
9419 opts.emit = EmitKind::Ir;
9420 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
9421 let result = run(&opts, source);
9422 assert_eq!(result.messages, Vec::<String>::new());
9423 assert!(result.text().contains("func @take(f32, f32, f32) -> i32"), "{}", result.text());
9424 }
9425
9426 #[test]
9427 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
9428 let source = "\
9431int use(int *);
9432void f(int n) {
9433 {
9434 int a[n];
9435 use(a);
9436 }
9437 use(0);
9438}
9439";
9440 let body = body(source);
9441 assert!(body.contains("mul.nsw"), "{body}");
9442 assert!(body.contains("stacksave"), "{body}");
9443 assert!(body.contains("alloca %"), "{body}");
9444 assert!(body.contains("stackrestore"), "{body}");
9445 }
9446
9447 #[test]
9448 fn a_goto_out_of_the_scope_of_one_gives_its_stack_back_on_the_way() {
9449 let source = "\
9454int use(int *);
9455int f(int n) {
9456 {
9457 int a[n];
9458 if (use(a)) goto out;
9459 use(0);
9460 }
9461out:
9462 return 0;
9463}
9464";
9465 let body = body(source);
9466 assert_eq!(body.matches("stackrestore").count(), 2, "{body}");
9468 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
9469 assert!(after.starts_with(" %4\n jump block"), "{body}");
9470 }
9471
9472 #[test]
9473 fn a_goto_to_a_label_the_array_is_still_alive_at_leaves_the_stack_alone() {
9474 let source = "\
9478int use(int *);
9479int f(int n) {
9480 int a[n];
9481again:
9482 if (use(a)) goto again;
9483 return 0;
9484}
9485";
9486 let body = body(source);
9487 assert!(body.contains("stacksave"), "{body}");
9488 assert!(!body.contains("stackrestore"), "{body}");
9489 }
9490
9491 #[test]
9492 fn a_goto_back_to_a_label_in_front_of_one_gives_it_back_every_time_round() {
9493 let source = "\
9498int use(int *);
9499int f(int n) {
9500again:
9501 {
9502 int a[n];
9503 if (use(a)) goto again;
9504 }
9505 return 0;
9506}
9507";
9508 let body = body(source);
9509 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
9510 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
9511 assert!(after.starts_with(" %4\n jump block1\n"), "{body}");
9512 }
9513
9514 #[test]
9515 fn the_head_of_a_for_loop_is_a_scope_that_closes_where_the_loop_is_left() {
9516 let source = "\
9522int f(void);
9523void t(void) {
9524 int count = 10;
9525 for (; count--;) {
9526 int b[f()];
9527 int i;
9528 for (i = 0; i < f(); i++) {
9529 b[i] = count;
9530 }
9531 }
9532}
9533";
9534 let body = body(source);
9535 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
9539 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
9540 let next = after.split("\n\n").next().expect("the block the restore is in");
9543 assert!(next.contains("jump block1("), "{body}");
9544 }
9545
9546 #[test]
9547 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
9548 let source = "\
9551unsigned long f(int n) {
9552 int a[n];
9553 n = 0;
9554 return sizeof a;
9555}
9556";
9557 let body = body(source);
9558 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
9560 }
9561
9562 #[test]
9563 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
9564 let source = "\
9567int use(int);
9568int f(int x) {
9569 return ({
9570 int t = use(x);
9571 t * t;
9572 });
9573}
9574";
9575 let expected = "\
9576block0(%0: i32):
9577 %1 = call @use(%0) : (i32) -> i32
9578 %2 = mul.nsw %1, %1
9579 return %2
9580";
9581 assert_eq!(body(source), expected);
9582 }
9583
9584 #[test]
9585 fn a_comma_whose_value_is_an_object_names_the_object_the_right_side_named() {
9586 let source = "\
9590struct pair { int a, b; };
9591void bail(void);
9592int f(struct pair p) {
9593 return (bail(), p).b;
9594}
9595";
9596 let expected = "\
9597block0(%0: i64):
9598 %1 = alloca, size 8, align 4
9599 store %0 -> %1, align 4
9600 call @bail() : ()
9601 %2 = iconst.i64 4
9602 %3 = ptr_add %1, %2
9603 %4 = load.i32 %3, align 4, tbaa !1
9604 return %4
9605";
9606 assert_eq!(body(source), expected);
9607 }
9608
9609 #[test]
9610 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
9611 let source = "int f(int x) { return ({ return x; 0; }); }\n";
9615 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
9616 }
9617
9618 #[test]
9619 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
9620 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
9624 let expected = "\
9625block0(%0: ptr):
9626 %1 = va_arg.f64 %0
9627 %2 = va_arg.f64 %0
9628 %3 = fadd %1, %2
9629 return %3
9630";
9631 assert_eq!(body(source), expected);
9632 }
9633
9634 #[test]
9635 fn one_that_reads_a_structure_answers_where_the_object_is() {
9636 let source = "\
9650struct s { int a; long b; };
9651long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.b; }
9652";
9653 let expected = "\
9654block0(%0: ptr):
9655 %1 = alloca, size 16, align 16
9656 %2 = va_object %0, size 16, align 8, in(int 8 at 0, int 8 at 8)
9657 memcpy %1, %2, size 16, align 8
9658 %3 = iconst.i64 8
9659 %4 = ptr_add %1, %3
9660 %5 = load.i64 %4, align 8, tbaa !1
9661 return %5
9662";
9663 assert_eq!(body(source), expected);
9664 }
9665
9666 #[test]
9670 fn the_classification_says_which_registers_the_object_arrived_in() {
9671 let source = "\
9672struct s { double a; double b; };
9673double f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a; }
9674";
9675 assert!(
9676 body(source)
9677 .contains("va_object %0, size 16, align 8, in(float f64 at 0, float f64 at 8)"),
9678 "{}",
9679 body(source)
9680 );
9681
9682 let big = "\
9683struct s { long a[4]; };
9684long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a[0]; }
9685";
9686 assert!(body(big).contains("va_object %0, size 32, align 8\n"), "{}", body(big));
9687 }
9688
9689 #[test]
9690 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
9691 let source = "\
9695int f(int c) {
9696 void *p = c ? &&one : &&two;
9697 goto *p;
9698one:
9699 return 1;
9700two:
9701 return 2;
9702}
9703";
9704 let expected = "\
9705block0(%0: i32):
9706 %1 = iconst.i32 0
9707 %2 = icmp ne %0, %1
9708 br_if %2, block1, block2
9709
9710block1:
9711 %3 = block_addr block3
9712 jump block4(%3)
9713
9714block2:
9715 %4 = block_addr block5
9716 jump block4(%4)
9717
9718block3:
9719 %5 = iconst.i32 1
9720 return %5
9721
9722block4(%6: ptr):
9723 indirect_br %6, block3, block5
9724
9725block5:
9726 %7 = iconst.i32 2
9727 return %7
9728";
9729 assert_eq!(body(source), expected);
9730 }
9731
9732 fn dispatch(labels: usize) -> String {
9735 let mask = labels - 1;
9736 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
9737 for index in 0..labels {
9738 source.push_str(&format!(" &&a{index},"));
9739 }
9740 source.push_str(" };\n\tint w = n, x = n + 1, y = n + 2, z = n + 3;\n");
9741 source.push_str(&format!("\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
9742 for index in 0..labels {
9743 let step = match index % 4 {
9744 0 => "w += x;",
9745 1 => "x += y;",
9746 2 => "y += z;",
9747 _ => "z += w;",
9748 };
9749 source.push_str(&format!("a{index}:\n\t{step}\n"));
9750 source.push_str("\tif (--n <= 0) return w + x + y + z;\n");
9751 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
9752 }
9753 source.push_str("}\n");
9754 source
9755 }
9756
9757 fn in_front_of_the_jump(text: &str) -> usize {
9759 let (before, _) = text.split_once("\tjmp\t*%").expect("a jump through a register");
9760 before.lines().rev().take_while(|line| line.starts_with("\tmov")).count()
9761 }
9762
9763 #[test]
9773 fn a_jump_through_a_register_writes_what_it_carries_and_not_the_whole_table() {
9774 let small = in_front_of_the_jump(&asm(&dispatch(4)));
9775 let large = in_front_of_the_jump(&asm(&dispatch(32)));
9776 assert_eq!(small, large, "eight times the labels and the same values in hand");
9777 assert!(large <= 8, "the values the loop keeps, and not a set of them per label: {large}");
9778 }
9779
9780 fn crowded(labels: usize) -> String {
9783 const VALUES: usize = 24;
9784 let mask = labels - 1;
9785 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
9786 for index in 0..labels {
9787 source.push_str(&format!(" &&a{index},"));
9788 }
9789 source.push_str(" };\n\t");
9790 for value in 0..VALUES {
9791 source.push_str(&format!("int v{value} = n + {value}; "));
9792 }
9793 let sum: Vec<String> = (0..VALUES).map(|value| format!("v{value}")).collect();
9794 source.push_str(&format!("\n\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
9795 for index in 0..labels {
9796 let (to, from) = (index % VALUES, (index + 1) % VALUES);
9797 source.push_str(&format!("a{index}:\n\tv{to} += v{from};\n"));
9798 source.push_str(&format!("\tif (--n <= 0) return {};\n", sum.join(" + ")));
9799 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
9800 }
9801 source.push_str("}\n");
9802 source
9803 }
9804
9805 fn the_frame(text: &str) -> u64 {
9807 text.lines()
9808 .find_map(|line| {
9809 let (size, _) = line.strip_prefix("\tsubq\t$")?.split_once(", %rsp")?;
9810 size.parse().ok()
9811 })
9812 .expect("a function that opens a frame")
9813 }
9814
9815 #[test]
9824 fn a_frame_holds_what_is_wanted_at_once_and_not_a_slot_for_every_label() {
9825 let small = the_frame(&asm(&crowded(16)));
9826 let large = the_frame(&asm(&crowded(64)));
9827 assert_eq!(small, large, "four times the labels and the same values: {small}, {large}");
9828 }
9829
9830 #[test]
9839 fn a_template_that_names_its_own_registers_gets_the_ones_it_named() {
9840 let source = "void save(void *nlr) {
9841 __asm volatile (
9842 \"movq %%rsp, 32(%%rdi) \\n\"
9843 \"movq %%rbx, 40(%%rdi) \\n\"
9844 \"movq %%r12, 48(%%rdi) \\n\"
9845 : : \"D\" (nlr) : \"memory\");
9846}
9847";
9848 let text = asm(source);
9849 assert!(text.contains("\tmovq\t%rsp, 32(%rdi)\n"), "{text}");
9850 assert!(text.contains("\tmovq\t%rbx, 40(%rdi)\n"), "{text}");
9851 assert!(text.contains("\tmovq\t%r12, 48(%rdi)\n"), "{text}");
9852 }
9853
9854 #[test]
9855 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
9856 let source = "void **next(void);
9859void f(void) { goto *next(); }
9860";
9861 let expected = "\
9862block0:
9863 %0 = call @next() : () -> ptr
9864 unreachable
9865";
9866 assert_eq!(body(source), expected);
9867 }
9868
9869 #[test]
9870 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
9871 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
9874 let expected = "\
9875block0:
9876 inline_asm.volatile \"mfence\", \"\", \"memory\"()
9877 return
9878";
9879 assert_eq!(body(source), expected);
9880 }
9881
9882 #[test]
9883 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
9884 let source = "\
9887int f(int x, int y) {
9888 int r;
9889 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
9890 return r + y;
9891}
9892";
9893 let expected = "\
9894block0(%0: i32, %1: i32):
9895 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
9896 %4 = add.nsw %2, %3
9897 return %4
9898";
9899 assert_eq!(body(source), expected);
9900 }
9901
9902 #[test]
9903 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
9904 let source = "\
9909struct pair { int a, b; };
9910int f(int x) {
9911 int slot = x;
9912 struct pair p = { x, x };
9913 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
9914 return slot + p.a;
9915}
9916";
9917 let text = body(source);
9918 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
9919 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
9920 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
9921 }
9922
9923 #[test]
9924 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
9925 let source = "\
9930int f(int x) {
9931 int r = 7;
9932 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
9933 return r;
9934away:
9935 return r;
9936}
9937";
9938 let expected = "\
9939block0(%0: i32):
9940 %1 = iconst.i32 7
9941 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
9942
9943block1:
9944 return %2
9945
9946block2:
9947 return %1
9948";
9949 assert_eq!(body(source), expected);
9950 }
9951
9952 #[test]
9953 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
9954 let mut opts = options();
9958 opts.emit = EmitKind::Ir;
9959 for (source, expected) in [
9960 (
9961 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
9962 "output operand constraint lacks '='",
9963 ),
9964 (
9965 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
9966 "lvalue required in 'asm' statement",
9967 ),
9968 (
9969 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
9970 "read-only variable 'g' used as 'asm' output",
9971 ),
9972 (
9973 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
9974 "input operand constraint contains '='",
9975 ),
9976 (
9977 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
9978 "memory input 0 is not directly addressable",
9979 ),
9980 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
9981 (
9982 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
9983 "duplicate asm operand name 'a'",
9984 ),
9985 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
9986 ] {
9987 let result = run(&opts, source);
9988 assert!(result.failed(), "expected this to be reported:\n{source}");
9989 assert!(
9990 result.messages.iter().any(|m| m.contains(expected)),
9991 "{expected}\n{:?}",
9992 result.messages
9993 );
9994 }
9995 }
9996
9997 #[test]
10002 fn an_asm_at_file_scope_that_is_directives_becomes_the_objects_it_defines() {
10003 let text = ir(concat!(
10004 "__asm__(\n",
10005 " \".section .rodata\\n\"\n",
10006 " \".globl first\\n\"\n",
10007 " \".balign 8\\n\"\n",
10008 " \"first:\\n\"\n",
10009 " \".long 1\\n\"\n",
10010 " \".long 2\\n\"\n",
10011 " \".globl last\\n\"\n",
10012 " \"last:\\n\"\n",
10013 " \".quad last - first\\n\");\n",
10014 "extern const int first[];\n",
10015 "extern const long last;\n",
10016 ));
10017 assert!(text.contains("global @first : bytes 8 = { i32 1, i32 2 }, align 8"), "{text}");
10018 assert!(text.contains("global @last : i64 = 8"), "{text}");
10019 }
10020
10021 #[test]
10025 fn a_name_an_asm_at_file_scope_defined_is_not_undone_by_a_declaration_of_it() {
10026 let text = ir(concat!(
10027 "__asm__(\".data\\n.globl counter\\ncounter:\\n.long 7\\n\");\n",
10028 "extern int counter;\n",
10029 "int read(void) { return counter; }\n",
10030 ));
10031 assert!(text.contains("global @counter : i32 = 7"), "{text}");
10032 }
10033
10034 #[test]
10039 fn bytes_under_no_label_at_file_scope_are_a_global_in_front_of_the_label() {
10040 let text = ir(concat!(
10041 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n662:\\n",
10042 ".pushsection .data.ignore\\n.byte 7\\n.popsection\\n.byte 662b - 661b\\n\");\n",
10043 "extern unsigned char stuff[];\n",
10044 "int read(void) { return stuff[0]; }\n",
10045 ));
10046 let under = text.find("global @.Lasm.0 : i8 = 41").expect(&text);
10047 let named = text.find("global @stuff : i8 = 42").expect(&text);
10048 assert!(under < named, "the bytes under no label come first: {text}");
10049 assert!(text.contains("global @.Lasm.1 : i8 = 7, align 1, linkage(internal), section"));
10050 let after = text.find("global @.Lasm.2 : i8 = 1").expect(&text);
10055 assert!(named < after, "{text}");
10056 }
10057
10058 #[test]
10063 fn a_distance_from_here_at_file_scope_is_a_hole_naming_the_global_it_measures_to() {
10064 let text = ir(concat!(
10065 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n",
10066 ".pushsection .data.ignore\\n.long 661b - .\\n.popsection\\n\");\n",
10067 "extern unsigned char stuff[];\n",
10068 "int read(void) { return stuff[0]; }\n",
10069 ));
10070 assert!(text.contains("global @.Lasm.1 : bytes 4 = { away.4 @stuff }"), "{text}");
10074 }
10075
10076 #[test]
10081 fn a_set_at_file_scope_is_a_second_name_for_what_it_names() {
10082 let text = ir(concat!(
10083 "void base(void) {}\n",
10084 "__asm__(\".weak one\\n.set one, base\");\n",
10085 "__asm__(\".globl two\\n.set two, base\");\n",
10086 "__asm__(\".set three, base\");\n",
10087 "void three(void) {}\n",
10088 ));
10089 assert!(text.contains("alias @one = @base, linkage(weak)"), "{text}");
10090 assert!(text.contains("alias @two = @base"), "{text}");
10091 assert!(!text.contains("alias @three"), "a definition of the name wins: {text}");
10092 assert!(text.contains("func @three"), "{text}");
10093 }
10094
10095 #[test]
10100 fn a_set_of_a_name_this_file_does_not_define_says_so() {
10101 let messages = errors("__asm__(\".set here, elsewhere\");\n");
10102 assert!(
10103 messages
10104 .iter()
10105 .any(|m| m.contains("'here' is aliased to undefined symbol 'elsewhere'")
10106 && m.contains("E0697")),
10107 "{messages:?}"
10108 );
10109 }
10110
10111 #[test]
10114 fn an_incbin_at_file_scope_is_the_bytes_of_the_file_it_names() {
10115 let mut opts = options();
10116 opts.emit = EmitKind::Ir;
10117 let mut fs = MemoryFileSystem::new();
10118 fs.insert(
10119 "/main.c",
10120 b"__asm__(\".data\\n.globl blob\\nblob:\\n.incbin \\\"seed\\\"\\n\");\n".to_vec(),
10121 );
10122 fs.insert("seed", b"hi".to_vec());
10123 let result = compile(&opts, "/main.c", &fs);
10124 assert_eq!(result.messages, Vec::<String>::new());
10125 let text = result.text();
10126 assert!(text.contains("global @blob : bytes 2 = { bytes \"hi\" }"), "{text}");
10127 }
10128
10129 #[test]
10132 fn an_incbin_naming_a_file_that_is_not_there_says_which_file() {
10133 let messages = errors("__asm__(\".data\\nb:\\n.incbin \\\"nowhere\\\"\\n\");\n");
10134 assert!(
10135 messages
10136 .iter()
10137 .any(|m| m.contains("cannot open 'nowhere' for reading") && m.contains("E0702")),
10138 "{messages:?}"
10139 );
10140 }
10141
10142 #[test]
10146 fn a_directive_in_an_asm_at_file_scope_is_refused_rather_than_ignored() {
10147 let source = "__asm__(\".data\\n.set alias, 4\\n\");\n";
10148 let messages = errors(source);
10149 assert!(
10150 messages
10151 .iter()
10152 .any(|m| m.contains("not supported yet") && m.contains("in an `asm` at file scope")),
10153 "{source}\n{messages:?}"
10154 );
10155 }
10156
10157 #[test]
10167 fn a_naked_function_is_its_own_prologue_and_its_own_ending() {
10168 let text = asm(concat!(
10169 "unsigned nlr_push_tail(void *nlr);\n",
10170 "__attribute__((naked)) unsigned nlr_push(void *nlr) {\n",
10171 " __asm volatile(\n",
10172 " \"movq (%rsp), %rax\\n\"\n",
10173 " \"movq %rax, 16(%rdi)\\n\"\n",
10174 " \"movq %rbx, 40(%rdi)\\n\"\n",
10175 " \"jmp nlr_push_tail\\n\");\n",
10176 "}\n",
10177 ));
10178 assert!(text.contains("\tmovq\t(%rsp), %rax\n"), "{text}");
10179 assert!(text.contains("\tjmp\tnlr_push_tail\n"), "{text}");
10180 assert!(text.contains("\tud2\n"), "{text}");
10181 assert!(!text.contains("\tpushq\t"), "nothing is saved in front of it: {text}");
10182 assert!(!text.contains("\tret\n"), "the jump is where it ends: {text}");
10183 }
10184
10185 #[test]
10188 fn what_a_function_without_a_prologue_cannot_be_given_is_refused() {
10189 let mut opts = options();
10190 opts.emit = EmitKind::Asm;
10191 for (source, why) in [
10192 (
10193 "__attribute__((naked)) void f(void) { volatile long a[8]; a[0] = 1; }\n",
10194 "bytes of frame",
10195 ),
10196 (
10197 "__attribute__((naked)) void f(int n) { char a[n]; __asm(\"nop\" ::\"r\"(a)); }\n",
10198 "has no prologue to point a frame pointer at it with",
10199 ),
10200 ("void elsewhere(void); void f(void) { __asm(\"jmp elsewhere\"); }\n", "jumps out of"),
10201 ] {
10202 let result = run(&opts, source);
10203 assert!(result.failed(), "expected this to be refused:\n{source}");
10204 assert!(
10205 result.messages.iter().any(|message| message.contains(why)),
10206 "{:?}",
10207 result.messages
10208 );
10209 }
10210 }
10211
10212 #[test]
10213 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
10214 let mut opts = options();
10215 opts.emit = EmitKind::Ir;
10216 for source in [
10217 "int f(int n) { void *p = &&out; if (n) goto *p; { int a[n]; out: return 1; } }\n",
10218 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
10219 ] {
10220 let result = run(&opts, source);
10221 assert!(result.failed(), "expected this to be reported:\n{source}");
10222 assert!(
10223 result.messages.iter().any(|m| m.contains("not supported yet")),
10224 "{:?}",
10225 result.messages
10226 );
10227 }
10228 }
10229
10230 fn round_trip(source: &str) -> (String, String) {
10232 let printed = ir(source);
10233 let mut opts = options();
10234 opts.emit = EmitKind::Ir;
10235 let mut fs = MemoryFileSystem::new();
10236 fs.insert("/main.ir", printed.clone().into_bytes());
10237 let result = compile_ir(&opts, "/main.ir", &fs);
10238 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
10239 (printed, result.text().to_owned())
10240 }
10241
10242 #[test]
10243 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
10244 let (printed, again) = round_trip(
10248 "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",
10249 );
10250 assert_eq!(printed, again);
10251 }
10252
10253 #[test]
10254 fn ir_that_is_not_ir_says_which_line_stopped_it() {
10255 let mut opts = options();
10256 opts.emit = EmitKind::Ir;
10257 let mut fs = MemoryFileSystem::new();
10258 let text = "\
10259; ModuleID = 'a.c'
10260; format 0
10261target triple = \"x86_64-unknown-linux-gnu\"
10262target datalayout = \"e-p:64:64-i64:64-S128\"
10263
10264func @f(), linkage(external) {
10265block0:
10266 frobnicate
10267}
10268";
10269 fs.insert("/main.ir", text.as_bytes().to_vec());
10270 let result = compile_ir(&opts, "/main.ir", &fs);
10271 assert!(result.failed());
10272 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
10273 }
10274
10275 #[test]
10276 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
10277 let mut opts = options();
10280 opts.emit = EmitKind::Ir;
10281 let mut fs = MemoryFileSystem::new();
10282 let text = "\
10283; ModuleID = 'a.c'
10284; format 0
10285target triple = \"x86_64-unknown-linux-gnu\"
10286target datalayout = \"e-p:64:64-i64:64-S128\"
10287
10288func @f(), linkage(external) {
10289block0:
10290 %0 = iconst.i32 1
10291 return %0
10292}
10293";
10294 fs.insert("/main.ir", text.as_bytes().to_vec());
10295 let result = compile_ir(&opts, "/main.ir", &fs);
10296 assert!(result.failed());
10297 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
10298 }
10299
10300 #[test]
10301 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
10302 let mut fs = MemoryFileSystem::new();
10304 fs.insert("/main.ir", Vec::new());
10305 let result = compile_ir(&options(), "/main.ir", &fs);
10306 assert!(result.failed());
10307 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
10308 }
10309
10310 #[test]
10311 fn the_printed_ir_reads_back_as_the_same_module() {
10312 let text = ir("\
10315struct point { int x, y; };
10316static const char greeting[] = \"hi\";
10317int table[4] = { 1, 2, 3 };
10318int puts(const char *);
10319double half(double x) { return x / 2.0; }
10320int f(int n) {
10321 int total = 0;
10322 for (int i = 0; i < n; i++) {
10323 if (i == 3) continue;
10324 total += table[i];
10325 }
10326 switch (n) {
10327 case 0: total = 1;
10328 case 1: total++; break;
10329 default: total = -total;
10330 }
10331 struct point p = { total, 1 };
10332 int *q = &p.y;
10333 puts(greeting);
10334 return p.x + *q;
10335}
10336int dispatch(int c) {
10337 void *p = c ? &&one : &&two;
10338 goto *p;
10339one:
10340 return 1;
10341two:
10342 return 2;
10343}
10344int assembly(int x, int *p) {
10345 int r;
10346 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
10347 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
10348 return r;
10349away:
10350 return 0;
10351}
10352");
10353 let mut names = Interner::new();
10354 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
10355 assert_eq!(rucc_ir::print(&module, &names), text);
10356 }
10357
10358 #[test]
10359 fn what_save_temps_keeps_is_the_text_that_was_compiled_and_the_assembly_that_was_assembled() {
10360 let mut opts = options();
10364 opts.emit = EmitKind::Object;
10365 opts.save_temps = rucc_session::SaveTemps::Object;
10366 let result = run(&opts, "#define N 2\nint a[N];\n");
10367 assert_eq!(result.messages, Vec::<String>::new());
10368 let text = result.temps.preprocessed.expect("the preprocessed text");
10369 assert!(text.contains("int a[2];"), "{text}");
10370 assert!(text.starts_with("# 1 \"/main.c\""), "{text}");
10371 let asm = result.temps.assembly.expect("the assembly");
10372 assert!(asm.contains("a:"), "{asm}");
10373 assert!(matches!(result.artifact, Artifact::Object { .. }), "{:?}", result.artifact);
10374 }
10375
10376 #[test]
10377 fn nothing_is_kept_unless_the_flag_asked_for_it() {
10378 let mut opts = options();
10381 opts.emit = EmitKind::Object;
10382 assert_eq!(run(&opts, "int a;\n").temps, Temps::default());
10383 }
10384
10385 #[test]
10386 fn a_compilation_that_stops_before_the_back_end_keeps_the_text_and_no_assembly() {
10387 let mut opts = options();
10390 opts.emit = EmitKind::Ir;
10391 opts.save_temps = rucc_session::SaveTemps::Cwd;
10392 let result = run(&opts, "int a;\n");
10393 assert!(result.temps.preprocessed.is_some());
10394 assert_eq!(result.temps.assembly, None);
10395 }
10396
10397 fn span(from: u64, len: u64, held: rucc_debug::Held) -> rucc_debug::Span {
10399 rucc_debug::Span { from, len, held }
10400 }
10401
10402 #[test]
10403 fn two_stretches_that_meet_and_agree_come_out_as_one() {
10404 let one = span(0, 4, rucc_debug::Held::Reg(3));
10405 let two = span(4, 4, rucc_debug::Held::Reg(3));
10406 assert_eq!(settle(vec![two, one]), vec![span(0, 8, rucc_debug::Held::Reg(3))]);
10407 }
10408
10409 #[test]
10410 fn a_stretch_another_starts_inside_and_disagrees_with_ends_where_the_other_starts() {
10411 let one = span(0, 8, rucc_debug::Held::Reg(3));
10412 let two = span(4, 8, rucc_debug::Held::Reg(4));
10413 let settled = settle(vec![one, two]);
10416 assert_eq!(
10417 settled,
10418 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 8, rucc_debug::Held::Reg(4))]
10419 );
10420 }
10421
10422 #[test]
10423 fn a_stretch_cut_by_one_that_ends_first_does_not_come_back_after_it() {
10424 let one = span(0, 16, rucc_debug::Held::Reg(3));
10427 let two = span(4, 4, rucc_debug::Held::Reg(4));
10428 assert_eq!(
10429 settle(vec![one, two]),
10430 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 4, rucc_debug::Held::Reg(4))]
10431 );
10432 }
10433
10434 #[test]
10435 fn a_stretch_inside_another_that_agrees_with_it_cuts_nothing() {
10436 let one = span(0, 16, rucc_debug::Held::Reg(3));
10437 let two = span(4, 4, rucc_debug::Held::Reg(3));
10438 assert_eq!(settle(vec![one, two]), vec![span(0, 16, rucc_debug::Held::Reg(3))]);
10439 }
10440
10441 #[test]
10442 fn a_stretch_two_others_disagree_over_the_whole_of_says_nothing_at_all() {
10443 let one = span(0, 8, rucc_debug::Held::Reg(3));
10444 let two = span(0, 8, rucc_debug::Held::Frame(-16));
10445 assert_eq!(settle(vec![one, two]), Vec::new());
10446 }
10447
10448 #[test]
10449 fn stretches_with_a_gap_between_them_keep_the_gap() {
10450 let one = span(0, 4, rucc_debug::Held::Reg(3));
10451 let two = span(16, 4, rucc_debug::Held::Reg(3));
10452 assert_eq!(settle(vec![one, two]), vec![one, two]);
10453 }
10454
10455 fn extent(len: usize) -> rucc_object::Extent {
10457 rucc_object::Extent {
10458 name: "f".to_owned(),
10459 start: 0,
10460 len,
10461 align: 1,
10462 binding: rucc_object::Binding::Global,
10463 visibility: rucc_object::Visibility::Default,
10464 patch: None,
10465 landings: Vec::new(),
10466 }
10467 }
10468
10469 fn row(at: usize, lo: u32, hi: u32) -> rucc_asm::Row {
10471 let span = Span::new(lo, hi);
10472 rucc_asm::Row { at, span, inst: None }
10473 }
10474
10475 #[test]
10476 fn a_row_ends_where_the_next_address_begins() {
10477 let rows = [row(0, 0, 1), row(4, 1, 2), row(10, 2, 3)];
10478 assert_eq!(ends(&extent(16), &rows), vec![4, 10, 16]);
10479 }
10480
10481 #[test]
10482 fn rows_sharing_an_address_all_end_where_the_next_address_begins() {
10483 let rows = [row(0, 0, 1), row(4, 1, 2), row(4, 2, 3), row(4, 3, 4)];
10486 assert_eq!(ends(&extent(12), &rows), vec![4, 12, 12, 12]);
10487 }
10488
10489 #[test]
10490 fn the_rows_of_a_scope_that_are_next_to_each_other_come_out_as_one_stretch() {
10491 let rows = [row(0, 0, 4), row(4, 10, 14), row(8, 14, 18), row(12, 40, 44)];
10492 let ends = ends(&extent(16), &rows);
10493 let scope = Span::new(8, 20);
10494 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 8 }]);
10495 }
10496
10497 #[test]
10498 fn a_scope_the_back_end_split_in_two_comes_out_as_two_stretches() {
10499 let rows = [row(0, 10, 14), row(4, 40, 44), row(8, 14, 18)];
10500 let ends = ends(&extent(12), &rows);
10501 let scope = Span::new(8, 20);
10502 let over = spread(scope, &ends, &rows);
10503 assert_eq!(
10504 over,
10505 vec![rucc_debug::Reach { from: 0, len: 4 }, rucc_debug::Reach { from: 8, len: 4 }]
10506 );
10507 }
10508
10509 #[test]
10510 fn a_row_with_no_source_of_its_own_belongs_to_no_scope() {
10511 let rows = [rucc_asm::Row { at: 0, span: Span::DUMMY, inst: None }, row(4, 10, 14)];
10513 let ends = ends(&extent(8), &rows);
10514 let scope = Span::new(0, 20);
10515 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 4 }]);
10516 }
10517
10518 fn scope(parent: Option<usize>, lo: u32, hi: u32) -> crate::shapes::Scope {
10520 let span = Span::new(lo, hi);
10521 crate::shapes::Scope { parent, span }
10522 }
10523
10524 #[test]
10525 fn a_function_gets_the_scopes_its_own_locals_are_in_and_nothing_else() {
10526 let scopes = [scope(None, 0, 10), scope(None, 20, 30), scope(Some(1), 22, 26)];
10528 let rows = [row(0, 22, 24), row(4, 26, 28)];
10529 let (out, at) = nests(&[Some(2)], &scopes, &extent(8), &rows);
10530 assert_eq!(at.get(&1), Some(&0));
10533 assert_eq!(at.get(&2), Some(&1));
10534 assert_eq!(at.get(&0), None);
10535 assert_eq!(out.len(), 2);
10536 assert_eq!(out[0].parent, None);
10537 assert_eq!(out[1].parent, Some(0));
10538 assert_eq!(out[0].over, vec![rucc_debug::Reach { from: 0, len: 8 }]);
10539 assert_eq!(out[1].over, vec![rucc_debug::Reach { from: 0, len: 4 }]);
10540 }
10541
10542 #[test]
10543 fn a_local_written_straight_into_the_body_pulls_no_scope_in() {
10544 let scopes = [scope(None, 20, 30)];
10545 let rows = [row(0, 22, 24)];
10546 let (out, at) = nests(&[None], &scopes, &extent(4), &rows);
10547 assert_eq!(out, Vec::new());
10548 assert!(at.is_empty());
10549 }
10550
10551 #[test]
10552 fn a_scope_whose_code_all_went_away_is_still_one_of_the_functions_scopes() {
10553 let scopes = [scope(None, 20, 30)];
10557 let rows = [row(0, 40, 44)];
10558 let (out, at) = nests(&[Some(0)], &scopes, &extent(4), &rows);
10559 assert_eq!(at.get(&0), Some(&0));
10560 assert_eq!(out.len(), 1);
10561 assert_eq!(out[0].over, Vec::new());
10562 }
10563}