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 mut keywords = Keywords::new(&mut sess.interner, opts.std, opts.gnu_extensions);
193 if sess.target.tuple.os() == rucc_tuple::Os::Windows {
194 keywords = keywords.windows();
195 }
196 let mut diagnostics: Vec<Diagnostic> = Vec::new();
197 let mut fired = Fired::new();
199 let mut pressure = Pressure::new();
201 let mut lowerings = Lowerings::asked(opts.lowering_dump.is_some());
202 let mut stack = StackUsage::new();
206 let mut dumps = Vec::new();
208 let mut remarks = String::new();
209 let mut passes = Vec::new();
211 let mut temps = Temps::default();
213
214 let bytes = match fs.read(Path::new(name)) {
215 Ok(bytes) => bytes,
216 Err(e) => return failure(format!("{name}: {e}")),
217 };
218 let Ok(file) = sess.sources.add_shared(crate::phase::source_name(name), bytes, None) else {
219 return failure(format!("{name}: the source map has no room left for this file"));
220 };
221 clock.lap("read");
222
223 let mut pp = rucc_pp::Preprocessor::with_prefix_map(opts.prefix_map.macros.clone());
227 let predef = rucc_pp::Predef::for_options(opts);
228 let expanded: Vec<PpToken> = {
229 let mut tokens = Vec::new();
230 {
235 let mut cx =
236 rucc_pp::Context::new(&mut sess.interner, &mut sess.sources, fs, &opts.search);
237 cx.lex = rucc_lex::Options::for_dialect(opts.std, opts.gnu_extensions);
238 cx.pedantic = opts.pedantic;
239 if pp.predefine(&sess.target, &predef, &mut cx).is_err() {
240 return failure(format!(
241 "{name}: the source map has no room for the built in macros"
242 ));
243 }
244 if pp.preinclude(&opts.preincludes, &mut tokens, &mut cx).is_err() {
245 return failure(format!("{name}: the source map has no room for the command line"));
246 }
247 tokens.append(&mut pp.run(file, &mut cx));
248 }
249 if opts.save_temps.wanted() {
250 temps.preprocessed = Some(rucc_pp::print(
251 file,
252 &tokens,
253 pp.line_directives(),
254 &sess.sources,
255 &sess.interner,
256 rucc_pp::PrintOptions { line_markers: opts.line_markers },
257 ));
258 }
259 tokens.iter().map(|token| token.to_pp()).collect()
260 };
261 diagnostics.extend(pp.take_diagnostics());
262 let deps = pp.dependencies().to_vec();
265 clock.lap("preprocess");
266
267 let cx = Convert {
270 keywords: &keywords,
271 interner: &sess.interner,
272 target: &sess.target,
273 std: opts.std,
274 gnu: opts.gnu_extensions,
275 pedantic: opts.pedantic,
276 };
277 let (tokens, complaints) = convert(&expanded, &cx);
278 diagnostics.extend(complaints);
279 clock.lap("convert");
280
281 let type_names: Vec<Symbol> =
283 sess.target.type_names().iter().filter_map(|&(name, _)| sess.interner.find(name)).collect();
284 let parsed = rucc_parse::parse(
285 &tokens,
286 rucc_parse::Context {
287 interner: &sess.interner,
288 std: opts.std,
289 gnu: opts.gnu_extensions,
290 pedantic: opts.pedantic,
291 error_limit: opts.error_limit as usize,
292 type_names: &type_names,
293 },
294 );
295 clock.lap("parse");
296 let parse_failed = parsed.diagnostics.iter().any(|d| d.severity.is_fatal());
297 diagnostics.extend(parsed.diagnostics);
298
299 let mut artifact = Artifact::Nothing;
300 let mut instrumented = Instrumented::default();
303 if !parse_failed {
304 let mut checker = Checker::new(
305 &parsed.ast,
306 CheckContext {
307 names: &sess.interner,
308 target: &sess.target,
309 std: opts.std,
310 gnu: opts.gnu_extensions,
311 pedantic: opts.pedantic,
312 permissive: opts.permissive,
313 gnu89_inline: opts.gnu89_inline,
314 error_limit: opts.error_limit as usize,
315 builtins: opts.builtins && opts.hosted,
318 no_builtin: &opts.no_builtin,
319 short_enums: opts.short_enums,
320 ms_extensions: sess.ms_extensions(),
321 trapping_math: opts.trapping_math,
322 isa: opts.isa,
323 },
324 );
325 checker.check_unit();
326 let checked = checker.finish();
327 clock.lap("check");
328 if !checked.failed() {
329 match opts.emit {
330 EmitKind::Tast => {
331 artifact = Artifact::Text(rucc_sema::print(
332 &checked.tast,
333 &checked.types,
334 &sess.interner,
335 ));
336 }
337 EmitKind::TypeGranules => {
341 artifact = Artifact::Text(rucc_types::granule_report(
342 &checked.types,
343 &sess.interner,
344 &sess.target,
345 ));
346 }
347 EmitKind::Ir
348 | EmitKind::MirFinal
349 | EmitKind::Asm
350 | EmitKind::Object
351 | EmitKind::Archive
352 | EmitKind::Executable
353 | EmitKind::SafetySummary => {
354 let mut read = |named: &str| {
359 fs.read(Path::new(named))
360 .map(|bytes| bytes.as_slice().to_vec())
361 .map_err(|why| why.to_string())
362 };
363 let meaning = if opts.debug_info {
369 crate::shapes::collect(
370 &checked.tast,
371 &checked.types,
372 &sess.target,
373 &sess.interner,
374 &sess.sources,
375 )
376 } else {
377 crate::shapes::Meaning::default()
378 };
379 let mut lowered = rucc_lower::lower(
380 crate::phase::source_name(name),
381 rucc_lower::Context {
382 tast: &checked.tast,
383 types: &checked.types,
384 target: &sess.target,
385 names: &mut sess.interner,
386 visibility: match opts.visibility {
387 Visibility::Default => IrVisibility::Default,
388 Visibility::Hidden => IrVisibility::Hidden,
389 Visibility::Protected => IrVisibility::Protected,
390 },
391 protector: match opts.protector {
392 Protector::None => LowerProtector::None,
393 Protector::Buffers => LowerProtector::Buffers,
394 Protector::Strong => LowerProtector::Strong,
395 Protector::All => LowerProtector::All,
396 },
397 wrapping: rucc_lower::Wrapping {
398 signed: opts.wrapping.signed,
399 pointer: opts.wrapping.pointer,
400 trap: opts.wrapping.trap,
401 },
402 aliasing: opts.strict_aliasing,
403 padding: opts.padding == Padding::Ignored,
404 contract: match opts.fp_contract {
405 Contract::Off => FpContract::Off,
406 Contract::On => FpContract::On,
407 Contract::Fast => FpContract::Fast,
408 },
409 align: opts.align_functions,
410 instrument: opts.instrument_functions,
411 exceptions: opts.exceptions,
412 read: &mut read,
413 },
414 );
415 clock.lap("lower");
419 let failed = lowered.diagnostics.iter().any(|d| d.severity.is_fatal());
420 if !failed {
421 if let Err(errors) = clock
426 .time("verify", || rucc_ir::verify(&lowered.module, &sess.interner))
427 {
428 for error in errors {
429 diagnostics.push(internal(&format!("invalid IR, {error}")));
430 }
431 } else if let Err(complaints) = clock
432 .time("instrument", || {
433 instrument(&mut lowered.module, &mut sess.interner, opts)
434 })
435 .map(|done| instrumented = done)
436 {
437 diagnostics.extend(complaints);
438 } else if let Err(complaints) = clock
439 .time("optimize", || {
440 optimize(
441 &mut lowered.module,
442 &mut sess.interner,
443 &sess.target,
444 opts,
445 name,
446 &mut dumps,
447 &mut remarks,
448 )
449 })
450 .map(|times| passes = times)
451 {
452 diagnostics.extend(complaints);
453 } else if opts.emit == EmitKind::SafetySummary {
454 artifact = Artifact::Text(
459 rucc_safety::summarize(
460 &lowered.module,
461 &sess.interner,
462 name,
463 opts.safety.as_str(),
464 instrumented.checks,
465 instrumented.interposed,
466 instrumented.crossings,
467 )
468 .render(),
469 );
470 } else if opts.emit == EmitKind::Ir {
471 artifact =
476 Artifact::Text(rucc_ir::print(&lowered.module, &sess.interner));
477 } else {
478 let made = clock.time("generate", || {
481 generate(
482 &mut lowered.module,
483 &mut sess.interner,
484 &sess.target,
485 opts,
486 &mut Recording {
487 fired: &mut fired,
488 pressure: &mut pressure,
489 lowerings: &mut lowerings,
490 stack: &mut stack,
491 },
492 &mut temps.assembly,
493 Origin { map: &sess.sources, name, meaning: &meaning },
494 )
495 });
496 match made {
497 Ok(made) => artifact = made,
498 Err(complaints) => diagnostics.extend(complaints),
499 }
500 }
501 }
502 diagnostics.extend(lowered.diagnostics);
503 }
504 EmitKind::SyntaxOnly => {}
506 _ => {}
507 }
508 }
509 diagnostics.extend(checked.diagnostics);
510 }
511 let mut wants = rucc_opt::Wants::none();
514 for spec in &opts.opt_info {
515 let _ = wants.add(spec);
517 }
518 if wants.wants(rucc_opt::stats::Kind::Optimized) {
519 remarks.push_str(&lowerings.remarks(name));
520 }
521
522 let mut messages = Vec::with_capacity(diagnostics.len());
523 let mut errors = 0;
524 for diag in &diagnostics {
525 if rucc_diag::dropped(diag, &sess.sources, opts.warnings, opts.system_header_warnings) {
531 continue;
532 }
533 if diag.severity.is_fatal()
534 || (diag.severity == Severity::Warning && opts.warnings_are_errors)
535 {
536 errors += 1;
537 }
538 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
539 }
540 if errors > 0 {
541 artifact = Artifact::Nothing;
543 }
544 let stack_usage = if opts.stack_usage && errors == 0 {
547 su_file(&stack, &sess.sources, crate::phase::source_name(name))
548 } else {
549 String::new()
550 };
551 clock.passes(passes);
554 let timing = clock.finish();
555 Compiled {
556 artifact,
557 messages,
558 errors,
559 fired,
560 pressure,
561 lowerings,
562 dumps,
563 remarks,
564 deps,
565 temps,
566 timing,
567 stack_usage,
568 }
569}
570
571#[must_use]
581pub fn compile_ir(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
582 let mut sess = Session::new(opts.clone());
583 if opts.emit != EmitKind::Ir {
584 return failure(format!(
585 "{name}: an input of IR can only be emitted as IR, and `--emit={}` asks for what \
586 the C in front of it became",
587 opts.emit.as_str()
588 ));
589 }
590 let bytes = match fs.read(Path::new(name)) {
591 Ok(bytes) => bytes,
592 Err(e) => return failure(format!("{name}: {e}")),
593 };
594 let Ok(text) = std::str::from_utf8(bytes.as_slice()) else {
595 return failure(format!("{name}: this is not text, so it is not IR"));
596 };
597
598 let module = match rucc_ir::parse(text, &mut sess.interner) {
599 Ok(module) => module,
600 Err(error) => {
601 return failure(format!("{name}:{}: {}", error.line, error.message));
602 }
603 };
604 let mut diagnostics: Vec<Diagnostic> = Vec::new();
605 if let Err(errors) = rucc_ir::verify(&module, &sess.interner) {
606 for error in errors {
607 diagnostics.push(invalid(&format!("invalid IR, {error}")));
608 }
609 }
610 let mut messages = Vec::with_capacity(diagnostics.len());
611 for diag in &diagnostics {
612 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
613 }
614 let errors = u32::try_from(messages.len()).unwrap_or(u32::MAX);
615 let artifact = if errors > 0 {
616 Artifact::Nothing
617 } else {
618 Artifact::Text(rucc_ir::print(&module, &sess.interner))
619 };
620 Compiled {
622 artifact,
623 messages,
624 errors,
625 fired: Fired::new(),
626 pressure: Pressure::new(),
627 lowerings: Lowerings::new(),
628 dumps: Vec::new(),
629 remarks: String::new(),
630 deps: Vec::new(),
631 temps: Temps::default(),
632 timing: crate::trace::Timing::default(),
633 stack_usage: String::new(),
634 }
635}
636
637fn instrument(
660 module: &mut rucc_ir::Module,
661 names: &mut Interner,
662 opts: &Options,
663) -> Result<Instrumented, Vec<Diagnostic>> {
664 if !opts.safety.instruments() {
665 return Ok(Instrumented::default());
666 }
667 let mut checks = rucc_safety::run(module, opts.subobject, opts.promise, opts.races);
668 checks.freed = rucc_safety::ending::checks(module, names);
676 let interposed = rucc_safety::redirect(module, names);
681 let crossings = rucc_safety::witness(module, names);
684 match rucc_ir::verify(module, names) {
685 Ok(()) => Ok(Instrumented { checks, interposed, crossings }),
686 Err(errors) => Err(errors
687 .iter()
688 .map(|e| internal(&format!("invalid IR after check insertion, {e}")))
689 .collect()),
690 }
691}
692
693#[derive(Clone, Copy, Debug, Default)]
699struct Instrumented {
700 checks: rucc_safety::Counts,
702 interposed: usize,
704 crossings: rucc_safety::Sites,
706}
707
708fn optimize(
722 module: &mut rucc_ir::Module,
723 names: &mut Interner,
724 target: &TargetInfo,
725 opts: &Options,
726 file: &str,
727 dumps: &mut Vec<rucc_opt::Dump>,
728 remarks: &mut String,
729) -> Result<Vec<(&'static str, std::time::Duration)>, Vec<Diagnostic>> {
730 let mut settings = rucc_opt::Options::for_level(opts.opt_level);
731 settings.interposition = match opts.interposition {
737 true => replaceable(target, opts),
738 false => IrPic::Executable,
739 };
740 settings.toggles.clone_from(&opts.passes);
741 settings.builtins = opts.builtins && opts.hosted;
746 settings.no_builtin.clone_from(&opts.no_builtin);
747 settings.isa = opts.isa;
750 settings.fuel = opts.pass_fuel.iter().cloned().collect();
751 settings.global_fuel = opts.pass_fuel_global;
752 settings.verify |= opts.verify_each;
753 for (on, spec) in &opts.pass_gates {
754 if let Err(why) = settings.gates.add(*on, spec) {
757 return Err(vec![internal(&why)]);
758 }
759 }
760 for spec in &opts.dump_ir {
761 if let Err(why) = settings.dumps.add(spec) {
764 return Err(vec![internal(&why)]);
765 }
766 }
767 let mut wants = rucc_opt::Wants::none();
768 for spec in &opts.opt_info {
769 if let Err(why) = wants.add(spec) {
772 return Err(vec![internal(&why)]);
773 }
774 }
775 let report = rucc_opt::run(module, names, &settings);
776 remarks.push_str(&rucc_opt::optinfo::render(file, &report, names, wants));
777 dumps.extend(report.dumps);
778 match report.broke.is_empty() {
779 true => Ok(report.time),
780 false => Err(report.broke.iter().map(|why| internal(why)).collect()),
781 }
782}
783
784fn replaceable(target: &TargetInfo, opts: &Options) -> IrPic {
818 match (target.tuple.os().object_format(), opts.pic) {
819 (Some(ObjectFormat::Elf), Pic::Library) => IrPic::Library,
820 _ => IrPic::Executable,
821 }
822}
823
824#[derive(Clone, Copy)]
831struct Origin<'a> {
832 map: &'a SourceMap,
834 name: &'a str,
836 meaning: &'a crate::shapes::Meaning,
838}
839
840fn generate(
841 module: &mut rucc_ir::Module,
842 names: &mut Interner,
843 target: &TargetInfo,
844 opts: &Options,
845 recording: &mut Recording<'_>,
846 assembly: &mut Option<String>,
847 origin: Origin<'_>,
848) -> Result<Artifact, Vec<Diagnostic>> {
849 let Some(machine) = Machine::for_target(target) else {
850 return Err(vec![unsupported(&format!(
851 "there is no back end for {} in this compiler yet, so there is nothing to generate",
852 target.tuple
853 ))]);
854 };
855 if opts.protector != Protector::None && machine.conv.guard.is_none() {
860 return Err(vec![unsupported(&format!(
861 "{} is not supported for {} yet, because the stack protector on that target is not \
862 the one this compiler writes",
863 opts.protector, target.tuple
864 ))]);
865 }
866 if opts.control.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
872 return Err(vec![unsupported(&format!(
873 "-fcf-protection={} is not supported for {} yet, because what says a file was built \
874 for it there is not the note this compiler writes",
875 opts.control, target.tuple
876 ))]);
877 }
878 let profile = match machine.conv.trace {
884 Some(trace) => opts.profile.then(|| opts.hook.early(trace.fentry)),
885 None if opts.profile => {
886 return Err(vec![unsupported(&format!(
887 "-pg is not supported for {} yet, because the profiler's hook on that target is \
888 not the one this compiler calls",
889 target.tuple
890 ))]);
891 }
892 None => None,
893 };
894 if opts.patchable.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
899 return Err(vec![unsupported(&format!(
900 "-fpatchable-function-entry= is not supported for {} yet, because what records where \
901 the room is there is not the section this compiler writes",
902 target.tuple
903 ))]);
904 }
905 let flags = pipeline::Flags {
906 frame_pointer: opts.keeps_frame_pointer(),
907 red_zone: opts.red_zone,
908 stack_clash: opts.stack_clash,
909 landing: opts.control.branch(),
910 profile: match profile {
911 None => pipeline::Profile::No,
912 Some(true) => pipeline::Profile::Early,
913 Some(false) => pipeline::Profile::Late,
914 },
915 patch: pipeline::Room { after: opts.patchable.after(), before: opts.patchable.before },
916 reorder: opts.reorder_blocks.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
923 reuse: opts.stack_reuse.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
928 schedule: opts.schedule_insns.unwrap_or_else(|| opts.opt_level.schedules()),
933 align_loops: opts.align_loops.unwrap_or(false),
939 accurate: opts.cycle_accurate_model,
941 verify: opts.verify_each,
944 allocator: if opts.backtracking.unwrap_or_else(|| opts.opt_level.runs_optimizer()) {
948 pipeline::Allocator::Backtracking
949 } else {
950 pipeline::Allocator::Single
951 },
952 goal: Goal::for_size(opts.opt_level.is_size()),
957 switch: opts.switch_shape.as_deref().and_then(rucc_codegen::switch::Force::named),
959 sibling: opts.sibling_calls.unwrap_or_else(|| opts.opt_level.sibling_calls()),
961 debug: opts.debug_info,
962 };
963
964 if opts.safety.instruments() {
973 rucc_opt::heap::annotate(module, names);
983 rucc_safety::handover::arrange(module);
990 rucc_safety::lower(module, names);
991 if let Err(errors) = rucc_ir::verify(module, names) {
992 return Err(errors
993 .iter()
994 .map(|e| internal(&format!("invalid IR after check lowering, {e}")))
995 .collect());
996 }
997 }
998
999 let copies = target.tuple.arch() == Arch::X86_64;
1010 let elsewhere = Elsewhere::of(module, replaceable(target, opts), target.object_format, copies);
1011
1012 let mut funcs = Vec::new();
1013 let mut complaints = Vec::new();
1014 for id in module.funcs() {
1015 if module[id].is_declaration() {
1016 continue;
1017 }
1018 match pipeline::compile_recording(
1019 &mut module[id],
1020 names,
1021 &machine,
1022 &elsewhere,
1023 flags,
1024 recording,
1025 ) {
1026 Ok(func) => funcs.push(func),
1027 Err(why) => {
1028 let name = names.resolve(module[id].name).to_owned();
1029 let span = why.inst().map_or(Span::DUMMY, |inst| module[id].span(inst));
1032 let said = format!("cannot generate code for '{name}': {why}");
1033 complaints.push(unsupported_at(&said, span));
1034 }
1035 }
1036 }
1037 if !complaints.is_empty() {
1038 return Err(complaints);
1039 }
1040 let (globals, aliases) = match opts.emit {
1046 EmitKind::Asm | EmitKind::Object | EmitKind::Archive | EmitKind::Executable => (
1047 rucc_asm::globals(module, names, target.object_format).map_err(refused)?,
1048 rucc_asm::aliases(module, names).map_err(refused)?,
1049 ),
1050 _ => (rucc_asm::Globals::default(), Vec::new()),
1051 };
1052 let unwind = opts.unwinds();
1056 match opts.emit {
1057 EmitKind::Asm => {
1058 rucc_asm::print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
1059 .map(Artifact::Text)
1060 .map_err(refused)
1061 }
1062 EmitKind::Object | EmitKind::Archive | EmitKind::Executable => {
1066 if opts.save_temps.wanted() {
1067 let listing = rucc_asm::print(
1068 &funcs,
1069 &globals,
1070 &aliases,
1071 names,
1072 target,
1073 unwind,
1074 output(opts, target),
1075 );
1076 *assembly = Some(listing.map_err(refused)?);
1077 }
1078 let aarch64 = target.tuple.arch() == Arch::Aarch64;
1089 if aarch64 || globals.kept() || rucc_asm::kept(&funcs, names, target) {
1090 if funcs.iter().any(|func| !func.landings.is_empty()) {
1094 return Err(vec![unsupported(
1095 "a cleanup that runs during an unwind, in a unit whose listing is read \
1096 back by the assembler",
1097 )]);
1098 }
1099 let print = if opts.debug_info { rucc_asm::print_marked } else { rucc_asm::print };
1100 let listing =
1101 print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
1102 .map_err(refused)?;
1103 let arch = target.tuple.arch();
1104 let read =
1105 rucc_asm::read_as(&listing, arch, target.object_format).map_err(|trouble| {
1106 let what = if aarch64 {
1107 "a unit for aarch64"
1108 } else if globals.kept() {
1109 "an `asm` at file scope"
1110 } else {
1111 "an `asm` template kept as text"
1112 };
1113 vec![unsupported(&format!(
1114 "{what}, whose listing the assembler stopped at on line {}: {}",
1115 trouble.line, trouble.why
1116 ))]
1117 })?;
1118 let info = if opts.debug_info {
1119 let assembled =
1120 placed(&read, &funcs, names, target).map_err(|why| vec![internal(&why)])?;
1121 describe(&assembled, &globals.image(), &funcs, origin, opts, target)
1122 .map_err(|why| vec![internal(&why)])?
1123 } else {
1124 rucc_object::Info::default()
1125 };
1126 let defines = rucc_object::assembled_defines(&read);
1127 let bytes =
1128 rucc_object::assembled_described(&read, target, &info).map_err(wrote)?;
1129 return Ok(Artifact::Object { bytes, defines });
1130 }
1131 let assembled = rucc_asm::assemble(&funcs, names, target, unwind, opts.debug_info)
1132 .map_err(refused)?;
1133 let data = globals.image();
1134 let info = if opts.debug_info {
1138 describe(&assembled, &data, &funcs, origin, opts, target)
1139 .map_err(|why| vec![internal(&why)])?
1140 } else {
1141 rucc_object::Info::default()
1142 };
1143 let text = assembled.text;
1144 let bytes =
1147 rucc_object::write(&text, &data, &aliases, target, output(opts, target), &info)
1148 .map_err(wrote)?;
1149 let defines = rucc_object::defines(&text, &data, &aliases, target).map_err(wrote)?;
1154 Ok(Artifact::Object { bytes, defines })
1155 }
1156 _ => Ok(Artifact::Text(rucc_mir::print(&funcs, names, target.regs))),
1157 }
1158}
1159
1160fn placed(
1172 read: &rucc_object::Assembled,
1173 funcs: &[rucc_mir::Func],
1174 names: &Interner,
1175 target: &TargetInfo,
1176) -> Result<rucc_asm::Assembled, String> {
1177 let at: HashMap<&str, &rucc_object::Name> =
1178 read.names.iter().map(|name| (name.name.as_str(), name)).collect();
1179 let offset = |name: &str| match at.get(name).map(|name| name.at) {
1180 Some(rucc_object::Held::In { part, offset }) => Some((part, offset)),
1181 _ => None,
1182 };
1183 let mut text = rucc_object::Text::default();
1184 let mut lines = Vec::with_capacity(funcs.len());
1185 let symbol = rucc_asm::Directives::of(target.object_format).symbol();
1189 for (which, func) in funcs.iter().enumerate() {
1190 let name = names.resolve(func.name);
1191 let Some((part, start)) = offset(&format!("{symbol}{name}")) else {
1192 return Err(format!("the listing has no label for the function '{name}'"));
1193 };
1194 let mut rows = Vec::with_capacity(func.inst_count() + 1);
1195 if !func.declared.is_dummy() {
1196 rows.push(rucc_asm::Row { at: 0, span: func.declared, inst: None });
1197 }
1198 for block in func.blocks() {
1199 for inst in func.insts(block) {
1200 let label = rucc_asm::mark(target, which, inst);
1201 let Some((held, here)) = offset(&label) else {
1202 return Err(format!("the listing has no label '{label}'"));
1203 };
1204 if held != part || here < start {
1205 return Err(format!("the label '{label}' is not inside '{name}'"));
1206 }
1207 let at = usize::try_from(here - start).map_err(|why| why.to_string())?;
1208 rows.push(rucc_asm::Row { at, span: func.span(inst), inst: Some(inst) });
1209 }
1210 }
1211 let size = at.get(format!("{symbol}{name}").as_str()).map_or(0, |name| name.size);
1214 let len = match offset(&rucc_asm::mark_end(target, which)) {
1215 Some((held, end)) if size == 0 && held == part && end >= start => end - start,
1216 _ => size,
1217 };
1218 text.funcs.push(rucc_object::Extent {
1219 name: name.to_owned(),
1220 start: usize::try_from(start).map_err(|why| why.to_string())?,
1221 len: usize::try_from(len).map_err(|why| why.to_string())?,
1222 align: func.align.unwrap_or(rucc_object::FUNC_ALIGN),
1223 binding: rucc_object::Binding::Global,
1224 visibility: rucc_object::Visibility::Default,
1225 patch: None,
1226 landings: Vec::new(),
1227 });
1228 lines.push(rows);
1229 }
1230 Ok(rucc_asm::Assembled { text, lines, frames: None })
1231}
1232
1233fn describe(
1252 assembled: &rucc_asm::Assembled,
1253 data: &rucc_object::Data,
1254 machine: &[rucc_mir::Func],
1255 origin: Origin<'_>,
1256 opts: &Options,
1257 target: &TargetInfo,
1258) -> Result<rucc_object::Info, String> {
1259 let rucc_asm::Assembled { text, lines, frames } = assembled;
1260 let rewrite = |path: &str| opts.prefix_map.debug.apply(path).into_owned();
1261 let mut files: Vec<String> = Vec::new();
1266 let mut funcs = Vec::with_capacity(text.funcs.len());
1267 for ((extent, rows), built) in text.funcs.iter().zip(lines).zip(machine) {
1268 let mut out: Vec<rucc_debug::Row> = Vec::with_capacity(rows.len());
1269 for row in rows {
1270 if row.span.is_dummy() {
1271 continue;
1272 }
1273 let Some(at) = origin.map.presumed(row.span.lo) else {
1274 continue;
1275 };
1276 let which = interned(&mut files, rewrite(at.name));
1277 let place = rucc_debug::Row {
1278 at: row.at as u64,
1279 file: which,
1280 line: at.line,
1281 column: at.column,
1282 };
1283 match out.last() {
1293 Some(last) if last.at == place.at => {}
1294 _ => out.push(place),
1295 }
1296 }
1297 if let Some(first) = out.first_mut() {
1304 first.at = 0;
1305 }
1306 let known = origin.meaning.funcs.get(&extent.name);
1312 let decl = known.map(|known| rucc_debug::Place {
1313 file: interned(&mut files, rewrite(&known.file)),
1314 line: known.line,
1315 });
1316 let mut sig = known.and_then(|known| known.sig.clone());
1325 let mut placed: Vec<(u32, i32)> = built.locals.clone();
1326 let mut spots = stretches(extent, rows, built, target);
1327 for &decl in &built.sharing {
1332 if !spots.iter().any(|(at, _)| *at == decl) {
1333 spots.push((decl, Vec::new()));
1334 }
1335 }
1336 if let (Some(sig), Some(known)) = (sig.as_mut(), known) {
1337 for (param, decl) in sig.params.iter_mut().zip(&known.params) {
1338 let Some(decl) = *decl else { continue };
1339 if let Some(which) = placed.iter().position(|&(at, _)| at == decl) {
1340 let at = rucc_debug::Held::Frame(i64::from(placed.remove(which).1));
1341 param.spot = Some(rucc_debug::Spot::Always(at));
1342 continue;
1343 }
1344 let Some(which) = spots.iter().position(|(at, _)| *at == decl) else { continue };
1348 param.spot = Some(rucc_debug::Spot::Over(spots.remove(which).1));
1349 }
1350 }
1351 let mut locals = Vec::with_capacity(placed.len() + spots.len());
1355 let mut wants: Vec<Option<usize>> = Vec::with_capacity(locals.capacity());
1359 for (decl, at) in placed {
1360 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1361 wants.push(named.scope);
1362 locals.push(rucc_debug::Local {
1363 name: named.name.clone(),
1364 ty: named.ty,
1365 decl: Some(rucc_debug::Place {
1366 file: interned(&mut files, rewrite(&named.file)),
1367 line: named.line,
1368 }),
1369 spot: rucc_debug::Spot::Always(rucc_debug::Held::Frame(i64::from(at))),
1370 scope: None,
1371 });
1372 }
1373 spots.sort_by_key(|(decl, _)| *decl);
1377 for (decl, spans) in spots {
1378 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1379 wants.push(named.scope);
1380 locals.push(rucc_debug::Local {
1381 name: named.name.clone(),
1382 ty: named.ty,
1383 decl: Some(rucc_debug::Place {
1384 file: interned(&mut files, rewrite(&named.file)),
1385 line: named.line,
1386 }),
1387 spot: rucc_debug::Spot::Over(spans),
1388 scope: None,
1389 });
1390 }
1391 let (scopes, at) = nests(&wants, &origin.meaning.scopes, extent, rows);
1396 for (local, want) in locals.iter_mut().zip(&wants) {
1397 local.scope = want.and_then(|want| at.get(&want).copied());
1398 }
1399 funcs.push(rucc_debug::Function {
1400 name: extent.name.clone(),
1401 len: extent.len as u64,
1402 rows: out,
1403 decl,
1404 sig,
1405 external: known.is_some_and(|known| known.external),
1406 locals,
1407 scopes,
1408 });
1409 }
1410 let mut globals = Vec::new();
1417 for object in &data.objects {
1418 let Some(held) = origin.meaning.objects.get(&object.name) else { continue };
1419 globals.push(rucc_debug::Global {
1420 name: object.name.clone(),
1421 ty: held.ty,
1422 decl: Some(rucc_debug::Place {
1423 file: interned(&mut files, rewrite(&held.file)),
1424 line: held.line,
1425 }),
1426 external: held.external,
1427 });
1428 }
1429 let unit = rucc_debug::Unit {
1430 name: rewrite(origin.name),
1431 dir: rewrite(opts.working_dir.as_deref().unwrap_or(".")),
1434 producer: format!("rucc {}", crate::VERSION),
1435 files,
1436 types: origin.meaning.types.clone(),
1437 funcs,
1438 globals,
1439 pointer: u8::try_from(target.pointer_width / 8).unwrap_or(8),
1440 frames: opts.unwinds() || frames.is_some(),
1445 mach_o: target.object_format == rucc_target::ObjectFormat::MachO,
1446 };
1447 let mut info = rucc_debug::write(&unit).map_err(|why| why.to_string())?;
1448 info.chunks.extend(frames.clone());
1449 Ok(info)
1450}
1451
1452fn stretches(
1463 extent: &rucc_object::Extent,
1464 rows: &[rucc_asm::Row],
1465 built: &rucc_mir::Func,
1466 target: &TargetInfo,
1467) -> Vec<(u32, Vec<rucc_debug::Span>)> {
1468 let (false, Some(regs)) = (built.kept.is_empty(), target.call_regs) else {
1471 return Vec::new();
1472 };
1473 let ends = ends(extent, rows);
1474 let mut bounds = vec![None; built.inst_count()];
1475 for (which, row) in rows.iter().enumerate() {
1476 let Some(inst) = row.inst else { continue };
1477 bounds[inst.index()] = Some((row.at as u64, ends[which]));
1478 }
1479 let mut spots: Vec<(u32, Vec<rucc_debug::Span>)> = Vec::new();
1480 for kept in &built.kept {
1481 let (Some((from, _)), Some((_, to))) = (bounds[kept.from.index()], bounds[kept.to.index()])
1482 else {
1483 continue;
1484 };
1485 if to <= from {
1486 continue;
1487 }
1488 let held = match kept.at {
1489 rucc_mir::Where::Reg { reg, class } => match regs.dwarf(class, reg) {
1492 Some(number) => rucc_debug::Held::Reg(number),
1493 None => continue,
1494 },
1495 rucc_mir::Where::Frame(at) => rucc_debug::Held::Frame(i64::from(at)),
1496 };
1497 let span = rucc_debug::Span { from, len: to - from, held };
1498 match spots.iter_mut().find(|(decl, _)| *decl == kept.decl) {
1499 Some((_, spans)) => spans.push(span),
1500 None => spots.push((kept.decl, vec![span])),
1501 }
1502 }
1503 for (_, spans) in &mut spots {
1504 *spans = settle(std::mem::take(spans));
1505 }
1506 spots.retain(|(_, spans)| !spans.is_empty());
1507 spots
1508}
1509
1510fn ends(extent: &rucc_object::Extent, rows: &[rucc_asm::Row]) -> Vec<u64> {
1520 let mut out = vec![extent.len as u64; rows.len()];
1521 let mut next = extent.len as u64;
1522 for which in (0..rows.len()).rev() {
1523 let at = rows[which].at as u64;
1524 out[which] = match next > at {
1527 true => next,
1528 false => out.get(which + 1).copied().unwrap_or(extent.len as u64),
1529 };
1530 next = next.min(at);
1531 }
1532 out
1533}
1534
1535fn nests(
1551 wants: &[Option<usize>],
1552 scopes: &[crate::shapes::Scope],
1553 extent: &rucc_object::Extent,
1554 rows: &[rucc_asm::Row],
1555) -> (Vec<rucc_debug::Scope>, HashMap<usize, usize>) {
1556 let mut needed: Vec<usize> = Vec::new();
1557 for &want in wants {
1558 let mut up = want;
1559 while let Some(which) = up {
1560 if needed.contains(&which) {
1561 break;
1562 }
1563 needed.push(which);
1564 up = scopes.get(which).and_then(|scope| scope.parent);
1565 }
1566 }
1567 needed.sort_unstable();
1570 let at: HashMap<usize, usize> =
1571 needed.iter().enumerate().map(|(which, &scope)| (scope, which)).collect();
1572 let ends = ends(extent, rows);
1573 let out = needed
1574 .iter()
1575 .map(|&which| {
1576 let scope = &scopes[which];
1577 rucc_debug::Scope {
1578 parent: scope.parent.and_then(|parent| at.get(&parent).copied()),
1579 over: spread(scope.span, &ends, rows),
1580 }
1581 })
1582 .collect();
1583 (out, at)
1584}
1585
1586fn spread(span: Span, ends: &[u64], rows: &[rucc_asm::Row]) -> Vec<rucc_debug::Reach> {
1594 let mut out: Vec<rucc_debug::Reach> = Vec::new();
1595 for (which, row) in rows.iter().enumerate() {
1596 if row.span.is_dummy() || row.span.lo < span.lo || row.span.hi > span.hi {
1597 continue;
1598 }
1599 let (from, to) = (row.at as u64, ends[which]);
1600 if to <= from {
1601 continue;
1602 }
1603 match out.last_mut() {
1604 Some(last) if last.from + last.len >= from => {
1605 last.len = to.saturating_sub(last.from).max(last.len);
1606 }
1607 _ => out.push(rucc_debug::Reach { from, len: to - from }),
1608 }
1609 }
1610 out
1611}
1612
1613fn settle(mut spans: Vec<rucc_debug::Span>) -> Vec<rucc_debug::Span> {
1630 spans.sort_by_key(|span| (span.from, span.len));
1631 for which in 0..spans.len() {
1632 let (from, end, held) =
1633 (spans[which].from, spans[which].from + spans[which].len, spans[which].held);
1634 let later = spans[which + 1..]
1635 .iter()
1636 .take_while(|later| later.from < end)
1637 .find(|later| later.from > from && later.held != held);
1638 if let Some(later) = later {
1639 spans[which].len = later.from - from;
1640 }
1641 }
1642 let mut edges: Vec<u64> =
1645 spans.iter().flat_map(|span| [span.from, span.from + span.len]).collect();
1646 edges.sort_unstable();
1647 edges.dedup();
1648 let mut out: Vec<rucc_debug::Span> = Vec::new();
1649 let mut first = 0;
1650 for pair in edges.windows(2) {
1651 let (from, to) = (pair[0], pair[1]);
1652 while spans.get(first).is_some_and(|span| span.from + span.len <= from) {
1656 first += 1;
1657 }
1658 let mut held = None;
1659 let mut agreed = true;
1660 for span in &spans[first..] {
1661 if span.from >= to {
1662 break;
1663 }
1664 if span.from > from || span.from + span.len < to {
1665 continue;
1666 }
1667 match held {
1668 None => held = Some(span.held),
1669 Some(seen) => agreed &= seen == span.held,
1670 }
1671 }
1672 let (Some(held), true) = (held, agreed) else { continue };
1673 match out.last_mut() {
1674 Some(last) if last.from + last.len == from && last.held == held => {
1675 last.len += to - from
1676 }
1677 _ => out.push(rucc_debug::Span { from, len: to - from, held }),
1678 }
1679 }
1680 out
1681}
1682
1683fn interned(files: &mut Vec<String>, name: String) -> usize {
1689 match files.iter().position(|have| *have == name) {
1690 Some(which) => which,
1691 None => {
1692 files.push(name);
1693 files.len() - 1
1694 }
1695 }
1696}
1697
1698fn output(opts: &Options, target: &TargetInfo) -> rucc_object::Output {
1710 let mut features = 0;
1711 if target.tuple.arch() == Arch::X86_64 {
1712 if opts.control.branch() {
1713 features |= rucc_object::Property::IBT;
1714 }
1715 if opts.control.ret() {
1716 features |= rucc_object::Property::SHSTK;
1717 }
1718 }
1719 rucc_object::Output {
1720 sections: rucc_object::Sections {
1721 functions: opts.function_sections,
1722 data: opts.data_sections,
1723 },
1724 property: rucc_object::Property { features },
1725 }
1726}
1727
1728fn wrote(why: rucc_object::Error) -> Vec<Diagnostic> {
1734 match why {
1735 rucc_object::Error::Format { .. } => vec![unsupported(&why.to_string())],
1736 rucc_object::Error::Refused { .. } => vec![internal(&why.to_string())],
1737 }
1738}
1739
1740fn refused(why: rucc_asm::Error) -> Vec<Diagnostic> {
1747 match why {
1748 rucc_asm::Error::Thread { .. }
1749 | rucc_asm::Error::IFunc { .. }
1750 | rucc_asm::Error::Frame { .. } => {
1751 vec![unsupported(&why.to_string())]
1752 }
1753 _ => vec![internal(&why.to_string())],
1754 }
1755}
1756
1757fn unsupported(message: &str) -> Diagnostic {
1763 unsupported_at(message, Span::DUMMY)
1764}
1765
1766fn unsupported_at(message: &str, span: Span) -> Diagnostic {
1772 Diagnostic::error(message.to_owned(), span)
1773 .with_code("E0653")
1774 .note("this construct is not lowered yet, see https://github.com/tamnd/rucc/issues", span)
1775}
1776
1777fn invalid(message: &str) -> Diagnostic {
1779 Diagnostic::error(message.to_owned(), Span::DUMMY).with_code("E0661")
1780}
1781
1782fn internal(message: &str) -> Diagnostic {
1784 Diagnostic::error(format!("internal error: {message}"), Span::DUMMY)
1785 .with_code("E0652")
1786 .note("this is a bug in rucc rather than in the program, please report it", Span::DUMMY)
1787}
1788
1789fn su_file(stack: &StackUsage, sources: &SourceMap, file: &str) -> String {
1796 let mut out = String::new();
1797 for row in stack.rows() {
1798 let span = row.span();
1799 let at = (!span.is_dummy()).then(|| sources.presumed(span.lo)).flatten();
1800 let (name, line, column) = at.map_or((file, 0, 0), |at| (at.name, at.line, at.column));
1801 out.push_str(&row.line(name, line, column));
1802 }
1803 out
1804}
1805
1806fn failure(message: String) -> Compiled {
1809 Compiled {
1810 artifact: Artifact::Nothing,
1811 messages: vec![format!("rucc: error: {message}")],
1812 errors: 1,
1813 fired: Fired::new(),
1814 pressure: Pressure::new(),
1815 lowerings: Lowerings::new(),
1816 dumps: Vec::new(),
1817 remarks: String::new(),
1818 deps: Vec::new(),
1819 temps: Temps::default(),
1820 timing: crate::trace::Timing::default(),
1821 stack_usage: String::new(),
1822 }
1823}
1824
1825#[cfg(test)]
1826mod tests {
1827 use rucc_session::{MemoryFileSystem, Std};
1828 use rucc_target::Triple;
1829
1830 use super::*;
1831
1832 fn options() -> Options {
1833 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1834 opts.emit = EmitKind::Tast;
1835 opts.frame_pointer = Some(false);
1838 opts
1839 }
1840
1841 fn run(opts: &Options, source: &str) -> Compiled {
1842 let mut fs = MemoryFileSystem::new();
1843 fs.insert("/main.c", source.to_owned().into_bytes());
1844 compile(opts, "/main.c", &fs)
1845 }
1846
1847 fn freestanding() -> Options {
1851 let mut opts = options();
1852 opts.hosted = false;
1853 opts.search.push_system(rucc_session::runtime::DIR);
1854 opts
1855 }
1856
1857 fn shipped(source: &str) -> String {
1859 let result = run(&freestanding(), source);
1860 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1861 result.text().to_owned()
1862 }
1863
1864 fn tast(source: &str) -> String {
1866 let result = run(&options(), source);
1867 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1868 result.text().to_owned()
1869 }
1870
1871 #[test]
1872 fn the_shipped_stdarg_declares_a_list_and_the_four_operators() {
1873 let text = shipped(concat!(
1874 "#include <stdarg.h>\n",
1875 "int sum(int n, ...) {\n",
1876 " va_list ap, copy;\n",
1877 " va_start(ap, n);\n",
1878 " va_copy(copy, ap);\n",
1879 " int total = va_arg(ap, int) + va_arg(copy, int);\n",
1880 " va_end(ap);\n",
1881 " va_end(copy);\n",
1882 " return total;\n",
1883 "}\n",
1884 ));
1885 assert!(text.contains("va-start"), "{text}");
1886 assert!(text.contains("va-copy"), "{text}");
1887 assert!(text.contains("va-arg"), "{text}");
1888 assert!(text.contains("va-end"), "{text}");
1889 }
1890
1891 #[test]
1895 fn stdarg_hands_out_the_type_alone_when_that_is_all_that_was_asked_for() {
1896 let text = shipped(concat!(
1897 "#define __need___va_list\n",
1898 "#include <stdarg.h>\n",
1899 "int vprint(const char *f, __gnuc_va_list ap);\n",
1900 "#ifdef va_start\n",
1901 "#error va_start should not be defined\n",
1902 "#endif\n",
1903 "#ifdef _VA_LIST_DEFINED\n",
1904 "#error va_list should not have been made\n",
1905 "#endif\n",
1906 ));
1907 assert!(text.contains("vprint"), "{text}");
1908 }
1909
1910 #[test]
1913 fn stddef_answers_one_piece_at_a_time_and_the_next_request_still_gets_through() {
1914 let text = shipped(concat!(
1915 "#define __need_size_t\n",
1916 "#include <stddef.h>\n",
1917 "#ifdef offsetof\n",
1918 "#error offsetof should not be defined yet\n",
1919 "#endif\n",
1920 "#define __need_ptrdiff_t\n",
1921 "#include <stddef.h>\n",
1922 "#include <stddef.h>\n",
1923 "size_t a;\n",
1924 "ptrdiff_t b;\n",
1925 "wchar_t c;\n",
1926 "max_align_t d;\n",
1927 "void *e = NULL;\n",
1928 "struct P { int x; long y; };\n",
1929 "size_t f = offsetof(struct P, y);\n",
1930 ));
1931 assert!(text.contains("decl #0 a : unsigned long"), "{text}");
1932 assert!(text.contains("decl #1 b : long"), "{text}");
1933 }
1934
1935 #[test]
1936 fn the_shipped_limits_and_float_are_the_targets_own_answers() {
1937 let text = shipped(concat!(
1938 "#include <limits.h>\n",
1939 "#include <float.h>\n",
1940 "int bits = CHAR_BIT;\n",
1941 "long big = LONG_MAX;\n",
1942 "int low = INT_MIN;\n",
1943 "int radix = FLT_RADIX;\n",
1944 "int digits = DBL_MANT_DIG;\n",
1945 ));
1946 assert!(text.contains("const 8 : int"), "{text}");
1947 assert!(text.contains("const 9223372036854775807 : long"), "{text}");
1948 assert!(text.contains("const 2 : int"), "{text}");
1949 assert!(text.contains("const 53 : int"), "{text}");
1950 }
1951
1952 #[test]
1956 fn the_shipped_stdint_writes_the_whole_set_when_there_is_no_library_to_defer_to() {
1957 let text = shipped(concat!(
1958 "#include <stdint.h>\n",
1959 "int64_t a = INT64_C(1);\n",
1960 "uint_least16_t b;\n",
1961 "intptr_t c;\n",
1962 "uintmax_t d = UINTMAX_MAX;\n",
1963 "int wide = sizeof(int_fast64_t);\n",
1964 ));
1965 assert!(text.contains("decl #0 a : long"), "{text}");
1966 assert!(text.contains("decl #1 b : unsigned short"), "{text}");
1967 assert!(text.contains("decl #2 c : long"), "{text}");
1968 }
1969
1970 #[test]
1981 fn the_shipped_mmintrin_defines_the_mmx_type_and_the_operations_over_it() {
1982 let text = shipped(concat!(
1983 "#include <mmintrin.h>\n",
1984 "__m64 add(__m64 a, __m64 b) { return _mm_add_pi16(a, b); }\n",
1985 "__m64 pack(__m64 a, __m64 b) { return _m_packsswb(a, b); }\n",
1986 "__m64 shift(__m64 a) { return _mm_srai_pi32(a, 3); }\n",
1987 "int low(__m64 a) { return _mm_cvtsi64_si32(a); }\n",
1988 "void done(void) { _mm_empty(); }\n",
1989 ));
1990 assert!(text.contains("add"), "{text}");
1991 assert!(text.contains("pack"), "{text}");
1992 assert!(text.contains("shift"), "{text}");
1993 }
1994
1995 #[test]
2000 fn the_shipped_mm_malloc_asks_for_aligned_memory_and_gives_it_back() {
2001 let text = shipped(concat!(
2002 "#include <mm_malloc.h>\n",
2003 "void *get(void) { return _mm_malloc(64, 16); }\n",
2004 "void put(void *p) { _mm_free(p); }\n",
2005 ));
2006 assert!(text.contains("get"), "{text}");
2007 assert!(text.contains("put"), "{text}");
2008 }
2009
2010 #[test]
2022 fn the_shipped_xmmintrin_defines_the_sse_type_and_the_operations_over_it() {
2023 let text = shipped(concat!(
2024 "#include <xmmintrin.h>\n",
2025 "__m128 add(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
2026 "__m128 one(__m128 a, __m128 b) { return _mm_max_ss(a, b); }\n",
2027 "__m128 mask(__m128 a, __m128 b) { return _mm_cmpnle_ps(a, b); }\n",
2028 "__m128 pick(__m128 a, __m128 b) { return _mm_shuffle_ps(a, b, _MM_SHUFFLE(0,1,2,3)); }\n",
2029 "int bits(__m128 a) { return _mm_movemask_ps(a); }\n",
2030 "int near(__m128 a) { return _mm_cvtss_si32(a); }\n",
2031 "__m128 wide(__m64 a) { return _mm_cvtpi16_ps(a); }\n",
2032 "void *room(void) { return _mm_malloc(64, 16); }\n",
2033 "void hint(const float *p) { _mm_prefetch(p, _MM_HINT_T0); _mm_sfence(); }\n",
2034 ));
2035 assert!(text.contains("add"), "{text}");
2036 assert!(text.contains("mask"), "{text}");
2037 assert!(text.contains("pick"), "{text}");
2038 assert!(text.contains("wide"), "{text}");
2039 }
2040
2041 #[test]
2048 fn the_shipped_xmmintrin_leaves_out_the_names_that_need_an_instruction() {
2049 let text = rucc_session::runtime::header("xmmintrin.h").expect("xmmintrin.h is shipped");
2050 for absent in [
2051 "_mm_sqrt_ps",
2052 "_mm_sqrt_ss",
2053 "_mm_rsqrt_ps",
2054 "_mm_rsqrt_ss",
2055 "_mm_getcsr",
2056 "_mm_setcsr",
2057 ] {
2058 let defined = text.contains(&format!("{absent}("));
2059 assert!(!defined, "{absent} is defined and the header says it is not");
2060 assert!(text.contains(absent), "{absent} is absent and unexplained");
2061 }
2062 }
2063
2064 #[test]
2065 fn the_shipped_emmintrin_defines_both_sse2_types_and_the_operations_over_them() {
2066 let text = shipped(concat!(
2067 "#include <emmintrin.h>\n",
2068 "__m128i add(__m128i a, __m128i b) { return _mm_add_epi64(a, b); }\n",
2069 "__m128i wide(__m128i a, __m128i b) { return _mm_mul_epu32(a, b); }\n",
2070 "__m128i pick(__m128i a) { return _mm_shuffle_epi32(a, _MM_SHUFFLE(0,1,2,3)); }\n",
2071 "__m128i up(__m128i a) { return _mm_slli_epi64(a, 13); }\n",
2072 "__m128i down(__m128i a) { return _mm_srli_si128(a, 3); }\n",
2073 "__m128i pack(__m128i a, __m128i b) { return _mm_packus_epi16(a, b); }\n",
2074 "int bits(__m128i a) { return _mm_movemask_epi8(a); }\n",
2075 "__m128d sum(__m128d a, __m128d b) { return _mm_add_sd(a, b); }\n",
2076 "__m128d mask(__m128d a, __m128d b) { return _mm_cmpunord_pd(a, b); }\n",
2077 "__m128i near(__m128d a) { return _mm_cvtpd_epi32(a); }\n",
2078 "__m128d over(__m128 a) { return _mm_cvtps_pd(a); }\n",
2079 "__m128i half(__m64 a) { return _mm_movpi64_epi64(a); }\n",
2080 "__m128i grab(void const *p) { return _mm_loadu_si128(p); }\n",
2081 "void wall(void) { _mm_lfence(); _mm_mfence(); }\n",
2082 ));
2083 assert!(text.contains("wide"), "{text}");
2084 assert!(text.contains("pack"), "{text}");
2085 assert!(text.contains("near"), "{text}");
2086 assert!(text.contains("half"), "{text}");
2087 }
2088
2089 #[test]
2093 fn the_shipped_immintrin_reaches_the_names_the_headers_under_it_define() {
2094 let text = shipped(concat!(
2095 "#include <immintrin.h>\n",
2096 "unsigned long long matching(unsigned char tag, unsigned char const *bucket) {\n",
2097 " __m128i const want = _mm_set1_epi8((char)tag);\n",
2098 " __m128i const chunk = _mm_loadu_si128((__m128i const *)(void const *)bucket);\n",
2099 " __m128i const same = _mm_cmpeq_epi8(chunk, want);\n",
2100 " return (unsigned long long)_mm_movemask_epi8(same);\n",
2101 "}\n",
2102 "__m64 narrow(__m64 a, __m64 b) { return _mm_add_pi32(a, b); }\n",
2103 "__m128 single(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
2104 ));
2105 assert!(text.contains("matching"), "{text}");
2106 assert!(text.contains("narrow"), "the MMX header is not reached: {text}");
2107 assert!(text.contains("single"), "the SSE header is not reached: {text}");
2108 }
2109
2110 #[test]
2114 fn the_shipped_x86intrin_reaches_the_fences_windows_headers_ask_it_for() {
2115 let text = shipped(concat!(
2116 "#include <x86intrin.h>\n",
2117 "void barriers(void *p) {\n",
2118 " _mm_lfence();\n",
2119 " _mm_sfence();\n",
2120 " _mm_mfence();\n",
2121 " _mm_pause();\n",
2122 " _mm_clflush(p);\n",
2123 "}\n",
2124 "__m128i wide(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
2125 ));
2126 assert!(text.contains("barriers"), "{text}");
2127 assert!(text.contains("wide"), "the SSE2 header is not reached: {text}");
2128 }
2129
2130 #[test]
2134 fn the_umbrella_and_the_header_under_it_can_both_be_included() {
2135 let text = shipped(concat!(
2136 "#include <immintrin.h>\n",
2137 "#include <emmintrin.h>\n",
2138 "#include <immintrin.h>\n",
2139 "#include <x86intrin.h>\n",
2140 "__m128i twice(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
2141 ));
2142 assert!(text.contains("twice"), "{text}");
2143 }
2144
2145 #[test]
2148 fn the_shipped_arm_neon_has_what_xxhash_asks_it_for() {
2149 let mut opts = freestanding();
2150 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2151 let source = concat!(
2152 "#include <arm_neon.h>\n",
2153 "uint64x2_t acc(uint64x2_t sum, const void *in, const void *key) {\n",
2154 " uint8x16_t data = vld1q_u8((const uint8_t *)in);\n",
2155 " uint8x16_t k = vld1q_u8((const uint8_t *)key);\n",
2156 " uint64x2_t mixed = vreinterpretq_u64_u8(veorq_u8(data, k));\n",
2157 " uint32x2_t lo = vmovn_u64(mixed);\n",
2158 " uint32x2_t hi = vshrn_n_u64(mixed, 32);\n",
2159 " return vmlal_u32(sum, lo, hi);\n",
2160 "}\n",
2161 "uint32x4x2_t pair(uint32x4_t a, uint32x4_t b) { return vzipq_u32(a, b); }\n",
2162 "uint32_t total(uint32x4_t a) { return vaddvq_u32(a); }\n",
2163 );
2164 let result = run(&opts, source);
2165 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2166 assert!(result.text().contains("pair"), "{}", result.text());
2167 assert!(result.text().contains("total"), "{}", result.text());
2168 }
2169
2170 #[test]
2172 fn the_shipped_arm_neon_refuses_another_target() {
2173 let result = run(&freestanding(), "#include <arm_neon.h>\n");
2174 let said = result.messages.join("\n");
2175 assert!(said.contains("arm_neon.h is for AArch64"), "{said}");
2176 }
2177
2178 #[test]
2182 fn the_shipped_emmintrin_leaves_out_the_two_square_roots() {
2183 let text = rucc_session::runtime::header("emmintrin.h").expect("emmintrin.h is shipped");
2184 for absent in ["_mm_sqrt_pd", "_mm_sqrt_sd"] {
2185 let defined = text.contains(&format!("{absent}("));
2186 assert!(!defined, "{absent} is defined and the header says it is not");
2187 assert!(text.contains(absent), "{absent} is absent and unexplained");
2188 }
2189 }
2190
2191 #[test]
2196 fn the_shipped_nmmintrin_is_one_instruction_per_step_under_sse4_2() {
2197 let mut opts = freestanding();
2198 opts.emit = EmitKind::Asm;
2199 let mut choices = rucc_target::Choices::new();
2200 choices.read("sse4.2").expect("gcc knows sse4.2");
2201 opts.isa = choices.over(opts.isa);
2202 let source = concat!(
2203 "#include <nmmintrin.h>\n",
2204 "unsigned b(unsigned c, unsigned char v) { return _mm_crc32_u8(c, v); }\n",
2205 "unsigned w(unsigned c, unsigned short v) { return _mm_crc32_u16(c, v); }\n",
2206 "unsigned l(unsigned c, unsigned v) { return _mm_crc32_u32(c, v); }\n",
2207 "unsigned long long q(unsigned long long c, unsigned long long v) {\n",
2208 " return _mm_crc32_u64(c, v);\n",
2209 "}\n",
2210 "int n(unsigned v) { return _mm_popcnt_u32(v); }\n",
2211 "long long m(unsigned long long v) { return _mm_popcnt_u64(v); }\n",
2212 );
2213 let result = run(&opts, source);
2214 assert_eq!(result.messages, Vec::<String>::new());
2215 let text = result.text();
2216 for step in ["crc32b", "crc32w", "crc32l", "crc32q", "popcntl", "popcntq"] {
2217 assert!(text.contains(step), "no {step} in:\n{text}");
2218 }
2219 }
2220
2221 #[test]
2224 fn the_shipped_smmintrin_refuses_a_caller_not_built_for_the_checksum() {
2225 let result = run(
2226 &freestanding(),
2227 "#include <immintrin.h>\nunsigned f(unsigned c) { return _mm_crc32_u32(c, 1); }\n",
2228 );
2229 let said = result.messages.join("\n");
2230 let refusal = "inlining failed in call to 'always_inline' '_mm_crc32_u32': target \
2231 specific option mismatch";
2232 assert!(said.contains(refusal), "{said}");
2233 }
2234
2235 #[test]
2240 fn a_function_built_for_sse4_2_calls_the_steps_without_a_flag() {
2241 let mut opts = freestanding();
2242 opts.emit = EmitKind::Asm;
2243 let source = concat!(
2244 "#include <nmmintrin.h>\n",
2245 "#if defined(__has_attribute) && __has_attribute (target)\n",
2246 "__attribute__((target(\"sse4.2\")))\n",
2247 "#endif\n",
2248 "unsigned l(unsigned c, unsigned v) { return _mm_crc32_u32(c, v); }\n",
2249 "__attribute__((target(\"popcnt\")))\n",
2250 "int n(unsigned v) { return _mm_popcnt_u32(v); }\n",
2251 );
2252 let result = run(&opts, source);
2253 assert_eq!(result.messages, Vec::<String>::new());
2254 let text = result.text();
2255 assert!(text.contains("crc32l") && text.contains("popcntl"), "{text}");
2256 let l = &text[text.find("\nl:").expect("l is defined")..];
2257 let l = &l[..l.find("ret").expect("l returns")];
2258 assert!(l.contains("crc32l") && !l.contains("call"), "{l}");
2259 }
2260
2261 #[test]
2268 fn the_shipped_avx512_headers_pass_postgres_probes() {
2269 let popcount = concat!(
2270 "#include <immintrin.h>\n",
2271 "#include <stdint.h>\n",
2272 "char buf[sizeof(__m512i)];\n",
2273 "#if defined(__has_attribute) && __has_attribute (target)\n",
2274 "__attribute__((target(\"avx512vpopcntdq,avx512bw\")))\n",
2275 "#endif\n",
2276 "int popcount_test(void)\n",
2277 "{\n",
2278 " int64_t popcnt = 0;\n",
2279 " __m512i accum = _mm512_setzero_si512();\n",
2280 " __m512i val = _mm512_maskz_loadu_epi8((__mmask64) 0xf0f0f0f0f0f0f0f0, (const __m512i *) buf);\n",
2281 " __m512i cnt = _mm512_popcnt_epi64(val);\n",
2282 " accum = _mm512_add_epi64(accum, cnt);\n",
2283 " popcnt = _mm512_reduce_add_epi64(accum);\n",
2284 " return (int) popcnt;\n",
2285 "}\n",
2286 );
2287 let pclmul = concat!(
2288 "#include <immintrin.h>\n",
2289 "__m512i x;\n",
2290 "__m512i y;\n",
2291 "#if defined(__has_attribute) && __has_attribute (target)\n",
2292 "__attribute__((target(\"vpclmulqdq,avx512vl\")))\n",
2293 "#endif\n",
2294 "int avx512_pclmul_test(void)\n",
2295 "{\n",
2296 " __m128i z;\n",
2297 " x = _mm512_xor_si512(_mm512_zextsi128_si512(_mm_cvtsi32_si128(0)), x);\n",
2298 " y = _mm512_clmulepi64_epi128(x, y, 0);\n",
2299 " z = _mm_ternarylogic_epi64(\n",
2300 " _mm512_castsi512_si128(y),\n",
2301 " _mm512_extracti32x4_epi32(y, 1),\n",
2302 " _mm512_extracti32x4_epi32(y, 2),\n",
2303 " 0x96);\n",
2304 " return _mm_crc32_u64(0, _mm_extract_epi64(z, 0));\n",
2305 "}\n",
2306 );
2307 let checks: [(&str, &str, &[&str]); 2] = [
2308 (popcount, "popcount_test", &["kmovq", "vmovdqu8", "vpopcntq", "vpaddq", "vshufi64x2"]),
2309 (pclmul, "avx512_pclmul_test", &["vpxorq", "vpclmulqdq", "vpternlogq", "crc32q"]),
2310 ];
2311 for (source, name, wanted) in checks {
2312 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
2313 let mut opts = freestanding();
2314 opts.emit = EmitKind::Asm;
2315 opts.opt_level = level;
2316 let result = run(&opts, source);
2317 assert_eq!(result.messages, Vec::<String>::new(), "{name} at {level:?}");
2318 let text = result.text();
2319 let start = text.find(&format!("\n{name}:")).expect("the probe is written out");
2320 let body = &text[start..];
2321 let body = &body[..body.find(".size").unwrap_or(body.len())];
2322 for instruction in wanted {
2323 assert!(
2324 body.contains(instruction),
2325 "no {instruction} at {level:?} in:\n{body}"
2326 );
2327 }
2328 assert!(!body.contains("call"), "a call left behind at {level:?} in:\n{body}");
2329 }
2330 }
2331 }
2332
2333 #[test]
2336 fn the_shipped_avx512_headers_refuse_a_caller_not_built_for_them() {
2337 let result = run(
2338 &freestanding(),
2339 "#include <immintrin.h>\n__m512i f(__m512i a) { return _mm512_popcnt_epi64(a); }\n",
2340 );
2341 let said = result.messages.join("\n");
2342 let refusal = "inlining failed in call to 'always_inline' '_mm512_popcnt_epi64': target \
2343 specific option mismatch";
2344 assert!(said.contains(refusal), "{said}");
2345 }
2346
2347 #[test]
2352 fn the_sse3_to_sse4_2_intrinsics_are_the_instructions_under_the_attribute() {
2353 let mut opts = freestanding();
2354 opts.emit = EmitKind::Asm;
2355 let source = concat!(
2356 "#include <immintrin.h>\n",
2357 "__attribute__((target(\"sse3\")))\n",
2358 "__m128i a(const __m128i *p) { return _mm_lddqu_si128(p); }\n",
2359 "__attribute__((target(\"sse3\")))\n",
2360 "__m128 b(__m128 x, __m128 y) { return _mm_hadd_ps(x, y); }\n",
2361 "__attribute__((target(\"ssse3\")))\n",
2362 "__m128i c(__m128i x, __m128i y) { return _mm_shuffle_epi8(_mm_abs_epi32(x), y); }\n",
2363 "__attribute__((target(\"ssse3\")))\n",
2364 "__m128i d(__m128i x, __m128i y) { return _mm_alignr_epi8(x, y, 5); }\n",
2365 "__attribute__((target(\"sse4.1\")))\n",
2366 "int e(__m128i x, __m128i y) {\n",
2367 " return _mm_extract_epi32(_mm_min_epi32(_mm_mullo_epi32(x, y), y), 2);\n",
2368 "}\n",
2369 "__attribute__((target(\"sse4.1\")))\n",
2370 "__m128 f(__m128 x) { return _mm_round_ps(x, _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); }\n",
2371 "__attribute__((target(\"sse4.1\")))\n",
2372 "__m128i g(__m128i x, __m128i y, __m128i m) { return _mm_blendv_epi8(x, y, m); }\n",
2373 "__attribute__((target(\"sse4.1\")))\n",
2374 "int h(__m128i x) { return _mm_testz_si128(x, x); }\n",
2375 "__attribute__((target(\"sse4.2\")))\n",
2376 "__m128i i(__m128i x, __m128i y) { return _mm_cmpgt_epi64(x, y); }\n",
2377 "__attribute__((target(\"sse4.2\")))\n",
2378 "int j(__m128i x, __m128i y) { return _mm_cmpistri(x, y, _SIDD_CMP_EQUAL_EACH); }\n",
2379 );
2380 let result = run(&opts, source);
2381 assert_eq!(result.messages, Vec::<String>::new());
2382 let text = result.text();
2383 for insn in [
2384 "lddqu",
2385 "haddps",
2386 "pabsd",
2387 "pshufb",
2388 "palignr $5,",
2389 "pmulld",
2390 "pminsd",
2391 "pextrd $2,",
2392 "roundps $8,",
2393 "pblendvb",
2394 "ptest",
2395 "pcmpgtq",
2396 "pcmpistri $8,",
2397 ] {
2398 assert!(text.contains(insn), "no {insn} in:\n{text}");
2399 }
2400 assert!(!text.contains("call"), "{text}");
2401 }
2402
2403 #[test]
2406 fn the_sse3_to_sse4_1_intrinsics_are_refused_a_caller_not_built_for_them() {
2407 let source = concat!(
2408 "#include <immintrin.h>\n",
2409 "__m128i f(__m128i x, __m128i y) { return _mm_shuffle_epi8(x, y); }\n",
2410 );
2411 let said = run(&freestanding(), source).messages.join("\n");
2412 let refusal = "inlining failed in call to 'always_inline' '_mm_shuffle_epi8': target \
2413 specific option mismatch";
2414 assert!(said.contains(refusal), "{said}");
2415
2416 let mut opts = freestanding();
2417 let mut choices = rucc_target::Choices::new();
2418 choices.read("ssse3").expect("gcc knows ssse3");
2419 opts.isa = choices.over(opts.isa);
2420 let result =
2421 run(&opts, &format!("{source}__m128 g(__m128 x) {{ return _mm_movehdup_ps(x); }}\n"));
2422 assert_eq!(result.messages, Vec::<String>::new());
2423 let result = run(
2424 &opts,
2425 "#include <immintrin.h>\n__m128i h(__m128i x) { return _mm_abs_epi8(_mm_cvtepi8_epi32(x)); }\n",
2426 );
2427 let said = result.messages.join("\n");
2428 assert!(said.contains("'_mm_cvtepi8_epi32': target specific option mismatch"), "{said}");
2429 }
2430
2431 #[test]
2434 fn a_target_string_gcc_does_not_know_is_refused() {
2435 for (string, name) in [("sse5", "sse5"), ("+crc", "+crc"), ("sse4.2,foo", "foo")] {
2436 let source =
2437 format!("__attribute__((target(\"{string}\"))) int f(void) {{ return 0; }}\n");
2438 let said = run(&freestanding(), &source).messages.join("\n");
2439 let wanted = format!("attribute 'target' argument '{name}' is unknown");
2440 assert!(said.contains(&wanted), "{string}: {said}");
2441 }
2442 }
2443
2444 #[test]
2447 fn an_aarch64_target_string_is_still_accepted() {
2448 let mut opts = freestanding();
2449 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2450 let source = "__attribute__((target(\"+crc\"))) int f(void) { return 0; }\n";
2451 let result = run(&opts, source);
2452 assert_eq!(result.messages, Vec::<String>::new());
2453 }
2454
2455 #[test]
2456 fn the_three_formality_headers_still_have_to_work() {
2457 let text = shipped(concat!(
2458 "#include <stdbool.h>\n",
2459 "#include <stdalign.h>\n",
2460 "#include <iso646.h>\n",
2461 "#include <stdnoreturn.h>\n",
2462 "int t = true and not false;\n",
2463 "_Alignas(16) char buf[16];\n",
2464 "int a = alignof(long);\n",
2465 ));
2466 assert!(text.contains("decl #0 t : int"), "{text}");
2467 assert!(text.contains("const 8 : unsigned long"), "{text}");
2468 }
2469
2470 #[test]
2478 fn every_shipped_header_can_be_included_twice() {
2479 let once: String = rucc_session::runtime::names()
2482 .iter()
2483 .filter(|name| **name != "arm_neon.h")
2484 .map(|name| format!("#include <{name}>\n"))
2485 .collect();
2486 let twice = once.repeat(2);
2487 assert_eq!(shipped(&format!("{once}int x;\n")), shipped(&format!("{twice}int x;\n")));
2488
2489 let mut opts = freestanding();
2490 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2491 let tree = |source: &str| {
2492 let result = run(&opts, source);
2493 assert_eq!(
2494 result.messages,
2495 Vec::<String>::new(),
2496 "expected this to compile:\n{source}"
2497 );
2498 result.text().to_owned()
2499 };
2500 let neon = "#include <arm_neon.h>\n";
2501 assert_eq!(tree(&format!("{neon}int x;\n")), tree(&format!("{neon}{neon}int x;\n")));
2502 }
2503
2504 #[test]
2505 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
2506 let fs = MemoryFileSystem::new();
2507 let result = compile(&options(), "/nope.c", &fs);
2508 assert!(result.failed());
2509 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
2510 assert!(result.text().is_empty());
2511 }
2512
2513 #[test]
2514 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
2515 let text = tast("int x = 1;\n");
2516 let expected = "\
2517decl #0 x : int object external static defined
2518 init
2519 +0
2520 const 1 : int
2521";
2522 assert_eq!(text, expected);
2523 }
2524
2525 #[test]
2526 fn the_macros_are_expanded_before_anything_is_parsed() {
2527 let text = tast("#define N 2\nint a[N];\n");
2531 assert!(text.starts_with("decl #0 a : int[2] object external static tentative"), "{text}");
2532 }
2533
2534 #[test]
2540 fn a_pragma_is_not_a_declaration_and_the_parse_walks_past_the_ones_it_does_not_read() {
2541 let text = tast(concat!(
2542 "#pragma pack(4)\n",
2543 "struct s { int a; };\n",
2544 "#pragma pack()\n",
2545 "int b;\n",
2546 "_Pragma(\"GCC visibility push(default)\") int c;\n",
2547 ));
2548 assert!(text.contains("decl #0 b : int"), "{text}");
2549 assert!(text.contains("decl #1 c : int"), "{text}");
2550 }
2551
2552 #[test]
2556 fn the_byte_swaps_and_the_bit_counts_of_a_constant_are_constants() {
2557 tast(concat!(
2558 "static const unsigned magic = __builtin_bswap32(0x11223344u);\n",
2559 "_Static_assert(__builtin_bswap16(0x1234) == 0x3412, \"16\");\n",
2560 "_Static_assert(__builtin_bswap32(0x11223344u) == 0x44332211u, \"32\");\n",
2561 "_Static_assert(__builtin_bswap64(0x0102030405060708ull) == 0x0807060504030201ull, \"64\");\n",
2562 "_Static_assert(__builtin_popcountll(-1ll) == 64 && __builtin_popcount(-1) == 32, \"ones\");\n",
2563 "_Static_assert(__builtin_parity(7) == 1 && __builtin_parity(3) == 0, \"parity\");\n",
2564 "_Static_assert(__builtin_ffs(0) == 0 && __builtin_ffs(8) == 4, \"ffs\");\n",
2565 "_Static_assert(__builtin_clrsb(0) == 31 && __builtin_clrsb(-1) == 31, \"clrsb\");\n",
2566 "_Static_assert(__builtin_clrsbl(1) == 62, \"clrsbl\");\n",
2567 "_Static_assert(__builtin_clz(1) == 31 && __builtin_clzl(1) == 63, \"clz\");\n",
2568 "_Static_assert(__builtin_ctzll(1ull << 40) == 40, \"ctz\");\n",
2569 "_Static_assert(__builtin_clz(0) == 32 && __builtin_ctzll(0) == 64, \"zero\");\n",
2570 ));
2571 }
2572
2573 #[test]
2581 fn the_layout_attributes_move_the_members_and_the_record_the_way_gcc_lays_them_out() {
2582 tast(concat!(
2583 "struct A { char c; int i; } __attribute__((packed));\n",
2584 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2585 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2586 "struct B { char c; int i; } __attribute__((aligned));\n",
2589 "_Static_assert(sizeof(struct B) == 16 && _Alignof(struct B) == 16, \"B\");\n",
2590 "struct C { char c; int i __attribute__((packed)); };\n",
2591 "_Static_assert(sizeof(struct C) == 5 && _Alignof(struct C) == 1, \"C\");\n",
2592 "_Static_assert(__builtin_offsetof(struct C, i) == 1, \"C.i\");\n",
2593 "struct D { char c; int i; } __attribute__((packed, aligned(4)));\n",
2594 "_Static_assert(sizeof(struct D) == 8 && _Alignof(struct D) == 4, \"D\");\n",
2595 "_Static_assert(__builtin_offsetof(struct D, i) == 1, \"D.i\");\n",
2596 "struct E { char c; _Alignas(8) int i; };\n",
2597 "_Static_assert(sizeof(struct E) == 16 && _Alignof(struct E) == 8, \"E\");\n",
2598 "_Static_assert(__builtin_offsetof(struct E, i) == 8, \"E.i\");\n",
2599 "struct F { char c; int i __attribute__((aligned(8))); };\n",
2600 "_Static_assert(sizeof(struct F) == 16 && _Alignof(struct F) == 8, \"F\");\n",
2601 "struct G { char c; short s; } __attribute__((aligned(2)));\n",
2604 "_Static_assert(sizeof(struct G) == 4 && _Alignof(struct G) == 2, \"G\");\n",
2605 "struct H { char c; int i; } __attribute__((aligned(2)));\n",
2606 "_Static_assert(sizeof(struct H) == 8 && _Alignof(struct H) == 4, \"H\");\n",
2607 "struct I { [[gnu::packed]] char c; int i; };\n",
2610 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2611 "struct J { char c; [[gnu::packed]] int i; };\n",
2612 "_Static_assert(sizeof(struct J) == 5 && _Alignof(struct J) == 1, \"J\");\n",
2613 "struct M { char c; int i : 5; int j : 20; } __attribute__((packed));\n",
2614 "_Static_assert(sizeof(struct M) == 5 && _Alignof(struct M) == 1, \"M\");\n",
2615 "struct N { char c; long long l; } __attribute__((aligned(32)));\n",
2616 "_Static_assert(sizeof(struct N) == 32 && _Alignof(struct N) == 32, \"N\");\n",
2617 "union L { char c; int i; } __attribute__((packed));\n",
2618 "_Static_assert(sizeof(union L) == 4 && _Alignof(union L) == 1, \"L\");\n",
2619 "struct O { char c; int i; } __attribute__((__packed__));\n",
2623 "_Static_assert(sizeof(struct O) == 5 && _Alignof(struct O) == 1, \"O\");\n",
2624 "struct P { char c; int i; } __attribute__((__aligned__(8)));\n",
2625 "_Static_assert(sizeof(struct P) == 8 && _Alignof(struct P) == 8, \"P\");\n",
2626 ));
2627 }
2628
2629 #[test]
2642 fn a_transparent_union_takes_the_member_a_value_fits_and_is_declared_either_way() {
2643 let text = tast(concat!(
2644 "struct one { int x; };\n",
2645 "struct two { long y; };\n",
2646 "typedef union { struct one *a; struct two *b; void *any; }\n",
2647 " __attribute__((__transparent_union__)) arg;\n",
2648 "int takes(arg v);\n",
2649 "int f(struct one *p, struct two *q, char *c) {\n",
2650 " return takes(p) + takes(q) + takes(c) + takes(0);\n",
2651 "}\n",
2652 "int takes(struct one *p);\n",
2654 "int (*as_a_member)(struct one *) = takes;\n",
2655 "int (*as_the_union)(arg) = takes;\n",
2656 ));
2657 assert!(text.contains("compound-literal"), "{text}");
2658 }
2659
2660 #[test]
2666 fn the_attribute_on_the_declarator_of_a_typedef_is_the_one_glibc_writes() {
2667 let text = tast(concat!(
2668 "struct sockaddr { int family; };\n",
2669 "struct sockaddr_in { int family; int addr; };\n",
2670 "typedef union { struct sockaddr *plain; struct sockaddr_in *inet; }\n",
2671 " addr_arg __attribute__((__transparent_union__));\n",
2672 "int bind_to(int fd, addr_arg where);\n",
2673 "int f(struct sockaddr_in *where) { return bind_to(0, where); }\n",
2674 ));
2675 assert!(text.contains("compound-literal"), "{text}");
2676 }
2677
2678 #[test]
2686 fn a_transparent_union_that_cannot_keep_the_promise_is_dropped_with_a_word_about_it() {
2687 let result = run(
2688 &options(),
2689 concat!(
2690 "union wider { int small; double large; } __attribute__((transparent_union));\n",
2691 "struct plain { int x; } __attribute__((transparent_union));\n",
2692 ),
2693 );
2694 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
2695 assert!(!result.failed(), "{:?}", result.messages);
2696 for message in &result.messages {
2697 assert!(message.contains("'transparent_union' attribute ignored"), "{message}");
2698 }
2699 assert!(result.messages[0].contains("first member"), "{:?}", result.messages);
2700 assert!(result.messages[1].contains("only a union"), "{:?}", result.messages);
2701 }
2702
2703 #[test]
2712 fn an_access_to_a_packed_member_says_the_alignment_the_layout_left_it() {
2713 let packed = body(concat!(
2714 "struct P { char c; int v; } __attribute__((packed));\n",
2715 "int f(struct P *p) { return p->v; }\n",
2716 ));
2717 assert!(packed.contains("load.i32 %2, align 1,"), "{packed}");
2718 let plain = body(concat!(
2720 "struct P { char c; int v; };\n",
2721 "int f(struct P *p) { return p->v; }\n",
2722 ));
2723 assert!(plain.contains("load.i32 %2, align 4,"), "{plain}");
2724 }
2725
2726 #[test]
2733 fn what_is_inside_a_packed_member_is_no_more_aligned_than_the_member_is() {
2734 let stepped = body(concat!(
2735 "struct P { char c; int v[4]; } __attribute__((packed));\n",
2736 "int f(struct P *p, int i) { return p->v[i]; }\n",
2737 ));
2738 assert!(stepped.contains(", align 1,"), "{stepped}");
2739 assert!(!stepped.contains(", align 4,"), "{stepped}");
2740 let nested = body(concat!(
2741 "struct Inner { int v; };\n",
2742 "struct P { char c; struct Inner in; } __attribute__((packed));\n",
2743 "int f(struct P *p) { return p->in.v; }\n",
2744 ));
2745 assert!(nested.contains(", align 1,"), "{nested}");
2746 assert!(!nested.contains(", align 4,"), "{nested}");
2747 }
2748
2749 #[test]
2765 fn an_access_through_a_typedef_that_lowered_its_alignment_says_the_one_the_typedef_asked_for() {
2766 let through = body(concat!(
2767 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2768 "unsigned int f(const void *p) { return *(const unalign32 *)p; }\n",
2769 ));
2770 assert!(through.contains("load.i32 %0, align 1,"), "{through}");
2771 let stepped = body(concat!(
2774 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2775 "unsigned int f(unalign32 *p, int i) { return p[i]; }\n",
2776 ));
2777 assert!(stepped.contains(", align 1,"), "{stepped}");
2778 assert!(!stepped.contains(", align 4,"), "{stepped}");
2779 let plain = body(concat!(
2782 "typedef unsigned int word;\n",
2783 "unsigned int f(const void *p) { return *(const word *)p; }\n",
2784 ));
2785 assert!(plain.contains("load.i32 %0, align 4,"), "{plain}");
2786 }
2787
2788 #[test]
2799 fn a_vector_read_through_a_typedef_that_lowered_its_alignment_comes_back_a_piece_at_a_time() {
2800 let prefix = concat!(
2801 "typedef long long v2di __attribute__((__vector_size__(16)));\n",
2802 "typedef long long v2di_u __attribute__((__vector_size__(16), __aligned__(1)));\n",
2803 );
2804 let loaded =
2805 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di_u *)p; }}"));
2806 assert_eq!(loaded.matches("align 1\n").count(), 2, "{loaded}");
2807 assert!(!loaded.contains("align 16"), "{loaded}");
2808 let stored = body(&format!("{prefix}void f(void *p, v2di b) {{ *(v2di_u *)p = b; }}"));
2811 assert!(stored.contains("memcpy %0, %3, size 16, align 1"), "{stored}");
2812 let aligned =
2814 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di *)p; }}"));
2815 assert!(aligned.contains("align 16"), "{aligned}");
2816 }
2817
2818 #[test]
2827 fn the_aligned_attribute_on_a_declaration_raises_what_that_one_object_is_aligned_to() {
2828 tast(concat!(
2829 "int v __attribute__((aligned(64)));\n",
2830 "_Static_assert(__alignof__(v) == 64, \"v\");\n",
2831 "__attribute__((aligned(32))) int w;\n",
2834 "_Static_assert(__alignof__(w) == 32, \"w\");\n",
2835 "[[gnu::aligned(16)]] int x;\n",
2836 "_Static_assert(__alignof__(x) == 16, \"x\");\n",
2837 "int y __attribute__((aligned(2)));\n",
2840 "_Static_assert(__alignof__(y) == 4, \"y\");\n",
2841 "void f(void) { int a __attribute__((aligned(128)));\n",
2843 "_Static_assert(__alignof__(a) == 128, \"a\"); (void)a; }\n",
2844 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2847 "void g(void) __attribute__((aligned(256)));\n",
2850 "void g(void) {}\n",
2851 "_Static_assert(__alignof__(g) == 256, \"g\");\n",
2852 ));
2853 }
2854
2855 #[test]
2859 fn what_a_declaration_asked_to_be_aligned_to_is_what_the_assembler_is_told() {
2860 let text = asm(concat!(
2861 "int v __attribute__((aligned(64)));\n",
2862 "void g(void) __attribute__((aligned(256)));\n",
2863 "void g(void) {}\n",
2864 "void plain(void) {}\n",
2865 ));
2866 assert!(text.contains("\t.p2align\t6\n\t.type\tv, @object\n"), "{text}");
2867 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2868 assert!(text.contains("\t.p2align\t4, 0x90\n\t.globl\tplain\n"), "{text}");
2869 }
2870
2871 #[test]
2877 fn the_alignment_the_command_line_asked_of_every_function_is_a_floor_under_all_of_them() {
2878 let source = concat!(
2879 "void g(void) __attribute__((aligned(256)));\n",
2880 "void g(void) {}\n",
2881 "void small(void) __attribute__((aligned(4)));\n",
2882 "void small(void) {}\n",
2883 "void plain(void) {}\n",
2884 );
2885 let listing = |align: Option<u32>| {
2886 let mut opts = options();
2887 opts.emit = EmitKind::Asm;
2888 opts.align_functions = align;
2889 let result = run(&opts, source);
2890 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
2891 result.text().to_owned()
2892 };
2893
2894 let text = listing(Some(32));
2895 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "the larger one wins: {text}");
2896 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tsmall\n"), "{text}");
2897 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tplain\n"), "{text}");
2898
2899 let text = listing(Some(8));
2902 assert!(text.contains("\t.p2align\t3, 0x90\n\t.globl\tplain\n"), "{text}");
2903 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2904 }
2905
2906 #[test]
2915 fn an_aligned_typedef_says_what_an_object_of_it_is_aligned_to_and_may_lower_it() {
2916 tast(concat!(
2917 "typedef int L __attribute__((aligned(2)));\n",
2918 "_Static_assert(__alignof__(L) == 2, \"L\");\n",
2919 "_Static_assert(_Alignof(L) == 2, \"L alignof\");\n",
2920 "_Static_assert(sizeof(L) == 4, \"L size\");\n",
2922 "struct T { char c; L x; };\n",
2923 "_Static_assert(sizeof(struct T) == 6, \"T\");\n",
2924 "_Static_assert(__builtin_offsetof(struct T, x) == 2, \"T.x\");\n",
2925 "typedef int H __attribute__((aligned(16)));\n",
2927 "_Static_assert(__alignof__(H) == 16, \"H\");\n",
2928 "_Static_assert(sizeof(H) == 4, \"H size\");\n",
2929 "struct U { char c; H x; };\n",
2930 "_Static_assert(sizeof(struct U) == 32, \"U\");\n",
2931 "_Static_assert(__builtin_offsetof(struct U, x) == 16, \"U.x\");\n",
2932 "typedef L M __attribute__((aligned(8)));\n",
2935 "_Static_assert(__alignof__(M) == 8, \"M\");\n",
2936 "typedef L N;\n",
2939 "_Static_assert(__alignof__(N) == 2, \"N\");\n",
2940 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2942 ));
2943 let text = asm(concat!(
2944 "typedef int L __attribute__((aligned(2)));\n",
2945 "typedef int H __attribute__((aligned(16)));\n",
2946 "L low;\n",
2947 "H high;\n",
2948 ));
2949 assert!(text.contains("\t.p2align\t1\n\t.type\tlow, @object\n"), "{text}");
2950 assert!(text.contains("\t.p2align\t4\n\t.type\thigh, @object\n"), "{text}");
2951 }
2952
2953 #[test]
2961 fn the_vector_size_attribute_builds_a_type_of_lanes_and_measures_it_in_bytes() {
2962 tast(concat!(
2963 "typedef int __attribute__((vector_size(16))) v4si;\n",
2964 "_Static_assert(sizeof(v4si) == 16 && _Alignof(v4si) == 16, \"v4si\");\n",
2965 "typedef char __attribute__((vector_size(16))) v16qi;\n",
2966 "_Static_assert(sizeof(v16qi) == 16, \"v16qi\");\n",
2967 "typedef int __attribute__((vector_size(4))) v1si;\n",
2970 "_Static_assert(sizeof(v1si) == 4, \"v1si\");\n",
2971 "typedef float __attribute__((__vector_size__(8))) v2sf;\n",
2973 "_Static_assert(sizeof(v2sf) == 8, \"v2sf\");\n",
2974 "typedef short [[gnu::vector_size(8)]] v4hi;\n",
2975 "_Static_assert(sizeof(v4hi) == 8, \"v4hi\");\n",
2976 "v4si g;\n",
2979 "_Static_assert(sizeof(g[0]) == 4, \"lane\");\n",
2980 "_Static_assert(sizeof(g + g) == 16, \"whole\");\n",
2981 "_Static_assert(sizeof(g + 1) == 16, \"broadcast\");\n",
2984 "_Static_assert(sizeof(v4si[3]) == 48, \"array\");\n",
2986 ));
2987 }
2988
2989 #[test]
2999 fn a_vector_is_written_whole_into_an_array_of_them_and_named_by_a_type_name() {
3000 tast(concat!(
3001 "typedef int __attribute__((vector_size(8))) v2si;\n",
3002 "v2si table[] = { (v2si){ 1, 2 }, (v2si){ 3, 4 } };\n",
3003 "_Static_assert(sizeof(table) == 16, \"two of them and not eight lanes\");\n",
3004 "v2si written = (int __attribute__((vector_size(8)))){ 5, 6 };\n",
3006 "_Static_assert(sizeof((int __attribute__((vector_size(16)))){ 0 }) == 16, \"named\");\n",
3007 "v2si lanes[2] = { 1, 2, 3, 4 };\n",
3010 "_Static_assert(sizeof(lanes) == 16, \"still elided\");\n",
3011 ));
3012 }
3013
3014 #[test]
3022 fn a_lane_is_assignable_and_a_shift_takes_a_count_of_its_own_lane() {
3023 let result = run(
3024 &options(),
3025 concat!(
3026 "typedef int __attribute__((vector_size(16))) v4si;\n",
3027 "typedef unsigned __attribute__((vector_size(16))) v4ui;\n",
3028 "void write(v4si *out, v4ui a, v4si b, int n) {\n",
3029 " v4si v = { 1, 2, 3, 4 };\n",
3030 " v[0] = n;\n",
3031 " v[1] += n;\n",
3032 " v[2]++;\n",
3033 " *&v[3] = n;\n",
3034 " v4ui shifted = a >> b;\n",
3036 " shifted <<= b;\n",
3037 " *out = v + (v4si)shifted + (1 << b);\n",
3040 "}\n",
3041 "void refused(const v4si c) {\n",
3044 " c[0] = 1;\n",
3045 "}\n",
3046 ),
3047 );
3048 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
3049 assert!(result.messages[0].contains("assignment of read-only"), "{:?}", result.messages);
3050 }
3051
3052 #[test]
3064 fn a_record_that_asks_for_the_other_byte_order_swaps_every_scalar_it_holds() {
3065 let read = "int f(struct s *p) { return p->i; }\n";
3066 let big = "struct s { int i; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
3067 assert!(body(&format!("{big}{read}")).contains("bswap"), "{big}");
3068
3069 let armoured =
3070 "struct s { int i; } __attribute__((__scalar_storage_order__(\"big-endian\")));\n";
3071 assert!(body(&format!("{armoured}{read}")).contains("bswap"), "{armoured}");
3072
3073 let front = "struct __attribute__((scalar_storage_order(\"big-endian\"))) s { int i; };\n";
3074 assert!(body(&format!("{front}{read}")).contains("bswap"), "{front}");
3075
3076 let standard = "struct s { int i; } [[gnu::scalar_storage_order(\"big-endian\")]];\n";
3077 assert!(body(&format!("{standard}{read}")).contains("bswap"), "{standard}");
3078
3079 let same =
3080 "struct s { int i; } __attribute__((scalar_storage_order(\"little-endian\")));\n";
3081 assert!(!body(&format!("{same}{read}")).contains("bswap"), "{same}");
3082
3083 let byte = "struct s { char c; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
3085 let source = format!("{byte}int f(struct s *p) {{ return p->c; }}\n");
3086 assert!(!body(&source).contains("bswap"), "{byte}");
3087
3088 tast(concat!(
3089 "struct s { int i; short h; char c; }",
3090 " __attribute__((scalar_storage_order(\"big-endian\")));\n",
3091 "_Static_assert(sizeof(struct s) == 8 && _Alignof(struct s) == 4, \"s\");\n",
3092 "_Static_assert(__builtin_offsetof(struct s, h) == 4, \"s.h\");\n",
3093 "_Static_assert(__builtin_offsetof(struct s, c) == 6, \"s.c\");\n",
3094 ));
3095 }
3096
3097 #[test]
3103 fn a_bit_field_in_one_of_those_records_is_counted_from_the_top_of_its_bytes() {
3104 let members = "short i : 12; char c1 : 1; char c2 : 1; char c3 : 1; char c4 : 1;";
3105 let read = "int f(struct s *p) { return p->i; }\n";
3106 let plain = format!("struct s {{ {members} }};\n{read}");
3107 let reversed = format!(
3108 "struct s {{ {members} }} __attribute__((scalar_storage_order(\"big-endian\")));\n\
3109 {read}"
3110 );
3111 assert!(body(&plain).contains("shl"), "{}", body(&plain));
3112 assert!(!body(&plain).contains("bswap"), "{}", body(&plain));
3113 let built = body(&reversed);
3116 assert!(built.contains("bswap"), "{built}");
3117 assert!(!built.contains("shl"), "{built}");
3118 assert!(built.contains("ashr"), "{built}");
3119 }
3120
3121 #[test]
3128 fn the_address_of_a_scalar_stored_the_other_way_round_is_refused() {
3129 let opts = options();
3130 let record = "struct s { int i; int a[2]; struct in { int n; } w; }\n\
3131 __attribute__((scalar_storage_order(\"big-endian\")));\n";
3132 let taken = format!("{record}int *f(struct s *p) {{ return &p->i; }}\n");
3133 assert_eq!(
3134 run(&opts, &taken).messages,
3135 ["/main.c:3:30: error: cannot take address of scalar with reverse storage order \
3136 [E0712]"]
3137 );
3138 let element = format!("{record}int *f(struct s *p) {{ return &p->a[0]; }}\n");
3139 let messages = run(&opts, &element).messages;
3140 assert!(messages[0].contains("[E0712]"), "{messages:?}");
3141
3142 let whole = format!("{record}int *f(struct s *p) {{ return (int *) &p->w; }}\n");
3143 assert_eq!(run(&opts, &whole).messages, Vec::<String>::new(), "{whole}");
3144 }
3145
3146 #[test]
3150 fn a_storage_order_that_names_neither_end_is_refused_with_the_two_words_that_are_taken() {
3151 let opts = options();
3152 let wrong = "struct s { int i; } __attribute__((scalar_storage_order(\"middle\")));\n";
3153 assert_eq!(
3154 run(&opts, wrong).messages,
3155 ["/main.c:1:36: error: 'scalar_storage_order' argument must be one of \"big-endian\" \
3156 or \"little-endian\" [E0688]"]
3157 );
3158 let bare = "struct s { int i; } __attribute__((scalar_storage_order));\n";
3159 let messages = run(&opts, bare).messages;
3160 assert!(messages[0].contains("[E0688]"), "{messages:?}");
3161 }
3162
3163 #[test]
3173 fn packing_is_what_decides_whether_a_bit_field_may_straddle_its_own_storage() {
3174 assert_eq!(bit_field_byte("struct s { int x : 12; char y : 6; };"), 2);
3176 assert_eq!(
3177 bit_field_byte("struct s { int x : 12; char y : 6; } __attribute__((packed));"),
3178 1
3179 );
3180 assert_eq!(
3181 bit_field_byte("struct s { int x : 12; __attribute__((packed)) char y : 6; };"),
3182 1
3183 );
3184 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { int x : 12; char y : 6; };"), 1);
3185 assert_eq!(bit_field_byte("struct s { char x; int y : 30; };"), 4);
3187 assert_eq!(bit_field_byte("struct s { char x; int y : 30; } __attribute__((packed));"), 1);
3188 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { char x; int y : 30; };"), 1);
3190 assert_eq!(bit_field_byte("#pragma pack(2)\nstruct s { char x; int y : 30; };"), 1);
3191 }
3192
3193 fn bit_field_byte(record: &str) -> u64 {
3195 let source = format!("{record}\nint f(struct s *p) {{ return p->y; }}\n");
3196 let body = body(&source);
3197 let Some((before, _)) = body.split_once("ptr_add") else { return 0 };
3198 let (_, constant) = before.rsplit_once("iconst.i64 ").expect("an offset constant");
3199 constant.lines().next().expect("a line").trim().parse().expect("a byte offset")
3200 }
3201
3202 #[test]
3208 fn an_attribute_among_the_specifiers_is_kept_beside_the_ones_written_in_front() {
3209 tast(concat!(
3210 "struct a { char c; __attribute__((aligned(8))) int i; };\n",
3211 "_Static_assert(sizeof(struct a) == 16 && _Alignof(struct a) == 8, \"a\");\n",
3212 "_Static_assert(__builtin_offsetof(struct a, i) == 8, \"a.i\");\n",
3213 "struct b { char c; __attribute__((packed)) int i; };\n",
3214 "_Static_assert(sizeof(struct b) == 5 && _Alignof(struct b) == 1, \"b\");\n",
3215 "_Static_assert(__builtin_offsetof(struct b, i) == 1, \"b.i\");\n",
3216 "typedef struct { char c; int i; } __attribute__((packed)) c;\n",
3217 "_Static_assert(sizeof(c) == 5 && _Alignof(c) == 1, \"c\");\n",
3218 ));
3219 }
3220
3221 #[test]
3227 fn pragma_pack_caps_every_member_and_is_read_where_the_body_closes() {
3228 tast(concat!(
3229 "#pragma pack(1)\n",
3230 "struct A { char c; int i; };\n",
3231 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
3232 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
3233 "#pragma pack()\n",
3234 "struct B { char c; int i; };\n",
3235 "_Static_assert(sizeof(struct B) == 8 && _Alignof(struct B) == 4, \"B\");\n",
3236 "#pragma pack(2)\n",
3237 "struct C { char c; int i; double d; };\n",
3238 "_Static_assert(sizeof(struct C) == 14 && _Alignof(struct C) == 2, \"C\");\n",
3239 "_Static_assert(__builtin_offsetof(struct C, d) == 6, \"C.d\");\n",
3240 "struct K { char c; int i __attribute__((aligned(8))); };\n",
3242 "_Static_assert(sizeof(struct K) == 6 && _Alignof(struct K) == 2, \"K\");\n",
3243 "_Static_assert(__builtin_offsetof(struct K, i) == 2, \"K.i\");\n",
3244 "struct J { char c; int i; } __attribute__((aligned(8)));\n",
3246 "_Static_assert(sizeof(struct J) == 8 && _Alignof(struct J) == 8, \"J\");\n",
3247 "#pragma pack()\n",
3248 "#pragma pack(push, 1)\n",
3249 "struct D { char c; short s; };\n",
3250 "_Static_assert(sizeof(struct D) == 3 && _Alignof(struct D) == 1, \"D\");\n",
3251 "#pragma pack(pop)\n",
3252 "struct E { char c; short s; };\n",
3253 "_Static_assert(sizeof(struct E) == 4 && _Alignof(struct E) == 2, \"E\");\n",
3254 "struct H { char c;\n",
3256 "#pragma pack(1)\n",
3257 " int i; };\n",
3258 "_Static_assert(sizeof(struct H) == 5 && _Alignof(struct H) == 1, \"H\");\n",
3259 "#pragma pack(1)\n",
3260 "struct I { char c;\n",
3261 "#pragma pack()\n",
3262 " int i; };\n",
3263 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
3264 "#pragma pack()\n",
3265 "#pragma pack(push, 8)\n",
3267 "#pragma pack(push, 1)\n",
3268 "struct P { char c; int i; };\n",
3269 "_Static_assert(sizeof(struct P) == 5 && _Alignof(struct P) == 1, \"P\");\n",
3270 "#pragma pack(pop)\n",
3271 "struct Q { char c; int i; };\n",
3272 "_Static_assert(sizeof(struct Q) == 8 && _Alignof(struct Q) == 4, \"Q\");\n",
3273 "#pragma pack(pop)\n",
3274 "#pragma pack(16)\n",
3276 "struct R { char c; int i; };\n",
3277 "_Static_assert(sizeof(struct R) == 8 && _Alignof(struct R) == 4, \"R\");\n",
3278 "#pragma pack()\n",
3279 "#pragma pack(1)\n",
3280 "struct S { char c; int i : 5; int j : 20; };\n",
3281 "_Static_assert(sizeof(struct S) == 5 && _Alignof(struct S) == 1, \"S\");\n",
3282 "union T { char c; int i; };\n",
3283 "_Static_assert(sizeof(union T) == 4 && _Alignof(union T) == 1, \"T\");\n",
3284 "#pragma pack()\n",
3285 ));
3286 }
3287
3288 #[test]
3292 fn a_pack_line_that_is_not_one_is_reported_in_the_words_gcc_uses() {
3293 let result = run(
3294 &options(),
3295 concat!(
3296 "#pragma pack 4\n",
3297 "#pragma pack(pop)\n",
3298 "#pragma pack(3)\n",
3299 "#pragma pack(1) junk\n",
3300 "#pragma pack(push, 1\n",
3301 "#pragma pack(x)\n",
3302 "#pragma pack(0)\n",
3305 "#pragma pack(push)\n",
3306 "struct s { char c; int i; };\n",
3307 "#pragma pack(pop)\n",
3308 "#pragma pack(pop, foo)\n",
3309 ),
3310 );
3311 let expected = [
3312 "missing `(` after `#pragma pack` - ignored",
3313 "`#pragma pack (pop)` encountered without matching `#pragma pack (push)`",
3314 "alignment must be a small power of two, not 3",
3315 "junk at end of `#pragma pack`",
3316 "malformed `#pragma pack(push[, id][, <n>])` - ignored",
3317 "unknown action `x` for `#pragma pack` - ignored",
3318 "`#pragma pack(pop, foo)` encountered without matching `#pragma pack(push, foo)`",
3319 ];
3320 assert_eq!(result.messages.len(), expected.len(), "{:?}", result.messages);
3321 for (message, want) in result.messages.iter().zip(expected) {
3322 assert!(message.contains(want), "expected {want:?} in {message:?}");
3323 }
3324 }
3325
3326 #[test]
3333 fn a_declaration_behind_an_empty_macro_is_not_eaten_by_the_pragma_above_it() {
3334 let result = run(
3335 &options(),
3336 concat!(
3337 "#pragma pack(push, 1)\n",
3338 "#pragma pack(pop)\n",
3339 "#define API\n",
3340 "API const char version[] = \"3.53.4\";\n",
3341 "const char *get(void) { return version; }\n",
3342 ),
3343 );
3344 assert!(result.messages.is_empty(), "{:?}", result.messages);
3345 }
3346
3347 #[test]
3351 fn the_wide_integer_answers_to_all_three_of_its_names() {
3352 let text = tast("__uint128_t a; __int128_t b; unsigned __int128 c;\n");
3353 assert!(text.contains("decl #0 a : unsigned __int128"), "{text}");
3354 assert!(text.contains("decl #1 b : __int128"), "{text}");
3355 assert!(text.contains("decl #2 c : unsigned __int128"), "{text}");
3356 }
3357
3358 #[test]
3359 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
3360 let text = tast("long f(int a, long b) { return a + b; }\n");
3364 assert!(text.contains("convert arithmetic"), "{text}");
3365 }
3366
3367 #[test]
3368 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
3369 for source in [
3370 "#error stop\n",
3371 "int f(void) { return 1 + ; }\n",
3372 "int f(void) { return undeclared; }\n",
3373 ] {
3374 let result = run(&options(), source);
3375 assert!(result.failed(), "expected this to fail:\n{source}");
3376 assert!(
3377 result.text().is_empty(),
3378 "a file that did not compile wrote a tree:\n{source}"
3379 );
3380 }
3381 }
3382
3383 #[test]
3384 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
3385 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
3389 assert_eq!(result.errors, 1, "{:?}", result.messages);
3390 }
3391
3392 #[test]
3393 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
3394 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
3398 assert_eq!(result.errors, 1, "{:?}", result.messages);
3399 }
3400
3401 #[test]
3402 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
3403 let source = "int f(void) { char c = 300; return c; }\n";
3404 let plain = run(&options(), source);
3405 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
3406 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
3407 assert!(!plain.text().is_empty(), "a warning is not a reason to write nothing");
3408
3409 let mut opts = options();
3410 opts.warnings_are_errors = true;
3411 let strict = run(&opts, source);
3412 assert!(strict.failed());
3413 assert!(strict.text().is_empty(), "and under -Werror it is a reason to write nothing");
3414 for message in &strict.messages {
3415 assert!(!message.contains("warning:"), "{message}");
3416 }
3417 }
3418
3419 #[test]
3420 fn w_drops_the_warning_before_werror_can_promote_it() {
3421 let source = "int f(void) { char c = 300; return c; }\n";
3422 let mut opts = options();
3423 opts.warnings = false;
3424 let quiet = run(&opts, source);
3425 assert_eq!(quiet.messages, Vec::<String>::new());
3426 assert_eq!(quiet.errors, 0);
3427 assert!(!quiet.text().is_empty(), "and the file still compiles");
3428
3429 opts.warnings_are_errors = true;
3432 let both = run(&opts, source);
3433 assert_eq!(both.messages, Vec::<String>::new());
3434 assert!(!both.failed(), "-w -Werror is not an error about a warning nobody saw");
3435 }
3436
3437 #[test]
3438 fn the_dialect_reaches_the_keywords_and_the_checking() {
3439 let source = "typeof(1) x;\n";
3442 let mut opts = options();
3443 opts.std = Std::C23;
3444 opts.gnu_extensions = false;
3445 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
3446
3447 opts.std = Std::C17;
3448 assert!(run(&opts, source).failed());
3449 }
3450
3451 #[test]
3452 fn asking_for_a_kind_that_is_not_written_yet_runs_the_front_end_and_writes_nothing() {
3453 let mut opts = options();
3454 opts.emit = EmitKind::Object;
3455 let result = run(&opts, "int x = 1;\n");
3456 assert!(!result.failed(), "{:?}", result.messages);
3457 assert!(result.text().is_empty());
3458 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
3461 }
3462
3463 fn mir(source: &str) -> String {
3465 let mut opts = options();
3466 opts.emit = EmitKind::MirFinal;
3467 let result = run(&opts, source);
3468 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3469 result.text().to_owned()
3470 }
3471
3472 #[test]
3478 fn a_function_goes_from_c_to_instructions_with_real_registers_in_them() {
3479 let text = mir("int add(int a, int b) { return a + b; }\n");
3480 assert!(text.starts_with("mfunc @add {"), "{text}");
3481 assert!(text.contains("x64.add_rr_32"), "{text}");
3482 assert!(text.contains("x64.ret"), "{text}");
3483 assert!(!text.contains('%'), "{text}");
3486 }
3487
3488 #[test]
3490 fn a_function_with_no_body_produces_no_machine_function() {
3491 let text = mir("int g(int);\nint f(int a) { return g(a); }\n");
3492 assert_eq!(text.matches("mfunc @").count(), 1, "{text}");
3493 assert!(text.contains("mfunc @f {"), "{text}");
3494 assert!(text.contains("x64.call"), "{text}");
3495 }
3496
3497 #[test]
3499 fn every_definition_in_the_file_is_generated_and_they_keep_their_order() {
3500 let text = mir("int a(int x) { return x; }\nint b(int x) { return x; }\n");
3501 let first = text.find("mfunc @a").expect("the first function");
3502 let second = text.find("mfunc @b").expect("the second function");
3503 assert!(first < second, "{text}");
3504 }
3505
3506 #[test]
3508 fn the_target_decides_which_convention_the_generated_code_follows() {
3509 let mut opts = options();
3510 opts.emit = EmitKind::MirFinal;
3511 let linux = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
3512 assert!(linux.contains("$rdi"), "{linux}");
3513
3514 opts.target = "x86_64-pc-windows-msvc".parse::<Triple>().unwrap();
3515 let windows = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
3516 assert!(windows.contains("$rcx"), "{windows}");
3517 assert!(!windows.contains("$rdi"), "{windows}");
3518 }
3519
3520 #[test]
3528 fn a_tagged_member_with_no_name_is_a_member_on_windows_and_nothing_on_linux() {
3529 let source = concat!(
3530 "struct S { union U { int i; void *p; }; unsigned long tymed; };\n",
3531 "int size(void) { return sizeof(struct S); }\n",
3532 "int f(struct S *s) { s->i = 1; return s->i; }\n",
3533 );
3534
3535 let mut opts = options();
3536 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3537 let windows = run(&opts, source);
3538 assert!(windows.messages.is_empty(), "{:?}", windows.messages);
3539
3540 let linux = run(&options(), source);
3541 assert_eq!(linux.messages.len(), 3, "{:?}", linux.messages);
3542 assert!(linux.messages[0].contains("does not declare anything"), "{:?}", linux.messages);
3543
3544 let mut opts = options();
3547 opts.ms_extensions = Some(true);
3548 let asked = run(&opts, source);
3549 assert!(asked.messages.is_empty(), "{:?}", asked.messages);
3550 }
3551
3552 #[test]
3554 fn a_target_this_has_no_back_end_for_is_reported_rather_than_generated() {
3555 let mut opts = options();
3556 opts.emit = EmitKind::MirFinal;
3557 opts.target = "riscv64-unknown-linux-gnu".parse::<Triple>().unwrap();
3558 let result = run(&opts, "int f(int a) { return a; }\n");
3559 assert!(result.failed());
3560 assert!(result.messages[0].contains("no back end for riscv64"), "{:?}", result.messages);
3561 assert!(result.text().is_empty());
3562 }
3563
3564 #[test]
3567 fn an_aarch64_target_is_written_as_aarch64_assembly() {
3568 let mut opts = options();
3569 opts.emit = EmitKind::Asm;
3570 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3571 let source = "int g(int);\nint f(int a, int b) { return g(a) + b; }\n";
3572 let result = run(&opts, source);
3573 assert!(!result.failed(), "{:?}", result.messages);
3574 let text = result.text();
3575 for line in ["stp x29, x30, [sp, #-16]!", "mov x29, sp", "bl g", "ldp x29, x30, [sp], #16"]
3576 {
3577 assert!(text.contains(line), "{line} is not in\n{text}");
3578 }
3579 assert!(!text.contains('%'), "{text}");
3580 }
3581
3582 #[test]
3585 fn an_aarch64_target_reaches_an_object_file() {
3586 let mut opts = options();
3587 opts.emit = EmitKind::Object;
3588 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3589 let source = concat!(
3590 "int g(int);\n",
3591 "int table[4] = {1, 2, 3, 4};\n",
3592 "int f(int a, int b) { return g(a) + table[b & 3]; }\n",
3593 );
3594 let result = run(&opts, source);
3595 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3596 let bytes = match result.artifact {
3597 Artifact::Object { bytes, defines } => {
3598 assert_eq!(defines, ["f", "table"]);
3599 bytes
3600 }
3601 other => panic!("expected an object, got {other:?}"),
3602 };
3603 assert_eq!(&bytes[..4], b"\x7fELF");
3604 assert_eq!(&bytes[18..20], &183u16.to_le_bytes(), "EM_AARCH64");
3605
3606 opts.debug_info = true;
3609 let result = run(&opts, source);
3610 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3611 let bytes = match result.artifact {
3612 Artifact::Object { bytes, .. } => bytes,
3613 other => panic!("expected an object, got {other:?}"),
3614 };
3615 let has = |name: &[u8]| bytes.windows(name.len()).any(|at| at == name);
3616 assert!(has(b".debug_line\0") && has(b".debug_info\0"));
3617 assert!(!has(b"rucc_row"), "a row label reached the symbol table");
3618 }
3619
3620 #[test]
3623 fn the_aarch64_vector_type_names_are_declared_on_that_target_and_nowhere_else() {
3624 let mut opts = options();
3625 opts.emit = EmitKind::Asm;
3626 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3627 let source = "typedef __Float32x4_t f4;\n__SVFloat32_t sv(__SVFloat32_t, __SVBool_t);\n\
3628 int n = sizeof(f4) + sizeof(__Int8x8_t);\n\
3629 int f(f4 v) { int __Uint8x16_t = 3; return v[1] + __Uint8x16_t; }\n";
3630 let result = run(&opts, source);
3631 assert!(!result.failed(), "{:?}", result.messages);
3632 assert!(result.text().contains(".long\t24"), "{}", result.text());
3633 opts.target = "x86_64-unknown-linux-gnu".parse::<Triple>().unwrap();
3634 let result = run(&opts, "typedef __Float32x4_t f4;\n");
3635 assert!(result.failed());
3636 let result = run(&opts, "int __Float32x4_t = 1;\n");
3637 assert!(!result.failed(), "{:?}", result.messages);
3638 }
3639
3640 #[test]
3643 fn an_aarch64_result_in_memory_is_reached_through_x8() {
3644 let mut opts = options();
3645 opts.emit = EmitKind::Asm;
3646 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3647 let source = "struct big { long a, b, c; };\nstruct big make(long v);\n\
3648 long f(long v) { return make(v).c; }\n\
3649 struct big g(long v) { struct big b = { v, v, v }; return b; }\n";
3650 let result = run(&opts, source);
3651 assert!(!result.failed(), "{:?}", result.messages);
3652 let text = result.text();
3653 assert!(text.contains("x8"), "{text}");
3654 assert!(text.contains("bl make"), "{text}");
3655 }
3656
3657 #[test]
3660 fn an_aarch64_remainder_is_a_division_and_a_multiply_subtract() {
3661 let mut opts = options();
3662 opts.emit = EmitKind::Asm;
3663 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3664 let source = "int s(int a, int b) { return a % b; }\n\
3665 unsigned long u(unsigned long a, unsigned long b) { return a % b; }\n";
3666 let result = run(&opts, source);
3667 assert!(!result.failed(), "{:?}", result.messages);
3668 let text = result.text();
3669 let at = |what: &str| text.find(what).unwrap_or_else(|| panic!("{what} is not in\n{text}"));
3670 assert!(at("sdiv w") < at("msub w"), "{text}");
3671 assert!(at("udiv x") < at("msub x"), "{text}");
3672 }
3673
3674 #[test]
3677 fn an_aarch64_jump_table_is_reached_with_adr() {
3678 let mut opts = options();
3679 opts.emit = EmitKind::Asm;
3680 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3681 let source = "int f(int x) { switch (x) { case 0: return 10; case 1: return 21; \
3682 case 2: return 32; case 3: return 43; case 4: return 54; case 5: return 65; \
3683 case 6: return 76; case 7: return 87; case 8: return 98; case 9: return 9; \
3684 case 10: return 19; case 11: return 29; default: return 0; } }\n";
3685 let result = run(&opts, source);
3686 assert!(!result.failed(), "{:?}", result.messages);
3687 let text = result.text();
3688 let at = |what: &str| text.find(what).unwrap_or_else(|| panic!("{what} is not in\n{text}"));
3689 assert!(at("adr x") < at("ldrsw x"), "{text}");
3690 assert!(at("ldrsw x") < at("br x"), "{text}");
3691 assert!(text.contains("_j0:"), "{text}");
3692 assert!(text.contains(".long"), "{text}");
3693 }
3694
3695 #[test]
3699 fn an_aarch64_va_start_writes_the_five_fields_of_its_list() {
3700 let mut opts = options();
3701 opts.emit = EmitKind::Asm;
3702 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3703 let source = "typedef __builtin_va_list va_list;\n\
3704 int f(int n, ...) { va_list ap; __builtin_va_start(ap, n); \
3705 int x = __builtin_va_arg(ap, int); double d = __builtin_va_arg(ap, double); \
3706 __builtin_va_end(ap); return x + (int)d; }\n";
3707 let result = run(&opts, source);
3708 assert!(!result.failed(), "{:?}", result.messages);
3709 let text = result.text();
3710 assert!(text.contains("#-56"), "{text}");
3711 assert!(text.contains("#-128"), "{text}");
3712 assert!(text.contains("#24]"), "{text}");
3713 assert!(text.contains("#28]"), "{text}");
3714 assert!(text.contains("str q"), "{text}");
3715 }
3716
3717 #[test]
3720 fn an_aarch64_long_double_is_a_quad_in_a_vector_register() {
3721 let mut opts = options();
3722 opts.emit = EmitKind::Asm;
3723 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3724 let source = "void f(long double *p, long double x) { *p = *p + x; }\n";
3725 let result = run(&opts, source);
3726 assert!(!result.failed(), "{:?}", result.messages);
3727 let text = result.text();
3728 assert!(text.contains("ldr q"), "{text}");
3729 assert!(text.contains("str q"), "{text}");
3730 assert!(text.contains("__addtf3"), "{text}");
3731 }
3732
3733 #[test]
3736 fn an_aarch64_thread_local_is_reached_through_tpidr_el0() {
3737 let mut opts = options();
3738 opts.emit = EmitKind::Asm;
3739 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3740 let source = "__thread int n;\nint *f(void) { return &n; }\n\
3741 void *g(void) { return __builtin_thread_pointer(); }\n";
3742 let result = run(&opts, source);
3743 assert!(!result.failed(), "{:?}", result.messages);
3744 let text = result.text();
3745 assert!(text.contains(":gottprel:n"), "{text}");
3746 assert!(text.contains(":gottprel_lo12:n]"), "{text}");
3747 assert_eq!(text.matches("mrs x").count(), 2, "{text}");
3748 assert!(text.contains("tpidr_el0"), "{text}");
3749 }
3750
3751 #[test]
3754 fn a_darwin_thread_local_is_reached_through_its_descriptor() {
3755 let source = "__thread int n = 5;\nint *f(void) { return &n; }\n";
3756 for (triple, wanted) in [
3757 ("aarch64-apple-darwin", &["_n@TLVPPAGE\n", "_n@TLVPPAGEOFF]\n", "\tblr x"][..]),
3758 ("x86_64-apple-darwin", &["_n@TLVP(%rip), %rdi\n", "\tcall\t*%"][..]),
3759 ] {
3760 let mut opts = options();
3761 opts.emit = EmitKind::Asm;
3762 opts.target = triple.parse::<Triple>().unwrap();
3763 let result = run(&opts, source);
3764 assert!(!result.failed(), "{triple}: {:?}", result.messages);
3765 let text = result.text();
3766 for want in wanted {
3767 assert!(text.contains(want), "{triple} wanted {want:?}:\n{text}");
3768 }
3769 assert!(text.contains("\n_n:\n\t.quad\t__tlv_bootstrap\n"), "{text}");
3770 assert!(!text.contains("tpidr_el0") && !text.contains("%fs"), "{text}");
3771 }
3772 }
3773
3774 #[test]
3776 fn the_thread_pointer_is_refused_on_darwin() {
3777 let mut opts = options();
3778 opts.emit = EmitKind::Asm;
3779 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3780 let result = run(&opts, "void *f(void) { return __builtin_thread_pointer(); }\n");
3781 assert!(result.failed());
3782 assert!(result.messages[0].contains("thread pointer"), "{:?}", result.messages);
3783 }
3784
3785 #[test]
3788 fn a_darwin_variadic_definition_saves_nothing_and_walks_the_stack() {
3789 let mut opts = options();
3790 opts.emit = EmitKind::Asm;
3791 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3792 let source = "int f(int n, ...) { __builtin_va_list ap; __builtin_va_start(ap, n);\n\
3793 int r = __builtin_va_arg(ap, int); __builtin_va_end(ap); return r; }\n";
3794 let result = run(&opts, source);
3795 assert!(!result.failed(), "{:?}", result.messages);
3796 let text = result.text();
3797 assert!(!text.contains("str q"), "{text}");
3798 assert!(!text.contains("x7"), "{text}");
3799 }
3800
3801 #[test]
3804 fn a_darwin_call_puts_its_variadic_arguments_in_memory() {
3805 let mut opts = options();
3806 opts.emit = EmitKind::Asm;
3807 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3808 let source = "int printf(const char *, ...);\n\
3809 int g(double x) { return printf(\"%d %f\", 7, x); }\n";
3810 let result = run(&opts, source);
3811 assert!(!result.failed(), "{:?}", result.messages);
3812 let text = result.text();
3813 assert!(text.contains("str d0, [sp, #8]"), "{text}");
3814 }
3815
3816 #[test]
3820 fn a_darwin_listing_is_one_apples_assembler_reads() {
3821 let mut opts = options();
3822 opts.emit = EmitKind::Asm;
3823 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3824 let source = "extern int ext;\n\
3825 int g[4];\n\
3826 int f(int i) { return g[i] + ext; }\n";
3827 let result = run(&opts, source);
3828 assert!(!result.failed(), "{:?}", result.messages);
3829 let text = result.text();
3830 assert!(text.contains(", _g@PAGE\n"), "{text}");
3831 assert!(text.contains(", _g@PAGEOFF\n"), "{text}");
3832 assert!(text.contains(", _ext@GOTPAGE\n"), "{text}");
3833 assert!(text.contains(", _ext@GOTPAGEOFF]\n"), "{text}");
3834 assert!(!text.contains(":lo12:"), "{text}");
3835 assert!(text.contains("\t.globl\t_g\n\t.zerofill\t__DATA,__bss,_g,16,2\n"), "{text}");
3836 }
3837
3838 #[test]
3844 fn a_signed_char_on_aarch64_is_widened_with_its_sign_before_it_is_added_to() {
3845 for target in ["aarch64-linux-gnu", "aarch64-apple-darwin"] {
3846 let mut opts = options();
3847 opts.emit = EmitKind::Asm;
3848 opts.target = target.parse::<Triple>().unwrap();
3849 let source = "int f(signed char *p) { return *p + 1; }\n\
3850 unsigned g(unsigned short *p) { return *p + 1u; }\n";
3851 let result = run(&opts, source);
3852 assert!(!result.failed(), "{:?}", result.messages);
3853 let text = result.text();
3854 let signed = text.contains("\tsxtb w") || text.contains("\tldrsb w");
3855 assert!(signed, "{target}: {text}");
3856 }
3857 }
3858
3859 #[test]
3871 fn a_construct_the_back_end_cannot_reach_yet_is_reported_against_its_function() {
3872 let mut opts = options();
3873 opts.emit = EmitKind::MirFinal;
3874 let source = "void a(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
3875 s; s.x = 1; v[0] = s.x; }\n\
3876 void b(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
3877 s; s.x = 1; v[0] = s.x; }\n";
3878 let result = run(&opts, source);
3879 assert!(result.failed());
3880 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
3881 assert!(result.messages[0].contains("cannot generate code for 'a'"), "{:?}", result);
3882 assert!(result.messages[0].contains("wants more alignment"), "{:?}", result);
3883 assert!(result.messages[1].contains("cannot generate code for 'b'"), "{:?}", result);
3884 assert!(result.text().is_empty());
3885 }
3886
3887 #[test]
3895 fn a_variable_length_array_walks_its_pages_where_every_page_of_the_frame_is_to_be_touched() {
3896 let mut opts = options();
3897 opts.emit = EmitKind::MirFinal;
3898 let source = "void a(int n) { int v[n]; v[0] = 1; }\n";
3899 let plain = run(&opts, source);
3900 assert!(!plain.failed(), "{:?}", plain.messages);
3901 assert!(!plain.text().contains("cmp_set_a_64"), "{}", plain.text());
3902
3903 opts.stack_clash = true;
3904 let result = run(&opts, source);
3905 assert!(!result.failed(), "{:?}", result.messages);
3906 assert!(result.text().contains("cmp_set_a_64"), "{}", result.text());
3907 assert!(result.text().contains("or_mi_8"), "{}", result.text());
3908 }
3909
3910 #[test]
3920 fn a_function_that_keeps_a_frame_pointer_on_windows_reaches_an_object_file() {
3921 let mut opts = options();
3922 opts.emit = EmitKind::Object;
3923 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3924 let source = concat!(
3925 "void use(void *p);\n",
3926 "void array(int n) { int v[n]; v[0] = 1; use(v); }\n",
3927 "void taken(unsigned long n) { use(__builtin_alloca(n)); }\n",
3928 );
3929 let result = run(&opts, source);
3930 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3931 let bytes = match result.artifact {
3932 Artifact::Object { bytes, .. } => bytes,
3933 other => panic!("expected an object, got {other:?}"),
3934 };
3935 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3936
3937 let mut opts = options();
3940 opts.emit = EmitKind::Object;
3941 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3942 }
3943
3944 #[test]
3955 fn the_address_of_a_function_this_file_only_declares_reaches_a_windows_object() {
3956 let source = concat!(
3957 "void other(void *p);\n",
3958 "void takes(void (*f)(void *));\n",
3959 "void (*held)(void *);\n",
3960 "void pass(void) { takes(other); }\n",
3961 "void keep(void) { held = other; }\n",
3962 "void call(void) { other(0); }\n",
3963 );
3964 let mut opts = options();
3965 opts.emit = EmitKind::Object;
3966 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3967 let result = run(&opts, source);
3968 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3969 let bytes = match result.artifact {
3970 Artifact::Object { bytes, .. } => bytes,
3971 other => panic!("expected an object, got {other:?}"),
3972 };
3973 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3974
3975 let mut opts = options();
3978 opts.emit = EmitKind::Object;
3979 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3980 }
3981
3982 #[test]
3996 fn an_opcode_with_no_name_in_the_rule_language_is_named_by_its_own_spelling() {
3997 let mut opts = options();
3998 opts.emit = EmitKind::MirFinal;
3999 let source =
4000 "long double f(int a) {\n __int128 wide = a;\n return (long double) wide;\n}\n";
4001 let result = run(&opts, source);
4002 assert!(result.failed());
4003 assert!(
4004 result.messages[0].contains("no rule lowers a `sext` producing a `i128`"),
4005 "{result:?}"
4006 );
4007 assert!(result.messages[0].contains(":2:"), "the line the widening is on: {result:?}");
4008 assert!(!result.messages[0].contains("this instruction"), "{result:?}");
4009 }
4010
4011 #[test]
4013 fn the_note_on_unfinished_work_points_at_the_issues_rather_than_at_the_plan() {
4014 let mut opts = options();
4015 opts.emit = EmitKind::MirFinal;
4016 let source = "long double f(int a) { __int128 wide = a; return (long double) wide; }\n";
4017 let result = run(&opts, source);
4018 assert!(result.failed());
4019 let note = result.messages.iter().find(|line| line.contains("note:")).expect("a note");
4020 assert!(note.contains("https://github.com/tamnd/rucc/issues"), "{note}");
4021 assert!(!note.contains("spec/17-milestones.md"), "{note}");
4022 }
4023
4024 #[test]
4026 fn the_frame_flags_on_the_command_line_reach_the_generated_frame() {
4027 let source = "int f(int a) { return a; }\n";
4028 assert!(!mir(source).contains("$rbp"), "a leaf needs no frame pointer when told so");
4029
4030 let mut opts = options();
4031 opts.emit = EmitKind::MirFinal;
4032 opts.frame_pointer = Some(true);
4033 let kept = run(&opts, source).text().to_owned();
4034 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
4035
4036 opts.frame_pointer = None;
4038 let kept = run(&opts, source).text().to_owned();
4039 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
4040 }
4041
4042 fn asm(source: &str) -> String {
4044 let mut opts = options();
4045 opts.emit = EmitKind::Asm;
4046 let result = run(&opts, source);
4047 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4048 result.text().to_owned()
4049 }
4050
4051 #[test]
4058 fn a_function_goes_from_c_to_assembly_an_assembler_would_take() {
4059 let text = asm("int add(int a, int b) { return a + b; }\n");
4060 assert!(text.contains("\t.globl\tadd\n"), "{text}");
4061 assert!(text.contains("\t.type\tadd, @function\n"), "{text}");
4062 assert!(text.contains("\nadd:\n"), "{text}");
4063 assert!(text.contains("\taddl\t"), "{text}");
4064 assert!(text.contains("\tret\n"), "{text}");
4065 assert!(text.contains("\t.size\tadd, .-add\n"), "{text}");
4066 assert!(text.contains(".note.GNU-stack"), "{text}");
4069 }
4070
4071 #[test]
4077 fn a_call_through_a_function_pointer_goes_through_the_register_it_is_in() {
4078 let text = asm("int g(int);\nint f(int (*p)(int), int a) { return p(a) + g(a); }\n");
4079 assert!(text.contains("\tcall\t*%"), "{text}");
4080 assert!(text.contains("\tcall\tg\n"), "{text}");
4081 assert!(text.contains("%rdi"), "{text}");
4085 }
4086
4087 #[test]
4091 fn the_address_of_a_global_is_read_from_the_instruction_pointer() {
4092 let text = asm("extern int counter;\nint f(void) { return counter; }\n");
4093 assert!(text.contains("\tmovl\tcounter(%rip), %eax\n"), "{text}");
4094 }
4095
4096 #[test]
4105 fn a_branch_on_a_comparison_jumps_on_the_opposite_of_what_it_compared() {
4106 let arms = "return 1; return 2;";
4107 let signed = [("==", "jne"), ("!=", "je"), ("<", "jge"), ("<=", "jg"), (">", "jle")];
4108 for (operator, jump) in signed.into_iter().chain([(">=", "jl")]) {
4109 let text = asm(&format!("int f(int a, int b) {{ if (a {operator} b) {arms} }}\n"));
4110 assert!(
4111 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
4112 "{operator}: {text}"
4113 );
4114 assert!(!text.contains("\tset"), "{operator}: {text}");
4115 assert!(!text.contains("\ttest"), "{operator}: {text}");
4116 }
4117 let unsigned = [("<", "jae"), ("<=", "ja"), (">", "jbe"), (">=", "jb")];
4118 for (operator, jump) in unsigned {
4119 let source =
4120 format!("int f(unsigned a, unsigned b) {{ if (a {operator} b) {arms} }}\n");
4121 let text = asm(&source);
4122 assert!(
4123 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
4124 "{operator}: {text}"
4125 );
4126 }
4127
4128 let text = asm("int f(int a) { if (a < 7) return 1; return 2; }\n");
4131 assert!(text.contains("\tcmpl\t$7, %edi\n\tjge\t"), "{text}");
4132 }
4133
4134 #[test]
4140 fn a_comparison_whose_answer_the_program_wanted_still_writes_a_byte() {
4141 let text = asm("int f(int a, int b) { return a < b; }\n");
4142 assert!(text.contains("\tsetl\t"), "{text}");
4143 }
4144
4145 fn optimized(source: &str) -> String {
4147 let mut opts = options();
4148 opts.emit = EmitKind::Asm;
4149 opts.opt_level = rucc_session::OptLevel::O2;
4150 let result = run(&opts, source);
4151 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4152 result.text().to_owned()
4153 }
4154
4155 #[test]
4157 fn opt_info_says_what_each_switch_became_and_a_forced_shape_is_what_it_says() {
4158 let arms: String = (0..40)
4159 .map(|k| format!("case {}: return g({k});", k * 17))
4160 .collect::<Vec<_>>()
4161 .join(" ");
4162 let source = format!("int g(int);\nint f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n");
4163 let said = |shape: Option<&str>| {
4164 let mut opts = options();
4165 opts.emit = EmitKind::Asm;
4166 opts.opt_level = rucc_session::OptLevel::O2;
4167 opts.opt_info = vec![String::new()];
4168 opts.switch_shape = shape.map(str::to_owned);
4169 let result = run(&opts, &source);
4170 assert_eq!(result.messages, Vec::<String>::new());
4171 let lines: Vec<String> = result
4172 .remarks
4173 .lines()
4174 .filter(|line| line.contains("[switch-lowering]"))
4175 .map(str::to_owned)
4176 .collect();
4177 assert_eq!(lines.len(), 1, "{}", result.remarks);
4178 lines[0].clone()
4179 };
4180 assert!(said(None).contains(": f: optimized: switch of 40 cases lowered as a tree;"));
4181 assert!(said(Some("table")).contains("lowered as a table;"));
4182 assert!(said(Some("walk")).contains("lowered as a walk;"));
4183 }
4184
4185 #[test]
4195 fn a_switch_whose_arms_are_a_function_of_the_label_is_a_range_check_and_arithmetic() {
4196 let arms: String =
4197 (0..16).map(|k| format!("case {k}: return {};", k + 1)).collect::<Vec<_>>().join(" ");
4198 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
4199 assert!(text.contains("\tcmpl\t$15, %edi\n\tja\t"), "{text}");
4200 assert!(text.contains("\taddl\t$1, %edi"), "{text}");
4201 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
4202 }
4203
4204 #[test]
4212 fn a_dense_switch_whose_arms_are_not_a_line_is_a_load_from_a_table() {
4213 let arms: String = (0..16)
4214 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
4215 .collect::<Vec<_>>()
4216 .join(" ");
4217 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
4218 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
4219 assert!(!text.contains("\tjmp\t*"), "{text}");
4220 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
4221 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
4222 let section = text[..text.find("CSWTCH.0:").unwrap_or(0)].rfind("\t.section\t.rodata");
4223 assert!(section.is_some(), "{text}");
4224 assert_eq!(table.matches("\t.long\t").count(), 16, "{text}");
4225 assert!(table.contains("\t.long\t100\n"), "{text}");
4226 }
4227
4228 #[test]
4234 fn a_switch_whose_arms_give_strings_is_a_table_of_how_far_away_they_are() {
4235 let text = optimized(
4236 "const char *f(int k) { switch (k) { case 0: return \"zero\"; \
4237 case 1: return \"one\"; case 2: return \"two\"; case 3: return \"three\"; } \
4238 return \"many\"; }\n",
4239 );
4240 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
4241 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
4242 assert!(!text.contains(".data.rel.ro"), "{text}");
4243 let at = text.find("CSWTCH.0:").expect("the table is in the output");
4244 assert!(text[..at].rfind("\t.section\t.rodata").is_some(), "{text}");
4245 let table = &text[at..];
4246 assert_eq!(table.matches(" - .\n").count(), 4, "{text}");
4247 assert!(table.contains("\t.long\t.Lstr.1+4 - .\n"), "{text}");
4248 }
4249
4250 #[test]
4256 fn a_table_at_os_has_cells_as_narrow_as_its_answers() {
4257 let arms: String = (0..16)
4258 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
4259 .collect::<Vec<_>>()
4260 .join(" ");
4261 let mut opts = options();
4262 opts.emit = EmitKind::Asm;
4263 opts.opt_level = rucc_session::OptLevel::Os;
4264 let result = run(&opts, &format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
4265 assert_eq!(result.messages, Vec::<String>::new());
4266 let text = result.text();
4267 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
4268 assert_eq!(table.matches("\t.byte\t").count(), 16, "{text}");
4269 assert!(text.contains("\tmovsbl\t"), "{text}");
4270 }
4271
4272 #[test]
4279 fn a_table_that_covers_its_operand_has_no_range_check() {
4280 let text = optimized(
4281 "int f(unsigned x) { switch (x & 3) { case 0: return 5; case 1: return 9; \
4282 case 2: return 2; case 3: return 7; } return -1; }\n",
4283 );
4284 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
4285 assert!(!text.contains("\tcmp"), "{text}");
4286 assert!(!text.contains("$-1"), "{text}");
4287 }
4288
4289 #[test]
4294 fn a_store_to_a_local_the_loop_just_read_is_a_conditional_move() {
4295 let text = optimized(
4296 "int f(const int *v, int n, int k) { int best[8] = {0}; \
4297 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; \
4298 return best[k & 7]; }\n",
4299 );
4300 assert!(text.contains("\tcmovgl"), "{text}");
4301 assert!(!text.contains("\tset"), "{text}");
4302 assert!(!text.contains("\ttestb"), "{text}");
4303 }
4304
4305 #[test]
4307 fn a_store_to_a_global_the_loop_just_read_keeps_its_branch() {
4308 let text = optimized(
4309 "int best[8]; void f(const int *v, int n) { \
4310 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; }\n",
4311 );
4312 assert!(!text.contains("\tcmov"), "{text}");
4313 }
4314
4315 #[test]
4323 fn a_conversion_from_a_constant_double_is_the_number_it_converts_to() {
4324 let text = optimized("int f(void) { double d = 2.75; return (int) d; }\n");
4325 assert!(text.contains("movl\t$2, %eax"), "{text}");
4326 assert!(!text.contains("cvttsd2si"), "{text}");
4327 }
4328
4329 #[test]
4337 fn a_slot_of_a_read_only_table_is_the_value_the_table_holds() {
4338 let text =
4339 optimized("static const int t[4] = {10, 20, 30, 40};\nint f(void) { return t[2]; }\n");
4340 assert!(text.contains("movl\t$30, %eax"), "{text}");
4341 assert!(!text.contains("t(%rip)"), "{text}");
4342 }
4343
4344 #[test]
4347 fn a_byte_of_a_read_only_string_is_the_byte_the_string_spells() {
4348 let text = optimized("static const char s[] = \"abc\";\nint f(void) { return s[1]; }\n");
4349 assert!(text.contains("movl\t$98, %eax"), "{text}");
4350 }
4351
4352 #[test]
4356 fn a_table_that_is_not_read_only_keeps_its_load() {
4357 let text = optimized(
4358 "static int t[4] = {10, 20, 30, 40};\nvoid g(int x) { t[2] = x; }\nint f(void) { return t[2]; }\n",
4359 );
4360 assert!(!text.contains("movl\t$30, %eax"), "{text}");
4361 }
4362
4363 #[test]
4370 fn a_call_guarded_by_a_condition_a_read_only_object_settles_is_not_emitted() {
4371 let text = optimized(
4372 "void link_error(void);\nconst double one = 1.0;\nint main(void) { if ((int) one != 1) link_error(); return 0; }\n",
4373 );
4374 assert!(!text.contains("call\tlink_error"), "{text}");
4375 }
4376
4377 #[test]
4379 fn a_cast_between_a_pointer_and_an_integer_leaves_the_value_where_it_is() {
4380 let text = asm("long f(void *p) { return (long)p; }\n");
4381 for line in text.lines().filter(|line| line.starts_with('\t') && !line.contains('.')) {
4386 let mnemonic = line.split_whitespace().next().unwrap_or("");
4387 assert!(matches!(mnemonic, "movq" | "ret"), "{line} in\n{text}");
4388 }
4389 }
4390
4391 #[test]
4395 fn an_argument_past_the_last_register_is_read_out_of_the_caller_s_stack() {
4396 let six = "long a, long b, long c, long d, long e, long f";
4397 let text = asm(&format!("long f({six}, long g, long h) {{ return g + h; }}\n"));
4398
4399 assert!(text.contains("\tmovq\t8(%rsp), "), "{text}");
4406 assert!(text.contains("\taddq\t16(%rsp), "), "{text}");
4407
4408 let narrow = asm(&format!("int f({six}, int g) {{ return g; }}\n"));
4412 assert!(narrow.contains("\tmovl\t8(%rsp), "), "{narrow}");
4413 let eight =
4414 "double a, double b, double c, double d, double e, double f, double g, double h";
4415 let float = asm(&format!("double f({eight}, double i) {{ return i; }}\n"));
4416 assert!(float.contains("\tmovsd\t8(%rsp), "), "{float}");
4417 }
4418
4419 #[test]
4422 fn a_call_writes_the_arguments_with_no_register_left_at_the_stack_pointer() {
4423 let six = "1, 2, 3, 4, 5, 6";
4424 let decl = "long g(long, long, long, long, long, long, long, long);\n";
4425 let text = asm(&format!("{decl}long f(void) {{ return g({six}, 7, 8); }}\n"));
4426
4427 assert!(text.contains("\tmovq\t%"), "{text}");
4428 assert!(text.contains(", (%rsp)\n"), "{text}");
4429 assert!(text.contains(", 8(%rsp)\n"), "{text}");
4430 assert!(text.contains("\tsubq\t$"), "{text}");
4432
4433 let narrow = "int g(int, int, int, int, int, int, int);\n";
4435 let text = asm(&format!("{narrow}int f(void) {{ return g({six}, 7); }}\n"));
4436 assert!(text.contains("\tmovl\t%"), "{text}");
4437 assert!(text.contains(", (%rsp)\n"), "{text}");
4438 }
4439
4440 #[test]
4443 fn a_variadic_call_counts_registers_and_not_arguments() {
4444 let nine = "1., 2., 3., 4., 5., 6., 7., 8., 9.";
4445 let decl = "int g(int, ...);\n";
4446 let text = asm(&format!("{decl}int f(void) {{ return g(0, {nine}); }}\n"));
4447
4448 assert!(text.contains("\tmovl\t$8, "), "eight registers, not nine: {text}");
4449 assert!(text.contains("\tmovsd\t%"), "{text}");
4450 assert!(text.contains(", (%rsp)\n"), "{text}");
4451 }
4452
4453 #[test]
4458 fn a_variadic_function_writes_the_argument_registers_it_was_handed_into_its_frame() {
4459 let body =
4460 "__builtin_va_list ap; __builtin_va_start(ap, n); __builtin_va_end(ap); return n;";
4461 let text = asm(&format!("int f(int n, ...) {{ {body} }}\n"));
4462
4463 let stores = |mnemonic: &str| text.matches(&format!("\t{mnemonic}\t%")).count();
4466 assert!(text.contains(", 8(%r"), "the second slot, not the first: {text}");
4467 assert!(!text.contains(", 0(%r"), "{text}");
4468 assert_eq!(stores("movaps"), 8, "every vector register: {text}");
4471 assert_eq!(stores("movsd"), 0, "and the whole of each one: {text}");
4472
4473 assert!(text.contains("\tsubq\t$"), "{text}");
4475 }
4476
4477 #[test]
4480 fn va_start_writes_the_four_fields_the_psabi_describes() {
4481 let start = "__builtin_va_list ap; __builtin_va_start(ap, d);";
4482 let params = "int a, int b, int c, double d";
4483 let text = asm(&format!("int f({params}, ...) {{ {start} return a; }}\n"));
4484
4485 assert!(text.contains(" movl $24, "), "{text}");
4489 assert!(text.contains(" movl $64, "), "{text}");
4490 assert!(text.contains(", 8(%r"), "{text}");
4494 assert!(text.contains(", 16(%r"), "{text}");
4495 let frame: u32 = text
4496 .lines()
4497 .find_map(|line| line.trim().strip_prefix("subq $")?.split(',').next()?.parse().ok())
4498 .expect("a variadic function takes a frame for the save area");
4499 let above = |line: &str| {
4500 let at: u32 = line.trim().strip_prefix("leaq ")?.split('(').next()?.parse().ok()?;
4501 Some(at > frame)
4502 };
4503 assert!(text.lines().filter_map(above).any(|it| it), "{frame}: {text}");
4504 }
4505
4506 #[test]
4509 fn va_arg_branches_on_whether_the_argument_is_still_in_the_save_area() {
4510 let read = "__builtin_va_list ap; __builtin_va_start(ap, n);";
4511 let ints = format!("int f(int n, ...) {{ {read} return __builtin_va_arg(ap, int); }}\n");
4512 let text = asm(&ints);
4513
4514 assert!(text.contains("$40, "), "{text}");
4517 assert!(text.contains(" cmpl "), "{text}");
4518 assert!(text.contains(" ja "), "{text}");
4522
4523 let arg = "__builtin_va_arg(ap, double)";
4524 let text = asm(&format!("double f(int n, ...) {{ {read} return {arg}; }}\n"));
4525 assert!(text.contains("$160, "), "the last vector slot: {text}");
4526 }
4527
4528 #[test]
4531 fn a_structure_assignment_is_a_move_for_each_word_of_it() {
4532 let decl = "struct pair { long a, b; };\n";
4533 let body = "struct pair p = *q; return p.a + p.b;";
4534 let text = asm(&format!("{decl}long f(struct pair *q) {{ {body} }}\n"));
4535
4536 assert!(!text.contains("memcpy"), "nothing calls the library: {text}");
4537 assert!(!text.contains("\tcall"), "{text}");
4538 assert!(text.matches("\tmovq\t").count() >= 4, "two words each way: {text}");
4540 }
4541
4542 #[test]
4545 fn how_wide_a_word_of_a_copy_is_follows_the_alignment() {
4546 let decl = "struct bytes { char a[8]; };\n";
4547 let body = "struct bytes p = *q; return p.a[0];";
4548 let text = asm(&format!("{decl}int f(struct bytes *q) {{ {body} }}\n"));
4549
4550 assert!(text.matches("\tmovb\t").count() >= 16, "a byte at a time: {text}");
4552 }
4553
4554 #[test]
4557 fn the_part_of_an_initialiser_that_names_nothing_is_stored_as_zero() {
4558 let decl = "struct wide { long a, b, c; };\n";
4559 let text = asm(&format!("{decl}long f(void) {{ struct wide w = {{ 7 }}; return w.c; }}\n"));
4560
4561 assert!(!text.contains("memset"), "nothing calls the library: {text}");
4562 assert!(text.contains("\tmovq\t$0, ") || text.contains("\txorl\t"), "the zero: {text}");
4567 }
4568
4569 #[test]
4572 fn a_copy_too_large_to_unroll_calls_the_runtime() {
4573 let decl = "struct huge { char a[4096]; };\n";
4574 let mut opts = options();
4575 opts.emit = EmitKind::Asm;
4576 let source = format!("{decl}void f(struct huge *p, struct huge *q) {{ *p = *q; }}\n");
4577 let result = run(&opts, &source);
4578 assert!(!result.failed(), "{:?}", result.messages);
4579 let text = result.text();
4580 assert!(text.contains("call") && text.contains("memcpy"), "{text}");
4581 assert!(text.contains("4096"), "the size travels: {text}");
4584 }
4585
4586 #[test]
4593 fn a_structure_too_large_to_unroll_is_copied_into_the_argument_area_by_the_runtime() {
4594 let decl = "struct huge { char a[4096]; };\nint take(struct huge);\n";
4595 let text = asm(&format!("{decl}int f(struct huge *p) {{ return take(*p); }}\n"));
4596
4597 let copy = text.find("call\tmemcpy").expect("the copy");
4598 let call = text.find("call\ttake").expect("the call");
4599 assert!(copy < call, "the copy comes first: {text}");
4600 assert!(text.contains("movq\t%rsp, %rdi"), "the destination: {text}");
4605 assert!(text.contains("$4096, %edx"), "the size: {text}");
4606 }
4607
4608 #[test]
4611 fn a_realigned_frame_reads_them_through_the_frame_pointer() {
4612 let six = "long a, long b, long c, long d, long e, long f";
4613 let body = "_Alignas(32) long wide[4]; wide[0] = g; return wide[0];";
4614 let text = asm(&format!("long f({six}, long g) {{ {body} }}\n"));
4615
4616 assert!(text.contains("\tandq\t$-32, %rsp"), "{text}");
4620 assert!(text.contains("\tmovq\t16(%rbp), "), "{text}");
4621 assert!(!text.contains("\tmovq\t16(%rsp), "), "{text}");
4622 }
4623
4624 #[test]
4626 fn the_target_decides_how_the_assembly_is_spelled() {
4627 let mut opts = options();
4628 opts.emit = EmitKind::Asm;
4629 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
4630 let text = run(&opts, "int f(void) { return 0; }\n").text().to_owned();
4631 assert!(text.contains("__TEXT,__text"), "{text}");
4632 assert!(text.contains("\n_f:\n"), "{text}");
4633 assert!(!text.contains(".note.GNU-stack"), "{text}");
4634 }
4635
4636 fn obj(source: &str) -> Vec<u8> {
4638 let mut opts = options();
4639 opts.emit = EmitKind::Object;
4640 let result = run(&opts, source);
4641 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4642 match result.artifact {
4643 Artifact::Object { bytes, .. } => bytes,
4644 other => panic!("expected an object, got {other:?}"),
4645 }
4646 }
4647
4648 #[test]
4654 fn a_function_goes_from_c_to_an_object_a_linker_would_take() {
4655 let bytes = obj("int add(int a, int b) { return a + b; }\n");
4656 assert_eq!(&bytes[..4], b"\x7fELF", "an object file starts by saying it is one");
4657 let text = asm("int add(int a, int b) { return a + b; }\n");
4658 assert!(
4659 text.contains("\taddl\t"),
4660 "and the listing of it is the same instructions:\n{text}"
4661 );
4662 }
4663
4664 #[test]
4666 fn a_variable_goes_from_c_to_the_section_it_belongs_in() {
4667 let text = asm("int counter = 42;\nstatic int hidden;\nconst int fixed = 7;\n");
4668 assert!(text.contains("\t.data\n\t.globl\tcounter\n"), "{text}");
4669 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
4670 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
4671 assert!(text.contains("\t.bss\n\t.p2align\t2\n"), "{text}");
4674 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
4675 assert!(!text.contains(".globl\thidden"), "{text}");
4676 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4679 }
4680
4681 #[test]
4688 fn a_bit_field_initializer_writes_every_byte_of_the_value_and_not_only_the_ones_that_are_set() {
4689 let text = asm("struct s { unsigned f : 20; } x = { 0x12300 };\n");
4690 assert!(text.contains("\t.data\n"), "there is something to write: {text}");
4691 assert!(text.contains("\nx:\n\t.ascii\t\"\\000#\\001\"\n"), "and it is the value: {text}");
4692
4693 let text = asm("struct s { unsigned a : 8; unsigned b : 8; } x = { 0, 3 };\n");
4696 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\003\"\n"), "{text}");
4697
4698 let text = asm("struct s { unsigned long long f : 40; } x = { 0x100000 };\n");
4701 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\000\\020\"\n\t.space\t5\n"), "{text}");
4702
4703 let text = asm("struct s { unsigned f : 20; } x = { 0 };\n");
4705 assert!(text.contains("\t.bss\n"), "an object of zeroes is zeroes: {text}");
4706 assert!(text.contains("\nx:\n\t.space\t4\n"), "{text}");
4707 }
4708
4709 #[test]
4711 fn a_string_literal_is_a_variable_with_a_name_no_program_could_write() {
4712 let text = asm("const char *f(void) { return \"hi\"; }\n");
4713 assert!(text.contains("\t.ascii\t\"hi\\000\"\n"), "{text}");
4714 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4715 let label = text
4716 .lines()
4717 .find(|line| line.starts_with(".Lstr"))
4718 .unwrap_or_else(|| panic!("a label for the literal in\n{text}"));
4719 assert!(!text.contains(&format!(".globl\t{}", label.trim_end_matches(':'))), "{text}");
4720 }
4721
4722 #[test]
4724 fn an_address_in_an_initializer_is_left_to_the_linker() {
4725 let source = "int counter;\nint *p = &counter;\n";
4726 let text = asm(source);
4727 assert!(text.contains("\np:\n\t.quad\tcounter\n"), "{text}");
4728 let bytes = obj(source);
4731 assert!(bytes.windows(8).any(|w| w == b"counter\0"), "the object has to name it");
4732 }
4733
4734 #[test]
4743 fn a_constant_holding_an_address_goes_in_the_section_the_loader_may_write_once() {
4744 let text = asm("static void a(void) {}\nstatic void b(void) {}\n\
4747 struct m { void (*x)(void); void (*y)(void); };\n\
4748 const struct m t = { a, b };\n");
4749 assert!(text.contains("\t.section\t.data.rel.ro.local,\"aw\",@progbits\n"), "{text}");
4750 assert!(text.contains("\nt:\n\t.quad\ta\n\t.quad\tb\n"), "{text}");
4751
4752 let text =
4755 asm("void a(void);\nstruct m { void (*x)(void); };\nconst struct m t = { a };\n");
4756 assert!(text.contains("\t.section\t.data.rel.ro,\"aw\",@progbits\n"), "{text}");
4757
4758 let text = asm("const int fixed = 7;\n");
4760 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4761 }
4762
4763 #[test]
4770 fn a_thread_local_variable_is_storage_a_thread_gets_a_copy_of_and_an_offset_into_it() {
4771 let text = asm("_Thread_local int x = 1;\nint read(void) { return x; }\n");
4772 assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
4775 assert!(text.contains("\t.type\tx, @tls_object\n"), "{text}");
4776 assert!(text.contains("x@GOTTPOFF(%rip)"), "{text}");
4779 assert!(text.contains("%fs:0"), "{text}");
4780 }
4781
4782 #[test]
4788 fn the_address_of_this_thread_s_own_storage_is_read_out_of_the_segment_register() {
4789 let text = asm("void *here(void) { return __builtin_thread_pointer(); }\n");
4790 assert!(text.contains("movq\t%fs:0, "), "{text}");
4791 assert!(!text.contains("GOTTPOFF"), "{text}");
4793 }
4794
4795 #[test]
4806 fn a_prefetch_is_one_of_four_instructions_and_the_locality_is_what_picks() {
4807 for (locality, wanted) in
4808 [(0, "prefetchnta"), (1, "prefetcht2"), (2, "prefetcht1"), (3, "prefetcht0")]
4809 {
4810 let source =
4811 format!("void warm(void *p) {{ __builtin_prefetch(p, 0, {locality}); }}\n");
4812 let text = asm(&source);
4813 assert!(text.contains(&format!("\t{wanted}\t")), "locality {locality}: {text}");
4814 }
4815 let text = asm("void warm(void *p) { __builtin_prefetch(p); }\n");
4817 assert!(text.contains("\tprefetcht0\t"), "{text}");
4818 let text = asm("void warm(void *p) { __builtin_prefetch(p, 1); }\n");
4821 assert!(text.contains("\tprefetcht0\t"), "{text}");
4822 assert!(!text.contains("prefetchw"), "{text}");
4823 }
4824
4825 #[test]
4828 fn an_aarch64_prefetch_is_a_prfm_that_says_the_locality_and_whether_it_writes() {
4829 let mut opts = options();
4830 opts.emit = EmitKind::Asm;
4831 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
4832 for (write, kind) in [(0, "pld"), (1, "pst")] {
4833 for (locality, wanted) in [(0, "l1strm"), (1, "l3keep"), (2, "l2keep"), (3, "l1keep")] {
4834 let source = format!(
4835 "void warm(void *p) {{ __builtin_prefetch(p, {write}, {locality}); }}\n"
4836 );
4837 let result = run(&opts, &source);
4838 assert_eq!(result.messages, Vec::<String>::new(), "{source}");
4839 let text = result.text();
4840 assert!(text.contains("prfm"), "{source}{text}");
4841 assert!(text.contains(&format!("{kind}{wanted}, [x0]")), "{source}{text}");
4842 }
4843 }
4844 }
4845
4846 #[test]
4857 fn a_trap_is_the_instruction_the_machine_has_no_meaning_for() {
4858 let text = asm("void stop(void) { __builtin_trap(); }\n");
4859 assert!(text.contains("\tud2\n"), "{text}");
4860 assert!(!text.contains("\tcall"), "a stop is not a call to anything: {text}");
4861
4862 let text = asm("int stop(int a) { __builtin_trap(); return a + 1; }\n");
4863 assert!(text.contains("\tud2\n"), "{text}");
4864 assert!(text.contains("\taddl\t"), "the block goes on after a stop: {text}");
4865 }
4866
4867 #[test]
4871 fn cpu_init_is_a_call_to_the_libgcc_function_that_fills_in_the_model() {
4872 let text = asm("void start(void) { __builtin_cpu_init(); }\n");
4873 assert!(text.contains("\tcall\t__cpu_indicator_init"), "{text}");
4874 assert!(!text.contains("__builtin_cpu_init"), "{text}");
4875 }
4876
4877 #[test]
4885 fn cpu_supports_is_a_bit_of_the_words_libgcc_fills_in() {
4886 let text = asm("int f(void) { return __builtin_cpu_supports(\"sse4.2\"); }\n");
4887 assert!(text.contains("__cpu_model"), "{text}");
4888 assert!(text.contains("12(%"), "the fourth word of the model: {text}");
4889 assert!(text.contains("$256"), "{text}");
4890 assert!(!text.contains("\tcall"), "the answer is a read and not a call: {text}");
4891
4892 let text = asm("int f(void) { return __builtin_cpu_supports(\"vpclmulqdq\"); }\n");
4893 assert!(text.contains("__cpu_features2"), "{text}");
4894 assert!(text.contains("$2,"), "{text}");
4895
4896 let text = asm("int f(void) { return __builtin_cpu_supports(\"xsave\"); }\n");
4897 assert!(text.contains("__cpu_features2"), "{text}");
4898 assert!(text.contains("4(%"), "the second word of the second object: {text}");
4899 assert!(text.contains("$131072"), "{text}");
4900
4901 let text = asm("int f(void) { return __builtin_cpu_supports(\"avx512vbmi2\"); }\n");
4902 assert!(text.contains("set"), "the top bit is answered as a one: {text}");
4903 }
4904
4905 #[test]
4908 fn cpu_is_compares_one_word_of_the_model_with_a_number() {
4909 let text = asm("int f(void) { return __builtin_cpu_is(\"amd\"); }\n");
4910 assert!(text.contains("__cpu_model"), "{text}");
4911 assert!(text.contains("$2,"), "{text}");
4912
4913 let text = asm("int f(void) { return __builtin_cpu_is(\"znver4\"); }\n");
4914 assert!(text.contains("8(%"), "the subtype is the third word: {text}");
4915 assert!(text.contains("$29,"), "{text}");
4916 }
4917
4918 #[test]
4922 fn a_cpu_builtin_takes_a_name_it_knows_written_as_a_literal() {
4923 let mut opts = options();
4924 opts.emit = EmitKind::Ir;
4925 for (source, wanted) in [
4926 (
4927 "int f(const char *s) { return __builtin_cpu_supports(s); }\n",
4928 "parameter to builtin must be a string constant or literal",
4929 ),
4930 (
4931 "int f(void) { return __builtin_cpu_supports(\"sse5\"); }\n",
4932 "parameter to builtin not valid: sse5",
4933 ),
4934 (
4935 "int f(void) { return __builtin_cpu_is(\"sse\"); }\n",
4936 "parameter to builtin not valid: sse",
4937 ),
4938 ] {
4939 let result = run(&opts, source);
4940 assert!(
4941 result.messages.iter().any(|m| m.contains(wanted)),
4942 "{source}{:?}",
4943 result.messages
4944 );
4945 }
4946 let text = asm("int f(void) { return __builtin_cpu_supports((const char *)\"avx2\"); }\n");
4948 assert!(text.contains("$1024"), "{text}");
4949
4950 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
4951 for source in [
4952 "void f(void) { __builtin_cpu_init(); }\n",
4953 "int f(void) { return __builtin_cpu_supports(\"sse4.2\"); }\n",
4954 ] {
4955 let result = run(&opts, source);
4956 assert!(
4957 result.messages.iter().any(|m| m.contains("only available on x86-64")),
4958 "{source}{:?}",
4959 result.messages
4960 );
4961 }
4962 }
4963
4964 #[test]
4976 fn assume_aligned_is_its_first_argument_and_keeps_the_rest() {
4977 let text = asm("void *aligned(char *p) { return __builtin_assume_aligned(p, 16); }\n");
4978 assert!(!text.contains("assume_aligned"), "{text}");
4979 assert!(!text.contains("\tcall"), "nothing is called for an alignment fact: {text}");
4980
4981 let source = "unsigned long width(void);\n\
4982 void *aligned(char *p) { return __builtin_assume_aligned(p, width()); }\n";
4983 let text = asm(source);
4984 assert!(!text.contains("assume_aligned"), "{text}");
4985 assert!(text.contains("width"), "the argument that is not the answer still runs: {text}");
4986 }
4987
4988 #[test]
4998 fn the_frame_address_is_the_frame_pointer_after_walking_that_many_links() {
4999 let text = asm("void *here(void) { return __builtin_frame_address(0); }\n");
5000 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
5001 assert!(text.contains("movq\t%rbp, %rax"), "{text}");
5002 assert!(!text.contains("\tcall"), "a frame address is not a call to anything: {text}");
5003
5004 let walk = |depth: u32| {
5005 let source = format!("void *up(void) {{ return __builtin_frame_address({depth}); }}\n");
5006 asm(&source).matches("movq\t(%r").count()
5007 };
5008 assert_eq!(walk(1), 1, "one link is one load");
5009 assert_eq!(walk(3), 3, "three links are three loads");
5010 }
5011
5012 #[test]
5022 fn the_return_address_is_one_word_above_the_frame_the_walk_ended_at() {
5023 let text = asm("void *back(void) { return __builtin_return_address(0); }\n");
5024 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
5025 assert!(text.contains("movq\t8(%rbp), %rax"), "{text}");
5026 assert!(!text.contains("\tcall"), "a return address is not a call to anything: {text}");
5027
5028 let text = asm("void *back(void) { return __builtin_return_address(2); }\n");
5029 assert_eq!(text.matches("movq\t(%r").count(), 2, "two links are two loads: {text}");
5030 assert!(text.contains("movq\t8(%r"), "and the answer is above the last of them: {text}");
5031 }
5032
5033 #[test]
5044 fn a_depth_that_is_not_a_small_constant_is_refused() {
5045 let mut opts = options();
5046 opts.emit = EmitKind::Ir;
5047 for source in [
5048 "void *up(int n) { return __builtin_return_address(n); }\n",
5049 "void *up(void) { return __builtin_frame_address(1000); }\n",
5050 ] {
5051 let messages = run(&opts, source).messages;
5052 let named = messages.iter().any(|m| m.contains("E0705"));
5053 assert!(named, "expected a refusal in {messages:?}");
5054 }
5055 }
5056
5057 #[test]
5069 fn an_alloca_takes_the_bytes_off_the_stack_pointer_and_answers_where_they_are() {
5070 let text =
5071 asm("void use(void *p); void f(unsigned long n) { use(__builtin_alloca(n)); }\n");
5072 assert!(text.contains("andq\t$-16"), "the size is rounded up to sixteen: {text}");
5073 assert!(text.contains("subq\t%rdi, %rsp"), "and taken off the stack pointer: {text}");
5074 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
5075
5076 let plain = concat!(
5079 "extern void *alloca(__SIZE_TYPE__);\n",
5080 "void use(void *p);\n",
5081 "void f(unsigned long n) { use(alloca(n)); }\n",
5082 );
5083 let text = asm(plain);
5084 assert!(text.contains("subq\t%rdi, %rsp"), "the plain name is the same bytes: {text}");
5085 assert_eq!(text.matches("\tcall").count(), 1, "and is not a call either: {text}");
5086
5087 let own = concat!(
5090 "static void *alloca(unsigned long n) { return 0; }\n",
5091 "void *f(unsigned long n) { return alloca(n); }\n",
5092 );
5093 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
5094 }
5095
5096 #[test]
5111 fn a_builtin_the_program_never_declared_is_the_builtin_rather_than_the_one_c89_wrote_down() {
5112 let mut opts = options();
5116 opts.permissive = true;
5117 let undeclared = "void use(void *p);
5118void f(unsigned long n) { use(alloca(n)); }
5119";
5120 assert_eq!(
5121 run(&opts, undeclared).messages,
5122 [
5123 "/main.c:2:31: warning: implicit declaration of function 'alloca' [E0521]",
5124 "/main.c:2:31: warning: incompatible implicit declaration of built-in function \
5125 'alloca' [E0713]",
5126 ]
5127 );
5128
5129 opts.emit = EmitKind::Asm;
5130 let text = run(&opts, undeclared).text().to_owned();
5131 assert!(text.contains("subq\t%rdi, %rsp"), "the bytes come off the stack: {text}");
5132 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
5133
5134 let string = "unsigned long f(void) { return strlen(\"abc\"); }\n";
5137 let text = run(&opts, string).text().to_owned();
5138 assert!(text.contains("call\tstrlen"), "strlen is still a call: {text}");
5139
5140 let own = concat!(
5143 "static void *alloca(unsigned long n) { return 0; }\n",
5144 "void *f(unsigned long n) { return alloca(n); }\n",
5145 );
5146 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
5147 }
5148
5149 #[test]
5159 fn the_bytes_an_alloca_took_are_still_there_at_the_end_of_the_block_that_took_them() {
5160 let inner = "{ use(__builtin_alloca(n)); }";
5161 for body in [inner.to_owned(), format!("int a[n]; {inner} use(a);")] {
5162 let source = format!("void use(void *p);\nvoid f(unsigned long n) {{ {body} }}\n");
5163 let text = asm(&source);
5164 for line in text.lines().filter(|line| line.trim_end().ends_with(", %rsp")) {
5168 let taking = line.contains("subq");
5169 let leaving = line.contains("%rbp");
5170 assert!(taking || leaving, "nothing puts the stack back: {line} in {text}");
5171 }
5172 }
5173 }
5174
5175 #[test]
5177 fn the_object_and_the_listing_are_two_spellings_of_one_compilation() {
5178 let source = "int callee(void); int g(void) { return callee(); }\n";
5182 let bytes = obj(source);
5183 assert!(
5184 bytes.windows(7).any(|w| w == b"callee\0"),
5185 "the object has to name the callee for the linker to find it"
5186 );
5187 let text = asm(source);
5188 assert!(text.contains("\tcall\tcallee\n"), "{text}");
5189 }
5190
5191 #[test]
5197 fn compiling_for_an_executable_produces_an_object_and_not_a_dump() {
5198 let mut opts = options();
5199 opts.emit = EmitKind::Executable;
5201 let result = run(&opts, "int main(void) { return 0; }\n");
5202 assert_eq!(result.messages, Vec::<String>::new());
5203 match result.artifact {
5204 Artifact::Object { bytes, .. } => assert_eq!(&bytes[..4], b"\x7fELF"),
5205 other => panic!("expected an object, got {other:?}"),
5206 }
5207 }
5208
5209 #[test]
5211 fn a_platform_with_no_object_writer_is_said_so_rather_than_written_as_elf() {
5212 let mut opts = options();
5213 opts.emit = EmitKind::Object;
5214 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
5215 let result = run(&opts, "int f(void) { return 0; }\n");
5216 assert!(result.failed(), "an object nobody can read is worse than a message");
5217 assert!(
5218 result.messages.iter().any(|m| m.contains("no object writer")),
5219 "{:?}",
5220 result.messages
5221 );
5222 }
5223
5224 fn ir(source: &str) -> String {
5226 let mut opts = options();
5227 opts.emit = EmitKind::Ir;
5228 let result = run(&opts, source);
5229 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5230 result.text().to_owned()
5231 }
5232
5233 fn errors(source: &str) -> Vec<String> {
5235 let mut opts = options();
5236 opts.emit = EmitKind::Ir;
5237 let result = run(&opts, source);
5238 assert!(result.failed(), "expected this to be refused:\n{source}");
5239 result.messages
5240 }
5241
5242 fn body(source: &str) -> String {
5244 let text = ir(source);
5245 let (_, rest) = text.split_once("{\n").expect("a function definition");
5246 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
5247 body.to_owned()
5248 }
5249
5250 #[test]
5258 fn gnu89_inline_is_what_decides_whether_a_bare_inline_definition_reaches_the_module() {
5259 let source = "inline int f(int x) { return x + 1; }\n";
5260 let with = |flag: bool| {
5261 let mut opts = options();
5262 opts.emit = EmitKind::Ir;
5263 opts.gnu89_inline = flag;
5264 let result = run(&opts, source);
5265 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5266 result.text().to_owned()
5267 };
5268
5269 assert!(!with(false).contains("block0"), "no body: {}", with(false));
5272
5273 assert!(with(true).contains("block0"), "a body: {}", with(true));
5276 }
5277
5278 #[test]
5285 fn an_access_through_a_type_names_the_type_it_went_through() {
5286 let source = "\
5287struct s { int a; float b; };\n\
5288union u { int i; float f; };\n\
5289int scalar(int *p) { return *p; }\n\
5290float member(struct s *p) { p->a = 1; return p->b; }\n\
5291int element(int *a, long i) { return a[i]; }\n\
5292float through_a_union(union u *p) { p->i = 1; return p->f; }\n";
5293 let text = ir(source);
5294 assert!(text.contains(r#"!0 = tbaa "char""#), "the root: {text}");
5295 assert!(text.contains(r#"tbaa "int", parent !0"#), "int under it: {text}");
5296 assert!(text.contains(r#"tbaa "float", parent !0"#), "float under it: {text}");
5297 let named = text.lines().filter(|line| line.contains(", tbaa !")).count();
5300 assert_eq!(named, 6, "six accesses: {text}");
5301 }
5302
5303 #[test]
5310 fn turning_strict_aliasing_off_leaves_the_type_off_every_access() {
5311 let source = "int punned(float *f, int *i) { *i = 1; *f = 2.0f; return *i; }\n";
5312 let mut opts = options();
5313 opts.emit = EmitKind::Ir;
5314 opts.strict_aliasing = false;
5315 let result = run(&opts, source);
5316 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5317 let text = result.text().to_owned();
5318 assert!(!text.contains("tbaa"), "not even the root: {text}");
5319 }
5320
5321 #[test]
5327 fn instrumenting_functions_calls_the_hooks_around_every_body_but_the_hooks() {
5328 let source = concat!(
5329 "#define NOCHK __attribute__((no_instrument_function))\n",
5330 "void __cyg_profile_func_enter(void *, void *) NOCHK;\n",
5331 "void __cyg_profile_func_exit(void *, void *) NOCHK;\n",
5332 "int calls;\n",
5333 "int pick(int x) { if (x) return 1; return 2; }\n",
5334 "void quiet(void) NOCHK;\n",
5335 "void quiet(void) { calls++; }\n",
5336 "void __cyg_profile_func_enter(void *fn, void *site) { calls++; }\n",
5337 "void __cyg_profile_func_exit(void *fn, void *site) { calls--; }\n",
5338 );
5339 let mut opts = options();
5340 opts.emit = EmitKind::Ir;
5341 opts.instrument_functions = true;
5342 let result = run(&opts, source);
5343 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5344 let text = result.text().to_owned();
5345 let body = |name: &str| -> String {
5346 let open = format!("func @{name}(");
5347 let start = text.find(&open).unwrap_or_else(|| panic!("no {name}: {text}"));
5348 let rest = &text[start..];
5349 rest[..rest.find("\n}").unwrap_or(rest.len())].to_owned()
5350 };
5351 let pick = body("pick");
5352 assert_eq!(pick.matches("call @__cyg_profile_func_enter(").count(), 1, "{pick}");
5353 assert_eq!(pick.matches("call @__cyg_profile_func_exit(").count(), 2, "{pick}");
5354 assert!(pick.contains("return_address"), "{pick}");
5355 assert!(pick.contains("global_addr @pick"), "{pick}");
5356 for quiet in ["quiet", "__cyg_profile_func_enter", "__cyg_profile_func_exit"] {
5357 assert!(!body(quiet).contains("call "), "{quiet} is left alone: {text}");
5358 }
5359
5360 opts.instrument_functions = false;
5361 let result = run(&opts, source);
5362 assert!(!result.text().contains("call @__cyg_profile"), "off unless asked for");
5363 }
5364
5365 #[test]
5370 fn calls_that_owe_the_same_handlers_share_one_landing_pad() {
5371 let source = concat!(
5372 "void done(int *p);\n",
5373 "void work(int);\n",
5374 "void f(void) {\n",
5375 " int a __attribute__((cleanup(done))) = 1;\n",
5376 " work(1); work(2);\n",
5377 " { int b __attribute__((cleanup(done))) = 2; work(3); work(4); }\n",
5378 " work(5); work(6);\n",
5379 "}\n",
5380 );
5381 let mut opts = options();
5382 opts.emit = EmitKind::Ir;
5383 opts.exceptions = true;
5384 let result = run(&opts, source);
5385 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5386 let text = result.text();
5387 assert_eq!(text.matches("= unwound").count(), 8, "every call has its edge: {text}");
5390 assert_eq!(text.matches("= landing").count(), 2, "one pad for a, one for b and a: {text}");
5391 }
5392
5393 #[test]
5397 fn a_goto_that_runs_handlers_gives_each_one_an_edge_to_what_is_still_owed() {
5398 let source = concat!(
5399 "void done(int *p);\n",
5400 "void f(int n) {\n",
5401 " int a __attribute__((cleanup(done))) = 1;\n",
5402 " { int b __attribute__((cleanup(done))) = 2;\n",
5403 " { int c __attribute__((cleanup(done))) = 3; if (n) goto out; }\n",
5404 " }\n",
5405 "out:\n",
5406 " return;\n",
5407 "}\n",
5408 );
5409 let mut opts = options();
5410 opts.emit = EmitKind::Ir;
5411 opts.exceptions = true;
5412 let result = run(&opts, source);
5413 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5414 let text = result.text();
5415 assert_eq!(text.matches("= unwound").count(), 5, "{text}");
5418 assert_eq!(text.matches("= landing").count(), 2, "{text}");
5419 }
5420
5421 #[test]
5427 fn a_call_an_unwind_would_leave_a_cleanup_behind_gets_a_landing_pad_under_exceptions() {
5428 let source = concat!(
5429 "void done(int *p);\n",
5430 "void work(void);\n",
5431 "void calls(void) { int x __attribute__((cleanup(done))) = 1; work(); }\n",
5432 "int quiet(int y) { int x __attribute__((cleanup(done))) = y; return x + 1; }\n",
5433 "void after(void) { { int x __attribute__((cleanup(done))) = 1; } work(); }\n",
5434 );
5435 let mut opts = options();
5436 opts.emit = EmitKind::Ir;
5437 let result = run(&opts, source);
5438 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5439 assert!(!result.text().contains("landing"), "{}", result.text());
5440
5441 opts.exceptions = true;
5442 let result = run(&opts, source);
5443 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5444 let text = result.text();
5445 assert_eq!(text.matches("= landing").count(), 1, "only the call in calls: {text}");
5446 assert!(text.contains("_Unwind_Resume"), "{text}");
5447
5448 opts.emit = EmitKind::Asm;
5449 let result = run(&opts, source);
5450 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5451 let text = result.text();
5452 assert!(text.contains(".cfi_personality 0x9b,DW.ref.__gcc_personality_v0"), "{text}");
5453 assert!(text.contains(".cfi_lsda 0x1b,.LLSDA_calls"), "{text}");
5454 assert!(text.contains(".gcc_except_table"), "{text}");
5455 assert_eq!(text.matches(".cfi_lsda").count(), 1, "{text}");
5456
5457 opts.emit = EmitKind::Object;
5458 let result = run(&opts, source);
5459 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5460 let bytes = result.artifact.bytes();
5461 let has = |what: &[u8]| bytes.windows(what.len()).any(|window| window == what);
5462 assert!(has(b".gcc_except_table\0"), "the call site table has a section");
5463 assert!(has(b"zPLR\0"), "a header naming the personality routine");
5464 assert!(has(b"zR\0"), "and the plain one for the functions with no pad");
5465 assert!(has(b"DW.ref.__gcc_personality_v0\0"), "the pointer the header reads through");
5466
5467 opts.emit = EmitKind::Ir;
5468 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
5469 let result = run(&opts, source);
5470 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
5471 assert!(result.messages[0].contains("landing pad"), "{:?}", result.messages);
5472 assert!(result.messages[0].contains(":3:"), "the call in calls: {:?}", result.messages);
5473 }
5474
5475 #[test]
5481 fn an_asm_at_file_scope_with_an_instruction_in_it_is_assembled() {
5482 let source = concat!(
5483 "extern void vide(void);\n",
5484 "__asm__(\".text;.globl _us;_us:;movl $0x1234ABCD, %eax;ret\");\n",
5485 "__asm__(\"vide: ret\");\n",
5486 "unsigned short _us(void);\n",
5487 "int main(void) { vide(); return _us() == 0xABCD ? 0 : 1; }\n",
5488 );
5489 let mut opts = options();
5490 opts.emit = EmitKind::Ir;
5491 let result = run(&opts, source);
5492 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5493 assert_eq!(result.text().matches("module asm ").count(), 2, "{}", result.text());
5494
5495 opts.emit = EmitKind::Asm;
5496 let result = run(&opts, source);
5497 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5498 let text = result.text();
5499 assert!(text.contains("#APP\nvide: ret\n#NO_APP\n"), "{text}");
5500 let main = text.find("main:").expect("main");
5501 assert!(text.find("#NO_APP").expect("the markers") < main, "templates first: {text}");
5502
5503 opts.emit = EmitKind::Object;
5504 let result = run(&opts, source);
5505 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5506 let (bytes, defines) = match result.artifact {
5507 Artifact::Object { bytes, defines } => (bytes, defines),
5508 other => panic!("expected an object, got {other:?}"),
5509 };
5510 assert!(defines.iter().any(|name| name == "_us"), "{defines:?}");
5511 let us = [0xb8, 0xcd, 0xab, 0x34, 0x12, 0xc3];
5513 assert!(bytes.windows(us.len()).any(|window| window == us), "the template's bytes");
5514
5515 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
5517 opts.emit = EmitKind::Ir;
5518 let result = run(&opts, source);
5519 assert!(!result.messages.is_empty(), "refused on Mach-O");
5520 assert!(result.messages[0].contains("the instruction 'movl'"), "{:?}", result.messages);
5521 }
5522
5523 #[test]
5531 fn a_bare_return_from_a_function_that_promised_a_value_gives_back_a_zero() {
5532 let mut opts = options();
5533 opts.emit = EmitKind::Ir;
5534 opts.std = Std::C89;
5535 let compiled = |source: &str| {
5536 let result = run(&opts, source);
5537 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
5538 result.text().to_owned()
5539 };
5540
5541 let text = compiled("int f(int x) { if (x) return; return 3; }\n");
5542 assert!(text.contains("iconst.i32 0\n return"), "zero goes back: {text}");
5543 assert!(!text.contains("unreachable"), "the branch that reached it is kept: {text}");
5544
5545 let text = compiled("double f(int x) { if (x) return; return 1.0; }\n");
5547 assert!(text.contains("fconst.f64 0x0\n return"), "a float zero goes back: {text}");
5548 }
5549
5550 #[test]
5558 fn a_call_to_a_name_nothing_declared_declares_it_as_c89_said_to() {
5559 let mut opts = options();
5560 opts.emit = EmitKind::Ir;
5561 opts.std = Std::C89;
5562 let compiled = |source: &str| {
5563 let result = run(&opts, source);
5564 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
5565 result.text().to_owned()
5566 };
5567
5568 let text = compiled("int f(void) { return g(); }\n");
5570 assert!(text.contains("call @g"), "the call is to the name that was written: {text}");
5571 assert!(text.contains("i32"), "and it gives back an int: {text}");
5572
5573 let text = compiled("int f(char c) { return g(c); }\n");
5576 assert!(text.contains("sext.i32"), "the argument is promoted: {text}");
5577
5578 let mut opts = options();
5581 opts.std = Std::C89;
5582 let said = run(&opts, "int f(void) { return h; }\n").messages.join("\n");
5583 assert!(said.contains("'h' undeclared"), "not a call, so not declared: {said}");
5584 }
5585
5586 #[test]
5596 fn a_name_called_before_it_is_defined_still_gets_its_definition() {
5597 let mut opts = options();
5598 opts.emit = EmitKind::Ir;
5599 opts.std = Std::C89;
5600 let text = run(&opts, "int f(void) { return dummy(); }\ndummy () { return 7; }\n")
5601 .text()
5602 .to_owned();
5603 assert!(text.contains("func @f()"), "the caller is there: {text}");
5604 assert!(text.contains("func @dummy"), "and so is what it calls: {text}");
5605 assert!(text.contains("iconst.i32 7"), "with the body it was given: {text}");
5606 }
5607
5608 #[test]
5616 fn an_old_style_parameter_is_converted_from_what_the_call_promoted_it_to() {
5617 let mut opts = options();
5618 opts.emit = EmitKind::Ir;
5619 opts.std = Std::C89;
5620 let compiled = |source: &str| run(&opts, source).text().to_owned();
5621
5622 let text = compiled("f (c) unsigned char c; { return c; }\n");
5623 assert!(text.contains("func @f(i32"), "an int arrives: {text}");
5624 assert!(text.contains("trunc.i8"), "and is cut down to what was declared: {text}");
5625 assert!(text.contains("zext.i32"), "then read back unsigned: {text}");
5626
5627 let text = compiled("f (s) short s; { return s; }\n");
5629 assert!(text.contains("trunc.i16"), "cut down: {text}");
5630 assert!(text.contains("sext.i32"), "and read back signed: {text}");
5631
5632 let text = compiled("f (x) float x; { return x * 2; }\n");
5635 assert!(text.contains("func @f(f64"), "a double arrives: {text}");
5636 assert!(text.contains("fptrunc.f32"), "and is narrowed to the float: {text}");
5637
5638 let text = compiled("int f(unsigned char c) { return c; }\n");
5641 assert!(text.contains("func @f(i8)"), "the declared type arrives: {text}");
5642 assert!(!text.contains("trunc"), "so there is nothing to cut down: {text}");
5643 }
5644
5645 #[test]
5654 fn the_rules_gcc_promoted_are_decided_by_the_dialect_and_by_fpermissive() {
5655 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
5657 let cases = [
5658 ("static counted;\n", ["", "error", "warning", "error"]),
5659 ("int f(void) { return g(); }\n", ["", "error", "warning", "error"]),
5660 ("int f(x) { return x; }\n", ["", "error", "warning", "error"]),
5661 ("int *p;\nvoid h(void) { p = 1; }\n", ["warning", "error", "warning", "error"]),
5662 (
5663 "char *q;\nint *r;\nvoid k(void) { r = q; }\n",
5664 ["warning", "error", "warning", "error"],
5665 ),
5666 ("int f(void) { return; }\n", ["", "error", "warning", "error"]),
5667 ("void g(void) { return 1; }\n", ["warning", "error", "warning", "error"]),
5668 ];
5669
5670 for (source, wanted) in cases {
5671 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
5672 let mut opts = options();
5673 opts.std = std;
5674 opts.permissive = permissive;
5675 let said = run(&opts, source).messages.join("\n");
5676 let severity = if said.contains(": error: ") {
5677 "error"
5678 } else if said.contains(": warning: ") {
5679 "warning"
5680 } else {
5681 ""
5682 };
5683 let how = if permissive { " -fpermissive" } else { "" };
5684 assert_eq!(
5685 severity,
5686 wanted,
5687 "under -std={}{how}, {source} was answered with `{said}`",
5688 std.as_str()
5689 );
5690 if wanted.is_empty() {
5691 assert!(said.is_empty(), "nothing to say, but said `{said}`");
5692 }
5693 }
5694 }
5695 }
5696
5697 #[test]
5706 fn the_three_variadic_builtins_answer_a_bad_list_the_way_a_call_answers_a_bad_argument() {
5707 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
5708 let cases = [
5709 (
5710 "int f(int n, ...) { char *p; return __builtin_va_arg(p, int); }\n",
5711 "first argument to 'va_arg' not of type 'va_list'",
5712 ["error", "error", "error", "error"],
5713 ),
5714 (
5715 "void f(int n, ...) { char *p; __builtin_va_start(p, n); }\n",
5716 "passing argument 1 of '__builtin_va_start' from incompatible pointer type",
5717 ["warning", "error", "warning", "error"],
5718 ),
5719 (
5720 "void f(int n, ...) { int x; __builtin_va_end(x); }\n",
5721 "passing argument 1 of '__builtin_va_end' makes pointer from integer without a \
5722 cast",
5723 ["warning", "error", "warning", "error"],
5724 ),
5725 (
5726 "void f(int n, ...) { __builtin_va_list a; char *p; __builtin_va_copy(a, p); }\n",
5727 "passing argument 2 of '__builtin_va_copy' from incompatible pointer type",
5728 ["warning", "error", "warning", "error"],
5729 ),
5730 ];
5731
5732 for (source, message, wanted) in cases {
5733 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
5734 let mut opts = options();
5735 opts.std = std;
5736 opts.permissive = permissive;
5737 let said = run(&opts, source).messages.join("\n");
5738 let how = if permissive { " -fpermissive" } else { "" };
5739 assert!(
5740 said.contains(&format!(": {wanted}: {message}")),
5741 "under -std={}{how}, {source} was answered with `{said}`",
5742 std.as_str()
5743 );
5744 }
5745 }
5746 }
5747
5748 fn safe_ir(tier: rucc_session::Safety, source: &str) -> String {
5750 let mut opts = options();
5751 opts.emit = EmitKind::Ir;
5752 opts.safety = tier;
5753 let result = run(&opts, source);
5754 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5755 result.text().to_owned()
5756 }
5757
5758 const READS_THROUGH_A_POINTER: &str = "int read(int *p) { return p[1]; }\n";
5759
5760 fn padded_ir(padding: Padding, source: &str) -> String {
5762 let mut opts = options();
5763 opts.emit = EmitKind::Ir;
5764 opts.safety = rucc_session::Safety::Detect;
5765 opts.padding = padding;
5766 let result = run(&opts, source);
5767 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5768 result.text().to_owned()
5769 }
5770
5771 const FILLS_A_RECORD_A_MEMBER_AT_A_TIME: &str = "struct padded { char tag; int value; };\n\
5772 void fill(struct padded *p) { p->tag = 1; p->value = 2; }\n";
5773
5774 #[test]
5775 fn a_record_filled_a_member_at_a_time_comes_out_whole_when_padding_does_not_participate() {
5776 let text = padded_ir(Padding::Ignored, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
5780 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
5781 }
5782
5783 #[test]
5784 fn a_store_says_only_what_it_wrote_when_padding_does_participate() {
5785 let text = padded_ir(Padding::Tracked, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
5788 assert!(!text.contains("owns"), "{text}");
5789 }
5790
5791 #[test]
5792 fn a_member_of_a_union_owns_nothing_after_it() {
5793 let text = padded_ir(
5797 Padding::Ignored,
5798 "union u { char tag; long wide; };\nvoid fill(union u *p) { p->tag = 1; }\n",
5799 );
5800 assert!(!text.contains("owns"), "{text}");
5801 }
5802
5803 #[test]
5804 fn an_inner_records_trailing_padding_reaches_the_outer_records() {
5805 let text = padded_ir(
5810 Padding::Ignored,
5811 "struct inner { char c; };\n\
5812 struct outer { struct inner in; int x; };\n\
5813 void fill(struct outer *p) { p->in.c = 1; p->x = 2; }\n",
5814 );
5815 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
5816 }
5817
5818 #[test]
5819 fn a_build_that_did_not_ask_for_the_monitor_is_compiled_the_way_it_always_was() {
5820 let text = ir(READS_THROUGH_A_POINTER);
5824 assert!(!text.contains("check_"), "{text}");
5825 assert!(!text.contains("cap_of"), "{text}");
5826 }
5827
5828 #[test]
5829 fn asking_for_a_tier_puts_the_checks_in_before_the_optimizer_sees_them() {
5830 let text = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5831 assert!(text.contains("cap_of"), "{text}");
5832 assert!(text.contains("check_bounds"), "{text}");
5833 assert!(text.contains("check_live"), "{text}");
5834 assert!(text.contains("check_deriv"), "{text}");
5836 assert!(text.contains("check_type"), "{text}");
5838 }
5839
5840 #[test]
5841 fn the_three_tiers_that_are_not_off_all_check_the_same_accesses_so_far() {
5842 let detect = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5846 for tier in [rucc_session::Safety::Enforce, rucc_session::Safety::Kernel] {
5847 assert_eq!(safe_ir(tier, READS_THROUGH_A_POINTER), detect, "{tier}");
5848 }
5849 }
5850
5851 fn summary(tier: rucc_session::Safety, source: &str) -> String {
5853 let mut opts = options();
5854 opts.emit = EmitKind::SafetySummary;
5855 opts.safety = tier;
5856 let result = run(&opts, source);
5857 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5858 result.text().to_owned()
5859 }
5860
5861 #[test]
5862 fn the_summary_counts_the_checks_that_went_in_and_the_ones_still_standing() {
5863 let text = summary(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5864 assert!(text.contains("\"tier\": \"detect\""), "{text}");
5865 assert!(
5867 text.contains("\"bounds\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"),
5868 "{text}"
5869 );
5870 assert!(
5871 text.contains(
5872 "\"derivation\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"
5873 ),
5874 "{text}"
5875 );
5876 }
5877
5878 #[test]
5879 fn a_build_without_the_monitor_summarises_as_a_build_with_no_checks_in_it() {
5880 let text = summary(rucc_session::Safety::Off, READS_THROUGH_A_POINTER);
5884 assert!(text.contains("\"tier\": \"off\""), "{text}");
5885 assert!(
5886 text.contains("\"bounds\": { \"emitted\": 0, \"remaining\": 0, \"discharged\": 0 }"),
5887 "{text}"
5888 );
5889 }
5890
5891 #[test]
5892 fn a_call_the_boundary_models_is_counted_apart_from_one_it_does_not() {
5893 let text = summary(
5894 rucc_session::Safety::Detect,
5895 "void *memcpy(void *, const void *, unsigned long);\n\
5896 int puts(const char *);\n\
5897 void f(char *d, char *s) { memcpy(d, s, 4); puts(d); }\n",
5898 );
5899 assert!(text.contains("\"interposed\": 1"), "{text}");
5900 assert!(text.contains("\"puts\""), "{text}");
5901 assert!(!text.contains("__rucc_wrap_memcpy\""), "{text}");
5905 }
5906
5907 #[test]
5908 fn an_address_taken_of_a_library_function_is_counted_the_way_a_call_to_one_is() {
5909 let text = summary(
5914 rucc_session::Safety::Detect,
5915 "void *memcpy(void *, const void *, unsigned long);\n\
5916 int puts(const char *);\n\
5917 void *table[2] = { (void *)memcpy, (void *)puts };\n\
5918 void *f(int i) { return table[i]; }\n",
5919 );
5920 assert!(text.contains("\"interposed\": 1"), "{text}");
5921 assert!(text.contains("\"puts\""), "{text}");
5922 assert!(!text.contains("\"memcpy\""), "{text}");
5923 }
5924
5925 #[test]
5926 fn the_two_directions_a_pointer_crosses_the_boundary_are_counted_apart() {
5927 let text = summary(
5931 rucc_session::Safety::Detect,
5932 "void *notes_open(void);\n\
5933 char *f(char *p) { char *q = notes_open(); return q ? q : p; }\n",
5934 );
5935 assert!(text.contains("\"crossings\": { \"entered\": 1, \"returned\": 1 }"), "{text}");
5936 assert!(text.contains("\"notes_open\""), "{text}");
5937 }
5938
5939 #[test]
5940 fn a_static_function_nobody_takes_the_address_of_is_not_a_crossing() {
5941 let text = summary(
5944 rucc_session::Safety::Detect,
5945 "static int len(const char *p) { return p ? 1 : 0; }\n\
5946 int f(void) { return len(\"x\"); }\n",
5947 );
5948 assert!(text.contains("\"crossings\": { \"entered\": 0, \"returned\": 0 }"), "{text}");
5949 }
5950
5951 fn granules(source: &str) -> String {
5953 let mut opts = options();
5954 opts.emit = EmitKind::TypeGranules;
5955 let result = run(&opts, source);
5956 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5957 result.text().to_owned()
5958 }
5959
5960 #[test]
5961 fn the_granule_report_names_every_record_and_both_keyings() {
5962 let text = granules(
5963 "struct hot { char *p; int a; int b; };\n\
5964 int f(struct hot *h) { return h->a; }\n",
5965 );
5966 assert!(text.contains("struct hot"), "{text}");
5967 assert!(text.contains("every type distinct"), "{text}");
5970 assert!(text.contains("every pointer one type"), "{text}");
5971 assert!(text.contains("budget"), "{text}");
5972 }
5973
5974 #[test]
5975 fn a_record_nothing_uses_is_still_measured() {
5976 let text = granules("struct unused { long a; double b; };\nint f(void) { return 0; }\n");
5979 assert!(text.contains("struct unused"), "{text}");
5980 }
5981
5982 #[test]
5983 fn the_granule_report_stops_before_anything_is_lowered() {
5984 let text = granules(
5988 "struct wide { long double d; };\n\
5989 long double f(long double x) { return x * x; }\n",
5990 );
5991 assert!(text.contains("struct wide"), "{text}");
5992 }
5993
5994 #[test]
5995 fn a_witness_reaches_the_assembler_as_a_call_to_the_runtime() {
5996 let text = safe_asm(rucc_session::Safety::Detect, "char *f(char *p) { return p; }\n");
5999 assert!(text.contains("\tcall\t__rucc_cap_witness\n"), "{text}");
6000 }
6001
6002 #[test]
6003 fn a_pointer_turned_into_an_integer_is_on_the_trust_set() {
6004 let text = summary(
6005 rucc_session::Safety::Detect,
6006 "unsigned long f(int *p) { return (unsigned long) p; }\n",
6007 );
6008 assert!(text.contains("\"exposed\": 1"), "{text}");
6009 }
6010
6011 fn safe_asm(tier: rucc_session::Safety, source: &str) -> String {
6013 let mut opts = options();
6014 opts.emit = EmitKind::Asm;
6015 opts.safety = tier;
6016 let result = run(&opts, source);
6017 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
6018 result.text().to_owned()
6019 }
6020
6021 #[test]
6022 fn a_check_reaches_the_assembler_as_a_call_to_the_runtime() {
6023 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
6024 assert!(text.contains("\tcall\t__rucc_check_bounds\n"), "{text}");
6025 assert!(text.contains("\tcall\t__rucc_check_live\n"), "{text}");
6026 assert!(text.contains("\tcall\t__rucc_check_deriv\n"), "{text}");
6027 assert!(text.contains("\tcall\t__rucc_check_typed_init\n"), "{text}");
6030 }
6031
6032 #[test]
6033 fn every_check_that_reached_the_assembler_has_a_row_describing_it() {
6034 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
6040 let section = format!("\t.section\t{},", rucc_safety::SECTION);
6041 assert_eq!(text.matches(§ion).count(), 4, "{text}");
6042 for index in 0..4 {
6043 let name = format!("__rucc_safety_desc_{index}");
6044 assert!(text.contains(&format!("{name}:\n")), "{text}");
6047 assert!(text.contains(&format!("{name}(%rip)")), "{text}");
6048 }
6049 assert!(!text.contains("__rucc_safety_desc_4"), "{text}");
6050 }
6051
6052 #[test]
6060 fn builtin_constant_p_is_folded_where_it_is_written_rather_than_called() {
6061 let text = ir(concat!(
6062 "int g;\n",
6063 "int a = __builtin_constant_p(1);\n",
6064 "int b = __builtin_constant_p(g);\n",
6065 "int c = __builtin_constant_p(\"abc\");\n",
6066 "int d = __builtin_constant_p(&g);\n",
6067 "int e = __builtin_constant_p(1.5);\n",
6068 "int h = __builtin_choose_expr(__builtin_constant_p(3), 11, 22);\n",
6069 ));
6070 assert!(text.contains("global @a : i32 = 1,"), "{text}");
6071 assert!(text.contains("global @b : i32 = 0,"), "{text}");
6072 assert!(text.contains("global @c : i32 = 1,"), "{text}");
6073 assert!(text.contains("global @d : i32 = 0,"), "{text}");
6074 assert!(text.contains("global @e : i32 = 1,"), "{text}");
6075 assert!(text.contains("global @h : i32 = 11,"), "{text}");
6076 assert!(!text.contains("__builtin_constant_p"), "it is not a call to anything:\n{text}");
6077
6078 let text = body("int f(void) { int i = 0; __builtin_constant_p(i++); return i; }\n");
6082 assert_eq!(text, "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 0\n return %0\n");
6083 }
6084
6085 #[test]
6094 fn a_call_to_a_library_builtin_reaches_the_library_function() {
6095 let text = body("void f(void) { __builtin_abort(); }\n");
6096 assert_eq!(text, "block0:\n call @abort() : ()\n return\n");
6097
6098 let text = ir("int f(const char *s) { return __builtin_puts(s) + __builtin_strlen(s); }\n");
6101 assert!(text.contains("call @puts(%0) : (ptr) -> i32"), "{text}");
6102 assert!(text.contains("call @strlen(%0) : (ptr) -> i64"), "{text}");
6103 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
6104 }
6105
6106 #[test]
6119 fn a_chk_builtin_reaches_the_checking_function_and_keeps_the_size() {
6120 let text = ir(concat!(
6121 "char d[8];\n",
6122 "void f(const char *s, unsigned long n) {\n",
6123 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
6124 " __builtin___strcpy_chk(d, s, __builtin_object_size(d, 1));\n",
6125 " __builtin___memset_chk(d, 0, n, 8);\n",
6126 "}\n",
6127 ));
6128 assert!(text.contains("call @__memcpy_chk("), "{text}");
6129 assert!(text.contains("call @__strcpy_chk("), "{text}");
6130 assert!(text.contains("call @__memset_chk("), "{text}");
6131 assert!(text.contains("iconst.i64 8"), "the object size reaches the call: {text}");
6132 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
6133 }
6134
6135 #[test]
6143 fn a_checking_call_whose_size_says_nothing_is_known_is_the_plain_library_call() {
6144 let text = ir(concat!(
6145 "extern char *p;\n",
6146 "char d[8];\n",
6147 "void f(const char *s, unsigned long n) {\n",
6148 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
6149 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
6150 " __builtin___strcpy_chk(p, s, __builtin_object_size(p, 0));\n",
6151 " __builtin___stpncpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
6152 " __builtin___sprintf_chk(p, 1, __builtin_object_size(p, 0), s);\n",
6153 "}\n",
6154 ));
6155
6156 assert!(
6158 text.contains("call @__memcpy_chk(%2, %0, %1, %3) : (ptr, ptr, i64, i64)"),
6159 "{text}"
6160 );
6161
6162 assert!(text.contains("call @memcpy(%6, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
6165 assert!(text.contains("call @strcpy(%10, %0) : (ptr, ptr) -> ptr"), "{text}");
6166 assert!(text.contains("call @stpncpy(%14, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
6167
6168 assert!(text.contains("call @__sprintf_chk("), "{text}");
6171
6172 let asm = asm(concat!(
6175 "void f(char *p, const char *s, unsigned long n) {\n",
6176 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
6177 "}\n",
6178 ));
6179 assert!(asm.contains("call\tmemcpy"), "{asm}");
6180 assert!(!asm.contains("$-1"), "the size that went away leaves no instruction:\n{asm}");
6181 }
6182
6183 #[test]
6191 fn the_v_spellings_of_the_chk_family_take_the_list_a_va_list_parameter_holds() {
6192 let text = ir(concat!(
6193 "char d[64];\n",
6194 "int f(const char *fmt, ...) {\n",
6195 " __builtin_va_list ap;\n",
6196 " __builtin_va_start(ap, fmt);\n",
6197 " int n = __builtin___vsprintf_chk(d, 1, __builtin_object_size(d, 0), fmt, ap);\n",
6198 " __builtin_va_end(ap);\n",
6199 " return n;\n",
6200 "}\n",
6201 ));
6202 assert!(text.contains("call @__vsprintf_chk("), "{text}");
6203 assert!(text.contains("iconst.i64 64"), "the object size reaches the call: {text}");
6204 }
6205
6206 #[test]
6217 fn the_absolute_value_family_is_the_magnitude_and_not_a_call() {
6218 let text = body(concat!(
6219 "long long llabs(long long);\n",
6220 "long long f(long long x) { return llabs(x); }\n",
6221 ));
6222 assert!(text.contains("%1 = iconst.i64 63"), "{text}");
6223 assert!(text.contains("%2 = ashr %0, %1"), "{text}");
6224 assert!(text.contains("%3 = xor %0, %2"), "{text}");
6225 assert!(text.contains("%4 = sub %3, %2"), "{text}");
6226 assert!(!text.contains("call"), "the call does not happen:\n{text}");
6227
6228 let text = body("int abs(int);\nint f(int x) { return abs(x); }\n");
6231 assert!(text.contains("iconst.i32 31"), "{text}");
6232 let text = body("long labs(long);\nlong f(long x) { return labs(x); }\n");
6233 assert!(text.contains("iconst.i64 63"), "{text}");
6234
6235 let text = body("long long f(long long x) { return __builtin_llabs(x); }\n");
6238 assert!(!text.contains("call"), "{text}");
6239
6240 let text = ir(concat!(
6242 "long long llabs(long long b);\n",
6243 "long long g(long long x) { return llabs(x); }\n",
6244 "long long llabs(long long b) { return 7; }\n",
6245 ));
6246 assert!(!text.contains("call @llabs"), "{text}");
6247 }
6248
6249 #[test]
6256 fn a_byte_swap_is_arithmetic_and_not_a_call() {
6257 let text = body("unsigned f(unsigned x) { return __builtin_bswap32(x); }\n");
6258 assert_eq!(text, "block0(%0: i32):\n %1 = bswap %0\n return %1\n");
6259
6260 let text = body("unsigned f(unsigned char c) { return __builtin_bswap32(c); }\n");
6263 assert!(text.contains("zext.i32 %0"), "widened first: {text}");
6264 assert!(text.contains("bswap %1"), "and swapped at four bytes: {text}");
6265 }
6266
6267 #[test]
6273 fn the_byte_swaps_reverse_at_the_width_their_name_says() {
6274 for (name, ty, width) in [
6275 ("__builtin_bswap16", "unsigned short", "i16"),
6276 ("__builtin_bswap32", "unsigned", "i32"),
6277 ("__builtin_bswap64", "unsigned long long", "i64"),
6278 ] {
6279 let source = format!("{ty} f({ty} x) {{ return {name}(x); }}\n");
6280 let text = body(&source);
6281 assert_eq!(
6282 text,
6283 format!("block0(%0: {width}):\n %1 = bswap %0\n return %1\n"),
6284 "{name}"
6285 );
6286 }
6287 }
6288
6289 #[test]
6296 fn the_bit_counts_are_instructions_and_not_calls() {
6297 let text = body("int f(unsigned x) { return __builtin_clz(x); }\n");
6298 assert_eq!(text, "block0(%0: i32):\n %1 = ctlz %0\n return %1\n");
6299
6300 let text = body("int f(unsigned x) { return __builtin_ctz(x); }\n");
6301 assert_eq!(text, "block0(%0: i32):\n %1 = cttz %0\n return %1\n");
6302
6303 let text = body("int f(unsigned x) { return __builtin_popcount(x); }\n");
6304 assert_eq!(text, "block0(%0: i32):\n %1 = ctpop %0\n return %1\n");
6305 }
6306
6307 #[test]
6316 fn the_bit_counts_ask_about_the_width_their_name_says() {
6317 let text = body("int f(unsigned long long x) { return __builtin_clzll(x); }\n");
6318 assert!(text.starts_with("block0(%0: i64):"), "counted at eight bytes: {text}");
6319 assert!(text.contains("%1 = ctlz %0"), "{text}");
6320 assert!(text.contains("trunc.i32 %1"), "and answered in an int: {text}");
6321
6322 let text = body("int f(unsigned long long x) { return __builtin_clz(x); }\n");
6325 assert!(text.contains("trunc.i32 %0"), "narrowed to what was asked about: {text}");
6326 assert!(text.contains("ctlz %1"), "and counted there: {text}");
6327
6328 let text = body("int f(unsigned long x) { return __builtin_popcountl(x); }\n");
6329 assert!(text.contains("%1 = ctpop %0"), "{text}");
6330 assert!(!text.contains("call"), "{text}");
6331 }
6332
6333 #[test]
6338 fn a_parity_is_the_low_bit_of_the_set_bit_count() {
6339 let text = body("int f(unsigned x) { return __builtin_parity(x); }\n");
6340 assert!(text.contains("%1 = ctpop %0"), "{text}");
6341 assert!(text.contains("iconst.i32 1"), "{text}");
6342 assert!(text.contains("and %1, %2"), "the low bit of it: {text}");
6343 }
6344
6345 #[test]
6351 fn the_first_set_bit_is_one_based_and_zero_for_a_zero() {
6352 let text = body("int f(int x) { return __builtin_ffs(x); }\n");
6353 assert!(text.contains("%1 = cttz %0"), "{text}");
6354 assert!(text.contains("%4 = add %1, %2"), "one more than the count: {text}");
6355 assert!(text.contains("%5 = icmp ne %0, %3"), "whether there was a bit at all: {text}");
6356 assert!(text.contains("%7 = sub %3, %6"), "spread to a mask: {text}");
6357 assert!(text.contains("%8 = and %4, %7"), "and kept only then: {text}");
6358 assert!(!text.contains("br_if"), "no branch: {text}");
6359 }
6360
6361 #[test]
6371 fn the_redundant_sign_bit_count_is_instructions_and_not_a_call() {
6372 let text = body("int f(int x) { return __builtin_clrsb(x); }\n");
6373 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
6374 assert!(text.contains("%2 = ashr %0, %1"), "the sign over every bit: {text}");
6375 assert!(text.contains("%3 = xor %0, %2"), "folded onto it: {text}");
6376 assert!(text.contains("%5 = shl %3, %4"), "one less than the count: {text}");
6377 assert!(text.contains("%6 = or %5, %4"), "with something to count at zero: {text}");
6378 assert!(text.contains("%7 = ctlz %6"), "{text}");
6379 assert!(!text.contains("call"), "{text}");
6380 assert!(!text.contains("br_if"), "no branch: {text}");
6381 }
6382
6383 #[test]
6389 fn the_unsigned_absolute_value_family_answers_in_the_unsigned_type() {
6390 let text = body("unsigned f(int x) { return __builtin_uabs(x); }\n");
6391 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
6392 assert!(text.contains("%4 = sub %3, %2"), "{text}");
6393 assert!(!text.contains("call"), "nothing declares uabs, so a call would not link: {text}");
6394
6395 let text = body("unsigned long long f(long long x) { return __builtin_ullabs(x); }\n");
6396 assert!(text.contains("iconst.i64 63"), "at the width the name says: {text}");
6397
6398 let text = body("int f(int x) { return __builtin_uabs(x) > 2147483647u; }\n");
6401 assert!(text.contains("icmp ugt"), "compared unsigned: {text}");
6402 }
6403
6404 #[test]
6412 fn the_widest_absolute_value_is_whichever_type_the_target_makes_intmax_t() {
6413 let text = body("long f(long x) { return __builtin_imaxabs(x); }\n");
6414 assert!(text.contains("iconst.i64 63"), "{text}");
6415 assert!(text.contains("%4 = sub %3, %2"), "{text}");
6416 assert!(!text.contains("call"), "{text}");
6417
6418 let text = body("unsigned long f(long x) { return __builtin_umaxabs(x); }\n");
6419 assert!(text.contains("iconst.i64 63"), "{text}");
6420 assert!(!text.contains("call"), "{text}");
6421 }
6422
6423 #[test]
6431 fn an_overflow_predicate_writes_nothing_and_answers_the_bit_the_check_would() {
6432 let text =
6433 body("int f(int a, int b) { return __builtin_add_overflow_p(a, b, (int) 0); }\n");
6434 assert!(text.contains("%2, %3 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
6435 assert!(!text.contains("store"), "nothing is written: {text}");
6436 assert!(!text.contains("call"), "{text}");
6437
6438 let text =
6441 body("int f(int a, int b) { return __builtin_mul_overflow_p(a, b, (long long) 0); }\n");
6442 assert!(text.contains("smul_overflow.(i64, i1)"), "{text}");
6443 assert!(!text.contains("store"), "{text}");
6444
6445 let text = body(concat!(
6448 "int g(void);\n",
6449 "int f(int a, int b) { return __builtin_sub_overflow_p(a, b, g()); }\n",
6450 ));
6451 assert!(!text.contains("call @g"), "the third argument is not evaluated: {text}");
6452 }
6453
6454 #[test]
6464 fn an_overflow_check_is_arithmetic_and_not_a_call() {
6465 let text =
6466 body("int f(int a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
6467 assert!(text.contains("%3, %4 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
6468 assert!(text.contains("store %3 -> %2"), "{text}");
6469 assert!(!text.contains("call"), "{text}");
6470
6471 let text =
6472 body("int f(int a, int b, int *r) { return __builtin_sub_overflow(a, b, r); }\n");
6473 assert!(text.contains("ssub_overflow.(i32, i1) %0, %1"), "{text}");
6474
6475 let text =
6476 body("int f(int a, int b, int *r) { return __builtin_mul_overflow(a, b, r); }\n");
6477 assert!(text.contains("smul_overflow.(i32, i1) %0, %1"), "{text}");
6478
6479 let text = body(
6482 "int f(unsigned a, unsigned b, unsigned *r) { return __builtin_add_overflow(a, b, r); }\n",
6483 );
6484 assert!(text.contains("uadd_overflow.(i32, i1) %0, %1"), "{text}");
6485 }
6486
6487 #[test]
6495 fn an_overflow_check_is_done_at_a_type_that_holds_every_operand() {
6496 let text = body(
6497 "int f(unsigned a, int b, long long *r) { return __builtin_add_overflow(a, b, r); }\n",
6498 );
6499 assert!(text.contains("%3 = zext.i64 %0"), "the unsigned operand keeps its value: {text}");
6500 assert!(text.contains("%4 = sext.i64 %1"), "and so does the signed one: {text}");
6501 assert!(text.contains("sadd_overflow.(i64, i1) %3, %4"), "{text}");
6502
6503 let text = body(
6506 "int f(long long a, long long b, long long *r) { return __builtin_mul_overflow(a, b, r); }\n",
6507 );
6508 assert!(text.contains("smul_overflow.(i64, i1) %0, %1"), "{text}");
6509 assert!(!text.contains("sext."), "{text}");
6510 assert!(!text.contains("zext.i64"), "{text}");
6512 }
6513
6514 #[test]
6522 fn an_overflow_check_writes_the_wrapped_answer_whether_or_not_it_fit() {
6523 let text =
6524 body("int f(int a, int b, char *r) { return __builtin_sub_overflow(a, b, r); }\n");
6525 assert!(text.contains("%3, %4 = ssub_overflow.(i32, i1) %0, %1"), "{text}");
6526 assert!(text.contains("%5 = trunc.i8 %3"), "narrowed to where it goes: {text}");
6527 assert!(text.contains("%6 = sext.i32 %5"), "and back: {text}");
6528 assert!(text.contains("%7 = icmp ne %6, %3"), "which is whether it fit: {text}");
6529 assert!(text.contains("store %5 -> %2"), "the narrowed value is stored either way: {text}");
6530 assert!(text.contains("%8 = or %4, %7"), "and either bit is an overflow: {text}");
6531 }
6532
6533 #[test]
6540 fn a_call_needing_more_than_the_widest_type_still_compiles() {
6541 for name in ["add", "sub", "mul"] {
6542 let source = format!(
6543 "int f(unsigned __int128 a, long long b, __int128 *r) {{\n \
6544 return __builtin_{name}_overflow(a, b, r);\n}}\n"
6545 );
6546 let mut opts = options();
6547 opts.emit = EmitKind::MirFinal;
6548 assert!(!run(&opts, &source).failed(), "{name} was refused or stopped the back end");
6549 }
6550 }
6551
6552 #[test]
6555 fn an_overflow_check_over_something_that_is_not_an_integer_says_so() {
6556 let messages =
6557 errors("int f(double a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
6558 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
6559
6560 let messages =
6561 errors("int f(int a, int b, double *r) { return __builtin_add_overflow(a, b, r); }\n");
6562 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
6563 }
6564
6565 #[test]
6576 fn an_ordered_access_is_ordered_in_the_ir() {
6577 let text = body("int f(int *p) { return __atomic_load_n(p, 0); }\n");
6578 assert!(text.contains("atomic_load.i32 %0, align 4, relaxed"), "{text}");
6579
6580 let text = body("long f(long *p) { return __atomic_load_n(p, 2); }\n");
6581 assert!(text.contains("atomic_load.i64 %0, align 8, acquire"), "{text}");
6582
6583 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
6584 assert!(text.contains("atomic_store %1 -> %0, align 4, release"), "{text}");
6585
6586 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
6587 assert!(text.contains("atomic_store %1 -> %0, align 4, seq_cst"), "{text}");
6588
6589 let text = body("void f(char *p, int v) { __atomic_store_n(p, v, 0); }\n");
6592 assert!(text.contains("trunc.i8 %1"), "{text}");
6593 assert!(text.contains("atomic_store %2 -> %0, align 1, relaxed"), "{text}");
6594 }
6595
6596 #[test]
6605 fn an_ordered_access_is_the_plain_instruction_on_this_machine() {
6606 let text = asm("int f(int *p) { return __atomic_load_n(p, 5); }\n");
6607 assert!(text.contains("movl\t(%rdi), %eax"), "{text}");
6608 assert!(!text.contains("mfence"), "a load needs no barrier here: {text}");
6609
6610 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
6611 assert!(text.contains("movl\t%esi, (%rdi)"), "{text}");
6612 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
6613
6614 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
6615 let (before, after) = text.split_once("mfence").expect("a barrier: {text}");
6616 assert!(before.contains("movl\t%esi, (%rdi)"), "the store comes first: {text}");
6617 assert!(!after.contains("movl"), "and nothing else is between them: {text}");
6618 }
6619
6620 #[test]
6630 fn a_barrier_is_one_instruction_at_the_strongest_ordering_and_none_below_it() {
6631 assert!(asm("void f(void) { __atomic_thread_fence(5); }\n").contains("mfence"));
6632 assert!(asm("void f(void) { __sync_synchronize(); }\n").contains("mfence"));
6633
6634 for weaker in ["1", "2", "3", "4"] {
6635 let source = format!("void f(void) {{ __atomic_thread_fence({weaker}); }}\n");
6636 assert!(!asm(&source).contains("mfence"), "{weaker} costs nothing here");
6637 }
6638 }
6639
6640 #[test]
6650 fn the_three_x86_fences_are_the_barrier_the_strongest_ordering_gives() {
6651 for name in ["__builtin_ia32_sfence", "__builtin_ia32_lfence", "__builtin_ia32_mfence"] {
6652 let source = format!("void f(void) {{ {name}(); }}\n");
6653 assert!(asm(&source).contains("mfence"), "{name} is a barrier");
6654 let text = body(&source);
6655 assert!(text.contains("fence seq_cst"), "{name}: {text}");
6656 }
6657
6658 let result = run(&options(), "void f(void) { __builtin_ia32_sfence(1); }\n");
6659 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
6660 assert!(result.messages[0].contains("too many arguments"), "{:?}", result.messages);
6661 }
6662
6663 #[test]
6669 fn a_compare_and_exchange_is_one_instruction_answering_two_things() {
6670 let text =
6673 body("int f(int *p, int e, int d) { return __sync_val_compare_and_swap(p, e, d); }\n");
6674 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
6675 assert!(text.contains("return %3"), "the value it found: {text}");
6676
6677 let text =
6678 body("int f(int *p, int e, int d) { return __sync_bool_compare_and_swap(p, e, d); }\n");
6679 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
6680 assert!(text.contains("zext.i32 %4"), "whether it happened: {text}");
6681
6682 let text = body(
6685 "int f(int *p, int *e, int d) { return __atomic_compare_exchange_n(p, e, d, 0, 4, 2); }\n",
6686 );
6687 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
6688 assert!(text.contains("%4, %5 = cmpxchg.(i32, i1) %0, %3, %2, align 4, acq_rel"), "{text}");
6689 assert!(text.contains("br_if %5, block2, block1"), "{text}");
6690 assert!(text.contains("store %4 -> %1, align 4"), "{text}");
6691
6692 let text = body(
6695 "int f(int *p, int *e, int *d) { return __atomic_compare_exchange(p, e, d, 0, 5, 5); }\n",
6696 );
6697 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
6698 assert!(text.contains("%4 = load.i32 %2, align 4"), "{text}");
6699 assert!(text.contains("%5, %6 = cmpxchg.(i32, i1) %0, %3, %4, align 4, seq_cst"), "{text}");
6700 }
6701
6702 #[test]
6709 fn a_compare_and_exchange_is_a_locked_instruction_at_the_width_of_the_object() {
6710 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
6711 for (ty, suffix, reg) in widths {
6712 let source = format!(
6713 "int f({ty} *p, {ty} e, {ty} d) {{ return __sync_bool_compare_and_swap(p, e, d); }}\n"
6714 );
6715 let text = asm(&source);
6716 assert!(text.contains("\tlock\n"), "{ty}: {text}");
6717 assert!(text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
6718 assert!(text.contains("sete\t"), "{ty}: {text}");
6719 }
6720 let source =
6721 "int f(long *p, long e, long d) { return __sync_bool_compare_and_swap(p, e, d); }\n";
6722 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
6723
6724 for order in ["0", "2", "3", "4", "5"] {
6728 let call = format!("__atomic_compare_exchange_n(p, e, d, 0, {order}, 0)");
6729 let source = format!("int f(int *p, int *e, int d) {{ return {call}; }}\n");
6730 let text = asm(&source);
6731 assert!(text.contains("cmpxchgl\t"), "{order}: {text}");
6732 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
6733 }
6734 }
6735
6736 #[test]
6748 fn a_read_modify_write_is_one_instruction_and_the_arithmetic_a_name_asks_for() {
6749 let text = body("int f(int *p, int v) { return __atomic_fetch_add(p, v, 5); }\n");
6750 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
6751 assert!(text.contains("return %2"), "the value that was there: {text}");
6752
6753 let text = body("int f(int *p, int v) { return __atomic_add_fetch(p, v, 5); }\n");
6754 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
6755 assert!(text.contains("%3 = add %2, %1"), "and the value afterwards: {text}");
6756
6757 let text = body("int f(int *p, int v) { return __atomic_sub_fetch(p, v, 5); }\n");
6758 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
6759 assert!(text.contains("%3 = sub %2, %1"), "{text}");
6760
6761 let text = body("int f(int *p, int v) { return __sync_fetch_and_sub(p, v); }\n");
6763 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
6764
6765 let text = body("int f(int *p, int v) { return __atomic_exchange_n(p, v, 5); }\n");
6768 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, seq_cst"), "{text}");
6769
6770 let text = body("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
6771 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, acquire"), "{text}");
6772
6773 let text = body("void f(int *p) { __sync_lock_release(p); }\n");
6776 assert!(text.contains("release"), "{text}");
6777 assert!(text.contains("%1 = iconst.i32 0"), "{text}");
6778
6779 let text = body("void f(int *p, int guard) { __sync_lock_release(p, guard); }\n");
6783 assert!(text.contains("%2 = iconst.i32 0"), "{text}");
6784 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
6785
6786 let text = body("int f(int *p, int v) { return __atomic_fetch_and(p, v, 5); }\n");
6789 assert!(text.contains("%2 = atomic_rmw.i32 and %0, %1, align 4, seq_cst"), "{text}");
6790
6791 let text = body("int f(int *p, int v) { return __sync_or_and_fetch(p, v); }\n");
6792 assert!(text.contains("%2 = atomic_rmw.i32 or %0, %1, align 4, seq_cst"), "{text}");
6793 assert!(text.contains("%3 = or %2, %1"), "and the value afterwards: {text}");
6794
6795 let text = body("int f(int *p, int v) { return __atomic_nand_fetch(p, v, 5); }\n");
6798 assert!(text.contains("%2 = atomic_rmw.i32 nand %0, %1, align 4, seq_cst"), "{text}");
6799 assert!(text.contains("%3 = and %2, %1"), "{text}");
6800 assert!(text.contains("%4 = iconst.i32 -1"), "{text}");
6801 assert!(text.contains("%5 = xor %3, %4"), "{text}");
6802 }
6803
6804 #[test]
6815 fn a_bitwise_read_modify_write_is_a_loop_around_the_compare_and_exchange() {
6816 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
6817 for (ty, suffix, reg) in widths {
6818 for (name, call, insn) in [
6819 ("and", "__atomic_fetch_and(p, v, 5)", "and"),
6820 ("or", "__sync_fetch_and_or(p, v)", "or"),
6821 ("xor", "__atomic_xor_fetch(p, v, 5)", "xor"),
6822 ] {
6823 let source = format!("{ty} f({ty} *p, {ty} v) {{ return {call}; }}\n");
6824 let text = asm(&source);
6825 assert!(text.contains("\tlock\n"), "{ty} {name}: {text}");
6826 assert!(
6827 text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")),
6828 "{ty} {name}: {text}"
6829 );
6830 assert!(text.contains(&format!("{insn}{suffix}\t")), "{ty} {name}: {text}");
6831 assert!(!text.contains("\txadd"), "{ty} {name} is not an add: {text}");
6833 assert!(!text.contains("\txchg"), "{ty} {name} is not an exchange: {text}");
6834 }
6835 }
6836 let source = "long f(long *p, long v) { return __atomic_fetch_or(p, v, 5); }\n";
6837 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
6838
6839 let text = asm("int f(int *p, int v) { return __sync_fetch_and_nand(p, v); }\n");
6843 assert!(text.contains("cmpxchgl\t"), "{text}");
6844 assert!(text.contains("andl\t"), "{text}");
6845 assert!(text.contains("notl\t"), "{text}");
6846 }
6847
6848 #[test]
6857 fn an_access_through_a_second_pointer_is_the_same_access_and_one_more() {
6858 let text = body("void f(int *p, int *r) { __atomic_load(p, r, 5); }\n");
6859 assert!(text.contains("%2 = atomic_load.i32 %0, align 4, seq_cst"), "{text}");
6860 assert!(text.contains("store %2 -> %1, align 4"), "and out through the place: {text}");
6861
6862 let text = body("void f(int *p, int *v) { __atomic_store(p, v, 3); }\n");
6863 assert!(text.contains("%2 = load.i32 %1, align 4"), "in through the place: {text}");
6864 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
6865
6866 let text = body("void f(int *p, int *v, int *r) { __atomic_exchange(p, v, r, 5); }\n");
6869 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
6870 assert!(text.contains("%4 = atomic_rmw.i32 xchg %0, %3, align 4, seq_cst"), "{text}");
6871 assert!(text.contains("store %4 -> %2, align 4"), "{text}");
6872 }
6873
6874 #[test]
6885 fn a_flag_is_an_exchange_of_one_byte_and_a_store_of_a_zero_over_the_same_byte() {
6886 for pointer in ["char", "int", "void"] {
6887 let source = format!("int f({pointer} *p) {{ return __atomic_test_and_set(p, 5); }}\n");
6888 let text = body(&source);
6889 assert!(text.contains("%1 = iconst.i8 1"), "{pointer}: {text}");
6890 assert!(
6891 text.contains("%2 = atomic_rmw.i8 xchg %0, %1, align 1, seq_cst"),
6892 "{pointer}: {text}"
6893 );
6894 assert!(text.contains("%4 = icmp ne %2, %3"), "{pointer}: {text}");
6895
6896 let source = format!("void f({pointer} *p) {{ __atomic_clear(p, 3); }}\n");
6897 let text = body(&source);
6898 assert!(text.contains("atomic_store %2 -> %0, align 1, release"), "{pointer}: {text}");
6899 }
6900
6901 let text = asm("int f(int *p) { return __atomic_test_and_set(p, 5); }\n");
6904 assert!(text.contains("xchgb\t%al, (%rdi)"), "{text}");
6905 assert!(text.contains("setne\t"), "{text}");
6906 }
6907
6908 #[test]
6916 fn a_read_modify_write_is_an_exchange_or_a_locked_add_at_the_width_of_the_object() {
6917 let widths = [("char", "b", "%sil"), ("short", "w", "%si"), ("int", "l", "%esi")];
6918 for (ty, suffix, reg) in widths {
6919 let source =
6920 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_fetch_add(p, v, 5); }}\n");
6921 let text = asm(&source);
6922 assert!(text.contains("\tlock\n"), "{ty}: {text}");
6923 assert!(text.contains(&format!("xadd{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
6924
6925 let source =
6926 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_exchange_n(p, v, 5); }}\n");
6927 let text = asm(&source);
6928 assert!(text.contains(&format!("xchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
6929 assert!(!text.contains("\tlock\n"), "an exchange is locked already: {ty}: {text}");
6930 }
6931 let source = "long f(long *p, long v) { return __atomic_fetch_add(p, v, 5); }\n";
6932 assert!(asm(source).contains("xaddq\t%rsi, (%rdi)"), "{}", asm(source));
6933
6934 let source = "int f(int *p, int v) { return __atomic_fetch_sub(p, v, 5); }\n";
6937 let text = asm(source);
6938 assert!(text.contains("negl\t"), "{text}");
6939 assert!(text.contains("xaddl\t"), "{text}");
6940
6941 for order in ["0", "2", "3", "4", "5"] {
6944 let source =
6945 format!("int f(int *p, int v) {{ return __atomic_fetch_add(p, v, {order}); }}\n");
6946 let text = asm(&source);
6947 assert!(text.contains("xaddl\t"), "{order}: {text}");
6948 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
6949 }
6950
6951 let text = asm("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
6955 assert!(text.contains("xchgl\t%esi, (%rdi)"), "{text}");
6956 let text = asm("void f(int *p) { __sync_lock_release(p); }\n");
6963 assert!(text.contains("xorl\t%eax, %eax"), "{text}");
6964 assert!(text.contains("movl\t%eax, (%rdi)"), "{text}");
6965 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
6966 }
6967
6968 #[test]
6980 fn the_lock_free_questions_are_answered_as_constants() {
6981 for size in ["1", "2", "4", "8"] {
6982 let source =
6983 format!("int f(void) {{ return __atomic_always_lock_free({size}, 0); }}\n");
6984 let text = asm(&source);
6985 assert!(text.contains("movb\t$1, %al"), "{size} bytes is lock free: {text}");
6986 assert!(!text.contains("call"), "and is not a call: {text}");
6987 }
6988 for size in ["3", "16", "sizeof(long double)"] {
6989 let source = format!("int f(void) {{ return __atomic_is_lock_free({size}, 0); }}\n");
6990 let text = asm(&source);
6991 assert!(text.contains("movb\t$0, %al"), "{size} bytes is not: {text}");
6992 assert!(!text.contains("call"), "and is not a call either: {text}");
6993 }
6994
6995 let text = asm("int f(int n) { return __atomic_is_lock_free(n, 0); }\n");
6999 assert!(text.contains("movb\t$0, %al"), "a size nobody knows is not lock free: {text}");
7000 let text = asm("int f(int *p) { return __atomic_always_lock_free(8, p); }\n");
7001 assert!(text.contains("movb\t$0, %al"), "eight bytes at four is not: {text}");
7002 let text = asm("int f(long *p) { return __atomic_always_lock_free(8, p); }\n");
7003 assert!(text.contains("movb\t$1, %al"), "and at eight it is: {text}");
7004 }
7005
7006 #[test]
7018 fn a_memory_order_an_operation_cannot_carry_is_read_as_the_strongest() {
7019 let mut opts = options();
7020 opts.emit = EmitKind::Ir;
7021
7022 let acquire_store = run(&opts, "void f(int *p, int v) { __atomic_store_n(p, v, 2); }\n");
7023 assert!(acquire_store.text().contains("seq_cst"), "{:?}", acquire_store.text());
7024 assert!(acquire_store.messages[0].contains("[W0333]"), "{:?}", acquire_store.messages);
7025
7026 let nonsense = run(&opts, "int f(int *p) { return __atomic_load_n(p, 99); }\n");
7027 assert!(nonsense.text().contains("seq_cst"), "{:?}", nonsense.text());
7028 assert!(nonsense.messages[0].contains("[W0333]"), "{:?}", nonsense.messages);
7029
7030 let computed = run(&opts, "int f(int *p, int n) { return __atomic_load_n(p, n); }\n");
7031 assert!(computed.text().contains("seq_cst"), "{:?}", computed.text());
7032 assert_eq!(computed.messages, Vec::<String>::new(), "a computed order is not a mistake");
7033 }
7034
7035 #[test]
7047 fn a_conversion_between_a_float_and_the_widest_unsigned_integer_is_written_without_a_branch() {
7048 let text = asm("double f(unsigned long long x) { return (double)x; }\n");
7049 assert!(text.contains("cvtsi2sdq"), "the signed conversion is what runs: {text}");
7050 assert!(text.contains("shrq"), "with the value halved first: {text}");
7051 assert!(text.contains("addsd"), "and doubled after: {text}");
7052 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
7053
7054 let text = asm("unsigned long long f(double d) { return (unsigned long long)d; }\n");
7055 assert!(text.contains("cvttsd2siq"), "the signed conversion is what runs: {text}");
7056 assert!(text.contains("subsd"), "with half the range taken off first: {text}");
7057 assert!(text.contains("shlq\t$63"), "and the top bit put back: {text}");
7058 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
7059 }
7060
7061 #[test]
7072 fn a_plain_name_the_program_took_is_the_programs_own_function() {
7073 let taken = concat!(
7074 "static long long llabs(long long b) { return 7; }\n",
7075 "long long f(long long x) { return llabs(x); }\n",
7076 );
7077 assert!(ir(taken).contains("call @llabs"), "a static definition is the program's own");
7078
7079 let retyped = concat!("int llabs(int b);\n", "int f(int x) { return llabs(x); }\n",);
7080 assert!(ir(retyped).contains("call @llabs"), "another type is another function");
7081
7082 let plain = concat!(
7083 "long long llabs(long long b);\n",
7084 "long long f(long long x) { return llabs(x); }\n",
7085 );
7086 let mut opts = options();
7087 opts.emit = EmitKind::Ir;
7088 assert!(!run(&opts, plain).text().contains("call @llabs"), "the library's by default");
7089
7090 opts.builtins = false;
7091 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin");
7092
7093 opts.builtins = true;
7094 opts.no_builtin = vec!["llabs".to_owned()];
7095 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin-llabs");
7096 let one = "long labs(long b);\nlong f(long x) { return labs(x); }\n";
7097 assert!(!run(&opts, one).text().contains("call @labs"), "one name and not the family");
7098
7099 opts.no_builtin = Vec::new();
7102 opts.builtins = false;
7103 let prefixed = "long long f(long long x) { return __builtin_llabs(x); }\n";
7104 assert!(!run(&opts, prefixed).text().contains("call @llabs"), "the prefix is a promise");
7105 }
7106
7107 #[test]
7120 fn the_hint_builtins_are_their_first_argument_and_the_hint_leaves_no_trace() {
7121 let text = ir(concat!(
7122 "long a = __builtin_expect(7, 1);\n",
7123 "long b = __builtin_expect_with_probability(9, 1, 0.9);\n",
7124 "unsigned long c = sizeof(__builtin_expect((char)1, 1));\n",
7125 ));
7126 assert!(text.contains("global @a : i64 = 7,"), "{text}");
7127 assert!(text.contains("global @b : i64 = 9,"), "{text}");
7128 assert!(text.contains("global @c : i64 = 8,"), "{text}");
7129 assert!(!text.contains("__builtin_expect"), "it is not a call to anything:\n{text}");
7130
7131 let text = body("long f(char c) { return __builtin_expect(c, 1); }\n");
7134 assert!(text.contains("sext"), "{text}");
7135
7136 let one = "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 1\n %2 = sext.i64 %1\n return %0\n";
7140 assert_eq!(body("int f(void) { int i = 0; __builtin_expect(1, i++); return i; }\n"), one);
7141 let source = "int g(void) { int i = 0; __builtin_expect_with_probability(1, i++, 0.5); return i; }\n";
7142 assert_eq!(body(source), one);
7143
7144 let kept = body("int f(int n) { int i = 0; __builtin_expect(n, i++); return i; }\n");
7149 assert!(kept.contains("add.nsw"), "the hint still runs: {kept}");
7150 assert!(kept.ends_with("return %3\n"), "and the answer is what it left behind: {kept}");
7151 let both = "int g(int n) { int i = 0; __builtin_expect_with_probability(n, i++, 0.5); return i; }\n";
7152 assert!(body(both).contains("add.nsw"), "and so does the one with three arguments");
7153 }
7154
7155 #[test]
7167 fn a_promise_that_control_does_not_arrive_writes_no_instruction() {
7168 let promised = "int f(int x) { if (x) return 1; __builtin_unreachable(); }\n";
7169 let text = ir(promised);
7170 assert!(text.contains(" unreachable_hint\n"), "{text}");
7171 assert!(!text.contains("call"), "it is not a call to anything:\n{text}");
7172
7173 let after = body("int g(int x) { __builtin_unreachable(); return x; }\n");
7177 assert!(after.contains("return"), "{after}");
7178
7179 let text = asm(promised);
7182 let mine = text.split_once("\nf:\n").expect("a definition").1;
7183 let mine = mine.split_once("\t.size").expect("a definition").0;
7184 let plain = asm("int f(int x) { if (x) return 1; }\n");
7185 let plain = plain.split_once("\nf:\n").expect("a definition").1;
7186 let plain = plain.split_once("\t.size").expect("a definition").0;
7187 assert_eq!(mine, plain);
7188 let last = mine.lines().rfind(|line| !line.trim_start().starts_with('.'));
7191 assert_eq!(last.map(str::trim), Some("ret"), "{mine}");
7192 assert!(!mine.contains("ud2"), "{mine}");
7193 }
7194
7195 #[test]
7202 fn a_library_builtin_is_diagnosed_under_the_name_the_program_wrote() {
7203 let mut opts = options();
7204 opts.emit = EmitKind::Ir;
7205 let messages = run(&opts, "void f(void) { __builtin_abort(1); }\n").messages;
7206 assert!(
7207 messages.iter().any(|m| m.contains("__builtin_abort")),
7208 "expected the written name in {messages:?}"
7209 );
7210 }
7211
7212 #[test]
7220 fn a_builtin_nothing_lowers_is_refused_by_name() {
7221 let mut opts = options();
7222 opts.emit = EmitKind::Ir;
7223 let builtin = "__atomic_signal_fence";
7224 let source = format!("int counter;\nint f(void) {{ return ({builtin}(5), 0); }}\n");
7225 let messages = run(&opts, &source).messages;
7226 let named = messages.iter().any(|m| m.contains(builtin) && m.contains("E0686"));
7227 assert!(named, "expected {builtin} to be refused by name in {messages:?}");
7228 }
7229
7230 #[test]
7239 fn what_is_refused_is_the_call_and_not_the_name() {
7240 let text = ir(concat!(
7241 "void __atomic_signal_fence(int order) { (void)order; }\n",
7242 "void f(void) { __atomic_signal_fence(5); }\n",
7243 ));
7244 assert!(text.contains("call @__atomic_signal_fence"), "{text}");
7245 }
7246
7247 #[test]
7256 fn the_object_size_of_an_address_is_what_the_layout_leaves_in_front_of_it() {
7257 let text = ir(concat!(
7258 "struct S { char a[8]; int n; char b[12]; };\n",
7259 "char g[32];\n",
7260 "struct S gs;\n",
7261 "unsigned long whole = __builtin_object_size(g, 0);\n",
7262 "unsigned long moved = __builtin_object_size(g + 4, 0);\n",
7263 "unsigned long back = __builtin_object_size(g + 30 - 2, 0);\n",
7264 "unsigned long outer = __builtin_object_size(gs.a, 0);\n",
7265 "unsigned long inner = __builtin_object_size(gs.a, 1);\n",
7266 "unsigned long scalar = __builtin_object_size(&gs.n, 1);\n",
7267 "unsigned long after = __builtin_object_size(&gs.n, 0);\n",
7268 "unsigned long into = __builtin_object_size(&gs.b[2], 1);\n",
7269 "unsigned long text = __builtin_object_size(\"hello\", 0);\n",
7270 "unsigned long dyn = __builtin_dynamic_object_size(gs.b, 1);\n",
7271 ));
7272 for (name, size) in [
7273 ("whole", 32),
7274 ("moved", 28),
7275 ("back", 4),
7276 ("outer", 24),
7277 ("inner", 8),
7278 ("scalar", 4),
7279 ("after", 16),
7280 ("into", 10),
7281 ("text", 6),
7282 ("dyn", 12),
7283 ] {
7284 let said = format!("global @{name} : i64 = {size},");
7285 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
7286 }
7287 }
7288
7289 #[test]
7297 fn the_object_behind_an_address_can_be_one_with_automatic_storage() {
7298 let text = body(concat!(
7299 "struct S { char a[8]; int n; char b[12]; };\n",
7300 "unsigned long f(void) {\n",
7301 " char loc[20];\n",
7302 " struct S ls;\n",
7303 " return __builtin_object_size(loc + 3, 0) + __builtin_object_size(ls.b + 2, 1);\n",
7304 "}\n",
7305 ));
7306 assert!(text.contains("iconst.i64 17"), "twenty bytes with three used: {text}");
7307 assert!(text.contains("iconst.i64 10"), "twelve bytes with two used: {text}");
7308 }
7309
7310 #[test]
7320 fn an_address_with_no_object_in_sight_answers_at_the_end_of_the_range_its_kind_asks_for() {
7321 let text = ir(concat!(
7322 "struct T { int n; char f[]; };\n",
7323 "extern char *p;\n",
7324 "extern struct T *t;\n",
7325 "unsigned long largest = __builtin_object_size(p, 0);\n",
7326 "unsigned long nearest = __builtin_object_size(p, 1);\n",
7327 "unsigned long least = __builtin_object_size(p, 2);\n",
7328 "unsigned long tight = __builtin_object_size(p, 3);\n",
7329 "unsigned long flex = __builtin_object_size(t->f, 1);\n",
7330 "int says = __builtin_object_size(p, 0) == (unsigned long)-1;\n",
7331 ));
7332 for name in ["largest", "nearest", "flex"] {
7333 let said = format!("global @{name} : i64 = -1,");
7337 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
7338 }
7339 for name in ["least", "tight"] {
7340 let said = format!("global @{name} : i64 = 0,");
7341 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
7342 }
7343 assert!(text.contains("global @says : i32 = 1,"), "{text}");
7344 }
7345
7346 #[test]
7353 fn the_address_an_object_size_is_asked_about_is_not_evaluated() {
7354 let text = body(concat!(
7355 "extern char *side(void);\n",
7356 "unsigned long f(void) { return __builtin_object_size(side(), 0); }\n",
7357 ));
7358 assert!(!text.contains("call"), "nothing is called: {text}");
7359 }
7360
7361 #[test]
7366 fn a_kind_that_is_not_one_of_the_four_is_refused() {
7367 for source in [
7368 "extern char *p;\nextern int k;\nunsigned long f(void) ".to_owned()
7369 + "{ return __builtin_object_size(p, k); }\n",
7370 "extern char *p;\nunsigned long f(void) { return __builtin_object_size(p, 4); }\n"
7371 .to_owned(),
7372 "extern char *p;\nunsigned long f(void) ".to_owned()
7373 + "{ return __builtin_dynamic_object_size(p, -1); }\n",
7374 ] {
7375 let messages = errors(&source);
7376 let named = messages.iter().any(|m| m.contains("E0709") && m.contains("0 to 3"));
7377 assert!(named, "expected a complaint about the kind in {messages:?}");
7378 }
7379 }
7380
7381 #[test]
7387 fn the_pair_that_saves_a_place_lowers_to_the_two_markers() {
7388 let text = ir(concat!(
7389 "void *buf[5];\n",
7390 "int f(void) {\n",
7391 " if (__builtin_setjmp(buf)) return 2;\n",
7392 " return 1;\n",
7393 "}\n",
7394 "void g(void) { __builtin_longjmp(buf, 1); }\n",
7395 ));
7396 assert!(text.contains("= setjmp_marker.i32 %0\n"), "the save answers a value: {text}");
7397 assert!(text.contains(" longjmp_marker %0\n"), "the restore answers nothing: {text}");
7398 assert!(!text.contains("call @"), "neither of them is a call: {text}");
7399 }
7400
7401 #[test]
7409 fn a_local_of_a_function_that_saves_a_place_gets_a_slot() {
7410 let text = ir(concat!(
7411 "void *buf[5];\n",
7412 "int f(int x) { int a = 0; if (__builtin_setjmp(buf)) return a; a = 1; return x; }\n",
7413 "int g(int x) { int a = 0; if (x) return a; a = 1; return x; }\n",
7414 ));
7415 let (saves, plain) = text.split_once("func @g").expect("both functions");
7416 assert_eq!(saves.matches("= alloca").count(), 2, "the parameter and the local: {text}");
7417 assert!(saves.contains("store %9 -> %2"), "the local is written through: {text}");
7418 assert!(!plain.contains("alloca"), "nothing in the plain one needs a slot: {text}");
7419 }
7420
7421 #[test]
7430 fn a_value_live_across_sigsetjmp_keeps_its_spill_slot() {
7431 each_spill_slot_written_once(&across("int __sigsetjmp(sigjmp_buf, int);\n", "__sigsetjmp"));
7432 }
7433
7434 #[test]
7437 fn a_value_live_across_a_returns_twice_call_keeps_its_spill_slot() {
7438 let declared = "int save_here(sigjmp_buf, int) __attribute__((__returns_twice__));\n";
7439 each_spill_slot_written_once(&across(declared, "save_here"));
7440 }
7441
7442 #[test]
7450 fn a_value_live_across_setjmp_shares_its_slot_with_nothing_in_the_first_arm() {
7451 let source = concat!(
7452 "typedef long jmp_buf[25];\n",
7453 "int _setjmp(jmp_buf);\n",
7454 "void longjmp(jmp_buf, int) __attribute__((noreturn));\n",
7455 "int printf(const char *, ...);\n",
7456 "static jmp_buf *stack;\n",
7457 "static volatile long long sink;\n",
7458 "static int cells[64];\n",
7459 "static volatile int seed_in = 3;\n",
7460 "static void work(void) { longjmp(*stack, 1); }\n",
7461 "int main(void) {\n",
7462 " int seed = seed_in;\n",
7463 " int v = seed * 2;\n",
7464 " jmp_buf buf;\n",
7465 " if (_setjmp(buf) == 0) {\n",
7466 " stack = &buf;\n",
7467 " int *p = &cells[seed + 3];\n",
7468 " int a = v + 8;\n",
7469 " int b = seed * 2005;\n",
7470 " int *q = &cells[v + 1];\n",
7471 " work();\n",
7472 " sink = *p + a + b + *q;\n",
7473 " } else {\n",
7474 " printf(\"%d\\n\", v);\n",
7475 " }\n",
7476 " return 0;\n",
7477 "}\n",
7478 );
7479 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
7480 let mut opts = options();
7481 opts.emit = EmitKind::Asm;
7482 opts.opt_level = level;
7483 let result = run(&opts, source);
7484 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
7485 let text = result.text();
7486 let body = text.split_once("\nmain:\n").expect("the function").1;
7487 let lines: Vec<&str> = body.lines().map(str::trim).collect();
7488 let save = lines.iter().position(|l| *l == "call\t_setjmp").expect("the save");
7489 let jump = lines[save..]
7490 .iter()
7491 .position(|l| *l == "call\twork" || *l == "call\tlongjmp")
7492 .map(|at| save + at)
7493 .unwrap_or_else(|| panic!("the call that jumps back at {level:?}:\n{text}"));
7494 let printf = lines.iter().position(|l| *l == "call\tprintf").expect("the handler");
7495 let slot = lines[jump..printf]
7497 .iter()
7498 .rev()
7499 .find_map(|l| l.strip_suffix(", %rsi").or_else(|| l.strip_suffix(", %esi")))
7500 .and_then(|l| l.split_once('\t'))
7501 .map(|(_, place)| place)
7502 .filter(|place| place.ends_with("(%rsp)") || place.ends_with("(%rbp)"))
7503 .unwrap_or_else(|| panic!("the handler reads v from a slot at {level:?}:\n{text}"));
7504 let writes = |l: &&str| {
7505 !l.starts_with("cmp") && !l.starts_with("test") && l.ends_with(&format!(", {slot}"))
7506 };
7507 assert!(
7508 lines[..save].iter().any(writes),
7509 "{slot} is written before the save at {level:?}:\n{text}"
7510 );
7511 assert!(
7512 !lines[save..jump].iter().any(writes),
7513 "{slot} is written again before the jump at {level:?}:\n{text}"
7514 );
7515 }
7516 }
7517
7518 fn across(declared: &str, save: &str) -> String {
7521 asm(&format!(
7522 "typedef long sigjmp_buf[25];\n{declared}int id(int);\nvoid thrower(int);\n\
7523 int work(int n) {{\n\
7524 \x20 int v0 = id(n), v1 = id(n + 1), v2 = id(n + 2), v3 = id(n + 3), v4 = id(n + 4);\n\
7525 \x20 sigjmp_buf b;\n\
7526 \x20 if ({save}(b, 0) == 0) {{\n\
7527 \x20 int w0 = id(v0 + v1), w1 = id(v1 + v2), w2 = id(v2 + v3);\n\
7528 \x20 int w3 = id(v3 + v4), w4 = id(v4 + v0);\n\
7529 \x20 thrower(n);\n\
7530 \x20 return w0 ^ w1 ^ w2 ^ w3 ^ w4;\n\
7531 \x20 }}\n\
7532 \x20 return v0 + v1 + v2 + v3 + v4;\n\
7533 }}\n"
7534 ))
7535 }
7536
7537 fn each_spill_slot_written_once(text: &str) {
7539 let mut stored = Vec::new();
7540 for line in text.lines().map(str::trim) {
7541 let Some(operands) = line.strip_prefix("movq\t%") else { continue };
7542 if let Some((_, place)) = operands.split_once(", ") {
7543 if place.ends_with("(%rsp)") {
7544 assert!(!stored.contains(&place), "{place} is written twice:\n{text}");
7545 stored.push(place);
7546 }
7547 }
7548 }
7549 assert!(!stored.is_empty(), "something should have been spilled:\n{text}");
7550 }
7551
7552 #[test]
7561 fn the_save_writes_four_words_and_carries_on_in_a_new_block() {
7562 let text =
7563 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
7564 let body = text.split_once("\nf:\n").expect("the function").1;
7565 assert!(body.contains("\tmovq\t%rsp, %rbp\n"), "a frame pointer whatever: {text}");
7566 assert!(body.contains("\tsubq\t$8, %rsp\n"), "no red zone: {text}");
7567 assert!(body.contains("\tmovq\t%rbp, (%rax)\n"), "the frame pointer: {text}");
7568 assert!(body.contains("\tmovq\t%rsp, 16(%rax)\n"), "the stack pointer: {text}");
7569 assert!(body.contains("\tleaq\t.Lf_1(%rip), %rcx\n"), "where to come back to: {text}");
7570 assert!(body.contains("\tmovq\t%rcx, 8(%rax)\n"), "and that goes in the buffer: {text}");
7571 let back = body.split_once(".Lf_1:\n").expect("the block control comes back to").1;
7572 assert!(back.starts_with("\tmovq\t(%rsp), %rax\n"), "the answer is read back: {text}");
7573 }
7574
7575 #[test]
7583 fn a_save_destroys_every_register_the_allocator_hands_out() {
7584 let text =
7585 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
7586 for reg in ["%rbx", "%r12", "%r13", "%r14", "%r15"] {
7587 assert!(text.contains(&format!("\tpushq\t{reg}\n")), "{reg} is saved: {text}");
7588 assert!(text.contains(&format!("\tpopq\t{reg}\n")), "{reg} is restored: {text}");
7589 }
7590 }
7591
7592 #[test]
7599 fn the_restore_puts_the_frame_back_before_it_jumps() {
7600 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
7601 let mut opts = options();
7602 opts.emit = EmitKind::Asm;
7603 opts.opt_level = level;
7604 let source = "void *buf[5];\nvoid g(void) { __builtin_longjmp(buf, 1); }\n";
7605 let result = run(&opts, source);
7606 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
7607 let text = result.text().to_owned();
7608 let jump = text.find("\tjmp\t*%").unwrap_or_else(|| panic!("an indirect jump: {text}"));
7609 let stack = text.find(", %rsp\n").unwrap_or_else(|| panic!("the stack back: {text}"));
7610 let frame = text.find(", %rbp\n").unwrap_or_else(|| panic!("the frame back: {text}"));
7611 assert!(stack < jump, "the stack goes back first at {level:?}: {text}");
7612 assert!(frame < jump, "and so does the frame at {level:?}: {text}");
7613 }
7614 }
7615
7616 #[test]
7622 fn a_longjmp_whose_second_argument_is_not_one_is_turned_down() {
7623 for source in [
7624 "void *buf[5];\nvoid f(void) { __builtin_longjmp(buf, 0); }\n",
7625 "void *buf[5];\nextern int v;\nvoid f(void) { __builtin_longjmp(buf, v); }\n",
7626 ] {
7627 let messages = errors(source);
7628 let named = messages.iter().any(|m| m.contains("E0710"));
7629 assert!(named, "expected a complaint about the value in {messages:?}");
7630 }
7631 }
7632
7633 #[test]
7638 fn a_static_function_nothing_refers_to_is_not_emitted() {
7639 let text = ir("static int dropped(void) { return 1; }\n\
7640 static int kept(void) { return 2; }\n\
7641 int main(void) { return kept(); }\n");
7642 assert!(text.contains("func @kept"), "{text}");
7643 assert!(!text.contains("dropped"), "{text}");
7644 }
7645
7646 #[test]
7652 fn two_static_functions_that_only_call_each_other_are_both_dropped() {
7653 let text = ir("static int ping(void);\n\
7654 static int pong(void) { return ping(); }\n\
7655 static int ping(void) { return pong(); }\n\
7656 int main(void) { return 0; }\n");
7657 assert!(!text.contains("ping"), "{text}");
7658 assert!(!text.contains("pong"), "{text}");
7659 }
7660
7661 #[test]
7667 fn naming_a_static_function_anywhere_keeps_it() {
7668 let text = ir("static int by_address(void) { return 1; }\n\
7669 static int in_an_image(void) { return 2; }\n\
7670 static int deeper(void) { return 3; }\n\
7671 static int reaches_deeper(void) { return deeper(); }\n\
7672 static int (*table[1])(void) = {in_an_image};\n\
7673 int main(void) {\n\
7674 int (*p)(void) = by_address;\n\
7675 return p() + table[0]() + reaches_deeper();\n\
7676 }\n");
7677 for kept in ["by_address", "in_an_image", "deeper", "reaches_deeper"] {
7678 assert!(text.contains(&format!("func @{kept}")), "expected {kept} in:\n{text}");
7679 }
7680 }
7681
7682 #[test]
7688 fn an_attribute_keeps_a_static_function_nothing_refers_to() {
7689 for attribute in ["used", "retain", "constructor", "destructor", "__used__"] {
7690 let source = format!(
7691 "__attribute__(({attribute})) static int kept(void) {{ return 1; }}\n\
7692 int main(void) {{ return 0; }}\n"
7693 );
7694 let text = ir(&source);
7695 assert!(text.contains("func @kept"), "for {attribute}:\n{text}");
7696 }
7697 }
7698
7699 #[test]
7702 fn nonnull_is_answered_yes_and_taken_with_or_without_operands() {
7703 let text = ir("#if !__has_attribute(nonnull) || !__has_attribute(__nonnull__)\n\
7704 #error nonnull\n\
7705 #endif\n\
7706 __attribute__((nonnull)) int first(char *p);\n\
7707 int both(char *a, int n, char *b) __attribute__((__nonnull__(1, 3)));\n\
7708 int both(char *a, int n, char *b) { return first(a) + n + (b != 0); }\n");
7709 assert!(text.contains("func @both"), "{text}");
7710 }
7711
7712 #[test]
7715 fn a_function_anything_could_call_is_emitted_without_being_called() {
7716 let text =
7717 ir("int nobody_here_calls_it(void) { return 1; }\nint main(void) { return 0; }\n");
7718 assert!(text.contains("func @nobody_here_calls_it"), "{text}");
7719 }
7720
7721 #[test]
7728 fn a_classification_c_has_an_operator_for_is_that_operator() {
7729 for (builtin, operator) in [
7730 ("__builtin_isgreater", "binary >"),
7731 ("__builtin_isgreaterequal", "binary >="),
7732 ("__builtin_isless", "binary <"),
7733 ("__builtin_islessequal", "binary <="),
7734 ] {
7735 let source = format!("int f(double x, double y) {{ return {builtin}(x, y); }}\n");
7736 let text = tast(&source);
7737 assert!(text.contains(&format!("{operator} : int")), "for {builtin}:\n{text}");
7738 }
7739 }
7740
7741 #[test]
7750 fn the_classification_builtins_are_comparisons_and_not_calls() {
7751 let text = body("int f(double x, double y) { return __builtin_isunordered(x, y); }\n");
7752 assert_eq!(
7753 text,
7754 "block0(%0: f64, %1: f64):\n %2 = fcmp uno %0, %1\n %3 = zext.i32 \
7755 %2\n return %3\n"
7756 );
7757
7758 let text = body("int f(double x, double y) { return __builtin_islessgreater(x, y); }\n");
7760 assert!(text.contains("fcmp one %0, %1"), "{text}");
7761
7762 let text = body("int f(double x) { return __builtin_isnan(x); }\n");
7763 assert!(text.contains("fcmp uno %0, %0"), "{text}");
7764
7765 let text = body("int f(double x) { return __builtin_isinf(x); }\n");
7766 assert!(text.contains("fconst.f64 0x7ff0000000000000"), "{text}");
7767 assert!(text.contains("fconst.f64 0xfff0000000000000"), "{text}");
7768 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
7769 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
7770 assert!(text.contains("%5 = or %3, %4"), "{text}");
7771
7772 let text = body("int f(double x) { return __builtin_isfinite(x); }\n");
7775 assert!(text.contains("%3 = fcmp olt %2, %0"), "{text}");
7776 assert!(text.contains("%4 = fcmp olt %0, %1"), "{text}");
7777 assert!(text.contains("%5 = and %3, %4"), "{text}");
7778
7779 let text = body("int f(double x) { return __builtin_signbit(x); }\n");
7780 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
7781 assert!(text.contains("icmp slt %1, %2"), "{text}");
7782
7783 let text = body("int f(long double x) { return __builtin_signbitl(x); }\n");
7787 assert!(text.contains("load.i16"), "{text}");
7788 assert!(text.contains("icmp slt"), "{text}");
7789 assert!(!text.contains("i80"), "{text}");
7790
7791 let text = body("double g(void);\nint f(void) { return __builtin_isnan(g()); }\n");
7794 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
7795 }
7796
7797 #[test]
7804 fn a_classification_spelling_that_names_a_width_converts_before_it_asks() {
7805 let text = ir(concat!(
7806 "int a = __builtin_isinff(1e300);\n",
7807 "int b = __builtin_isinf(1e300);\n",
7808 "int c = __builtin_isnan(0.0);\n",
7812 "int d = __builtin_signbit(-0.0);\n",
7813 "int e = __builtin_islessgreater(1.0, 2.0);\n",
7814 ));
7815 assert!(text.contains("global @a : i32 = 1,"), "{text}");
7816 assert!(text.contains("global @b : i32 = 0,"), "{text}");
7817 assert!(text.contains("global @c : i32 = 0,"), "{text}");
7818 assert!(text.contains("global @d : i32 = 1,"), "{text}");
7819 assert!(text.contains("global @e : i32 = 1,"), "{text}");
7820 }
7821
7822 #[test]
7824 fn a_classification_builtin_refuses_an_argument_that_is_not_floating_point() {
7825 let mut opts = options();
7826 opts.emit = EmitKind::Ir;
7827 let source = concat!(
7828 "int a(int x) { return __builtin_isnan(x); }\n",
7829 "int b(int x, int y) { return __builtin_isunordered(x, y); }\n",
7830 "int c(double x) { return __builtin_isnan(x, x); }\n",
7831 );
7832 let messages = run(&opts, source).messages;
7833 assert_eq!(
7834 messages,
7835 [
7836 "/main.c:1:23: error: non-floating-point argument in call to function \
7837 '__builtin_isnan' [E0685]",
7838 "/main.c:2:30: error: non-floating-point arguments in call to function \
7839 '__builtin_isunordered' [E0685]",
7840 "/main.c:3:26: error: too many arguments to function '__builtin_isnan' [E0511]",
7841 ]
7842 );
7843 }
7844
7845 #[test]
7854 fn the_last_three_classification_builtins_are_comparisons_and_not_calls() {
7855 let text = body("int f(double x) { return __builtin_isnormal(x); }\n");
7856 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
7860 assert!(text.contains("%2 = iconst.i64 9223372036854775807"), "{text}");
7861 assert!(text.contains("%3 = and %1, %2"), "{text}");
7862 assert!(text.contains("%4 = iconst.i64 4503599627370496"), "{text}");
7863 assert!(text.contains("%5 = iconst.i64 9218868437227405312"), "{text}");
7864 assert!(text.contains("%6 = icmp uge %3, %4"), "{text}");
7865 assert!(text.contains("%7 = icmp ult %3, %5"), "{text}");
7866 assert!(text.contains("%8 = and %6, %7"), "{text}");
7867
7868 let text = body("int f(long double x) { return __builtin_isnormal(x); }\n");
7873 assert!(text.contains("fconst.f80 0x18000000000000000"), "{text}");
7874 assert!(text.contains("fconst.f80 0x7fff8000000000000000"), "{text}");
7875 assert!(text.contains("fcmp oge"), "{text}");
7876 assert!(text.contains("fcmp olt"), "{text}");
7877
7878 let text = body("int f(double x) { return __builtin_isinf_sign(x); }\n");
7879 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
7880 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
7881 assert!(text.contains("%7 = sub %5, %6"), "{text}");
7882
7883 let text = body("int f(double x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n");
7884 assert!(text.contains("fcmp uno %0, %0"), "{text}");
7885 assert!(text.contains("fcmp oeq %0, %6"), "{text}");
7886 assert_eq!(text.matches(" = zext.i32 ").count(), 4, "{text}");
7890 assert_eq!(text.matches(" = xor ").count(), 4, "{text}");
7891 assert!(!text.contains("call"), "{text}");
7892
7893 let text = body(concat!(
7896 "double g(void);\n",
7897 "int f(void) { return __builtin_fpclassify(0, 1, 2, 3, 4, g()); }\n",
7898 ));
7899 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
7900 }
7901
7902 #[test]
7909 fn the_last_three_classification_builtins_fold_where_their_operand_is_a_constant() {
7910 let text = ir(concat!(
7911 "int a = __builtin_isnormal(1.0);\n",
7912 "int b = __builtin_isnormal(0.0);\n",
7913 "int c = __builtin_isnormal(1.0 / 0.0);\n",
7914 "int d = __builtin_isinf_sign(-1.0 / 0.0);\n",
7915 "int e = __builtin_isinf_sign(1.0);\n",
7916 "int g = __builtin_fpclassify(0, 1, 2, 3, 4, 0.0);\n",
7917 "int h = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0);\n",
7918 "int i = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0 / 0.0);\n",
7919 ));
7920 assert!(text.contains("global @a : i32 = 1,"), "{text}");
7921 assert!(text.contains("global @b : i32 = 0,"), "{text}");
7922 assert!(text.contains("global @c : i32 = 0,"), "{text}");
7923 assert!(text.contains("global @d : i32 = -1,"), "{text}");
7924 assert!(text.contains("global @e : i32 = 0,"), "{text}");
7925 assert!(text.contains("global @g : i32 = 4,"), "{text}");
7926 assert!(text.contains("global @h : i32 = 2,"), "{text}");
7927 assert!(text.contains("global @i : i32 = 1,"), "{text}");
7928 }
7929
7930 #[test]
7936 fn fpclassify_refuses_an_answer_that_is_not_an_integer_constant() {
7937 let mut opts = options();
7938 opts.emit = EmitKind::Ir;
7939 let source = concat!(
7940 "int a(double x, int n) { return __builtin_fpclassify(0, 1, n, 3, 4, x); }\n",
7941 "int b(double x) { return __builtin_fpclassify(0, 1, 2, 3, x); }\n",
7942 "int c(int x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n",
7943 );
7944 let messages = run(&opts, source).messages;
7945 assert_eq!(
7946 messages,
7947 [
7948 "/main.c:1:60: error: non-const integer argument 3 in call to function \
7949 '__builtin_fpclassify' [E0687]",
7950 "/main.c:2:26: error: too few arguments to function '__builtin_fpclassify' \
7951 [E0511]",
7952 "/main.c:3:23: error: non-floating-point argument in call to function \
7953 '__builtin_fpclassify' [E0685]",
7954 ]
7955 );
7956 }
7957
7958 #[test]
7966 fn a_builtin_whose_answer_is_a_constant_is_one_and_not_a_call() {
7967 let text = ir(concat!(
7968 "double a = __builtin_inf();\n",
7969 "float b = __builtin_huge_valf();\n",
7970 "long double c = __builtin_infl();\n",
7971 "double d = __builtin_huge_val();\n",
7972 ));
7973 assert!(text.contains("global @a : f64 = 0x7ff0000000000000,"), "{text}");
7974 assert!(text.contains("global @b : f32 = 0x7f800000,"), "{text}");
7975 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
7976 assert!(text.contains("global @d : f64 = 0x7ff0000000000000,"), "{text}");
7977 assert!(!text.contains("call"), "{text}");
7978 }
7979
7980 #[test]
7989 fn a_nan_is_written_with_the_payload_the_program_asked_for() {
7990 let text = ir(concat!(
7991 "double a = __builtin_nan(\"\");\n",
7992 "double b = __builtin_nan(\"0x1\");\n",
7993 "double c = __builtin_nan(\"010\");\n",
7995 "double d = __builtin_nans(\"\");\n",
7996 "double e = __builtin_nans(\"0x1\");\n",
7997 "float f = __builtin_nanf(\"0x1\");\n",
7998 "float g = __builtin_nansf(\"\");\n",
7999 "long double h = __builtin_nansl(\"\");\n",
8000 ));
8001 assert!(text.contains("global @a : f64 = 0x7ff8000000000000,"), "{text}");
8002 assert!(text.contains("global @b : f64 = 0x7ff8000000000001,"), "{text}");
8003 assert!(text.contains("global @c : f64 = 0x7ff8000000000008,"), "{text}");
8004 assert!(text.contains("global @d : f64 = 0x7ff4000000000000,"), "{text}");
8005 assert!(text.contains("global @e : f64 = 0x7ff0000000000001,"), "{text}");
8006 assert!(text.contains("global @f : f32 = 0x7fc00001,"), "{text}");
8007 assert!(text.contains("global @g : f32 = 0x7fa00000,"), "{text}");
8008 assert!(text.contains("f80 0x7fffa000000000000000"), "{text}");
8009
8010 let text = ir(concat!(
8013 "double f(const char *p) { return __builtin_nan(p); }\n",
8014 "double g(void) { return __builtin_nans(\"1x\"); }\n",
8015 ));
8016 assert_eq!(text.matches("call @nan(").count(), 1, "{text}");
8017 assert_eq!(text.matches("call @nans(").count(), 1, "{text}");
8018 }
8019
8020 #[test]
8028 fn the_length_and_the_order_of_a_string_literal_are_known_here() {
8029 let text = ir(concat!(
8030 "unsigned long a = __builtin_strlen(\"hello\");\n",
8031 "unsigned long b = __builtin_strlen(\"a\\0bc\");\n",
8032 "int c = __builtin_strcmp(\"X\", \"X\\376\") < 0;\n",
8033 "int d = __builtin_strcmp(\"abc\", \"abc\");\n",
8034 "int e = __builtin_strcmp(\"abc\", \"ab\") > 0;\n",
8035 ));
8036 assert!(text.contains("global @a : i64 = 5,"), "{text}");
8037 assert!(text.contains("global @b : i64 = 1,"), "{text}");
8038 assert!(text.contains("global @c : i32 = 1,"), "{text}");
8039 assert!(text.contains("global @d : i32 = 0,"), "{text}");
8040 assert!(text.contains("global @e : i32 = 1,"), "{text}");
8041 assert!(!text.contains("call"), "{text}");
8042
8043 let text = ir("unsigned long f(const char *p) { return __builtin_strlen(p); }\n");
8045 assert!(text.contains("call @strlen("), "{text}");
8046 }
8047
8048 #[test]
8055 fn a_sign_builtin_is_a_mask_over_the_bits_and_not_a_call() {
8056 let text = body("double f(double x) { return __builtin_fabs(x); }\n");
8057 assert!(text.contains("bitcast.i64 %0"), "{text}");
8058 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
8059 assert!(text.contains("and %1, %2"), "{text}");
8060 assert!(text.contains("bitcast.f64 %3"), "{text}");
8061 assert!(!text.contains("call"), "{text}");
8062
8063 let text = body("double f(double x, double y) { return __builtin_copysign(x, y); }\n");
8064 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
8065 assert!(text.contains("%8 = or %4, %7"), "{text}");
8066 assert!(!text.contains("call"), "{text}");
8067
8068 let text = body("long double f(long double x) { return __builtin_fabsl(x); }\n");
8071 assert!(text.contains("iconst.i16 32767"), "{text}");
8072 assert!(text.contains("load.f80"), "{text}");
8073 assert!(!text.contains("call"), "{text}");
8074
8075 let text = body("double f(float x) { return __builtin_fabs(x); }\n");
8078 assert!(text.contains("fpext.f64 %0"), "{text}");
8079 assert!(text.contains("bitcast.i64 %1"), "{text}");
8080 }
8081
8082 #[test]
8089 fn a_shuffle_picks_each_lane_by_the_low_bits_of_the_mask() {
8090 let text = body(concat!(
8091 "typedef int v2 __attribute__((vector_size(8)));\n",
8092 "void f(v2 *v, v2 m) { *v = __builtin_shuffle(*v, m); }\n",
8093 ));
8094 assert!(text.contains("memcpy"), "{text}");
8095 assert_eq!(text.matches("iconst.i32 1\n").count(), 2, "{text}");
8096 assert_eq!(text.matches(" = and ").count(), 2, "{text}");
8097 assert!(!text.contains("call"), "{text}");
8098
8099 let text = body(concat!(
8102 "typedef char v4 __attribute__((vector_size(4)));\n",
8103 "v4 f(v4 a, v4 b, v4 m) { return __builtin_shuffle(a, b, m); }\n",
8104 ));
8105 assert_eq!(text.matches("iconst.i32 7\n").count(), 4, "{text}");
8106 assert!(text.contains("zext.i32"), "{text}");
8107 assert!(!text.contains("call"), "{text}");
8108 }
8109
8110 #[test]
8114 fn the_arguments_a_function_was_called_with_are_saved_on_the_way_in() {
8115 let text =
8116 mir("void *f(int a, double b) { (void)a; (void)b; return __builtin_apply_args(); }\n");
8117 assert!(text.matches("x64.mov_mr_64").count() >= 7, "{text}");
8119 assert!(text.matches("x64.movaps_mr").count() >= 8, "{text}");
8120 for reg in ["$rdi", "$rsi", "$rdx", "$rcx", "$r8", "$r9", "$xmm0", "$xmm7"] {
8121 assert!(text.contains(reg), "{reg} is not saved in\n{text}");
8122 }
8123
8124 let text = mir("int f(int a) { return a; }\n");
8126 assert!(!text.contains("movaps_mr"), "{text}");
8127 }
8128
8129 #[test]
8133 fn a_call_built_from_saved_arguments_loads_every_argument_register() {
8134 let text = mir(concat!(
8135 "void *g(void *args, void (*h)()) {\n",
8136 " return __builtin_apply(h, args, 64);\n",
8137 "}\n",
8138 ));
8139 assert!(text.matches("x64.mov_rm_64").count() >= 7, "{text}");
8140 assert!(text.matches("x64.movaps_rm").count() >= 8, "{text}");
8141 assert!(text.contains("call"), "{text}");
8142 assert!(text.matches("x64.movaps_mr").count() >= 2, "{text}");
8144
8145 let mut opts = options();
8147 opts.emit = EmitKind::Ir;
8148 let result = run(
8149 &opts,
8150 "void *g(void *a, void (*h)(), int n) { return __builtin_apply(h, a, n); }\n",
8151 );
8152 assert!(result.failed(), "{:?}", result.messages);
8153 assert!(
8154 result
8155 .messages
8156 .iter()
8157 .any(|m| m.contains("the size given to '__builtin_apply' is a constant")),
8158 "{:?}",
8159 result.messages
8160 );
8161 }
8162
8163 #[test]
8165 fn a_shuffle_refuses_what_gcc_refuses() {
8166 let mut opts = options();
8167 opts.emit = EmitKind::Ir;
8168 let source = concat!(
8169 "typedef int v4 __attribute__((vector_size(16)));\n",
8170 "typedef float f4 __attribute__((vector_size(16)));\n",
8171 "typedef short s8 __attribute__((vector_size(16)));\n",
8172 "typedef long long l4 __attribute__((vector_size(32)));\n",
8173 "void a(v4 x, f4 m) { __builtin_shuffle(x, m); }\n",
8174 "void b(int x, v4 m) { __builtin_shuffle(x, m); }\n",
8175 "void c(v4 x, f4 y, v4 m) { __builtin_shuffle(x, y, m); }\n",
8176 "void d(v4 x, s8 m) { __builtin_shuffle(x, m); }\n",
8177 "void e(f4 x, l4 m) { __builtin_shuffle(x, m); }\n",
8178 "void g(v4 x) { __builtin_shuffle(x); }\n",
8179 );
8180 let messages = run(&opts, source).messages;
8181 let wanted = [
8182 "last argument must be an integer vector [E0715]",
8183 "arguments must be vectors [E0715]",
8184 "argument vectors must be of the same type [E0715]",
8185 "number of elements of the argument vector(s) and the mask vector should be the same \
8186 [E0715]",
8187 "argument vector(s) inner type must have the same size as inner type of the mask \
8188 [E0715]",
8189 "too few arguments to function '__builtin_shuffle' [E0511]",
8190 ];
8191 assert_eq!(messages.len(), wanted.len(), "{messages:?}");
8192 for (message, wanted) in messages.iter().zip(wanted) {
8193 assert!(message.ends_with(wanted), "{message}");
8194 }
8195 }
8196
8197 #[test]
8206 fn the_plain_math_names_are_the_same_mask_and_not_a_call() {
8207 let text =
8208 body(concat!("double fabs(double x);\n", "double f(double x) { return fabs(x); }\n",));
8209 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
8210 assert!(!text.contains("call"), "{text}");
8211
8212 let text =
8213 body(concat!("float fabsf(float x);\n", "float f(float x) { return fabsf(x); }\n",));
8214 assert!(text.contains("bitcast.i32 %0"), "{text}");
8215 assert!(!text.contains("call"), "{text}");
8216
8217 let text = body(concat!(
8218 "double copysign(double x, double y);\n",
8219 "double f(double x, double y) { return copysign(x, y); }\n",
8220 ));
8221 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
8222 assert!(!text.contains("call"), "{text}");
8223
8224 let text = body(concat!(
8225 "float copysignf(float x, float y);\n",
8226 "float f(float x, float y) { return copysignf(x, y); }\n",
8227 ));
8228 assert!(!text.contains("call"), "{text}");
8229
8230 let text = ir(concat!(
8234 "long double fabsl(long double x);\n",
8235 "long double f(long double x) { return fabsl(x); }\n",
8236 ));
8237 assert!(text.contains("call @fabsl"), "{text}");
8238 }
8239
8240 #[test]
8248 fn a_plain_math_name_the_program_took_is_the_programs_own_function() {
8249 let taken = concat!(
8250 "static double fabs(double b) { return 7; }\n",
8251 "double f(double x) { return fabs(x); }\n",
8252 );
8253 assert!(ir(taken).contains("call @fabs"), "a static definition is the program's own");
8254
8255 let retyped = concat!("int fabs(int b);\n", "int f(int x) { return fabs(x); }\n");
8256 assert!(ir(retyped).contains("call @fabs"), "another type is another function");
8257
8258 let plain = concat!("double fabs(double b);\n", "double f(double x) { return fabs(x); }\n");
8259 let mut opts = options();
8260 opts.emit = EmitKind::Ir;
8261 assert!(!run(&opts, plain).text().contains("call @fabs"), "the library's by default");
8262
8263 opts.builtins = false;
8264 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin");
8265
8266 opts.builtins = true;
8267 opts.no_builtin = vec!["fabs".to_owned()];
8268 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin-fabs");
8269 let one = concat!(
8270 "double copysign(double a, double b);\n",
8271 "double f(double x) { return copysign(x, 1.0); }\n",
8272 );
8273 assert!(!run(&opts, one).text().contains("call @copysign"), "one name and not the family");
8274
8275 opts.no_builtin = Vec::new();
8277 opts.builtins = false;
8278 let prefixed = "double f(double x) { return __builtin_fabs(x); }\n";
8279 assert!(!run(&opts, prefixed).text().contains("call @fabs"), "the prefix is not a library");
8280 }
8281
8282 #[test]
8291 fn the_sign_builtins_answer_a_zero_and_a_nan_the_way_the_bits_say() {
8292 let text = ir(concat!(
8293 "double a = __builtin_fabs(-3.5);\n",
8294 "double b = __builtin_copysign(1.0, -0.0);\n",
8295 "double c = __builtin_copysign(0.0, -2.0);\n",
8296 "double d = __builtin_copysign(-__builtin_nan(\"\"), 1.0);\n",
8298 "double e = __builtin_fabs(-__builtin_nan(\"0x1\"));\n",
8299 "float g = __builtin_copysignf(-0.0f, 2.0f);\n",
8300 "long double h = __builtin_copysignl(1.0L, -1.0L);\n",
8301 "long double i = __builtin_fabsl(-__builtin_infl());\n",
8302 ));
8303 assert!(text.contains("global @a : f64 = 0x400c000000000000,"), "{text}");
8304 assert!(text.contains("global @b : f64 = 0xbff0000000000000,"), "{text}");
8305 assert!(text.contains("global @c : f64 = 0x8000000000000000,"), "{text}");
8306 assert!(text.contains("global @d : f64 = 0x7ff8000000000000,"), "{text}");
8307 assert!(text.contains("global @e : f64 = 0x7ff8000000000001,"), "{text}");
8308 assert!(text.contains("global @g : f32 = 0x0,"), "{text}");
8309 assert!(text.contains("f80 0xbfff8000000000000000"), "{text}");
8310 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
8311 }
8312
8313 #[test]
8320 fn the_sign_of_a_long_double_is_in_the_word_at_the_top_of_it() {
8321 for source in [
8322 "int f(long double x) { return __builtin_signbit(x); }\n",
8323 "long double f(long double x) { return __builtin_fabsl(x); }\n",
8324 "long double f(long double x, long double y) { return __builtin_copysignl(x, y); }\n",
8325 "int f(long double x) { return __builtin_isnormal(x); }\n",
8326 ] {
8327 let text = body(source);
8328 assert!(!text.contains("i80"), "{text}");
8329 assert!(text.contains("i16"), "{text}");
8330 }
8331 }
8332
8333 #[test]
8341 fn the_complex_builtins_are_the_halves_of_the_value_and_not_a_call() {
8342 let text = body("double f(_Complex double z) { return __builtin_creal(z); }\n");
8343 assert!(!text.contains("call"), "{text}");
8344 let text = body("double f(_Complex double z) { return __builtin_cimag(z); }\n");
8345 assert!(!text.contains("call"), "{text}");
8346
8347 let text = body("_Complex double f(_Complex double z) { return __builtin_conj(z); }\n");
8350 assert_eq!(text.matches("fneg").count(), 1, "{text}");
8351 assert!(!text.contains("call"), "{text}");
8352 let negated = body("_Complex double f(_Complex double z) { return -z; }\n");
8353 assert_eq!(negated.matches("fneg").count(), 2, "{negated}");
8354
8355 let written = body("_Complex double f(_Complex double z) { return ~z; }\n");
8358 assert_eq!(written, text, "the name and the operator are the same thing");
8359
8360 let text = body(concat!(
8362 "double creal(_Complex double z);\n",
8363 "double f(_Complex double z) { return creal(z); }\n",
8364 ));
8365 assert!(!text.contains("call"), "{text}");
8366 let text = body(concat!(
8367 "_Complex float conjf(_Complex float z);\n",
8368 "_Complex float f(_Complex float z) { return conjf(z); }\n",
8369 ));
8370 assert_eq!(text.matches("fneg").count(), 1, "{text}");
8371 assert!(!text.contains("call"), "{text}");
8372
8373 let taken = concat!(
8376 "static double creal(_Complex double z) { return 7; }\n",
8377 "double f(_Complex double z) { return creal(z); }\n",
8378 );
8379 assert!(ir(taken).contains("call @creal"), "a static definition is the program's own");
8380 let retyped = concat!("int cimag(int z);\n", "int f(int z) { return cimag(z); }\n");
8381 assert!(ir(retyped).contains("call @cimag"), "another type is another function");
8382 let plain = concat!(
8383 "double cimag(_Complex double z);\n",
8384 "double f(_Complex double z) { return cimag(z); }\n",
8385 );
8386 let mut opts = options();
8387 opts.emit = EmitKind::Ir;
8388 opts.builtins = false;
8389 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin");
8390 opts.builtins = true;
8391 opts.no_builtin = vec!["cimag".to_owned()];
8392 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin-cimag");
8393
8394 let text = ir(concat!(
8396 "double a = __builtin_creal(1.5 + 2.5i);\n",
8397 "double b = __builtin_cimag(1.5 + 2.5i);\n",
8398 "_Complex double c = __builtin_conj(1.5 + 2.5i);\n",
8399 ));
8400 assert!(text.contains("global @a : f64 = 0x3ff8000000000000,"), "{text}");
8401 assert!(text.contains("global @b : f64 = 0x4004000000000000,"), "{text}");
8402 assert!(
8403 text.contains("{ f64 0x3ff8000000000000, f64 0xc004000000000000 }"),
8404 "the conjugate of a constant is the constant with the second half negated: {text}"
8405 );
8406 assert!(!text.contains("call"), "{text}");
8407 }
8408
8409 #[test]
8417 fn a_math_library_builtin_of_a_constant_is_the_answer_and_not_a_call() {
8418 let text = ir(concat!(
8419 "double a = __builtin_ceil(1.5);\n",
8420 "double b = __builtin_floor(1.5);\n",
8421 "double c = __builtin_trunc(-1.5);\n",
8422 "double d = __builtin_round(2.5);\n",
8425 "double e = __builtin_ceil(-0.5);\n",
8427 "double f = __builtin_fmax(1.0, 2.0);\n",
8428 "double g = __builtin_fmin(1.0, 2.0);\n",
8429 "float h = __builtin_ceilf(1.25f);\n",
8430 "double ceil(double x);\n",
8433 "double i = ceil(2.25);\n",
8434 ));
8435 assert!(text.contains("global @a : f64 = 0x4000000000000000,"), "{text}");
8436 assert!(text.contains("global @b : f64 = 0x3ff0000000000000,"), "{text}");
8437 assert!(text.contains("global @c : f64 = 0xbff0000000000000,"), "{text}");
8438 assert!(text.contains("global @d : f64 = 0x4008000000000000,"), "{text}");
8439 assert!(text.contains("global @e : f64 = 0x8000000000000000,"), "{text}");
8440 assert!(text.contains("global @f : f64 = 0x4000000000000000,"), "{text}");
8441 assert!(text.contains("global @g : f64 = 0x3ff0000000000000,"), "{text}");
8442 assert!(text.contains("global @h : f32 = 0x40000000,"), "{text}");
8443 assert!(text.contains("global @i : f64 = 0x4008000000000000,"), "{text}");
8444 assert!(!text.contains("call"), "{text}");
8445 }
8446
8447 #[test]
8455 fn a_math_library_builtin_of_anything_else_is_a_call_to_the_library() {
8456 let text = ir(concat!(
8457 "double f(double x) { return __builtin_ceil(x); }\n",
8458 "float g(float x) { return __builtin_floorf(x); }\n",
8459 "double h(double x, double y) { return __builtin_fmax(x, y); }\n",
8460 ));
8461 assert!(text.contains("call @ceil("), "{text}");
8462 assert!(text.contains("call @floorf("), "{text}");
8463 assert!(text.contains("call @fmax("), "{text}");
8464
8465 let text = ir(concat!(
8469 "double f(void) { return __builtin_rint(2.5); }\n",
8470 "double g(void) { return __builtin_nearbyint(2.5); }\n",
8471 ));
8472 assert!(text.contains("call @rint("), "{text}");
8473 assert!(text.contains("call @nearbyint("), "{text}");
8474
8475 let text = ir("double f(void) { return __builtin_fmin(__builtin_nan(\"\"), 1.0); }\n");
8478 assert!(text.contains("call @fmin("), "{text}");
8479
8480 let plain = concat!("double ceil(double x);\n", "double f(void) { return ceil(2.25); }\n");
8483 let mut opts = options();
8484 opts.emit = EmitKind::Ir;
8485 opts.no_builtin = vec!["ceil".to_owned()];
8486 assert!(run(&opts, plain).text().contains("call @ceil("), "-fno-builtin-ceil");
8487 }
8488
8489 #[test]
8496 fn a_constexpr_object_is_a_constant_wherever_one_is_required() {
8497 let text = ir(concat!(
8498 "constexpr int side = 4;\n",
8499 "constexpr int wider = side + 1;\n",
8500 "constexpr double half = 1.5;\n",
8501 "struct point { int x; int y; };\n",
8502 "constexpr struct point origin = { 5, 6 };\n",
8503 "int square[side * side];\n",
8504 "int rectangle[wider];\n",
8505 "int rounded[(int)half * 2];\n",
8506 "int across[origin.y];\n",
8507 "enum named { four = side };\n",
8508 "int e = four;\n",
8509 ));
8510 assert!(text.contains("global @square : bytes 64 ="), "{text}");
8511 assert!(text.contains("global @rectangle : bytes 20 ="), "{text}");
8512 assert!(text.contains("global @rounded : bytes 8 ="), "{text}");
8513 assert!(text.contains("global @across : bytes 24 ="), "{text}");
8514 assert!(text.contains("global @e : i32 = 4,"), "{text}");
8515
8516 let mut opts = options();
8519 opts.emit = EmitKind::Ir;
8520 let konst = "const int n = 1;\nint a[n];\n";
8521 let message = "/main.c:2:5: error: variably modified 'a' at file scope [E0538]";
8522 assert_eq!(run(&opts, konst).messages, [message]);
8523
8524 let subscript = "constexpr int t[3] = { 1, 2, 3 };\nint a[t[1]];\n";
8526 assert_eq!(run(&opts, subscript).messages, [message]);
8527
8528 let address = "constexpr int c = 3;\nint *p = &c;\n";
8530 let warning = "/main.c:2:6: warning: initialization discards 'const' qualifier from \
8531 pointer target type [E0514]";
8532 assert_eq!(run(&opts, address).messages, [warning]);
8533 }
8534
8535 #[test]
8544 fn a_member_whose_size_was_refused_is_not_a_flexible_array_member() {
8545 let mut opts = options();
8546 opts.emit = EmitKind::Ir;
8547
8548 let alone = "int k;\nextern struct D { int a[k]; } ed;\n";
8549 let message = "/main.c:2:23: error: variably modified 'a' at file scope [E0538]";
8550 assert_eq!(run(&opts, alone).messages, [message]);
8551
8552 let first = "int k;\nextern struct E { int a[k]; int b; } ee;\n";
8554 assert_eq!(run(&opts, first).messages, [message]);
8555
8556 let negative = "struct F { int a[-1]; };\n";
8559 let refused = "/main.c:1:18: error: size of array 'a' is negative [E0536]";
8560 assert_eq!(run(&opts, negative).messages, [refused]);
8561
8562 let flexible = "struct G { int a[]; };\n";
8565 let named = "/main.c:1:16: error: flexible array member in a struct with no named \
8566 members [E0554]";
8567 assert_eq!(run(&opts, flexible).messages, [named]);
8568 }
8569
8570 #[test]
8585 fn a_pointer_to_an_array_gains_a_qualifier_the_same_way_a_pointer_to_anything_else_does() {
8586 let mut opts = options();
8587 opts.emit = EmitKind::Ir;
8588 let prefix = "typedef unsigned int B[4];\nstruct H { B category[2]; };\n";
8589
8590 let adding = format!("{prefix}const B *f(struct H *h) {{ return &h->category[0]; }}\n");
8592 assert_eq!(run(&opts, &adding).messages, [] as [String; 0]);
8593
8594 let plain = concat!(
8597 "const unsigned int (*f(unsigned int (*p)[4]))[4] { return p; }\n",
8598 "const unsigned int (*g(unsigned int (*p)[2][3]))[2][3] { return p; }\n",
8599 );
8600 assert_eq!(run(&opts, plain).messages, [] as [String; 0]);
8601
8602 let dropping = format!("{prefix}B *f(const B *p) {{ return p; }}\n");
8605 let warning = "/main.c:3:27: warning: return discards 'const' qualifier from pointer target type \
8606 [E0514]";
8607 assert_eq!(run(&opts, &dropping).messages, [warning]);
8608
8609 let wrong = "const unsigned int (*f(unsigned short (*p)[4]))[4] { return p; }\n";
8612 let error = "/main.c:1:61: error: returning 'unsigned short (*)[4]' from a function with \
8613 incompatible return type 'const unsigned int (*)[4]' [E0512]";
8614 assert_eq!(run(&opts, wrong).messages, [error]);
8615 }
8616
8617 #[test]
8626 fn an_old_style_definition_takes_its_types_from_the_declarations_under_its_list() {
8627 let mut opts = options();
8630 opts.std = Std::C17;
8631 let source = concat!(
8632 "int add(a, b)\n",
8633 "int a;\n",
8634 "int b;\n",
8635 "{ return a + b; }\n",
8636 "int promoted(c)\n",
8637 "char c;\n",
8638 "{ return c; }\n",
8639 "int narrow(char);\n",
8640 "int narrow(c)\n",
8641 "char c;\n",
8642 "{ return c; }\n",
8643 "int first(a)\n",
8644 "int a[4];\n",
8645 "{ return a[0]; }\n",
8646 );
8647 let result = run(&opts, source);
8648 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
8649 let text = result.text();
8650 assert!(text.contains("add : int(int, int) function external defined"), "{text}");
8651 assert!(text.contains("promoted : int(int) function external defined"), "{text}");
8652 assert!(text.contains("c : char object automatic defined"), "{text}");
8654 assert!(text.contains("narrow : int(char) function external defined"), "{text}");
8655 assert!(text.contains("first : int(int *) function external defined"), "{text}");
8657 }
8658
8659 #[test]
8666 fn the_two_halves_of_an_old_style_parameter_list_have_to_agree() {
8667 let mut opts = options();
8668 opts.std = Std::C17;
8669 for (source, message) in [
8670 ("int f(a, a)\nint a;\n{ return a; }\n", "1:10: error: multiple parameters named 'a'"),
8671 (
8672 "int f(a)\nint a;\nint b;\n{ return a; }\n",
8673 "3:5: error: declaration for parameter 'b' but no such parameter",
8674 ),
8675 ("int f(a)\nint a;\nint a;\n{ return a; }\n", "3:5: error: redefinition of parameter"),
8676 ("int f(a)\nint a = 1;\n{ return a; }\n", "2:5: error: parameter 'a' is initialized"),
8677 (
8678 "int f(a)\nstatic int a;\n{ return a; }\n",
8679 "2:12: error: storage class specified for parameter 'a'",
8680 ),
8681 (
8682 "int f(char);\nint f(a)\nshort a;\n{ return a; }\n",
8683 "2:7: error: argument 'a' doesn't match prototype",
8684 ),
8685 ] {
8686 let result = run(&opts, source);
8687 assert!(result.failed(), "expected this to fail:\n{source}");
8688 assert!(result.messages[0].contains(message), "{:?}", result.messages);
8689 }
8690
8691 let implicit = "int f(a, b)\nint a;\n{ return a + b; }\n";
8694 let mut older = options();
8695 older.std = Std::C89;
8696 assert!(!run(&older, implicit).failed(), "{:?}", run(&older, implicit).messages);
8697 let result = run(&opts, implicit);
8698 assert!(
8699 result.messages[0].contains("1:10: error: type of 'b' defaults to 'int'"),
8700 "{:?}",
8701 result.messages
8702 );
8703
8704 let mut newer = options();
8708 newer.std = Std::C23;
8709 let plain = "int f(a)\nint a;\n{ return a; }\n";
8710 let result = run(&newer, plain);
8711 assert!(!result.failed(), "{:?}", result.messages);
8712 assert_eq!(
8713 result.messages,
8714 ["/main.c:1:5: warning: old-style function definition [E0412]"]
8715 );
8716 assert!(run(&opts, plain).messages.is_empty(), "and nothing to say in the dialects before");
8717 }
8718
8719 #[test]
8726 fn the_obsolete_designators_are_taken_and_are_pedantic_warnings() {
8727 let array = "int a[8] = { [3] 7 };\n";
8728 let member = "struct s { int x; } v = { x: 7 };\n";
8729 for source in [array, member] {
8730 let result = run(&options(), source);
8731 assert!(!result.failed(), "{:?}", result.messages);
8732 assert!(result.messages.is_empty(), "nothing to say: {:?}", result.messages);
8733 }
8734
8735 let mut asked = options();
8736 asked.pedantic = true;
8737 assert_eq!(
8738 run(&asked, array).messages,
8739 ["/main.c:1:18: warning: obsolete designator, write `[i] =` instead [E0415]"]
8740 );
8741 assert_eq!(
8742 run(&asked, member).messages,
8743 ["/main.c:1:27: warning: obsolete designator, write `.field =` instead [E0413]"]
8744 );
8745 }
8746
8747 #[test]
8754 fn a_type_is_refused_when_it_passes_the_largest_object_and_not_before() {
8755 let text = ir(concat!(
8756 "struct huge_struct { short buf[(1L << 62) - 256]; int a, b, c, d; };\n",
8757 "struct brim { char buf[9223372036854775807L]; };\n",
8758 "struct bitty { char buf[9223372036854775800L]; int x : 1; };\n",
8759 "unsigned long h = sizeof(struct huge_struct);\n",
8760 "unsigned long b = sizeof(struct brim);\n",
8761 "unsigned long y = sizeof(struct bitty);\n",
8762 ));
8763 assert!(text.contains("global @h : i64 = 9223372036854775312,"), "{text}");
8764 assert!(text.contains("global @b : i64 = 9223372036854775807,"), "{text}");
8765 assert!(text.contains("global @y : i64 = 9223372036854775804,"), "{text}");
8766
8767 let mut opts = options();
8768 opts.emit = EmitKind::Ir;
8769 let over = "struct over { char buf[9223372036854775800L]; char x[8]; };\n";
8770 let message = "/main.c:1:1: error: type 'struct over' is too large [E0560]";
8771 assert_eq!(run(&opts, over).messages, [message]);
8772 let array = "struct wide { short buf[1L << 62]; };\n";
8773 let message = "/main.c:1:25: error: size of array 'buf' exceeds \
8774 maximum object size '9223372036854775807' [E0537]";
8775 assert_eq!(run(&opts, array).messages[0], message);
8776 }
8777
8778 fn compile_bytes(source: &[u8]) -> Compiled {
8783 let mut opts = options();
8784 opts.emit = EmitKind::Ir;
8785 let mut fs = MemoryFileSystem::new();
8786 fs.insert("/main.c", source.to_vec());
8787 compile(&opts, "/main.c", &fs)
8788 }
8789
8790 #[test]
8797 fn a_byte_that_is_not_a_character_is_kept_in_a_literal_and_refused_outside_one() {
8798 let mut source = b"char s[] = \"a".to_vec();
8799 source.push(0xff);
8800 source.extend_from_slice(b"b\";\nchar c = '");
8801 source.push(0xff);
8802 source.extend_from_slice(b"';\n");
8803 let result = compile_bytes(&source);
8804 assert_eq!(result.messages, Vec::<String>::new(), "a raw byte in a literal is that byte");
8805 assert!(result.text().contains(r#"bytes "a\ffb\00""#), "{}", result.text());
8806 assert!(result.text().contains("global @c : i8 = -1,"), "{}", result.text());
8808
8809 let mut stray = b"int a".to_vec();
8810 stray.push(0xff);
8811 stray.extend_from_slice(b" = 1;\n");
8812 let result = compile_bytes(&stray);
8813 assert!(
8814 result.messages.iter().any(|m| m.contains("source is not valid UTF-8 here")),
8815 "{:?}",
8816 result.messages
8817 );
8818 }
8819
8820 #[test]
8821 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
8822 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
8823 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
8824 let expected = "\
8825func @add(i32, i32) -> i32, linkage(external) {
8826block0(%0: i32, %1: i32):
8827 %2 = add.nsw %0, %1
8828 return %2
8829}
8830";
8831 assert!(text.contains(expected), "{text}");
8832 }
8833
8834 #[test]
8835 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
8836 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
8837 assert!(!text.contains("alloca"), "{text}");
8838 assert!(!text.contains("load"), "{text}");
8839 assert!(!text.contains("store"), "{text}");
8840 }
8841
8842 #[test]
8843 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
8844 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
8845 let expected = "\
8846block0:
8847 %0 = alloca, size 4, align 4
8848 %1 = iconst.i32 1
8849 store %1 -> %0, align 4, tbaa !1
8850 %2 = call @g(%0) : (ptr) -> i32
8851 return %2
8852";
8853 assert_eq!(text, expected);
8854 }
8855
8856 #[test]
8857 fn a_loop_carries_what_it_changes_as_block_parameters() {
8858 let text = body(
8861 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
8862 return total;\n}\n",
8863 );
8864 assert!(!text.contains("alloca"), "{text}");
8865 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
8866 assert!(text.contains("jump block1("), "{text}");
8867 }
8868
8869 #[test]
8870 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
8871 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
8872 assert!(text.contains("icmp slt %0, %1"), "{text}");
8873 assert!(!text.contains("zext"), "{text}");
8874 }
8875
8876 #[test]
8877 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
8878 let text = body("int f(int a, int b) { return a && b; }\n");
8879 let expected = "\
8880block0(%0: i32, %1: i32):
8881 %2 = iconst.i32 0
8882 %3 = icmp ne %0, %2
8883 %4 = iconst.i1 0
8884 br_if %3, block1, block2(%4)
8885
8886block1:
8887 %5 = iconst.i32 0
8888 %6 = icmp ne %1, %5
8889 jump block2(%6)
8890
8891block2(%7: i1):
8892 %8 = zext.i32 %7
8893 return %8
8894";
8895 assert_eq!(text, expected);
8896 }
8897
8898 #[test]
8899 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
8900 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
8901 assert!(!text.contains("block3"), "{text}");
8904 assert!(!text.contains("iconst.i32 3"), "{text}");
8905 }
8906
8907 #[test]
8908 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
8909 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
8910 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
8911 assert!(body("int f(void) { }\n").contains("unreachable"));
8912 }
8913
8914 #[test]
8915 fn a_structure_is_copied_rather_than_held_in_a_value() {
8916 let text = body(
8917 "struct point { int x, y; };\n\
8918 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
8919 );
8920 assert!(text.contains("memcpy"), "{text}");
8921 }
8922
8923 #[test]
8924 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
8925 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
8926 assert!(text.contains("memset"), "{text}");
8927 }
8928
8929 #[test]
8930 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
8931 let text = body(
8932 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
8933 default: r = 4; } return r; }\n",
8934 );
8935 let expected = "\
8936block0(%0: i32):
8937 %1 = iconst.i32 0
8938 switch %0, block1, [1 => block2, 2 => block3(%1)]
8939
8940block1:
8941 %2 = iconst.i32 4
8942 jump block4(%2)
8943
8944block2:
8945 %3 = iconst.i32 1
8946 jump block3(%3)
8947
8948block3(%4: i32):
8949 %5 = iconst.i32 2
8950 %6 = add.nsw %4, %5
8951 jump block4(%6)
8952
8953block4(%7: i32):
8954 return %7
8955";
8956 assert_eq!(text, expected);
8957 }
8958
8959 #[test]
8960 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
8961 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
8964 assert!(text.contains("%2 = sub %0, %1"), "{text}");
8965 assert!(text.contains("icmp ule"), "{text}");
8966 assert!(!text.contains("switch"), "{text}");
8967 }
8968
8969 #[test]
8970 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
8971 let text = body(
8972 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
8973 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
8974 );
8975 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
8978 assert!(text.contains("block5:\n jump block7("), "{text}");
8979 assert!(text.contains("block6:\n jump block8("), "{text}");
8980 }
8981
8982 #[test]
8983 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
8984 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
8985 }
8986
8987 #[test]
8988 fn a_label_a_loop_is_only_entered_through_builds_the_loop_around_it() {
8989 let text = body(
8994 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
8995 return n; }\n",
8996 );
8997 assert!(text.contains("switch %0, block1(%1), [1 => block2, 2 => block3(%1)]"), "{text}");
9000 assert!(text.contains("block3(%3: i32):\n %4 = iconst.i32 1"), "{text}");
9001 assert!(text.contains("block4:\n jump block3("), "{text}");
9002 }
9003
9004 #[test]
9005 fn a_goto_into_a_loop_body_enters_it_without_the_test() {
9006 let text = body("int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n");
9009 assert!(text.starts_with("block0(%0: i32, %1: i32):\n jump block1(%1)"), "{text}");
9010 assert!(text.contains("block1(%2: i32):\n %3 = iconst.i32 1"), "{text}");
9011 assert!(text.contains("br_if %6, block2, block3"), "{text}");
9012 }
9013
9014 #[test]
9015 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
9016 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
9017 assert!(!text.contains("alloca"), "{text}");
9021 assert!(text.contains("block2(%4: i32):\n return %4"), "{text}");
9022 assert_eq!(text.matches("jump block2(").count(), 2, "{text}");
9023 }
9024
9025 #[test]
9026 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
9027 let text =
9028 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
9029 assert!(!text.contains("alloca"), "{text}");
9030 assert!(text.contains("block1(%2: i32):"), "{text}");
9031 assert!(text.contains("jump block1(%5)"), "{text}");
9032 }
9033
9034 #[test]
9035 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
9036 assert_eq!(
9039 body("int f(int x) { return x; spare: return 0; }\n"),
9040 "block0(%0: i32):\n return %0\n"
9041 );
9042 }
9043
9044 #[test]
9045 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
9046 let text = body(
9047 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
9048 );
9049 assert_eq!(
9052 text,
9053 "\
9054block0(%0: ptr):
9055 %1 = load.i8 %0, align 1
9056 %2 = iconst.i8 3
9057 %3 = ashr %1, %2
9058 %4 = sext.i32 %3
9059 return %4
9060"
9061 );
9062 }
9063
9064 #[test]
9065 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
9066 let text =
9070 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
9071 assert_eq!(
9072 text,
9073 "\
9074block0(%0: ptr, %1: i32):
9075 %2 = iconst.i32 16777215
9076 %3 = and %1, %2
9077 %4 = trunc.i16 %3
9078 store %4 -> %0, align 2
9079 %5 = iconst.i32 16
9080 %6 = lshr %3, %5
9081 %7 = trunc.i8 %6
9082 %8 = iconst.i64 2
9083 %9 = ptr_add %0, %8
9084 store %7 -> %9, align 1
9085 return
9086"
9087 );
9088 }
9089
9090 #[test]
9091 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
9092 let text =
9093 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
9094 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
9097 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
9098 }
9099
9100 #[test]
9101 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
9102 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
9105 assert_eq!(text.matches("ashr").count(), 0, "{text}");
9106 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
9107 }
9108
9109 #[test]
9110 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
9111 let text = body(
9115 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
9116 );
9117 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
9118 }
9119
9120 #[test]
9121 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
9122 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
9125 assert!(
9126 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
9127 "{text}"
9128 );
9129 }
9130
9131 #[test]
9132 fn an_initialized_flexible_array_member_makes_the_object_larger_than_its_type() {
9133 let text = ir(concat!(
9138 "struct a { int i; int j[]; } x = { 1, { 2, 0, 2, 3 } };\n",
9139 "struct b { char c; char p[]; } y = { 'o', \"wx\" };\n",
9140 "struct c { char c; char p[]; } z = { '9', { 'e', 'b' } };\n",
9141 "char s[2] = \"hi\";\n",
9142 ));
9143 assert!(
9144 text.contains("global @x : bytes 20 = { i32 1, i32 2, i32 0, i32 2, i32 3 }"),
9145 "{text}"
9146 );
9147 assert!(text.contains("global @y : bytes 4 = { i8 111, bytes \"wx\\00\" }"), "{text}");
9148 assert!(text.contains("global @z : bytes 3 = { i8 57, i8 101, i8 98 }"), "{text}");
9149 assert!(text.contains("global @s : bytes 2 = { bytes \"hi\" }"), "{text}");
9152 }
9153
9154 #[test]
9155 fn a_definition_takes_a_parameter_it_left_unnamed() {
9156 let text = ir("int f(int a, int) { return a; }\n");
9160 assert!(text.contains("func @f(i32, i32) -> i32"), "{text}");
9161 assert!(text.contains("block0(%0: i32, %1: i32):"), "{text}");
9162
9163 let text = ir("int g(int, int n) { return n; }\n");
9166 assert!(text.contains("block0(%0: i32, %1: i32):\n return %1\n"), "{text}");
9167 }
9168
9169 #[test]
9170 fn an_assignment_of_a_structure_is_the_object_it_wrote() {
9171 let text = body(concat!(
9176 "struct s { int f; int g; };\n",
9177 "void h(struct s *a, struct s *c, struct s *d, struct s *e)\n",
9178 "{ *d = *e = a[0] = *c; }\n",
9179 ));
9180 assert_eq!(text.matches("memcpy").count(), 3, "{text}");
9181 assert!(text.contains("memcpy %8, %1, size 8, align 4\n"), "{text}");
9182 assert!(text.contains("memcpy %3, %8, size 8, align 4\n"), "{text}");
9183 assert!(text.contains("memcpy %2, %3, size 8, align 4\n"), "{text}");
9184 }
9185
9186 #[test]
9187 fn a_string_literal_stops_at_the_end_of_the_array_it_is_filling() {
9188 let mut opts = options();
9193 opts.emit = EmitKind::Ir;
9194 let result = run(
9195 &opts,
9196 concat!(
9197 "const char a[2][3] = { \"1234\", \"xyz\" };\n",
9198 "static const char b[3][5] = { \"12345\", \"678\", \"9\" };\n",
9199 "union u { struct { char x[4]; char y[4]; }; struct { char z[8]; }; };\n",
9200 "const union u c = { { \"1234\", \"567\" } };\n",
9201 ),
9202 );
9203 let text = result.text();
9204 assert_eq!(
9205 result.messages,
9206 ["/main.c:1:24: warning: initializer-string for array of 'const char' is too long \
9207 (5 chars into 3 available) [E0637]"]
9208 );
9209 assert!(text.contains("global @a : bytes 6 = { bytes \"123\", bytes \"xyz\" }"), "{text}");
9210 assert!(
9211 text.contains(
9212 "global @b : bytes 15 = { bytes \"12345\", bytes \"678\\00\", zero 1, \
9213 bytes \"9\\00\", zero 3 }"
9214 ),
9215 "{text}"
9216 );
9217 assert!(
9220 text.contains("global @c : bytes 8 = { bytes \"1234\", bytes \"567\\00\" }"),
9221 "{text}"
9222 );
9223 }
9224
9225 #[test]
9226 fn a_cast_of_a_record_to_its_own_type_is_the_object_that_was_cast() {
9227 let text = body(concat!(
9231 "struct s { int a, b; };\nstruct v { struct s s; int t; };\n",
9232 "void g(struct v *);\n",
9233 "void f(struct s *p) { struct v w = { (struct s)*p, 5 }; g(&w); }\n",
9234 ));
9235 assert_eq!(text.matches("memcpy").count(), 1, "{text}");
9236 }
9237
9238 #[test]
9239 fn a_compound_literal_read_in_a_static_initializer_lays_its_bytes_into_the_image() {
9240 let text = ir(concat!(
9245 "struct s { int x; };\n",
9246 "struct t { struct s s; int o; } a = { (struct s){ 2 }, 3 };\n",
9247 "int n = (int){ 7 };\n",
9248 "struct u { struct s p; struct s q; } b = { (struct s){ 1 }, (struct s){ } };\n",
9249 ));
9250 assert!(text.contains("global @a : bytes 8 = { i32 2, i32 3 }"), "{text}");
9251 assert!(text.contains("global @n : i32 = 7,"), "{text}");
9252 assert!(text.contains("global @b : bytes 8 = { i32 1, zero 4 }"), "{text}");
9255 }
9256
9257 #[test]
9258 fn the_address_of_a_compound_literal_asks_for_the_object_it_points_at() {
9259 let text = ir("struct s { int x; };\nstruct s *q = &(struct s){ 9 };\n");
9263 assert!(text.contains("global @.Lanon.0 : i32 = 9, align 4, linkage(internal)"), "{text}");
9264 assert!(text.contains("global @q : bytes 8 = { addr.8 @.Lanon.0 }"), "{text}");
9265 }
9266
9267 #[test]
9268 fn an_object_of_no_size_at_all_has_an_image_with_nothing_in_it() {
9269 let text = ir("unsigned char foo[1][0];\n");
9273 assert!(text.contains("global @foo : bytes 0 = {}, align 1"), "{text}");
9274 }
9275
9276 #[test]
9277 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
9278 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
9281 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
9282 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
9283 }
9284
9285 #[test]
9286 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
9287 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
9291 assert!(
9292 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
9293 "{text}"
9294 );
9295 }
9296
9297 #[test]
9298 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
9299 let text = body(
9304 "\
9305struct s { int a, b; };
9306struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
9307",
9308 );
9309 assert!(text.contains("block3(%7: ptr)"), "{text}");
9311 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
9312 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
9313 }
9314
9315 #[test]
9323 fn the_left_side_of_a_conditional_with_no_middle_is_evaluated_once() {
9324 let text = body("int f(int i) { return ++i ?: 10; }\n");
9325 assert!(text.contains("jump block3(%2)"), "the arm is the value that was tested: {text}");
9326 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
9327
9328 let text = body("long f(int i) { return ++i ?: 10L; }\n");
9331 assert!(text.contains("%5 = sext.i64 %2"), "the arm widens what was tested: {text}");
9332 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
9333
9334 let text = body("int g(void);\nint f(void) { return g() ?: 10; }\n");
9336 assert_eq!(text.matches("call @g").count(), 1, "called once: {text}");
9337
9338 let text = body("int f(int i) { return ++i ? ++i : 10; }\n");
9341 assert_eq!(text.matches("add.nsw").count(), 2, "incremented twice: {text}");
9342 }
9343
9344 #[test]
9345 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
9346 let text = ir("\
9350struct pair { int a, b; };
9351struct pair make(int a, int b);
9352struct pair twice(struct pair p) { return make(p.a, p.b); }
9353");
9354 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
9355 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
9356 }
9357
9358 #[test]
9359 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
9360 let text = ir("\
9364struct big { double v[8]; };
9365struct big grow(struct big b);
9366struct big twice(struct big b) { return grow(grow(b)); }
9367");
9368 assert!(
9369 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
9370 "{text}"
9371 );
9372 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
9373 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
9376 }
9377
9378 #[test]
9379 fn a_structure_passed_to_a_variadic_function_says_so_at_the_call() {
9380 let text = ir("\
9385struct big { double v[8]; };
9386struct pair { int a, b; };
9387int p(const char *, ...);
9388int f(struct big b, struct pair q) { return p(\"\", 1, b, q); }
9389");
9390 assert!(
9391 text.contains("call @p(%4, %5, %2 byval(64, align 8), %6) : (ptr, ...) -> i32"),
9392 "{text}"
9393 );
9394 }
9395
9396 #[test]
9397 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
9398 let body = body(
9401 "\
9402struct pair { int a, b; };
9403struct pair make(int a, int b);
9404int second(void) { return make(1, 2).b; }
9405",
9406 );
9407 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
9408 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
9409 }
9410
9411 #[test]
9412 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
9413 let source = "\
9417struct hfa { float x, y, z; };
9418int take(struct hfa h);
9419int give(struct hfa h) { return take(h); }
9420";
9421 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
9422 let mut opts = options();
9423 opts.emit = EmitKind::Ir;
9424 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
9425 let result = run(&opts, source);
9426 assert_eq!(result.messages, Vec::<String>::new());
9427 assert!(result.text().contains("func @take(f32, f32, f32) -> i32"), "{}", result.text());
9428 }
9429
9430 #[test]
9431 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
9432 let source = "\
9435int use(int *);
9436void f(int n) {
9437 {
9438 int a[n];
9439 use(a);
9440 }
9441 use(0);
9442}
9443";
9444 let body = body(source);
9445 assert!(body.contains("mul.nsw"), "{body}");
9446 assert!(body.contains("stacksave"), "{body}");
9447 assert!(body.contains("alloca %"), "{body}");
9448 assert!(body.contains("stackrestore"), "{body}");
9449 }
9450
9451 #[test]
9452 fn a_goto_out_of_the_scope_of_one_gives_its_stack_back_on_the_way() {
9453 let source = "\
9458int use(int *);
9459int f(int n) {
9460 {
9461 int a[n];
9462 if (use(a)) goto out;
9463 use(0);
9464 }
9465out:
9466 return 0;
9467}
9468";
9469 let body = body(source);
9470 assert_eq!(body.matches("stackrestore").count(), 2, "{body}");
9472 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
9473 assert!(after.starts_with(" %4\n jump block"), "{body}");
9474 }
9475
9476 #[test]
9477 fn a_goto_to_a_label_the_array_is_still_alive_at_leaves_the_stack_alone() {
9478 let source = "\
9482int use(int *);
9483int f(int n) {
9484 int a[n];
9485again:
9486 if (use(a)) goto again;
9487 return 0;
9488}
9489";
9490 let body = body(source);
9491 assert!(body.contains("stacksave"), "{body}");
9492 assert!(!body.contains("stackrestore"), "{body}");
9493 }
9494
9495 #[test]
9496 fn a_goto_back_to_a_label_in_front_of_one_gives_it_back_every_time_round() {
9497 let source = "\
9502int use(int *);
9503int f(int n) {
9504again:
9505 {
9506 int a[n];
9507 if (use(a)) goto again;
9508 }
9509 return 0;
9510}
9511";
9512 let body = body(source);
9513 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
9514 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
9515 assert!(after.starts_with(" %4\n jump block1\n"), "{body}");
9516 }
9517
9518 #[test]
9519 fn the_head_of_a_for_loop_is_a_scope_that_closes_where_the_loop_is_left() {
9520 let source = "\
9526int f(void);
9527void t(void) {
9528 int count = 10;
9529 for (; count--;) {
9530 int b[f()];
9531 int i;
9532 for (i = 0; i < f(); i++) {
9533 b[i] = count;
9534 }
9535 }
9536}
9537";
9538 let body = body(source);
9539 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
9543 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
9544 let next = after.split("\n\n").next().expect("the block the restore is in");
9547 assert!(next.contains("jump block1("), "{body}");
9548 }
9549
9550 #[test]
9551 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
9552 let source = "\
9555unsigned long f(int n) {
9556 int a[n];
9557 n = 0;
9558 return sizeof a;
9559}
9560";
9561 let body = body(source);
9562 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
9564 }
9565
9566 #[test]
9567 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
9568 let source = "\
9571int use(int);
9572int f(int x) {
9573 return ({
9574 int t = use(x);
9575 t * t;
9576 });
9577}
9578";
9579 let expected = "\
9580block0(%0: i32):
9581 %1 = call @use(%0) : (i32) -> i32
9582 %2 = mul.nsw %1, %1
9583 return %2
9584";
9585 assert_eq!(body(source), expected);
9586 }
9587
9588 #[test]
9589 fn a_comma_whose_value_is_an_object_names_the_object_the_right_side_named() {
9590 let source = "\
9594struct pair { int a, b; };
9595void bail(void);
9596int f(struct pair p) {
9597 return (bail(), p).b;
9598}
9599";
9600 let expected = "\
9601block0(%0: i64):
9602 %1 = alloca, size 8, align 4
9603 store %0 -> %1, align 4
9604 call @bail() : ()
9605 %2 = iconst.i64 4
9606 %3 = ptr_add %1, %2
9607 %4 = load.i32 %3, align 4, tbaa !1
9608 return %4
9609";
9610 assert_eq!(body(source), expected);
9611 }
9612
9613 #[test]
9614 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
9615 let source = "int f(int x) { return ({ return x; 0; }); }\n";
9619 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
9620 }
9621
9622 #[test]
9623 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
9624 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
9628 let expected = "\
9629block0(%0: ptr):
9630 %1 = va_arg.f64 %0
9631 %2 = va_arg.f64 %0
9632 %3 = fadd %1, %2
9633 return %3
9634";
9635 assert_eq!(body(source), expected);
9636 }
9637
9638 #[test]
9639 fn one_that_reads_a_structure_answers_where_the_object_is() {
9640 let source = "\
9654struct s { int a; long b; };
9655long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.b; }
9656";
9657 let expected = "\
9658block0(%0: ptr):
9659 %1 = alloca, size 16, align 16
9660 %2 = va_object %0, size 16, align 8, in(int 8 at 0, int 8 at 8)
9661 memcpy %1, %2, size 16, align 8
9662 %3 = iconst.i64 8
9663 %4 = ptr_add %1, %3
9664 %5 = load.i64 %4, align 8, tbaa !1
9665 return %5
9666";
9667 assert_eq!(body(source), expected);
9668 }
9669
9670 #[test]
9674 fn the_classification_says_which_registers_the_object_arrived_in() {
9675 let source = "\
9676struct s { double a; double b; };
9677double f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a; }
9678";
9679 assert!(
9680 body(source)
9681 .contains("va_object %0, size 16, align 8, in(float f64 at 0, float f64 at 8)"),
9682 "{}",
9683 body(source)
9684 );
9685
9686 let big = "\
9687struct s { long a[4]; };
9688long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a[0]; }
9689";
9690 assert!(body(big).contains("va_object %0, size 32, align 8\n"), "{}", body(big));
9691 }
9692
9693 #[test]
9694 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
9695 let source = "\
9699int f(int c) {
9700 void *p = c ? &&one : &&two;
9701 goto *p;
9702one:
9703 return 1;
9704two:
9705 return 2;
9706}
9707";
9708 let expected = "\
9709block0(%0: i32):
9710 %1 = iconst.i32 0
9711 %2 = icmp ne %0, %1
9712 br_if %2, block1, block2
9713
9714block1:
9715 %3 = block_addr block3
9716 jump block4(%3)
9717
9718block2:
9719 %4 = block_addr block5
9720 jump block4(%4)
9721
9722block3:
9723 %5 = iconst.i32 1
9724 return %5
9725
9726block4(%6: ptr):
9727 indirect_br %6, block3, block5
9728
9729block5:
9730 %7 = iconst.i32 2
9731 return %7
9732";
9733 assert_eq!(body(source), expected);
9734 }
9735
9736 fn dispatch(labels: usize) -> String {
9739 let mask = labels - 1;
9740 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
9741 for index in 0..labels {
9742 source.push_str(&format!(" &&a{index},"));
9743 }
9744 source.push_str(" };\n\tint w = n, x = n + 1, y = n + 2, z = n + 3;\n");
9745 source.push_str(&format!("\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
9746 for index in 0..labels {
9747 let step = match index % 4 {
9748 0 => "w += x;",
9749 1 => "x += y;",
9750 2 => "y += z;",
9751 _ => "z += w;",
9752 };
9753 source.push_str(&format!("a{index}:\n\t{step}\n"));
9754 source.push_str("\tif (--n <= 0) return w + x + y + z;\n");
9755 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
9756 }
9757 source.push_str("}\n");
9758 source
9759 }
9760
9761 fn in_front_of_the_jump(text: &str) -> usize {
9763 let (before, _) = text.split_once("\tjmp\t*%").expect("a jump through a register");
9764 before.lines().rev().take_while(|line| line.starts_with("\tmov")).count()
9765 }
9766
9767 #[test]
9777 fn a_jump_through_a_register_writes_what_it_carries_and_not_the_whole_table() {
9778 let small = in_front_of_the_jump(&asm(&dispatch(4)));
9779 let large = in_front_of_the_jump(&asm(&dispatch(32)));
9780 assert_eq!(small, large, "eight times the labels and the same values in hand");
9781 assert!(large <= 8, "the values the loop keeps, and not a set of them per label: {large}");
9782 }
9783
9784 fn crowded(labels: usize) -> String {
9787 const VALUES: usize = 24;
9788 let mask = labels - 1;
9789 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
9790 for index in 0..labels {
9791 source.push_str(&format!(" &&a{index},"));
9792 }
9793 source.push_str(" };\n\t");
9794 for value in 0..VALUES {
9795 source.push_str(&format!("int v{value} = n + {value}; "));
9796 }
9797 let sum: Vec<String> = (0..VALUES).map(|value| format!("v{value}")).collect();
9798 source.push_str(&format!("\n\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
9799 for index in 0..labels {
9800 let (to, from) = (index % VALUES, (index + 1) % VALUES);
9801 source.push_str(&format!("a{index}:\n\tv{to} += v{from};\n"));
9802 source.push_str(&format!("\tif (--n <= 0) return {};\n", sum.join(" + ")));
9803 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
9804 }
9805 source.push_str("}\n");
9806 source
9807 }
9808
9809 fn the_frame(text: &str) -> u64 {
9811 text.lines()
9812 .find_map(|line| {
9813 let (size, _) = line.strip_prefix("\tsubq\t$")?.split_once(", %rsp")?;
9814 size.parse().ok()
9815 })
9816 .expect("a function that opens a frame")
9817 }
9818
9819 #[test]
9828 fn a_frame_holds_what_is_wanted_at_once_and_not_a_slot_for_every_label() {
9829 let small = the_frame(&asm(&crowded(16)));
9830 let large = the_frame(&asm(&crowded(64)));
9831 assert_eq!(small, large, "four times the labels and the same values: {small}, {large}");
9832 }
9833
9834 #[test]
9843 fn a_template_that_names_its_own_registers_gets_the_ones_it_named() {
9844 let source = "void save(void *nlr) {
9845 __asm volatile (
9846 \"movq %%rsp, 32(%%rdi) \\n\"
9847 \"movq %%rbx, 40(%%rdi) \\n\"
9848 \"movq %%r12, 48(%%rdi) \\n\"
9849 : : \"D\" (nlr) : \"memory\");
9850}
9851";
9852 let text = asm(source);
9853 assert!(text.contains("\tmovq\t%rsp, 32(%rdi)\n"), "{text}");
9854 assert!(text.contains("\tmovq\t%rbx, 40(%rdi)\n"), "{text}");
9855 assert!(text.contains("\tmovq\t%r12, 48(%rdi)\n"), "{text}");
9856 }
9857
9858 #[test]
9859 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
9860 let source = "void **next(void);
9863void f(void) { goto *next(); }
9864";
9865 let expected = "\
9866block0:
9867 %0 = call @next() : () -> ptr
9868 unreachable
9869";
9870 assert_eq!(body(source), expected);
9871 }
9872
9873 #[test]
9874 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
9875 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
9878 let expected = "\
9879block0:
9880 inline_asm.volatile \"mfence\", \"\", \"memory\"()
9881 return
9882";
9883 assert_eq!(body(source), expected);
9884 }
9885
9886 #[test]
9887 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
9888 let source = "\
9891int f(int x, int y) {
9892 int r;
9893 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
9894 return r + y;
9895}
9896";
9897 let expected = "\
9898block0(%0: i32, %1: i32):
9899 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
9900 %4 = add.nsw %2, %3
9901 return %4
9902";
9903 assert_eq!(body(source), expected);
9904 }
9905
9906 #[test]
9907 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
9908 let source = "\
9913struct pair { int a, b; };
9914int f(int x) {
9915 int slot = x;
9916 struct pair p = { x, x };
9917 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
9918 return slot + p.a;
9919}
9920";
9921 let text = body(source);
9922 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
9923 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
9924 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
9925 }
9926
9927 #[test]
9928 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
9929 let source = "\
9934int f(int x) {
9935 int r = 7;
9936 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
9937 return r;
9938away:
9939 return r;
9940}
9941";
9942 let expected = "\
9943block0(%0: i32):
9944 %1 = iconst.i32 7
9945 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
9946
9947block1:
9948 return %2
9949
9950block2:
9951 return %1
9952";
9953 assert_eq!(body(source), expected);
9954 }
9955
9956 #[test]
9957 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
9958 let mut opts = options();
9962 opts.emit = EmitKind::Ir;
9963 for (source, expected) in [
9964 (
9965 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
9966 "output operand constraint lacks '='",
9967 ),
9968 (
9969 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
9970 "lvalue required in 'asm' statement",
9971 ),
9972 (
9973 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
9974 "read-only variable 'g' used as 'asm' output",
9975 ),
9976 (
9977 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
9978 "input operand constraint contains '='",
9979 ),
9980 (
9981 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
9982 "memory input 0 is not directly addressable",
9983 ),
9984 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
9985 (
9986 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
9987 "duplicate asm operand name 'a'",
9988 ),
9989 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
9990 ] {
9991 let result = run(&opts, source);
9992 assert!(result.failed(), "expected this to be reported:\n{source}");
9993 assert!(
9994 result.messages.iter().any(|m| m.contains(expected)),
9995 "{expected}\n{:?}",
9996 result.messages
9997 );
9998 }
9999 }
10000
10001 #[test]
10006 fn an_asm_at_file_scope_that_is_directives_becomes_the_objects_it_defines() {
10007 let text = ir(concat!(
10008 "__asm__(\n",
10009 " \".section .rodata\\n\"\n",
10010 " \".globl first\\n\"\n",
10011 " \".balign 8\\n\"\n",
10012 " \"first:\\n\"\n",
10013 " \".long 1\\n\"\n",
10014 " \".long 2\\n\"\n",
10015 " \".globl last\\n\"\n",
10016 " \"last:\\n\"\n",
10017 " \".quad last - first\\n\");\n",
10018 "extern const int first[];\n",
10019 "extern const long last;\n",
10020 ));
10021 assert!(text.contains("global @first : bytes 8 = { i32 1, i32 2 }, align 8"), "{text}");
10022 assert!(text.contains("global @last : i64 = 8"), "{text}");
10023 }
10024
10025 #[test]
10029 fn a_name_an_asm_at_file_scope_defined_is_not_undone_by_a_declaration_of_it() {
10030 let text = ir(concat!(
10031 "__asm__(\".data\\n.globl counter\\ncounter:\\n.long 7\\n\");\n",
10032 "extern int counter;\n",
10033 "int read(void) { return counter; }\n",
10034 ));
10035 assert!(text.contains("global @counter : i32 = 7"), "{text}");
10036 }
10037
10038 #[test]
10043 fn bytes_under_no_label_at_file_scope_are_a_global_in_front_of_the_label() {
10044 let text = ir(concat!(
10045 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n662:\\n",
10046 ".pushsection .data.ignore\\n.byte 7\\n.popsection\\n.byte 662b - 661b\\n\");\n",
10047 "extern unsigned char stuff[];\n",
10048 "int read(void) { return stuff[0]; }\n",
10049 ));
10050 let under = text.find("global @.Lasm.0 : i8 = 41").expect(&text);
10051 let named = text.find("global @stuff : i8 = 42").expect(&text);
10052 assert!(under < named, "the bytes under no label come first: {text}");
10053 assert!(text.contains("global @.Lasm.1 : i8 = 7, align 1, linkage(internal), section"));
10054 let after = text.find("global @.Lasm.2 : i8 = 1").expect(&text);
10059 assert!(named < after, "{text}");
10060 }
10061
10062 #[test]
10067 fn a_distance_from_here_at_file_scope_is_a_hole_naming_the_global_it_measures_to() {
10068 let text = ir(concat!(
10069 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n",
10070 ".pushsection .data.ignore\\n.long 661b - .\\n.popsection\\n\");\n",
10071 "extern unsigned char stuff[];\n",
10072 "int read(void) { return stuff[0]; }\n",
10073 ));
10074 assert!(text.contains("global @.Lasm.1 : bytes 4 = { away.4 @stuff }"), "{text}");
10078 }
10079
10080 #[test]
10085 fn a_set_at_file_scope_is_a_second_name_for_what_it_names() {
10086 let text = ir(concat!(
10087 "void base(void) {}\n",
10088 "__asm__(\".weak one\\n.set one, base\");\n",
10089 "__asm__(\".globl two\\n.set two, base\");\n",
10090 "__asm__(\".set three, base\");\n",
10091 "void three(void) {}\n",
10092 ));
10093 assert!(text.contains("alias @one = @base, linkage(weak)"), "{text}");
10094 assert!(text.contains("alias @two = @base"), "{text}");
10095 assert!(!text.contains("alias @three"), "a definition of the name wins: {text}");
10096 assert!(text.contains("func @three"), "{text}");
10097 }
10098
10099 #[test]
10104 fn a_set_of_a_name_this_file_does_not_define_says_so() {
10105 let messages = errors("__asm__(\".set here, elsewhere\");\n");
10106 assert!(
10107 messages
10108 .iter()
10109 .any(|m| m.contains("'here' is aliased to undefined symbol 'elsewhere'")
10110 && m.contains("E0697")),
10111 "{messages:?}"
10112 );
10113 }
10114
10115 #[test]
10118 fn an_incbin_at_file_scope_is_the_bytes_of_the_file_it_names() {
10119 let mut opts = options();
10120 opts.emit = EmitKind::Ir;
10121 let mut fs = MemoryFileSystem::new();
10122 fs.insert(
10123 "/main.c",
10124 b"__asm__(\".data\\n.globl blob\\nblob:\\n.incbin \\\"seed\\\"\\n\");\n".to_vec(),
10125 );
10126 fs.insert("seed", b"hi".to_vec());
10127 let result = compile(&opts, "/main.c", &fs);
10128 assert_eq!(result.messages, Vec::<String>::new());
10129 let text = result.text();
10130 assert!(text.contains("global @blob : bytes 2 = { bytes \"hi\" }"), "{text}");
10131 }
10132
10133 #[test]
10136 fn an_incbin_naming_a_file_that_is_not_there_says_which_file() {
10137 let messages = errors("__asm__(\".data\\nb:\\n.incbin \\\"nowhere\\\"\\n\");\n");
10138 assert!(
10139 messages
10140 .iter()
10141 .any(|m| m.contains("cannot open 'nowhere' for reading") && m.contains("E0702")),
10142 "{messages:?}"
10143 );
10144 }
10145
10146 #[test]
10150 fn a_directive_in_an_asm_at_file_scope_is_refused_rather_than_ignored() {
10151 let source = "__asm__(\".data\\n.set alias, 4\\n\");\n";
10152 let messages = errors(source);
10153 assert!(
10154 messages
10155 .iter()
10156 .any(|m| m.contains("not supported yet") && m.contains("in an `asm` at file scope")),
10157 "{source}\n{messages:?}"
10158 );
10159 }
10160
10161 #[test]
10171 fn a_naked_function_is_its_own_prologue_and_its_own_ending() {
10172 let text = asm(concat!(
10173 "unsigned nlr_push_tail(void *nlr);\n",
10174 "__attribute__((naked)) unsigned nlr_push(void *nlr) {\n",
10175 " __asm volatile(\n",
10176 " \"movq (%rsp), %rax\\n\"\n",
10177 " \"movq %rax, 16(%rdi)\\n\"\n",
10178 " \"movq %rbx, 40(%rdi)\\n\"\n",
10179 " \"jmp nlr_push_tail\\n\");\n",
10180 "}\n",
10181 ));
10182 assert!(text.contains("\tmovq\t(%rsp), %rax\n"), "{text}");
10183 assert!(text.contains("\tjmp\tnlr_push_tail\n"), "{text}");
10184 assert!(text.contains("\tud2\n"), "{text}");
10185 assert!(!text.contains("\tpushq\t"), "nothing is saved in front of it: {text}");
10186 assert!(!text.contains("\tret\n"), "the jump is where it ends: {text}");
10187 }
10188
10189 #[test]
10192 fn what_a_function_without_a_prologue_cannot_be_given_is_refused() {
10193 let mut opts = options();
10194 opts.emit = EmitKind::Asm;
10195 for (source, why) in [
10196 (
10197 "__attribute__((naked)) void f(void) { volatile long a[8]; a[0] = 1; }\n",
10198 "bytes of frame",
10199 ),
10200 (
10201 "__attribute__((naked)) void f(int n) { char a[n]; __asm(\"nop\" ::\"r\"(a)); }\n",
10202 "has no prologue to point a frame pointer at it with",
10203 ),
10204 ("void elsewhere(void); void f(void) { __asm(\"jmp elsewhere\"); }\n", "jumps out of"),
10205 ] {
10206 let result = run(&opts, source);
10207 assert!(result.failed(), "expected this to be refused:\n{source}");
10208 assert!(
10209 result.messages.iter().any(|message| message.contains(why)),
10210 "{:?}",
10211 result.messages
10212 );
10213 }
10214 }
10215
10216 #[test]
10217 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
10218 let mut opts = options();
10219 opts.emit = EmitKind::Ir;
10220 for source in [
10221 "int f(int n) { void *p = &&out; if (n) goto *p; { int a[n]; out: return 1; } }\n",
10222 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
10223 ] {
10224 let result = run(&opts, source);
10225 assert!(result.failed(), "expected this to be reported:\n{source}");
10226 assert!(
10227 result.messages.iter().any(|m| m.contains("not supported yet")),
10228 "{:?}",
10229 result.messages
10230 );
10231 }
10232 }
10233
10234 fn round_trip(source: &str) -> (String, String) {
10236 let printed = ir(source);
10237 let mut opts = options();
10238 opts.emit = EmitKind::Ir;
10239 let mut fs = MemoryFileSystem::new();
10240 fs.insert("/main.ir", printed.clone().into_bytes());
10241 let result = compile_ir(&opts, "/main.ir", &fs);
10242 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
10243 (printed, result.text().to_owned())
10244 }
10245
10246 #[test]
10247 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
10248 let (printed, again) = round_trip(
10252 "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",
10253 );
10254 assert_eq!(printed, again);
10255 }
10256
10257 #[test]
10258 fn ir_that_is_not_ir_says_which_line_stopped_it() {
10259 let mut opts = options();
10260 opts.emit = EmitKind::Ir;
10261 let mut fs = MemoryFileSystem::new();
10262 let text = "\
10263; ModuleID = 'a.c'
10264; format 0
10265target triple = \"x86_64-unknown-linux-gnu\"
10266target datalayout = \"e-p:64:64-i64:64-S128\"
10267
10268func @f(), linkage(external) {
10269block0:
10270 frobnicate
10271}
10272";
10273 fs.insert("/main.ir", text.as_bytes().to_vec());
10274 let result = compile_ir(&opts, "/main.ir", &fs);
10275 assert!(result.failed());
10276 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
10277 }
10278
10279 #[test]
10280 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
10281 let mut opts = options();
10284 opts.emit = EmitKind::Ir;
10285 let mut fs = MemoryFileSystem::new();
10286 let text = "\
10287; ModuleID = 'a.c'
10288; format 0
10289target triple = \"x86_64-unknown-linux-gnu\"
10290target datalayout = \"e-p:64:64-i64:64-S128\"
10291
10292func @f(), linkage(external) {
10293block0:
10294 %0 = iconst.i32 1
10295 return %0
10296}
10297";
10298 fs.insert("/main.ir", text.as_bytes().to_vec());
10299 let result = compile_ir(&opts, "/main.ir", &fs);
10300 assert!(result.failed());
10301 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
10302 }
10303
10304 #[test]
10305 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
10306 let mut fs = MemoryFileSystem::new();
10308 fs.insert("/main.ir", Vec::new());
10309 let result = compile_ir(&options(), "/main.ir", &fs);
10310 assert!(result.failed());
10311 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
10312 }
10313
10314 #[test]
10315 fn the_printed_ir_reads_back_as_the_same_module() {
10316 let text = ir("\
10319struct point { int x, y; };
10320static const char greeting[] = \"hi\";
10321int table[4] = { 1, 2, 3 };
10322int puts(const char *);
10323double half(double x) { return x / 2.0; }
10324int f(int n) {
10325 int total = 0;
10326 for (int i = 0; i < n; i++) {
10327 if (i == 3) continue;
10328 total += table[i];
10329 }
10330 switch (n) {
10331 case 0: total = 1;
10332 case 1: total++; break;
10333 default: total = -total;
10334 }
10335 struct point p = { total, 1 };
10336 int *q = &p.y;
10337 puts(greeting);
10338 return p.x + *q;
10339}
10340int dispatch(int c) {
10341 void *p = c ? &&one : &&two;
10342 goto *p;
10343one:
10344 return 1;
10345two:
10346 return 2;
10347}
10348int assembly(int x, int *p) {
10349 int r;
10350 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
10351 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
10352 return r;
10353away:
10354 return 0;
10355}
10356");
10357 let mut names = Interner::new();
10358 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
10359 assert_eq!(rucc_ir::print(&module, &names), text);
10360 }
10361
10362 #[test]
10363 fn what_save_temps_keeps_is_the_text_that_was_compiled_and_the_assembly_that_was_assembled() {
10364 let mut opts = options();
10368 opts.emit = EmitKind::Object;
10369 opts.save_temps = rucc_session::SaveTemps::Object;
10370 let result = run(&opts, "#define N 2\nint a[N];\n");
10371 assert_eq!(result.messages, Vec::<String>::new());
10372 let text = result.temps.preprocessed.expect("the preprocessed text");
10373 assert!(text.contains("int a[2];"), "{text}");
10374 assert!(text.starts_with("# 1 \"/main.c\""), "{text}");
10375 let asm = result.temps.assembly.expect("the assembly");
10376 assert!(asm.contains("a:"), "{asm}");
10377 assert!(matches!(result.artifact, Artifact::Object { .. }), "{:?}", result.artifact);
10378 }
10379
10380 #[test]
10381 fn nothing_is_kept_unless_the_flag_asked_for_it() {
10382 let mut opts = options();
10385 opts.emit = EmitKind::Object;
10386 assert_eq!(run(&opts, "int a;\n").temps, Temps::default());
10387 }
10388
10389 #[test]
10390 fn a_compilation_that_stops_before_the_back_end_keeps_the_text_and_no_assembly() {
10391 let mut opts = options();
10394 opts.emit = EmitKind::Ir;
10395 opts.save_temps = rucc_session::SaveTemps::Cwd;
10396 let result = run(&opts, "int a;\n");
10397 assert!(result.temps.preprocessed.is_some());
10398 assert_eq!(result.temps.assembly, None);
10399 }
10400
10401 fn span(from: u64, len: u64, held: rucc_debug::Held) -> rucc_debug::Span {
10403 rucc_debug::Span { from, len, held }
10404 }
10405
10406 #[test]
10407 fn two_stretches_that_meet_and_agree_come_out_as_one() {
10408 let one = span(0, 4, rucc_debug::Held::Reg(3));
10409 let two = span(4, 4, rucc_debug::Held::Reg(3));
10410 assert_eq!(settle(vec![two, one]), vec![span(0, 8, rucc_debug::Held::Reg(3))]);
10411 }
10412
10413 #[test]
10414 fn a_stretch_another_starts_inside_and_disagrees_with_ends_where_the_other_starts() {
10415 let one = span(0, 8, rucc_debug::Held::Reg(3));
10416 let two = span(4, 8, rucc_debug::Held::Reg(4));
10417 let settled = settle(vec![one, two]);
10420 assert_eq!(
10421 settled,
10422 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 8, rucc_debug::Held::Reg(4))]
10423 );
10424 }
10425
10426 #[test]
10427 fn a_stretch_cut_by_one_that_ends_first_does_not_come_back_after_it() {
10428 let one = span(0, 16, rucc_debug::Held::Reg(3));
10431 let two = span(4, 4, rucc_debug::Held::Reg(4));
10432 assert_eq!(
10433 settle(vec![one, two]),
10434 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 4, rucc_debug::Held::Reg(4))]
10435 );
10436 }
10437
10438 #[test]
10439 fn a_stretch_inside_another_that_agrees_with_it_cuts_nothing() {
10440 let one = span(0, 16, rucc_debug::Held::Reg(3));
10441 let two = span(4, 4, rucc_debug::Held::Reg(3));
10442 assert_eq!(settle(vec![one, two]), vec![span(0, 16, rucc_debug::Held::Reg(3))]);
10443 }
10444
10445 #[test]
10446 fn a_stretch_two_others_disagree_over_the_whole_of_says_nothing_at_all() {
10447 let one = span(0, 8, rucc_debug::Held::Reg(3));
10448 let two = span(0, 8, rucc_debug::Held::Frame(-16));
10449 assert_eq!(settle(vec![one, two]), Vec::new());
10450 }
10451
10452 #[test]
10453 fn stretches_with_a_gap_between_them_keep_the_gap() {
10454 let one = span(0, 4, rucc_debug::Held::Reg(3));
10455 let two = span(16, 4, rucc_debug::Held::Reg(3));
10456 assert_eq!(settle(vec![one, two]), vec![one, two]);
10457 }
10458
10459 fn extent(len: usize) -> rucc_object::Extent {
10461 rucc_object::Extent {
10462 name: "f".to_owned(),
10463 start: 0,
10464 len,
10465 align: 1,
10466 binding: rucc_object::Binding::Global,
10467 visibility: rucc_object::Visibility::Default,
10468 patch: None,
10469 landings: Vec::new(),
10470 }
10471 }
10472
10473 fn row(at: usize, lo: u32, hi: u32) -> rucc_asm::Row {
10475 let span = Span::new(lo, hi);
10476 rucc_asm::Row { at, span, inst: None }
10477 }
10478
10479 #[test]
10480 fn a_row_ends_where_the_next_address_begins() {
10481 let rows = [row(0, 0, 1), row(4, 1, 2), row(10, 2, 3)];
10482 assert_eq!(ends(&extent(16), &rows), vec![4, 10, 16]);
10483 }
10484
10485 #[test]
10486 fn rows_sharing_an_address_all_end_where_the_next_address_begins() {
10487 let rows = [row(0, 0, 1), row(4, 1, 2), row(4, 2, 3), row(4, 3, 4)];
10490 assert_eq!(ends(&extent(12), &rows), vec![4, 12, 12, 12]);
10491 }
10492
10493 #[test]
10494 fn the_rows_of_a_scope_that_are_next_to_each_other_come_out_as_one_stretch() {
10495 let rows = [row(0, 0, 4), row(4, 10, 14), row(8, 14, 18), row(12, 40, 44)];
10496 let ends = ends(&extent(16), &rows);
10497 let scope = Span::new(8, 20);
10498 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 8 }]);
10499 }
10500
10501 #[test]
10502 fn a_scope_the_back_end_split_in_two_comes_out_as_two_stretches() {
10503 let rows = [row(0, 10, 14), row(4, 40, 44), row(8, 14, 18)];
10504 let ends = ends(&extent(12), &rows);
10505 let scope = Span::new(8, 20);
10506 let over = spread(scope, &ends, &rows);
10507 assert_eq!(
10508 over,
10509 vec![rucc_debug::Reach { from: 0, len: 4 }, rucc_debug::Reach { from: 8, len: 4 }]
10510 );
10511 }
10512
10513 #[test]
10514 fn a_row_with_no_source_of_its_own_belongs_to_no_scope() {
10515 let rows = [rucc_asm::Row { at: 0, span: Span::DUMMY, inst: None }, row(4, 10, 14)];
10517 let ends = ends(&extent(8), &rows);
10518 let scope = Span::new(0, 20);
10519 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 4 }]);
10520 }
10521
10522 fn scope(parent: Option<usize>, lo: u32, hi: u32) -> crate::shapes::Scope {
10524 let span = Span::new(lo, hi);
10525 crate::shapes::Scope { parent, span }
10526 }
10527
10528 #[test]
10529 fn a_function_gets_the_scopes_its_own_locals_are_in_and_nothing_else() {
10530 let scopes = [scope(None, 0, 10), scope(None, 20, 30), scope(Some(1), 22, 26)];
10532 let rows = [row(0, 22, 24), row(4, 26, 28)];
10533 let (out, at) = nests(&[Some(2)], &scopes, &extent(8), &rows);
10534 assert_eq!(at.get(&1), Some(&0));
10537 assert_eq!(at.get(&2), Some(&1));
10538 assert_eq!(at.get(&0), None);
10539 assert_eq!(out.len(), 2);
10540 assert_eq!(out[0].parent, None);
10541 assert_eq!(out[1].parent, Some(0));
10542 assert_eq!(out[0].over, vec![rucc_debug::Reach { from: 0, len: 8 }]);
10543 assert_eq!(out[1].over, vec![rucc_debug::Reach { from: 0, len: 4 }]);
10544 }
10545
10546 #[test]
10547 fn a_local_written_straight_into_the_body_pulls_no_scope_in() {
10548 let scopes = [scope(None, 20, 30)];
10549 let rows = [row(0, 22, 24)];
10550 let (out, at) = nests(&[None], &scopes, &extent(4), &rows);
10551 assert_eq!(out, Vec::new());
10552 assert!(at.is_empty());
10553 }
10554
10555 #[test]
10556 fn a_scope_whose_code_all_went_away_is_still_one_of_the_functions_scopes() {
10557 let scopes = [scope(None, 20, 30)];
10561 let rows = [row(0, 40, 44)];
10562 let (out, at) = nests(&[Some(0)], &scopes, &extent(4), &rows);
10563 assert_eq!(at.get(&0), Some(&0));
10564 assert_eq!(out.len(), 1);
10565 assert_eq!(out[0].over, Vec::new());
10566 }
10567}