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 goal: Goal::for_size(opts.opt_level.is_size()),
946 switch: opts.switch_shape.as_deref().and_then(rucc_codegen::switch::Force::named),
948 sibling: opts.sibling_calls.unwrap_or_else(|| opts.opt_level.sibling_calls()),
950 };
951
952 if opts.safety.instruments() {
961 rucc_opt::heap::annotate(module, names);
971 rucc_safety::handover::arrange(module);
978 rucc_safety::lower(module, names);
979 if let Err(errors) = rucc_ir::verify(module, names) {
980 return Err(errors
981 .iter()
982 .map(|e| internal(&format!("invalid IR after check lowering, {e}")))
983 .collect());
984 }
985 }
986
987 let copies = target.tuple.arch() == Arch::X86_64;
998 let elsewhere = Elsewhere::of(module, replaceable(target, opts), target.object_format, copies);
999
1000 let mut funcs = Vec::new();
1001 let mut complaints = Vec::new();
1002 for id in module.funcs() {
1003 if module[id].is_declaration() {
1004 continue;
1005 }
1006 match pipeline::compile_recording(
1007 &mut module[id],
1008 names,
1009 &machine,
1010 &elsewhere,
1011 flags,
1012 recording,
1013 ) {
1014 Ok(func) => funcs.push(func),
1015 Err(why) => {
1016 let name = names.resolve(module[id].name).to_owned();
1017 let span = why.inst().map_or(Span::DUMMY, |inst| module[id].span(inst));
1020 let said = format!("cannot generate code for '{name}': {why}");
1021 complaints.push(unsupported_at(&said, span));
1022 }
1023 }
1024 }
1025 if !complaints.is_empty() {
1026 return Err(complaints);
1027 }
1028 let (globals, aliases) = match opts.emit {
1034 EmitKind::Asm | EmitKind::Object | EmitKind::Archive | EmitKind::Executable => (
1035 rucc_asm::globals(module, names, target.object_format).map_err(refused)?,
1036 rucc_asm::aliases(module, names).map_err(refused)?,
1037 ),
1038 _ => (rucc_asm::Globals::default(), Vec::new()),
1039 };
1040 let unwind = opts.unwinds();
1044 match opts.emit {
1045 EmitKind::Asm => {
1046 rucc_asm::print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
1047 .map(Artifact::Text)
1048 .map_err(refused)
1049 }
1050 EmitKind::Object | EmitKind::Archive | EmitKind::Executable => {
1054 if opts.save_temps.wanted() {
1055 let listing = rucc_asm::print(
1056 &funcs,
1057 &globals,
1058 &aliases,
1059 names,
1060 target,
1061 unwind,
1062 output(opts, target),
1063 );
1064 *assembly = Some(listing.map_err(refused)?);
1065 }
1066 let aarch64 = target.tuple.arch() == Arch::Aarch64;
1077 if aarch64 || globals.kept() || rucc_asm::kept(&funcs, names, target) {
1078 if funcs.iter().any(|func| !func.landings.is_empty()) {
1082 return Err(vec![unsupported(
1083 "a cleanup that runs during an unwind, in a unit whose listing is read \
1084 back by the assembler",
1085 )]);
1086 }
1087 let print = if opts.debug_info { rucc_asm::print_marked } else { rucc_asm::print };
1088 let listing =
1089 print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
1090 .map_err(refused)?;
1091 let arch = target.tuple.arch();
1092 let read =
1093 rucc_asm::read_as(&listing, arch, target.object_format).map_err(|trouble| {
1094 let what = if aarch64 {
1095 "a unit for aarch64"
1096 } else if globals.kept() {
1097 "an `asm` at file scope"
1098 } else {
1099 "an `asm` template kept as text"
1100 };
1101 vec![unsupported(&format!(
1102 "{what}, whose listing the assembler stopped at on line {}: {}",
1103 trouble.line, trouble.why
1104 ))]
1105 })?;
1106 let info = if opts.debug_info {
1107 let assembled =
1108 placed(&read, &funcs, names, target).map_err(|why| vec![internal(&why)])?;
1109 describe(&assembled, &globals.image(), &funcs, origin, opts, target)
1110 .map_err(|why| vec![internal(&why)])?
1111 } else {
1112 rucc_object::Info::default()
1113 };
1114 let defines = rucc_object::assembled_defines(&read);
1115 let bytes =
1116 rucc_object::assembled_described(&read, target, &info).map_err(wrote)?;
1117 return Ok(Artifact::Object { bytes, defines });
1118 }
1119 let assembled = rucc_asm::assemble(&funcs, names, target, unwind, opts.debug_info)
1120 .map_err(refused)?;
1121 let data = globals.image();
1122 let info = if opts.debug_info {
1126 describe(&assembled, &data, &funcs, origin, opts, target)
1127 .map_err(|why| vec![internal(&why)])?
1128 } else {
1129 rucc_object::Info::default()
1130 };
1131 let text = assembled.text;
1132 let bytes =
1135 rucc_object::write(&text, &data, &aliases, target, output(opts, target), &info)
1136 .map_err(wrote)?;
1137 let defines = rucc_object::defines(&text, &data, &aliases, target).map_err(wrote)?;
1142 Ok(Artifact::Object { bytes, defines })
1143 }
1144 _ => Ok(Artifact::Text(rucc_mir::print(&funcs, names, target.regs))),
1145 }
1146}
1147
1148fn placed(
1160 read: &rucc_object::Assembled,
1161 funcs: &[rucc_mir::Func],
1162 names: &Interner,
1163 target: &TargetInfo,
1164) -> Result<rucc_asm::Assembled, String> {
1165 let at: HashMap<&str, &rucc_object::Name> =
1166 read.names.iter().map(|name| (name.name.as_str(), name)).collect();
1167 let offset = |name: &str| match at.get(name).map(|name| name.at) {
1168 Some(rucc_object::Held::In { part, offset }) => Some((part, offset)),
1169 _ => None,
1170 };
1171 let mut text = rucc_object::Text::default();
1172 let mut lines = Vec::with_capacity(funcs.len());
1173 let symbol = rucc_asm::Directives::of(target.object_format).symbol();
1177 for (which, func) in funcs.iter().enumerate() {
1178 let name = names.resolve(func.name);
1179 let Some((part, start)) = offset(&format!("{symbol}{name}")) else {
1180 return Err(format!("the listing has no label for the function '{name}'"));
1181 };
1182 let mut rows = Vec::with_capacity(func.inst_count() + 1);
1183 if !func.declared.is_dummy() {
1184 rows.push(rucc_asm::Row { at: 0, span: func.declared, inst: None });
1185 }
1186 for block in func.blocks() {
1187 for inst in func.insts(block) {
1188 let label = rucc_asm::mark(target, which, inst);
1189 let Some((held, here)) = offset(&label) else {
1190 return Err(format!("the listing has no label '{label}'"));
1191 };
1192 if held != part || here < start {
1193 return Err(format!("the label '{label}' is not inside '{name}'"));
1194 }
1195 let at = usize::try_from(here - start).map_err(|why| why.to_string())?;
1196 rows.push(rucc_asm::Row { at, span: func.span(inst), inst: Some(inst) });
1197 }
1198 }
1199 let size = at.get(format!("{symbol}{name}").as_str()).map_or(0, |name| name.size);
1202 let len = match offset(&rucc_asm::mark_end(target, which)) {
1203 Some((held, end)) if size == 0 && held == part && end >= start => end - start,
1204 _ => size,
1205 };
1206 text.funcs.push(rucc_object::Extent {
1207 name: name.to_owned(),
1208 start: usize::try_from(start).map_err(|why| why.to_string())?,
1209 len: usize::try_from(len).map_err(|why| why.to_string())?,
1210 align: func.align.unwrap_or(rucc_object::FUNC_ALIGN),
1211 binding: rucc_object::Binding::Global,
1212 visibility: rucc_object::Visibility::Default,
1213 patch: None,
1214 landings: Vec::new(),
1215 });
1216 lines.push(rows);
1217 }
1218 Ok(rucc_asm::Assembled { text, lines, frames: None })
1219}
1220
1221fn describe(
1240 assembled: &rucc_asm::Assembled,
1241 data: &rucc_object::Data,
1242 machine: &[rucc_mir::Func],
1243 origin: Origin<'_>,
1244 opts: &Options,
1245 target: &TargetInfo,
1246) -> Result<rucc_object::Info, String> {
1247 let rucc_asm::Assembled { text, lines, frames } = assembled;
1248 let rewrite = |path: &str| opts.prefix_map.debug.apply(path).into_owned();
1249 let mut files: Vec<String> = Vec::new();
1254 let mut funcs = Vec::with_capacity(text.funcs.len());
1255 for ((extent, rows), built) in text.funcs.iter().zip(lines).zip(machine) {
1256 let mut out: Vec<rucc_debug::Row> = Vec::with_capacity(rows.len());
1257 for row in rows {
1258 if row.span.is_dummy() {
1259 continue;
1260 }
1261 let Some(at) = origin.map.presumed(row.span.lo) else {
1262 continue;
1263 };
1264 let which = interned(&mut files, rewrite(at.name));
1265 let place = rucc_debug::Row {
1266 at: row.at as u64,
1267 file: which,
1268 line: at.line,
1269 column: at.column,
1270 };
1271 match out.last() {
1281 Some(last) if last.at == place.at => {}
1282 _ => out.push(place),
1283 }
1284 }
1285 if let Some(first) = out.first_mut() {
1292 first.at = 0;
1293 }
1294 let known = origin.meaning.funcs.get(&extent.name);
1300 let decl = known.map(|known| rucc_debug::Place {
1301 file: interned(&mut files, rewrite(&known.file)),
1302 line: known.line,
1303 });
1304 let mut sig = known.and_then(|known| known.sig.clone());
1313 let mut placed: Vec<(u32, i32)> = built.locals.clone();
1314 let mut spots = stretches(extent, rows, built, target);
1315 for &decl in &built.sharing {
1320 if !spots.iter().any(|(at, _)| *at == decl) {
1321 spots.push((decl, Vec::new()));
1322 }
1323 }
1324 if let (Some(sig), Some(known)) = (sig.as_mut(), known) {
1325 for (param, decl) in sig.params.iter_mut().zip(&known.params) {
1326 let Some(decl) = *decl else { continue };
1327 if let Some(which) = placed.iter().position(|&(at, _)| at == decl) {
1328 let at = rucc_debug::Held::Frame(i64::from(placed.remove(which).1));
1329 param.spot = Some(rucc_debug::Spot::Always(at));
1330 continue;
1331 }
1332 let Some(which) = spots.iter().position(|(at, _)| *at == decl) else { continue };
1336 param.spot = Some(rucc_debug::Spot::Over(spots.remove(which).1));
1337 }
1338 }
1339 let mut locals = Vec::with_capacity(placed.len() + spots.len());
1343 let mut wants: Vec<Option<usize>> = Vec::with_capacity(locals.capacity());
1347 for (decl, at) in placed {
1348 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1349 wants.push(named.scope);
1350 locals.push(rucc_debug::Local {
1351 name: named.name.clone(),
1352 ty: named.ty,
1353 decl: Some(rucc_debug::Place {
1354 file: interned(&mut files, rewrite(&named.file)),
1355 line: named.line,
1356 }),
1357 spot: rucc_debug::Spot::Always(rucc_debug::Held::Frame(i64::from(at))),
1358 scope: None,
1359 });
1360 }
1361 spots.sort_by_key(|(decl, _)| *decl);
1365 for (decl, spans) in spots {
1366 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1367 wants.push(named.scope);
1368 locals.push(rucc_debug::Local {
1369 name: named.name.clone(),
1370 ty: named.ty,
1371 decl: Some(rucc_debug::Place {
1372 file: interned(&mut files, rewrite(&named.file)),
1373 line: named.line,
1374 }),
1375 spot: rucc_debug::Spot::Over(spans),
1376 scope: None,
1377 });
1378 }
1379 let (scopes, at) = nests(&wants, &origin.meaning.scopes, extent, rows);
1384 for (local, want) in locals.iter_mut().zip(&wants) {
1385 local.scope = want.and_then(|want| at.get(&want).copied());
1386 }
1387 funcs.push(rucc_debug::Function {
1388 name: extent.name.clone(),
1389 len: extent.len as u64,
1390 rows: out,
1391 decl,
1392 sig,
1393 external: known.is_some_and(|known| known.external),
1394 locals,
1395 scopes,
1396 });
1397 }
1398 let mut globals = Vec::new();
1405 for object in &data.objects {
1406 let Some(held) = origin.meaning.objects.get(&object.name) else { continue };
1407 globals.push(rucc_debug::Global {
1408 name: object.name.clone(),
1409 ty: held.ty,
1410 decl: Some(rucc_debug::Place {
1411 file: interned(&mut files, rewrite(&held.file)),
1412 line: held.line,
1413 }),
1414 external: held.external,
1415 });
1416 }
1417 let unit = rucc_debug::Unit {
1418 name: rewrite(origin.name),
1419 dir: rewrite(opts.working_dir.as_deref().unwrap_or(".")),
1422 producer: format!("rucc {}", crate::VERSION),
1423 files,
1424 types: origin.meaning.types.clone(),
1425 funcs,
1426 globals,
1427 pointer: u8::try_from(target.pointer_width / 8).unwrap_or(8),
1428 frames: opts.unwinds() || frames.is_some(),
1433 mach_o: target.object_format == rucc_target::ObjectFormat::MachO,
1434 };
1435 let mut info = rucc_debug::write(&unit).map_err(|why| why.to_string())?;
1436 info.chunks.extend(frames.clone());
1437 Ok(info)
1438}
1439
1440fn stretches(
1451 extent: &rucc_object::Extent,
1452 rows: &[rucc_asm::Row],
1453 built: &rucc_mir::Func,
1454 target: &TargetInfo,
1455) -> Vec<(u32, Vec<rucc_debug::Span>)> {
1456 let (false, Some(regs)) = (built.kept.is_empty(), target.call_regs) else {
1459 return Vec::new();
1460 };
1461 let ends = ends(extent, rows);
1462 let mut bounds = vec![None; built.inst_count()];
1463 for (which, row) in rows.iter().enumerate() {
1464 let Some(inst) = row.inst else { continue };
1465 bounds[inst.index()] = Some((row.at as u64, ends[which]));
1466 }
1467 let mut spots: Vec<(u32, Vec<rucc_debug::Span>)> = Vec::new();
1468 for kept in &built.kept {
1469 let (Some((from, _)), Some((_, to))) = (bounds[kept.from.index()], bounds[kept.to.index()])
1470 else {
1471 continue;
1472 };
1473 if to <= from {
1474 continue;
1475 }
1476 let held = match kept.at {
1477 rucc_mir::Where::Reg { reg, class } => match regs.dwarf(class, reg) {
1480 Some(number) => rucc_debug::Held::Reg(number),
1481 None => continue,
1482 },
1483 rucc_mir::Where::Frame(at) => rucc_debug::Held::Frame(i64::from(at)),
1484 };
1485 let span = rucc_debug::Span { from, len: to - from, held };
1486 match spots.iter_mut().find(|(decl, _)| *decl == kept.decl) {
1487 Some((_, spans)) => spans.push(span),
1488 None => spots.push((kept.decl, vec![span])),
1489 }
1490 }
1491 for (_, spans) in &mut spots {
1492 *spans = settle(std::mem::take(spans));
1493 }
1494 spots.retain(|(_, spans)| !spans.is_empty());
1495 spots
1496}
1497
1498fn ends(extent: &rucc_object::Extent, rows: &[rucc_asm::Row]) -> Vec<u64> {
1508 let mut out = vec![extent.len as u64; rows.len()];
1509 let mut next = extent.len as u64;
1510 for which in (0..rows.len()).rev() {
1511 let at = rows[which].at as u64;
1512 out[which] = match next > at {
1515 true => next,
1516 false => out.get(which + 1).copied().unwrap_or(extent.len as u64),
1517 };
1518 next = next.min(at);
1519 }
1520 out
1521}
1522
1523fn nests(
1539 wants: &[Option<usize>],
1540 scopes: &[crate::shapes::Scope],
1541 extent: &rucc_object::Extent,
1542 rows: &[rucc_asm::Row],
1543) -> (Vec<rucc_debug::Scope>, HashMap<usize, usize>) {
1544 let mut needed: Vec<usize> = Vec::new();
1545 for &want in wants {
1546 let mut up = want;
1547 while let Some(which) = up {
1548 if needed.contains(&which) {
1549 break;
1550 }
1551 needed.push(which);
1552 up = scopes.get(which).and_then(|scope| scope.parent);
1553 }
1554 }
1555 needed.sort_unstable();
1558 let at: HashMap<usize, usize> =
1559 needed.iter().enumerate().map(|(which, &scope)| (scope, which)).collect();
1560 let ends = ends(extent, rows);
1561 let out = needed
1562 .iter()
1563 .map(|&which| {
1564 let scope = &scopes[which];
1565 rucc_debug::Scope {
1566 parent: scope.parent.and_then(|parent| at.get(&parent).copied()),
1567 over: spread(scope.span, &ends, rows),
1568 }
1569 })
1570 .collect();
1571 (out, at)
1572}
1573
1574fn spread(span: Span, ends: &[u64], rows: &[rucc_asm::Row]) -> Vec<rucc_debug::Reach> {
1582 let mut out: Vec<rucc_debug::Reach> = Vec::new();
1583 for (which, row) in rows.iter().enumerate() {
1584 if row.span.is_dummy() || row.span.lo < span.lo || row.span.hi > span.hi {
1585 continue;
1586 }
1587 let (from, to) = (row.at as u64, ends[which]);
1588 if to <= from {
1589 continue;
1590 }
1591 match out.last_mut() {
1592 Some(last) if last.from + last.len >= from => {
1593 last.len = to.saturating_sub(last.from).max(last.len);
1594 }
1595 _ => out.push(rucc_debug::Reach { from, len: to - from }),
1596 }
1597 }
1598 out
1599}
1600
1601fn settle(mut spans: Vec<rucc_debug::Span>) -> Vec<rucc_debug::Span> {
1618 spans.sort_by_key(|span| (span.from, span.len));
1619 for which in 0..spans.len() {
1620 let (from, end, held) =
1621 (spans[which].from, spans[which].from + spans[which].len, spans[which].held);
1622 let later = spans[which + 1..]
1623 .iter()
1624 .take_while(|later| later.from < end)
1625 .find(|later| later.from > from && later.held != held);
1626 if let Some(later) = later {
1627 spans[which].len = later.from - from;
1628 }
1629 }
1630 let mut edges: Vec<u64> =
1633 spans.iter().flat_map(|span| [span.from, span.from + span.len]).collect();
1634 edges.sort_unstable();
1635 edges.dedup();
1636 let mut out: Vec<rucc_debug::Span> = Vec::new();
1637 let mut first = 0;
1638 for pair in edges.windows(2) {
1639 let (from, to) = (pair[0], pair[1]);
1640 while spans.get(first).is_some_and(|span| span.from + span.len <= from) {
1644 first += 1;
1645 }
1646 let mut held = None;
1647 let mut agreed = true;
1648 for span in &spans[first..] {
1649 if span.from >= to {
1650 break;
1651 }
1652 if span.from > from || span.from + span.len < to {
1653 continue;
1654 }
1655 match held {
1656 None => held = Some(span.held),
1657 Some(seen) => agreed &= seen == span.held,
1658 }
1659 }
1660 let (Some(held), true) = (held, agreed) else { continue };
1661 match out.last_mut() {
1662 Some(last) if last.from + last.len == from && last.held == held => {
1663 last.len += to - from
1664 }
1665 _ => out.push(rucc_debug::Span { from, len: to - from, held }),
1666 }
1667 }
1668 out
1669}
1670
1671fn interned(files: &mut Vec<String>, name: String) -> usize {
1677 match files.iter().position(|have| *have == name) {
1678 Some(which) => which,
1679 None => {
1680 files.push(name);
1681 files.len() - 1
1682 }
1683 }
1684}
1685
1686fn output(opts: &Options, target: &TargetInfo) -> rucc_object::Output {
1698 let mut features = 0;
1699 if target.tuple.arch() == Arch::X86_64 {
1700 if opts.control.branch() {
1701 features |= rucc_object::Property::IBT;
1702 }
1703 if opts.control.ret() {
1704 features |= rucc_object::Property::SHSTK;
1705 }
1706 }
1707 rucc_object::Output {
1708 sections: rucc_object::Sections {
1709 functions: opts.function_sections,
1710 data: opts.data_sections,
1711 },
1712 property: rucc_object::Property { features },
1713 }
1714}
1715
1716fn wrote(why: rucc_object::Error) -> Vec<Diagnostic> {
1722 match why {
1723 rucc_object::Error::Format { .. } => vec![unsupported(&why.to_string())],
1724 rucc_object::Error::Refused { .. } => vec![internal(&why.to_string())],
1725 }
1726}
1727
1728fn refused(why: rucc_asm::Error) -> Vec<Diagnostic> {
1735 match why {
1736 rucc_asm::Error::Thread { .. }
1737 | rucc_asm::Error::IFunc { .. }
1738 | rucc_asm::Error::Frame { .. } => {
1739 vec![unsupported(&why.to_string())]
1740 }
1741 _ => vec![internal(&why.to_string())],
1742 }
1743}
1744
1745fn unsupported(message: &str) -> Diagnostic {
1751 unsupported_at(message, Span::DUMMY)
1752}
1753
1754fn unsupported_at(message: &str, span: Span) -> Diagnostic {
1760 Diagnostic::error(message.to_owned(), span)
1761 .with_code("E0653")
1762 .note("this construct is not lowered yet, see https://github.com/tamnd/rucc/issues", span)
1763}
1764
1765fn invalid(message: &str) -> Diagnostic {
1767 Diagnostic::error(message.to_owned(), Span::DUMMY).with_code("E0661")
1768}
1769
1770fn internal(message: &str) -> Diagnostic {
1772 Diagnostic::error(format!("internal error: {message}"), Span::DUMMY)
1773 .with_code("E0652")
1774 .note("this is a bug in rucc rather than in the program, please report it", Span::DUMMY)
1775}
1776
1777fn su_file(stack: &StackUsage, sources: &SourceMap, file: &str) -> String {
1784 let mut out = String::new();
1785 for row in stack.rows() {
1786 let span = row.span();
1787 let at = (!span.is_dummy()).then(|| sources.presumed(span.lo)).flatten();
1788 let (name, line, column) = at.map_or((file, 0, 0), |at| (at.name, at.line, at.column));
1789 out.push_str(&row.line(name, line, column));
1790 }
1791 out
1792}
1793
1794fn failure(message: String) -> Compiled {
1797 Compiled {
1798 artifact: Artifact::Nothing,
1799 messages: vec![format!("rucc: error: {message}")],
1800 errors: 1,
1801 fired: Fired::new(),
1802 pressure: Pressure::new(),
1803 lowerings: Lowerings::new(),
1804 dumps: Vec::new(),
1805 remarks: String::new(),
1806 deps: Vec::new(),
1807 temps: Temps::default(),
1808 timing: crate::trace::Timing::default(),
1809 stack_usage: String::new(),
1810 }
1811}
1812
1813#[cfg(test)]
1814mod tests {
1815 use rucc_session::{MemoryFileSystem, Std};
1816 use rucc_target::Triple;
1817
1818 use super::*;
1819
1820 fn options() -> Options {
1821 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1822 opts.emit = EmitKind::Tast;
1823 opts.frame_pointer = Some(false);
1826 opts
1827 }
1828
1829 fn run(opts: &Options, source: &str) -> Compiled {
1830 let mut fs = MemoryFileSystem::new();
1831 fs.insert("/main.c", source.to_owned().into_bytes());
1832 compile(opts, "/main.c", &fs)
1833 }
1834
1835 fn freestanding() -> Options {
1839 let mut opts = options();
1840 opts.hosted = false;
1841 opts.search.push_system(rucc_session::runtime::DIR);
1842 opts
1843 }
1844
1845 fn shipped(source: &str) -> String {
1847 let result = run(&freestanding(), source);
1848 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1849 result.text().to_owned()
1850 }
1851
1852 fn tast(source: &str) -> String {
1854 let result = run(&options(), source);
1855 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1856 result.text().to_owned()
1857 }
1858
1859 #[test]
1860 fn the_shipped_stdarg_declares_a_list_and_the_four_operators() {
1861 let text = shipped(concat!(
1862 "#include <stdarg.h>\n",
1863 "int sum(int n, ...) {\n",
1864 " va_list ap, copy;\n",
1865 " va_start(ap, n);\n",
1866 " va_copy(copy, ap);\n",
1867 " int total = va_arg(ap, int) + va_arg(copy, int);\n",
1868 " va_end(ap);\n",
1869 " va_end(copy);\n",
1870 " return total;\n",
1871 "}\n",
1872 ));
1873 assert!(text.contains("va-start"), "{text}");
1874 assert!(text.contains("va-copy"), "{text}");
1875 assert!(text.contains("va-arg"), "{text}");
1876 assert!(text.contains("va-end"), "{text}");
1877 }
1878
1879 #[test]
1883 fn stdarg_hands_out_the_type_alone_when_that_is_all_that_was_asked_for() {
1884 let text = shipped(concat!(
1885 "#define __need___va_list\n",
1886 "#include <stdarg.h>\n",
1887 "int vprint(const char *f, __gnuc_va_list ap);\n",
1888 "#ifdef va_start\n",
1889 "#error va_start should not be defined\n",
1890 "#endif\n",
1891 "#ifdef _VA_LIST_DEFINED\n",
1892 "#error va_list should not have been made\n",
1893 "#endif\n",
1894 ));
1895 assert!(text.contains("vprint"), "{text}");
1896 }
1897
1898 #[test]
1901 fn stddef_answers_one_piece_at_a_time_and_the_next_request_still_gets_through() {
1902 let text = shipped(concat!(
1903 "#define __need_size_t\n",
1904 "#include <stddef.h>\n",
1905 "#ifdef offsetof\n",
1906 "#error offsetof should not be defined yet\n",
1907 "#endif\n",
1908 "#define __need_ptrdiff_t\n",
1909 "#include <stddef.h>\n",
1910 "#include <stddef.h>\n",
1911 "size_t a;\n",
1912 "ptrdiff_t b;\n",
1913 "wchar_t c;\n",
1914 "max_align_t d;\n",
1915 "void *e = NULL;\n",
1916 "struct P { int x; long y; };\n",
1917 "size_t f = offsetof(struct P, y);\n",
1918 ));
1919 assert!(text.contains("decl #0 a : unsigned long"), "{text}");
1920 assert!(text.contains("decl #1 b : long"), "{text}");
1921 }
1922
1923 #[test]
1924 fn the_shipped_limits_and_float_are_the_targets_own_answers() {
1925 let text = shipped(concat!(
1926 "#include <limits.h>\n",
1927 "#include <float.h>\n",
1928 "int bits = CHAR_BIT;\n",
1929 "long big = LONG_MAX;\n",
1930 "int low = INT_MIN;\n",
1931 "int radix = FLT_RADIX;\n",
1932 "int digits = DBL_MANT_DIG;\n",
1933 ));
1934 assert!(text.contains("const 8 : int"), "{text}");
1935 assert!(text.contains("const 9223372036854775807 : long"), "{text}");
1936 assert!(text.contains("const 2 : int"), "{text}");
1937 assert!(text.contains("const 53 : int"), "{text}");
1938 }
1939
1940 #[test]
1944 fn the_shipped_stdint_writes_the_whole_set_when_there_is_no_library_to_defer_to() {
1945 let text = shipped(concat!(
1946 "#include <stdint.h>\n",
1947 "int64_t a = INT64_C(1);\n",
1948 "uint_least16_t b;\n",
1949 "intptr_t c;\n",
1950 "uintmax_t d = UINTMAX_MAX;\n",
1951 "int wide = sizeof(int_fast64_t);\n",
1952 ));
1953 assert!(text.contains("decl #0 a : long"), "{text}");
1954 assert!(text.contains("decl #1 b : unsigned short"), "{text}");
1955 assert!(text.contains("decl #2 c : long"), "{text}");
1956 }
1957
1958 #[test]
1969 fn the_shipped_mmintrin_defines_the_mmx_type_and_the_operations_over_it() {
1970 let text = shipped(concat!(
1971 "#include <mmintrin.h>\n",
1972 "__m64 add(__m64 a, __m64 b) { return _mm_add_pi16(a, b); }\n",
1973 "__m64 pack(__m64 a, __m64 b) { return _m_packsswb(a, b); }\n",
1974 "__m64 shift(__m64 a) { return _mm_srai_pi32(a, 3); }\n",
1975 "int low(__m64 a) { return _mm_cvtsi64_si32(a); }\n",
1976 "void done(void) { _mm_empty(); }\n",
1977 ));
1978 assert!(text.contains("add"), "{text}");
1979 assert!(text.contains("pack"), "{text}");
1980 assert!(text.contains("shift"), "{text}");
1981 }
1982
1983 #[test]
1988 fn the_shipped_mm_malloc_asks_for_aligned_memory_and_gives_it_back() {
1989 let text = shipped(concat!(
1990 "#include <mm_malloc.h>\n",
1991 "void *get(void) { return _mm_malloc(64, 16); }\n",
1992 "void put(void *p) { _mm_free(p); }\n",
1993 ));
1994 assert!(text.contains("get"), "{text}");
1995 assert!(text.contains("put"), "{text}");
1996 }
1997
1998 #[test]
2010 fn the_shipped_xmmintrin_defines_the_sse_type_and_the_operations_over_it() {
2011 let text = shipped(concat!(
2012 "#include <xmmintrin.h>\n",
2013 "__m128 add(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
2014 "__m128 one(__m128 a, __m128 b) { return _mm_max_ss(a, b); }\n",
2015 "__m128 mask(__m128 a, __m128 b) { return _mm_cmpnle_ps(a, b); }\n",
2016 "__m128 pick(__m128 a, __m128 b) { return _mm_shuffle_ps(a, b, _MM_SHUFFLE(0,1,2,3)); }\n",
2017 "int bits(__m128 a) { return _mm_movemask_ps(a); }\n",
2018 "int near(__m128 a) { return _mm_cvtss_si32(a); }\n",
2019 "__m128 wide(__m64 a) { return _mm_cvtpi16_ps(a); }\n",
2020 "void *room(void) { return _mm_malloc(64, 16); }\n",
2021 "void hint(const float *p) { _mm_prefetch(p, _MM_HINT_T0); _mm_sfence(); }\n",
2022 ));
2023 assert!(text.contains("add"), "{text}");
2024 assert!(text.contains("mask"), "{text}");
2025 assert!(text.contains("pick"), "{text}");
2026 assert!(text.contains("wide"), "{text}");
2027 }
2028
2029 #[test]
2036 fn the_shipped_xmmintrin_leaves_out_the_names_that_need_an_instruction() {
2037 let text = rucc_session::runtime::header("xmmintrin.h").expect("xmmintrin.h is shipped");
2038 for absent in [
2039 "_mm_sqrt_ps",
2040 "_mm_sqrt_ss",
2041 "_mm_rsqrt_ps",
2042 "_mm_rsqrt_ss",
2043 "_mm_getcsr",
2044 "_mm_setcsr",
2045 ] {
2046 let defined = text.contains(&format!("{absent}("));
2047 assert!(!defined, "{absent} is defined and the header says it is not");
2048 assert!(text.contains(absent), "{absent} is absent and unexplained");
2049 }
2050 }
2051
2052 #[test]
2053 fn the_shipped_emmintrin_defines_both_sse2_types_and_the_operations_over_them() {
2054 let text = shipped(concat!(
2055 "#include <emmintrin.h>\n",
2056 "__m128i add(__m128i a, __m128i b) { return _mm_add_epi64(a, b); }\n",
2057 "__m128i wide(__m128i a, __m128i b) { return _mm_mul_epu32(a, b); }\n",
2058 "__m128i pick(__m128i a) { return _mm_shuffle_epi32(a, _MM_SHUFFLE(0,1,2,3)); }\n",
2059 "__m128i up(__m128i a) { return _mm_slli_epi64(a, 13); }\n",
2060 "__m128i down(__m128i a) { return _mm_srli_si128(a, 3); }\n",
2061 "__m128i pack(__m128i a, __m128i b) { return _mm_packus_epi16(a, b); }\n",
2062 "int bits(__m128i a) { return _mm_movemask_epi8(a); }\n",
2063 "__m128d sum(__m128d a, __m128d b) { return _mm_add_sd(a, b); }\n",
2064 "__m128d mask(__m128d a, __m128d b) { return _mm_cmpunord_pd(a, b); }\n",
2065 "__m128i near(__m128d a) { return _mm_cvtpd_epi32(a); }\n",
2066 "__m128d over(__m128 a) { return _mm_cvtps_pd(a); }\n",
2067 "__m128i half(__m64 a) { return _mm_movpi64_epi64(a); }\n",
2068 "__m128i grab(void const *p) { return _mm_loadu_si128(p); }\n",
2069 "void wall(void) { _mm_lfence(); _mm_mfence(); }\n",
2070 ));
2071 assert!(text.contains("wide"), "{text}");
2072 assert!(text.contains("pack"), "{text}");
2073 assert!(text.contains("near"), "{text}");
2074 assert!(text.contains("half"), "{text}");
2075 }
2076
2077 #[test]
2081 fn the_shipped_immintrin_reaches_the_names_the_headers_under_it_define() {
2082 let text = shipped(concat!(
2083 "#include <immintrin.h>\n",
2084 "unsigned long long matching(unsigned char tag, unsigned char const *bucket) {\n",
2085 " __m128i const want = _mm_set1_epi8((char)tag);\n",
2086 " __m128i const chunk = _mm_loadu_si128((__m128i const *)(void const *)bucket);\n",
2087 " __m128i const same = _mm_cmpeq_epi8(chunk, want);\n",
2088 " return (unsigned long long)_mm_movemask_epi8(same);\n",
2089 "}\n",
2090 "__m64 narrow(__m64 a, __m64 b) { return _mm_add_pi32(a, b); }\n",
2091 "__m128 single(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
2092 ));
2093 assert!(text.contains("matching"), "{text}");
2094 assert!(text.contains("narrow"), "the MMX header is not reached: {text}");
2095 assert!(text.contains("single"), "the SSE header is not reached: {text}");
2096 }
2097
2098 #[test]
2102 fn the_shipped_x86intrin_reaches_the_fences_windows_headers_ask_it_for() {
2103 let text = shipped(concat!(
2104 "#include <x86intrin.h>\n",
2105 "void barriers(void *p) {\n",
2106 " _mm_lfence();\n",
2107 " _mm_sfence();\n",
2108 " _mm_mfence();\n",
2109 " _mm_pause();\n",
2110 " _mm_clflush(p);\n",
2111 "}\n",
2112 "__m128i wide(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
2113 ));
2114 assert!(text.contains("barriers"), "{text}");
2115 assert!(text.contains("wide"), "the SSE2 header is not reached: {text}");
2116 }
2117
2118 #[test]
2122 fn the_umbrella_and_the_header_under_it_can_both_be_included() {
2123 let text = shipped(concat!(
2124 "#include <immintrin.h>\n",
2125 "#include <emmintrin.h>\n",
2126 "#include <immintrin.h>\n",
2127 "#include <x86intrin.h>\n",
2128 "__m128i twice(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
2129 ));
2130 assert!(text.contains("twice"), "{text}");
2131 }
2132
2133 #[test]
2136 fn the_shipped_arm_neon_has_what_xxhash_asks_it_for() {
2137 let mut opts = freestanding();
2138 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2139 let source = concat!(
2140 "#include <arm_neon.h>\n",
2141 "uint64x2_t acc(uint64x2_t sum, const void *in, const void *key) {\n",
2142 " uint8x16_t data = vld1q_u8((const uint8_t *)in);\n",
2143 " uint8x16_t k = vld1q_u8((const uint8_t *)key);\n",
2144 " uint64x2_t mixed = vreinterpretq_u64_u8(veorq_u8(data, k));\n",
2145 " uint32x2_t lo = vmovn_u64(mixed);\n",
2146 " uint32x2_t hi = vshrn_n_u64(mixed, 32);\n",
2147 " return vmlal_u32(sum, lo, hi);\n",
2148 "}\n",
2149 "uint32x4x2_t pair(uint32x4_t a, uint32x4_t b) { return vzipq_u32(a, b); }\n",
2150 "uint32_t total(uint32x4_t a) { return vaddvq_u32(a); }\n",
2151 );
2152 let result = run(&opts, source);
2153 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2154 assert!(result.text().contains("pair"), "{}", result.text());
2155 assert!(result.text().contains("total"), "{}", result.text());
2156 }
2157
2158 #[test]
2160 fn the_shipped_arm_neon_refuses_another_target() {
2161 let result = run(&freestanding(), "#include <arm_neon.h>\n");
2162 let said = result.messages.join("\n");
2163 assert!(said.contains("arm_neon.h is for AArch64"), "{said}");
2164 }
2165
2166 #[test]
2170 fn the_shipped_emmintrin_leaves_out_the_two_square_roots() {
2171 let text = rucc_session::runtime::header("emmintrin.h").expect("emmintrin.h is shipped");
2172 for absent in ["_mm_sqrt_pd", "_mm_sqrt_sd"] {
2173 let defined = text.contains(&format!("{absent}("));
2174 assert!(!defined, "{absent} is defined and the header says it is not");
2175 assert!(text.contains(absent), "{absent} is absent and unexplained");
2176 }
2177 }
2178
2179 #[test]
2184 fn the_shipped_nmmintrin_is_one_instruction_per_step_under_sse4_2() {
2185 let mut opts = freestanding();
2186 opts.emit = EmitKind::Asm;
2187 let mut choices = rucc_target::Choices::new();
2188 choices.read("sse4.2").expect("gcc knows sse4.2");
2189 opts.isa = choices.over(opts.isa);
2190 let source = concat!(
2191 "#include <nmmintrin.h>\n",
2192 "unsigned b(unsigned c, unsigned char v) { return _mm_crc32_u8(c, v); }\n",
2193 "unsigned w(unsigned c, unsigned short v) { return _mm_crc32_u16(c, v); }\n",
2194 "unsigned l(unsigned c, unsigned v) { return _mm_crc32_u32(c, v); }\n",
2195 "unsigned long long q(unsigned long long c, unsigned long long v) {\n",
2196 " return _mm_crc32_u64(c, v);\n",
2197 "}\n",
2198 "int n(unsigned v) { return _mm_popcnt_u32(v); }\n",
2199 "long long m(unsigned long long v) { return _mm_popcnt_u64(v); }\n",
2200 );
2201 let result = run(&opts, source);
2202 assert_eq!(result.messages, Vec::<String>::new());
2203 let text = result.text();
2204 for step in ["crc32b", "crc32w", "crc32l", "crc32q", "popcntl", "popcntq"] {
2205 assert!(text.contains(step), "no {step} in:\n{text}");
2206 }
2207 }
2208
2209 #[test]
2212 fn the_shipped_smmintrin_refuses_a_caller_not_built_for_the_checksum() {
2213 let result = run(
2214 &freestanding(),
2215 "#include <immintrin.h>\nunsigned f(unsigned c) { return _mm_crc32_u32(c, 1); }\n",
2216 );
2217 let said = result.messages.join("\n");
2218 let refusal = "inlining failed in call to 'always_inline' '_mm_crc32_u32': target \
2219 specific option mismatch";
2220 assert!(said.contains(refusal), "{said}");
2221 }
2222
2223 #[test]
2228 fn a_function_built_for_sse4_2_calls_the_steps_without_a_flag() {
2229 let mut opts = freestanding();
2230 opts.emit = EmitKind::Asm;
2231 let source = concat!(
2232 "#include <nmmintrin.h>\n",
2233 "#if defined(__has_attribute) && __has_attribute (target)\n",
2234 "__attribute__((target(\"sse4.2\")))\n",
2235 "#endif\n",
2236 "unsigned l(unsigned c, unsigned v) { return _mm_crc32_u32(c, v); }\n",
2237 "__attribute__((target(\"popcnt\")))\n",
2238 "int n(unsigned v) { return _mm_popcnt_u32(v); }\n",
2239 );
2240 let result = run(&opts, source);
2241 assert_eq!(result.messages, Vec::<String>::new());
2242 let text = result.text();
2243 assert!(text.contains("crc32l") && text.contains("popcntl"), "{text}");
2244 let l = &text[text.find("\nl:").expect("l is defined")..];
2245 let l = &l[..l.find("ret").expect("l returns")];
2246 assert!(l.contains("crc32l") && !l.contains("call"), "{l}");
2247 }
2248
2249 #[test]
2256 fn the_shipped_avx512_headers_pass_postgres_probes() {
2257 let popcount = concat!(
2258 "#include <immintrin.h>\n",
2259 "#include <stdint.h>\n",
2260 "char buf[sizeof(__m512i)];\n",
2261 "#if defined(__has_attribute) && __has_attribute (target)\n",
2262 "__attribute__((target(\"avx512vpopcntdq,avx512bw\")))\n",
2263 "#endif\n",
2264 "int popcount_test(void)\n",
2265 "{\n",
2266 " int64_t popcnt = 0;\n",
2267 " __m512i accum = _mm512_setzero_si512();\n",
2268 " __m512i val = _mm512_maskz_loadu_epi8((__mmask64) 0xf0f0f0f0f0f0f0f0, (const __m512i *) buf);\n",
2269 " __m512i cnt = _mm512_popcnt_epi64(val);\n",
2270 " accum = _mm512_add_epi64(accum, cnt);\n",
2271 " popcnt = _mm512_reduce_add_epi64(accum);\n",
2272 " return (int) popcnt;\n",
2273 "}\n",
2274 );
2275 let pclmul = concat!(
2276 "#include <immintrin.h>\n",
2277 "__m512i x;\n",
2278 "__m512i y;\n",
2279 "#if defined(__has_attribute) && __has_attribute (target)\n",
2280 "__attribute__((target(\"vpclmulqdq,avx512vl\")))\n",
2281 "#endif\n",
2282 "int avx512_pclmul_test(void)\n",
2283 "{\n",
2284 " __m128i z;\n",
2285 " x = _mm512_xor_si512(_mm512_zextsi128_si512(_mm_cvtsi32_si128(0)), x);\n",
2286 " y = _mm512_clmulepi64_epi128(x, y, 0);\n",
2287 " z = _mm_ternarylogic_epi64(\n",
2288 " _mm512_castsi512_si128(y),\n",
2289 " _mm512_extracti32x4_epi32(y, 1),\n",
2290 " _mm512_extracti32x4_epi32(y, 2),\n",
2291 " 0x96);\n",
2292 " return _mm_crc32_u64(0, _mm_extract_epi64(z, 0));\n",
2293 "}\n",
2294 );
2295 let checks: [(&str, &str, &[&str]); 2] = [
2296 (popcount, "popcount_test", &["kmovq", "vmovdqu8", "vpopcntq", "vpaddq", "vshufi64x2"]),
2297 (pclmul, "avx512_pclmul_test", &["vpxorq", "vpclmulqdq", "vpternlogq", "crc32q"]),
2298 ];
2299 for (source, name, wanted) in checks {
2300 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
2301 let mut opts = freestanding();
2302 opts.emit = EmitKind::Asm;
2303 opts.opt_level = level;
2304 let result = run(&opts, source);
2305 assert_eq!(result.messages, Vec::<String>::new(), "{name} at {level:?}");
2306 let text = result.text();
2307 let start = text.find(&format!("\n{name}:")).expect("the probe is written out");
2308 let body = &text[start..];
2309 let body = &body[..body.find(".size").unwrap_or(body.len())];
2310 for instruction in wanted {
2311 assert!(
2312 body.contains(instruction),
2313 "no {instruction} at {level:?} in:\n{body}"
2314 );
2315 }
2316 assert!(!body.contains("call"), "a call left behind at {level:?} in:\n{body}");
2317 }
2318 }
2319 }
2320
2321 #[test]
2324 fn the_shipped_avx512_headers_refuse_a_caller_not_built_for_them() {
2325 let result = run(
2326 &freestanding(),
2327 "#include <immintrin.h>\n__m512i f(__m512i a) { return _mm512_popcnt_epi64(a); }\n",
2328 );
2329 let said = result.messages.join("\n");
2330 let refusal = "inlining failed in call to 'always_inline' '_mm512_popcnt_epi64': target \
2331 specific option mismatch";
2332 assert!(said.contains(refusal), "{said}");
2333 }
2334
2335 #[test]
2340 fn the_sse3_to_sse4_2_intrinsics_are_the_instructions_under_the_attribute() {
2341 let mut opts = freestanding();
2342 opts.emit = EmitKind::Asm;
2343 let source = concat!(
2344 "#include <immintrin.h>\n",
2345 "__attribute__((target(\"sse3\")))\n",
2346 "__m128i a(const __m128i *p) { return _mm_lddqu_si128(p); }\n",
2347 "__attribute__((target(\"sse3\")))\n",
2348 "__m128 b(__m128 x, __m128 y) { return _mm_hadd_ps(x, y); }\n",
2349 "__attribute__((target(\"ssse3\")))\n",
2350 "__m128i c(__m128i x, __m128i y) { return _mm_shuffle_epi8(_mm_abs_epi32(x), y); }\n",
2351 "__attribute__((target(\"ssse3\")))\n",
2352 "__m128i d(__m128i x, __m128i y) { return _mm_alignr_epi8(x, y, 5); }\n",
2353 "__attribute__((target(\"sse4.1\")))\n",
2354 "int e(__m128i x, __m128i y) {\n",
2355 " return _mm_extract_epi32(_mm_min_epi32(_mm_mullo_epi32(x, y), y), 2);\n",
2356 "}\n",
2357 "__attribute__((target(\"sse4.1\")))\n",
2358 "__m128 f(__m128 x) { return _mm_round_ps(x, _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); }\n",
2359 "__attribute__((target(\"sse4.1\")))\n",
2360 "__m128i g(__m128i x, __m128i y, __m128i m) { return _mm_blendv_epi8(x, y, m); }\n",
2361 "__attribute__((target(\"sse4.1\")))\n",
2362 "int h(__m128i x) { return _mm_testz_si128(x, x); }\n",
2363 "__attribute__((target(\"sse4.2\")))\n",
2364 "__m128i i(__m128i x, __m128i y) { return _mm_cmpgt_epi64(x, y); }\n",
2365 "__attribute__((target(\"sse4.2\")))\n",
2366 "int j(__m128i x, __m128i y) { return _mm_cmpistri(x, y, _SIDD_CMP_EQUAL_EACH); }\n",
2367 );
2368 let result = run(&opts, source);
2369 assert_eq!(result.messages, Vec::<String>::new());
2370 let text = result.text();
2371 for insn in [
2372 "lddqu",
2373 "haddps",
2374 "pabsd",
2375 "pshufb",
2376 "palignr $5,",
2377 "pmulld",
2378 "pminsd",
2379 "pextrd $2,",
2380 "roundps $8,",
2381 "pblendvb",
2382 "ptest",
2383 "pcmpgtq",
2384 "pcmpistri $8,",
2385 ] {
2386 assert!(text.contains(insn), "no {insn} in:\n{text}");
2387 }
2388 assert!(!text.contains("call"), "{text}");
2389 }
2390
2391 #[test]
2394 fn the_sse3_to_sse4_1_intrinsics_are_refused_a_caller_not_built_for_them() {
2395 let source = concat!(
2396 "#include <immintrin.h>\n",
2397 "__m128i f(__m128i x, __m128i y) { return _mm_shuffle_epi8(x, y); }\n",
2398 );
2399 let said = run(&freestanding(), source).messages.join("\n");
2400 let refusal = "inlining failed in call to 'always_inline' '_mm_shuffle_epi8': target \
2401 specific option mismatch";
2402 assert!(said.contains(refusal), "{said}");
2403
2404 let mut opts = freestanding();
2405 let mut choices = rucc_target::Choices::new();
2406 choices.read("ssse3").expect("gcc knows ssse3");
2407 opts.isa = choices.over(opts.isa);
2408 let result =
2409 run(&opts, &format!("{source}__m128 g(__m128 x) {{ return _mm_movehdup_ps(x); }}\n"));
2410 assert_eq!(result.messages, Vec::<String>::new());
2411 let result = run(
2412 &opts,
2413 "#include <immintrin.h>\n__m128i h(__m128i x) { return _mm_abs_epi8(_mm_cvtepi8_epi32(x)); }\n",
2414 );
2415 let said = result.messages.join("\n");
2416 assert!(said.contains("'_mm_cvtepi8_epi32': target specific option mismatch"), "{said}");
2417 }
2418
2419 #[test]
2422 fn a_target_string_gcc_does_not_know_is_refused() {
2423 for (string, name) in [("sse5", "sse5"), ("+crc", "+crc"), ("sse4.2,foo", "foo")] {
2424 let source =
2425 format!("__attribute__((target(\"{string}\"))) int f(void) {{ return 0; }}\n");
2426 let said = run(&freestanding(), &source).messages.join("\n");
2427 let wanted = format!("attribute 'target' argument '{name}' is unknown");
2428 assert!(said.contains(&wanted), "{string}: {said}");
2429 }
2430 }
2431
2432 #[test]
2435 fn an_aarch64_target_string_is_still_accepted() {
2436 let mut opts = freestanding();
2437 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2438 let source = "__attribute__((target(\"+crc\"))) int f(void) { return 0; }\n";
2439 let result = run(&opts, source);
2440 assert_eq!(result.messages, Vec::<String>::new());
2441 }
2442
2443 #[test]
2444 fn the_three_formality_headers_still_have_to_work() {
2445 let text = shipped(concat!(
2446 "#include <stdbool.h>\n",
2447 "#include <stdalign.h>\n",
2448 "#include <iso646.h>\n",
2449 "#include <stdnoreturn.h>\n",
2450 "int t = true and not false;\n",
2451 "_Alignas(16) char buf[16];\n",
2452 "int a = alignof(long);\n",
2453 ));
2454 assert!(text.contains("decl #0 t : int"), "{text}");
2455 assert!(text.contains("const 8 : unsigned long"), "{text}");
2456 }
2457
2458 #[test]
2466 fn every_shipped_header_can_be_included_twice() {
2467 let once: String = rucc_session::runtime::names()
2470 .iter()
2471 .filter(|name| **name != "arm_neon.h")
2472 .map(|name| format!("#include <{name}>\n"))
2473 .collect();
2474 let twice = once.repeat(2);
2475 assert_eq!(shipped(&format!("{once}int x;\n")), shipped(&format!("{twice}int x;\n")));
2476
2477 let mut opts = freestanding();
2478 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2479 let tree = |source: &str| {
2480 let result = run(&opts, source);
2481 assert_eq!(
2482 result.messages,
2483 Vec::<String>::new(),
2484 "expected this to compile:\n{source}"
2485 );
2486 result.text().to_owned()
2487 };
2488 let neon = "#include <arm_neon.h>\n";
2489 assert_eq!(tree(&format!("{neon}int x;\n")), tree(&format!("{neon}{neon}int x;\n")));
2490 }
2491
2492 #[test]
2493 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
2494 let fs = MemoryFileSystem::new();
2495 let result = compile(&options(), "/nope.c", &fs);
2496 assert!(result.failed());
2497 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
2498 assert!(result.text().is_empty());
2499 }
2500
2501 #[test]
2502 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
2503 let text = tast("int x = 1;\n");
2504 let expected = "\
2505decl #0 x : int object external static defined
2506 init
2507 +0
2508 const 1 : int
2509";
2510 assert_eq!(text, expected);
2511 }
2512
2513 #[test]
2514 fn the_macros_are_expanded_before_anything_is_parsed() {
2515 let text = tast("#define N 2\nint a[N];\n");
2519 assert!(text.starts_with("decl #0 a : int[2] object external static tentative"), "{text}");
2520 }
2521
2522 #[test]
2528 fn a_pragma_is_not_a_declaration_and_the_parse_walks_past_the_ones_it_does_not_read() {
2529 let text = tast(concat!(
2530 "#pragma pack(4)\n",
2531 "struct s { int a; };\n",
2532 "#pragma pack()\n",
2533 "int b;\n",
2534 "_Pragma(\"GCC visibility push(default)\") int c;\n",
2535 ));
2536 assert!(text.contains("decl #0 b : int"), "{text}");
2537 assert!(text.contains("decl #1 c : int"), "{text}");
2538 }
2539
2540 #[test]
2544 fn the_byte_swaps_and_the_bit_counts_of_a_constant_are_constants() {
2545 tast(concat!(
2546 "static const unsigned magic = __builtin_bswap32(0x11223344u);\n",
2547 "_Static_assert(__builtin_bswap16(0x1234) == 0x3412, \"16\");\n",
2548 "_Static_assert(__builtin_bswap32(0x11223344u) == 0x44332211u, \"32\");\n",
2549 "_Static_assert(__builtin_bswap64(0x0102030405060708ull) == 0x0807060504030201ull, \"64\");\n",
2550 "_Static_assert(__builtin_popcountll(-1ll) == 64 && __builtin_popcount(-1) == 32, \"ones\");\n",
2551 "_Static_assert(__builtin_parity(7) == 1 && __builtin_parity(3) == 0, \"parity\");\n",
2552 "_Static_assert(__builtin_ffs(0) == 0 && __builtin_ffs(8) == 4, \"ffs\");\n",
2553 "_Static_assert(__builtin_clrsb(0) == 31 && __builtin_clrsb(-1) == 31, \"clrsb\");\n",
2554 "_Static_assert(__builtin_clrsbl(1) == 62, \"clrsbl\");\n",
2555 "_Static_assert(__builtin_clz(1) == 31 && __builtin_clzl(1) == 63, \"clz\");\n",
2556 "_Static_assert(__builtin_ctzll(1ull << 40) == 40, \"ctz\");\n",
2557 "_Static_assert(__builtin_clz(0) == 32 && __builtin_ctzll(0) == 64, \"zero\");\n",
2558 ));
2559 }
2560
2561 #[test]
2569 fn the_layout_attributes_move_the_members_and_the_record_the_way_gcc_lays_them_out() {
2570 tast(concat!(
2571 "struct A { char c; int i; } __attribute__((packed));\n",
2572 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2573 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2574 "struct B { char c; int i; } __attribute__((aligned));\n",
2577 "_Static_assert(sizeof(struct B) == 16 && _Alignof(struct B) == 16, \"B\");\n",
2578 "struct C { char c; int i __attribute__((packed)); };\n",
2579 "_Static_assert(sizeof(struct C) == 5 && _Alignof(struct C) == 1, \"C\");\n",
2580 "_Static_assert(__builtin_offsetof(struct C, i) == 1, \"C.i\");\n",
2581 "struct D { char c; int i; } __attribute__((packed, aligned(4)));\n",
2582 "_Static_assert(sizeof(struct D) == 8 && _Alignof(struct D) == 4, \"D\");\n",
2583 "_Static_assert(__builtin_offsetof(struct D, i) == 1, \"D.i\");\n",
2584 "struct E { char c; _Alignas(8) int i; };\n",
2585 "_Static_assert(sizeof(struct E) == 16 && _Alignof(struct E) == 8, \"E\");\n",
2586 "_Static_assert(__builtin_offsetof(struct E, i) == 8, \"E.i\");\n",
2587 "struct F { char c; int i __attribute__((aligned(8))); };\n",
2588 "_Static_assert(sizeof(struct F) == 16 && _Alignof(struct F) == 8, \"F\");\n",
2589 "struct G { char c; short s; } __attribute__((aligned(2)));\n",
2592 "_Static_assert(sizeof(struct G) == 4 && _Alignof(struct G) == 2, \"G\");\n",
2593 "struct H { char c; int i; } __attribute__((aligned(2)));\n",
2594 "_Static_assert(sizeof(struct H) == 8 && _Alignof(struct H) == 4, \"H\");\n",
2595 "struct I { [[gnu::packed]] char c; int i; };\n",
2598 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2599 "struct J { char c; [[gnu::packed]] int i; };\n",
2600 "_Static_assert(sizeof(struct J) == 5 && _Alignof(struct J) == 1, \"J\");\n",
2601 "struct M { char c; int i : 5; int j : 20; } __attribute__((packed));\n",
2602 "_Static_assert(sizeof(struct M) == 5 && _Alignof(struct M) == 1, \"M\");\n",
2603 "struct N { char c; long long l; } __attribute__((aligned(32)));\n",
2604 "_Static_assert(sizeof(struct N) == 32 && _Alignof(struct N) == 32, \"N\");\n",
2605 "union L { char c; int i; } __attribute__((packed));\n",
2606 "_Static_assert(sizeof(union L) == 4 && _Alignof(union L) == 1, \"L\");\n",
2607 "struct O { char c; int i; } __attribute__((__packed__));\n",
2611 "_Static_assert(sizeof(struct O) == 5 && _Alignof(struct O) == 1, \"O\");\n",
2612 "struct P { char c; int i; } __attribute__((__aligned__(8)));\n",
2613 "_Static_assert(sizeof(struct P) == 8 && _Alignof(struct P) == 8, \"P\");\n",
2614 ));
2615 }
2616
2617 #[test]
2630 fn a_transparent_union_takes_the_member_a_value_fits_and_is_declared_either_way() {
2631 let text = tast(concat!(
2632 "struct one { int x; };\n",
2633 "struct two { long y; };\n",
2634 "typedef union { struct one *a; struct two *b; void *any; }\n",
2635 " __attribute__((__transparent_union__)) arg;\n",
2636 "int takes(arg v);\n",
2637 "int f(struct one *p, struct two *q, char *c) {\n",
2638 " return takes(p) + takes(q) + takes(c) + takes(0);\n",
2639 "}\n",
2640 "int takes(struct one *p);\n",
2642 "int (*as_a_member)(struct one *) = takes;\n",
2643 "int (*as_the_union)(arg) = takes;\n",
2644 ));
2645 assert!(text.contains("compound-literal"), "{text}");
2646 }
2647
2648 #[test]
2654 fn the_attribute_on_the_declarator_of_a_typedef_is_the_one_glibc_writes() {
2655 let text = tast(concat!(
2656 "struct sockaddr { int family; };\n",
2657 "struct sockaddr_in { int family; int addr; };\n",
2658 "typedef union { struct sockaddr *plain; struct sockaddr_in *inet; }\n",
2659 " addr_arg __attribute__((__transparent_union__));\n",
2660 "int bind_to(int fd, addr_arg where);\n",
2661 "int f(struct sockaddr_in *where) { return bind_to(0, where); }\n",
2662 ));
2663 assert!(text.contains("compound-literal"), "{text}");
2664 }
2665
2666 #[test]
2674 fn a_transparent_union_that_cannot_keep_the_promise_is_dropped_with_a_word_about_it() {
2675 let result = run(
2676 &options(),
2677 concat!(
2678 "union wider { int small; double large; } __attribute__((transparent_union));\n",
2679 "struct plain { int x; } __attribute__((transparent_union));\n",
2680 ),
2681 );
2682 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
2683 assert!(!result.failed(), "{:?}", result.messages);
2684 for message in &result.messages {
2685 assert!(message.contains("'transparent_union' attribute ignored"), "{message}");
2686 }
2687 assert!(result.messages[0].contains("first member"), "{:?}", result.messages);
2688 assert!(result.messages[1].contains("only a union"), "{:?}", result.messages);
2689 }
2690
2691 #[test]
2700 fn an_access_to_a_packed_member_says_the_alignment_the_layout_left_it() {
2701 let packed = body(concat!(
2702 "struct P { char c; int v; } __attribute__((packed));\n",
2703 "int f(struct P *p) { return p->v; }\n",
2704 ));
2705 assert!(packed.contains("load.i32 %2, align 1,"), "{packed}");
2706 let plain = body(concat!(
2708 "struct P { char c; int v; };\n",
2709 "int f(struct P *p) { return p->v; }\n",
2710 ));
2711 assert!(plain.contains("load.i32 %2, align 4,"), "{plain}");
2712 }
2713
2714 #[test]
2721 fn what_is_inside_a_packed_member_is_no_more_aligned_than_the_member_is() {
2722 let stepped = body(concat!(
2723 "struct P { char c; int v[4]; } __attribute__((packed));\n",
2724 "int f(struct P *p, int i) { return p->v[i]; }\n",
2725 ));
2726 assert!(stepped.contains(", align 1,"), "{stepped}");
2727 assert!(!stepped.contains(", align 4,"), "{stepped}");
2728 let nested = body(concat!(
2729 "struct Inner { int v; };\n",
2730 "struct P { char c; struct Inner in; } __attribute__((packed));\n",
2731 "int f(struct P *p) { return p->in.v; }\n",
2732 ));
2733 assert!(nested.contains(", align 1,"), "{nested}");
2734 assert!(!nested.contains(", align 4,"), "{nested}");
2735 }
2736
2737 #[test]
2753 fn an_access_through_a_typedef_that_lowered_its_alignment_says_the_one_the_typedef_asked_for() {
2754 let through = body(concat!(
2755 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2756 "unsigned int f(const void *p) { return *(const unalign32 *)p; }\n",
2757 ));
2758 assert!(through.contains("load.i32 %0, align 1,"), "{through}");
2759 let stepped = body(concat!(
2762 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2763 "unsigned int f(unalign32 *p, int i) { return p[i]; }\n",
2764 ));
2765 assert!(stepped.contains(", align 1,"), "{stepped}");
2766 assert!(!stepped.contains(", align 4,"), "{stepped}");
2767 let plain = body(concat!(
2770 "typedef unsigned int word;\n",
2771 "unsigned int f(const void *p) { return *(const word *)p; }\n",
2772 ));
2773 assert!(plain.contains("load.i32 %0, align 4,"), "{plain}");
2774 }
2775
2776 #[test]
2787 fn a_vector_read_through_a_typedef_that_lowered_its_alignment_comes_back_a_piece_at_a_time() {
2788 let prefix = concat!(
2789 "typedef long long v2di __attribute__((__vector_size__(16)));\n",
2790 "typedef long long v2di_u __attribute__((__vector_size__(16), __aligned__(1)));\n",
2791 );
2792 let loaded =
2793 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di_u *)p; }}"));
2794 assert_eq!(loaded.matches("align 1\n").count(), 2, "{loaded}");
2795 assert!(!loaded.contains("align 16"), "{loaded}");
2796 let stored = body(&format!("{prefix}void f(void *p, v2di b) {{ *(v2di_u *)p = b; }}"));
2799 assert!(stored.contains("memcpy %0, %3, size 16, align 1"), "{stored}");
2800 let aligned =
2802 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di *)p; }}"));
2803 assert!(aligned.contains("align 16"), "{aligned}");
2804 }
2805
2806 #[test]
2815 fn the_aligned_attribute_on_a_declaration_raises_what_that_one_object_is_aligned_to() {
2816 tast(concat!(
2817 "int v __attribute__((aligned(64)));\n",
2818 "_Static_assert(__alignof__(v) == 64, \"v\");\n",
2819 "__attribute__((aligned(32))) int w;\n",
2822 "_Static_assert(__alignof__(w) == 32, \"w\");\n",
2823 "[[gnu::aligned(16)]] int x;\n",
2824 "_Static_assert(__alignof__(x) == 16, \"x\");\n",
2825 "int y __attribute__((aligned(2)));\n",
2828 "_Static_assert(__alignof__(y) == 4, \"y\");\n",
2829 "void f(void) { int a __attribute__((aligned(128)));\n",
2831 "_Static_assert(__alignof__(a) == 128, \"a\"); (void)a; }\n",
2832 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2835 "void g(void) __attribute__((aligned(256)));\n",
2838 "void g(void) {}\n",
2839 "_Static_assert(__alignof__(g) == 256, \"g\");\n",
2840 ));
2841 }
2842
2843 #[test]
2847 fn what_a_declaration_asked_to_be_aligned_to_is_what_the_assembler_is_told() {
2848 let text = asm(concat!(
2849 "int v __attribute__((aligned(64)));\n",
2850 "void g(void) __attribute__((aligned(256)));\n",
2851 "void g(void) {}\n",
2852 "void plain(void) {}\n",
2853 ));
2854 assert!(text.contains("\t.p2align\t6\n\t.type\tv, @object\n"), "{text}");
2855 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2856 assert!(text.contains("\t.p2align\t4, 0x90\n\t.globl\tplain\n"), "{text}");
2857 }
2858
2859 #[test]
2865 fn the_alignment_the_command_line_asked_of_every_function_is_a_floor_under_all_of_them() {
2866 let source = concat!(
2867 "void g(void) __attribute__((aligned(256)));\n",
2868 "void g(void) {}\n",
2869 "void small(void) __attribute__((aligned(4)));\n",
2870 "void small(void) {}\n",
2871 "void plain(void) {}\n",
2872 );
2873 let listing = |align: Option<u32>| {
2874 let mut opts = options();
2875 opts.emit = EmitKind::Asm;
2876 opts.align_functions = align;
2877 let result = run(&opts, source);
2878 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
2879 result.text().to_owned()
2880 };
2881
2882 let text = listing(Some(32));
2883 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "the larger one wins: {text}");
2884 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tsmall\n"), "{text}");
2885 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tplain\n"), "{text}");
2886
2887 let text = listing(Some(8));
2890 assert!(text.contains("\t.p2align\t3, 0x90\n\t.globl\tplain\n"), "{text}");
2891 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2892 }
2893
2894 #[test]
2903 fn an_aligned_typedef_says_what_an_object_of_it_is_aligned_to_and_may_lower_it() {
2904 tast(concat!(
2905 "typedef int L __attribute__((aligned(2)));\n",
2906 "_Static_assert(__alignof__(L) == 2, \"L\");\n",
2907 "_Static_assert(_Alignof(L) == 2, \"L alignof\");\n",
2908 "_Static_assert(sizeof(L) == 4, \"L size\");\n",
2910 "struct T { char c; L x; };\n",
2911 "_Static_assert(sizeof(struct T) == 6, \"T\");\n",
2912 "_Static_assert(__builtin_offsetof(struct T, x) == 2, \"T.x\");\n",
2913 "typedef int H __attribute__((aligned(16)));\n",
2915 "_Static_assert(__alignof__(H) == 16, \"H\");\n",
2916 "_Static_assert(sizeof(H) == 4, \"H size\");\n",
2917 "struct U { char c; H x; };\n",
2918 "_Static_assert(sizeof(struct U) == 32, \"U\");\n",
2919 "_Static_assert(__builtin_offsetof(struct U, x) == 16, \"U.x\");\n",
2920 "typedef L M __attribute__((aligned(8)));\n",
2923 "_Static_assert(__alignof__(M) == 8, \"M\");\n",
2924 "typedef L N;\n",
2927 "_Static_assert(__alignof__(N) == 2, \"N\");\n",
2928 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2930 ));
2931 let text = asm(concat!(
2932 "typedef int L __attribute__((aligned(2)));\n",
2933 "typedef int H __attribute__((aligned(16)));\n",
2934 "L low;\n",
2935 "H high;\n",
2936 ));
2937 assert!(text.contains("\t.p2align\t1\n\t.type\tlow, @object\n"), "{text}");
2938 assert!(text.contains("\t.p2align\t4\n\t.type\thigh, @object\n"), "{text}");
2939 }
2940
2941 #[test]
2949 fn the_vector_size_attribute_builds_a_type_of_lanes_and_measures_it_in_bytes() {
2950 tast(concat!(
2951 "typedef int __attribute__((vector_size(16))) v4si;\n",
2952 "_Static_assert(sizeof(v4si) == 16 && _Alignof(v4si) == 16, \"v4si\");\n",
2953 "typedef char __attribute__((vector_size(16))) v16qi;\n",
2954 "_Static_assert(sizeof(v16qi) == 16, \"v16qi\");\n",
2955 "typedef int __attribute__((vector_size(4))) v1si;\n",
2958 "_Static_assert(sizeof(v1si) == 4, \"v1si\");\n",
2959 "typedef float __attribute__((__vector_size__(8))) v2sf;\n",
2961 "_Static_assert(sizeof(v2sf) == 8, \"v2sf\");\n",
2962 "typedef short [[gnu::vector_size(8)]] v4hi;\n",
2963 "_Static_assert(sizeof(v4hi) == 8, \"v4hi\");\n",
2964 "v4si g;\n",
2967 "_Static_assert(sizeof(g[0]) == 4, \"lane\");\n",
2968 "_Static_assert(sizeof(g + g) == 16, \"whole\");\n",
2969 "_Static_assert(sizeof(g + 1) == 16, \"broadcast\");\n",
2972 "_Static_assert(sizeof(v4si[3]) == 48, \"array\");\n",
2974 ));
2975 }
2976
2977 #[test]
2987 fn a_vector_is_written_whole_into_an_array_of_them_and_named_by_a_type_name() {
2988 tast(concat!(
2989 "typedef int __attribute__((vector_size(8))) v2si;\n",
2990 "v2si table[] = { (v2si){ 1, 2 }, (v2si){ 3, 4 } };\n",
2991 "_Static_assert(sizeof(table) == 16, \"two of them and not eight lanes\");\n",
2992 "v2si written = (int __attribute__((vector_size(8)))){ 5, 6 };\n",
2994 "_Static_assert(sizeof((int __attribute__((vector_size(16)))){ 0 }) == 16, \"named\");\n",
2995 "v2si lanes[2] = { 1, 2, 3, 4 };\n",
2998 "_Static_assert(sizeof(lanes) == 16, \"still elided\");\n",
2999 ));
3000 }
3001
3002 #[test]
3010 fn a_lane_is_assignable_and_a_shift_takes_a_count_of_its_own_lane() {
3011 let result = run(
3012 &options(),
3013 concat!(
3014 "typedef int __attribute__((vector_size(16))) v4si;\n",
3015 "typedef unsigned __attribute__((vector_size(16))) v4ui;\n",
3016 "void write(v4si *out, v4ui a, v4si b, int n) {\n",
3017 " v4si v = { 1, 2, 3, 4 };\n",
3018 " v[0] = n;\n",
3019 " v[1] += n;\n",
3020 " v[2]++;\n",
3021 " *&v[3] = n;\n",
3022 " v4ui shifted = a >> b;\n",
3024 " shifted <<= b;\n",
3025 " *out = v + (v4si)shifted + (1 << b);\n",
3028 "}\n",
3029 "void refused(const v4si c) {\n",
3032 " c[0] = 1;\n",
3033 "}\n",
3034 ),
3035 );
3036 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
3037 assert!(result.messages[0].contains("assignment of read-only"), "{:?}", result.messages);
3038 }
3039
3040 #[test]
3052 fn a_record_that_asks_for_the_other_byte_order_swaps_every_scalar_it_holds() {
3053 let read = "int f(struct s *p) { return p->i; }\n";
3054 let big = "struct s { int i; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
3055 assert!(body(&format!("{big}{read}")).contains("bswap"), "{big}");
3056
3057 let armoured =
3058 "struct s { int i; } __attribute__((__scalar_storage_order__(\"big-endian\")));\n";
3059 assert!(body(&format!("{armoured}{read}")).contains("bswap"), "{armoured}");
3060
3061 let front = "struct __attribute__((scalar_storage_order(\"big-endian\"))) s { int i; };\n";
3062 assert!(body(&format!("{front}{read}")).contains("bswap"), "{front}");
3063
3064 let standard = "struct s { int i; } [[gnu::scalar_storage_order(\"big-endian\")]];\n";
3065 assert!(body(&format!("{standard}{read}")).contains("bswap"), "{standard}");
3066
3067 let same =
3068 "struct s { int i; } __attribute__((scalar_storage_order(\"little-endian\")));\n";
3069 assert!(!body(&format!("{same}{read}")).contains("bswap"), "{same}");
3070
3071 let byte = "struct s { char c; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
3073 let source = format!("{byte}int f(struct s *p) {{ return p->c; }}\n");
3074 assert!(!body(&source).contains("bswap"), "{byte}");
3075
3076 tast(concat!(
3077 "struct s { int i; short h; char c; }",
3078 " __attribute__((scalar_storage_order(\"big-endian\")));\n",
3079 "_Static_assert(sizeof(struct s) == 8 && _Alignof(struct s) == 4, \"s\");\n",
3080 "_Static_assert(__builtin_offsetof(struct s, h) == 4, \"s.h\");\n",
3081 "_Static_assert(__builtin_offsetof(struct s, c) == 6, \"s.c\");\n",
3082 ));
3083 }
3084
3085 #[test]
3091 fn a_bit_field_in_one_of_those_records_is_counted_from_the_top_of_its_bytes() {
3092 let members = "short i : 12; char c1 : 1; char c2 : 1; char c3 : 1; char c4 : 1;";
3093 let read = "int f(struct s *p) { return p->i; }\n";
3094 let plain = format!("struct s {{ {members} }};\n{read}");
3095 let reversed = format!(
3096 "struct s {{ {members} }} __attribute__((scalar_storage_order(\"big-endian\")));\n\
3097 {read}"
3098 );
3099 assert!(body(&plain).contains("shl"), "{}", body(&plain));
3100 assert!(!body(&plain).contains("bswap"), "{}", body(&plain));
3101 let built = body(&reversed);
3104 assert!(built.contains("bswap"), "{built}");
3105 assert!(!built.contains("shl"), "{built}");
3106 assert!(built.contains("ashr"), "{built}");
3107 }
3108
3109 #[test]
3116 fn the_address_of_a_scalar_stored_the_other_way_round_is_refused() {
3117 let opts = options();
3118 let record = "struct s { int i; int a[2]; struct in { int n; } w; }\n\
3119 __attribute__((scalar_storage_order(\"big-endian\")));\n";
3120 let taken = format!("{record}int *f(struct s *p) {{ return &p->i; }}\n");
3121 assert_eq!(
3122 run(&opts, &taken).messages,
3123 ["/main.c:3:30: error: cannot take address of scalar with reverse storage order \
3124 [E0712]"]
3125 );
3126 let element = format!("{record}int *f(struct s *p) {{ return &p->a[0]; }}\n");
3127 let messages = run(&opts, &element).messages;
3128 assert!(messages[0].contains("[E0712]"), "{messages:?}");
3129
3130 let whole = format!("{record}int *f(struct s *p) {{ return (int *) &p->w; }}\n");
3131 assert_eq!(run(&opts, &whole).messages, Vec::<String>::new(), "{whole}");
3132 }
3133
3134 #[test]
3138 fn a_storage_order_that_names_neither_end_is_refused_with_the_two_words_that_are_taken() {
3139 let opts = options();
3140 let wrong = "struct s { int i; } __attribute__((scalar_storage_order(\"middle\")));\n";
3141 assert_eq!(
3142 run(&opts, wrong).messages,
3143 ["/main.c:1:36: error: 'scalar_storage_order' argument must be one of \"big-endian\" \
3144 or \"little-endian\" [E0688]"]
3145 );
3146 let bare = "struct s { int i; } __attribute__((scalar_storage_order));\n";
3147 let messages = run(&opts, bare).messages;
3148 assert!(messages[0].contains("[E0688]"), "{messages:?}");
3149 }
3150
3151 #[test]
3161 fn packing_is_what_decides_whether_a_bit_field_may_straddle_its_own_storage() {
3162 assert_eq!(bit_field_byte("struct s { int x : 12; char y : 6; };"), 2);
3164 assert_eq!(
3165 bit_field_byte("struct s { int x : 12; char y : 6; } __attribute__((packed));"),
3166 1
3167 );
3168 assert_eq!(
3169 bit_field_byte("struct s { int x : 12; __attribute__((packed)) char y : 6; };"),
3170 1
3171 );
3172 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { int x : 12; char y : 6; };"), 1);
3173 assert_eq!(bit_field_byte("struct s { char x; int y : 30; };"), 4);
3175 assert_eq!(bit_field_byte("struct s { char x; int y : 30; } __attribute__((packed));"), 1);
3176 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { char x; int y : 30; };"), 1);
3178 assert_eq!(bit_field_byte("#pragma pack(2)\nstruct s { char x; int y : 30; };"), 1);
3179 }
3180
3181 fn bit_field_byte(record: &str) -> u64 {
3183 let source = format!("{record}\nint f(struct s *p) {{ return p->y; }}\n");
3184 let body = body(&source);
3185 let Some((before, _)) = body.split_once("ptr_add") else { return 0 };
3186 let (_, constant) = before.rsplit_once("iconst.i64 ").expect("an offset constant");
3187 constant.lines().next().expect("a line").trim().parse().expect("a byte offset")
3188 }
3189
3190 #[test]
3196 fn an_attribute_among_the_specifiers_is_kept_beside_the_ones_written_in_front() {
3197 tast(concat!(
3198 "struct a { char c; __attribute__((aligned(8))) int i; };\n",
3199 "_Static_assert(sizeof(struct a) == 16 && _Alignof(struct a) == 8, \"a\");\n",
3200 "_Static_assert(__builtin_offsetof(struct a, i) == 8, \"a.i\");\n",
3201 "struct b { char c; __attribute__((packed)) int i; };\n",
3202 "_Static_assert(sizeof(struct b) == 5 && _Alignof(struct b) == 1, \"b\");\n",
3203 "_Static_assert(__builtin_offsetof(struct b, i) == 1, \"b.i\");\n",
3204 "typedef struct { char c; int i; } __attribute__((packed)) c;\n",
3205 "_Static_assert(sizeof(c) == 5 && _Alignof(c) == 1, \"c\");\n",
3206 ));
3207 }
3208
3209 #[test]
3215 fn pragma_pack_caps_every_member_and_is_read_where_the_body_closes() {
3216 tast(concat!(
3217 "#pragma pack(1)\n",
3218 "struct A { char c; int i; };\n",
3219 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
3220 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
3221 "#pragma pack()\n",
3222 "struct B { char c; int i; };\n",
3223 "_Static_assert(sizeof(struct B) == 8 && _Alignof(struct B) == 4, \"B\");\n",
3224 "#pragma pack(2)\n",
3225 "struct C { char c; int i; double d; };\n",
3226 "_Static_assert(sizeof(struct C) == 14 && _Alignof(struct C) == 2, \"C\");\n",
3227 "_Static_assert(__builtin_offsetof(struct C, d) == 6, \"C.d\");\n",
3228 "struct K { char c; int i __attribute__((aligned(8))); };\n",
3230 "_Static_assert(sizeof(struct K) == 6 && _Alignof(struct K) == 2, \"K\");\n",
3231 "_Static_assert(__builtin_offsetof(struct K, i) == 2, \"K.i\");\n",
3232 "struct J { char c; int i; } __attribute__((aligned(8)));\n",
3234 "_Static_assert(sizeof(struct J) == 8 && _Alignof(struct J) == 8, \"J\");\n",
3235 "#pragma pack()\n",
3236 "#pragma pack(push, 1)\n",
3237 "struct D { char c; short s; };\n",
3238 "_Static_assert(sizeof(struct D) == 3 && _Alignof(struct D) == 1, \"D\");\n",
3239 "#pragma pack(pop)\n",
3240 "struct E { char c; short s; };\n",
3241 "_Static_assert(sizeof(struct E) == 4 && _Alignof(struct E) == 2, \"E\");\n",
3242 "struct H { char c;\n",
3244 "#pragma pack(1)\n",
3245 " int i; };\n",
3246 "_Static_assert(sizeof(struct H) == 5 && _Alignof(struct H) == 1, \"H\");\n",
3247 "#pragma pack(1)\n",
3248 "struct I { char c;\n",
3249 "#pragma pack()\n",
3250 " int i; };\n",
3251 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
3252 "#pragma pack()\n",
3253 "#pragma pack(push, 8)\n",
3255 "#pragma pack(push, 1)\n",
3256 "struct P { char c; int i; };\n",
3257 "_Static_assert(sizeof(struct P) == 5 && _Alignof(struct P) == 1, \"P\");\n",
3258 "#pragma pack(pop)\n",
3259 "struct Q { char c; int i; };\n",
3260 "_Static_assert(sizeof(struct Q) == 8 && _Alignof(struct Q) == 4, \"Q\");\n",
3261 "#pragma pack(pop)\n",
3262 "#pragma pack(16)\n",
3264 "struct R { char c; int i; };\n",
3265 "_Static_assert(sizeof(struct R) == 8 && _Alignof(struct R) == 4, \"R\");\n",
3266 "#pragma pack()\n",
3267 "#pragma pack(1)\n",
3268 "struct S { char c; int i : 5; int j : 20; };\n",
3269 "_Static_assert(sizeof(struct S) == 5 && _Alignof(struct S) == 1, \"S\");\n",
3270 "union T { char c; int i; };\n",
3271 "_Static_assert(sizeof(union T) == 4 && _Alignof(union T) == 1, \"T\");\n",
3272 "#pragma pack()\n",
3273 ));
3274 }
3275
3276 #[test]
3280 fn a_pack_line_that_is_not_one_is_reported_in_the_words_gcc_uses() {
3281 let result = run(
3282 &options(),
3283 concat!(
3284 "#pragma pack 4\n",
3285 "#pragma pack(pop)\n",
3286 "#pragma pack(3)\n",
3287 "#pragma pack(1) junk\n",
3288 "#pragma pack(push, 1\n",
3289 "#pragma pack(x)\n",
3290 "#pragma pack(0)\n",
3293 "#pragma pack(push)\n",
3294 "struct s { char c; int i; };\n",
3295 "#pragma pack(pop)\n",
3296 "#pragma pack(pop, foo)\n",
3297 ),
3298 );
3299 let expected = [
3300 "missing `(` after `#pragma pack` - ignored",
3301 "`#pragma pack (pop)` encountered without matching `#pragma pack (push)`",
3302 "alignment must be a small power of two, not 3",
3303 "junk at end of `#pragma pack`",
3304 "malformed `#pragma pack(push[, id][, <n>])` - ignored",
3305 "unknown action `x` for `#pragma pack` - ignored",
3306 "`#pragma pack(pop, foo)` encountered without matching `#pragma pack(push, foo)`",
3307 ];
3308 assert_eq!(result.messages.len(), expected.len(), "{:?}", result.messages);
3309 for (message, want) in result.messages.iter().zip(expected) {
3310 assert!(message.contains(want), "expected {want:?} in {message:?}");
3311 }
3312 }
3313
3314 #[test]
3321 fn a_declaration_behind_an_empty_macro_is_not_eaten_by_the_pragma_above_it() {
3322 let result = run(
3323 &options(),
3324 concat!(
3325 "#pragma pack(push, 1)\n",
3326 "#pragma pack(pop)\n",
3327 "#define API\n",
3328 "API const char version[] = \"3.53.4\";\n",
3329 "const char *get(void) { return version; }\n",
3330 ),
3331 );
3332 assert!(result.messages.is_empty(), "{:?}", result.messages);
3333 }
3334
3335 #[test]
3339 fn the_wide_integer_answers_to_all_three_of_its_names() {
3340 let text = tast("__uint128_t a; __int128_t b; unsigned __int128 c;\n");
3341 assert!(text.contains("decl #0 a : unsigned __int128"), "{text}");
3342 assert!(text.contains("decl #1 b : __int128"), "{text}");
3343 assert!(text.contains("decl #2 c : unsigned __int128"), "{text}");
3344 }
3345
3346 #[test]
3347 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
3348 let text = tast("long f(int a, long b) { return a + b; }\n");
3352 assert!(text.contains("convert arithmetic"), "{text}");
3353 }
3354
3355 #[test]
3356 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
3357 for source in [
3358 "#error stop\n",
3359 "int f(void) { return 1 + ; }\n",
3360 "int f(void) { return undeclared; }\n",
3361 ] {
3362 let result = run(&options(), source);
3363 assert!(result.failed(), "expected this to fail:\n{source}");
3364 assert!(
3365 result.text().is_empty(),
3366 "a file that did not compile wrote a tree:\n{source}"
3367 );
3368 }
3369 }
3370
3371 #[test]
3372 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
3373 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
3377 assert_eq!(result.errors, 1, "{:?}", result.messages);
3378 }
3379
3380 #[test]
3381 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
3382 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
3386 assert_eq!(result.errors, 1, "{:?}", result.messages);
3387 }
3388
3389 #[test]
3390 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
3391 let source = "int f(void) { char c = 300; return c; }\n";
3392 let plain = run(&options(), source);
3393 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
3394 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
3395 assert!(!plain.text().is_empty(), "a warning is not a reason to write nothing");
3396
3397 let mut opts = options();
3398 opts.warnings_are_errors = true;
3399 let strict = run(&opts, source);
3400 assert!(strict.failed());
3401 assert!(strict.text().is_empty(), "and under -Werror it is a reason to write nothing");
3402 for message in &strict.messages {
3403 assert!(!message.contains("warning:"), "{message}");
3404 }
3405 }
3406
3407 #[test]
3408 fn w_drops_the_warning_before_werror_can_promote_it() {
3409 let source = "int f(void) { char c = 300; return c; }\n";
3410 let mut opts = options();
3411 opts.warnings = false;
3412 let quiet = run(&opts, source);
3413 assert_eq!(quiet.messages, Vec::<String>::new());
3414 assert_eq!(quiet.errors, 0);
3415 assert!(!quiet.text().is_empty(), "and the file still compiles");
3416
3417 opts.warnings_are_errors = true;
3420 let both = run(&opts, source);
3421 assert_eq!(both.messages, Vec::<String>::new());
3422 assert!(!both.failed(), "-w -Werror is not an error about a warning nobody saw");
3423 }
3424
3425 #[test]
3426 fn the_dialect_reaches_the_keywords_and_the_checking() {
3427 let source = "typeof(1) x;\n";
3430 let mut opts = options();
3431 opts.std = Std::C23;
3432 opts.gnu_extensions = false;
3433 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
3434
3435 opts.std = Std::C17;
3436 assert!(run(&opts, source).failed());
3437 }
3438
3439 #[test]
3440 fn asking_for_a_kind_that_is_not_written_yet_runs_the_front_end_and_writes_nothing() {
3441 let mut opts = options();
3442 opts.emit = EmitKind::Object;
3443 let result = run(&opts, "int x = 1;\n");
3444 assert!(!result.failed(), "{:?}", result.messages);
3445 assert!(result.text().is_empty());
3446 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
3449 }
3450
3451 fn mir(source: &str) -> String {
3453 let mut opts = options();
3454 opts.emit = EmitKind::MirFinal;
3455 let result = run(&opts, source);
3456 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3457 result.text().to_owned()
3458 }
3459
3460 #[test]
3466 fn a_function_goes_from_c_to_instructions_with_real_registers_in_them() {
3467 let text = mir("int add(int a, int b) { return a + b; }\n");
3468 assert!(text.starts_with("mfunc @add {"), "{text}");
3469 assert!(text.contains("x64.add_rr_32"), "{text}");
3470 assert!(text.contains("x64.ret"), "{text}");
3471 assert!(!text.contains('%'), "{text}");
3474 }
3475
3476 #[test]
3478 fn a_function_with_no_body_produces_no_machine_function() {
3479 let text = mir("int g(int);\nint f(int a) { return g(a); }\n");
3480 assert_eq!(text.matches("mfunc @").count(), 1, "{text}");
3481 assert!(text.contains("mfunc @f {"), "{text}");
3482 assert!(text.contains("x64.call"), "{text}");
3483 }
3484
3485 #[test]
3487 fn every_definition_in_the_file_is_generated_and_they_keep_their_order() {
3488 let text = mir("int a(int x) { return x; }\nint b(int x) { return x; }\n");
3489 let first = text.find("mfunc @a").expect("the first function");
3490 let second = text.find("mfunc @b").expect("the second function");
3491 assert!(first < second, "{text}");
3492 }
3493
3494 #[test]
3496 fn the_target_decides_which_convention_the_generated_code_follows() {
3497 let mut opts = options();
3498 opts.emit = EmitKind::MirFinal;
3499 let linux = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
3500 assert!(linux.contains("$rdi"), "{linux}");
3501
3502 opts.target = "x86_64-pc-windows-msvc".parse::<Triple>().unwrap();
3503 let windows = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
3504 assert!(windows.contains("$rcx"), "{windows}");
3505 assert!(!windows.contains("$rdi"), "{windows}");
3506 }
3507
3508 #[test]
3516 fn a_tagged_member_with_no_name_is_a_member_on_windows_and_nothing_on_linux() {
3517 let source = concat!(
3518 "struct S { union U { int i; void *p; }; unsigned long tymed; };\n",
3519 "int size(void) { return sizeof(struct S); }\n",
3520 "int f(struct S *s) { s->i = 1; return s->i; }\n",
3521 );
3522
3523 let mut opts = options();
3524 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3525 let windows = run(&opts, source);
3526 assert!(windows.messages.is_empty(), "{:?}", windows.messages);
3527
3528 let linux = run(&options(), source);
3529 assert_eq!(linux.messages.len(), 3, "{:?}", linux.messages);
3530 assert!(linux.messages[0].contains("does not declare anything"), "{:?}", linux.messages);
3531
3532 let mut opts = options();
3535 opts.ms_extensions = Some(true);
3536 let asked = run(&opts, source);
3537 assert!(asked.messages.is_empty(), "{:?}", asked.messages);
3538 }
3539
3540 #[test]
3542 fn a_target_this_has_no_back_end_for_is_reported_rather_than_generated() {
3543 let mut opts = options();
3544 opts.emit = EmitKind::MirFinal;
3545 opts.target = "riscv64-unknown-linux-gnu".parse::<Triple>().unwrap();
3546 let result = run(&opts, "int f(int a) { return a; }\n");
3547 assert!(result.failed());
3548 assert!(result.messages[0].contains("no back end for riscv64"), "{:?}", result.messages);
3549 assert!(result.text().is_empty());
3550 }
3551
3552 #[test]
3555 fn an_aarch64_target_is_written_as_aarch64_assembly() {
3556 let mut opts = options();
3557 opts.emit = EmitKind::Asm;
3558 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3559 let source = "int g(int);\nint f(int a, int b) { return g(a) + b; }\n";
3560 let result = run(&opts, source);
3561 assert!(!result.failed(), "{:?}", result.messages);
3562 let text = result.text();
3563 for line in ["stp x29, x30, [sp, #-16]!", "mov x29, sp", "bl g", "ldp x29, x30, [sp], #16"]
3564 {
3565 assert!(text.contains(line), "{line} is not in\n{text}");
3566 }
3567 assert!(!text.contains('%'), "{text}");
3568 }
3569
3570 #[test]
3573 fn an_aarch64_target_reaches_an_object_file() {
3574 let mut opts = options();
3575 opts.emit = EmitKind::Object;
3576 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3577 let source = concat!(
3578 "int g(int);\n",
3579 "int table[4] = {1, 2, 3, 4};\n",
3580 "int f(int a, int b) { return g(a) + table[b & 3]; }\n",
3581 );
3582 let result = run(&opts, source);
3583 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3584 let bytes = match result.artifact {
3585 Artifact::Object { bytes, defines } => {
3586 assert_eq!(defines, ["f", "table"]);
3587 bytes
3588 }
3589 other => panic!("expected an object, got {other:?}"),
3590 };
3591 assert_eq!(&bytes[..4], b"\x7fELF");
3592 assert_eq!(&bytes[18..20], &183u16.to_le_bytes(), "EM_AARCH64");
3593
3594 opts.debug_info = true;
3597 let result = run(&opts, source);
3598 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3599 let bytes = match result.artifact {
3600 Artifact::Object { bytes, .. } => bytes,
3601 other => panic!("expected an object, got {other:?}"),
3602 };
3603 let has = |name: &[u8]| bytes.windows(name.len()).any(|at| at == name);
3604 assert!(has(b".debug_line\0") && has(b".debug_info\0"));
3605 assert!(!has(b"rucc_row"), "a row label reached the symbol table");
3606 }
3607
3608 #[test]
3611 fn the_aarch64_vector_type_names_are_declared_on_that_target_and_nowhere_else() {
3612 let mut opts = options();
3613 opts.emit = EmitKind::Asm;
3614 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3615 let source = "typedef __Float32x4_t f4;\n__SVFloat32_t sv(__SVFloat32_t, __SVBool_t);\n\
3616 int n = sizeof(f4) + sizeof(__Int8x8_t);\n\
3617 int f(f4 v) { int __Uint8x16_t = 3; return v[1] + __Uint8x16_t; }\n";
3618 let result = run(&opts, source);
3619 assert!(!result.failed(), "{:?}", result.messages);
3620 assert!(result.text().contains(".long\t24"), "{}", result.text());
3621 opts.target = "x86_64-unknown-linux-gnu".parse::<Triple>().unwrap();
3622 let result = run(&opts, "typedef __Float32x4_t f4;\n");
3623 assert!(result.failed());
3624 let result = run(&opts, "int __Float32x4_t = 1;\n");
3625 assert!(!result.failed(), "{:?}", result.messages);
3626 }
3627
3628 #[test]
3631 fn an_aarch64_result_in_memory_is_reached_through_x8() {
3632 let mut opts = options();
3633 opts.emit = EmitKind::Asm;
3634 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3635 let source = "struct big { long a, b, c; };\nstruct big make(long v);\n\
3636 long f(long v) { return make(v).c; }\n\
3637 struct big g(long v) { struct big b = { v, v, v }; return b; }\n";
3638 let result = run(&opts, source);
3639 assert!(!result.failed(), "{:?}", result.messages);
3640 let text = result.text();
3641 assert!(text.contains("x8"), "{text}");
3642 assert!(text.contains("bl make"), "{text}");
3643 }
3644
3645 #[test]
3648 fn an_aarch64_remainder_is_a_division_and_a_multiply_subtract() {
3649 let mut opts = options();
3650 opts.emit = EmitKind::Asm;
3651 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3652 let source = "int s(int a, int b) { return a % b; }\n\
3653 unsigned long u(unsigned long a, unsigned long b) { return a % b; }\n";
3654 let result = run(&opts, source);
3655 assert!(!result.failed(), "{:?}", result.messages);
3656 let text = result.text();
3657 let at = |what: &str| text.find(what).unwrap_or_else(|| panic!("{what} is not in\n{text}"));
3658 assert!(at("sdiv w") < at("msub w"), "{text}");
3659 assert!(at("udiv x") < at("msub x"), "{text}");
3660 }
3661
3662 #[test]
3665 fn an_aarch64_jump_table_is_reached_with_adr() {
3666 let mut opts = options();
3667 opts.emit = EmitKind::Asm;
3668 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3669 let source = "int f(int x) { switch (x) { case 0: return 10; case 1: return 21; \
3670 case 2: return 32; case 3: return 43; case 4: return 54; case 5: return 65; \
3671 case 6: return 76; case 7: return 87; case 8: return 98; case 9: return 9; \
3672 case 10: return 19; case 11: return 29; default: return 0; } }\n";
3673 let result = run(&opts, source);
3674 assert!(!result.failed(), "{:?}", result.messages);
3675 let text = result.text();
3676 let at = |what: &str| text.find(what).unwrap_or_else(|| panic!("{what} is not in\n{text}"));
3677 assert!(at("adr x") < at("ldrsw x"), "{text}");
3678 assert!(at("ldrsw x") < at("br x"), "{text}");
3679 assert!(text.contains("_j0:"), "{text}");
3680 assert!(text.contains(".long"), "{text}");
3681 }
3682
3683 #[test]
3687 fn an_aarch64_va_start_writes_the_five_fields_of_its_list() {
3688 let mut opts = options();
3689 opts.emit = EmitKind::Asm;
3690 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3691 let source = "typedef __builtin_va_list va_list;\n\
3692 int f(int n, ...) { va_list ap; __builtin_va_start(ap, n); \
3693 int x = __builtin_va_arg(ap, int); double d = __builtin_va_arg(ap, double); \
3694 __builtin_va_end(ap); return x + (int)d; }\n";
3695 let result = run(&opts, source);
3696 assert!(!result.failed(), "{:?}", result.messages);
3697 let text = result.text();
3698 assert!(text.contains("#-56"), "{text}");
3699 assert!(text.contains("#-128"), "{text}");
3700 assert!(text.contains("#24]"), "{text}");
3701 assert!(text.contains("#28]"), "{text}");
3702 assert!(text.contains("str q"), "{text}");
3703 }
3704
3705 #[test]
3708 fn an_aarch64_long_double_is_a_quad_in_a_vector_register() {
3709 let mut opts = options();
3710 opts.emit = EmitKind::Asm;
3711 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3712 let source = "void f(long double *p, long double x) { *p = *p + x; }\n";
3713 let result = run(&opts, source);
3714 assert!(!result.failed(), "{:?}", result.messages);
3715 let text = result.text();
3716 assert!(text.contains("ldr q"), "{text}");
3717 assert!(text.contains("str q"), "{text}");
3718 assert!(text.contains("__addtf3"), "{text}");
3719 }
3720
3721 #[test]
3724 fn an_aarch64_thread_local_is_reached_through_tpidr_el0() {
3725 let mut opts = options();
3726 opts.emit = EmitKind::Asm;
3727 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3728 let source = "__thread int n;\nint *f(void) { return &n; }\n\
3729 void *g(void) { return __builtin_thread_pointer(); }\n";
3730 let result = run(&opts, source);
3731 assert!(!result.failed(), "{:?}", result.messages);
3732 let text = result.text();
3733 assert!(text.contains(":gottprel:n"), "{text}");
3734 assert!(text.contains(":gottprel_lo12:n]"), "{text}");
3735 assert_eq!(text.matches("mrs x").count(), 2, "{text}");
3736 assert!(text.contains("tpidr_el0"), "{text}");
3737 }
3738
3739 #[test]
3742 fn a_darwin_thread_local_is_reached_through_its_descriptor() {
3743 let source = "__thread int n = 5;\nint *f(void) { return &n; }\n";
3744 for (triple, wanted) in [
3745 ("aarch64-apple-darwin", &["_n@TLVPPAGE\n", "_n@TLVPPAGEOFF]\n", "\tblr x"][..]),
3746 ("x86_64-apple-darwin", &["_n@TLVP(%rip), %rdi\n", "\tcall\t*%"][..]),
3747 ] {
3748 let mut opts = options();
3749 opts.emit = EmitKind::Asm;
3750 opts.target = triple.parse::<Triple>().unwrap();
3751 let result = run(&opts, source);
3752 assert!(!result.failed(), "{triple}: {:?}", result.messages);
3753 let text = result.text();
3754 for want in wanted {
3755 assert!(text.contains(want), "{triple} wanted {want:?}:\n{text}");
3756 }
3757 assert!(text.contains("\n_n:\n\t.quad\t__tlv_bootstrap\n"), "{text}");
3758 assert!(!text.contains("tpidr_el0") && !text.contains("%fs"), "{text}");
3759 }
3760 }
3761
3762 #[test]
3764 fn the_thread_pointer_is_refused_on_darwin() {
3765 let mut opts = options();
3766 opts.emit = EmitKind::Asm;
3767 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3768 let result = run(&opts, "void *f(void) { return __builtin_thread_pointer(); }\n");
3769 assert!(result.failed());
3770 assert!(result.messages[0].contains("thread pointer"), "{:?}", result.messages);
3771 }
3772
3773 #[test]
3776 fn a_darwin_variadic_definition_saves_nothing_and_walks_the_stack() {
3777 let mut opts = options();
3778 opts.emit = EmitKind::Asm;
3779 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3780 let source = "int f(int n, ...) { __builtin_va_list ap; __builtin_va_start(ap, n);\n\
3781 int r = __builtin_va_arg(ap, int); __builtin_va_end(ap); return r; }\n";
3782 let result = run(&opts, source);
3783 assert!(!result.failed(), "{:?}", result.messages);
3784 let text = result.text();
3785 assert!(!text.contains("str q"), "{text}");
3786 assert!(!text.contains("x7"), "{text}");
3787 }
3788
3789 #[test]
3792 fn a_darwin_call_puts_its_variadic_arguments_in_memory() {
3793 let mut opts = options();
3794 opts.emit = EmitKind::Asm;
3795 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3796 let source = "int printf(const char *, ...);\n\
3797 int g(double x) { return printf(\"%d %f\", 7, x); }\n";
3798 let result = run(&opts, source);
3799 assert!(!result.failed(), "{:?}", result.messages);
3800 let text = result.text();
3801 assert!(text.contains("str d0, [sp, #8]"), "{text}");
3802 }
3803
3804 #[test]
3808 fn a_darwin_listing_is_one_apples_assembler_reads() {
3809 let mut opts = options();
3810 opts.emit = EmitKind::Asm;
3811 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3812 let source = "extern int ext;\n\
3813 int g[4];\n\
3814 int f(int i) { return g[i] + ext; }\n";
3815 let result = run(&opts, source);
3816 assert!(!result.failed(), "{:?}", result.messages);
3817 let text = result.text();
3818 assert!(text.contains(", _g@PAGE\n"), "{text}");
3819 assert!(text.contains(", _g@PAGEOFF\n"), "{text}");
3820 assert!(text.contains(", _ext@GOTPAGE\n"), "{text}");
3821 assert!(text.contains(", _ext@GOTPAGEOFF]\n"), "{text}");
3822 assert!(!text.contains(":lo12:"), "{text}");
3823 assert!(text.contains("\t.globl\t_g\n\t.zerofill\t__DATA,__bss,_g,16,2\n"), "{text}");
3824 }
3825
3826 #[test]
3832 fn a_signed_char_on_aarch64_is_widened_with_its_sign_before_it_is_added_to() {
3833 for target in ["aarch64-linux-gnu", "aarch64-apple-darwin"] {
3834 let mut opts = options();
3835 opts.emit = EmitKind::Asm;
3836 opts.target = target.parse::<Triple>().unwrap();
3837 let source = "int f(signed char *p) { return *p + 1; }\n\
3838 unsigned g(unsigned short *p) { return *p + 1u; }\n";
3839 let result = run(&opts, source);
3840 assert!(!result.failed(), "{:?}", result.messages);
3841 let text = result.text();
3842 let signed = text.contains("\tsxtb w") || text.contains("\tldrsb w");
3843 assert!(signed, "{target}: {text}");
3844 }
3845 }
3846
3847 #[test]
3859 fn a_construct_the_back_end_cannot_reach_yet_is_reported_against_its_function() {
3860 let mut opts = options();
3861 opts.emit = EmitKind::MirFinal;
3862 let source = "void a(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
3863 s; s.x = 1; v[0] = s.x; }\n\
3864 void b(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
3865 s; s.x = 1; v[0] = s.x; }\n";
3866 let result = run(&opts, source);
3867 assert!(result.failed());
3868 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
3869 assert!(result.messages[0].contains("cannot generate code for 'a'"), "{:?}", result);
3870 assert!(result.messages[0].contains("wants more alignment"), "{:?}", result);
3871 assert!(result.messages[1].contains("cannot generate code for 'b'"), "{:?}", result);
3872 assert!(result.text().is_empty());
3873 }
3874
3875 #[test]
3883 fn a_variable_length_array_walks_its_pages_where_every_page_of_the_frame_is_to_be_touched() {
3884 let mut opts = options();
3885 opts.emit = EmitKind::MirFinal;
3886 let source = "void a(int n) { int v[n]; v[0] = 1; }\n";
3887 let plain = run(&opts, source);
3888 assert!(!plain.failed(), "{:?}", plain.messages);
3889 assert!(!plain.text().contains("cmp_set_a_64"), "{}", plain.text());
3890
3891 opts.stack_clash = true;
3892 let result = run(&opts, source);
3893 assert!(!result.failed(), "{:?}", result.messages);
3894 assert!(result.text().contains("cmp_set_a_64"), "{}", result.text());
3895 assert!(result.text().contains("or_mi_8"), "{}", result.text());
3896 }
3897
3898 #[test]
3908 fn a_function_that_keeps_a_frame_pointer_on_windows_reaches_an_object_file() {
3909 let mut opts = options();
3910 opts.emit = EmitKind::Object;
3911 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3912 let source = concat!(
3913 "void use(void *p);\n",
3914 "void array(int n) { int v[n]; v[0] = 1; use(v); }\n",
3915 "void taken(unsigned long n) { use(__builtin_alloca(n)); }\n",
3916 );
3917 let result = run(&opts, source);
3918 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3919 let bytes = match result.artifact {
3920 Artifact::Object { bytes, .. } => bytes,
3921 other => panic!("expected an object, got {other:?}"),
3922 };
3923 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3924
3925 let mut opts = options();
3928 opts.emit = EmitKind::Object;
3929 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3930 }
3931
3932 #[test]
3943 fn the_address_of_a_function_this_file_only_declares_reaches_a_windows_object() {
3944 let source = concat!(
3945 "void other(void *p);\n",
3946 "void takes(void (*f)(void *));\n",
3947 "void (*held)(void *);\n",
3948 "void pass(void) { takes(other); }\n",
3949 "void keep(void) { held = other; }\n",
3950 "void call(void) { other(0); }\n",
3951 );
3952 let mut opts = options();
3953 opts.emit = EmitKind::Object;
3954 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3955 let result = run(&opts, source);
3956 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3957 let bytes = match result.artifact {
3958 Artifact::Object { bytes, .. } => bytes,
3959 other => panic!("expected an object, got {other:?}"),
3960 };
3961 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3962
3963 let mut opts = options();
3966 opts.emit = EmitKind::Object;
3967 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3968 }
3969
3970 #[test]
3984 fn an_opcode_with_no_name_in_the_rule_language_is_named_by_its_own_spelling() {
3985 let mut opts = options();
3986 opts.emit = EmitKind::MirFinal;
3987 let source =
3988 "long double f(int a) {\n __int128 wide = a;\n return (long double) wide;\n}\n";
3989 let result = run(&opts, source);
3990 assert!(result.failed());
3991 assert!(
3992 result.messages[0].contains("no rule lowers a `sext` producing a `i128`"),
3993 "{result:?}"
3994 );
3995 assert!(result.messages[0].contains(":2:"), "the line the widening is on: {result:?}");
3996 assert!(!result.messages[0].contains("this instruction"), "{result:?}");
3997 }
3998
3999 #[test]
4001 fn the_note_on_unfinished_work_points_at_the_issues_rather_than_at_the_plan() {
4002 let mut opts = options();
4003 opts.emit = EmitKind::MirFinal;
4004 let source = "long double f(int a) { __int128 wide = a; return (long double) wide; }\n";
4005 let result = run(&opts, source);
4006 assert!(result.failed());
4007 let note = result.messages.iter().find(|line| line.contains("note:")).expect("a note");
4008 assert!(note.contains("https://github.com/tamnd/rucc/issues"), "{note}");
4009 assert!(!note.contains("spec/17-milestones.md"), "{note}");
4010 }
4011
4012 #[test]
4014 fn the_frame_flags_on_the_command_line_reach_the_generated_frame() {
4015 let source = "int f(int a) { return a; }\n";
4016 assert!(!mir(source).contains("$rbp"), "a leaf needs no frame pointer when told so");
4017
4018 let mut opts = options();
4019 opts.emit = EmitKind::MirFinal;
4020 opts.frame_pointer = Some(true);
4021 let kept = run(&opts, source).text().to_owned();
4022 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
4023
4024 opts.frame_pointer = None;
4026 let kept = run(&opts, source).text().to_owned();
4027 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
4028 }
4029
4030 fn asm(source: &str) -> String {
4032 let mut opts = options();
4033 opts.emit = EmitKind::Asm;
4034 let result = run(&opts, source);
4035 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4036 result.text().to_owned()
4037 }
4038
4039 #[test]
4046 fn a_function_goes_from_c_to_assembly_an_assembler_would_take() {
4047 let text = asm("int add(int a, int b) { return a + b; }\n");
4048 assert!(text.contains("\t.globl\tadd\n"), "{text}");
4049 assert!(text.contains("\t.type\tadd, @function\n"), "{text}");
4050 assert!(text.contains("\nadd:\n"), "{text}");
4051 assert!(text.contains("\taddl\t"), "{text}");
4052 assert!(text.contains("\tret\n"), "{text}");
4053 assert!(text.contains("\t.size\tadd, .-add\n"), "{text}");
4054 assert!(text.contains(".note.GNU-stack"), "{text}");
4057 }
4058
4059 #[test]
4065 fn a_call_through_a_function_pointer_goes_through_the_register_it_is_in() {
4066 let text = asm("int g(int);\nint f(int (*p)(int), int a) { return p(a) + g(a); }\n");
4067 assert!(text.contains("\tcall\t*%"), "{text}");
4068 assert!(text.contains("\tcall\tg\n"), "{text}");
4069 assert!(text.contains("%rdi"), "{text}");
4073 }
4074
4075 #[test]
4079 fn the_address_of_a_global_is_read_from_the_instruction_pointer() {
4080 let text = asm("extern int counter;\nint f(void) { return counter; }\n");
4081 assert!(text.contains("\tmovl\tcounter(%rip), %eax\n"), "{text}");
4082 }
4083
4084 #[test]
4093 fn a_branch_on_a_comparison_jumps_on_the_opposite_of_what_it_compared() {
4094 let arms = "return 1; return 2;";
4095 let signed = [("==", "jne"), ("!=", "je"), ("<", "jge"), ("<=", "jg"), (">", "jle")];
4096 for (operator, jump) in signed.into_iter().chain([(">=", "jl")]) {
4097 let text = asm(&format!("int f(int a, int b) {{ if (a {operator} b) {arms} }}\n"));
4098 assert!(
4099 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
4100 "{operator}: {text}"
4101 );
4102 assert!(!text.contains("\tset"), "{operator}: {text}");
4103 assert!(!text.contains("\ttest"), "{operator}: {text}");
4104 }
4105 let unsigned = [("<", "jae"), ("<=", "ja"), (">", "jbe"), (">=", "jb")];
4106 for (operator, jump) in unsigned {
4107 let source =
4108 format!("int f(unsigned a, unsigned b) {{ if (a {operator} b) {arms} }}\n");
4109 let text = asm(&source);
4110 assert!(
4111 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
4112 "{operator}: {text}"
4113 );
4114 }
4115
4116 let text = asm("int f(int a) { if (a < 7) return 1; return 2; }\n");
4119 assert!(text.contains("\tcmpl\t$7, %edi\n\tjge\t"), "{text}");
4120 }
4121
4122 #[test]
4128 fn a_comparison_whose_answer_the_program_wanted_still_writes_a_byte() {
4129 let text = asm("int f(int a, int b) { return a < b; }\n");
4130 assert!(text.contains("\tsetl\t"), "{text}");
4131 }
4132
4133 fn optimized(source: &str) -> String {
4135 let mut opts = options();
4136 opts.emit = EmitKind::Asm;
4137 opts.opt_level = rucc_session::OptLevel::O2;
4138 let result = run(&opts, source);
4139 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4140 result.text().to_owned()
4141 }
4142
4143 #[test]
4145 fn opt_info_says_what_each_switch_became_and_a_forced_shape_is_what_it_says() {
4146 let arms: String = (0..40)
4147 .map(|k| format!("case {}: return g({k});", k * 17))
4148 .collect::<Vec<_>>()
4149 .join(" ");
4150 let source = format!("int g(int);\nint f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n");
4151 let said = |shape: Option<&str>| {
4152 let mut opts = options();
4153 opts.emit = EmitKind::Asm;
4154 opts.opt_level = rucc_session::OptLevel::O2;
4155 opts.opt_info = vec![String::new()];
4156 opts.switch_shape = shape.map(str::to_owned);
4157 let result = run(&opts, &source);
4158 assert_eq!(result.messages, Vec::<String>::new());
4159 let lines: Vec<String> = result
4160 .remarks
4161 .lines()
4162 .filter(|line| line.contains("[switch-lowering]"))
4163 .map(str::to_owned)
4164 .collect();
4165 assert_eq!(lines.len(), 1, "{}", result.remarks);
4166 lines[0].clone()
4167 };
4168 assert!(said(None).contains(": f: optimized: switch of 40 cases lowered as a tree;"));
4169 assert!(said(Some("table")).contains("lowered as a table;"));
4170 assert!(said(Some("walk")).contains("lowered as a walk;"));
4171 }
4172
4173 #[test]
4183 fn a_switch_whose_arms_are_a_function_of_the_label_is_a_range_check_and_arithmetic() {
4184 let arms: String =
4185 (0..16).map(|k| format!("case {k}: return {};", k + 1)).collect::<Vec<_>>().join(" ");
4186 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
4187 assert!(text.contains("\tcmpl\t$15, %edi\n\tja\t"), "{text}");
4188 assert!(text.contains("\taddl\t$1, %edi"), "{text}");
4189 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
4190 }
4191
4192 #[test]
4200 fn a_dense_switch_whose_arms_are_not_a_line_is_a_load_from_a_table() {
4201 let arms: String = (0..16)
4202 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
4203 .collect::<Vec<_>>()
4204 .join(" ");
4205 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
4206 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
4207 assert!(!text.contains("\tjmp\t*"), "{text}");
4208 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
4209 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
4210 let section = text[..text.find("CSWTCH.0:").unwrap_or(0)].rfind("\t.section\t.rodata");
4211 assert!(section.is_some(), "{text}");
4212 assert_eq!(table.matches("\t.long\t").count(), 16, "{text}");
4213 assert!(table.contains("\t.long\t100\n"), "{text}");
4214 }
4215
4216 #[test]
4222 fn a_switch_whose_arms_give_strings_is_a_table_of_how_far_away_they_are() {
4223 let text = optimized(
4224 "const char *f(int k) { switch (k) { case 0: return \"zero\"; \
4225 case 1: return \"one\"; case 2: return \"two\"; case 3: return \"three\"; } \
4226 return \"many\"; }\n",
4227 );
4228 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
4229 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
4230 assert!(!text.contains(".data.rel.ro"), "{text}");
4231 let at = text.find("CSWTCH.0:").expect("the table is in the output");
4232 assert!(text[..at].rfind("\t.section\t.rodata").is_some(), "{text}");
4233 let table = &text[at..];
4234 assert_eq!(table.matches(" - .\n").count(), 4, "{text}");
4235 assert!(table.contains("\t.long\t.Lstr.1+4 - .\n"), "{text}");
4236 }
4237
4238 #[test]
4244 fn a_table_at_os_has_cells_as_narrow_as_its_answers() {
4245 let arms: String = (0..16)
4246 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
4247 .collect::<Vec<_>>()
4248 .join(" ");
4249 let mut opts = options();
4250 opts.emit = EmitKind::Asm;
4251 opts.opt_level = rucc_session::OptLevel::Os;
4252 let result = run(&opts, &format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
4253 assert_eq!(result.messages, Vec::<String>::new());
4254 let text = result.text();
4255 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
4256 assert_eq!(table.matches("\t.byte\t").count(), 16, "{text}");
4257 assert!(text.contains("\tmovsbl\t"), "{text}");
4258 }
4259
4260 #[test]
4267 fn a_table_that_covers_its_operand_has_no_range_check() {
4268 let text = optimized(
4269 "int f(unsigned x) { switch (x & 3) { case 0: return 5; case 1: return 9; \
4270 case 2: return 2; case 3: return 7; } return -1; }\n",
4271 );
4272 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
4273 assert!(!text.contains("\tcmp"), "{text}");
4274 assert!(!text.contains("$-1"), "{text}");
4275 }
4276
4277 #[test]
4282 fn a_store_to_a_local_the_loop_just_read_is_a_conditional_move() {
4283 let text = optimized(
4284 "int f(const int *v, int n, int k) { int best[8] = {0}; \
4285 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; \
4286 return best[k & 7]; }\n",
4287 );
4288 assert!(text.contains("\tcmovgl"), "{text}");
4289 assert!(!text.contains("\tset"), "{text}");
4290 assert!(!text.contains("\ttestb"), "{text}");
4291 }
4292
4293 #[test]
4295 fn a_store_to_a_global_the_loop_just_read_keeps_its_branch() {
4296 let text = optimized(
4297 "int best[8]; void f(const int *v, int n) { \
4298 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; }\n",
4299 );
4300 assert!(!text.contains("\tcmov"), "{text}");
4301 }
4302
4303 #[test]
4311 fn a_conversion_from_a_constant_double_is_the_number_it_converts_to() {
4312 let text = optimized("int f(void) { double d = 2.75; return (int) d; }\n");
4313 assert!(text.contains("movl\t$2, %eax"), "{text}");
4314 assert!(!text.contains("cvttsd2si"), "{text}");
4315 }
4316
4317 #[test]
4325 fn a_slot_of_a_read_only_table_is_the_value_the_table_holds() {
4326 let text =
4327 optimized("static const int t[4] = {10, 20, 30, 40};\nint f(void) { return t[2]; }\n");
4328 assert!(text.contains("movl\t$30, %eax"), "{text}");
4329 assert!(!text.contains("t(%rip)"), "{text}");
4330 }
4331
4332 #[test]
4335 fn a_byte_of_a_read_only_string_is_the_byte_the_string_spells() {
4336 let text = optimized("static const char s[] = \"abc\";\nint f(void) { return s[1]; }\n");
4337 assert!(text.contains("movl\t$98, %eax"), "{text}");
4338 }
4339
4340 #[test]
4344 fn a_table_that_is_not_read_only_keeps_its_load() {
4345 let text = optimized(
4346 "static int t[4] = {10, 20, 30, 40};\nvoid g(int x) { t[2] = x; }\nint f(void) { return t[2]; }\n",
4347 );
4348 assert!(!text.contains("movl\t$30, %eax"), "{text}");
4349 }
4350
4351 #[test]
4358 fn a_call_guarded_by_a_condition_a_read_only_object_settles_is_not_emitted() {
4359 let text = optimized(
4360 "void link_error(void);\nconst double one = 1.0;\nint main(void) { if ((int) one != 1) link_error(); return 0; }\n",
4361 );
4362 assert!(!text.contains("call\tlink_error"), "{text}");
4363 }
4364
4365 #[test]
4367 fn a_cast_between_a_pointer_and_an_integer_leaves_the_value_where_it_is() {
4368 let text = asm("long f(void *p) { return (long)p; }\n");
4369 for line in text.lines().filter(|line| line.starts_with('\t') && !line.contains('.')) {
4374 let mnemonic = line.split_whitespace().next().unwrap_or("");
4375 assert!(matches!(mnemonic, "movq" | "ret"), "{line} in\n{text}");
4376 }
4377 }
4378
4379 #[test]
4383 fn an_argument_past_the_last_register_is_read_out_of_the_caller_s_stack() {
4384 let six = "long a, long b, long c, long d, long e, long f";
4385 let text = asm(&format!("long f({six}, long g, long h) {{ return g + h; }}\n"));
4386
4387 assert!(text.contains("\tmovq\t8(%rsp), "), "{text}");
4394 assert!(text.contains("\taddq\t16(%rsp), "), "{text}");
4395
4396 let narrow = asm(&format!("int f({six}, int g) {{ return g; }}\n"));
4400 assert!(narrow.contains("\tmovl\t8(%rsp), "), "{narrow}");
4401 let eight =
4402 "double a, double b, double c, double d, double e, double f, double g, double h";
4403 let float = asm(&format!("double f({eight}, double i) {{ return i; }}\n"));
4404 assert!(float.contains("\tmovsd\t8(%rsp), "), "{float}");
4405 }
4406
4407 #[test]
4410 fn a_call_writes_the_arguments_with_no_register_left_at_the_stack_pointer() {
4411 let six = "1, 2, 3, 4, 5, 6";
4412 let decl = "long g(long, long, long, long, long, long, long, long);\n";
4413 let text = asm(&format!("{decl}long f(void) {{ return g({six}, 7, 8); }}\n"));
4414
4415 assert!(text.contains("\tmovq\t%"), "{text}");
4416 assert!(text.contains(", (%rsp)\n"), "{text}");
4417 assert!(text.contains(", 8(%rsp)\n"), "{text}");
4418 assert!(text.contains("\tsubq\t$"), "{text}");
4420
4421 let narrow = "int g(int, int, int, int, int, int, int);\n";
4423 let text = asm(&format!("{narrow}int f(void) {{ return g({six}, 7); }}\n"));
4424 assert!(text.contains("\tmovl\t%"), "{text}");
4425 assert!(text.contains(", (%rsp)\n"), "{text}");
4426 }
4427
4428 #[test]
4431 fn a_variadic_call_counts_registers_and_not_arguments() {
4432 let nine = "1., 2., 3., 4., 5., 6., 7., 8., 9.";
4433 let decl = "int g(int, ...);\n";
4434 let text = asm(&format!("{decl}int f(void) {{ return g(0, {nine}); }}\n"));
4435
4436 assert!(text.contains("\tmovl\t$8, "), "eight registers, not nine: {text}");
4437 assert!(text.contains("\tmovsd\t%"), "{text}");
4438 assert!(text.contains(", (%rsp)\n"), "{text}");
4439 }
4440
4441 #[test]
4446 fn a_variadic_function_writes_the_argument_registers_it_was_handed_into_its_frame() {
4447 let body =
4448 "__builtin_va_list ap; __builtin_va_start(ap, n); __builtin_va_end(ap); return n;";
4449 let text = asm(&format!("int f(int n, ...) {{ {body} }}\n"));
4450
4451 let stores = |mnemonic: &str| text.matches(&format!("\t{mnemonic}\t%")).count();
4454 assert!(text.contains(", 8(%r"), "the second slot, not the first: {text}");
4455 assert!(!text.contains(", 0(%r"), "{text}");
4456 assert_eq!(stores("movaps"), 8, "every vector register: {text}");
4459 assert_eq!(stores("movsd"), 0, "and the whole of each one: {text}");
4460
4461 assert!(text.contains("\tsubq\t$"), "{text}");
4463 }
4464
4465 #[test]
4468 fn va_start_writes_the_four_fields_the_psabi_describes() {
4469 let start = "__builtin_va_list ap; __builtin_va_start(ap, d);";
4470 let params = "int a, int b, int c, double d";
4471 let text = asm(&format!("int f({params}, ...) {{ {start} return a; }}\n"));
4472
4473 assert!(text.contains(" movl $24, "), "{text}");
4477 assert!(text.contains(" movl $64, "), "{text}");
4478 assert!(text.contains(", 8(%r"), "{text}");
4482 assert!(text.contains(", 16(%r"), "{text}");
4483 let frame: u32 = text
4484 .lines()
4485 .find_map(|line| line.trim().strip_prefix("subq $")?.split(',').next()?.parse().ok())
4486 .expect("a variadic function takes a frame for the save area");
4487 let above = |line: &str| {
4488 let at: u32 = line.trim().strip_prefix("leaq ")?.split('(').next()?.parse().ok()?;
4489 Some(at > frame)
4490 };
4491 assert!(text.lines().filter_map(above).any(|it| it), "{frame}: {text}");
4492 }
4493
4494 #[test]
4497 fn va_arg_branches_on_whether_the_argument_is_still_in_the_save_area() {
4498 let read = "__builtin_va_list ap; __builtin_va_start(ap, n);";
4499 let ints = format!("int f(int n, ...) {{ {read} return __builtin_va_arg(ap, int); }}\n");
4500 let text = asm(&ints);
4501
4502 assert!(text.contains("$40, "), "{text}");
4505 assert!(text.contains(" cmpl "), "{text}");
4506 assert!(text.contains(" ja "), "{text}");
4510
4511 let arg = "__builtin_va_arg(ap, double)";
4512 let text = asm(&format!("double f(int n, ...) {{ {read} return {arg}; }}\n"));
4513 assert!(text.contains("$160, "), "the last vector slot: {text}");
4514 }
4515
4516 #[test]
4519 fn a_structure_assignment_is_a_move_for_each_word_of_it() {
4520 let decl = "struct pair { long a, b; };\n";
4521 let body = "struct pair p = *q; return p.a + p.b;";
4522 let text = asm(&format!("{decl}long f(struct pair *q) {{ {body} }}\n"));
4523
4524 assert!(!text.contains("memcpy"), "nothing calls the library: {text}");
4525 assert!(!text.contains("\tcall"), "{text}");
4526 assert!(text.matches("\tmovq\t").count() >= 4, "two words each way: {text}");
4528 }
4529
4530 #[test]
4533 fn how_wide_a_word_of_a_copy_is_follows_the_alignment() {
4534 let decl = "struct bytes { char a[8]; };\n";
4535 let body = "struct bytes p = *q; return p.a[0];";
4536 let text = asm(&format!("{decl}int f(struct bytes *q) {{ {body} }}\n"));
4537
4538 assert!(text.matches("\tmovb\t").count() >= 16, "a byte at a time: {text}");
4540 }
4541
4542 #[test]
4545 fn the_part_of_an_initialiser_that_names_nothing_is_stored_as_zero() {
4546 let decl = "struct wide { long a, b, c; };\n";
4547 let text = asm(&format!("{decl}long f(void) {{ struct wide w = {{ 7 }}; return w.c; }}\n"));
4548
4549 assert!(!text.contains("memset"), "nothing calls the library: {text}");
4550 assert!(text.contains("\tmovq\t$0, ") || text.contains("\txorl\t"), "the zero: {text}");
4555 }
4556
4557 #[test]
4560 fn a_copy_too_large_to_unroll_calls_the_runtime() {
4561 let decl = "struct huge { char a[4096]; };\n";
4562 let mut opts = options();
4563 opts.emit = EmitKind::Asm;
4564 let source = format!("{decl}void f(struct huge *p, struct huge *q) {{ *p = *q; }}\n");
4565 let result = run(&opts, &source);
4566 assert!(!result.failed(), "{:?}", result.messages);
4567 let text = result.text();
4568 assert!(text.contains("call") && text.contains("memcpy"), "{text}");
4569 assert!(text.contains("4096"), "the size travels: {text}");
4572 }
4573
4574 #[test]
4581 fn a_structure_too_large_to_unroll_is_copied_into_the_argument_area_by_the_runtime() {
4582 let decl = "struct huge { char a[4096]; };\nint take(struct huge);\n";
4583 let text = asm(&format!("{decl}int f(struct huge *p) {{ return take(*p); }}\n"));
4584
4585 let copy = text.find("call\tmemcpy").expect("the copy");
4586 let call = text.find("call\ttake").expect("the call");
4587 assert!(copy < call, "the copy comes first: {text}");
4588 assert!(text.contains("movq\t%rsp, %rdi"), "the destination: {text}");
4593 assert!(text.contains("$4096, %edx"), "the size: {text}");
4594 }
4595
4596 #[test]
4599 fn a_realigned_frame_reads_them_through_the_frame_pointer() {
4600 let six = "long a, long b, long c, long d, long e, long f";
4601 let body = "_Alignas(32) long wide[4]; wide[0] = g; return wide[0];";
4602 let text = asm(&format!("long f({six}, long g) {{ {body} }}\n"));
4603
4604 assert!(text.contains("\tandq\t$-32, %rsp"), "{text}");
4608 assert!(text.contains("\tmovq\t16(%rbp), "), "{text}");
4609 assert!(!text.contains("\tmovq\t16(%rsp), "), "{text}");
4610 }
4611
4612 #[test]
4614 fn the_target_decides_how_the_assembly_is_spelled() {
4615 let mut opts = options();
4616 opts.emit = EmitKind::Asm;
4617 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
4618 let text = run(&opts, "int f(void) { return 0; }\n").text().to_owned();
4619 assert!(text.contains("__TEXT,__text"), "{text}");
4620 assert!(text.contains("\n_f:\n"), "{text}");
4621 assert!(!text.contains(".note.GNU-stack"), "{text}");
4622 }
4623
4624 fn obj(source: &str) -> Vec<u8> {
4626 let mut opts = options();
4627 opts.emit = EmitKind::Object;
4628 let result = run(&opts, source);
4629 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4630 match result.artifact {
4631 Artifact::Object { bytes, .. } => bytes,
4632 other => panic!("expected an object, got {other:?}"),
4633 }
4634 }
4635
4636 #[test]
4642 fn a_function_goes_from_c_to_an_object_a_linker_would_take() {
4643 let bytes = obj("int add(int a, int b) { return a + b; }\n");
4644 assert_eq!(&bytes[..4], b"\x7fELF", "an object file starts by saying it is one");
4645 let text = asm("int add(int a, int b) { return a + b; }\n");
4646 assert!(
4647 text.contains("\taddl\t"),
4648 "and the listing of it is the same instructions:\n{text}"
4649 );
4650 }
4651
4652 #[test]
4654 fn a_variable_goes_from_c_to_the_section_it_belongs_in() {
4655 let text = asm("int counter = 42;\nstatic int hidden;\nconst int fixed = 7;\n");
4656 assert!(text.contains("\t.data\n\t.globl\tcounter\n"), "{text}");
4657 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
4658 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
4659 assert!(text.contains("\t.bss\n\t.p2align\t2\n"), "{text}");
4662 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
4663 assert!(!text.contains(".globl\thidden"), "{text}");
4664 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4667 }
4668
4669 #[test]
4676 fn a_bit_field_initializer_writes_every_byte_of_the_value_and_not_only_the_ones_that_are_set() {
4677 let text = asm("struct s { unsigned f : 20; } x = { 0x12300 };\n");
4678 assert!(text.contains("\t.data\n"), "there is something to write: {text}");
4679 assert!(text.contains("\nx:\n\t.ascii\t\"\\000#\\001\"\n"), "and it is the value: {text}");
4680
4681 let text = asm("struct s { unsigned a : 8; unsigned b : 8; } x = { 0, 3 };\n");
4684 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\003\"\n"), "{text}");
4685
4686 let text = asm("struct s { unsigned long long f : 40; } x = { 0x100000 };\n");
4689 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\000\\020\"\n\t.space\t5\n"), "{text}");
4690
4691 let text = asm("struct s { unsigned f : 20; } x = { 0 };\n");
4693 assert!(text.contains("\t.bss\n"), "an object of zeroes is zeroes: {text}");
4694 assert!(text.contains("\nx:\n\t.space\t4\n"), "{text}");
4695 }
4696
4697 #[test]
4699 fn a_string_literal_is_a_variable_with_a_name_no_program_could_write() {
4700 let text = asm("const char *f(void) { return \"hi\"; }\n");
4701 assert!(text.contains("\t.ascii\t\"hi\\000\"\n"), "{text}");
4702 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4703 let label = text
4704 .lines()
4705 .find(|line| line.starts_with(".Lstr"))
4706 .unwrap_or_else(|| panic!("a label for the literal in\n{text}"));
4707 assert!(!text.contains(&format!(".globl\t{}", label.trim_end_matches(':'))), "{text}");
4708 }
4709
4710 #[test]
4712 fn an_address_in_an_initializer_is_left_to_the_linker() {
4713 let source = "int counter;\nint *p = &counter;\n";
4714 let text = asm(source);
4715 assert!(text.contains("\np:\n\t.quad\tcounter\n"), "{text}");
4716 let bytes = obj(source);
4719 assert!(bytes.windows(8).any(|w| w == b"counter\0"), "the object has to name it");
4720 }
4721
4722 #[test]
4731 fn a_constant_holding_an_address_goes_in_the_section_the_loader_may_write_once() {
4732 let text = asm("static void a(void) {}\nstatic void b(void) {}\n\
4735 struct m { void (*x)(void); void (*y)(void); };\n\
4736 const struct m t = { a, b };\n");
4737 assert!(text.contains("\t.section\t.data.rel.ro.local,\"aw\",@progbits\n"), "{text}");
4738 assert!(text.contains("\nt:\n\t.quad\ta\n\t.quad\tb\n"), "{text}");
4739
4740 let text =
4743 asm("void a(void);\nstruct m { void (*x)(void); };\nconst struct m t = { a };\n");
4744 assert!(text.contains("\t.section\t.data.rel.ro,\"aw\",@progbits\n"), "{text}");
4745
4746 let text = asm("const int fixed = 7;\n");
4748 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4749 }
4750
4751 #[test]
4758 fn a_thread_local_variable_is_storage_a_thread_gets_a_copy_of_and_an_offset_into_it() {
4759 let text = asm("_Thread_local int x = 1;\nint read(void) { return x; }\n");
4760 assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
4763 assert!(text.contains("\t.type\tx, @tls_object\n"), "{text}");
4764 assert!(text.contains("x@GOTTPOFF(%rip)"), "{text}");
4767 assert!(text.contains("%fs:0"), "{text}");
4768 }
4769
4770 #[test]
4776 fn the_address_of_this_thread_s_own_storage_is_read_out_of_the_segment_register() {
4777 let text = asm("void *here(void) { return __builtin_thread_pointer(); }\n");
4778 assert!(text.contains("movq\t%fs:0, "), "{text}");
4779 assert!(!text.contains("GOTTPOFF"), "{text}");
4781 }
4782
4783 #[test]
4794 fn a_prefetch_is_one_of_four_instructions_and_the_locality_is_what_picks() {
4795 for (locality, wanted) in
4796 [(0, "prefetchnta"), (1, "prefetcht2"), (2, "prefetcht1"), (3, "prefetcht0")]
4797 {
4798 let source =
4799 format!("void warm(void *p) {{ __builtin_prefetch(p, 0, {locality}); }}\n");
4800 let text = asm(&source);
4801 assert!(text.contains(&format!("\t{wanted}\t")), "locality {locality}: {text}");
4802 }
4803 let text = asm("void warm(void *p) { __builtin_prefetch(p); }\n");
4805 assert!(text.contains("\tprefetcht0\t"), "{text}");
4806 let text = asm("void warm(void *p) { __builtin_prefetch(p, 1); }\n");
4809 assert!(text.contains("\tprefetcht0\t"), "{text}");
4810 assert!(!text.contains("prefetchw"), "{text}");
4811 }
4812
4813 #[test]
4816 fn an_aarch64_prefetch_is_a_prfm_that_says_the_locality_and_whether_it_writes() {
4817 let mut opts = options();
4818 opts.emit = EmitKind::Asm;
4819 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
4820 for (write, kind) in [(0, "pld"), (1, "pst")] {
4821 for (locality, wanted) in [(0, "l1strm"), (1, "l3keep"), (2, "l2keep"), (3, "l1keep")] {
4822 let source = format!(
4823 "void warm(void *p) {{ __builtin_prefetch(p, {write}, {locality}); }}\n"
4824 );
4825 let result = run(&opts, &source);
4826 assert_eq!(result.messages, Vec::<String>::new(), "{source}");
4827 let text = result.text();
4828 assert!(text.contains("prfm"), "{source}{text}");
4829 assert!(text.contains(&format!("{kind}{wanted}, [x0]")), "{source}{text}");
4830 }
4831 }
4832 }
4833
4834 #[test]
4845 fn a_trap_is_the_instruction_the_machine_has_no_meaning_for() {
4846 let text = asm("void stop(void) { __builtin_trap(); }\n");
4847 assert!(text.contains("\tud2\n"), "{text}");
4848 assert!(!text.contains("\tcall"), "a stop is not a call to anything: {text}");
4849
4850 let text = asm("int stop(int a) { __builtin_trap(); return a + 1; }\n");
4851 assert!(text.contains("\tud2\n"), "{text}");
4852 assert!(text.contains("\taddl\t"), "the block goes on after a stop: {text}");
4853 }
4854
4855 #[test]
4859 fn cpu_init_is_a_call_to_the_libgcc_function_that_fills_in_the_model() {
4860 let text = asm("void start(void) { __builtin_cpu_init(); }\n");
4861 assert!(text.contains("\tcall\t__cpu_indicator_init"), "{text}");
4862 assert!(!text.contains("__builtin_cpu_init"), "{text}");
4863 }
4864
4865 #[test]
4873 fn cpu_supports_is_a_bit_of_the_words_libgcc_fills_in() {
4874 let text = asm("int f(void) { return __builtin_cpu_supports(\"sse4.2\"); }\n");
4875 assert!(text.contains("__cpu_model"), "{text}");
4876 assert!(text.contains("12(%"), "the fourth word of the model: {text}");
4877 assert!(text.contains("$256"), "{text}");
4878 assert!(!text.contains("\tcall"), "the answer is a read and not a call: {text}");
4879
4880 let text = asm("int f(void) { return __builtin_cpu_supports(\"vpclmulqdq\"); }\n");
4881 assert!(text.contains("__cpu_features2"), "{text}");
4882 assert!(text.contains("$2,"), "{text}");
4883
4884 let text = asm("int f(void) { return __builtin_cpu_supports(\"xsave\"); }\n");
4885 assert!(text.contains("__cpu_features2"), "{text}");
4886 assert!(text.contains("4(%"), "the second word of the second object: {text}");
4887 assert!(text.contains("$131072"), "{text}");
4888
4889 let text = asm("int f(void) { return __builtin_cpu_supports(\"avx512vbmi2\"); }\n");
4890 assert!(text.contains("set"), "the top bit is answered as a one: {text}");
4891 }
4892
4893 #[test]
4896 fn cpu_is_compares_one_word_of_the_model_with_a_number() {
4897 let text = asm("int f(void) { return __builtin_cpu_is(\"amd\"); }\n");
4898 assert!(text.contains("__cpu_model"), "{text}");
4899 assert!(text.contains("$2,"), "{text}");
4900
4901 let text = asm("int f(void) { return __builtin_cpu_is(\"znver4\"); }\n");
4902 assert!(text.contains("8(%"), "the subtype is the third word: {text}");
4903 assert!(text.contains("$29,"), "{text}");
4904 }
4905
4906 #[test]
4910 fn a_cpu_builtin_takes_a_name_it_knows_written_as_a_literal() {
4911 let mut opts = options();
4912 opts.emit = EmitKind::Ir;
4913 for (source, wanted) in [
4914 (
4915 "int f(const char *s) { return __builtin_cpu_supports(s); }\n",
4916 "parameter to builtin must be a string constant or literal",
4917 ),
4918 (
4919 "int f(void) { return __builtin_cpu_supports(\"sse5\"); }\n",
4920 "parameter to builtin not valid: sse5",
4921 ),
4922 (
4923 "int f(void) { return __builtin_cpu_is(\"sse\"); }\n",
4924 "parameter to builtin not valid: sse",
4925 ),
4926 ] {
4927 let result = run(&opts, source);
4928 assert!(
4929 result.messages.iter().any(|m| m.contains(wanted)),
4930 "{source}{:?}",
4931 result.messages
4932 );
4933 }
4934 let text = asm("int f(void) { return __builtin_cpu_supports((const char *)\"avx2\"); }\n");
4936 assert!(text.contains("$1024"), "{text}");
4937
4938 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
4939 for source in [
4940 "void f(void) { __builtin_cpu_init(); }\n",
4941 "int f(void) { return __builtin_cpu_supports(\"sse4.2\"); }\n",
4942 ] {
4943 let result = run(&opts, source);
4944 assert!(
4945 result.messages.iter().any(|m| m.contains("only available on x86-64")),
4946 "{source}{:?}",
4947 result.messages
4948 );
4949 }
4950 }
4951
4952 #[test]
4964 fn assume_aligned_is_its_first_argument_and_keeps_the_rest() {
4965 let text = asm("void *aligned(char *p) { return __builtin_assume_aligned(p, 16); }\n");
4966 assert!(!text.contains("assume_aligned"), "{text}");
4967 assert!(!text.contains("\tcall"), "nothing is called for an alignment fact: {text}");
4968
4969 let source = "unsigned long width(void);\n\
4970 void *aligned(char *p) { return __builtin_assume_aligned(p, width()); }\n";
4971 let text = asm(source);
4972 assert!(!text.contains("assume_aligned"), "{text}");
4973 assert!(text.contains("width"), "the argument that is not the answer still runs: {text}");
4974 }
4975
4976 #[test]
4986 fn the_frame_address_is_the_frame_pointer_after_walking_that_many_links() {
4987 let text = asm("void *here(void) { return __builtin_frame_address(0); }\n");
4988 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
4989 assert!(text.contains("movq\t%rbp, %rax"), "{text}");
4990 assert!(!text.contains("\tcall"), "a frame address is not a call to anything: {text}");
4991
4992 let walk = |depth: u32| {
4993 let source = format!("void *up(void) {{ return __builtin_frame_address({depth}); }}\n");
4994 asm(&source).matches("movq\t(%r").count()
4995 };
4996 assert_eq!(walk(1), 1, "one link is one load");
4997 assert_eq!(walk(3), 3, "three links are three loads");
4998 }
4999
5000 #[test]
5010 fn the_return_address_is_one_word_above_the_frame_the_walk_ended_at() {
5011 let text = asm("void *back(void) { return __builtin_return_address(0); }\n");
5012 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
5013 assert!(text.contains("movq\t8(%rbp), %rax"), "{text}");
5014 assert!(!text.contains("\tcall"), "a return address is not a call to anything: {text}");
5015
5016 let text = asm("void *back(void) { return __builtin_return_address(2); }\n");
5017 assert_eq!(text.matches("movq\t(%r").count(), 2, "two links are two loads: {text}");
5018 assert!(text.contains("movq\t8(%r"), "and the answer is above the last of them: {text}");
5019 }
5020
5021 #[test]
5032 fn a_depth_that_is_not_a_small_constant_is_refused() {
5033 let mut opts = options();
5034 opts.emit = EmitKind::Ir;
5035 for source in [
5036 "void *up(int n) { return __builtin_return_address(n); }\n",
5037 "void *up(void) { return __builtin_frame_address(1000); }\n",
5038 ] {
5039 let messages = run(&opts, source).messages;
5040 let named = messages.iter().any(|m| m.contains("E0705"));
5041 assert!(named, "expected a refusal in {messages:?}");
5042 }
5043 }
5044
5045 #[test]
5057 fn an_alloca_takes_the_bytes_off_the_stack_pointer_and_answers_where_they_are() {
5058 let text =
5059 asm("void use(void *p); void f(unsigned long n) { use(__builtin_alloca(n)); }\n");
5060 assert!(text.contains("andq\t$-16"), "the size is rounded up to sixteen: {text}");
5061 assert!(text.contains("subq\t%rdi, %rsp"), "and taken off the stack pointer: {text}");
5062 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
5063
5064 let plain = concat!(
5067 "extern void *alloca(__SIZE_TYPE__);\n",
5068 "void use(void *p);\n",
5069 "void f(unsigned long n) { use(alloca(n)); }\n",
5070 );
5071 let text = asm(plain);
5072 assert!(text.contains("subq\t%rdi, %rsp"), "the plain name is the same bytes: {text}");
5073 assert_eq!(text.matches("\tcall").count(), 1, "and is not a call either: {text}");
5074
5075 let own = concat!(
5078 "static void *alloca(unsigned long n) { return 0; }\n",
5079 "void *f(unsigned long n) { return alloca(n); }\n",
5080 );
5081 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
5082 }
5083
5084 #[test]
5099 fn a_builtin_the_program_never_declared_is_the_builtin_rather_than_the_one_c89_wrote_down() {
5100 let mut opts = options();
5104 opts.permissive = true;
5105 let undeclared = "void use(void *p);
5106void f(unsigned long n) { use(alloca(n)); }
5107";
5108 assert_eq!(
5109 run(&opts, undeclared).messages,
5110 [
5111 "/main.c:2:31: warning: implicit declaration of function 'alloca' [E0521]",
5112 "/main.c:2:31: warning: incompatible implicit declaration of built-in function \
5113 'alloca' [E0713]",
5114 ]
5115 );
5116
5117 opts.emit = EmitKind::Asm;
5118 let text = run(&opts, undeclared).text().to_owned();
5119 assert!(text.contains("subq\t%rdi, %rsp"), "the bytes come off the stack: {text}");
5120 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
5121
5122 let string = "unsigned long f(void) { return strlen(\"abc\"); }\n";
5125 let text = run(&opts, string).text().to_owned();
5126 assert!(text.contains("call\tstrlen"), "strlen is still a call: {text}");
5127
5128 let own = concat!(
5131 "static void *alloca(unsigned long n) { return 0; }\n",
5132 "void *f(unsigned long n) { return alloca(n); }\n",
5133 );
5134 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
5135 }
5136
5137 #[test]
5147 fn the_bytes_an_alloca_took_are_still_there_at_the_end_of_the_block_that_took_them() {
5148 let inner = "{ use(__builtin_alloca(n)); }";
5149 for body in [inner.to_owned(), format!("int a[n]; {inner} use(a);")] {
5150 let source = format!("void use(void *p);\nvoid f(unsigned long n) {{ {body} }}\n");
5151 let text = asm(&source);
5152 for line in text.lines().filter(|line| line.trim_end().ends_with(", %rsp")) {
5156 let taking = line.contains("subq");
5157 let leaving = line.contains("%rbp");
5158 assert!(taking || leaving, "nothing puts the stack back: {line} in {text}");
5159 }
5160 }
5161 }
5162
5163 #[test]
5165 fn the_object_and_the_listing_are_two_spellings_of_one_compilation() {
5166 let source = "int callee(void); int g(void) { return callee(); }\n";
5170 let bytes = obj(source);
5171 assert!(
5172 bytes.windows(7).any(|w| w == b"callee\0"),
5173 "the object has to name the callee for the linker to find it"
5174 );
5175 let text = asm(source);
5176 assert!(text.contains("\tcall\tcallee\n"), "{text}");
5177 }
5178
5179 #[test]
5185 fn compiling_for_an_executable_produces_an_object_and_not_a_dump() {
5186 let mut opts = options();
5187 opts.emit = EmitKind::Executable;
5189 let result = run(&opts, "int main(void) { return 0; }\n");
5190 assert_eq!(result.messages, Vec::<String>::new());
5191 match result.artifact {
5192 Artifact::Object { bytes, .. } => assert_eq!(&bytes[..4], b"\x7fELF"),
5193 other => panic!("expected an object, got {other:?}"),
5194 }
5195 }
5196
5197 #[test]
5199 fn a_platform_with_no_object_writer_is_said_so_rather_than_written_as_elf() {
5200 let mut opts = options();
5201 opts.emit = EmitKind::Object;
5202 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
5203 let result = run(&opts, "int f(void) { return 0; }\n");
5204 assert!(result.failed(), "an object nobody can read is worse than a message");
5205 assert!(
5206 result.messages.iter().any(|m| m.contains("no object writer")),
5207 "{:?}",
5208 result.messages
5209 );
5210 }
5211
5212 fn ir(source: &str) -> String {
5214 let mut opts = options();
5215 opts.emit = EmitKind::Ir;
5216 let result = run(&opts, source);
5217 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5218 result.text().to_owned()
5219 }
5220
5221 fn errors(source: &str) -> Vec<String> {
5223 let mut opts = options();
5224 opts.emit = EmitKind::Ir;
5225 let result = run(&opts, source);
5226 assert!(result.failed(), "expected this to be refused:\n{source}");
5227 result.messages
5228 }
5229
5230 fn body(source: &str) -> String {
5232 let text = ir(source);
5233 let (_, rest) = text.split_once("{\n").expect("a function definition");
5234 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
5235 body.to_owned()
5236 }
5237
5238 #[test]
5246 fn gnu89_inline_is_what_decides_whether_a_bare_inline_definition_reaches_the_module() {
5247 let source = "inline int f(int x) { return x + 1; }\n";
5248 let with = |flag: bool| {
5249 let mut opts = options();
5250 opts.emit = EmitKind::Ir;
5251 opts.gnu89_inline = flag;
5252 let result = run(&opts, source);
5253 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5254 result.text().to_owned()
5255 };
5256
5257 assert!(!with(false).contains("block0"), "no body: {}", with(false));
5260
5261 assert!(with(true).contains("block0"), "a body: {}", with(true));
5264 }
5265
5266 #[test]
5273 fn an_access_through_a_type_names_the_type_it_went_through() {
5274 let source = "\
5275struct s { int a; float b; };\n\
5276union u { int i; float f; };\n\
5277int scalar(int *p) { return *p; }\n\
5278float member(struct s *p) { p->a = 1; return p->b; }\n\
5279int element(int *a, long i) { return a[i]; }\n\
5280float through_a_union(union u *p) { p->i = 1; return p->f; }\n";
5281 let text = ir(source);
5282 assert!(text.contains(r#"!0 = tbaa "char""#), "the root: {text}");
5283 assert!(text.contains(r#"tbaa "int", parent !0"#), "int under it: {text}");
5284 assert!(text.contains(r#"tbaa "float", parent !0"#), "float under it: {text}");
5285 let named = text.lines().filter(|line| line.contains(", tbaa !")).count();
5288 assert_eq!(named, 6, "six accesses: {text}");
5289 }
5290
5291 #[test]
5298 fn turning_strict_aliasing_off_leaves_the_type_off_every_access() {
5299 let source = "int punned(float *f, int *i) { *i = 1; *f = 2.0f; return *i; }\n";
5300 let mut opts = options();
5301 opts.emit = EmitKind::Ir;
5302 opts.strict_aliasing = false;
5303 let result = run(&opts, source);
5304 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5305 let text = result.text().to_owned();
5306 assert!(!text.contains("tbaa"), "not even the root: {text}");
5307 }
5308
5309 #[test]
5315 fn instrumenting_functions_calls_the_hooks_around_every_body_but_the_hooks() {
5316 let source = concat!(
5317 "#define NOCHK __attribute__((no_instrument_function))\n",
5318 "void __cyg_profile_func_enter(void *, void *) NOCHK;\n",
5319 "void __cyg_profile_func_exit(void *, void *) NOCHK;\n",
5320 "int calls;\n",
5321 "int pick(int x) { if (x) return 1; return 2; }\n",
5322 "void quiet(void) NOCHK;\n",
5323 "void quiet(void) { calls++; }\n",
5324 "void __cyg_profile_func_enter(void *fn, void *site) { calls++; }\n",
5325 "void __cyg_profile_func_exit(void *fn, void *site) { calls--; }\n",
5326 );
5327 let mut opts = options();
5328 opts.emit = EmitKind::Ir;
5329 opts.instrument_functions = true;
5330 let result = run(&opts, source);
5331 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5332 let text = result.text().to_owned();
5333 let body = |name: &str| -> String {
5334 let open = format!("func @{name}(");
5335 let start = text.find(&open).unwrap_or_else(|| panic!("no {name}: {text}"));
5336 let rest = &text[start..];
5337 rest[..rest.find("\n}").unwrap_or(rest.len())].to_owned()
5338 };
5339 let pick = body("pick");
5340 assert_eq!(pick.matches("call @__cyg_profile_func_enter(").count(), 1, "{pick}");
5341 assert_eq!(pick.matches("call @__cyg_profile_func_exit(").count(), 2, "{pick}");
5342 assert!(pick.contains("return_address"), "{pick}");
5343 assert!(pick.contains("global_addr @pick"), "{pick}");
5344 for quiet in ["quiet", "__cyg_profile_func_enter", "__cyg_profile_func_exit"] {
5345 assert!(!body(quiet).contains("call "), "{quiet} is left alone: {text}");
5346 }
5347
5348 opts.instrument_functions = false;
5349 let result = run(&opts, source);
5350 assert!(!result.text().contains("call @__cyg_profile"), "off unless asked for");
5351 }
5352
5353 #[test]
5359 fn a_call_an_unwind_would_leave_a_cleanup_behind_gets_a_landing_pad_under_exceptions() {
5360 let source = concat!(
5361 "void done(int *p);\n",
5362 "void work(void);\n",
5363 "void calls(void) { int x __attribute__((cleanup(done))) = 1; work(); }\n",
5364 "int quiet(int y) { int x __attribute__((cleanup(done))) = y; return x + 1; }\n",
5365 "void after(void) { { int x __attribute__((cleanup(done))) = 1; } work(); }\n",
5366 );
5367 let mut opts = options();
5368 opts.emit = EmitKind::Ir;
5369 let result = run(&opts, source);
5370 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5371 assert!(!result.text().contains("landing"), "{}", result.text());
5372
5373 opts.exceptions = true;
5374 let result = run(&opts, source);
5375 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5376 let text = result.text();
5377 assert_eq!(text.matches("= landing").count(), 1, "only the call in calls: {text}");
5378 assert!(text.contains("_Unwind_Resume"), "{text}");
5379
5380 opts.emit = EmitKind::Asm;
5381 let result = run(&opts, source);
5382 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5383 let text = result.text();
5384 assert!(text.contains(".cfi_personality 0x9b,DW.ref.__gcc_personality_v0"), "{text}");
5385 assert!(text.contains(".cfi_lsda 0x1b,.LLSDA_calls"), "{text}");
5386 assert!(text.contains(".gcc_except_table"), "{text}");
5387 assert_eq!(text.matches(".cfi_lsda").count(), 1, "{text}");
5388
5389 opts.emit = EmitKind::Object;
5390 let result = run(&opts, source);
5391 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5392 let bytes = result.artifact.bytes();
5393 let has = |what: &[u8]| bytes.windows(what.len()).any(|window| window == what);
5394 assert!(has(b".gcc_except_table\0"), "the call site table has a section");
5395 assert!(has(b"zPLR\0"), "a header naming the personality routine");
5396 assert!(has(b"zR\0"), "and the plain one for the functions with no pad");
5397 assert!(has(b"DW.ref.__gcc_personality_v0\0"), "the pointer the header reads through");
5398
5399 opts.emit = EmitKind::Ir;
5400 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
5401 let result = run(&opts, source);
5402 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
5403 assert!(result.messages[0].contains("landing pad"), "{:?}", result.messages);
5404 assert!(result.messages[0].contains(":3:"), "the call in calls: {:?}", result.messages);
5405 }
5406
5407 #[test]
5413 fn an_asm_at_file_scope_with_an_instruction_in_it_is_assembled() {
5414 let source = concat!(
5415 "extern void vide(void);\n",
5416 "__asm__(\".text;.globl _us;_us:;movl $0x1234ABCD, %eax;ret\");\n",
5417 "__asm__(\"vide: ret\");\n",
5418 "unsigned short _us(void);\n",
5419 "int main(void) { vide(); return _us() == 0xABCD ? 0 : 1; }\n",
5420 );
5421 let mut opts = options();
5422 opts.emit = EmitKind::Ir;
5423 let result = run(&opts, source);
5424 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5425 assert_eq!(result.text().matches("module asm ").count(), 2, "{}", result.text());
5426
5427 opts.emit = EmitKind::Asm;
5428 let result = run(&opts, source);
5429 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5430 let text = result.text();
5431 assert!(text.contains("#APP\nvide: ret\n#NO_APP\n"), "{text}");
5432 let main = text.find("main:").expect("main");
5433 assert!(text.find("#NO_APP").expect("the markers") < main, "templates first: {text}");
5434
5435 opts.emit = EmitKind::Object;
5436 let result = run(&opts, source);
5437 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5438 let (bytes, defines) = match result.artifact {
5439 Artifact::Object { bytes, defines } => (bytes, defines),
5440 other => panic!("expected an object, got {other:?}"),
5441 };
5442 assert!(defines.iter().any(|name| name == "_us"), "{defines:?}");
5443 let us = [0xb8, 0xcd, 0xab, 0x34, 0x12, 0xc3];
5445 assert!(bytes.windows(us.len()).any(|window| window == us), "the template's bytes");
5446
5447 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
5449 opts.emit = EmitKind::Ir;
5450 let result = run(&opts, source);
5451 assert!(!result.messages.is_empty(), "refused on Mach-O");
5452 assert!(result.messages[0].contains("the instruction 'movl'"), "{:?}", result.messages);
5453 }
5454
5455 #[test]
5463 fn a_bare_return_from_a_function_that_promised_a_value_gives_back_a_zero() {
5464 let mut opts = options();
5465 opts.emit = EmitKind::Ir;
5466 opts.std = Std::C89;
5467 let compiled = |source: &str| {
5468 let result = run(&opts, source);
5469 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
5470 result.text().to_owned()
5471 };
5472
5473 let text = compiled("int f(int x) { if (x) return; return 3; }\n");
5474 assert!(text.contains("iconst.i32 0\n return"), "zero goes back: {text}");
5475 assert!(!text.contains("unreachable"), "the branch that reached it is kept: {text}");
5476
5477 let text = compiled("double f(int x) { if (x) return; return 1.0; }\n");
5479 assert!(text.contains("fconst.f64 0x0\n return"), "a float zero goes back: {text}");
5480 }
5481
5482 #[test]
5490 fn a_call_to_a_name_nothing_declared_declares_it_as_c89_said_to() {
5491 let mut opts = options();
5492 opts.emit = EmitKind::Ir;
5493 opts.std = Std::C89;
5494 let compiled = |source: &str| {
5495 let result = run(&opts, source);
5496 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
5497 result.text().to_owned()
5498 };
5499
5500 let text = compiled("int f(void) { return g(); }\n");
5502 assert!(text.contains("call @g"), "the call is to the name that was written: {text}");
5503 assert!(text.contains("i32"), "and it gives back an int: {text}");
5504
5505 let text = compiled("int f(char c) { return g(c); }\n");
5508 assert!(text.contains("sext.i32"), "the argument is promoted: {text}");
5509
5510 let mut opts = options();
5513 opts.std = Std::C89;
5514 let said = run(&opts, "int f(void) { return h; }\n").messages.join("\n");
5515 assert!(said.contains("'h' undeclared"), "not a call, so not declared: {said}");
5516 }
5517
5518 #[test]
5528 fn a_name_called_before_it_is_defined_still_gets_its_definition() {
5529 let mut opts = options();
5530 opts.emit = EmitKind::Ir;
5531 opts.std = Std::C89;
5532 let text = run(&opts, "int f(void) { return dummy(); }\ndummy () { return 7; }\n")
5533 .text()
5534 .to_owned();
5535 assert!(text.contains("func @f()"), "the caller is there: {text}");
5536 assert!(text.contains("func @dummy"), "and so is what it calls: {text}");
5537 assert!(text.contains("iconst.i32 7"), "with the body it was given: {text}");
5538 }
5539
5540 #[test]
5548 fn an_old_style_parameter_is_converted_from_what_the_call_promoted_it_to() {
5549 let mut opts = options();
5550 opts.emit = EmitKind::Ir;
5551 opts.std = Std::C89;
5552 let compiled = |source: &str| run(&opts, source).text().to_owned();
5553
5554 let text = compiled("f (c) unsigned char c; { return c; }\n");
5555 assert!(text.contains("func @f(i32"), "an int arrives: {text}");
5556 assert!(text.contains("trunc.i8"), "and is cut down to what was declared: {text}");
5557 assert!(text.contains("zext.i32"), "then read back unsigned: {text}");
5558
5559 let text = compiled("f (s) short s; { return s; }\n");
5561 assert!(text.contains("trunc.i16"), "cut down: {text}");
5562 assert!(text.contains("sext.i32"), "and read back signed: {text}");
5563
5564 let text = compiled("f (x) float x; { return x * 2; }\n");
5567 assert!(text.contains("func @f(f64"), "a double arrives: {text}");
5568 assert!(text.contains("fptrunc.f32"), "and is narrowed to the float: {text}");
5569
5570 let text = compiled("int f(unsigned char c) { return c; }\n");
5573 assert!(text.contains("func @f(i8)"), "the declared type arrives: {text}");
5574 assert!(!text.contains("trunc"), "so there is nothing to cut down: {text}");
5575 }
5576
5577 #[test]
5586 fn the_rules_gcc_promoted_are_decided_by_the_dialect_and_by_fpermissive() {
5587 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
5589 let cases = [
5590 ("static counted;\n", ["", "error", "warning", "error"]),
5591 ("int f(void) { return g(); }\n", ["", "error", "warning", "error"]),
5592 ("int f(x) { return x; }\n", ["", "error", "warning", "error"]),
5593 ("int *p;\nvoid h(void) { p = 1; }\n", ["warning", "error", "warning", "error"]),
5594 (
5595 "char *q;\nint *r;\nvoid k(void) { r = q; }\n",
5596 ["warning", "error", "warning", "error"],
5597 ),
5598 ("int f(void) { return; }\n", ["", "error", "warning", "error"]),
5599 ("void g(void) { return 1; }\n", ["warning", "error", "warning", "error"]),
5600 ];
5601
5602 for (source, wanted) in cases {
5603 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
5604 let mut opts = options();
5605 opts.std = std;
5606 opts.permissive = permissive;
5607 let said = run(&opts, source).messages.join("\n");
5608 let severity = if said.contains(": error: ") {
5609 "error"
5610 } else if said.contains(": warning: ") {
5611 "warning"
5612 } else {
5613 ""
5614 };
5615 let how = if permissive { " -fpermissive" } else { "" };
5616 assert_eq!(
5617 severity,
5618 wanted,
5619 "under -std={}{how}, {source} was answered with `{said}`",
5620 std.as_str()
5621 );
5622 if wanted.is_empty() {
5623 assert!(said.is_empty(), "nothing to say, but said `{said}`");
5624 }
5625 }
5626 }
5627 }
5628
5629 #[test]
5638 fn the_three_variadic_builtins_answer_a_bad_list_the_way_a_call_answers_a_bad_argument() {
5639 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
5640 let cases = [
5641 (
5642 "int f(int n, ...) { char *p; return __builtin_va_arg(p, int); }\n",
5643 "first argument to 'va_arg' not of type 'va_list'",
5644 ["error", "error", "error", "error"],
5645 ),
5646 (
5647 "void f(int n, ...) { char *p; __builtin_va_start(p, n); }\n",
5648 "passing argument 1 of '__builtin_va_start' from incompatible pointer type",
5649 ["warning", "error", "warning", "error"],
5650 ),
5651 (
5652 "void f(int n, ...) { int x; __builtin_va_end(x); }\n",
5653 "passing argument 1 of '__builtin_va_end' makes pointer from integer without a \
5654 cast",
5655 ["warning", "error", "warning", "error"],
5656 ),
5657 (
5658 "void f(int n, ...) { __builtin_va_list a; char *p; __builtin_va_copy(a, p); }\n",
5659 "passing argument 2 of '__builtin_va_copy' from incompatible pointer type",
5660 ["warning", "error", "warning", "error"],
5661 ),
5662 ];
5663
5664 for (source, message, wanted) in cases {
5665 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
5666 let mut opts = options();
5667 opts.std = std;
5668 opts.permissive = permissive;
5669 let said = run(&opts, source).messages.join("\n");
5670 let how = if permissive { " -fpermissive" } else { "" };
5671 assert!(
5672 said.contains(&format!(": {wanted}: {message}")),
5673 "under -std={}{how}, {source} was answered with `{said}`",
5674 std.as_str()
5675 );
5676 }
5677 }
5678 }
5679
5680 fn safe_ir(tier: rucc_session::Safety, source: &str) -> String {
5682 let mut opts = options();
5683 opts.emit = EmitKind::Ir;
5684 opts.safety = tier;
5685 let result = run(&opts, source);
5686 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5687 result.text().to_owned()
5688 }
5689
5690 const READS_THROUGH_A_POINTER: &str = "int read(int *p) { return p[1]; }\n";
5691
5692 fn padded_ir(padding: Padding, source: &str) -> String {
5694 let mut opts = options();
5695 opts.emit = EmitKind::Ir;
5696 opts.safety = rucc_session::Safety::Detect;
5697 opts.padding = padding;
5698 let result = run(&opts, source);
5699 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5700 result.text().to_owned()
5701 }
5702
5703 const FILLS_A_RECORD_A_MEMBER_AT_A_TIME: &str = "struct padded { char tag; int value; };\n\
5704 void fill(struct padded *p) { p->tag = 1; p->value = 2; }\n";
5705
5706 #[test]
5707 fn a_record_filled_a_member_at_a_time_comes_out_whole_when_padding_does_not_participate() {
5708 let text = padded_ir(Padding::Ignored, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
5712 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
5713 }
5714
5715 #[test]
5716 fn a_store_says_only_what_it_wrote_when_padding_does_participate() {
5717 let text = padded_ir(Padding::Tracked, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
5720 assert!(!text.contains("owns"), "{text}");
5721 }
5722
5723 #[test]
5724 fn a_member_of_a_union_owns_nothing_after_it() {
5725 let text = padded_ir(
5729 Padding::Ignored,
5730 "union u { char tag; long wide; };\nvoid fill(union u *p) { p->tag = 1; }\n",
5731 );
5732 assert!(!text.contains("owns"), "{text}");
5733 }
5734
5735 #[test]
5736 fn an_inner_records_trailing_padding_reaches_the_outer_records() {
5737 let text = padded_ir(
5742 Padding::Ignored,
5743 "struct inner { char c; };\n\
5744 struct outer { struct inner in; int x; };\n\
5745 void fill(struct outer *p) { p->in.c = 1; p->x = 2; }\n",
5746 );
5747 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
5748 }
5749
5750 #[test]
5751 fn a_build_that_did_not_ask_for_the_monitor_is_compiled_the_way_it_always_was() {
5752 let text = ir(READS_THROUGH_A_POINTER);
5756 assert!(!text.contains("check_"), "{text}");
5757 assert!(!text.contains("cap_of"), "{text}");
5758 }
5759
5760 #[test]
5761 fn asking_for_a_tier_puts_the_checks_in_before_the_optimizer_sees_them() {
5762 let text = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5763 assert!(text.contains("cap_of"), "{text}");
5764 assert!(text.contains("check_bounds"), "{text}");
5765 assert!(text.contains("check_live"), "{text}");
5766 assert!(text.contains("check_deriv"), "{text}");
5768 assert!(text.contains("check_type"), "{text}");
5770 }
5771
5772 #[test]
5773 fn the_three_tiers_that_are_not_off_all_check_the_same_accesses_so_far() {
5774 let detect = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5778 for tier in [rucc_session::Safety::Enforce, rucc_session::Safety::Kernel] {
5779 assert_eq!(safe_ir(tier, READS_THROUGH_A_POINTER), detect, "{tier}");
5780 }
5781 }
5782
5783 fn summary(tier: rucc_session::Safety, source: &str) -> String {
5785 let mut opts = options();
5786 opts.emit = EmitKind::SafetySummary;
5787 opts.safety = tier;
5788 let result = run(&opts, source);
5789 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5790 result.text().to_owned()
5791 }
5792
5793 #[test]
5794 fn the_summary_counts_the_checks_that_went_in_and_the_ones_still_standing() {
5795 let text = summary(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5796 assert!(text.contains("\"tier\": \"detect\""), "{text}");
5797 assert!(
5799 text.contains("\"bounds\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"),
5800 "{text}"
5801 );
5802 assert!(
5803 text.contains(
5804 "\"derivation\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"
5805 ),
5806 "{text}"
5807 );
5808 }
5809
5810 #[test]
5811 fn a_build_without_the_monitor_summarises_as_a_build_with_no_checks_in_it() {
5812 let text = summary(rucc_session::Safety::Off, READS_THROUGH_A_POINTER);
5816 assert!(text.contains("\"tier\": \"off\""), "{text}");
5817 assert!(
5818 text.contains("\"bounds\": { \"emitted\": 0, \"remaining\": 0, \"discharged\": 0 }"),
5819 "{text}"
5820 );
5821 }
5822
5823 #[test]
5824 fn a_call_the_boundary_models_is_counted_apart_from_one_it_does_not() {
5825 let text = summary(
5826 rucc_session::Safety::Detect,
5827 "void *memcpy(void *, const void *, unsigned long);\n\
5828 int puts(const char *);\n\
5829 void f(char *d, char *s) { memcpy(d, s, 4); puts(d); }\n",
5830 );
5831 assert!(text.contains("\"interposed\": 1"), "{text}");
5832 assert!(text.contains("\"puts\""), "{text}");
5833 assert!(!text.contains("__rucc_wrap_memcpy\""), "{text}");
5837 }
5838
5839 #[test]
5840 fn an_address_taken_of_a_library_function_is_counted_the_way_a_call_to_one_is() {
5841 let text = summary(
5846 rucc_session::Safety::Detect,
5847 "void *memcpy(void *, const void *, unsigned long);\n\
5848 int puts(const char *);\n\
5849 void *table[2] = { (void *)memcpy, (void *)puts };\n\
5850 void *f(int i) { return table[i]; }\n",
5851 );
5852 assert!(text.contains("\"interposed\": 1"), "{text}");
5853 assert!(text.contains("\"puts\""), "{text}");
5854 assert!(!text.contains("\"memcpy\""), "{text}");
5855 }
5856
5857 #[test]
5858 fn the_two_directions_a_pointer_crosses_the_boundary_are_counted_apart() {
5859 let text = summary(
5863 rucc_session::Safety::Detect,
5864 "void *notes_open(void);\n\
5865 char *f(char *p) { char *q = notes_open(); return q ? q : p; }\n",
5866 );
5867 assert!(text.contains("\"crossings\": { \"entered\": 1, \"returned\": 1 }"), "{text}");
5868 assert!(text.contains("\"notes_open\""), "{text}");
5869 }
5870
5871 #[test]
5872 fn a_static_function_nobody_takes_the_address_of_is_not_a_crossing() {
5873 let text = summary(
5876 rucc_session::Safety::Detect,
5877 "static int len(const char *p) { return p ? 1 : 0; }\n\
5878 int f(void) { return len(\"x\"); }\n",
5879 );
5880 assert!(text.contains("\"crossings\": { \"entered\": 0, \"returned\": 0 }"), "{text}");
5881 }
5882
5883 fn granules(source: &str) -> String {
5885 let mut opts = options();
5886 opts.emit = EmitKind::TypeGranules;
5887 let result = run(&opts, source);
5888 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5889 result.text().to_owned()
5890 }
5891
5892 #[test]
5893 fn the_granule_report_names_every_record_and_both_keyings() {
5894 let text = granules(
5895 "struct hot { char *p; int a; int b; };\n\
5896 int f(struct hot *h) { return h->a; }\n",
5897 );
5898 assert!(text.contains("struct hot"), "{text}");
5899 assert!(text.contains("every type distinct"), "{text}");
5902 assert!(text.contains("every pointer one type"), "{text}");
5903 assert!(text.contains("budget"), "{text}");
5904 }
5905
5906 #[test]
5907 fn a_record_nothing_uses_is_still_measured() {
5908 let text = granules("struct unused { long a; double b; };\nint f(void) { return 0; }\n");
5911 assert!(text.contains("struct unused"), "{text}");
5912 }
5913
5914 #[test]
5915 fn the_granule_report_stops_before_anything_is_lowered() {
5916 let text = granules(
5920 "struct wide { long double d; };\n\
5921 long double f(long double x) { return x * x; }\n",
5922 );
5923 assert!(text.contains("struct wide"), "{text}");
5924 }
5925
5926 #[test]
5927 fn a_witness_reaches_the_assembler_as_a_call_to_the_runtime() {
5928 let text = safe_asm(rucc_session::Safety::Detect, "char *f(char *p) { return p; }\n");
5931 assert!(text.contains("\tcall\t__rucc_cap_witness\n"), "{text}");
5932 }
5933
5934 #[test]
5935 fn a_pointer_turned_into_an_integer_is_on_the_trust_set() {
5936 let text = summary(
5937 rucc_session::Safety::Detect,
5938 "unsigned long f(int *p) { return (unsigned long) p; }\n",
5939 );
5940 assert!(text.contains("\"exposed\": 1"), "{text}");
5941 }
5942
5943 fn safe_asm(tier: rucc_session::Safety, source: &str) -> String {
5945 let mut opts = options();
5946 opts.emit = EmitKind::Asm;
5947 opts.safety = tier;
5948 let result = run(&opts, source);
5949 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5950 result.text().to_owned()
5951 }
5952
5953 #[test]
5954 fn a_check_reaches_the_assembler_as_a_call_to_the_runtime() {
5955 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5956 assert!(text.contains("\tcall\t__rucc_check_bounds\n"), "{text}");
5957 assert!(text.contains("\tcall\t__rucc_check_live\n"), "{text}");
5958 assert!(text.contains("\tcall\t__rucc_check_deriv\n"), "{text}");
5959 assert!(text.contains("\tcall\t__rucc_check_typed_init\n"), "{text}");
5962 }
5963
5964 #[test]
5965 fn every_check_that_reached_the_assembler_has_a_row_describing_it() {
5966 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5972 let section = format!("\t.section\t{},", rucc_safety::SECTION);
5973 assert_eq!(text.matches(§ion).count(), 4, "{text}");
5974 for index in 0..4 {
5975 let name = format!("__rucc_safety_desc_{index}");
5976 assert!(text.contains(&format!("{name}:\n")), "{text}");
5979 assert!(text.contains(&format!("{name}(%rip)")), "{text}");
5980 }
5981 assert!(!text.contains("__rucc_safety_desc_4"), "{text}");
5982 }
5983
5984 #[test]
5992 fn builtin_constant_p_is_folded_where_it_is_written_rather_than_called() {
5993 let text = ir(concat!(
5994 "int g;\n",
5995 "int a = __builtin_constant_p(1);\n",
5996 "int b = __builtin_constant_p(g);\n",
5997 "int c = __builtin_constant_p(\"abc\");\n",
5998 "int d = __builtin_constant_p(&g);\n",
5999 "int e = __builtin_constant_p(1.5);\n",
6000 "int h = __builtin_choose_expr(__builtin_constant_p(3), 11, 22);\n",
6001 ));
6002 assert!(text.contains("global @a : i32 = 1,"), "{text}");
6003 assert!(text.contains("global @b : i32 = 0,"), "{text}");
6004 assert!(text.contains("global @c : i32 = 1,"), "{text}");
6005 assert!(text.contains("global @d : i32 = 0,"), "{text}");
6006 assert!(text.contains("global @e : i32 = 1,"), "{text}");
6007 assert!(text.contains("global @h : i32 = 11,"), "{text}");
6008 assert!(!text.contains("__builtin_constant_p"), "it is not a call to anything:\n{text}");
6009
6010 let text = body("int f(void) { int i = 0; __builtin_constant_p(i++); return i; }\n");
6014 assert_eq!(text, "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 0\n return %0\n");
6015 }
6016
6017 #[test]
6026 fn a_call_to_a_library_builtin_reaches_the_library_function() {
6027 let text = body("void f(void) { __builtin_abort(); }\n");
6028 assert_eq!(text, "block0:\n call @abort() : ()\n return\n");
6029
6030 let text = ir("int f(const char *s) { return __builtin_puts(s) + __builtin_strlen(s); }\n");
6033 assert!(text.contains("call @puts(%0) : (ptr) -> i32"), "{text}");
6034 assert!(text.contains("call @strlen(%0) : (ptr) -> i64"), "{text}");
6035 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
6036 }
6037
6038 #[test]
6051 fn a_chk_builtin_reaches_the_checking_function_and_keeps_the_size() {
6052 let text = ir(concat!(
6053 "char d[8];\n",
6054 "void f(const char *s, unsigned long n) {\n",
6055 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
6056 " __builtin___strcpy_chk(d, s, __builtin_object_size(d, 1));\n",
6057 " __builtin___memset_chk(d, 0, n, 8);\n",
6058 "}\n",
6059 ));
6060 assert!(text.contains("call @__memcpy_chk("), "{text}");
6061 assert!(text.contains("call @__strcpy_chk("), "{text}");
6062 assert!(text.contains("call @__memset_chk("), "{text}");
6063 assert!(text.contains("iconst.i64 8"), "the object size reaches the call: {text}");
6064 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
6065 }
6066
6067 #[test]
6075 fn a_checking_call_whose_size_says_nothing_is_known_is_the_plain_library_call() {
6076 let text = ir(concat!(
6077 "extern char *p;\n",
6078 "char d[8];\n",
6079 "void f(const char *s, unsigned long n) {\n",
6080 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
6081 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
6082 " __builtin___strcpy_chk(p, s, __builtin_object_size(p, 0));\n",
6083 " __builtin___stpncpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
6084 " __builtin___sprintf_chk(p, 1, __builtin_object_size(p, 0), s);\n",
6085 "}\n",
6086 ));
6087
6088 assert!(
6090 text.contains("call @__memcpy_chk(%2, %0, %1, %3) : (ptr, ptr, i64, i64)"),
6091 "{text}"
6092 );
6093
6094 assert!(text.contains("call @memcpy(%6, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
6097 assert!(text.contains("call @strcpy(%10, %0) : (ptr, ptr) -> ptr"), "{text}");
6098 assert!(text.contains("call @stpncpy(%14, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
6099
6100 assert!(text.contains("call @__sprintf_chk("), "{text}");
6103
6104 let asm = asm(concat!(
6107 "void f(char *p, const char *s, unsigned long n) {\n",
6108 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
6109 "}\n",
6110 ));
6111 assert!(asm.contains("call\tmemcpy"), "{asm}");
6112 assert!(!asm.contains("$-1"), "the size that went away leaves no instruction:\n{asm}");
6113 }
6114
6115 #[test]
6123 fn the_v_spellings_of_the_chk_family_take_the_list_a_va_list_parameter_holds() {
6124 let text = ir(concat!(
6125 "char d[64];\n",
6126 "int f(const char *fmt, ...) {\n",
6127 " __builtin_va_list ap;\n",
6128 " __builtin_va_start(ap, fmt);\n",
6129 " int n = __builtin___vsprintf_chk(d, 1, __builtin_object_size(d, 0), fmt, ap);\n",
6130 " __builtin_va_end(ap);\n",
6131 " return n;\n",
6132 "}\n",
6133 ));
6134 assert!(text.contains("call @__vsprintf_chk("), "{text}");
6135 assert!(text.contains("iconst.i64 64"), "the object size reaches the call: {text}");
6136 }
6137
6138 #[test]
6149 fn the_absolute_value_family_is_the_magnitude_and_not_a_call() {
6150 let text = body(concat!(
6151 "long long llabs(long long);\n",
6152 "long long f(long long x) { return llabs(x); }\n",
6153 ));
6154 assert!(text.contains("%1 = iconst.i64 63"), "{text}");
6155 assert!(text.contains("%2 = ashr %0, %1"), "{text}");
6156 assert!(text.contains("%3 = xor %0, %2"), "{text}");
6157 assert!(text.contains("%4 = sub %3, %2"), "{text}");
6158 assert!(!text.contains("call"), "the call does not happen:\n{text}");
6159
6160 let text = body("int abs(int);\nint f(int x) { return abs(x); }\n");
6163 assert!(text.contains("iconst.i32 31"), "{text}");
6164 let text = body("long labs(long);\nlong f(long x) { return labs(x); }\n");
6165 assert!(text.contains("iconst.i64 63"), "{text}");
6166
6167 let text = body("long long f(long long x) { return __builtin_llabs(x); }\n");
6170 assert!(!text.contains("call"), "{text}");
6171
6172 let text = ir(concat!(
6174 "long long llabs(long long b);\n",
6175 "long long g(long long x) { return llabs(x); }\n",
6176 "long long llabs(long long b) { return 7; }\n",
6177 ));
6178 assert!(!text.contains("call @llabs"), "{text}");
6179 }
6180
6181 #[test]
6188 fn a_byte_swap_is_arithmetic_and_not_a_call() {
6189 let text = body("unsigned f(unsigned x) { return __builtin_bswap32(x); }\n");
6190 assert_eq!(text, "block0(%0: i32):\n %1 = bswap %0\n return %1\n");
6191
6192 let text = body("unsigned f(unsigned char c) { return __builtin_bswap32(c); }\n");
6195 assert!(text.contains("zext.i32 %0"), "widened first: {text}");
6196 assert!(text.contains("bswap %1"), "and swapped at four bytes: {text}");
6197 }
6198
6199 #[test]
6205 fn the_byte_swaps_reverse_at_the_width_their_name_says() {
6206 for (name, ty, width) in [
6207 ("__builtin_bswap16", "unsigned short", "i16"),
6208 ("__builtin_bswap32", "unsigned", "i32"),
6209 ("__builtin_bswap64", "unsigned long long", "i64"),
6210 ] {
6211 let source = format!("{ty} f({ty} x) {{ return {name}(x); }}\n");
6212 let text = body(&source);
6213 assert_eq!(
6214 text,
6215 format!("block0(%0: {width}):\n %1 = bswap %0\n return %1\n"),
6216 "{name}"
6217 );
6218 }
6219 }
6220
6221 #[test]
6228 fn the_bit_counts_are_instructions_and_not_calls() {
6229 let text = body("int f(unsigned x) { return __builtin_clz(x); }\n");
6230 assert_eq!(text, "block0(%0: i32):\n %1 = ctlz %0\n return %1\n");
6231
6232 let text = body("int f(unsigned x) { return __builtin_ctz(x); }\n");
6233 assert_eq!(text, "block0(%0: i32):\n %1 = cttz %0\n return %1\n");
6234
6235 let text = body("int f(unsigned x) { return __builtin_popcount(x); }\n");
6236 assert_eq!(text, "block0(%0: i32):\n %1 = ctpop %0\n return %1\n");
6237 }
6238
6239 #[test]
6248 fn the_bit_counts_ask_about_the_width_their_name_says() {
6249 let text = body("int f(unsigned long long x) { return __builtin_clzll(x); }\n");
6250 assert!(text.starts_with("block0(%0: i64):"), "counted at eight bytes: {text}");
6251 assert!(text.contains("%1 = ctlz %0"), "{text}");
6252 assert!(text.contains("trunc.i32 %1"), "and answered in an int: {text}");
6253
6254 let text = body("int f(unsigned long long x) { return __builtin_clz(x); }\n");
6257 assert!(text.contains("trunc.i32 %0"), "narrowed to what was asked about: {text}");
6258 assert!(text.contains("ctlz %1"), "and counted there: {text}");
6259
6260 let text = body("int f(unsigned long x) { return __builtin_popcountl(x); }\n");
6261 assert!(text.contains("%1 = ctpop %0"), "{text}");
6262 assert!(!text.contains("call"), "{text}");
6263 }
6264
6265 #[test]
6270 fn a_parity_is_the_low_bit_of_the_set_bit_count() {
6271 let text = body("int f(unsigned x) { return __builtin_parity(x); }\n");
6272 assert!(text.contains("%1 = ctpop %0"), "{text}");
6273 assert!(text.contains("iconst.i32 1"), "{text}");
6274 assert!(text.contains("and %1, %2"), "the low bit of it: {text}");
6275 }
6276
6277 #[test]
6283 fn the_first_set_bit_is_one_based_and_zero_for_a_zero() {
6284 let text = body("int f(int x) { return __builtin_ffs(x); }\n");
6285 assert!(text.contains("%1 = cttz %0"), "{text}");
6286 assert!(text.contains("%4 = add %1, %2"), "one more than the count: {text}");
6287 assert!(text.contains("%5 = icmp ne %0, %3"), "whether there was a bit at all: {text}");
6288 assert!(text.contains("%7 = sub %3, %6"), "spread to a mask: {text}");
6289 assert!(text.contains("%8 = and %4, %7"), "and kept only then: {text}");
6290 assert!(!text.contains("br_if"), "no branch: {text}");
6291 }
6292
6293 #[test]
6303 fn the_redundant_sign_bit_count_is_instructions_and_not_a_call() {
6304 let text = body("int f(int x) { return __builtin_clrsb(x); }\n");
6305 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
6306 assert!(text.contains("%2 = ashr %0, %1"), "the sign over every bit: {text}");
6307 assert!(text.contains("%3 = xor %0, %2"), "folded onto it: {text}");
6308 assert!(text.contains("%5 = shl %3, %4"), "one less than the count: {text}");
6309 assert!(text.contains("%6 = or %5, %4"), "with something to count at zero: {text}");
6310 assert!(text.contains("%7 = ctlz %6"), "{text}");
6311 assert!(!text.contains("call"), "{text}");
6312 assert!(!text.contains("br_if"), "no branch: {text}");
6313 }
6314
6315 #[test]
6321 fn the_unsigned_absolute_value_family_answers_in_the_unsigned_type() {
6322 let text = body("unsigned f(int x) { return __builtin_uabs(x); }\n");
6323 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
6324 assert!(text.contains("%4 = sub %3, %2"), "{text}");
6325 assert!(!text.contains("call"), "nothing declares uabs, so a call would not link: {text}");
6326
6327 let text = body("unsigned long long f(long long x) { return __builtin_ullabs(x); }\n");
6328 assert!(text.contains("iconst.i64 63"), "at the width the name says: {text}");
6329
6330 let text = body("int f(int x) { return __builtin_uabs(x) > 2147483647u; }\n");
6333 assert!(text.contains("icmp ugt"), "compared unsigned: {text}");
6334 }
6335
6336 #[test]
6344 fn the_widest_absolute_value_is_whichever_type_the_target_makes_intmax_t() {
6345 let text = body("long f(long x) { return __builtin_imaxabs(x); }\n");
6346 assert!(text.contains("iconst.i64 63"), "{text}");
6347 assert!(text.contains("%4 = sub %3, %2"), "{text}");
6348 assert!(!text.contains("call"), "{text}");
6349
6350 let text = body("unsigned long f(long x) { return __builtin_umaxabs(x); }\n");
6351 assert!(text.contains("iconst.i64 63"), "{text}");
6352 assert!(!text.contains("call"), "{text}");
6353 }
6354
6355 #[test]
6363 fn an_overflow_predicate_writes_nothing_and_answers_the_bit_the_check_would() {
6364 let text =
6365 body("int f(int a, int b) { return __builtin_add_overflow_p(a, b, (int) 0); }\n");
6366 assert!(text.contains("%2, %3 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
6367 assert!(!text.contains("store"), "nothing is written: {text}");
6368 assert!(!text.contains("call"), "{text}");
6369
6370 let text =
6373 body("int f(int a, int b) { return __builtin_mul_overflow_p(a, b, (long long) 0); }\n");
6374 assert!(text.contains("smul_overflow.(i64, i1)"), "{text}");
6375 assert!(!text.contains("store"), "{text}");
6376
6377 let text = body(concat!(
6380 "int g(void);\n",
6381 "int f(int a, int b) { return __builtin_sub_overflow_p(a, b, g()); }\n",
6382 ));
6383 assert!(!text.contains("call @g"), "the third argument is not evaluated: {text}");
6384 }
6385
6386 #[test]
6396 fn an_overflow_check_is_arithmetic_and_not_a_call() {
6397 let text =
6398 body("int f(int a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
6399 assert!(text.contains("%3, %4 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
6400 assert!(text.contains("store %3 -> %2"), "{text}");
6401 assert!(!text.contains("call"), "{text}");
6402
6403 let text =
6404 body("int f(int a, int b, int *r) { return __builtin_sub_overflow(a, b, r); }\n");
6405 assert!(text.contains("ssub_overflow.(i32, i1) %0, %1"), "{text}");
6406
6407 let text =
6408 body("int f(int a, int b, int *r) { return __builtin_mul_overflow(a, b, r); }\n");
6409 assert!(text.contains("smul_overflow.(i32, i1) %0, %1"), "{text}");
6410
6411 let text = body(
6414 "int f(unsigned a, unsigned b, unsigned *r) { return __builtin_add_overflow(a, b, r); }\n",
6415 );
6416 assert!(text.contains("uadd_overflow.(i32, i1) %0, %1"), "{text}");
6417 }
6418
6419 #[test]
6427 fn an_overflow_check_is_done_at_a_type_that_holds_every_operand() {
6428 let text = body(
6429 "int f(unsigned a, int b, long long *r) { return __builtin_add_overflow(a, b, r); }\n",
6430 );
6431 assert!(text.contains("%3 = zext.i64 %0"), "the unsigned operand keeps its value: {text}");
6432 assert!(text.contains("%4 = sext.i64 %1"), "and so does the signed one: {text}");
6433 assert!(text.contains("sadd_overflow.(i64, i1) %3, %4"), "{text}");
6434
6435 let text = body(
6438 "int f(long long a, long long b, long long *r) { return __builtin_mul_overflow(a, b, r); }\n",
6439 );
6440 assert!(text.contains("smul_overflow.(i64, i1) %0, %1"), "{text}");
6441 assert!(!text.contains("sext."), "{text}");
6442 assert!(!text.contains("zext.i64"), "{text}");
6444 }
6445
6446 #[test]
6454 fn an_overflow_check_writes_the_wrapped_answer_whether_or_not_it_fit() {
6455 let text =
6456 body("int f(int a, int b, char *r) { return __builtin_sub_overflow(a, b, r); }\n");
6457 assert!(text.contains("%3, %4 = ssub_overflow.(i32, i1) %0, %1"), "{text}");
6458 assert!(text.contains("%5 = trunc.i8 %3"), "narrowed to where it goes: {text}");
6459 assert!(text.contains("%6 = sext.i32 %5"), "and back: {text}");
6460 assert!(text.contains("%7 = icmp ne %6, %3"), "which is whether it fit: {text}");
6461 assert!(text.contains("store %5 -> %2"), "the narrowed value is stored either way: {text}");
6462 assert!(text.contains("%8 = or %4, %7"), "and either bit is an overflow: {text}");
6463 }
6464
6465 #[test]
6472 fn a_call_needing_more_than_the_widest_type_still_compiles() {
6473 for name in ["add", "sub", "mul"] {
6474 let source = format!(
6475 "int f(unsigned __int128 a, long long b, __int128 *r) {{\n \
6476 return __builtin_{name}_overflow(a, b, r);\n}}\n"
6477 );
6478 let mut opts = options();
6479 opts.emit = EmitKind::MirFinal;
6480 assert!(!run(&opts, &source).failed(), "{name} was refused or stopped the back end");
6481 }
6482 }
6483
6484 #[test]
6487 fn an_overflow_check_over_something_that_is_not_an_integer_says_so() {
6488 let messages =
6489 errors("int f(double a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
6490 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
6491
6492 let messages =
6493 errors("int f(int a, int b, double *r) { return __builtin_add_overflow(a, b, r); }\n");
6494 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
6495 }
6496
6497 #[test]
6508 fn an_ordered_access_is_ordered_in_the_ir() {
6509 let text = body("int f(int *p) { return __atomic_load_n(p, 0); }\n");
6510 assert!(text.contains("atomic_load.i32 %0, align 4, relaxed"), "{text}");
6511
6512 let text = body("long f(long *p) { return __atomic_load_n(p, 2); }\n");
6513 assert!(text.contains("atomic_load.i64 %0, align 8, acquire"), "{text}");
6514
6515 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
6516 assert!(text.contains("atomic_store %1 -> %0, align 4, release"), "{text}");
6517
6518 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
6519 assert!(text.contains("atomic_store %1 -> %0, align 4, seq_cst"), "{text}");
6520
6521 let text = body("void f(char *p, int v) { __atomic_store_n(p, v, 0); }\n");
6524 assert!(text.contains("trunc.i8 %1"), "{text}");
6525 assert!(text.contains("atomic_store %2 -> %0, align 1, relaxed"), "{text}");
6526 }
6527
6528 #[test]
6537 fn an_ordered_access_is_the_plain_instruction_on_this_machine() {
6538 let text = asm("int f(int *p) { return __atomic_load_n(p, 5); }\n");
6539 assert!(text.contains("movl\t(%rdi), %eax"), "{text}");
6540 assert!(!text.contains("mfence"), "a load needs no barrier here: {text}");
6541
6542 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
6543 assert!(text.contains("movl\t%esi, (%rdi)"), "{text}");
6544 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
6545
6546 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
6547 let (before, after) = text.split_once("mfence").expect("a barrier: {text}");
6548 assert!(before.contains("movl\t%esi, (%rdi)"), "the store comes first: {text}");
6549 assert!(!after.contains("movl"), "and nothing else is between them: {text}");
6550 }
6551
6552 #[test]
6562 fn a_barrier_is_one_instruction_at_the_strongest_ordering_and_none_below_it() {
6563 assert!(asm("void f(void) { __atomic_thread_fence(5); }\n").contains("mfence"));
6564 assert!(asm("void f(void) { __sync_synchronize(); }\n").contains("mfence"));
6565
6566 for weaker in ["1", "2", "3", "4"] {
6567 let source = format!("void f(void) {{ __atomic_thread_fence({weaker}); }}\n");
6568 assert!(!asm(&source).contains("mfence"), "{weaker} costs nothing here");
6569 }
6570 }
6571
6572 #[test]
6582 fn the_three_x86_fences_are_the_barrier_the_strongest_ordering_gives() {
6583 for name in ["__builtin_ia32_sfence", "__builtin_ia32_lfence", "__builtin_ia32_mfence"] {
6584 let source = format!("void f(void) {{ {name}(); }}\n");
6585 assert!(asm(&source).contains("mfence"), "{name} is a barrier");
6586 let text = body(&source);
6587 assert!(text.contains("fence seq_cst"), "{name}: {text}");
6588 }
6589
6590 let result = run(&options(), "void f(void) { __builtin_ia32_sfence(1); }\n");
6591 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
6592 assert!(result.messages[0].contains("too many arguments"), "{:?}", result.messages);
6593 }
6594
6595 #[test]
6601 fn a_compare_and_exchange_is_one_instruction_answering_two_things() {
6602 let text =
6605 body("int f(int *p, int e, int d) { return __sync_val_compare_and_swap(p, e, d); }\n");
6606 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
6607 assert!(text.contains("return %3"), "the value it found: {text}");
6608
6609 let text =
6610 body("int f(int *p, int e, int d) { return __sync_bool_compare_and_swap(p, e, d); }\n");
6611 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
6612 assert!(text.contains("zext.i32 %4"), "whether it happened: {text}");
6613
6614 let text = body(
6617 "int f(int *p, int *e, int d) { return __atomic_compare_exchange_n(p, e, d, 0, 4, 2); }\n",
6618 );
6619 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
6620 assert!(text.contains("%4, %5 = cmpxchg.(i32, i1) %0, %3, %2, align 4, acq_rel"), "{text}");
6621 assert!(text.contains("br_if %5, block2, block1"), "{text}");
6622 assert!(text.contains("store %4 -> %1, align 4"), "{text}");
6623
6624 let text = body(
6627 "int f(int *p, int *e, int *d) { return __atomic_compare_exchange(p, e, d, 0, 5, 5); }\n",
6628 );
6629 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
6630 assert!(text.contains("%4 = load.i32 %2, align 4"), "{text}");
6631 assert!(text.contains("%5, %6 = cmpxchg.(i32, i1) %0, %3, %4, align 4, seq_cst"), "{text}");
6632 }
6633
6634 #[test]
6641 fn a_compare_and_exchange_is_a_locked_instruction_at_the_width_of_the_object() {
6642 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
6643 for (ty, suffix, reg) in widths {
6644 let source = format!(
6645 "int f({ty} *p, {ty} e, {ty} d) {{ return __sync_bool_compare_and_swap(p, e, d); }}\n"
6646 );
6647 let text = asm(&source);
6648 assert!(text.contains("\tlock\n"), "{ty}: {text}");
6649 assert!(text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
6650 assert!(text.contains("sete\t"), "{ty}: {text}");
6651 }
6652 let source =
6653 "int f(long *p, long e, long d) { return __sync_bool_compare_and_swap(p, e, d); }\n";
6654 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
6655
6656 for order in ["0", "2", "3", "4", "5"] {
6660 let call = format!("__atomic_compare_exchange_n(p, e, d, 0, {order}, 0)");
6661 let source = format!("int f(int *p, int *e, int d) {{ return {call}; }}\n");
6662 let text = asm(&source);
6663 assert!(text.contains("cmpxchgl\t"), "{order}: {text}");
6664 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
6665 }
6666 }
6667
6668 #[test]
6680 fn a_read_modify_write_is_one_instruction_and_the_arithmetic_a_name_asks_for() {
6681 let text = body("int f(int *p, int v) { return __atomic_fetch_add(p, v, 5); }\n");
6682 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
6683 assert!(text.contains("return %2"), "the value that was there: {text}");
6684
6685 let text = body("int f(int *p, int v) { return __atomic_add_fetch(p, v, 5); }\n");
6686 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
6687 assert!(text.contains("%3 = add %2, %1"), "and the value afterwards: {text}");
6688
6689 let text = body("int f(int *p, int v) { return __atomic_sub_fetch(p, v, 5); }\n");
6690 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
6691 assert!(text.contains("%3 = sub %2, %1"), "{text}");
6692
6693 let text = body("int f(int *p, int v) { return __sync_fetch_and_sub(p, v); }\n");
6695 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
6696
6697 let text = body("int f(int *p, int v) { return __atomic_exchange_n(p, v, 5); }\n");
6700 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, seq_cst"), "{text}");
6701
6702 let text = body("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
6703 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, acquire"), "{text}");
6704
6705 let text = body("void f(int *p) { __sync_lock_release(p); }\n");
6708 assert!(text.contains("release"), "{text}");
6709 assert!(text.contains("%1 = iconst.i32 0"), "{text}");
6710
6711 let text = body("void f(int *p, int guard) { __sync_lock_release(p, guard); }\n");
6715 assert!(text.contains("%2 = iconst.i32 0"), "{text}");
6716 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
6717
6718 let text = body("int f(int *p, int v) { return __atomic_fetch_and(p, v, 5); }\n");
6721 assert!(text.contains("%2 = atomic_rmw.i32 and %0, %1, align 4, seq_cst"), "{text}");
6722
6723 let text = body("int f(int *p, int v) { return __sync_or_and_fetch(p, v); }\n");
6724 assert!(text.contains("%2 = atomic_rmw.i32 or %0, %1, align 4, seq_cst"), "{text}");
6725 assert!(text.contains("%3 = or %2, %1"), "and the value afterwards: {text}");
6726
6727 let text = body("int f(int *p, int v) { return __atomic_nand_fetch(p, v, 5); }\n");
6730 assert!(text.contains("%2 = atomic_rmw.i32 nand %0, %1, align 4, seq_cst"), "{text}");
6731 assert!(text.contains("%3 = and %2, %1"), "{text}");
6732 assert!(text.contains("%4 = iconst.i32 -1"), "{text}");
6733 assert!(text.contains("%5 = xor %3, %4"), "{text}");
6734 }
6735
6736 #[test]
6747 fn a_bitwise_read_modify_write_is_a_loop_around_the_compare_and_exchange() {
6748 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
6749 for (ty, suffix, reg) in widths {
6750 for (name, call, insn) in [
6751 ("and", "__atomic_fetch_and(p, v, 5)", "and"),
6752 ("or", "__sync_fetch_and_or(p, v)", "or"),
6753 ("xor", "__atomic_xor_fetch(p, v, 5)", "xor"),
6754 ] {
6755 let source = format!("{ty} f({ty} *p, {ty} v) {{ return {call}; }}\n");
6756 let text = asm(&source);
6757 assert!(text.contains("\tlock\n"), "{ty} {name}: {text}");
6758 assert!(
6759 text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")),
6760 "{ty} {name}: {text}"
6761 );
6762 assert!(text.contains(&format!("{insn}{suffix}\t")), "{ty} {name}: {text}");
6763 assert!(!text.contains("\txadd"), "{ty} {name} is not an add: {text}");
6765 assert!(!text.contains("\txchg"), "{ty} {name} is not an exchange: {text}");
6766 }
6767 }
6768 let source = "long f(long *p, long v) { return __atomic_fetch_or(p, v, 5); }\n";
6769 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
6770
6771 let text = asm("int f(int *p, int v) { return __sync_fetch_and_nand(p, v); }\n");
6775 assert!(text.contains("cmpxchgl\t"), "{text}");
6776 assert!(text.contains("andl\t"), "{text}");
6777 assert!(text.contains("notl\t"), "{text}");
6778 }
6779
6780 #[test]
6789 fn an_access_through_a_second_pointer_is_the_same_access_and_one_more() {
6790 let text = body("void f(int *p, int *r) { __atomic_load(p, r, 5); }\n");
6791 assert!(text.contains("%2 = atomic_load.i32 %0, align 4, seq_cst"), "{text}");
6792 assert!(text.contains("store %2 -> %1, align 4"), "and out through the place: {text}");
6793
6794 let text = body("void f(int *p, int *v) { __atomic_store(p, v, 3); }\n");
6795 assert!(text.contains("%2 = load.i32 %1, align 4"), "in through the place: {text}");
6796 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
6797
6798 let text = body("void f(int *p, int *v, int *r) { __atomic_exchange(p, v, r, 5); }\n");
6801 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
6802 assert!(text.contains("%4 = atomic_rmw.i32 xchg %0, %3, align 4, seq_cst"), "{text}");
6803 assert!(text.contains("store %4 -> %2, align 4"), "{text}");
6804 }
6805
6806 #[test]
6817 fn a_flag_is_an_exchange_of_one_byte_and_a_store_of_a_zero_over_the_same_byte() {
6818 for pointer in ["char", "int", "void"] {
6819 let source = format!("int f({pointer} *p) {{ return __atomic_test_and_set(p, 5); }}\n");
6820 let text = body(&source);
6821 assert!(text.contains("%1 = iconst.i8 1"), "{pointer}: {text}");
6822 assert!(
6823 text.contains("%2 = atomic_rmw.i8 xchg %0, %1, align 1, seq_cst"),
6824 "{pointer}: {text}"
6825 );
6826 assert!(text.contains("%4 = icmp ne %2, %3"), "{pointer}: {text}");
6827
6828 let source = format!("void f({pointer} *p) {{ __atomic_clear(p, 3); }}\n");
6829 let text = body(&source);
6830 assert!(text.contains("atomic_store %2 -> %0, align 1, release"), "{pointer}: {text}");
6831 }
6832
6833 let text = asm("int f(int *p) { return __atomic_test_and_set(p, 5); }\n");
6836 assert!(text.contains("xchgb\t%al, (%rdi)"), "{text}");
6837 assert!(text.contains("setne\t"), "{text}");
6838 }
6839
6840 #[test]
6848 fn a_read_modify_write_is_an_exchange_or_a_locked_add_at_the_width_of_the_object() {
6849 let widths = [("char", "b", "%sil"), ("short", "w", "%si"), ("int", "l", "%esi")];
6850 for (ty, suffix, reg) in widths {
6851 let source =
6852 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_fetch_add(p, v, 5); }}\n");
6853 let text = asm(&source);
6854 assert!(text.contains("\tlock\n"), "{ty}: {text}");
6855 assert!(text.contains(&format!("xadd{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
6856
6857 let source =
6858 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_exchange_n(p, v, 5); }}\n");
6859 let text = asm(&source);
6860 assert!(text.contains(&format!("xchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
6861 assert!(!text.contains("\tlock\n"), "an exchange is locked already: {ty}: {text}");
6862 }
6863 let source = "long f(long *p, long v) { return __atomic_fetch_add(p, v, 5); }\n";
6864 assert!(asm(source).contains("xaddq\t%rsi, (%rdi)"), "{}", asm(source));
6865
6866 let source = "int f(int *p, int v) { return __atomic_fetch_sub(p, v, 5); }\n";
6869 let text = asm(source);
6870 assert!(text.contains("negl\t"), "{text}");
6871 assert!(text.contains("xaddl\t"), "{text}");
6872
6873 for order in ["0", "2", "3", "4", "5"] {
6876 let source =
6877 format!("int f(int *p, int v) {{ return __atomic_fetch_add(p, v, {order}); }}\n");
6878 let text = asm(&source);
6879 assert!(text.contains("xaddl\t"), "{order}: {text}");
6880 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
6881 }
6882
6883 let text = asm("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
6887 assert!(text.contains("xchgl\t%esi, (%rdi)"), "{text}");
6888 let text = asm("void f(int *p) { __sync_lock_release(p); }\n");
6895 assert!(text.contains("xorl\t%eax, %eax"), "{text}");
6896 assert!(text.contains("movl\t%eax, (%rdi)"), "{text}");
6897 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
6898 }
6899
6900 #[test]
6912 fn the_lock_free_questions_are_answered_as_constants() {
6913 for size in ["1", "2", "4", "8"] {
6914 let source =
6915 format!("int f(void) {{ return __atomic_always_lock_free({size}, 0); }}\n");
6916 let text = asm(&source);
6917 assert!(text.contains("movb\t$1, %al"), "{size} bytes is lock free: {text}");
6918 assert!(!text.contains("call"), "and is not a call: {text}");
6919 }
6920 for size in ["3", "16", "sizeof(long double)"] {
6921 let source = format!("int f(void) {{ return __atomic_is_lock_free({size}, 0); }}\n");
6922 let text = asm(&source);
6923 assert!(text.contains("movb\t$0, %al"), "{size} bytes is not: {text}");
6924 assert!(!text.contains("call"), "and is not a call either: {text}");
6925 }
6926
6927 let text = asm("int f(int n) { return __atomic_is_lock_free(n, 0); }\n");
6931 assert!(text.contains("movb\t$0, %al"), "a size nobody knows is not lock free: {text}");
6932 let text = asm("int f(int *p) { return __atomic_always_lock_free(8, p); }\n");
6933 assert!(text.contains("movb\t$0, %al"), "eight bytes at four is not: {text}");
6934 let text = asm("int f(long *p) { return __atomic_always_lock_free(8, p); }\n");
6935 assert!(text.contains("movb\t$1, %al"), "and at eight it is: {text}");
6936 }
6937
6938 #[test]
6950 fn a_memory_order_an_operation_cannot_carry_is_read_as_the_strongest() {
6951 let mut opts = options();
6952 opts.emit = EmitKind::Ir;
6953
6954 let acquire_store = run(&opts, "void f(int *p, int v) { __atomic_store_n(p, v, 2); }\n");
6955 assert!(acquire_store.text().contains("seq_cst"), "{:?}", acquire_store.text());
6956 assert!(acquire_store.messages[0].contains("[W0333]"), "{:?}", acquire_store.messages);
6957
6958 let nonsense = run(&opts, "int f(int *p) { return __atomic_load_n(p, 99); }\n");
6959 assert!(nonsense.text().contains("seq_cst"), "{:?}", nonsense.text());
6960 assert!(nonsense.messages[0].contains("[W0333]"), "{:?}", nonsense.messages);
6961
6962 let computed = run(&opts, "int f(int *p, int n) { return __atomic_load_n(p, n); }\n");
6963 assert!(computed.text().contains("seq_cst"), "{:?}", computed.text());
6964 assert_eq!(computed.messages, Vec::<String>::new(), "a computed order is not a mistake");
6965 }
6966
6967 #[test]
6979 fn a_conversion_between_a_float_and_the_widest_unsigned_integer_is_written_without_a_branch() {
6980 let text = asm("double f(unsigned long long x) { return (double)x; }\n");
6981 assert!(text.contains("cvtsi2sdq"), "the signed conversion is what runs: {text}");
6982 assert!(text.contains("shrq"), "with the value halved first: {text}");
6983 assert!(text.contains("addsd"), "and doubled after: {text}");
6984 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
6985
6986 let text = asm("unsigned long long f(double d) { return (unsigned long long)d; }\n");
6987 assert!(text.contains("cvttsd2siq"), "the signed conversion is what runs: {text}");
6988 assert!(text.contains("subsd"), "with half the range taken off first: {text}");
6989 assert!(text.contains("shlq\t$63"), "and the top bit put back: {text}");
6990 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
6991 }
6992
6993 #[test]
7004 fn a_plain_name_the_program_took_is_the_programs_own_function() {
7005 let taken = concat!(
7006 "static long long llabs(long long b) { return 7; }\n",
7007 "long long f(long long x) { return llabs(x); }\n",
7008 );
7009 assert!(ir(taken).contains("call @llabs"), "a static definition is the program's own");
7010
7011 let retyped = concat!("int llabs(int b);\n", "int f(int x) { return llabs(x); }\n",);
7012 assert!(ir(retyped).contains("call @llabs"), "another type is another function");
7013
7014 let plain = concat!(
7015 "long long llabs(long long b);\n",
7016 "long long f(long long x) { return llabs(x); }\n",
7017 );
7018 let mut opts = options();
7019 opts.emit = EmitKind::Ir;
7020 assert!(!run(&opts, plain).text().contains("call @llabs"), "the library's by default");
7021
7022 opts.builtins = false;
7023 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin");
7024
7025 opts.builtins = true;
7026 opts.no_builtin = vec!["llabs".to_owned()];
7027 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin-llabs");
7028 let one = "long labs(long b);\nlong f(long x) { return labs(x); }\n";
7029 assert!(!run(&opts, one).text().contains("call @labs"), "one name and not the family");
7030
7031 opts.no_builtin = Vec::new();
7034 opts.builtins = false;
7035 let prefixed = "long long f(long long x) { return __builtin_llabs(x); }\n";
7036 assert!(!run(&opts, prefixed).text().contains("call @llabs"), "the prefix is a promise");
7037 }
7038
7039 #[test]
7052 fn the_hint_builtins_are_their_first_argument_and_the_hint_leaves_no_trace() {
7053 let text = ir(concat!(
7054 "long a = __builtin_expect(7, 1);\n",
7055 "long b = __builtin_expect_with_probability(9, 1, 0.9);\n",
7056 "unsigned long c = sizeof(__builtin_expect((char)1, 1));\n",
7057 ));
7058 assert!(text.contains("global @a : i64 = 7,"), "{text}");
7059 assert!(text.contains("global @b : i64 = 9,"), "{text}");
7060 assert!(text.contains("global @c : i64 = 8,"), "{text}");
7061 assert!(!text.contains("__builtin_expect"), "it is not a call to anything:\n{text}");
7062
7063 let text = body("long f(char c) { return __builtin_expect(c, 1); }\n");
7066 assert!(text.contains("sext"), "{text}");
7067
7068 let one = "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 1\n %2 = sext.i64 %1\n return %0\n";
7072 assert_eq!(body("int f(void) { int i = 0; __builtin_expect(1, i++); return i; }\n"), one);
7073 let source = "int g(void) { int i = 0; __builtin_expect_with_probability(1, i++, 0.5); return i; }\n";
7074 assert_eq!(body(source), one);
7075
7076 let kept = body("int f(int n) { int i = 0; __builtin_expect(n, i++); return i; }\n");
7081 assert!(kept.contains("add.nsw"), "the hint still runs: {kept}");
7082 assert!(kept.ends_with("return %3\n"), "and the answer is what it left behind: {kept}");
7083 let both = "int g(int n) { int i = 0; __builtin_expect_with_probability(n, i++, 0.5); return i; }\n";
7084 assert!(body(both).contains("add.nsw"), "and so does the one with three arguments");
7085 }
7086
7087 #[test]
7099 fn a_promise_that_control_does_not_arrive_writes_no_instruction() {
7100 let promised = "int f(int x) { if (x) return 1; __builtin_unreachable(); }\n";
7101 let text = ir(promised);
7102 assert!(text.contains(" unreachable_hint\n"), "{text}");
7103 assert!(!text.contains("call"), "it is not a call to anything:\n{text}");
7104
7105 let after = body("int g(int x) { __builtin_unreachable(); return x; }\n");
7109 assert!(after.contains("return"), "{after}");
7110
7111 let text = asm(promised);
7114 let mine = text.split_once("\nf:\n").expect("a definition").1;
7115 let mine = mine.split_once("\t.size").expect("a definition").0;
7116 let plain = asm("int f(int x) { if (x) return 1; }\n");
7117 let plain = plain.split_once("\nf:\n").expect("a definition").1;
7118 let plain = plain.split_once("\t.size").expect("a definition").0;
7119 assert_eq!(mine, plain);
7120 let last = mine.lines().rfind(|line| !line.trim_start().starts_with('.'));
7123 assert_eq!(last.map(str::trim), Some("ret"), "{mine}");
7124 assert!(!mine.contains("ud2"), "{mine}");
7125 }
7126
7127 #[test]
7134 fn a_library_builtin_is_diagnosed_under_the_name_the_program_wrote() {
7135 let mut opts = options();
7136 opts.emit = EmitKind::Ir;
7137 let messages = run(&opts, "void f(void) { __builtin_abort(1); }\n").messages;
7138 assert!(
7139 messages.iter().any(|m| m.contains("__builtin_abort")),
7140 "expected the written name in {messages:?}"
7141 );
7142 }
7143
7144 #[test]
7152 fn a_builtin_nothing_lowers_is_refused_by_name() {
7153 let mut opts = options();
7154 opts.emit = EmitKind::Ir;
7155 let builtin = "__atomic_signal_fence";
7156 let source = format!("int counter;\nint f(void) {{ return ({builtin}(5), 0); }}\n");
7157 let messages = run(&opts, &source).messages;
7158 let named = messages.iter().any(|m| m.contains(builtin) && m.contains("E0686"));
7159 assert!(named, "expected {builtin} to be refused by name in {messages:?}");
7160 }
7161
7162 #[test]
7171 fn what_is_refused_is_the_call_and_not_the_name() {
7172 let text = ir(concat!(
7173 "void __atomic_signal_fence(int order) { (void)order; }\n",
7174 "void f(void) { __atomic_signal_fence(5); }\n",
7175 ));
7176 assert!(text.contains("call @__atomic_signal_fence"), "{text}");
7177 }
7178
7179 #[test]
7188 fn the_object_size_of_an_address_is_what_the_layout_leaves_in_front_of_it() {
7189 let text = ir(concat!(
7190 "struct S { char a[8]; int n; char b[12]; };\n",
7191 "char g[32];\n",
7192 "struct S gs;\n",
7193 "unsigned long whole = __builtin_object_size(g, 0);\n",
7194 "unsigned long moved = __builtin_object_size(g + 4, 0);\n",
7195 "unsigned long back = __builtin_object_size(g + 30 - 2, 0);\n",
7196 "unsigned long outer = __builtin_object_size(gs.a, 0);\n",
7197 "unsigned long inner = __builtin_object_size(gs.a, 1);\n",
7198 "unsigned long scalar = __builtin_object_size(&gs.n, 1);\n",
7199 "unsigned long after = __builtin_object_size(&gs.n, 0);\n",
7200 "unsigned long into = __builtin_object_size(&gs.b[2], 1);\n",
7201 "unsigned long text = __builtin_object_size(\"hello\", 0);\n",
7202 "unsigned long dyn = __builtin_dynamic_object_size(gs.b, 1);\n",
7203 ));
7204 for (name, size) in [
7205 ("whole", 32),
7206 ("moved", 28),
7207 ("back", 4),
7208 ("outer", 24),
7209 ("inner", 8),
7210 ("scalar", 4),
7211 ("after", 16),
7212 ("into", 10),
7213 ("text", 6),
7214 ("dyn", 12),
7215 ] {
7216 let said = format!("global @{name} : i64 = {size},");
7217 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
7218 }
7219 }
7220
7221 #[test]
7229 fn the_object_behind_an_address_can_be_one_with_automatic_storage() {
7230 let text = body(concat!(
7231 "struct S { char a[8]; int n; char b[12]; };\n",
7232 "unsigned long f(void) {\n",
7233 " char loc[20];\n",
7234 " struct S ls;\n",
7235 " return __builtin_object_size(loc + 3, 0) + __builtin_object_size(ls.b + 2, 1);\n",
7236 "}\n",
7237 ));
7238 assert!(text.contains("iconst.i64 17"), "twenty bytes with three used: {text}");
7239 assert!(text.contains("iconst.i64 10"), "twelve bytes with two used: {text}");
7240 }
7241
7242 #[test]
7252 fn an_address_with_no_object_in_sight_answers_at_the_end_of_the_range_its_kind_asks_for() {
7253 let text = ir(concat!(
7254 "struct T { int n; char f[]; };\n",
7255 "extern char *p;\n",
7256 "extern struct T *t;\n",
7257 "unsigned long largest = __builtin_object_size(p, 0);\n",
7258 "unsigned long nearest = __builtin_object_size(p, 1);\n",
7259 "unsigned long least = __builtin_object_size(p, 2);\n",
7260 "unsigned long tight = __builtin_object_size(p, 3);\n",
7261 "unsigned long flex = __builtin_object_size(t->f, 1);\n",
7262 "int says = __builtin_object_size(p, 0) == (unsigned long)-1;\n",
7263 ));
7264 for name in ["largest", "nearest", "flex"] {
7265 let said = format!("global @{name} : i64 = -1,");
7269 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
7270 }
7271 for name in ["least", "tight"] {
7272 let said = format!("global @{name} : i64 = 0,");
7273 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
7274 }
7275 assert!(text.contains("global @says : i32 = 1,"), "{text}");
7276 }
7277
7278 #[test]
7285 fn the_address_an_object_size_is_asked_about_is_not_evaluated() {
7286 let text = body(concat!(
7287 "extern char *side(void);\n",
7288 "unsigned long f(void) { return __builtin_object_size(side(), 0); }\n",
7289 ));
7290 assert!(!text.contains("call"), "nothing is called: {text}");
7291 }
7292
7293 #[test]
7298 fn a_kind_that_is_not_one_of_the_four_is_refused() {
7299 for source in [
7300 "extern char *p;\nextern int k;\nunsigned long f(void) ".to_owned()
7301 + "{ return __builtin_object_size(p, k); }\n",
7302 "extern char *p;\nunsigned long f(void) { return __builtin_object_size(p, 4); }\n"
7303 .to_owned(),
7304 "extern char *p;\nunsigned long f(void) ".to_owned()
7305 + "{ return __builtin_dynamic_object_size(p, -1); }\n",
7306 ] {
7307 let messages = errors(&source);
7308 let named = messages.iter().any(|m| m.contains("E0709") && m.contains("0 to 3"));
7309 assert!(named, "expected a complaint about the kind in {messages:?}");
7310 }
7311 }
7312
7313 #[test]
7319 fn the_pair_that_saves_a_place_lowers_to_the_two_markers() {
7320 let text = ir(concat!(
7321 "void *buf[5];\n",
7322 "int f(void) {\n",
7323 " if (__builtin_setjmp(buf)) return 2;\n",
7324 " return 1;\n",
7325 "}\n",
7326 "void g(void) { __builtin_longjmp(buf, 1); }\n",
7327 ));
7328 assert!(text.contains("= setjmp_marker.i32 %0\n"), "the save answers a value: {text}");
7329 assert!(text.contains(" longjmp_marker %0\n"), "the restore answers nothing: {text}");
7330 assert!(!text.contains("call @"), "neither of them is a call: {text}");
7331 }
7332
7333 #[test]
7341 fn a_local_of_a_function_that_saves_a_place_gets_a_slot() {
7342 let text = ir(concat!(
7343 "void *buf[5];\n",
7344 "int f(int x) { int a = 0; if (__builtin_setjmp(buf)) return a; a = 1; return x; }\n",
7345 "int g(int x) { int a = 0; if (x) return a; a = 1; return x; }\n",
7346 ));
7347 let (saves, plain) = text.split_once("func @g").expect("both functions");
7348 assert_eq!(saves.matches("= alloca").count(), 2, "the parameter and the local: {text}");
7349 assert!(saves.contains("store %9 -> %2"), "the local is written through: {text}");
7350 assert!(!plain.contains("alloca"), "nothing in the plain one needs a slot: {text}");
7351 }
7352
7353 #[test]
7362 fn a_value_live_across_sigsetjmp_keeps_its_spill_slot() {
7363 each_spill_slot_written_once(&across("int __sigsetjmp(sigjmp_buf, int);\n", "__sigsetjmp"));
7364 }
7365
7366 #[test]
7369 fn a_value_live_across_a_returns_twice_call_keeps_its_spill_slot() {
7370 let declared = "int save_here(sigjmp_buf, int) __attribute__((__returns_twice__));\n";
7371 each_spill_slot_written_once(&across(declared, "save_here"));
7372 }
7373
7374 #[test]
7382 fn a_value_live_across_setjmp_shares_its_slot_with_nothing_in_the_first_arm() {
7383 let source = concat!(
7384 "typedef long jmp_buf[25];\n",
7385 "int _setjmp(jmp_buf);\n",
7386 "void longjmp(jmp_buf, int) __attribute__((noreturn));\n",
7387 "int printf(const char *, ...);\n",
7388 "static jmp_buf *stack;\n",
7389 "static volatile long long sink;\n",
7390 "static int cells[64];\n",
7391 "static volatile int seed_in = 3;\n",
7392 "static void work(void) { longjmp(*stack, 1); }\n",
7393 "int main(void) {\n",
7394 " int seed = seed_in;\n",
7395 " int v = seed * 2;\n",
7396 " jmp_buf buf;\n",
7397 " if (_setjmp(buf) == 0) {\n",
7398 " stack = &buf;\n",
7399 " int *p = &cells[seed + 3];\n",
7400 " int a = v + 8;\n",
7401 " int b = seed * 2005;\n",
7402 " int *q = &cells[v + 1];\n",
7403 " work();\n",
7404 " sink = *p + a + b + *q;\n",
7405 " } else {\n",
7406 " printf(\"%d\\n\", v);\n",
7407 " }\n",
7408 " return 0;\n",
7409 "}\n",
7410 );
7411 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
7412 let mut opts = options();
7413 opts.emit = EmitKind::Asm;
7414 opts.opt_level = level;
7415 let result = run(&opts, source);
7416 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
7417 let text = result.text();
7418 let body = text.split_once("\nmain:\n").expect("the function").1;
7419 let lines: Vec<&str> = body.lines().map(str::trim).collect();
7420 let save = lines.iter().position(|l| *l == "call\t_setjmp").expect("the save");
7421 let jump = lines[save..]
7422 .iter()
7423 .position(|l| *l == "call\twork" || *l == "call\tlongjmp")
7424 .map(|at| save + at)
7425 .unwrap_or_else(|| panic!("the call that jumps back at {level:?}:\n{text}"));
7426 let printf = lines.iter().position(|l| *l == "call\tprintf").expect("the handler");
7427 let slot = lines[jump..printf]
7429 .iter()
7430 .rev()
7431 .find_map(|l| l.strip_suffix(", %rsi").or_else(|| l.strip_suffix(", %esi")))
7432 .and_then(|l| l.split_once('\t'))
7433 .map(|(_, place)| place)
7434 .filter(|place| place.ends_with("(%rsp)") || place.ends_with("(%rbp)"))
7435 .unwrap_or_else(|| panic!("the handler reads v from a slot at {level:?}:\n{text}"));
7436 let writes = |l: &&str| {
7437 !l.starts_with("cmp") && !l.starts_with("test") && l.ends_with(&format!(", {slot}"))
7438 };
7439 assert!(
7440 lines[..save].iter().any(writes),
7441 "{slot} is written before the save at {level:?}:\n{text}"
7442 );
7443 assert!(
7444 !lines[save..jump].iter().any(writes),
7445 "{slot} is written again before the jump at {level:?}:\n{text}"
7446 );
7447 }
7448 }
7449
7450 fn across(declared: &str, save: &str) -> String {
7453 asm(&format!(
7454 "typedef long sigjmp_buf[25];\n{declared}int id(int);\nvoid thrower(int);\n\
7455 int work(int n) {{\n\
7456 \x20 int v0 = id(n), v1 = id(n + 1), v2 = id(n + 2), v3 = id(n + 3), v4 = id(n + 4);\n\
7457 \x20 sigjmp_buf b;\n\
7458 \x20 if ({save}(b, 0) == 0) {{\n\
7459 \x20 int w0 = id(v0 + v1), w1 = id(v1 + v2), w2 = id(v2 + v3);\n\
7460 \x20 int w3 = id(v3 + v4), w4 = id(v4 + v0);\n\
7461 \x20 thrower(n);\n\
7462 \x20 return w0 ^ w1 ^ w2 ^ w3 ^ w4;\n\
7463 \x20 }}\n\
7464 \x20 return v0 + v1 + v2 + v3 + v4;\n\
7465 }}\n"
7466 ))
7467 }
7468
7469 fn each_spill_slot_written_once(text: &str) {
7471 let mut stored = Vec::new();
7472 for line in text.lines().map(str::trim) {
7473 let Some(operands) = line.strip_prefix("movq\t%") else { continue };
7474 if let Some((_, place)) = operands.split_once(", ") {
7475 if place.ends_with("(%rsp)") {
7476 assert!(!stored.contains(&place), "{place} is written twice:\n{text}");
7477 stored.push(place);
7478 }
7479 }
7480 }
7481 assert!(!stored.is_empty(), "something should have been spilled:\n{text}");
7482 }
7483
7484 #[test]
7493 fn the_save_writes_four_words_and_carries_on_in_a_new_block() {
7494 let text =
7495 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
7496 let body = text.split_once("\nf:\n").expect("the function").1;
7497 assert!(body.contains("\tmovq\t%rsp, %rbp\n"), "a frame pointer whatever: {text}");
7498 assert!(body.contains("\tsubq\t$8, %rsp\n"), "no red zone: {text}");
7499 assert!(body.contains("\tmovq\t%rbp, (%rax)\n"), "the frame pointer: {text}");
7500 assert!(body.contains("\tmovq\t%rsp, 16(%rax)\n"), "the stack pointer: {text}");
7501 assert!(body.contains("\tleaq\t.Lf_1(%rip), %rcx\n"), "where to come back to: {text}");
7502 assert!(body.contains("\tmovq\t%rcx, 8(%rax)\n"), "and that goes in the buffer: {text}");
7503 let back = body.split_once(".Lf_1:\n").expect("the block control comes back to").1;
7504 assert!(back.starts_with("\tmovq\t(%rsp), %rax\n"), "the answer is read back: {text}");
7505 }
7506
7507 #[test]
7515 fn a_save_destroys_every_register_the_allocator_hands_out() {
7516 let text =
7517 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
7518 for reg in ["%rbx", "%r12", "%r13", "%r14", "%r15"] {
7519 assert!(text.contains(&format!("\tpushq\t{reg}\n")), "{reg} is saved: {text}");
7520 assert!(text.contains(&format!("\tpopq\t{reg}\n")), "{reg} is restored: {text}");
7521 }
7522 }
7523
7524 #[test]
7531 fn the_restore_puts_the_frame_back_before_it_jumps() {
7532 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
7533 let mut opts = options();
7534 opts.emit = EmitKind::Asm;
7535 opts.opt_level = level;
7536 let source = "void *buf[5];\nvoid g(void) { __builtin_longjmp(buf, 1); }\n";
7537 let result = run(&opts, source);
7538 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
7539 let text = result.text().to_owned();
7540 let jump = text.find("\tjmp\t*%").unwrap_or_else(|| panic!("an indirect jump: {text}"));
7541 let stack = text.find(", %rsp\n").unwrap_or_else(|| panic!("the stack back: {text}"));
7542 let frame = text.find(", %rbp\n").unwrap_or_else(|| panic!("the frame back: {text}"));
7543 assert!(stack < jump, "the stack goes back first at {level:?}: {text}");
7544 assert!(frame < jump, "and so does the frame at {level:?}: {text}");
7545 }
7546 }
7547
7548 #[test]
7554 fn a_longjmp_whose_second_argument_is_not_one_is_turned_down() {
7555 for source in [
7556 "void *buf[5];\nvoid f(void) { __builtin_longjmp(buf, 0); }\n",
7557 "void *buf[5];\nextern int v;\nvoid f(void) { __builtin_longjmp(buf, v); }\n",
7558 ] {
7559 let messages = errors(source);
7560 let named = messages.iter().any(|m| m.contains("E0710"));
7561 assert!(named, "expected a complaint about the value in {messages:?}");
7562 }
7563 }
7564
7565 #[test]
7570 fn a_static_function_nothing_refers_to_is_not_emitted() {
7571 let text = ir("static int dropped(void) { return 1; }\n\
7572 static int kept(void) { return 2; }\n\
7573 int main(void) { return kept(); }\n");
7574 assert!(text.contains("func @kept"), "{text}");
7575 assert!(!text.contains("dropped"), "{text}");
7576 }
7577
7578 #[test]
7584 fn two_static_functions_that_only_call_each_other_are_both_dropped() {
7585 let text = ir("static int ping(void);\n\
7586 static int pong(void) { return ping(); }\n\
7587 static int ping(void) { return pong(); }\n\
7588 int main(void) { return 0; }\n");
7589 assert!(!text.contains("ping"), "{text}");
7590 assert!(!text.contains("pong"), "{text}");
7591 }
7592
7593 #[test]
7599 fn naming_a_static_function_anywhere_keeps_it() {
7600 let text = ir("static int by_address(void) { return 1; }\n\
7601 static int in_an_image(void) { return 2; }\n\
7602 static int deeper(void) { return 3; }\n\
7603 static int reaches_deeper(void) { return deeper(); }\n\
7604 static int (*table[1])(void) = {in_an_image};\n\
7605 int main(void) {\n\
7606 int (*p)(void) = by_address;\n\
7607 return p() + table[0]() + reaches_deeper();\n\
7608 }\n");
7609 for kept in ["by_address", "in_an_image", "deeper", "reaches_deeper"] {
7610 assert!(text.contains(&format!("func @{kept}")), "expected {kept} in:\n{text}");
7611 }
7612 }
7613
7614 #[test]
7620 fn an_attribute_keeps_a_static_function_nothing_refers_to() {
7621 for attribute in ["used", "retain", "constructor", "destructor", "__used__"] {
7622 let source = format!(
7623 "__attribute__(({attribute})) static int kept(void) {{ return 1; }}\n\
7624 int main(void) {{ return 0; }}\n"
7625 );
7626 let text = ir(&source);
7627 assert!(text.contains("func @kept"), "for {attribute}:\n{text}");
7628 }
7629 }
7630
7631 #[test]
7634 fn nonnull_is_answered_yes_and_taken_with_or_without_operands() {
7635 let text = ir("#if !__has_attribute(nonnull) || !__has_attribute(__nonnull__)\n\
7636 #error nonnull\n\
7637 #endif\n\
7638 __attribute__((nonnull)) int first(char *p);\n\
7639 int both(char *a, int n, char *b) __attribute__((__nonnull__(1, 3)));\n\
7640 int both(char *a, int n, char *b) { return first(a) + n + (b != 0); }\n");
7641 assert!(text.contains("func @both"), "{text}");
7642 }
7643
7644 #[test]
7647 fn a_function_anything_could_call_is_emitted_without_being_called() {
7648 let text =
7649 ir("int nobody_here_calls_it(void) { return 1; }\nint main(void) { return 0; }\n");
7650 assert!(text.contains("func @nobody_here_calls_it"), "{text}");
7651 }
7652
7653 #[test]
7660 fn a_classification_c_has_an_operator_for_is_that_operator() {
7661 for (builtin, operator) in [
7662 ("__builtin_isgreater", "binary >"),
7663 ("__builtin_isgreaterequal", "binary >="),
7664 ("__builtin_isless", "binary <"),
7665 ("__builtin_islessequal", "binary <="),
7666 ] {
7667 let source = format!("int f(double x, double y) {{ return {builtin}(x, y); }}\n");
7668 let text = tast(&source);
7669 assert!(text.contains(&format!("{operator} : int")), "for {builtin}:\n{text}");
7670 }
7671 }
7672
7673 #[test]
7682 fn the_classification_builtins_are_comparisons_and_not_calls() {
7683 let text = body("int f(double x, double y) { return __builtin_isunordered(x, y); }\n");
7684 assert_eq!(
7685 text,
7686 "block0(%0: f64, %1: f64):\n %2 = fcmp uno %0, %1\n %3 = zext.i32 \
7687 %2\n return %3\n"
7688 );
7689
7690 let text = body("int f(double x, double y) { return __builtin_islessgreater(x, y); }\n");
7692 assert!(text.contains("fcmp one %0, %1"), "{text}");
7693
7694 let text = body("int f(double x) { return __builtin_isnan(x); }\n");
7695 assert!(text.contains("fcmp uno %0, %0"), "{text}");
7696
7697 let text = body("int f(double x) { return __builtin_isinf(x); }\n");
7698 assert!(text.contains("fconst.f64 0x7ff0000000000000"), "{text}");
7699 assert!(text.contains("fconst.f64 0xfff0000000000000"), "{text}");
7700 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
7701 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
7702 assert!(text.contains("%5 = or %3, %4"), "{text}");
7703
7704 let text = body("int f(double x) { return __builtin_isfinite(x); }\n");
7707 assert!(text.contains("%3 = fcmp olt %2, %0"), "{text}");
7708 assert!(text.contains("%4 = fcmp olt %0, %1"), "{text}");
7709 assert!(text.contains("%5 = and %3, %4"), "{text}");
7710
7711 let text = body("int f(double x) { return __builtin_signbit(x); }\n");
7712 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
7713 assert!(text.contains("icmp slt %1, %2"), "{text}");
7714
7715 let text = body("int f(long double x) { return __builtin_signbitl(x); }\n");
7719 assert!(text.contains("load.i16"), "{text}");
7720 assert!(text.contains("icmp slt"), "{text}");
7721 assert!(!text.contains("i80"), "{text}");
7722
7723 let text = body("double g(void);\nint f(void) { return __builtin_isnan(g()); }\n");
7726 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
7727 }
7728
7729 #[test]
7736 fn a_classification_spelling_that_names_a_width_converts_before_it_asks() {
7737 let text = ir(concat!(
7738 "int a = __builtin_isinff(1e300);\n",
7739 "int b = __builtin_isinf(1e300);\n",
7740 "int c = __builtin_isnan(0.0);\n",
7744 "int d = __builtin_signbit(-0.0);\n",
7745 "int e = __builtin_islessgreater(1.0, 2.0);\n",
7746 ));
7747 assert!(text.contains("global @a : i32 = 1,"), "{text}");
7748 assert!(text.contains("global @b : i32 = 0,"), "{text}");
7749 assert!(text.contains("global @c : i32 = 0,"), "{text}");
7750 assert!(text.contains("global @d : i32 = 1,"), "{text}");
7751 assert!(text.contains("global @e : i32 = 1,"), "{text}");
7752 }
7753
7754 #[test]
7756 fn a_classification_builtin_refuses_an_argument_that_is_not_floating_point() {
7757 let mut opts = options();
7758 opts.emit = EmitKind::Ir;
7759 let source = concat!(
7760 "int a(int x) { return __builtin_isnan(x); }\n",
7761 "int b(int x, int y) { return __builtin_isunordered(x, y); }\n",
7762 "int c(double x) { return __builtin_isnan(x, x); }\n",
7763 );
7764 let messages = run(&opts, source).messages;
7765 assert_eq!(
7766 messages,
7767 [
7768 "/main.c:1:23: error: non-floating-point argument in call to function \
7769 '__builtin_isnan' [E0685]",
7770 "/main.c:2:30: error: non-floating-point arguments in call to function \
7771 '__builtin_isunordered' [E0685]",
7772 "/main.c:3:26: error: too many arguments to function '__builtin_isnan' [E0511]",
7773 ]
7774 );
7775 }
7776
7777 #[test]
7786 fn the_last_three_classification_builtins_are_comparisons_and_not_calls() {
7787 let text = body("int f(double x) { return __builtin_isnormal(x); }\n");
7788 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
7792 assert!(text.contains("%2 = iconst.i64 9223372036854775807"), "{text}");
7793 assert!(text.contains("%3 = and %1, %2"), "{text}");
7794 assert!(text.contains("%4 = iconst.i64 4503599627370496"), "{text}");
7795 assert!(text.contains("%5 = iconst.i64 9218868437227405312"), "{text}");
7796 assert!(text.contains("%6 = icmp uge %3, %4"), "{text}");
7797 assert!(text.contains("%7 = icmp ult %3, %5"), "{text}");
7798 assert!(text.contains("%8 = and %6, %7"), "{text}");
7799
7800 let text = body("int f(long double x) { return __builtin_isnormal(x); }\n");
7805 assert!(text.contains("fconst.f80 0x18000000000000000"), "{text}");
7806 assert!(text.contains("fconst.f80 0x7fff8000000000000000"), "{text}");
7807 assert!(text.contains("fcmp oge"), "{text}");
7808 assert!(text.contains("fcmp olt"), "{text}");
7809
7810 let text = body("int f(double x) { return __builtin_isinf_sign(x); }\n");
7811 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
7812 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
7813 assert!(text.contains("%7 = sub %5, %6"), "{text}");
7814
7815 let text = body("int f(double x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n");
7816 assert!(text.contains("fcmp uno %0, %0"), "{text}");
7817 assert!(text.contains("fcmp oeq %0, %6"), "{text}");
7818 assert_eq!(text.matches(" = zext.i32 ").count(), 4, "{text}");
7822 assert_eq!(text.matches(" = xor ").count(), 4, "{text}");
7823 assert!(!text.contains("call"), "{text}");
7824
7825 let text = body(concat!(
7828 "double g(void);\n",
7829 "int f(void) { return __builtin_fpclassify(0, 1, 2, 3, 4, g()); }\n",
7830 ));
7831 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
7832 }
7833
7834 #[test]
7841 fn the_last_three_classification_builtins_fold_where_their_operand_is_a_constant() {
7842 let text = ir(concat!(
7843 "int a = __builtin_isnormal(1.0);\n",
7844 "int b = __builtin_isnormal(0.0);\n",
7845 "int c = __builtin_isnormal(1.0 / 0.0);\n",
7846 "int d = __builtin_isinf_sign(-1.0 / 0.0);\n",
7847 "int e = __builtin_isinf_sign(1.0);\n",
7848 "int g = __builtin_fpclassify(0, 1, 2, 3, 4, 0.0);\n",
7849 "int h = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0);\n",
7850 "int i = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0 / 0.0);\n",
7851 ));
7852 assert!(text.contains("global @a : i32 = 1,"), "{text}");
7853 assert!(text.contains("global @b : i32 = 0,"), "{text}");
7854 assert!(text.contains("global @c : i32 = 0,"), "{text}");
7855 assert!(text.contains("global @d : i32 = -1,"), "{text}");
7856 assert!(text.contains("global @e : i32 = 0,"), "{text}");
7857 assert!(text.contains("global @g : i32 = 4,"), "{text}");
7858 assert!(text.contains("global @h : i32 = 2,"), "{text}");
7859 assert!(text.contains("global @i : i32 = 1,"), "{text}");
7860 }
7861
7862 #[test]
7868 fn fpclassify_refuses_an_answer_that_is_not_an_integer_constant() {
7869 let mut opts = options();
7870 opts.emit = EmitKind::Ir;
7871 let source = concat!(
7872 "int a(double x, int n) { return __builtin_fpclassify(0, 1, n, 3, 4, x); }\n",
7873 "int b(double x) { return __builtin_fpclassify(0, 1, 2, 3, x); }\n",
7874 "int c(int x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n",
7875 );
7876 let messages = run(&opts, source).messages;
7877 assert_eq!(
7878 messages,
7879 [
7880 "/main.c:1:60: error: non-const integer argument 3 in call to function \
7881 '__builtin_fpclassify' [E0687]",
7882 "/main.c:2:26: error: too few arguments to function '__builtin_fpclassify' \
7883 [E0511]",
7884 "/main.c:3:23: error: non-floating-point argument in call to function \
7885 '__builtin_fpclassify' [E0685]",
7886 ]
7887 );
7888 }
7889
7890 #[test]
7898 fn a_builtin_whose_answer_is_a_constant_is_one_and_not_a_call() {
7899 let text = ir(concat!(
7900 "double a = __builtin_inf();\n",
7901 "float b = __builtin_huge_valf();\n",
7902 "long double c = __builtin_infl();\n",
7903 "double d = __builtin_huge_val();\n",
7904 ));
7905 assert!(text.contains("global @a : f64 = 0x7ff0000000000000,"), "{text}");
7906 assert!(text.contains("global @b : f32 = 0x7f800000,"), "{text}");
7907 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
7908 assert!(text.contains("global @d : f64 = 0x7ff0000000000000,"), "{text}");
7909 assert!(!text.contains("call"), "{text}");
7910 }
7911
7912 #[test]
7921 fn a_nan_is_written_with_the_payload_the_program_asked_for() {
7922 let text = ir(concat!(
7923 "double a = __builtin_nan(\"\");\n",
7924 "double b = __builtin_nan(\"0x1\");\n",
7925 "double c = __builtin_nan(\"010\");\n",
7927 "double d = __builtin_nans(\"\");\n",
7928 "double e = __builtin_nans(\"0x1\");\n",
7929 "float f = __builtin_nanf(\"0x1\");\n",
7930 "float g = __builtin_nansf(\"\");\n",
7931 "long double h = __builtin_nansl(\"\");\n",
7932 ));
7933 assert!(text.contains("global @a : f64 = 0x7ff8000000000000,"), "{text}");
7934 assert!(text.contains("global @b : f64 = 0x7ff8000000000001,"), "{text}");
7935 assert!(text.contains("global @c : f64 = 0x7ff8000000000008,"), "{text}");
7936 assert!(text.contains("global @d : f64 = 0x7ff4000000000000,"), "{text}");
7937 assert!(text.contains("global @e : f64 = 0x7ff0000000000001,"), "{text}");
7938 assert!(text.contains("global @f : f32 = 0x7fc00001,"), "{text}");
7939 assert!(text.contains("global @g : f32 = 0x7fa00000,"), "{text}");
7940 assert!(text.contains("f80 0x7fffa000000000000000"), "{text}");
7941
7942 let text = ir(concat!(
7945 "double f(const char *p) { return __builtin_nan(p); }\n",
7946 "double g(void) { return __builtin_nans(\"1x\"); }\n",
7947 ));
7948 assert_eq!(text.matches("call @nan(").count(), 1, "{text}");
7949 assert_eq!(text.matches("call @nans(").count(), 1, "{text}");
7950 }
7951
7952 #[test]
7960 fn the_length_and_the_order_of_a_string_literal_are_known_here() {
7961 let text = ir(concat!(
7962 "unsigned long a = __builtin_strlen(\"hello\");\n",
7963 "unsigned long b = __builtin_strlen(\"a\\0bc\");\n",
7964 "int c = __builtin_strcmp(\"X\", \"X\\376\") < 0;\n",
7965 "int d = __builtin_strcmp(\"abc\", \"abc\");\n",
7966 "int e = __builtin_strcmp(\"abc\", \"ab\") > 0;\n",
7967 ));
7968 assert!(text.contains("global @a : i64 = 5,"), "{text}");
7969 assert!(text.contains("global @b : i64 = 1,"), "{text}");
7970 assert!(text.contains("global @c : i32 = 1,"), "{text}");
7971 assert!(text.contains("global @d : i32 = 0,"), "{text}");
7972 assert!(text.contains("global @e : i32 = 1,"), "{text}");
7973 assert!(!text.contains("call"), "{text}");
7974
7975 let text = ir("unsigned long f(const char *p) { return __builtin_strlen(p); }\n");
7977 assert!(text.contains("call @strlen("), "{text}");
7978 }
7979
7980 #[test]
7987 fn a_sign_builtin_is_a_mask_over_the_bits_and_not_a_call() {
7988 let text = body("double f(double x) { return __builtin_fabs(x); }\n");
7989 assert!(text.contains("bitcast.i64 %0"), "{text}");
7990 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
7991 assert!(text.contains("and %1, %2"), "{text}");
7992 assert!(text.contains("bitcast.f64 %3"), "{text}");
7993 assert!(!text.contains("call"), "{text}");
7994
7995 let text = body("double f(double x, double y) { return __builtin_copysign(x, y); }\n");
7996 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
7997 assert!(text.contains("%8 = or %4, %7"), "{text}");
7998 assert!(!text.contains("call"), "{text}");
7999
8000 let text = body("long double f(long double x) { return __builtin_fabsl(x); }\n");
8003 assert!(text.contains("iconst.i16 32767"), "{text}");
8004 assert!(text.contains("load.f80"), "{text}");
8005 assert!(!text.contains("call"), "{text}");
8006
8007 let text = body("double f(float x) { return __builtin_fabs(x); }\n");
8010 assert!(text.contains("fpext.f64 %0"), "{text}");
8011 assert!(text.contains("bitcast.i64 %1"), "{text}");
8012 }
8013
8014 #[test]
8021 fn a_shuffle_picks_each_lane_by_the_low_bits_of_the_mask() {
8022 let text = body(concat!(
8023 "typedef int v2 __attribute__((vector_size(8)));\n",
8024 "void f(v2 *v, v2 m) { *v = __builtin_shuffle(*v, m); }\n",
8025 ));
8026 assert!(text.contains("memcpy"), "{text}");
8027 assert_eq!(text.matches("iconst.i32 1\n").count(), 2, "{text}");
8028 assert_eq!(text.matches(" = and ").count(), 2, "{text}");
8029 assert!(!text.contains("call"), "{text}");
8030
8031 let text = body(concat!(
8034 "typedef char v4 __attribute__((vector_size(4)));\n",
8035 "v4 f(v4 a, v4 b, v4 m) { return __builtin_shuffle(a, b, m); }\n",
8036 ));
8037 assert_eq!(text.matches("iconst.i32 7\n").count(), 4, "{text}");
8038 assert!(text.contains("zext.i32"), "{text}");
8039 assert!(!text.contains("call"), "{text}");
8040 }
8041
8042 #[test]
8046 fn the_arguments_a_function_was_called_with_are_saved_on_the_way_in() {
8047 let text =
8048 mir("void *f(int a, double b) { (void)a; (void)b; return __builtin_apply_args(); }\n");
8049 assert!(text.matches("x64.mov_mr_64").count() >= 7, "{text}");
8051 assert!(text.matches("x64.movaps_mr").count() >= 8, "{text}");
8052 for reg in ["$rdi", "$rsi", "$rdx", "$rcx", "$r8", "$r9", "$xmm0", "$xmm7"] {
8053 assert!(text.contains(reg), "{reg} is not saved in\n{text}");
8054 }
8055
8056 let text = mir("int f(int a) { return a; }\n");
8058 assert!(!text.contains("movaps_mr"), "{text}");
8059 }
8060
8061 #[test]
8065 fn a_call_built_from_saved_arguments_loads_every_argument_register() {
8066 let text = mir(concat!(
8067 "void *g(void *args, void (*h)()) {\n",
8068 " return __builtin_apply(h, args, 64);\n",
8069 "}\n",
8070 ));
8071 assert!(text.matches("x64.mov_rm_64").count() >= 7, "{text}");
8072 assert!(text.matches("x64.movaps_rm").count() >= 8, "{text}");
8073 assert!(text.contains("call"), "{text}");
8074 assert!(text.matches("x64.movaps_mr").count() >= 2, "{text}");
8076
8077 let mut opts = options();
8079 opts.emit = EmitKind::Ir;
8080 let result = run(
8081 &opts,
8082 "void *g(void *a, void (*h)(), int n) { return __builtin_apply(h, a, n); }\n",
8083 );
8084 assert!(result.failed(), "{:?}", result.messages);
8085 assert!(
8086 result
8087 .messages
8088 .iter()
8089 .any(|m| m.contains("the size given to '__builtin_apply' is a constant")),
8090 "{:?}",
8091 result.messages
8092 );
8093 }
8094
8095 #[test]
8097 fn a_shuffle_refuses_what_gcc_refuses() {
8098 let mut opts = options();
8099 opts.emit = EmitKind::Ir;
8100 let source = concat!(
8101 "typedef int v4 __attribute__((vector_size(16)));\n",
8102 "typedef float f4 __attribute__((vector_size(16)));\n",
8103 "typedef short s8 __attribute__((vector_size(16)));\n",
8104 "typedef long long l4 __attribute__((vector_size(32)));\n",
8105 "void a(v4 x, f4 m) { __builtin_shuffle(x, m); }\n",
8106 "void b(int x, v4 m) { __builtin_shuffle(x, m); }\n",
8107 "void c(v4 x, f4 y, v4 m) { __builtin_shuffle(x, y, m); }\n",
8108 "void d(v4 x, s8 m) { __builtin_shuffle(x, m); }\n",
8109 "void e(f4 x, l4 m) { __builtin_shuffle(x, m); }\n",
8110 "void g(v4 x) { __builtin_shuffle(x); }\n",
8111 );
8112 let messages = run(&opts, source).messages;
8113 let wanted = [
8114 "last argument must be an integer vector [E0715]",
8115 "arguments must be vectors [E0715]",
8116 "argument vectors must be of the same type [E0715]",
8117 "number of elements of the argument vector(s) and the mask vector should be the same \
8118 [E0715]",
8119 "argument vector(s) inner type must have the same size as inner type of the mask \
8120 [E0715]",
8121 "too few arguments to function '__builtin_shuffle' [E0511]",
8122 ];
8123 assert_eq!(messages.len(), wanted.len(), "{messages:?}");
8124 for (message, wanted) in messages.iter().zip(wanted) {
8125 assert!(message.ends_with(wanted), "{message}");
8126 }
8127 }
8128
8129 #[test]
8138 fn the_plain_math_names_are_the_same_mask_and_not_a_call() {
8139 let text =
8140 body(concat!("double fabs(double x);\n", "double f(double x) { return fabs(x); }\n",));
8141 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
8142 assert!(!text.contains("call"), "{text}");
8143
8144 let text =
8145 body(concat!("float fabsf(float x);\n", "float f(float x) { return fabsf(x); }\n",));
8146 assert!(text.contains("bitcast.i32 %0"), "{text}");
8147 assert!(!text.contains("call"), "{text}");
8148
8149 let text = body(concat!(
8150 "double copysign(double x, double y);\n",
8151 "double f(double x, double y) { return copysign(x, y); }\n",
8152 ));
8153 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
8154 assert!(!text.contains("call"), "{text}");
8155
8156 let text = body(concat!(
8157 "float copysignf(float x, float y);\n",
8158 "float f(float x, float y) { return copysignf(x, y); }\n",
8159 ));
8160 assert!(!text.contains("call"), "{text}");
8161
8162 let text = ir(concat!(
8166 "long double fabsl(long double x);\n",
8167 "long double f(long double x) { return fabsl(x); }\n",
8168 ));
8169 assert!(text.contains("call @fabsl"), "{text}");
8170 }
8171
8172 #[test]
8180 fn a_plain_math_name_the_program_took_is_the_programs_own_function() {
8181 let taken = concat!(
8182 "static double fabs(double b) { return 7; }\n",
8183 "double f(double x) { return fabs(x); }\n",
8184 );
8185 assert!(ir(taken).contains("call @fabs"), "a static definition is the program's own");
8186
8187 let retyped = concat!("int fabs(int b);\n", "int f(int x) { return fabs(x); }\n");
8188 assert!(ir(retyped).contains("call @fabs"), "another type is another function");
8189
8190 let plain = concat!("double fabs(double b);\n", "double f(double x) { return fabs(x); }\n");
8191 let mut opts = options();
8192 opts.emit = EmitKind::Ir;
8193 assert!(!run(&opts, plain).text().contains("call @fabs"), "the library's by default");
8194
8195 opts.builtins = false;
8196 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin");
8197
8198 opts.builtins = true;
8199 opts.no_builtin = vec!["fabs".to_owned()];
8200 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin-fabs");
8201 let one = concat!(
8202 "double copysign(double a, double b);\n",
8203 "double f(double x) { return copysign(x, 1.0); }\n",
8204 );
8205 assert!(!run(&opts, one).text().contains("call @copysign"), "one name and not the family");
8206
8207 opts.no_builtin = Vec::new();
8209 opts.builtins = false;
8210 let prefixed = "double f(double x) { return __builtin_fabs(x); }\n";
8211 assert!(!run(&opts, prefixed).text().contains("call @fabs"), "the prefix is not a library");
8212 }
8213
8214 #[test]
8223 fn the_sign_builtins_answer_a_zero_and_a_nan_the_way_the_bits_say() {
8224 let text = ir(concat!(
8225 "double a = __builtin_fabs(-3.5);\n",
8226 "double b = __builtin_copysign(1.0, -0.0);\n",
8227 "double c = __builtin_copysign(0.0, -2.0);\n",
8228 "double d = __builtin_copysign(-__builtin_nan(\"\"), 1.0);\n",
8230 "double e = __builtin_fabs(-__builtin_nan(\"0x1\"));\n",
8231 "float g = __builtin_copysignf(-0.0f, 2.0f);\n",
8232 "long double h = __builtin_copysignl(1.0L, -1.0L);\n",
8233 "long double i = __builtin_fabsl(-__builtin_infl());\n",
8234 ));
8235 assert!(text.contains("global @a : f64 = 0x400c000000000000,"), "{text}");
8236 assert!(text.contains("global @b : f64 = 0xbff0000000000000,"), "{text}");
8237 assert!(text.contains("global @c : f64 = 0x8000000000000000,"), "{text}");
8238 assert!(text.contains("global @d : f64 = 0x7ff8000000000000,"), "{text}");
8239 assert!(text.contains("global @e : f64 = 0x7ff8000000000001,"), "{text}");
8240 assert!(text.contains("global @g : f32 = 0x0,"), "{text}");
8241 assert!(text.contains("f80 0xbfff8000000000000000"), "{text}");
8242 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
8243 }
8244
8245 #[test]
8252 fn the_sign_of_a_long_double_is_in_the_word_at_the_top_of_it() {
8253 for source in [
8254 "int f(long double x) { return __builtin_signbit(x); }\n",
8255 "long double f(long double x) { return __builtin_fabsl(x); }\n",
8256 "long double f(long double x, long double y) { return __builtin_copysignl(x, y); }\n",
8257 "int f(long double x) { return __builtin_isnormal(x); }\n",
8258 ] {
8259 let text = body(source);
8260 assert!(!text.contains("i80"), "{text}");
8261 assert!(text.contains("i16"), "{text}");
8262 }
8263 }
8264
8265 #[test]
8273 fn the_complex_builtins_are_the_halves_of_the_value_and_not_a_call() {
8274 let text = body("double f(_Complex double z) { return __builtin_creal(z); }\n");
8275 assert!(!text.contains("call"), "{text}");
8276 let text = body("double f(_Complex double z) { return __builtin_cimag(z); }\n");
8277 assert!(!text.contains("call"), "{text}");
8278
8279 let text = body("_Complex double f(_Complex double z) { return __builtin_conj(z); }\n");
8282 assert_eq!(text.matches("fneg").count(), 1, "{text}");
8283 assert!(!text.contains("call"), "{text}");
8284 let negated = body("_Complex double f(_Complex double z) { return -z; }\n");
8285 assert_eq!(negated.matches("fneg").count(), 2, "{negated}");
8286
8287 let written = body("_Complex double f(_Complex double z) { return ~z; }\n");
8290 assert_eq!(written, text, "the name and the operator are the same thing");
8291
8292 let text = body(concat!(
8294 "double creal(_Complex double z);\n",
8295 "double f(_Complex double z) { return creal(z); }\n",
8296 ));
8297 assert!(!text.contains("call"), "{text}");
8298 let text = body(concat!(
8299 "_Complex float conjf(_Complex float z);\n",
8300 "_Complex float f(_Complex float z) { return conjf(z); }\n",
8301 ));
8302 assert_eq!(text.matches("fneg").count(), 1, "{text}");
8303 assert!(!text.contains("call"), "{text}");
8304
8305 let taken = concat!(
8308 "static double creal(_Complex double z) { return 7; }\n",
8309 "double f(_Complex double z) { return creal(z); }\n",
8310 );
8311 assert!(ir(taken).contains("call @creal"), "a static definition is the program's own");
8312 let retyped = concat!("int cimag(int z);\n", "int f(int z) { return cimag(z); }\n");
8313 assert!(ir(retyped).contains("call @cimag"), "another type is another function");
8314 let plain = concat!(
8315 "double cimag(_Complex double z);\n",
8316 "double f(_Complex double z) { return cimag(z); }\n",
8317 );
8318 let mut opts = options();
8319 opts.emit = EmitKind::Ir;
8320 opts.builtins = false;
8321 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin");
8322 opts.builtins = true;
8323 opts.no_builtin = vec!["cimag".to_owned()];
8324 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin-cimag");
8325
8326 let text = ir(concat!(
8328 "double a = __builtin_creal(1.5 + 2.5i);\n",
8329 "double b = __builtin_cimag(1.5 + 2.5i);\n",
8330 "_Complex double c = __builtin_conj(1.5 + 2.5i);\n",
8331 ));
8332 assert!(text.contains("global @a : f64 = 0x3ff8000000000000,"), "{text}");
8333 assert!(text.contains("global @b : f64 = 0x4004000000000000,"), "{text}");
8334 assert!(
8335 text.contains("{ f64 0x3ff8000000000000, f64 0xc004000000000000 }"),
8336 "the conjugate of a constant is the constant with the second half negated: {text}"
8337 );
8338 assert!(!text.contains("call"), "{text}");
8339 }
8340
8341 #[test]
8349 fn a_math_library_builtin_of_a_constant_is_the_answer_and_not_a_call() {
8350 let text = ir(concat!(
8351 "double a = __builtin_ceil(1.5);\n",
8352 "double b = __builtin_floor(1.5);\n",
8353 "double c = __builtin_trunc(-1.5);\n",
8354 "double d = __builtin_round(2.5);\n",
8357 "double e = __builtin_ceil(-0.5);\n",
8359 "double f = __builtin_fmax(1.0, 2.0);\n",
8360 "double g = __builtin_fmin(1.0, 2.0);\n",
8361 "float h = __builtin_ceilf(1.25f);\n",
8362 "double ceil(double x);\n",
8365 "double i = ceil(2.25);\n",
8366 ));
8367 assert!(text.contains("global @a : f64 = 0x4000000000000000,"), "{text}");
8368 assert!(text.contains("global @b : f64 = 0x3ff0000000000000,"), "{text}");
8369 assert!(text.contains("global @c : f64 = 0xbff0000000000000,"), "{text}");
8370 assert!(text.contains("global @d : f64 = 0x4008000000000000,"), "{text}");
8371 assert!(text.contains("global @e : f64 = 0x8000000000000000,"), "{text}");
8372 assert!(text.contains("global @f : f64 = 0x4000000000000000,"), "{text}");
8373 assert!(text.contains("global @g : f64 = 0x3ff0000000000000,"), "{text}");
8374 assert!(text.contains("global @h : f32 = 0x40000000,"), "{text}");
8375 assert!(text.contains("global @i : f64 = 0x4008000000000000,"), "{text}");
8376 assert!(!text.contains("call"), "{text}");
8377 }
8378
8379 #[test]
8387 fn a_math_library_builtin_of_anything_else_is_a_call_to_the_library() {
8388 let text = ir(concat!(
8389 "double f(double x) { return __builtin_ceil(x); }\n",
8390 "float g(float x) { return __builtin_floorf(x); }\n",
8391 "double h(double x, double y) { return __builtin_fmax(x, y); }\n",
8392 ));
8393 assert!(text.contains("call @ceil("), "{text}");
8394 assert!(text.contains("call @floorf("), "{text}");
8395 assert!(text.contains("call @fmax("), "{text}");
8396
8397 let text = ir(concat!(
8401 "double f(void) { return __builtin_rint(2.5); }\n",
8402 "double g(void) { return __builtin_nearbyint(2.5); }\n",
8403 ));
8404 assert!(text.contains("call @rint("), "{text}");
8405 assert!(text.contains("call @nearbyint("), "{text}");
8406
8407 let text = ir("double f(void) { return __builtin_fmin(__builtin_nan(\"\"), 1.0); }\n");
8410 assert!(text.contains("call @fmin("), "{text}");
8411
8412 let plain = concat!("double ceil(double x);\n", "double f(void) { return ceil(2.25); }\n");
8415 let mut opts = options();
8416 opts.emit = EmitKind::Ir;
8417 opts.no_builtin = vec!["ceil".to_owned()];
8418 assert!(run(&opts, plain).text().contains("call @ceil("), "-fno-builtin-ceil");
8419 }
8420
8421 #[test]
8428 fn a_constexpr_object_is_a_constant_wherever_one_is_required() {
8429 let text = ir(concat!(
8430 "constexpr int side = 4;\n",
8431 "constexpr int wider = side + 1;\n",
8432 "constexpr double half = 1.5;\n",
8433 "struct point { int x; int y; };\n",
8434 "constexpr struct point origin = { 5, 6 };\n",
8435 "int square[side * side];\n",
8436 "int rectangle[wider];\n",
8437 "int rounded[(int)half * 2];\n",
8438 "int across[origin.y];\n",
8439 "enum named { four = side };\n",
8440 "int e = four;\n",
8441 ));
8442 assert!(text.contains("global @square : bytes 64 ="), "{text}");
8443 assert!(text.contains("global @rectangle : bytes 20 ="), "{text}");
8444 assert!(text.contains("global @rounded : bytes 8 ="), "{text}");
8445 assert!(text.contains("global @across : bytes 24 ="), "{text}");
8446 assert!(text.contains("global @e : i32 = 4,"), "{text}");
8447
8448 let mut opts = options();
8451 opts.emit = EmitKind::Ir;
8452 let konst = "const int n = 1;\nint a[n];\n";
8453 let message = "/main.c:2:5: error: variably modified 'a' at file scope [E0538]";
8454 assert_eq!(run(&opts, konst).messages, [message]);
8455
8456 let subscript = "constexpr int t[3] = { 1, 2, 3 };\nint a[t[1]];\n";
8458 assert_eq!(run(&opts, subscript).messages, [message]);
8459
8460 let address = "constexpr int c = 3;\nint *p = &c;\n";
8462 let warning = "/main.c:2:6: warning: initialization discards 'const' qualifier from \
8463 pointer target type [E0514]";
8464 assert_eq!(run(&opts, address).messages, [warning]);
8465 }
8466
8467 #[test]
8476 fn a_member_whose_size_was_refused_is_not_a_flexible_array_member() {
8477 let mut opts = options();
8478 opts.emit = EmitKind::Ir;
8479
8480 let alone = "int k;\nextern struct D { int a[k]; } ed;\n";
8481 let message = "/main.c:2:23: error: variably modified 'a' at file scope [E0538]";
8482 assert_eq!(run(&opts, alone).messages, [message]);
8483
8484 let first = "int k;\nextern struct E { int a[k]; int b; } ee;\n";
8486 assert_eq!(run(&opts, first).messages, [message]);
8487
8488 let negative = "struct F { int a[-1]; };\n";
8491 let refused = "/main.c:1:18: error: size of array 'a' is negative [E0536]";
8492 assert_eq!(run(&opts, negative).messages, [refused]);
8493
8494 let flexible = "struct G { int a[]; };\n";
8497 let named = "/main.c:1:16: error: flexible array member in a struct with no named \
8498 members [E0554]";
8499 assert_eq!(run(&opts, flexible).messages, [named]);
8500 }
8501
8502 #[test]
8517 fn a_pointer_to_an_array_gains_a_qualifier_the_same_way_a_pointer_to_anything_else_does() {
8518 let mut opts = options();
8519 opts.emit = EmitKind::Ir;
8520 let prefix = "typedef unsigned int B[4];\nstruct H { B category[2]; };\n";
8521
8522 let adding = format!("{prefix}const B *f(struct H *h) {{ return &h->category[0]; }}\n");
8524 assert_eq!(run(&opts, &adding).messages, [] as [String; 0]);
8525
8526 let plain = concat!(
8529 "const unsigned int (*f(unsigned int (*p)[4]))[4] { return p; }\n",
8530 "const unsigned int (*g(unsigned int (*p)[2][3]))[2][3] { return p; }\n",
8531 );
8532 assert_eq!(run(&opts, plain).messages, [] as [String; 0]);
8533
8534 let dropping = format!("{prefix}B *f(const B *p) {{ return p; }}\n");
8537 let warning = "/main.c:3:27: warning: return discards 'const' qualifier from pointer target type \
8538 [E0514]";
8539 assert_eq!(run(&opts, &dropping).messages, [warning]);
8540
8541 let wrong = "const unsigned int (*f(unsigned short (*p)[4]))[4] { return p; }\n";
8544 let error = "/main.c:1:61: error: returning 'unsigned short (*)[4]' from a function with \
8545 incompatible return type 'const unsigned int (*)[4]' [E0512]";
8546 assert_eq!(run(&opts, wrong).messages, [error]);
8547 }
8548
8549 #[test]
8558 fn an_old_style_definition_takes_its_types_from_the_declarations_under_its_list() {
8559 let mut opts = options();
8562 opts.std = Std::C17;
8563 let source = concat!(
8564 "int add(a, b)\n",
8565 "int a;\n",
8566 "int b;\n",
8567 "{ return a + b; }\n",
8568 "int promoted(c)\n",
8569 "char c;\n",
8570 "{ return c; }\n",
8571 "int narrow(char);\n",
8572 "int narrow(c)\n",
8573 "char c;\n",
8574 "{ return c; }\n",
8575 "int first(a)\n",
8576 "int a[4];\n",
8577 "{ return a[0]; }\n",
8578 );
8579 let result = run(&opts, source);
8580 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
8581 let text = result.text();
8582 assert!(text.contains("add : int(int, int) function external defined"), "{text}");
8583 assert!(text.contains("promoted : int(int) function external defined"), "{text}");
8584 assert!(text.contains("c : char object automatic defined"), "{text}");
8586 assert!(text.contains("narrow : int(char) function external defined"), "{text}");
8587 assert!(text.contains("first : int(int *) function external defined"), "{text}");
8589 }
8590
8591 #[test]
8598 fn the_two_halves_of_an_old_style_parameter_list_have_to_agree() {
8599 let mut opts = options();
8600 opts.std = Std::C17;
8601 for (source, message) in [
8602 ("int f(a, a)\nint a;\n{ return a; }\n", "1:10: error: multiple parameters named 'a'"),
8603 (
8604 "int f(a)\nint a;\nint b;\n{ return a; }\n",
8605 "3:5: error: declaration for parameter 'b' but no such parameter",
8606 ),
8607 ("int f(a)\nint a;\nint a;\n{ return a; }\n", "3:5: error: redefinition of parameter"),
8608 ("int f(a)\nint a = 1;\n{ return a; }\n", "2:5: error: parameter 'a' is initialized"),
8609 (
8610 "int f(a)\nstatic int a;\n{ return a; }\n",
8611 "2:12: error: storage class specified for parameter 'a'",
8612 ),
8613 (
8614 "int f(char);\nint f(a)\nshort a;\n{ return a; }\n",
8615 "2:7: error: argument 'a' doesn't match prototype",
8616 ),
8617 ] {
8618 let result = run(&opts, source);
8619 assert!(result.failed(), "expected this to fail:\n{source}");
8620 assert!(result.messages[0].contains(message), "{:?}", result.messages);
8621 }
8622
8623 let implicit = "int f(a, b)\nint a;\n{ return a + b; }\n";
8626 let mut older = options();
8627 older.std = Std::C89;
8628 assert!(!run(&older, implicit).failed(), "{:?}", run(&older, implicit).messages);
8629 let result = run(&opts, implicit);
8630 assert!(
8631 result.messages[0].contains("1:10: error: type of 'b' defaults to 'int'"),
8632 "{:?}",
8633 result.messages
8634 );
8635
8636 let mut newer = options();
8640 newer.std = Std::C23;
8641 let plain = "int f(a)\nint a;\n{ return a; }\n";
8642 let result = run(&newer, plain);
8643 assert!(!result.failed(), "{:?}", result.messages);
8644 assert_eq!(
8645 result.messages,
8646 ["/main.c:1:5: warning: old-style function definition [E0412]"]
8647 );
8648 assert!(run(&opts, plain).messages.is_empty(), "and nothing to say in the dialects before");
8649 }
8650
8651 #[test]
8658 fn the_obsolete_designators_are_taken_and_are_pedantic_warnings() {
8659 let array = "int a[8] = { [3] 7 };\n";
8660 let member = "struct s { int x; } v = { x: 7 };\n";
8661 for source in [array, member] {
8662 let result = run(&options(), source);
8663 assert!(!result.failed(), "{:?}", result.messages);
8664 assert!(result.messages.is_empty(), "nothing to say: {:?}", result.messages);
8665 }
8666
8667 let mut asked = options();
8668 asked.pedantic = true;
8669 assert_eq!(
8670 run(&asked, array).messages,
8671 ["/main.c:1:18: warning: obsolete designator, write `[i] =` instead [E0415]"]
8672 );
8673 assert_eq!(
8674 run(&asked, member).messages,
8675 ["/main.c:1:27: warning: obsolete designator, write `.field =` instead [E0413]"]
8676 );
8677 }
8678
8679 #[test]
8686 fn a_type_is_refused_when_it_passes_the_largest_object_and_not_before() {
8687 let text = ir(concat!(
8688 "struct huge_struct { short buf[(1L << 62) - 256]; int a, b, c, d; };\n",
8689 "struct brim { char buf[9223372036854775807L]; };\n",
8690 "struct bitty { char buf[9223372036854775800L]; int x : 1; };\n",
8691 "unsigned long h = sizeof(struct huge_struct);\n",
8692 "unsigned long b = sizeof(struct brim);\n",
8693 "unsigned long y = sizeof(struct bitty);\n",
8694 ));
8695 assert!(text.contains("global @h : i64 = 9223372036854775312,"), "{text}");
8696 assert!(text.contains("global @b : i64 = 9223372036854775807,"), "{text}");
8697 assert!(text.contains("global @y : i64 = 9223372036854775804,"), "{text}");
8698
8699 let mut opts = options();
8700 opts.emit = EmitKind::Ir;
8701 let over = "struct over { char buf[9223372036854775800L]; char x[8]; };\n";
8702 let message = "/main.c:1:1: error: type 'struct over' is too large [E0560]";
8703 assert_eq!(run(&opts, over).messages, [message]);
8704 let array = "struct wide { short buf[1L << 62]; };\n";
8705 let message = "/main.c:1:25: error: size of array 'buf' exceeds \
8706 maximum object size '9223372036854775807' [E0537]";
8707 assert_eq!(run(&opts, array).messages[0], message);
8708 }
8709
8710 fn compile_bytes(source: &[u8]) -> Compiled {
8715 let mut opts = options();
8716 opts.emit = EmitKind::Ir;
8717 let mut fs = MemoryFileSystem::new();
8718 fs.insert("/main.c", source.to_vec());
8719 compile(&opts, "/main.c", &fs)
8720 }
8721
8722 #[test]
8729 fn a_byte_that_is_not_a_character_is_kept_in_a_literal_and_refused_outside_one() {
8730 let mut source = b"char s[] = \"a".to_vec();
8731 source.push(0xff);
8732 source.extend_from_slice(b"b\";\nchar c = '");
8733 source.push(0xff);
8734 source.extend_from_slice(b"';\n");
8735 let result = compile_bytes(&source);
8736 assert_eq!(result.messages, Vec::<String>::new(), "a raw byte in a literal is that byte");
8737 assert!(result.text().contains(r#"bytes "a\ffb\00""#), "{}", result.text());
8738 assert!(result.text().contains("global @c : i8 = -1,"), "{}", result.text());
8740
8741 let mut stray = b"int a".to_vec();
8742 stray.push(0xff);
8743 stray.extend_from_slice(b" = 1;\n");
8744 let result = compile_bytes(&stray);
8745 assert!(
8746 result.messages.iter().any(|m| m.contains("source is not valid UTF-8 here")),
8747 "{:?}",
8748 result.messages
8749 );
8750 }
8751
8752 #[test]
8753 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
8754 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
8755 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
8756 let expected = "\
8757func @add(i32, i32) -> i32, linkage(external) {
8758block0(%0: i32, %1: i32):
8759 %2 = add.nsw %0, %1
8760 return %2
8761}
8762";
8763 assert!(text.contains(expected), "{text}");
8764 }
8765
8766 #[test]
8767 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
8768 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
8769 assert!(!text.contains("alloca"), "{text}");
8770 assert!(!text.contains("load"), "{text}");
8771 assert!(!text.contains("store"), "{text}");
8772 }
8773
8774 #[test]
8775 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
8776 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
8777 let expected = "\
8778block0:
8779 %0 = alloca, size 4, align 4
8780 %1 = iconst.i32 1
8781 store %1 -> %0, align 4, tbaa !1
8782 %2 = call @g(%0) : (ptr) -> i32
8783 return %2
8784";
8785 assert_eq!(text, expected);
8786 }
8787
8788 #[test]
8789 fn a_loop_carries_what_it_changes_as_block_parameters() {
8790 let text = body(
8793 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
8794 return total;\n}\n",
8795 );
8796 assert!(!text.contains("alloca"), "{text}");
8797 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
8798 assert!(text.contains("jump block1("), "{text}");
8799 }
8800
8801 #[test]
8802 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
8803 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
8804 assert!(text.contains("icmp slt %0, %1"), "{text}");
8805 assert!(!text.contains("zext"), "{text}");
8806 }
8807
8808 #[test]
8809 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
8810 let text = body("int f(int a, int b) { return a && b; }\n");
8811 let expected = "\
8812block0(%0: i32, %1: i32):
8813 %2 = iconst.i32 0
8814 %3 = icmp ne %0, %2
8815 %4 = iconst.i1 0
8816 br_if %3, block1, block2(%4)
8817
8818block1:
8819 %5 = iconst.i32 0
8820 %6 = icmp ne %1, %5
8821 jump block2(%6)
8822
8823block2(%7: i1):
8824 %8 = zext.i32 %7
8825 return %8
8826";
8827 assert_eq!(text, expected);
8828 }
8829
8830 #[test]
8831 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
8832 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
8833 assert!(!text.contains("block3"), "{text}");
8836 assert!(!text.contains("iconst.i32 3"), "{text}");
8837 }
8838
8839 #[test]
8840 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
8841 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
8842 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
8843 assert!(body("int f(void) { }\n").contains("unreachable"));
8844 }
8845
8846 #[test]
8847 fn a_structure_is_copied_rather_than_held_in_a_value() {
8848 let text = body(
8849 "struct point { int x, y; };\n\
8850 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
8851 );
8852 assert!(text.contains("memcpy"), "{text}");
8853 }
8854
8855 #[test]
8856 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
8857 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
8858 assert!(text.contains("memset"), "{text}");
8859 }
8860
8861 #[test]
8862 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
8863 let text = body(
8864 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
8865 default: r = 4; } return r; }\n",
8866 );
8867 let expected = "\
8868block0(%0: i32):
8869 %1 = iconst.i32 0
8870 switch %0, block1, [1 => block2, 2 => block3(%1)]
8871
8872block1:
8873 %2 = iconst.i32 4
8874 jump block4(%2)
8875
8876block2:
8877 %3 = iconst.i32 1
8878 jump block3(%3)
8879
8880block3(%4: i32):
8881 %5 = iconst.i32 2
8882 %6 = add.nsw %4, %5
8883 jump block4(%6)
8884
8885block4(%7: i32):
8886 return %7
8887";
8888 assert_eq!(text, expected);
8889 }
8890
8891 #[test]
8892 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
8893 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
8896 assert!(text.contains("%2 = sub %0, %1"), "{text}");
8897 assert!(text.contains("icmp ule"), "{text}");
8898 assert!(!text.contains("switch"), "{text}");
8899 }
8900
8901 #[test]
8902 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
8903 let text = body(
8904 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
8905 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
8906 );
8907 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
8910 assert!(text.contains("block5:\n jump block7("), "{text}");
8911 assert!(text.contains("block6:\n jump block8("), "{text}");
8912 }
8913
8914 #[test]
8915 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
8916 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
8917 }
8918
8919 #[test]
8920 fn a_label_a_loop_is_only_entered_through_builds_the_loop_around_it() {
8921 let text = body(
8926 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
8927 return n; }\n",
8928 );
8929 assert!(text.contains("switch %0, block1(%1), [1 => block2, 2 => block3(%1)]"), "{text}");
8932 assert!(text.contains("block3(%3: i32):\n %4 = iconst.i32 1"), "{text}");
8933 assert!(text.contains("block4:\n jump block3("), "{text}");
8934 }
8935
8936 #[test]
8937 fn a_goto_into_a_loop_body_enters_it_without_the_test() {
8938 let text = body("int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n");
8941 assert!(text.starts_with("block0(%0: i32, %1: i32):\n jump block1(%1)"), "{text}");
8942 assert!(text.contains("block1(%2: i32):\n %3 = iconst.i32 1"), "{text}");
8943 assert!(text.contains("br_if %6, block2, block3"), "{text}");
8944 }
8945
8946 #[test]
8947 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
8948 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
8949 assert!(!text.contains("alloca"), "{text}");
8953 assert!(text.contains("block2(%4: i32):\n return %4"), "{text}");
8954 assert_eq!(text.matches("jump block2(").count(), 2, "{text}");
8955 }
8956
8957 #[test]
8958 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
8959 let text =
8960 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
8961 assert!(!text.contains("alloca"), "{text}");
8962 assert!(text.contains("block1(%2: i32):"), "{text}");
8963 assert!(text.contains("jump block1(%5)"), "{text}");
8964 }
8965
8966 #[test]
8967 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
8968 assert_eq!(
8971 body("int f(int x) { return x; spare: return 0; }\n"),
8972 "block0(%0: i32):\n return %0\n"
8973 );
8974 }
8975
8976 #[test]
8977 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
8978 let text = body(
8979 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
8980 );
8981 assert_eq!(
8984 text,
8985 "\
8986block0(%0: ptr):
8987 %1 = load.i8 %0, align 1
8988 %2 = iconst.i8 3
8989 %3 = ashr %1, %2
8990 %4 = sext.i32 %3
8991 return %4
8992"
8993 );
8994 }
8995
8996 #[test]
8997 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
8998 let text =
9002 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
9003 assert_eq!(
9004 text,
9005 "\
9006block0(%0: ptr, %1: i32):
9007 %2 = iconst.i32 16777215
9008 %3 = and %1, %2
9009 %4 = trunc.i16 %3
9010 store %4 -> %0, align 2
9011 %5 = iconst.i32 16
9012 %6 = lshr %3, %5
9013 %7 = trunc.i8 %6
9014 %8 = iconst.i64 2
9015 %9 = ptr_add %0, %8
9016 store %7 -> %9, align 1
9017 return
9018"
9019 );
9020 }
9021
9022 #[test]
9023 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
9024 let text =
9025 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
9026 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
9029 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
9030 }
9031
9032 #[test]
9033 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
9034 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
9037 assert_eq!(text.matches("ashr").count(), 0, "{text}");
9038 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
9039 }
9040
9041 #[test]
9042 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
9043 let text = body(
9047 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
9048 );
9049 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
9050 }
9051
9052 #[test]
9053 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
9054 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
9057 assert!(
9058 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
9059 "{text}"
9060 );
9061 }
9062
9063 #[test]
9064 fn an_initialized_flexible_array_member_makes_the_object_larger_than_its_type() {
9065 let text = ir(concat!(
9070 "struct a { int i; int j[]; } x = { 1, { 2, 0, 2, 3 } };\n",
9071 "struct b { char c; char p[]; } y = { 'o', \"wx\" };\n",
9072 "struct c { char c; char p[]; } z = { '9', { 'e', 'b' } };\n",
9073 "char s[2] = \"hi\";\n",
9074 ));
9075 assert!(
9076 text.contains("global @x : bytes 20 = { i32 1, i32 2, i32 0, i32 2, i32 3 }"),
9077 "{text}"
9078 );
9079 assert!(text.contains("global @y : bytes 4 = { i8 111, bytes \"wx\\00\" }"), "{text}");
9080 assert!(text.contains("global @z : bytes 3 = { i8 57, i8 101, i8 98 }"), "{text}");
9081 assert!(text.contains("global @s : bytes 2 = { bytes \"hi\" }"), "{text}");
9084 }
9085
9086 #[test]
9087 fn a_definition_takes_a_parameter_it_left_unnamed() {
9088 let text = ir("int f(int a, int) { return a; }\n");
9092 assert!(text.contains("func @f(i32, i32) -> i32"), "{text}");
9093 assert!(text.contains("block0(%0: i32, %1: i32):"), "{text}");
9094
9095 let text = ir("int g(int, int n) { return n; }\n");
9098 assert!(text.contains("block0(%0: i32, %1: i32):\n return %1\n"), "{text}");
9099 }
9100
9101 #[test]
9102 fn an_assignment_of_a_structure_is_the_object_it_wrote() {
9103 let text = body(concat!(
9108 "struct s { int f; int g; };\n",
9109 "void h(struct s *a, struct s *c, struct s *d, struct s *e)\n",
9110 "{ *d = *e = a[0] = *c; }\n",
9111 ));
9112 assert_eq!(text.matches("memcpy").count(), 3, "{text}");
9113 assert!(text.contains("memcpy %8, %1, size 8, align 4\n"), "{text}");
9114 assert!(text.contains("memcpy %3, %8, size 8, align 4\n"), "{text}");
9115 assert!(text.contains("memcpy %2, %3, size 8, align 4\n"), "{text}");
9116 }
9117
9118 #[test]
9119 fn a_string_literal_stops_at_the_end_of_the_array_it_is_filling() {
9120 let mut opts = options();
9125 opts.emit = EmitKind::Ir;
9126 let result = run(
9127 &opts,
9128 concat!(
9129 "const char a[2][3] = { \"1234\", \"xyz\" };\n",
9130 "static const char b[3][5] = { \"12345\", \"678\", \"9\" };\n",
9131 "union u { struct { char x[4]; char y[4]; }; struct { char z[8]; }; };\n",
9132 "const union u c = { { \"1234\", \"567\" } };\n",
9133 ),
9134 );
9135 let text = result.text();
9136 assert_eq!(
9137 result.messages,
9138 ["/main.c:1:24: warning: initializer-string for array of 'const char' is too long \
9139 (5 chars into 3 available) [E0637]"]
9140 );
9141 assert!(text.contains("global @a : bytes 6 = { bytes \"123\", bytes \"xyz\" }"), "{text}");
9142 assert!(
9143 text.contains(
9144 "global @b : bytes 15 = { bytes \"12345\", bytes \"678\\00\", zero 1, \
9145 bytes \"9\\00\", zero 3 }"
9146 ),
9147 "{text}"
9148 );
9149 assert!(
9152 text.contains("global @c : bytes 8 = { bytes \"1234\", bytes \"567\\00\" }"),
9153 "{text}"
9154 );
9155 }
9156
9157 #[test]
9158 fn a_cast_of_a_record_to_its_own_type_is_the_object_that_was_cast() {
9159 let text = body(concat!(
9163 "struct s { int a, b; };\nstruct v { struct s s; int t; };\n",
9164 "void g(struct v *);\n",
9165 "void f(struct s *p) { struct v w = { (struct s)*p, 5 }; g(&w); }\n",
9166 ));
9167 assert_eq!(text.matches("memcpy").count(), 1, "{text}");
9168 }
9169
9170 #[test]
9171 fn a_compound_literal_read_in_a_static_initializer_lays_its_bytes_into_the_image() {
9172 let text = ir(concat!(
9177 "struct s { int x; };\n",
9178 "struct t { struct s s; int o; } a = { (struct s){ 2 }, 3 };\n",
9179 "int n = (int){ 7 };\n",
9180 "struct u { struct s p; struct s q; } b = { (struct s){ 1 }, (struct s){ } };\n",
9181 ));
9182 assert!(text.contains("global @a : bytes 8 = { i32 2, i32 3 }"), "{text}");
9183 assert!(text.contains("global @n : i32 = 7,"), "{text}");
9184 assert!(text.contains("global @b : bytes 8 = { i32 1, zero 4 }"), "{text}");
9187 }
9188
9189 #[test]
9190 fn the_address_of_a_compound_literal_asks_for_the_object_it_points_at() {
9191 let text = ir("struct s { int x; };\nstruct s *q = &(struct s){ 9 };\n");
9195 assert!(text.contains("global @.Lanon.0 : i32 = 9, align 4, linkage(internal)"), "{text}");
9196 assert!(text.contains("global @q : bytes 8 = { addr.8 @.Lanon.0 }"), "{text}");
9197 }
9198
9199 #[test]
9200 fn an_object_of_no_size_at_all_has_an_image_with_nothing_in_it() {
9201 let text = ir("unsigned char foo[1][0];\n");
9205 assert!(text.contains("global @foo : bytes 0 = {}, align 1"), "{text}");
9206 }
9207
9208 #[test]
9209 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
9210 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
9213 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
9214 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
9215 }
9216
9217 #[test]
9218 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
9219 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
9223 assert!(
9224 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
9225 "{text}"
9226 );
9227 }
9228
9229 #[test]
9230 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
9231 let text = body(
9236 "\
9237struct s { int a, b; };
9238struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
9239",
9240 );
9241 assert!(text.contains("block3(%7: ptr)"), "{text}");
9243 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
9244 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
9245 }
9246
9247 #[test]
9255 fn the_left_side_of_a_conditional_with_no_middle_is_evaluated_once() {
9256 let text = body("int f(int i) { return ++i ?: 10; }\n");
9257 assert!(text.contains("jump block3(%2)"), "the arm is the value that was tested: {text}");
9258 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
9259
9260 let text = body("long f(int i) { return ++i ?: 10L; }\n");
9263 assert!(text.contains("%5 = sext.i64 %2"), "the arm widens what was tested: {text}");
9264 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
9265
9266 let text = body("int g(void);\nint f(void) { return g() ?: 10; }\n");
9268 assert_eq!(text.matches("call @g").count(), 1, "called once: {text}");
9269
9270 let text = body("int f(int i) { return ++i ? ++i : 10; }\n");
9273 assert_eq!(text.matches("add.nsw").count(), 2, "incremented twice: {text}");
9274 }
9275
9276 #[test]
9277 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
9278 let text = ir("\
9282struct pair { int a, b; };
9283struct pair make(int a, int b);
9284struct pair twice(struct pair p) { return make(p.a, p.b); }
9285");
9286 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
9287 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
9288 }
9289
9290 #[test]
9291 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
9292 let text = ir("\
9296struct big { double v[8]; };
9297struct big grow(struct big b);
9298struct big twice(struct big b) { return grow(grow(b)); }
9299");
9300 assert!(
9301 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
9302 "{text}"
9303 );
9304 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
9305 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
9308 }
9309
9310 #[test]
9311 fn a_structure_passed_to_a_variadic_function_says_so_at_the_call() {
9312 let text = ir("\
9317struct big { double v[8]; };
9318struct pair { int a, b; };
9319int p(const char *, ...);
9320int f(struct big b, struct pair q) { return p(\"\", 1, b, q); }
9321");
9322 assert!(
9323 text.contains("call @p(%4, %5, %2 byval(64, align 8), %6) : (ptr, ...) -> i32"),
9324 "{text}"
9325 );
9326 }
9327
9328 #[test]
9329 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
9330 let body = body(
9333 "\
9334struct pair { int a, b; };
9335struct pair make(int a, int b);
9336int second(void) { return make(1, 2).b; }
9337",
9338 );
9339 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
9340 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
9341 }
9342
9343 #[test]
9344 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
9345 let source = "\
9349struct hfa { float x, y, z; };
9350int take(struct hfa h);
9351int give(struct hfa h) { return take(h); }
9352";
9353 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
9354 let mut opts = options();
9355 opts.emit = EmitKind::Ir;
9356 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
9357 let result = run(&opts, source);
9358 assert_eq!(result.messages, Vec::<String>::new());
9359 assert!(result.text().contains("func @take(f32, f32, f32) -> i32"), "{}", result.text());
9360 }
9361
9362 #[test]
9363 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
9364 let source = "\
9367int use(int *);
9368void f(int n) {
9369 {
9370 int a[n];
9371 use(a);
9372 }
9373 use(0);
9374}
9375";
9376 let body = body(source);
9377 assert!(body.contains("mul.nsw"), "{body}");
9378 assert!(body.contains("stacksave"), "{body}");
9379 assert!(body.contains("alloca %"), "{body}");
9380 assert!(body.contains("stackrestore"), "{body}");
9381 }
9382
9383 #[test]
9384 fn a_goto_out_of_the_scope_of_one_gives_its_stack_back_on_the_way() {
9385 let source = "\
9390int use(int *);
9391int f(int n) {
9392 {
9393 int a[n];
9394 if (use(a)) goto out;
9395 use(0);
9396 }
9397out:
9398 return 0;
9399}
9400";
9401 let body = body(source);
9402 assert_eq!(body.matches("stackrestore").count(), 2, "{body}");
9404 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
9405 assert!(after.starts_with(" %4\n jump block"), "{body}");
9406 }
9407
9408 #[test]
9409 fn a_goto_to_a_label_the_array_is_still_alive_at_leaves_the_stack_alone() {
9410 let source = "\
9414int use(int *);
9415int f(int n) {
9416 int a[n];
9417again:
9418 if (use(a)) goto again;
9419 return 0;
9420}
9421";
9422 let body = body(source);
9423 assert!(body.contains("stacksave"), "{body}");
9424 assert!(!body.contains("stackrestore"), "{body}");
9425 }
9426
9427 #[test]
9428 fn a_goto_back_to_a_label_in_front_of_one_gives_it_back_every_time_round() {
9429 let source = "\
9434int use(int *);
9435int f(int n) {
9436again:
9437 {
9438 int a[n];
9439 if (use(a)) goto again;
9440 }
9441 return 0;
9442}
9443";
9444 let body = body(source);
9445 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
9446 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
9447 assert!(after.starts_with(" %4\n jump block1\n"), "{body}");
9448 }
9449
9450 #[test]
9451 fn the_head_of_a_for_loop_is_a_scope_that_closes_where_the_loop_is_left() {
9452 let source = "\
9458int f(void);
9459void t(void) {
9460 int count = 10;
9461 for (; count--;) {
9462 int b[f()];
9463 int i;
9464 for (i = 0; i < f(); i++) {
9465 b[i] = count;
9466 }
9467 }
9468}
9469";
9470 let body = body(source);
9471 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
9475 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
9476 let next = after.split("\n\n").next().expect("the block the restore is in");
9479 assert!(next.contains("jump block1("), "{body}");
9480 }
9481
9482 #[test]
9483 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
9484 let source = "\
9487unsigned long f(int n) {
9488 int a[n];
9489 n = 0;
9490 return sizeof a;
9491}
9492";
9493 let body = body(source);
9494 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
9496 }
9497
9498 #[test]
9499 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
9500 let source = "\
9503int use(int);
9504int f(int x) {
9505 return ({
9506 int t = use(x);
9507 t * t;
9508 });
9509}
9510";
9511 let expected = "\
9512block0(%0: i32):
9513 %1 = call @use(%0) : (i32) -> i32
9514 %2 = mul.nsw %1, %1
9515 return %2
9516";
9517 assert_eq!(body(source), expected);
9518 }
9519
9520 #[test]
9521 fn a_comma_whose_value_is_an_object_names_the_object_the_right_side_named() {
9522 let source = "\
9526struct pair { int a, b; };
9527void bail(void);
9528int f(struct pair p) {
9529 return (bail(), p).b;
9530}
9531";
9532 let expected = "\
9533block0(%0: i64):
9534 %1 = alloca, size 8, align 4
9535 store %0 -> %1, align 4
9536 call @bail() : ()
9537 %2 = iconst.i64 4
9538 %3 = ptr_add %1, %2
9539 %4 = load.i32 %3, align 4, tbaa !1
9540 return %4
9541";
9542 assert_eq!(body(source), expected);
9543 }
9544
9545 #[test]
9546 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
9547 let source = "int f(int x) { return ({ return x; 0; }); }\n";
9551 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
9552 }
9553
9554 #[test]
9555 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
9556 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
9560 let expected = "\
9561block0(%0: ptr):
9562 %1 = va_arg.f64 %0
9563 %2 = va_arg.f64 %0
9564 %3 = fadd %1, %2
9565 return %3
9566";
9567 assert_eq!(body(source), expected);
9568 }
9569
9570 #[test]
9571 fn one_that_reads_a_structure_answers_where_the_object_is() {
9572 let source = "\
9586struct s { int a; long b; };
9587long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.b; }
9588";
9589 let expected = "\
9590block0(%0: ptr):
9591 %1 = alloca, size 16, align 16
9592 %2 = va_object %0, size 16, align 8, in(int 8 at 0, int 8 at 8)
9593 memcpy %1, %2, size 16, align 8
9594 %3 = iconst.i64 8
9595 %4 = ptr_add %1, %3
9596 %5 = load.i64 %4, align 8, tbaa !1
9597 return %5
9598";
9599 assert_eq!(body(source), expected);
9600 }
9601
9602 #[test]
9606 fn the_classification_says_which_registers_the_object_arrived_in() {
9607 let source = "\
9608struct s { double a; double b; };
9609double f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a; }
9610";
9611 assert!(
9612 body(source)
9613 .contains("va_object %0, size 16, align 8, in(float f64 at 0, float f64 at 8)"),
9614 "{}",
9615 body(source)
9616 );
9617
9618 let big = "\
9619struct s { long a[4]; };
9620long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a[0]; }
9621";
9622 assert!(body(big).contains("va_object %0, size 32, align 8\n"), "{}", body(big));
9623 }
9624
9625 #[test]
9626 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
9627 let source = "\
9631int f(int c) {
9632 void *p = c ? &&one : &&two;
9633 goto *p;
9634one:
9635 return 1;
9636two:
9637 return 2;
9638}
9639";
9640 let expected = "\
9641block0(%0: i32):
9642 %1 = iconst.i32 0
9643 %2 = icmp ne %0, %1
9644 br_if %2, block1, block2
9645
9646block1:
9647 %3 = block_addr block3
9648 jump block4(%3)
9649
9650block2:
9651 %4 = block_addr block5
9652 jump block4(%4)
9653
9654block3:
9655 %5 = iconst.i32 1
9656 return %5
9657
9658block4(%6: ptr):
9659 indirect_br %6, block3, block5
9660
9661block5:
9662 %7 = iconst.i32 2
9663 return %7
9664";
9665 assert_eq!(body(source), expected);
9666 }
9667
9668 fn dispatch(labels: usize) -> String {
9671 let mask = labels - 1;
9672 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
9673 for index in 0..labels {
9674 source.push_str(&format!(" &&a{index},"));
9675 }
9676 source.push_str(" };\n\tint w = n, x = n + 1, y = n + 2, z = n + 3;\n");
9677 source.push_str(&format!("\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
9678 for index in 0..labels {
9679 let step = match index % 4 {
9680 0 => "w += x;",
9681 1 => "x += y;",
9682 2 => "y += z;",
9683 _ => "z += w;",
9684 };
9685 source.push_str(&format!("a{index}:\n\t{step}\n"));
9686 source.push_str("\tif (--n <= 0) return w + x + y + z;\n");
9687 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
9688 }
9689 source.push_str("}\n");
9690 source
9691 }
9692
9693 fn in_front_of_the_jump(text: &str) -> usize {
9695 let (before, _) = text.split_once("\tjmp\t*%").expect("a jump through a register");
9696 before.lines().rev().take_while(|line| line.starts_with("\tmov")).count()
9697 }
9698
9699 #[test]
9709 fn a_jump_through_a_register_writes_what_it_carries_and_not_the_whole_table() {
9710 let small = in_front_of_the_jump(&asm(&dispatch(4)));
9711 let large = in_front_of_the_jump(&asm(&dispatch(32)));
9712 assert_eq!(small, large, "eight times the labels and the same values in hand");
9713 assert!(large <= 8, "the values the loop keeps, and not a set of them per label: {large}");
9714 }
9715
9716 fn crowded(labels: usize) -> String {
9719 const VALUES: usize = 24;
9720 let mask = labels - 1;
9721 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
9722 for index in 0..labels {
9723 source.push_str(&format!(" &&a{index},"));
9724 }
9725 source.push_str(" };\n\t");
9726 for value in 0..VALUES {
9727 source.push_str(&format!("int v{value} = n + {value}; "));
9728 }
9729 let sum: Vec<String> = (0..VALUES).map(|value| format!("v{value}")).collect();
9730 source.push_str(&format!("\n\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
9731 for index in 0..labels {
9732 let (to, from) = (index % VALUES, (index + 1) % VALUES);
9733 source.push_str(&format!("a{index}:\n\tv{to} += v{from};\n"));
9734 source.push_str(&format!("\tif (--n <= 0) return {};\n", sum.join(" + ")));
9735 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
9736 }
9737 source.push_str("}\n");
9738 source
9739 }
9740
9741 fn the_frame(text: &str) -> u64 {
9743 text.lines()
9744 .find_map(|line| {
9745 let (size, _) = line.strip_prefix("\tsubq\t$")?.split_once(", %rsp")?;
9746 size.parse().ok()
9747 })
9748 .expect("a function that opens a frame")
9749 }
9750
9751 #[test]
9760 fn a_frame_holds_what_is_wanted_at_once_and_not_a_slot_for_every_label() {
9761 let small = the_frame(&asm(&crowded(16)));
9762 let large = the_frame(&asm(&crowded(64)));
9763 assert_eq!(small, large, "four times the labels and the same values: {small}, {large}");
9764 }
9765
9766 #[test]
9775 fn a_template_that_names_its_own_registers_gets_the_ones_it_named() {
9776 let source = "void save(void *nlr) {
9777 __asm volatile (
9778 \"movq %%rsp, 32(%%rdi) \\n\"
9779 \"movq %%rbx, 40(%%rdi) \\n\"
9780 \"movq %%r12, 48(%%rdi) \\n\"
9781 : : \"D\" (nlr) : \"memory\");
9782}
9783";
9784 let text = asm(source);
9785 assert!(text.contains("\tmovq\t%rsp, 32(%rdi)\n"), "{text}");
9786 assert!(text.contains("\tmovq\t%rbx, 40(%rdi)\n"), "{text}");
9787 assert!(text.contains("\tmovq\t%r12, 48(%rdi)\n"), "{text}");
9788 }
9789
9790 #[test]
9791 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
9792 let source = "void **next(void);
9795void f(void) { goto *next(); }
9796";
9797 let expected = "\
9798block0:
9799 %0 = call @next() : () -> ptr
9800 unreachable
9801";
9802 assert_eq!(body(source), expected);
9803 }
9804
9805 #[test]
9806 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
9807 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
9810 let expected = "\
9811block0:
9812 inline_asm.volatile \"mfence\", \"\", \"memory\"()
9813 return
9814";
9815 assert_eq!(body(source), expected);
9816 }
9817
9818 #[test]
9819 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
9820 let source = "\
9823int f(int x, int y) {
9824 int r;
9825 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
9826 return r + y;
9827}
9828";
9829 let expected = "\
9830block0(%0: i32, %1: i32):
9831 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
9832 %4 = add.nsw %2, %3
9833 return %4
9834";
9835 assert_eq!(body(source), expected);
9836 }
9837
9838 #[test]
9839 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
9840 let source = "\
9845struct pair { int a, b; };
9846int f(int x) {
9847 int slot = x;
9848 struct pair p = { x, x };
9849 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
9850 return slot + p.a;
9851}
9852";
9853 let text = body(source);
9854 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
9855 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
9856 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
9857 }
9858
9859 #[test]
9860 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
9861 let source = "\
9866int f(int x) {
9867 int r = 7;
9868 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
9869 return r;
9870away:
9871 return r;
9872}
9873";
9874 let expected = "\
9875block0(%0: i32):
9876 %1 = iconst.i32 7
9877 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
9878
9879block1:
9880 return %2
9881
9882block2:
9883 return %1
9884";
9885 assert_eq!(body(source), expected);
9886 }
9887
9888 #[test]
9889 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
9890 let mut opts = options();
9894 opts.emit = EmitKind::Ir;
9895 for (source, expected) in [
9896 (
9897 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
9898 "output operand constraint lacks '='",
9899 ),
9900 (
9901 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
9902 "lvalue required in 'asm' statement",
9903 ),
9904 (
9905 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
9906 "read-only variable 'g' used as 'asm' output",
9907 ),
9908 (
9909 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
9910 "input operand constraint contains '='",
9911 ),
9912 (
9913 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
9914 "memory input 0 is not directly addressable",
9915 ),
9916 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
9917 (
9918 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
9919 "duplicate asm operand name 'a'",
9920 ),
9921 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
9922 ] {
9923 let result = run(&opts, source);
9924 assert!(result.failed(), "expected this to be reported:\n{source}");
9925 assert!(
9926 result.messages.iter().any(|m| m.contains(expected)),
9927 "{expected}\n{:?}",
9928 result.messages
9929 );
9930 }
9931 }
9932
9933 #[test]
9938 fn an_asm_at_file_scope_that_is_directives_becomes_the_objects_it_defines() {
9939 let text = ir(concat!(
9940 "__asm__(\n",
9941 " \".section .rodata\\n\"\n",
9942 " \".globl first\\n\"\n",
9943 " \".balign 8\\n\"\n",
9944 " \"first:\\n\"\n",
9945 " \".long 1\\n\"\n",
9946 " \".long 2\\n\"\n",
9947 " \".globl last\\n\"\n",
9948 " \"last:\\n\"\n",
9949 " \".quad last - first\\n\");\n",
9950 "extern const int first[];\n",
9951 "extern const long last;\n",
9952 ));
9953 assert!(text.contains("global @first : bytes 8 = { i32 1, i32 2 }, align 8"), "{text}");
9954 assert!(text.contains("global @last : i64 = 8"), "{text}");
9955 }
9956
9957 #[test]
9961 fn a_name_an_asm_at_file_scope_defined_is_not_undone_by_a_declaration_of_it() {
9962 let text = ir(concat!(
9963 "__asm__(\".data\\n.globl counter\\ncounter:\\n.long 7\\n\");\n",
9964 "extern int counter;\n",
9965 "int read(void) { return counter; }\n",
9966 ));
9967 assert!(text.contains("global @counter : i32 = 7"), "{text}");
9968 }
9969
9970 #[test]
9975 fn bytes_under_no_label_at_file_scope_are_a_global_in_front_of_the_label() {
9976 let text = ir(concat!(
9977 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n662:\\n",
9978 ".pushsection .data.ignore\\n.byte 7\\n.popsection\\n.byte 662b - 661b\\n\");\n",
9979 "extern unsigned char stuff[];\n",
9980 "int read(void) { return stuff[0]; }\n",
9981 ));
9982 let under = text.find("global @.Lasm.0 : i8 = 41").expect(&text);
9983 let named = text.find("global @stuff : i8 = 42").expect(&text);
9984 assert!(under < named, "the bytes under no label come first: {text}");
9985 assert!(text.contains("global @.Lasm.1 : i8 = 7, align 1, linkage(internal), section"));
9986 let after = text.find("global @.Lasm.2 : i8 = 1").expect(&text);
9991 assert!(named < after, "{text}");
9992 }
9993
9994 #[test]
9999 fn a_distance_from_here_at_file_scope_is_a_hole_naming_the_global_it_measures_to() {
10000 let text = ir(concat!(
10001 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n",
10002 ".pushsection .data.ignore\\n.long 661b - .\\n.popsection\\n\");\n",
10003 "extern unsigned char stuff[];\n",
10004 "int read(void) { return stuff[0]; }\n",
10005 ));
10006 assert!(text.contains("global @.Lasm.1 : bytes 4 = { away.4 @stuff }"), "{text}");
10010 }
10011
10012 #[test]
10017 fn a_set_at_file_scope_is_a_second_name_for_what_it_names() {
10018 let text = ir(concat!(
10019 "void base(void) {}\n",
10020 "__asm__(\".weak one\\n.set one, base\");\n",
10021 "__asm__(\".globl two\\n.set two, base\");\n",
10022 "__asm__(\".set three, base\");\n",
10023 "void three(void) {}\n",
10024 ));
10025 assert!(text.contains("alias @one = @base, linkage(weak)"), "{text}");
10026 assert!(text.contains("alias @two = @base"), "{text}");
10027 assert!(!text.contains("alias @three"), "a definition of the name wins: {text}");
10028 assert!(text.contains("func @three"), "{text}");
10029 }
10030
10031 #[test]
10036 fn a_set_of_a_name_this_file_does_not_define_says_so() {
10037 let messages = errors("__asm__(\".set here, elsewhere\");\n");
10038 assert!(
10039 messages
10040 .iter()
10041 .any(|m| m.contains("'here' is aliased to undefined symbol 'elsewhere'")
10042 && m.contains("E0697")),
10043 "{messages:?}"
10044 );
10045 }
10046
10047 #[test]
10050 fn an_incbin_at_file_scope_is_the_bytes_of_the_file_it_names() {
10051 let mut opts = options();
10052 opts.emit = EmitKind::Ir;
10053 let mut fs = MemoryFileSystem::new();
10054 fs.insert(
10055 "/main.c",
10056 b"__asm__(\".data\\n.globl blob\\nblob:\\n.incbin \\\"seed\\\"\\n\");\n".to_vec(),
10057 );
10058 fs.insert("seed", b"hi".to_vec());
10059 let result = compile(&opts, "/main.c", &fs);
10060 assert_eq!(result.messages, Vec::<String>::new());
10061 let text = result.text();
10062 assert!(text.contains("global @blob : bytes 2 = { bytes \"hi\" }"), "{text}");
10063 }
10064
10065 #[test]
10068 fn an_incbin_naming_a_file_that_is_not_there_says_which_file() {
10069 let messages = errors("__asm__(\".data\\nb:\\n.incbin \\\"nowhere\\\"\\n\");\n");
10070 assert!(
10071 messages
10072 .iter()
10073 .any(|m| m.contains("cannot open 'nowhere' for reading") && m.contains("E0702")),
10074 "{messages:?}"
10075 );
10076 }
10077
10078 #[test]
10082 fn a_directive_in_an_asm_at_file_scope_is_refused_rather_than_ignored() {
10083 let source = "__asm__(\".data\\n.set alias, 4\\n\");\n";
10084 let messages = errors(source);
10085 assert!(
10086 messages
10087 .iter()
10088 .any(|m| m.contains("not supported yet") && m.contains("in an `asm` at file scope")),
10089 "{source}\n{messages:?}"
10090 );
10091 }
10092
10093 #[test]
10103 fn a_naked_function_is_its_own_prologue_and_its_own_ending() {
10104 let text = asm(concat!(
10105 "unsigned nlr_push_tail(void *nlr);\n",
10106 "__attribute__((naked)) unsigned nlr_push(void *nlr) {\n",
10107 " __asm volatile(\n",
10108 " \"movq (%rsp), %rax\\n\"\n",
10109 " \"movq %rax, 16(%rdi)\\n\"\n",
10110 " \"movq %rbx, 40(%rdi)\\n\"\n",
10111 " \"jmp nlr_push_tail\\n\");\n",
10112 "}\n",
10113 ));
10114 assert!(text.contains("\tmovq\t(%rsp), %rax\n"), "{text}");
10115 assert!(text.contains("\tjmp\tnlr_push_tail\n"), "{text}");
10116 assert!(text.contains("\tud2\n"), "{text}");
10117 assert!(!text.contains("\tpushq\t"), "nothing is saved in front of it: {text}");
10118 assert!(!text.contains("\tret\n"), "the jump is where it ends: {text}");
10119 }
10120
10121 #[test]
10124 fn what_a_function_without_a_prologue_cannot_be_given_is_refused() {
10125 let mut opts = options();
10126 opts.emit = EmitKind::Asm;
10127 for (source, why) in [
10128 (
10129 "__attribute__((naked)) void f(void) { volatile long a[8]; a[0] = 1; }\n",
10130 "bytes of frame",
10131 ),
10132 (
10133 "__attribute__((naked)) void f(int n) { char a[n]; __asm(\"nop\" ::\"r\"(a)); }\n",
10134 "has no prologue to point a frame pointer at it with",
10135 ),
10136 ("void elsewhere(void); void f(void) { __asm(\"jmp elsewhere\"); }\n", "jumps out of"),
10137 ] {
10138 let result = run(&opts, source);
10139 assert!(result.failed(), "expected this to be refused:\n{source}");
10140 assert!(
10141 result.messages.iter().any(|message| message.contains(why)),
10142 "{:?}",
10143 result.messages
10144 );
10145 }
10146 }
10147
10148 #[test]
10149 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
10150 let mut opts = options();
10151 opts.emit = EmitKind::Ir;
10152 for source in [
10153 "int f(int n) { void *p = &&out; if (n) goto *p; { int a[n]; out: return 1; } }\n",
10154 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
10155 ] {
10156 let result = run(&opts, source);
10157 assert!(result.failed(), "expected this to be reported:\n{source}");
10158 assert!(
10159 result.messages.iter().any(|m| m.contains("not supported yet")),
10160 "{:?}",
10161 result.messages
10162 );
10163 }
10164 }
10165
10166 fn round_trip(source: &str) -> (String, String) {
10168 let printed = ir(source);
10169 let mut opts = options();
10170 opts.emit = EmitKind::Ir;
10171 let mut fs = MemoryFileSystem::new();
10172 fs.insert("/main.ir", printed.clone().into_bytes());
10173 let result = compile_ir(&opts, "/main.ir", &fs);
10174 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
10175 (printed, result.text().to_owned())
10176 }
10177
10178 #[test]
10179 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
10180 let (printed, again) = round_trip(
10184 "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",
10185 );
10186 assert_eq!(printed, again);
10187 }
10188
10189 #[test]
10190 fn ir_that_is_not_ir_says_which_line_stopped_it() {
10191 let mut opts = options();
10192 opts.emit = EmitKind::Ir;
10193 let mut fs = MemoryFileSystem::new();
10194 let text = "\
10195; ModuleID = 'a.c'
10196; format 0
10197target triple = \"x86_64-unknown-linux-gnu\"
10198target datalayout = \"e-p:64:64-i64:64-S128\"
10199
10200func @f(), linkage(external) {
10201block0:
10202 frobnicate
10203}
10204";
10205 fs.insert("/main.ir", text.as_bytes().to_vec());
10206 let result = compile_ir(&opts, "/main.ir", &fs);
10207 assert!(result.failed());
10208 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
10209 }
10210
10211 #[test]
10212 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
10213 let mut opts = options();
10216 opts.emit = EmitKind::Ir;
10217 let mut fs = MemoryFileSystem::new();
10218 let text = "\
10219; ModuleID = 'a.c'
10220; format 0
10221target triple = \"x86_64-unknown-linux-gnu\"
10222target datalayout = \"e-p:64:64-i64:64-S128\"
10223
10224func @f(), linkage(external) {
10225block0:
10226 %0 = iconst.i32 1
10227 return %0
10228}
10229";
10230 fs.insert("/main.ir", text.as_bytes().to_vec());
10231 let result = compile_ir(&opts, "/main.ir", &fs);
10232 assert!(result.failed());
10233 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
10234 }
10235
10236 #[test]
10237 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
10238 let mut fs = MemoryFileSystem::new();
10240 fs.insert("/main.ir", Vec::new());
10241 let result = compile_ir(&options(), "/main.ir", &fs);
10242 assert!(result.failed());
10243 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
10244 }
10245
10246 #[test]
10247 fn the_printed_ir_reads_back_as_the_same_module() {
10248 let text = ir("\
10251struct point { int x, y; };
10252static const char greeting[] = \"hi\";
10253int table[4] = { 1, 2, 3 };
10254int puts(const char *);
10255double half(double x) { return x / 2.0; }
10256int f(int n) {
10257 int total = 0;
10258 for (int i = 0; i < n; i++) {
10259 if (i == 3) continue;
10260 total += table[i];
10261 }
10262 switch (n) {
10263 case 0: total = 1;
10264 case 1: total++; break;
10265 default: total = -total;
10266 }
10267 struct point p = { total, 1 };
10268 int *q = &p.y;
10269 puts(greeting);
10270 return p.x + *q;
10271}
10272int dispatch(int c) {
10273 void *p = c ? &&one : &&two;
10274 goto *p;
10275one:
10276 return 1;
10277two:
10278 return 2;
10279}
10280int assembly(int x, int *p) {
10281 int r;
10282 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
10283 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
10284 return r;
10285away:
10286 return 0;
10287}
10288");
10289 let mut names = Interner::new();
10290 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
10291 assert_eq!(rucc_ir::print(&module, &names), text);
10292 }
10293
10294 #[test]
10295 fn what_save_temps_keeps_is_the_text_that_was_compiled_and_the_assembly_that_was_assembled() {
10296 let mut opts = options();
10300 opts.emit = EmitKind::Object;
10301 opts.save_temps = rucc_session::SaveTemps::Object;
10302 let result = run(&opts, "#define N 2\nint a[N];\n");
10303 assert_eq!(result.messages, Vec::<String>::new());
10304 let text = result.temps.preprocessed.expect("the preprocessed text");
10305 assert!(text.contains("int a[2];"), "{text}");
10306 assert!(text.starts_with("# 1 \"/main.c\""), "{text}");
10307 let asm = result.temps.assembly.expect("the assembly");
10308 assert!(asm.contains("a:"), "{asm}");
10309 assert!(matches!(result.artifact, Artifact::Object { .. }), "{:?}", result.artifact);
10310 }
10311
10312 #[test]
10313 fn nothing_is_kept_unless_the_flag_asked_for_it() {
10314 let mut opts = options();
10317 opts.emit = EmitKind::Object;
10318 assert_eq!(run(&opts, "int a;\n").temps, Temps::default());
10319 }
10320
10321 #[test]
10322 fn a_compilation_that_stops_before_the_back_end_keeps_the_text_and_no_assembly() {
10323 let mut opts = options();
10326 opts.emit = EmitKind::Ir;
10327 opts.save_temps = rucc_session::SaveTemps::Cwd;
10328 let result = run(&opts, "int a;\n");
10329 assert!(result.temps.preprocessed.is_some());
10330 assert_eq!(result.temps.assembly, None);
10331 }
10332
10333 fn span(from: u64, len: u64, held: rucc_debug::Held) -> rucc_debug::Span {
10335 rucc_debug::Span { from, len, held }
10336 }
10337
10338 #[test]
10339 fn two_stretches_that_meet_and_agree_come_out_as_one() {
10340 let one = span(0, 4, rucc_debug::Held::Reg(3));
10341 let two = span(4, 4, rucc_debug::Held::Reg(3));
10342 assert_eq!(settle(vec![two, one]), vec![span(0, 8, rucc_debug::Held::Reg(3))]);
10343 }
10344
10345 #[test]
10346 fn a_stretch_another_starts_inside_and_disagrees_with_ends_where_the_other_starts() {
10347 let one = span(0, 8, rucc_debug::Held::Reg(3));
10348 let two = span(4, 8, rucc_debug::Held::Reg(4));
10349 let settled = settle(vec![one, two]);
10352 assert_eq!(
10353 settled,
10354 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 8, rucc_debug::Held::Reg(4))]
10355 );
10356 }
10357
10358 #[test]
10359 fn a_stretch_cut_by_one_that_ends_first_does_not_come_back_after_it() {
10360 let one = span(0, 16, rucc_debug::Held::Reg(3));
10363 let two = span(4, 4, rucc_debug::Held::Reg(4));
10364 assert_eq!(
10365 settle(vec![one, two]),
10366 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 4, rucc_debug::Held::Reg(4))]
10367 );
10368 }
10369
10370 #[test]
10371 fn a_stretch_inside_another_that_agrees_with_it_cuts_nothing() {
10372 let one = span(0, 16, rucc_debug::Held::Reg(3));
10373 let two = span(4, 4, rucc_debug::Held::Reg(3));
10374 assert_eq!(settle(vec![one, two]), vec![span(0, 16, rucc_debug::Held::Reg(3))]);
10375 }
10376
10377 #[test]
10378 fn a_stretch_two_others_disagree_over_the_whole_of_says_nothing_at_all() {
10379 let one = span(0, 8, rucc_debug::Held::Reg(3));
10380 let two = span(0, 8, rucc_debug::Held::Frame(-16));
10381 assert_eq!(settle(vec![one, two]), Vec::new());
10382 }
10383
10384 #[test]
10385 fn stretches_with_a_gap_between_them_keep_the_gap() {
10386 let one = span(0, 4, rucc_debug::Held::Reg(3));
10387 let two = span(16, 4, rucc_debug::Held::Reg(3));
10388 assert_eq!(settle(vec![one, two]), vec![one, two]);
10389 }
10390
10391 fn extent(len: usize) -> rucc_object::Extent {
10393 rucc_object::Extent {
10394 name: "f".to_owned(),
10395 start: 0,
10396 len,
10397 align: 1,
10398 binding: rucc_object::Binding::Global,
10399 visibility: rucc_object::Visibility::Default,
10400 patch: None,
10401 landings: Vec::new(),
10402 }
10403 }
10404
10405 fn row(at: usize, lo: u32, hi: u32) -> rucc_asm::Row {
10407 let span = Span::new(lo, hi);
10408 rucc_asm::Row { at, span, inst: None }
10409 }
10410
10411 #[test]
10412 fn a_row_ends_where_the_next_address_begins() {
10413 let rows = [row(0, 0, 1), row(4, 1, 2), row(10, 2, 3)];
10414 assert_eq!(ends(&extent(16), &rows), vec![4, 10, 16]);
10415 }
10416
10417 #[test]
10418 fn rows_sharing_an_address_all_end_where_the_next_address_begins() {
10419 let rows = [row(0, 0, 1), row(4, 1, 2), row(4, 2, 3), row(4, 3, 4)];
10422 assert_eq!(ends(&extent(12), &rows), vec![4, 12, 12, 12]);
10423 }
10424
10425 #[test]
10426 fn the_rows_of_a_scope_that_are_next_to_each_other_come_out_as_one_stretch() {
10427 let rows = [row(0, 0, 4), row(4, 10, 14), row(8, 14, 18), row(12, 40, 44)];
10428 let ends = ends(&extent(16), &rows);
10429 let scope = Span::new(8, 20);
10430 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 8 }]);
10431 }
10432
10433 #[test]
10434 fn a_scope_the_back_end_split_in_two_comes_out_as_two_stretches() {
10435 let rows = [row(0, 10, 14), row(4, 40, 44), row(8, 14, 18)];
10436 let ends = ends(&extent(12), &rows);
10437 let scope = Span::new(8, 20);
10438 let over = spread(scope, &ends, &rows);
10439 assert_eq!(
10440 over,
10441 vec![rucc_debug::Reach { from: 0, len: 4 }, rucc_debug::Reach { from: 8, len: 4 }]
10442 );
10443 }
10444
10445 #[test]
10446 fn a_row_with_no_source_of_its_own_belongs_to_no_scope() {
10447 let rows = [rucc_asm::Row { at: 0, span: Span::DUMMY, inst: None }, row(4, 10, 14)];
10449 let ends = ends(&extent(8), &rows);
10450 let scope = Span::new(0, 20);
10451 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 4 }]);
10452 }
10453
10454 fn scope(parent: Option<usize>, lo: u32, hi: u32) -> crate::shapes::Scope {
10456 let span = Span::new(lo, hi);
10457 crate::shapes::Scope { parent, span }
10458 }
10459
10460 #[test]
10461 fn a_function_gets_the_scopes_its_own_locals_are_in_and_nothing_else() {
10462 let scopes = [scope(None, 0, 10), scope(None, 20, 30), scope(Some(1), 22, 26)];
10464 let rows = [row(0, 22, 24), row(4, 26, 28)];
10465 let (out, at) = nests(&[Some(2)], &scopes, &extent(8), &rows);
10466 assert_eq!(at.get(&1), Some(&0));
10469 assert_eq!(at.get(&2), Some(&1));
10470 assert_eq!(at.get(&0), None);
10471 assert_eq!(out.len(), 2);
10472 assert_eq!(out[0].parent, None);
10473 assert_eq!(out[1].parent, Some(0));
10474 assert_eq!(out[0].over, vec![rucc_debug::Reach { from: 0, len: 8 }]);
10475 assert_eq!(out[1].over, vec![rucc_debug::Reach { from: 0, len: 4 }]);
10476 }
10477
10478 #[test]
10479 fn a_local_written_straight_into_the_body_pulls_no_scope_in() {
10480 let scopes = [scope(None, 20, 30)];
10481 let rows = [row(0, 22, 24)];
10482 let (out, at) = nests(&[None], &scopes, &extent(4), &rows);
10483 assert_eq!(out, Vec::new());
10484 assert!(at.is_empty());
10485 }
10486
10487 #[test]
10488 fn a_scope_whose_code_all_went_away_is_still_one_of_the_functions_scopes() {
10489 let scopes = [scope(None, 20, 30)];
10493 let rows = [row(0, 40, 44)];
10494 let (out, at) = nests(&[Some(0)], &scopes, &extent(4), &rows);
10495 assert_eq!(at.get(&0), Some(&0));
10496 assert_eq!(out.len(), 1);
10497 assert_eq!(out[0].over, Vec::new());
10498 }
10499}