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_cost::Goal;
23use rucc_diag::{Diagnostic, Severity, SourceMap, Span};
24use rucc_ir::{FpContract, Pic as IrPic, Visibility as IrVisibility};
25use rucc_lex::{Convert, Keywords, PpToken, convert};
26use rucc_lower::Protector as LowerProtector;
27use rucc_sema::{Checker, Context as CheckContext};
28use rucc_session::{
29 Contract, EmitKind, FileSystem, Options, Padding, Pic, Protector, Session, Visibility,
30};
31use rucc_target::TargetInfo;
32use rucc_tuple::{Arch, ObjectFormat};
33
34use crate::preprocess::render;
35
36#[derive(Debug, Clone, PartialEq, Eq, Default)]
43pub enum Artifact {
44 #[default]
47 Nothing,
48 Text(String),
50 Object {
57 bytes: Vec<u8>,
59 defines: Vec<String>,
63 },
64}
65
66impl Artifact {
67 #[must_use]
69 pub fn bytes(&self) -> &[u8] {
70 match self {
71 Artifact::Nothing => &[],
72 Artifact::Text(text) => text.as_bytes(),
73 Artifact::Object { bytes, .. } => bytes,
74 }
75 }
76}
77
78#[derive(Debug, Clone, PartialEq, Eq)]
80pub struct Compiled {
81 pub artifact: Artifact,
83 pub messages: Vec<String>,
85 pub errors: u32,
87 pub fired: Fired,
93 pub pressure: Pressure,
98 pub lowerings: Lowerings,
103 pub dumps: Vec<rucc_opt::Dump>,
109 pub remarks: String,
115 pub deps: Vec<rucc_pp::Dependency>,
120 pub temps: Temps,
127 pub timing: crate::trace::Timing,
129}
130
131#[derive(Debug, Clone, PartialEq, Eq, Default)]
138pub struct Temps {
139 pub preprocessed: Option<String>,
141 pub assembly: Option<String>,
143}
144
145impl Compiled {
146 #[must_use]
148 pub fn failed(&self) -> bool {
149 self.errors > 0
150 }
151
152 #[must_use]
157 pub fn text(&self) -> &str {
158 match &self.artifact {
159 Artifact::Text(text) => text,
160 _ => "",
161 }
162 }
163}
164
165#[must_use]
178pub fn compile(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
179 let mut clock = crate::trace::Clock::start();
180 let mut sess = Session::new(opts.clone());
181 let keywords = Keywords::new(&mut sess.interner, opts.std, opts.gnu_extensions);
185 let mut diagnostics: Vec<Diagnostic> = Vec::new();
186 let mut fired = Fired::new();
188 let mut pressure = Pressure::new();
190 let mut lowerings = Lowerings::asked(opts.lowering_dump.is_some());
191 let mut dumps = Vec::new();
193 let mut remarks = String::new();
194 let mut passes = Vec::new();
196 let mut temps = Temps::default();
198
199 let bytes = match fs.read(Path::new(name)) {
200 Ok(bytes) => bytes,
201 Err(e) => return failure(format!("{name}: {e}")),
202 };
203 let Ok(file) = sess.sources.add_shared(crate::phase::source_name(name), bytes, None) else {
204 return failure(format!("{name}: the source map has no room left for this file"));
205 };
206 clock.lap("read");
207
208 let mut pp = rucc_pp::Preprocessor::with_prefix_map(opts.prefix_map.macros.clone());
212 let predef = rucc_pp::Predef::for_options(opts);
213 let expanded: Vec<PpToken> = {
214 let mut tokens = Vec::new();
215 {
220 let mut cx =
221 rucc_pp::Context::new(&mut sess.interner, &mut sess.sources, fs, &opts.search);
222 cx.lex = rucc_lex::Options::for_dialect(opts.std, opts.gnu_extensions);
223 cx.pedantic = opts.pedantic;
224 if pp.predefine(&sess.target, &predef, &mut cx).is_err() {
225 return failure(format!(
226 "{name}: the source map has no room for the built in macros"
227 ));
228 }
229 if pp.preinclude(&opts.preincludes, &mut tokens, &mut cx).is_err() {
230 return failure(format!("{name}: the source map has no room for the command line"));
231 }
232 tokens.append(&mut pp.run(file, &mut cx));
233 }
234 if opts.save_temps.wanted() {
235 temps.preprocessed = Some(rucc_pp::print(
236 file,
237 &tokens,
238 pp.line_directives(),
239 &sess.sources,
240 &sess.interner,
241 rucc_pp::PrintOptions { line_markers: opts.line_markers },
242 ));
243 }
244 tokens.iter().map(|token| token.to_pp()).collect()
245 };
246 diagnostics.extend(pp.take_diagnostics());
247 let deps = pp.dependencies().to_vec();
250 clock.lap("preprocess");
251
252 let cx = Convert {
255 keywords: &keywords,
256 interner: &sess.interner,
257 target: &sess.target,
258 std: opts.std,
259 gnu: opts.gnu_extensions,
260 pedantic: opts.pedantic,
261 };
262 let (tokens, complaints) = convert(&expanded, &cx);
263 diagnostics.extend(complaints);
264 clock.lap("convert");
265
266 let type_names: Vec<Symbol> =
268 sess.target.type_names().iter().filter_map(|&(name, _)| sess.interner.find(name)).collect();
269 let parsed = rucc_parse::parse(
270 &tokens,
271 rucc_parse::Context {
272 interner: &sess.interner,
273 std: opts.std,
274 gnu: opts.gnu_extensions,
275 pedantic: opts.pedantic,
276 error_limit: opts.error_limit as usize,
277 type_names: &type_names,
278 },
279 );
280 clock.lap("parse");
281 let parse_failed = parsed.diagnostics.iter().any(|d| d.severity.is_fatal());
282 diagnostics.extend(parsed.diagnostics);
283
284 let mut artifact = Artifact::Nothing;
285 let mut instrumented = Instrumented::default();
288 if !parse_failed {
289 let mut checker = Checker::new(
290 &parsed.ast,
291 CheckContext {
292 names: &sess.interner,
293 target: &sess.target,
294 std: opts.std,
295 gnu: opts.gnu_extensions,
296 pedantic: opts.pedantic,
297 permissive: opts.permissive,
298 gnu89_inline: opts.gnu89_inline,
299 error_limit: opts.error_limit as usize,
300 builtins: opts.builtins && opts.hosted,
303 no_builtin: &opts.no_builtin,
304 short_enums: opts.short_enums,
305 ms_extensions: sess.ms_extensions(),
306 trapping_math: opts.trapping_math,
307 isa: opts.isa,
308 },
309 );
310 checker.check_unit();
311 let checked = checker.finish();
312 clock.lap("check");
313 if !checked.failed() {
314 match opts.emit {
315 EmitKind::Tast => {
316 artifact = Artifact::Text(rucc_sema::print(
317 &checked.tast,
318 &checked.types,
319 &sess.interner,
320 ));
321 }
322 EmitKind::TypeGranules => {
326 artifact = Artifact::Text(rucc_types::granule_report(
327 &checked.types,
328 &sess.interner,
329 &sess.target,
330 ));
331 }
332 EmitKind::Ir
333 | EmitKind::MirFinal
334 | EmitKind::Asm
335 | EmitKind::Object
336 | EmitKind::Archive
337 | EmitKind::Executable
338 | EmitKind::SafetySummary => {
339 let mut read = |named: &str| {
344 fs.read(Path::new(named))
345 .map(|bytes| bytes.as_slice().to_vec())
346 .map_err(|why| why.to_string())
347 };
348 let meaning = if opts.debug_info {
354 crate::shapes::collect(
355 &checked.tast,
356 &checked.types,
357 &sess.target,
358 &sess.interner,
359 &sess.sources,
360 )
361 } else {
362 crate::shapes::Meaning::default()
363 };
364 let mut lowered = rucc_lower::lower(
365 crate::phase::source_name(name),
366 rucc_lower::Context {
367 tast: &checked.tast,
368 types: &checked.types,
369 target: &sess.target,
370 names: &mut sess.interner,
371 visibility: match opts.visibility {
372 Visibility::Default => IrVisibility::Default,
373 Visibility::Hidden => IrVisibility::Hidden,
374 Visibility::Protected => IrVisibility::Protected,
375 },
376 protector: match opts.protector {
377 Protector::None => LowerProtector::None,
378 Protector::Buffers => LowerProtector::Buffers,
379 Protector::Strong => LowerProtector::Strong,
380 Protector::All => LowerProtector::All,
381 },
382 wrapping: rucc_lower::Wrapping {
383 signed: opts.wrapping.signed,
384 pointer: opts.wrapping.pointer,
385 trap: opts.wrapping.trap,
386 },
387 aliasing: opts.strict_aliasing,
388 padding: opts.padding == Padding::Ignored,
389 contract: match opts.fp_contract {
390 Contract::Off => FpContract::Off,
391 Contract::On => FpContract::On,
392 Contract::Fast => FpContract::Fast,
393 },
394 align: opts.align_functions,
395 instrument: opts.instrument_functions,
396 exceptions: opts.exceptions,
397 read: &mut read,
398 },
399 );
400 clock.lap("lower");
404 let failed = lowered.diagnostics.iter().any(|d| d.severity.is_fatal());
405 if !failed {
406 if let Err(errors) = clock
411 .time("verify", || rucc_ir::verify(&lowered.module, &sess.interner))
412 {
413 for error in errors {
414 diagnostics.push(internal(&format!("invalid IR, {error}")));
415 }
416 } else if let Err(complaints) = clock
417 .time("instrument", || {
418 instrument(&mut lowered.module, &mut sess.interner, opts)
419 })
420 .map(|done| instrumented = done)
421 {
422 diagnostics.extend(complaints);
423 } else if let Err(complaints) = clock
424 .time("optimize", || {
425 optimize(
426 &mut lowered.module,
427 &mut sess.interner,
428 &sess.target,
429 opts,
430 name,
431 &mut dumps,
432 &mut remarks,
433 )
434 })
435 .map(|times| passes = times)
436 {
437 diagnostics.extend(complaints);
438 } else if opts.emit == EmitKind::SafetySummary {
439 artifact = Artifact::Text(
444 rucc_safety::summarize(
445 &lowered.module,
446 &sess.interner,
447 name,
448 opts.safety.as_str(),
449 instrumented.checks,
450 instrumented.interposed,
451 instrumented.crossings,
452 )
453 .render(),
454 );
455 } else if opts.emit == EmitKind::Ir {
456 artifact =
461 Artifact::Text(rucc_ir::print(&lowered.module, &sess.interner));
462 } else {
463 let made = clock.time("generate", || {
466 generate(
467 &mut lowered.module,
468 &mut sess.interner,
469 &sess.target,
470 opts,
471 &mut Recording {
472 fired: &mut fired,
473 pressure: &mut pressure,
474 lowerings: &mut lowerings,
475 },
476 &mut temps.assembly,
477 Origin { map: &sess.sources, name, meaning: &meaning },
478 )
479 });
480 match made {
481 Ok(made) => artifact = made,
482 Err(complaints) => diagnostics.extend(complaints),
483 }
484 }
485 }
486 diagnostics.extend(lowered.diagnostics);
487 }
488 EmitKind::SyntaxOnly => {}
490 _ => {}
491 }
492 }
493 diagnostics.extend(checked.diagnostics);
494 }
495 let mut wants = rucc_opt::Wants::none();
498 for spec in &opts.opt_info {
499 let _ = wants.add(spec);
501 }
502 if wants.wants(rucc_opt::stats::Kind::Optimized) {
503 remarks.push_str(&lowerings.remarks(name));
504 }
505
506 let mut messages = Vec::with_capacity(diagnostics.len());
507 let mut errors = 0;
508 for diag in &diagnostics {
509 if rucc_diag::dropped(diag, &sess.sources, opts.warnings, opts.system_header_warnings) {
515 continue;
516 }
517 if diag.severity.is_fatal()
518 || (diag.severity == Severity::Warning && opts.warnings_are_errors)
519 {
520 errors += 1;
521 }
522 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
523 }
524 if errors > 0 {
525 artifact = Artifact::Nothing;
527 }
528 clock.passes(passes);
531 let timing = clock.finish();
532 Compiled {
533 artifact,
534 messages,
535 errors,
536 fired,
537 pressure,
538 lowerings,
539 dumps,
540 remarks,
541 deps,
542 temps,
543 timing,
544 }
545}
546
547#[must_use]
557pub fn compile_ir(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
558 let mut sess = Session::new(opts.clone());
559 if opts.emit != EmitKind::Ir {
560 return failure(format!(
561 "{name}: an input of IR can only be emitted as IR, and `--emit={}` asks for what \
562 the C in front of it became",
563 opts.emit.as_str()
564 ));
565 }
566 let bytes = match fs.read(Path::new(name)) {
567 Ok(bytes) => bytes,
568 Err(e) => return failure(format!("{name}: {e}")),
569 };
570 let Ok(text) = std::str::from_utf8(bytes.as_slice()) else {
571 return failure(format!("{name}: this is not text, so it is not IR"));
572 };
573
574 let module = match rucc_ir::parse(text, &mut sess.interner) {
575 Ok(module) => module,
576 Err(error) => {
577 return failure(format!("{name}:{}: {}", error.line, error.message));
578 }
579 };
580 let mut diagnostics: Vec<Diagnostic> = Vec::new();
581 if let Err(errors) = rucc_ir::verify(&module, &sess.interner) {
582 for error in errors {
583 diagnostics.push(invalid(&format!("invalid IR, {error}")));
584 }
585 }
586 let mut messages = Vec::with_capacity(diagnostics.len());
587 for diag in &diagnostics {
588 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
589 }
590 let errors = u32::try_from(messages.len()).unwrap_or(u32::MAX);
591 let artifact = if errors > 0 {
592 Artifact::Nothing
593 } else {
594 Artifact::Text(rucc_ir::print(&module, &sess.interner))
595 };
596 Compiled {
598 artifact,
599 messages,
600 errors,
601 fired: Fired::new(),
602 pressure: Pressure::new(),
603 lowerings: Lowerings::new(),
604 dumps: Vec::new(),
605 remarks: String::new(),
606 deps: Vec::new(),
607 temps: Temps::default(),
608 timing: crate::trace::Timing::default(),
609 }
610}
611
612fn instrument(
635 module: &mut rucc_ir::Module,
636 names: &mut Interner,
637 opts: &Options,
638) -> Result<Instrumented, Vec<Diagnostic>> {
639 if !opts.safety.instruments() {
640 return Ok(Instrumented::default());
641 }
642 let mut checks = rucc_safety::run(module, opts.subobject, opts.promise, opts.races);
643 checks.freed = rucc_safety::ending::checks(module, names);
651 let interposed = rucc_safety::redirect(module, names);
656 let crossings = rucc_safety::witness(module, names);
659 match rucc_ir::verify(module, names) {
660 Ok(()) => Ok(Instrumented { checks, interposed, crossings }),
661 Err(errors) => Err(errors
662 .iter()
663 .map(|e| internal(&format!("invalid IR after check insertion, {e}")))
664 .collect()),
665 }
666}
667
668#[derive(Clone, Copy, Debug, Default)]
674struct Instrumented {
675 checks: rucc_safety::Counts,
677 interposed: usize,
679 crossings: rucc_safety::Sites,
681}
682
683fn optimize(
697 module: &mut rucc_ir::Module,
698 names: &mut Interner,
699 target: &TargetInfo,
700 opts: &Options,
701 file: &str,
702 dumps: &mut Vec<rucc_opt::Dump>,
703 remarks: &mut String,
704) -> Result<Vec<(&'static str, std::time::Duration)>, Vec<Diagnostic>> {
705 let mut settings = rucc_opt::Options::for_level(opts.opt_level);
706 settings.interposition = match opts.interposition {
712 true => replaceable(target, opts),
713 false => IrPic::Executable,
714 };
715 settings.toggles.clone_from(&opts.passes);
716 settings.builtins = opts.builtins && opts.hosted;
721 settings.no_builtin.clone_from(&opts.no_builtin);
722 settings.isa = opts.isa;
725 settings.fuel = opts.pass_fuel.iter().cloned().collect();
726 settings.global_fuel = opts.pass_fuel_global;
727 settings.verify |= opts.verify_each;
728 for (on, spec) in &opts.pass_gates {
729 if let Err(why) = settings.gates.add(*on, spec) {
732 return Err(vec![internal(&why)]);
733 }
734 }
735 for spec in &opts.dump_ir {
736 if let Err(why) = settings.dumps.add(spec) {
739 return Err(vec![internal(&why)]);
740 }
741 }
742 let mut wants = rucc_opt::Wants::none();
743 for spec in &opts.opt_info {
744 if let Err(why) = wants.add(spec) {
747 return Err(vec![internal(&why)]);
748 }
749 }
750 let report = rucc_opt::run(module, names, &settings);
751 remarks.push_str(&rucc_opt::optinfo::render(file, &report, names, wants));
752 dumps.extend(report.dumps);
753 match report.broke.is_empty() {
754 true => Ok(report.time),
755 false => Err(report.broke.iter().map(|why| internal(why)).collect()),
756 }
757}
758
759fn replaceable(target: &TargetInfo, opts: &Options) -> IrPic {
793 match (target.tuple.os().object_format(), opts.pic) {
794 (Some(ObjectFormat::Elf), Pic::Library) => IrPic::Library,
795 _ => IrPic::Executable,
796 }
797}
798
799#[derive(Clone, Copy)]
806struct Origin<'a> {
807 map: &'a SourceMap,
809 name: &'a str,
811 meaning: &'a crate::shapes::Meaning,
813}
814
815fn generate(
816 module: &mut rucc_ir::Module,
817 names: &mut Interner,
818 target: &TargetInfo,
819 opts: &Options,
820 recording: &mut Recording<'_>,
821 assembly: &mut Option<String>,
822 origin: Origin<'_>,
823) -> Result<Artifact, Vec<Diagnostic>> {
824 let Some(machine) = Machine::for_target(target) else {
825 return Err(vec![unsupported(&format!(
826 "there is no back end for {} in this compiler yet, so there is nothing to generate",
827 target.tuple
828 ))]);
829 };
830 if opts.protector != Protector::None && machine.conv.guard.is_none() {
835 return Err(vec![unsupported(&format!(
836 "{} is not supported for {} yet, because the stack protector on that target is not \
837 the one this compiler writes",
838 opts.protector, target.tuple
839 ))]);
840 }
841 if opts.control.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
847 return Err(vec![unsupported(&format!(
848 "-fcf-protection={} is not supported for {} yet, because what says a file was built \
849 for it there is not the note this compiler writes",
850 opts.control, target.tuple
851 ))]);
852 }
853 let profile = match machine.conv.trace {
859 Some(trace) => opts.profile.then(|| opts.hook.early(trace.fentry)),
860 None if opts.profile => {
861 return Err(vec![unsupported(&format!(
862 "-pg is not supported for {} yet, because the profiler's hook on that target is \
863 not the one this compiler calls",
864 target.tuple
865 ))]);
866 }
867 None => None,
868 };
869 if opts.patchable.any() && target.tuple.os().object_format() != Some(ObjectFormat::Elf) {
874 return Err(vec![unsupported(&format!(
875 "-fpatchable-function-entry= is not supported for {} yet, because what records where \
876 the room is there is not the section this compiler writes",
877 target.tuple
878 ))]);
879 }
880 let flags = pipeline::Flags {
881 frame_pointer: opts.keeps_frame_pointer(),
882 red_zone: opts.red_zone,
883 stack_clash: opts.stack_clash,
884 landing: opts.control.branch(),
885 profile: match profile {
886 None => pipeline::Profile::No,
887 Some(true) => pipeline::Profile::Early,
888 Some(false) => pipeline::Profile::Late,
889 },
890 patch: pipeline::Room { after: opts.patchable.after(), before: opts.patchable.before },
891 reorder: opts.reorder_blocks.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
898 reuse: opts.stack_reuse.unwrap_or_else(|| opts.opt_level.runs_optimizer()),
903 schedule: opts.schedule_insns.unwrap_or_else(|| opts.opt_level.schedules()),
908 align_loops: opts.align_loops.unwrap_or(false),
914 accurate: opts.cycle_accurate_model,
916 verify: opts.verify_each,
919 goal: Goal::for_size(opts.opt_level.is_size()),
924 switch: opts.switch_shape.as_deref().and_then(rucc_codegen::switch::Force::named),
926 sibling: opts.sibling_calls.unwrap_or_else(|| opts.opt_level.sibling_calls()),
928 };
929
930 if opts.safety.instruments() {
939 rucc_opt::heap::annotate(module, names);
949 rucc_safety::handover::arrange(module);
956 rucc_safety::lower(module, names);
957 if let Err(errors) = rucc_ir::verify(module, names) {
958 return Err(errors
959 .iter()
960 .map(|e| internal(&format!("invalid IR after check lowering, {e}")))
961 .collect());
962 }
963 }
964
965 let copies = target.tuple.arch() == Arch::X86_64;
976 let elsewhere = Elsewhere::of(module, replaceable(target, opts), target.object_format, copies);
977
978 let mut funcs = Vec::new();
979 let mut complaints = Vec::new();
980 for id in module.funcs() {
981 if module[id].is_declaration() {
982 continue;
983 }
984 match pipeline::compile_recording(
985 &mut module[id],
986 names,
987 &machine,
988 &elsewhere,
989 flags,
990 recording,
991 ) {
992 Ok(func) => funcs.push(func),
993 Err(why) => {
994 let name = names.resolve(module[id].name).to_owned();
995 let span = why.inst().map_or(Span::DUMMY, |inst| module[id].span(inst));
998 let said = format!("cannot generate code for '{name}': {why}");
999 complaints.push(unsupported_at(&said, span));
1000 }
1001 }
1002 }
1003 if !complaints.is_empty() {
1004 return Err(complaints);
1005 }
1006 let (globals, aliases) = match opts.emit {
1012 EmitKind::Asm | EmitKind::Object | EmitKind::Archive | EmitKind::Executable => (
1013 rucc_asm::globals(module, names, target.object_format).map_err(refused)?,
1014 rucc_asm::aliases(module, names).map_err(refused)?,
1015 ),
1016 _ => (rucc_asm::Globals::default(), Vec::new()),
1017 };
1018 let unwind = opts.unwinds();
1022 match opts.emit {
1023 EmitKind::Asm => {
1024 rucc_asm::print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
1025 .map(Artifact::Text)
1026 .map_err(refused)
1027 }
1028 EmitKind::Object | EmitKind::Archive | EmitKind::Executable => {
1032 if opts.save_temps.wanted() {
1033 let listing = rucc_asm::print(
1034 &funcs,
1035 &globals,
1036 &aliases,
1037 names,
1038 target,
1039 unwind,
1040 output(opts, target),
1041 );
1042 *assembly = Some(listing.map_err(refused)?);
1043 }
1044 let aarch64 = target.tuple.arch() == Arch::Aarch64;
1055 if aarch64 || globals.kept() || rucc_asm::kept(&funcs, names, target) {
1056 if funcs.iter().any(|func| !func.landings.is_empty()) {
1060 return Err(vec![unsupported(
1061 "a cleanup that runs during an unwind, in a unit whose listing is read \
1062 back by the assembler",
1063 )]);
1064 }
1065 let print = if opts.debug_info { rucc_asm::print_marked } else { rucc_asm::print };
1066 let listing =
1067 print(&funcs, &globals, &aliases, names, target, unwind, output(opts, target))
1068 .map_err(refused)?;
1069 let arch = target.tuple.arch();
1070 let read =
1071 rucc_asm::read_as(&listing, arch, target.object_format).map_err(|trouble| {
1072 let what = if aarch64 {
1073 "a unit for aarch64"
1074 } else if globals.kept() {
1075 "an `asm` at file scope"
1076 } else {
1077 "an `asm` template kept as text"
1078 };
1079 vec![unsupported(&format!(
1080 "{what}, whose listing the assembler stopped at on line {}: {}",
1081 trouble.line, trouble.why
1082 ))]
1083 })?;
1084 let info = if opts.debug_info {
1085 let assembled =
1086 placed(&read, &funcs, names, target).map_err(|why| vec![internal(&why)])?;
1087 describe(&assembled, &globals.image(), &funcs, origin, opts, target)
1088 .map_err(|why| vec![internal(&why)])?
1089 } else {
1090 rucc_object::Info::default()
1091 };
1092 let defines = rucc_object::assembled_defines(&read);
1093 let bytes =
1094 rucc_object::assembled_described(&read, target, &info).map_err(wrote)?;
1095 return Ok(Artifact::Object { bytes, defines });
1096 }
1097 let assembled = rucc_asm::assemble(&funcs, names, target, unwind, opts.debug_info)
1098 .map_err(refused)?;
1099 let data = globals.image();
1100 let info = if opts.debug_info {
1104 describe(&assembled, &data, &funcs, origin, opts, target)
1105 .map_err(|why| vec![internal(&why)])?
1106 } else {
1107 rucc_object::Info::default()
1108 };
1109 let text = assembled.text;
1110 let bytes =
1113 rucc_object::write(&text, &data, &aliases, target, output(opts, target), &info)
1114 .map_err(wrote)?;
1115 let defines = rucc_object::defines(&text, &data, &aliases, target).map_err(wrote)?;
1120 Ok(Artifact::Object { bytes, defines })
1121 }
1122 _ => Ok(Artifact::Text(rucc_mir::print(&funcs, names, target.regs))),
1123 }
1124}
1125
1126fn placed(
1138 read: &rucc_object::Assembled,
1139 funcs: &[rucc_mir::Func],
1140 names: &Interner,
1141 target: &TargetInfo,
1142) -> Result<rucc_asm::Assembled, String> {
1143 let at: HashMap<&str, &rucc_object::Name> =
1144 read.names.iter().map(|name| (name.name.as_str(), name)).collect();
1145 let offset = |name: &str| match at.get(name).map(|name| name.at) {
1146 Some(rucc_object::Held::In { part, offset }) => Some((part, offset)),
1147 _ => None,
1148 };
1149 let mut text = rucc_object::Text::default();
1150 let mut lines = Vec::with_capacity(funcs.len());
1151 for (which, func) in funcs.iter().enumerate() {
1152 let name = names.resolve(func.name);
1153 let Some((part, start)) = offset(name) else {
1154 return Err(format!("the listing has no label for the function '{name}'"));
1155 };
1156 let mut rows = Vec::with_capacity(func.inst_count() + 1);
1157 if !func.declared.is_dummy() {
1158 rows.push(rucc_asm::Row { at: 0, span: func.declared, inst: None });
1159 }
1160 for block in func.blocks() {
1161 for inst in func.insts(block) {
1162 let label = rucc_asm::mark(target, which, inst);
1163 let Some((held, here)) = offset(&label) else {
1164 return Err(format!("the listing has no label '{label}'"));
1165 };
1166 if held != part || here < start {
1167 return Err(format!("the label '{label}' is not inside '{name}'"));
1168 }
1169 let at = usize::try_from(here - start).map_err(|why| why.to_string())?;
1170 rows.push(rucc_asm::Row { at, span: func.span(inst), inst: Some(inst) });
1171 }
1172 }
1173 let len = at.get(name).map_or(0, |name| name.size);
1174 text.funcs.push(rucc_object::Extent {
1175 name: name.to_owned(),
1176 start: usize::try_from(start).map_err(|why| why.to_string())?,
1177 len: usize::try_from(len).map_err(|why| why.to_string())?,
1178 align: func.align.unwrap_or(rucc_object::FUNC_ALIGN),
1179 binding: rucc_object::Binding::Global,
1180 visibility: rucc_object::Visibility::Default,
1181 patch: None,
1182 landings: Vec::new(),
1183 });
1184 lines.push(rows);
1185 }
1186 Ok(rucc_asm::Assembled { text, lines, frames: None })
1187}
1188
1189fn describe(
1208 assembled: &rucc_asm::Assembled,
1209 data: &rucc_object::Data,
1210 machine: &[rucc_mir::Func],
1211 origin: Origin<'_>,
1212 opts: &Options,
1213 target: &TargetInfo,
1214) -> Result<rucc_object::Info, String> {
1215 let rucc_asm::Assembled { text, lines, frames } = assembled;
1216 let rewrite = |path: &str| opts.prefix_map.debug.apply(path).into_owned();
1217 let mut files: Vec<String> = Vec::new();
1222 let mut funcs = Vec::with_capacity(text.funcs.len());
1223 for ((extent, rows), built) in text.funcs.iter().zip(lines).zip(machine) {
1224 let mut out: Vec<rucc_debug::Row> = Vec::with_capacity(rows.len());
1225 for row in rows {
1226 if row.span.is_dummy() {
1227 continue;
1228 }
1229 let Some(at) = origin.map.presumed(row.span.lo) else {
1230 continue;
1231 };
1232 let which = interned(&mut files, rewrite(at.name));
1233 let place = rucc_debug::Row {
1234 at: row.at as u64,
1235 file: which,
1236 line: at.line,
1237 column: at.column,
1238 };
1239 match out.last() {
1249 Some(last) if last.at == place.at => {}
1250 _ => out.push(place),
1251 }
1252 }
1253 if let Some(first) = out.first_mut() {
1260 first.at = 0;
1261 }
1262 let known = origin.meaning.funcs.get(&extent.name);
1268 let decl = known.map(|known| rucc_debug::Place {
1269 file: interned(&mut files, rewrite(&known.file)),
1270 line: known.line,
1271 });
1272 let mut sig = known.and_then(|known| known.sig.clone());
1281 let mut placed: Vec<(u32, i32)> = built.locals.clone();
1282 let mut spots = stretches(extent, rows, built, target);
1283 for &decl in &built.sharing {
1288 if !spots.iter().any(|(at, _)| *at == decl) {
1289 spots.push((decl, Vec::new()));
1290 }
1291 }
1292 if let (Some(sig), Some(known)) = (sig.as_mut(), known) {
1293 for (param, decl) in sig.params.iter_mut().zip(&known.params) {
1294 let Some(decl) = *decl else { continue };
1295 if let Some(which) = placed.iter().position(|&(at, _)| at == decl) {
1296 let at = rucc_debug::Held::Frame(i64::from(placed.remove(which).1));
1297 param.spot = Some(rucc_debug::Spot::Always(at));
1298 continue;
1299 }
1300 let Some(which) = spots.iter().position(|(at, _)| *at == decl) else { continue };
1304 param.spot = Some(rucc_debug::Spot::Over(spots.remove(which).1));
1305 }
1306 }
1307 let mut locals = Vec::with_capacity(placed.len() + spots.len());
1311 let mut wants: Vec<Option<usize>> = Vec::with_capacity(locals.capacity());
1315 for (decl, at) in placed {
1316 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1317 wants.push(named.scope);
1318 locals.push(rucc_debug::Local {
1319 name: named.name.clone(),
1320 ty: named.ty,
1321 decl: Some(rucc_debug::Place {
1322 file: interned(&mut files, rewrite(&named.file)),
1323 line: named.line,
1324 }),
1325 spot: rucc_debug::Spot::Always(rucc_debug::Held::Frame(i64::from(at))),
1326 scope: None,
1327 });
1328 }
1329 spots.sort_by_key(|(decl, _)| *decl);
1333 for (decl, spans) in spots {
1334 let Some(named) = origin.meaning.locals.get(&decl) else { continue };
1335 wants.push(named.scope);
1336 locals.push(rucc_debug::Local {
1337 name: named.name.clone(),
1338 ty: named.ty,
1339 decl: Some(rucc_debug::Place {
1340 file: interned(&mut files, rewrite(&named.file)),
1341 line: named.line,
1342 }),
1343 spot: rucc_debug::Spot::Over(spans),
1344 scope: None,
1345 });
1346 }
1347 let (scopes, at) = nests(&wants, &origin.meaning.scopes, extent, rows);
1352 for (local, want) in locals.iter_mut().zip(&wants) {
1353 local.scope = want.and_then(|want| at.get(&want).copied());
1354 }
1355 funcs.push(rucc_debug::Function {
1356 name: extent.name.clone(),
1357 len: extent.len as u64,
1358 rows: out,
1359 decl,
1360 sig,
1361 external: known.is_some_and(|known| known.external),
1362 locals,
1363 scopes,
1364 });
1365 }
1366 let mut globals = Vec::new();
1373 for object in &data.objects {
1374 let Some(held) = origin.meaning.objects.get(&object.name) else { continue };
1375 globals.push(rucc_debug::Global {
1376 name: object.name.clone(),
1377 ty: held.ty,
1378 decl: Some(rucc_debug::Place {
1379 file: interned(&mut files, rewrite(&held.file)),
1380 line: held.line,
1381 }),
1382 external: held.external,
1383 });
1384 }
1385 let unit = rucc_debug::Unit {
1386 name: rewrite(origin.name),
1387 dir: rewrite(opts.working_dir.as_deref().unwrap_or(".")),
1390 producer: format!("rucc {}", crate::VERSION),
1391 files,
1392 types: origin.meaning.types.clone(),
1393 funcs,
1394 globals,
1395 pointer: u8::try_from(target.pointer_width / 8).unwrap_or(8),
1396 frames: opts.unwinds() || frames.is_some(),
1401 };
1402 let mut info = rucc_debug::write(&unit).map_err(|why| why.to_string())?;
1403 info.chunks.extend(frames.clone());
1404 Ok(info)
1405}
1406
1407fn stretches(
1418 extent: &rucc_object::Extent,
1419 rows: &[rucc_asm::Row],
1420 built: &rucc_mir::Func,
1421 target: &TargetInfo,
1422) -> Vec<(u32, Vec<rucc_debug::Span>)> {
1423 let (false, Some(regs)) = (built.kept.is_empty(), target.call_regs) else {
1426 return Vec::new();
1427 };
1428 let ends = ends(extent, rows);
1429 let mut bounds = vec![None; built.inst_count()];
1430 for (which, row) in rows.iter().enumerate() {
1431 let Some(inst) = row.inst else { continue };
1432 bounds[inst.index()] = Some((row.at as u64, ends[which]));
1433 }
1434 let mut spots: Vec<(u32, Vec<rucc_debug::Span>)> = Vec::new();
1435 for kept in &built.kept {
1436 let (Some((from, _)), Some((_, to))) = (bounds[kept.from.index()], bounds[kept.to.index()])
1437 else {
1438 continue;
1439 };
1440 if to <= from {
1441 continue;
1442 }
1443 let held = match kept.at {
1444 rucc_mir::Where::Reg { reg, class } => match regs.dwarf(class, reg) {
1447 Some(number) => rucc_debug::Held::Reg(number),
1448 None => continue,
1449 },
1450 rucc_mir::Where::Frame(at) => rucc_debug::Held::Frame(i64::from(at)),
1451 };
1452 let span = rucc_debug::Span { from, len: to - from, held };
1453 match spots.iter_mut().find(|(decl, _)| *decl == kept.decl) {
1454 Some((_, spans)) => spans.push(span),
1455 None => spots.push((kept.decl, vec![span])),
1456 }
1457 }
1458 for (_, spans) in &mut spots {
1459 *spans = settle(std::mem::take(spans));
1460 }
1461 spots.retain(|(_, spans)| !spans.is_empty());
1462 spots
1463}
1464
1465fn ends(extent: &rucc_object::Extent, rows: &[rucc_asm::Row]) -> Vec<u64> {
1475 let mut out = vec![extent.len as u64; rows.len()];
1476 let mut next = extent.len as u64;
1477 for which in (0..rows.len()).rev() {
1478 let at = rows[which].at as u64;
1479 out[which] = match next > at {
1482 true => next,
1483 false => out.get(which + 1).copied().unwrap_or(extent.len as u64),
1484 };
1485 next = next.min(at);
1486 }
1487 out
1488}
1489
1490fn nests(
1506 wants: &[Option<usize>],
1507 scopes: &[crate::shapes::Scope],
1508 extent: &rucc_object::Extent,
1509 rows: &[rucc_asm::Row],
1510) -> (Vec<rucc_debug::Scope>, HashMap<usize, usize>) {
1511 let mut needed: Vec<usize> = Vec::new();
1512 for &want in wants {
1513 let mut up = want;
1514 while let Some(which) = up {
1515 if needed.contains(&which) {
1516 break;
1517 }
1518 needed.push(which);
1519 up = scopes.get(which).and_then(|scope| scope.parent);
1520 }
1521 }
1522 needed.sort_unstable();
1525 let at: HashMap<usize, usize> =
1526 needed.iter().enumerate().map(|(which, &scope)| (scope, which)).collect();
1527 let ends = ends(extent, rows);
1528 let out = needed
1529 .iter()
1530 .map(|&which| {
1531 let scope = &scopes[which];
1532 rucc_debug::Scope {
1533 parent: scope.parent.and_then(|parent| at.get(&parent).copied()),
1534 over: spread(scope.span, &ends, rows),
1535 }
1536 })
1537 .collect();
1538 (out, at)
1539}
1540
1541fn spread(span: Span, ends: &[u64], rows: &[rucc_asm::Row]) -> Vec<rucc_debug::Reach> {
1549 let mut out: Vec<rucc_debug::Reach> = Vec::new();
1550 for (which, row) in rows.iter().enumerate() {
1551 if row.span.is_dummy() || row.span.lo < span.lo || row.span.hi > span.hi {
1552 continue;
1553 }
1554 let (from, to) = (row.at as u64, ends[which]);
1555 if to <= from {
1556 continue;
1557 }
1558 match out.last_mut() {
1559 Some(last) if last.from + last.len >= from => {
1560 last.len = to.saturating_sub(last.from).max(last.len);
1561 }
1562 _ => out.push(rucc_debug::Reach { from, len: to - from }),
1563 }
1564 }
1565 out
1566}
1567
1568fn settle(mut spans: Vec<rucc_debug::Span>) -> Vec<rucc_debug::Span> {
1585 spans.sort_by_key(|span| (span.from, span.len));
1586 for which in 0..spans.len() {
1587 let (from, end, held) =
1588 (spans[which].from, spans[which].from + spans[which].len, spans[which].held);
1589 let later = spans[which + 1..]
1590 .iter()
1591 .take_while(|later| later.from < end)
1592 .find(|later| later.from > from && later.held != held);
1593 if let Some(later) = later {
1594 spans[which].len = later.from - from;
1595 }
1596 }
1597 let mut edges: Vec<u64> =
1600 spans.iter().flat_map(|span| [span.from, span.from + span.len]).collect();
1601 edges.sort_unstable();
1602 edges.dedup();
1603 let mut out: Vec<rucc_debug::Span> = Vec::new();
1604 let mut first = 0;
1605 for pair in edges.windows(2) {
1606 let (from, to) = (pair[0], pair[1]);
1607 while spans.get(first).is_some_and(|span| span.from + span.len <= from) {
1611 first += 1;
1612 }
1613 let mut held = None;
1614 let mut agreed = true;
1615 for span in &spans[first..] {
1616 if span.from >= to {
1617 break;
1618 }
1619 if span.from > from || span.from + span.len < to {
1620 continue;
1621 }
1622 match held {
1623 None => held = Some(span.held),
1624 Some(seen) => agreed &= seen == span.held,
1625 }
1626 }
1627 let (Some(held), true) = (held, agreed) else { continue };
1628 match out.last_mut() {
1629 Some(last) if last.from + last.len == from && last.held == held => {
1630 last.len += to - from
1631 }
1632 _ => out.push(rucc_debug::Span { from, len: to - from, held }),
1633 }
1634 }
1635 out
1636}
1637
1638fn interned(files: &mut Vec<String>, name: String) -> usize {
1644 match files.iter().position(|have| *have == name) {
1645 Some(which) => which,
1646 None => {
1647 files.push(name);
1648 files.len() - 1
1649 }
1650 }
1651}
1652
1653fn output(opts: &Options, target: &TargetInfo) -> rucc_object::Output {
1665 let mut features = 0;
1666 if target.tuple.arch() == Arch::X86_64 {
1667 if opts.control.branch() {
1668 features |= rucc_object::Property::IBT;
1669 }
1670 if opts.control.ret() {
1671 features |= rucc_object::Property::SHSTK;
1672 }
1673 }
1674 rucc_object::Output {
1675 sections: rucc_object::Sections {
1676 functions: opts.function_sections,
1677 data: opts.data_sections,
1678 },
1679 property: rucc_object::Property { features },
1680 }
1681}
1682
1683fn wrote(why: rucc_object::Error) -> Vec<Diagnostic> {
1689 match why {
1690 rucc_object::Error::Format { .. } => vec![unsupported(&why.to_string())],
1691 rucc_object::Error::Refused { .. } => vec![internal(&why.to_string())],
1692 }
1693}
1694
1695fn refused(why: rucc_asm::Error) -> Vec<Diagnostic> {
1702 match why {
1703 rucc_asm::Error::Thread { .. }
1704 | rucc_asm::Error::IFunc { .. }
1705 | rucc_asm::Error::Frame { .. } => {
1706 vec![unsupported(&why.to_string())]
1707 }
1708 _ => vec![internal(&why.to_string())],
1709 }
1710}
1711
1712fn unsupported(message: &str) -> Diagnostic {
1718 unsupported_at(message, Span::DUMMY)
1719}
1720
1721fn unsupported_at(message: &str, span: Span) -> Diagnostic {
1727 Diagnostic::error(message.to_owned(), span)
1728 .with_code("E0653")
1729 .note("this construct is not lowered yet, see https://github.com/tamnd/rucc/issues", span)
1730}
1731
1732fn invalid(message: &str) -> Diagnostic {
1734 Diagnostic::error(message.to_owned(), Span::DUMMY).with_code("E0661")
1735}
1736
1737fn internal(message: &str) -> Diagnostic {
1739 Diagnostic::error(format!("internal error: {message}"), Span::DUMMY)
1740 .with_code("E0652")
1741 .note("this is a bug in rucc rather than in the program, please report it", Span::DUMMY)
1742}
1743
1744fn failure(message: String) -> Compiled {
1747 Compiled {
1748 artifact: Artifact::Nothing,
1749 messages: vec![format!("rucc: error: {message}")],
1750 errors: 1,
1751 fired: Fired::new(),
1752 pressure: Pressure::new(),
1753 lowerings: Lowerings::new(),
1754 dumps: Vec::new(),
1755 remarks: String::new(),
1756 deps: Vec::new(),
1757 temps: Temps::default(),
1758 timing: crate::trace::Timing::default(),
1759 }
1760}
1761
1762#[cfg(test)]
1763mod tests {
1764 use rucc_session::{MemoryFileSystem, Std};
1765 use rucc_target::Triple;
1766
1767 use super::*;
1768
1769 fn options() -> Options {
1770 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1771 opts.emit = EmitKind::Tast;
1772 opts.frame_pointer = Some(false);
1775 opts
1776 }
1777
1778 fn run(opts: &Options, source: &str) -> Compiled {
1779 let mut fs = MemoryFileSystem::new();
1780 fs.insert("/main.c", source.to_owned().into_bytes());
1781 compile(opts, "/main.c", &fs)
1782 }
1783
1784 fn freestanding() -> Options {
1788 let mut opts = options();
1789 opts.hosted = false;
1790 opts.search.push_system(rucc_session::runtime::DIR);
1791 opts
1792 }
1793
1794 fn shipped(source: &str) -> String {
1796 let result = run(&freestanding(), source);
1797 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1798 result.text().to_owned()
1799 }
1800
1801 fn tast(source: &str) -> String {
1803 let result = run(&options(), source);
1804 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
1805 result.text().to_owned()
1806 }
1807
1808 #[test]
1809 fn the_shipped_stdarg_declares_a_list_and_the_four_operators() {
1810 let text = shipped(concat!(
1811 "#include <stdarg.h>\n",
1812 "int sum(int n, ...) {\n",
1813 " va_list ap, copy;\n",
1814 " va_start(ap, n);\n",
1815 " va_copy(copy, ap);\n",
1816 " int total = va_arg(ap, int) + va_arg(copy, int);\n",
1817 " va_end(ap);\n",
1818 " va_end(copy);\n",
1819 " return total;\n",
1820 "}\n",
1821 ));
1822 assert!(text.contains("va-start"), "{text}");
1823 assert!(text.contains("va-copy"), "{text}");
1824 assert!(text.contains("va-arg"), "{text}");
1825 assert!(text.contains("va-end"), "{text}");
1826 }
1827
1828 #[test]
1832 fn stdarg_hands_out_the_type_alone_when_that_is_all_that_was_asked_for() {
1833 let text = shipped(concat!(
1834 "#define __need___va_list\n",
1835 "#include <stdarg.h>\n",
1836 "int vprint(const char *f, __gnuc_va_list ap);\n",
1837 "#ifdef va_start\n",
1838 "#error va_start should not be defined\n",
1839 "#endif\n",
1840 "#ifdef _VA_LIST_DEFINED\n",
1841 "#error va_list should not have been made\n",
1842 "#endif\n",
1843 ));
1844 assert!(text.contains("vprint"), "{text}");
1845 }
1846
1847 #[test]
1850 fn stddef_answers_one_piece_at_a_time_and_the_next_request_still_gets_through() {
1851 let text = shipped(concat!(
1852 "#define __need_size_t\n",
1853 "#include <stddef.h>\n",
1854 "#ifdef offsetof\n",
1855 "#error offsetof should not be defined yet\n",
1856 "#endif\n",
1857 "#define __need_ptrdiff_t\n",
1858 "#include <stddef.h>\n",
1859 "#include <stddef.h>\n",
1860 "size_t a;\n",
1861 "ptrdiff_t b;\n",
1862 "wchar_t c;\n",
1863 "max_align_t d;\n",
1864 "void *e = NULL;\n",
1865 "struct P { int x; long y; };\n",
1866 "size_t f = offsetof(struct P, y);\n",
1867 ));
1868 assert!(text.contains("decl #0 a : unsigned long"), "{text}");
1869 assert!(text.contains("decl #1 b : long"), "{text}");
1870 }
1871
1872 #[test]
1873 fn the_shipped_limits_and_float_are_the_targets_own_answers() {
1874 let text = shipped(concat!(
1875 "#include <limits.h>\n",
1876 "#include <float.h>\n",
1877 "int bits = CHAR_BIT;\n",
1878 "long big = LONG_MAX;\n",
1879 "int low = INT_MIN;\n",
1880 "int radix = FLT_RADIX;\n",
1881 "int digits = DBL_MANT_DIG;\n",
1882 ));
1883 assert!(text.contains("const 8 : int"), "{text}");
1884 assert!(text.contains("const 9223372036854775807 : long"), "{text}");
1885 assert!(text.contains("const 2 : int"), "{text}");
1886 assert!(text.contains("const 53 : int"), "{text}");
1887 }
1888
1889 #[test]
1893 fn the_shipped_stdint_writes_the_whole_set_when_there_is_no_library_to_defer_to() {
1894 let text = shipped(concat!(
1895 "#include <stdint.h>\n",
1896 "int64_t a = INT64_C(1);\n",
1897 "uint_least16_t b;\n",
1898 "intptr_t c;\n",
1899 "uintmax_t d = UINTMAX_MAX;\n",
1900 "int wide = sizeof(int_fast64_t);\n",
1901 ));
1902 assert!(text.contains("decl #0 a : long"), "{text}");
1903 assert!(text.contains("decl #1 b : unsigned short"), "{text}");
1904 assert!(text.contains("decl #2 c : long"), "{text}");
1905 }
1906
1907 #[test]
1918 fn the_shipped_mmintrin_defines_the_mmx_type_and_the_operations_over_it() {
1919 let text = shipped(concat!(
1920 "#include <mmintrin.h>\n",
1921 "__m64 add(__m64 a, __m64 b) { return _mm_add_pi16(a, b); }\n",
1922 "__m64 pack(__m64 a, __m64 b) { return _m_packsswb(a, b); }\n",
1923 "__m64 shift(__m64 a) { return _mm_srai_pi32(a, 3); }\n",
1924 "int low(__m64 a) { return _mm_cvtsi64_si32(a); }\n",
1925 "void done(void) { _mm_empty(); }\n",
1926 ));
1927 assert!(text.contains("add"), "{text}");
1928 assert!(text.contains("pack"), "{text}");
1929 assert!(text.contains("shift"), "{text}");
1930 }
1931
1932 #[test]
1937 fn the_shipped_mm_malloc_asks_for_aligned_memory_and_gives_it_back() {
1938 let text = shipped(concat!(
1939 "#include <mm_malloc.h>\n",
1940 "void *get(void) { return _mm_malloc(64, 16); }\n",
1941 "void put(void *p) { _mm_free(p); }\n",
1942 ));
1943 assert!(text.contains("get"), "{text}");
1944 assert!(text.contains("put"), "{text}");
1945 }
1946
1947 #[test]
1959 fn the_shipped_xmmintrin_defines_the_sse_type_and_the_operations_over_it() {
1960 let text = shipped(concat!(
1961 "#include <xmmintrin.h>\n",
1962 "__m128 add(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
1963 "__m128 one(__m128 a, __m128 b) { return _mm_max_ss(a, b); }\n",
1964 "__m128 mask(__m128 a, __m128 b) { return _mm_cmpnle_ps(a, b); }\n",
1965 "__m128 pick(__m128 a, __m128 b) { return _mm_shuffle_ps(a, b, _MM_SHUFFLE(0,1,2,3)); }\n",
1966 "int bits(__m128 a) { return _mm_movemask_ps(a); }\n",
1967 "int near(__m128 a) { return _mm_cvtss_si32(a); }\n",
1968 "__m128 wide(__m64 a) { return _mm_cvtpi16_ps(a); }\n",
1969 "void *room(void) { return _mm_malloc(64, 16); }\n",
1970 "void hint(const float *p) { _mm_prefetch(p, _MM_HINT_T0); _mm_sfence(); }\n",
1971 ));
1972 assert!(text.contains("add"), "{text}");
1973 assert!(text.contains("mask"), "{text}");
1974 assert!(text.contains("pick"), "{text}");
1975 assert!(text.contains("wide"), "{text}");
1976 }
1977
1978 #[test]
1985 fn the_shipped_xmmintrin_leaves_out_the_names_that_need_an_instruction() {
1986 let text = rucc_session::runtime::header("xmmintrin.h").expect("xmmintrin.h is shipped");
1987 for absent in [
1988 "_mm_sqrt_ps",
1989 "_mm_sqrt_ss",
1990 "_mm_rsqrt_ps",
1991 "_mm_rsqrt_ss",
1992 "_mm_getcsr",
1993 "_mm_setcsr",
1994 ] {
1995 let defined = text.contains(&format!("{absent}("));
1996 assert!(!defined, "{absent} is defined and the header says it is not");
1997 assert!(text.contains(absent), "{absent} is absent and unexplained");
1998 }
1999 }
2000
2001 #[test]
2002 fn the_shipped_emmintrin_defines_both_sse2_types_and_the_operations_over_them() {
2003 let text = shipped(concat!(
2004 "#include <emmintrin.h>\n",
2005 "__m128i add(__m128i a, __m128i b) { return _mm_add_epi64(a, b); }\n",
2006 "__m128i wide(__m128i a, __m128i b) { return _mm_mul_epu32(a, b); }\n",
2007 "__m128i pick(__m128i a) { return _mm_shuffle_epi32(a, _MM_SHUFFLE(0,1,2,3)); }\n",
2008 "__m128i up(__m128i a) { return _mm_slli_epi64(a, 13); }\n",
2009 "__m128i down(__m128i a) { return _mm_srli_si128(a, 3); }\n",
2010 "__m128i pack(__m128i a, __m128i b) { return _mm_packus_epi16(a, b); }\n",
2011 "int bits(__m128i a) { return _mm_movemask_epi8(a); }\n",
2012 "__m128d sum(__m128d a, __m128d b) { return _mm_add_sd(a, b); }\n",
2013 "__m128d mask(__m128d a, __m128d b) { return _mm_cmpunord_pd(a, b); }\n",
2014 "__m128i near(__m128d a) { return _mm_cvtpd_epi32(a); }\n",
2015 "__m128d over(__m128 a) { return _mm_cvtps_pd(a); }\n",
2016 "__m128i half(__m64 a) { return _mm_movpi64_epi64(a); }\n",
2017 "__m128i grab(void const *p) { return _mm_loadu_si128(p); }\n",
2018 "void wall(void) { _mm_lfence(); _mm_mfence(); }\n",
2019 ));
2020 assert!(text.contains("wide"), "{text}");
2021 assert!(text.contains("pack"), "{text}");
2022 assert!(text.contains("near"), "{text}");
2023 assert!(text.contains("half"), "{text}");
2024 }
2025
2026 #[test]
2030 fn the_shipped_immintrin_reaches_the_names_the_headers_under_it_define() {
2031 let text = shipped(concat!(
2032 "#include <immintrin.h>\n",
2033 "unsigned long long matching(unsigned char tag, unsigned char const *bucket) {\n",
2034 " __m128i const want = _mm_set1_epi8((char)tag);\n",
2035 " __m128i const chunk = _mm_loadu_si128((__m128i const *)(void const *)bucket);\n",
2036 " __m128i const same = _mm_cmpeq_epi8(chunk, want);\n",
2037 " return (unsigned long long)_mm_movemask_epi8(same);\n",
2038 "}\n",
2039 "__m64 narrow(__m64 a, __m64 b) { return _mm_add_pi32(a, b); }\n",
2040 "__m128 single(__m128 a, __m128 b) { return _mm_add_ps(a, b); }\n",
2041 ));
2042 assert!(text.contains("matching"), "{text}");
2043 assert!(text.contains("narrow"), "the MMX header is not reached: {text}");
2044 assert!(text.contains("single"), "the SSE header is not reached: {text}");
2045 }
2046
2047 #[test]
2051 fn the_shipped_x86intrin_reaches_the_fences_windows_headers_ask_it_for() {
2052 let text = shipped(concat!(
2053 "#include <x86intrin.h>\n",
2054 "void barriers(void *p) {\n",
2055 " _mm_lfence();\n",
2056 " _mm_sfence();\n",
2057 " _mm_mfence();\n",
2058 " _mm_pause();\n",
2059 " _mm_clflush(p);\n",
2060 "}\n",
2061 "__m128i wide(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
2062 ));
2063 assert!(text.contains("barriers"), "{text}");
2064 assert!(text.contains("wide"), "the SSE2 header is not reached: {text}");
2065 }
2066
2067 #[test]
2071 fn the_umbrella_and_the_header_under_it_can_both_be_included() {
2072 let text = shipped(concat!(
2073 "#include <immintrin.h>\n",
2074 "#include <emmintrin.h>\n",
2075 "#include <immintrin.h>\n",
2076 "#include <x86intrin.h>\n",
2077 "__m128i twice(__m128i a, __m128i b) { return _mm_add_epi32(a, b); }\n",
2078 ));
2079 assert!(text.contains("twice"), "{text}");
2080 }
2081
2082 #[test]
2085 fn the_shipped_arm_neon_has_what_xxhash_asks_it_for() {
2086 let mut opts = freestanding();
2087 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2088 let source = concat!(
2089 "#include <arm_neon.h>\n",
2090 "uint64x2_t acc(uint64x2_t sum, const void *in, const void *key) {\n",
2091 " uint8x16_t data = vld1q_u8((const uint8_t *)in);\n",
2092 " uint8x16_t k = vld1q_u8((const uint8_t *)key);\n",
2093 " uint64x2_t mixed = vreinterpretq_u64_u8(veorq_u8(data, k));\n",
2094 " uint32x2_t lo = vmovn_u64(mixed);\n",
2095 " uint32x2_t hi = vshrn_n_u64(mixed, 32);\n",
2096 " return vmlal_u32(sum, lo, hi);\n",
2097 "}\n",
2098 "uint32x4x2_t pair(uint32x4_t a, uint32x4_t b) { return vzipq_u32(a, b); }\n",
2099 "uint32_t total(uint32x4_t a) { return vaddvq_u32(a); }\n",
2100 );
2101 let result = run(&opts, source);
2102 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
2103 assert!(result.text().contains("pair"), "{}", result.text());
2104 assert!(result.text().contains("total"), "{}", result.text());
2105 }
2106
2107 #[test]
2109 fn the_shipped_arm_neon_refuses_another_target() {
2110 let result = run(&freestanding(), "#include <arm_neon.h>\n");
2111 let said = result.messages.join("\n");
2112 assert!(said.contains("arm_neon.h is for AArch64"), "{said}");
2113 }
2114
2115 #[test]
2119 fn the_shipped_emmintrin_leaves_out_the_two_square_roots() {
2120 let text = rucc_session::runtime::header("emmintrin.h").expect("emmintrin.h is shipped");
2121 for absent in ["_mm_sqrt_pd", "_mm_sqrt_sd"] {
2122 let defined = text.contains(&format!("{absent}("));
2123 assert!(!defined, "{absent} is defined and the header says it is not");
2124 assert!(text.contains(absent), "{absent} is absent and unexplained");
2125 }
2126 }
2127
2128 #[test]
2133 fn the_shipped_nmmintrin_is_one_instruction_per_step_under_sse4_2() {
2134 let mut opts = freestanding();
2135 opts.emit = EmitKind::Asm;
2136 let mut choices = rucc_target::Choices::new();
2137 choices.read("sse4.2").expect("gcc knows sse4.2");
2138 opts.isa = choices.over(opts.isa);
2139 let source = concat!(
2140 "#include <nmmintrin.h>\n",
2141 "unsigned b(unsigned c, unsigned char v) { return _mm_crc32_u8(c, v); }\n",
2142 "unsigned w(unsigned c, unsigned short v) { return _mm_crc32_u16(c, v); }\n",
2143 "unsigned l(unsigned c, unsigned v) { return _mm_crc32_u32(c, v); }\n",
2144 "unsigned long long q(unsigned long long c, unsigned long long v) {\n",
2145 " return _mm_crc32_u64(c, v);\n",
2146 "}\n",
2147 "int n(unsigned v) { return _mm_popcnt_u32(v); }\n",
2148 "long long m(unsigned long long v) { return _mm_popcnt_u64(v); }\n",
2149 );
2150 let result = run(&opts, source);
2151 assert_eq!(result.messages, Vec::<String>::new());
2152 let text = result.text();
2153 for step in ["crc32b", "crc32w", "crc32l", "crc32q", "popcntl", "popcntq"] {
2154 assert!(text.contains(step), "no {step} in:\n{text}");
2155 }
2156 }
2157
2158 #[test]
2161 fn the_shipped_smmintrin_refuses_a_caller_not_built_for_the_checksum() {
2162 let result = run(
2163 &freestanding(),
2164 "#include <immintrin.h>\nunsigned f(unsigned c) { return _mm_crc32_u32(c, 1); }\n",
2165 );
2166 let said = result.messages.join("\n");
2167 let refusal = "inlining failed in call to 'always_inline' '_mm_crc32_u32': target \
2168 specific option mismatch";
2169 assert!(said.contains(refusal), "{said}");
2170 }
2171
2172 #[test]
2177 fn a_function_built_for_sse4_2_calls_the_steps_without_a_flag() {
2178 let mut opts = freestanding();
2179 opts.emit = EmitKind::Asm;
2180 let source = concat!(
2181 "#include <nmmintrin.h>\n",
2182 "#if defined(__has_attribute) && __has_attribute (target)\n",
2183 "__attribute__((target(\"sse4.2\")))\n",
2184 "#endif\n",
2185 "unsigned l(unsigned c, unsigned v) { return _mm_crc32_u32(c, v); }\n",
2186 "__attribute__((target(\"popcnt\")))\n",
2187 "int n(unsigned v) { return _mm_popcnt_u32(v); }\n",
2188 );
2189 let result = run(&opts, source);
2190 assert_eq!(result.messages, Vec::<String>::new());
2191 let text = result.text();
2192 assert!(text.contains("crc32l") && text.contains("popcntl"), "{text}");
2193 let l = &text[text.find("\nl:").expect("l is defined")..];
2194 let l = &l[..l.find("ret").expect("l returns")];
2195 assert!(l.contains("crc32l") && !l.contains("call"), "{l}");
2196 }
2197
2198 #[test]
2205 fn the_shipped_avx512_headers_pass_postgres_probes() {
2206 let popcount = concat!(
2207 "#include <immintrin.h>\n",
2208 "#include <stdint.h>\n",
2209 "char buf[sizeof(__m512i)];\n",
2210 "#if defined(__has_attribute) && __has_attribute (target)\n",
2211 "__attribute__((target(\"avx512vpopcntdq,avx512bw\")))\n",
2212 "#endif\n",
2213 "int popcount_test(void)\n",
2214 "{\n",
2215 " int64_t popcnt = 0;\n",
2216 " __m512i accum = _mm512_setzero_si512();\n",
2217 " __m512i val = _mm512_maskz_loadu_epi8((__mmask64) 0xf0f0f0f0f0f0f0f0, (const __m512i *) buf);\n",
2218 " __m512i cnt = _mm512_popcnt_epi64(val);\n",
2219 " accum = _mm512_add_epi64(accum, cnt);\n",
2220 " popcnt = _mm512_reduce_add_epi64(accum);\n",
2221 " return (int) popcnt;\n",
2222 "}\n",
2223 );
2224 let pclmul = concat!(
2225 "#include <immintrin.h>\n",
2226 "__m512i x;\n",
2227 "__m512i y;\n",
2228 "#if defined(__has_attribute) && __has_attribute (target)\n",
2229 "__attribute__((target(\"vpclmulqdq,avx512vl\")))\n",
2230 "#endif\n",
2231 "int avx512_pclmul_test(void)\n",
2232 "{\n",
2233 " __m128i z;\n",
2234 " x = _mm512_xor_si512(_mm512_zextsi128_si512(_mm_cvtsi32_si128(0)), x);\n",
2235 " y = _mm512_clmulepi64_epi128(x, y, 0);\n",
2236 " z = _mm_ternarylogic_epi64(\n",
2237 " _mm512_castsi512_si128(y),\n",
2238 " _mm512_extracti32x4_epi32(y, 1),\n",
2239 " _mm512_extracti32x4_epi32(y, 2),\n",
2240 " 0x96);\n",
2241 " return _mm_crc32_u64(0, _mm_extract_epi64(z, 0));\n",
2242 "}\n",
2243 );
2244 let checks: [(&str, &str, &[&str]); 2] = [
2245 (popcount, "popcount_test", &["kmovq", "vmovdqu8", "vpopcntq", "vpaddq", "vshufi64x2"]),
2246 (pclmul, "avx512_pclmul_test", &["vpxorq", "vpclmulqdq", "vpternlogq", "crc32q"]),
2247 ];
2248 for (source, name, wanted) in checks {
2249 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
2250 let mut opts = freestanding();
2251 opts.emit = EmitKind::Asm;
2252 opts.opt_level = level;
2253 let result = run(&opts, source);
2254 assert_eq!(result.messages, Vec::<String>::new(), "{name} at {level:?}");
2255 let text = result.text();
2256 let start = text.find(&format!("\n{name}:")).expect("the probe is written out");
2257 let body = &text[start..];
2258 let body = &body[..body.find(".size").unwrap_or(body.len())];
2259 for instruction in wanted {
2260 assert!(
2261 body.contains(instruction),
2262 "no {instruction} at {level:?} in:\n{body}"
2263 );
2264 }
2265 assert!(!body.contains("call"), "a call left behind at {level:?} in:\n{body}");
2266 }
2267 }
2268 }
2269
2270 #[test]
2273 fn the_shipped_avx512_headers_refuse_a_caller_not_built_for_them() {
2274 let result = run(
2275 &freestanding(),
2276 "#include <immintrin.h>\n__m512i f(__m512i a) { return _mm512_popcnt_epi64(a); }\n",
2277 );
2278 let said = result.messages.join("\n");
2279 let refusal = "inlining failed in call to 'always_inline' '_mm512_popcnt_epi64': target \
2280 specific option mismatch";
2281 assert!(said.contains(refusal), "{said}");
2282 }
2283
2284 #[test]
2287 fn a_target_string_gcc_does_not_know_is_refused() {
2288 for (string, name) in [("sse5", "sse5"), ("+crc", "+crc"), ("sse4.2,foo", "foo")] {
2289 let source =
2290 format!("__attribute__((target(\"{string}\"))) int f(void) {{ return 0; }}\n");
2291 let said = run(&freestanding(), &source).messages.join("\n");
2292 let wanted = format!("attribute 'target' argument '{name}' is unknown");
2293 assert!(said.contains(&wanted), "{string}: {said}");
2294 }
2295 }
2296
2297 #[test]
2300 fn an_aarch64_target_string_is_still_accepted() {
2301 let mut opts = freestanding();
2302 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2303 let source = "__attribute__((target(\"+crc\"))) int f(void) { return 0; }\n";
2304 let result = run(&opts, source);
2305 assert_eq!(result.messages, Vec::<String>::new());
2306 }
2307
2308 #[test]
2309 fn the_three_formality_headers_still_have_to_work() {
2310 let text = shipped(concat!(
2311 "#include <stdbool.h>\n",
2312 "#include <stdalign.h>\n",
2313 "#include <iso646.h>\n",
2314 "#include <stdnoreturn.h>\n",
2315 "int t = true and not false;\n",
2316 "_Alignas(16) char buf[16];\n",
2317 "int a = alignof(long);\n",
2318 ));
2319 assert!(text.contains("decl #0 t : int"), "{text}");
2320 assert!(text.contains("const 8 : unsigned long"), "{text}");
2321 }
2322
2323 #[test]
2331 fn every_shipped_header_can_be_included_twice() {
2332 let once: String = rucc_session::runtime::names()
2335 .iter()
2336 .filter(|name| **name != "arm_neon.h")
2337 .map(|name| format!("#include <{name}>\n"))
2338 .collect();
2339 let twice = once.repeat(2);
2340 assert_eq!(shipped(&format!("{once}int x;\n")), shipped(&format!("{twice}int x;\n")));
2341
2342 let mut opts = freestanding();
2343 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
2344 let tree = |source: &str| {
2345 let result = run(&opts, source);
2346 assert_eq!(
2347 result.messages,
2348 Vec::<String>::new(),
2349 "expected this to compile:\n{source}"
2350 );
2351 result.text().to_owned()
2352 };
2353 let neon = "#include <arm_neon.h>\n";
2354 assert_eq!(tree(&format!("{neon}int x;\n")), tree(&format!("{neon}{neon}int x;\n")));
2355 }
2356
2357 #[test]
2358 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
2359 let fs = MemoryFileSystem::new();
2360 let result = compile(&options(), "/nope.c", &fs);
2361 assert!(result.failed());
2362 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
2363 assert!(result.text().is_empty());
2364 }
2365
2366 #[test]
2367 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
2368 let text = tast("int x = 1;\n");
2369 let expected = "\
2370decl #0 x : int object external static defined
2371 init
2372 +0
2373 const 1 : int
2374";
2375 assert_eq!(text, expected);
2376 }
2377
2378 #[test]
2379 fn the_macros_are_expanded_before_anything_is_parsed() {
2380 let text = tast("#define N 2\nint a[N];\n");
2384 assert!(text.starts_with("decl #0 a : int[2] object external static tentative"), "{text}");
2385 }
2386
2387 #[test]
2393 fn a_pragma_is_not_a_declaration_and_the_parse_walks_past_the_ones_it_does_not_read() {
2394 let text = tast(concat!(
2395 "#pragma pack(4)\n",
2396 "struct s { int a; };\n",
2397 "#pragma pack()\n",
2398 "int b;\n",
2399 "_Pragma(\"GCC visibility push(default)\") int c;\n",
2400 ));
2401 assert!(text.contains("decl #0 b : int"), "{text}");
2402 assert!(text.contains("decl #1 c : int"), "{text}");
2403 }
2404
2405 #[test]
2409 fn the_byte_swaps_and_the_bit_counts_of_a_constant_are_constants() {
2410 tast(concat!(
2411 "static const unsigned magic = __builtin_bswap32(0x11223344u);\n",
2412 "_Static_assert(__builtin_bswap16(0x1234) == 0x3412, \"16\");\n",
2413 "_Static_assert(__builtin_bswap32(0x11223344u) == 0x44332211u, \"32\");\n",
2414 "_Static_assert(__builtin_bswap64(0x0102030405060708ull) == 0x0807060504030201ull, \"64\");\n",
2415 "_Static_assert(__builtin_popcountll(-1ll) == 64 && __builtin_popcount(-1) == 32, \"ones\");\n",
2416 "_Static_assert(__builtin_parity(7) == 1 && __builtin_parity(3) == 0, \"parity\");\n",
2417 "_Static_assert(__builtin_ffs(0) == 0 && __builtin_ffs(8) == 4, \"ffs\");\n",
2418 "_Static_assert(__builtin_clrsb(0) == 31 && __builtin_clrsb(-1) == 31, \"clrsb\");\n",
2419 "_Static_assert(__builtin_clrsbl(1) == 62, \"clrsbl\");\n",
2420 "_Static_assert(__builtin_clz(1) == 31 && __builtin_clzl(1) == 63, \"clz\");\n",
2421 "_Static_assert(__builtin_ctzll(1ull << 40) == 40, \"ctz\");\n",
2422 "_Static_assert(__builtin_clz(0) == 32 && __builtin_ctzll(0) == 64, \"zero\");\n",
2423 ));
2424 }
2425
2426 #[test]
2434 fn the_layout_attributes_move_the_members_and_the_record_the_way_gcc_lays_them_out() {
2435 tast(concat!(
2436 "struct A { char c; int i; } __attribute__((packed));\n",
2437 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
2438 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
2439 "struct B { char c; int i; } __attribute__((aligned));\n",
2442 "_Static_assert(sizeof(struct B) == 16 && _Alignof(struct B) == 16, \"B\");\n",
2443 "struct C { char c; int i __attribute__((packed)); };\n",
2444 "_Static_assert(sizeof(struct C) == 5 && _Alignof(struct C) == 1, \"C\");\n",
2445 "_Static_assert(__builtin_offsetof(struct C, i) == 1, \"C.i\");\n",
2446 "struct D { char c; int i; } __attribute__((packed, aligned(4)));\n",
2447 "_Static_assert(sizeof(struct D) == 8 && _Alignof(struct D) == 4, \"D\");\n",
2448 "_Static_assert(__builtin_offsetof(struct D, i) == 1, \"D.i\");\n",
2449 "struct E { char c; _Alignas(8) int i; };\n",
2450 "_Static_assert(sizeof(struct E) == 16 && _Alignof(struct E) == 8, \"E\");\n",
2451 "_Static_assert(__builtin_offsetof(struct E, i) == 8, \"E.i\");\n",
2452 "struct F { char c; int i __attribute__((aligned(8))); };\n",
2453 "_Static_assert(sizeof(struct F) == 16 && _Alignof(struct F) == 8, \"F\");\n",
2454 "struct G { char c; short s; } __attribute__((aligned(2)));\n",
2457 "_Static_assert(sizeof(struct G) == 4 && _Alignof(struct G) == 2, \"G\");\n",
2458 "struct H { char c; int i; } __attribute__((aligned(2)));\n",
2459 "_Static_assert(sizeof(struct H) == 8 && _Alignof(struct H) == 4, \"H\");\n",
2460 "struct I { [[gnu::packed]] char c; int i; };\n",
2463 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
2464 "struct J { char c; [[gnu::packed]] int i; };\n",
2465 "_Static_assert(sizeof(struct J) == 5 && _Alignof(struct J) == 1, \"J\");\n",
2466 "struct M { char c; int i : 5; int j : 20; } __attribute__((packed));\n",
2467 "_Static_assert(sizeof(struct M) == 5 && _Alignof(struct M) == 1, \"M\");\n",
2468 "struct N { char c; long long l; } __attribute__((aligned(32)));\n",
2469 "_Static_assert(sizeof(struct N) == 32 && _Alignof(struct N) == 32, \"N\");\n",
2470 "union L { char c; int i; } __attribute__((packed));\n",
2471 "_Static_assert(sizeof(union L) == 4 && _Alignof(union L) == 1, \"L\");\n",
2472 "struct O { char c; int i; } __attribute__((__packed__));\n",
2476 "_Static_assert(sizeof(struct O) == 5 && _Alignof(struct O) == 1, \"O\");\n",
2477 "struct P { char c; int i; } __attribute__((__aligned__(8)));\n",
2478 "_Static_assert(sizeof(struct P) == 8 && _Alignof(struct P) == 8, \"P\");\n",
2479 ));
2480 }
2481
2482 #[test]
2495 fn a_transparent_union_takes_the_member_a_value_fits_and_is_declared_either_way() {
2496 let text = tast(concat!(
2497 "struct one { int x; };\n",
2498 "struct two { long y; };\n",
2499 "typedef union { struct one *a; struct two *b; void *any; }\n",
2500 " __attribute__((__transparent_union__)) arg;\n",
2501 "int takes(arg v);\n",
2502 "int f(struct one *p, struct two *q, char *c) {\n",
2503 " return takes(p) + takes(q) + takes(c) + takes(0);\n",
2504 "}\n",
2505 "int takes(struct one *p);\n",
2507 "int (*as_a_member)(struct one *) = takes;\n",
2508 "int (*as_the_union)(arg) = takes;\n",
2509 ));
2510 assert!(text.contains("compound-literal"), "{text}");
2511 }
2512
2513 #[test]
2519 fn the_attribute_on_the_declarator_of_a_typedef_is_the_one_glibc_writes() {
2520 let text = tast(concat!(
2521 "struct sockaddr { int family; };\n",
2522 "struct sockaddr_in { int family; int addr; };\n",
2523 "typedef union { struct sockaddr *plain; struct sockaddr_in *inet; }\n",
2524 " addr_arg __attribute__((__transparent_union__));\n",
2525 "int bind_to(int fd, addr_arg where);\n",
2526 "int f(struct sockaddr_in *where) { return bind_to(0, where); }\n",
2527 ));
2528 assert!(text.contains("compound-literal"), "{text}");
2529 }
2530
2531 #[test]
2539 fn a_transparent_union_that_cannot_keep_the_promise_is_dropped_with_a_word_about_it() {
2540 let result = run(
2541 &options(),
2542 concat!(
2543 "union wider { int small; double large; } __attribute__((transparent_union));\n",
2544 "struct plain { int x; } __attribute__((transparent_union));\n",
2545 ),
2546 );
2547 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
2548 assert!(!result.failed(), "{:?}", result.messages);
2549 for message in &result.messages {
2550 assert!(message.contains("'transparent_union' attribute ignored"), "{message}");
2551 }
2552 assert!(result.messages[0].contains("first member"), "{:?}", result.messages);
2553 assert!(result.messages[1].contains("only a union"), "{:?}", result.messages);
2554 }
2555
2556 #[test]
2565 fn an_access_to_a_packed_member_says_the_alignment_the_layout_left_it() {
2566 let packed = body(concat!(
2567 "struct P { char c; int v; } __attribute__((packed));\n",
2568 "int f(struct P *p) { return p->v; }\n",
2569 ));
2570 assert!(packed.contains("load.i32 %2, align 1,"), "{packed}");
2571 let plain = body(concat!(
2573 "struct P { char c; int v; };\n",
2574 "int f(struct P *p) { return p->v; }\n",
2575 ));
2576 assert!(plain.contains("load.i32 %2, align 4,"), "{plain}");
2577 }
2578
2579 #[test]
2586 fn what_is_inside_a_packed_member_is_no_more_aligned_than_the_member_is() {
2587 let stepped = body(concat!(
2588 "struct P { char c; int v[4]; } __attribute__((packed));\n",
2589 "int f(struct P *p, int i) { return p->v[i]; }\n",
2590 ));
2591 assert!(stepped.contains(", align 1,"), "{stepped}");
2592 assert!(!stepped.contains(", align 4,"), "{stepped}");
2593 let nested = body(concat!(
2594 "struct Inner { int v; };\n",
2595 "struct P { char c; struct Inner in; } __attribute__((packed));\n",
2596 "int f(struct P *p) { return p->in.v; }\n",
2597 ));
2598 assert!(nested.contains(", align 1,"), "{nested}");
2599 assert!(!nested.contains(", align 4,"), "{nested}");
2600 }
2601
2602 #[test]
2618 fn an_access_through_a_typedef_that_lowered_its_alignment_says_the_one_the_typedef_asked_for() {
2619 let through = body(concat!(
2620 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2621 "unsigned int f(const void *p) { return *(const unalign32 *)p; }\n",
2622 ));
2623 assert!(through.contains("load.i32 %0, align 1,"), "{through}");
2624 let stepped = body(concat!(
2627 "typedef __attribute__((aligned(1))) unsigned int unalign32;\n",
2628 "unsigned int f(unalign32 *p, int i) { return p[i]; }\n",
2629 ));
2630 assert!(stepped.contains(", align 1,"), "{stepped}");
2631 assert!(!stepped.contains(", align 4,"), "{stepped}");
2632 let plain = body(concat!(
2635 "typedef unsigned int word;\n",
2636 "unsigned int f(const void *p) { return *(const word *)p; }\n",
2637 ));
2638 assert!(plain.contains("load.i32 %0, align 4,"), "{plain}");
2639 }
2640
2641 #[test]
2652 fn a_vector_read_through_a_typedef_that_lowered_its_alignment_comes_back_a_piece_at_a_time() {
2653 let prefix = concat!(
2654 "typedef long long v2di __attribute__((__vector_size__(16)));\n",
2655 "typedef long long v2di_u __attribute__((__vector_size__(16), __aligned__(1)));\n",
2656 );
2657 let loaded =
2658 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di_u *)p; }}"));
2659 assert_eq!(loaded.matches("align 1\n").count(), 2, "{loaded}");
2660 assert!(!loaded.contains("align 16"), "{loaded}");
2661 let stored = body(&format!("{prefix}void f(void *p, v2di b) {{ *(v2di_u *)p = b; }}"));
2664 assert!(stored.contains("memcpy %0, %3, size 16, align 1"), "{stored}");
2665 let aligned =
2667 body(&format!("{prefix}v2di f(const void *p) {{ return *(const v2di *)p; }}"));
2668 assert!(aligned.contains("align 16"), "{aligned}");
2669 }
2670
2671 #[test]
2680 fn the_aligned_attribute_on_a_declaration_raises_what_that_one_object_is_aligned_to() {
2681 tast(concat!(
2682 "int v __attribute__((aligned(64)));\n",
2683 "_Static_assert(__alignof__(v) == 64, \"v\");\n",
2684 "__attribute__((aligned(32))) int w;\n",
2687 "_Static_assert(__alignof__(w) == 32, \"w\");\n",
2688 "[[gnu::aligned(16)]] int x;\n",
2689 "_Static_assert(__alignof__(x) == 16, \"x\");\n",
2690 "int y __attribute__((aligned(2)));\n",
2693 "_Static_assert(__alignof__(y) == 4, \"y\");\n",
2694 "void f(void) { int a __attribute__((aligned(128)));\n",
2696 "_Static_assert(__alignof__(a) == 128, \"a\"); (void)a; }\n",
2697 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2700 "void g(void) __attribute__((aligned(256)));\n",
2703 "void g(void) {}\n",
2704 "_Static_assert(__alignof__(g) == 256, \"g\");\n",
2705 ));
2706 }
2707
2708 #[test]
2712 fn what_a_declaration_asked_to_be_aligned_to_is_what_the_assembler_is_told() {
2713 let text = asm(concat!(
2714 "int v __attribute__((aligned(64)));\n",
2715 "void g(void) __attribute__((aligned(256)));\n",
2716 "void g(void) {}\n",
2717 "void plain(void) {}\n",
2718 ));
2719 assert!(text.contains("\t.p2align\t6\n\t.type\tv, @object\n"), "{text}");
2720 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2721 assert!(text.contains("\t.p2align\t4, 0x90\n\t.globl\tplain\n"), "{text}");
2722 }
2723
2724 #[test]
2730 fn the_alignment_the_command_line_asked_of_every_function_is_a_floor_under_all_of_them() {
2731 let source = concat!(
2732 "void g(void) __attribute__((aligned(256)));\n",
2733 "void g(void) {}\n",
2734 "void small(void) __attribute__((aligned(4)));\n",
2735 "void small(void) {}\n",
2736 "void plain(void) {}\n",
2737 );
2738 let listing = |align: Option<u32>| {
2739 let mut opts = options();
2740 opts.emit = EmitKind::Asm;
2741 opts.align_functions = align;
2742 let result = run(&opts, source);
2743 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
2744 result.text().to_owned()
2745 };
2746
2747 let text = listing(Some(32));
2748 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "the larger one wins: {text}");
2749 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tsmall\n"), "{text}");
2750 assert!(text.contains("\t.p2align\t5, 0x90\n\t.globl\tplain\n"), "{text}");
2751
2752 let text = listing(Some(8));
2755 assert!(text.contains("\t.p2align\t3, 0x90\n\t.globl\tplain\n"), "{text}");
2756 assert!(text.contains("\t.p2align\t8, 0x90\n\t.globl\tg\n"), "{text}");
2757 }
2758
2759 #[test]
2768 fn an_aligned_typedef_says_what_an_object_of_it_is_aligned_to_and_may_lower_it() {
2769 tast(concat!(
2770 "typedef int L __attribute__((aligned(2)));\n",
2771 "_Static_assert(__alignof__(L) == 2, \"L\");\n",
2772 "_Static_assert(_Alignof(L) == 2, \"L alignof\");\n",
2773 "_Static_assert(sizeof(L) == 4, \"L size\");\n",
2775 "struct T { char c; L x; };\n",
2776 "_Static_assert(sizeof(struct T) == 6, \"T\");\n",
2777 "_Static_assert(__builtin_offsetof(struct T, x) == 2, \"T.x\");\n",
2778 "typedef int H __attribute__((aligned(16)));\n",
2780 "_Static_assert(__alignof__(H) == 16, \"H\");\n",
2781 "_Static_assert(sizeof(H) == 4, \"H size\");\n",
2782 "struct U { char c; H x; };\n",
2783 "_Static_assert(sizeof(struct U) == 32, \"U\");\n",
2784 "_Static_assert(__builtin_offsetof(struct U, x) == 16, \"U.x\");\n",
2785 "typedef L M __attribute__((aligned(8)));\n",
2788 "_Static_assert(__alignof__(M) == 8, \"M\");\n",
2789 "typedef L N;\n",
2792 "_Static_assert(__alignof__(N) == 2, \"N\");\n",
2793 "_Static_assert(__alignof__(int) == 4, \"int\");\n",
2795 ));
2796 let text = asm(concat!(
2797 "typedef int L __attribute__((aligned(2)));\n",
2798 "typedef int H __attribute__((aligned(16)));\n",
2799 "L low;\n",
2800 "H high;\n",
2801 ));
2802 assert!(text.contains("\t.p2align\t1\n\t.type\tlow, @object\n"), "{text}");
2803 assert!(text.contains("\t.p2align\t4\n\t.type\thigh, @object\n"), "{text}");
2804 }
2805
2806 #[test]
2814 fn the_vector_size_attribute_builds_a_type_of_lanes_and_measures_it_in_bytes() {
2815 tast(concat!(
2816 "typedef int __attribute__((vector_size(16))) v4si;\n",
2817 "_Static_assert(sizeof(v4si) == 16 && _Alignof(v4si) == 16, \"v4si\");\n",
2818 "typedef char __attribute__((vector_size(16))) v16qi;\n",
2819 "_Static_assert(sizeof(v16qi) == 16, \"v16qi\");\n",
2820 "typedef int __attribute__((vector_size(4))) v1si;\n",
2823 "_Static_assert(sizeof(v1si) == 4, \"v1si\");\n",
2824 "typedef float __attribute__((__vector_size__(8))) v2sf;\n",
2826 "_Static_assert(sizeof(v2sf) == 8, \"v2sf\");\n",
2827 "typedef short [[gnu::vector_size(8)]] v4hi;\n",
2828 "_Static_assert(sizeof(v4hi) == 8, \"v4hi\");\n",
2829 "v4si g;\n",
2832 "_Static_assert(sizeof(g[0]) == 4, \"lane\");\n",
2833 "_Static_assert(sizeof(g + g) == 16, \"whole\");\n",
2834 "_Static_assert(sizeof(g + 1) == 16, \"broadcast\");\n",
2837 "_Static_assert(sizeof(v4si[3]) == 48, \"array\");\n",
2839 ));
2840 }
2841
2842 #[test]
2852 fn a_vector_is_written_whole_into_an_array_of_them_and_named_by_a_type_name() {
2853 tast(concat!(
2854 "typedef int __attribute__((vector_size(8))) v2si;\n",
2855 "v2si table[] = { (v2si){ 1, 2 }, (v2si){ 3, 4 } };\n",
2856 "_Static_assert(sizeof(table) == 16, \"two of them and not eight lanes\");\n",
2857 "v2si written = (int __attribute__((vector_size(8)))){ 5, 6 };\n",
2859 "_Static_assert(sizeof((int __attribute__((vector_size(16)))){ 0 }) == 16, \"named\");\n",
2860 "v2si lanes[2] = { 1, 2, 3, 4 };\n",
2863 "_Static_assert(sizeof(lanes) == 16, \"still elided\");\n",
2864 ));
2865 }
2866
2867 #[test]
2875 fn a_lane_is_assignable_and_a_shift_takes_a_count_of_its_own_lane() {
2876 let result = run(
2877 &options(),
2878 concat!(
2879 "typedef int __attribute__((vector_size(16))) v4si;\n",
2880 "typedef unsigned __attribute__((vector_size(16))) v4ui;\n",
2881 "void write(v4si *out, v4ui a, v4si b, int n) {\n",
2882 " v4si v = { 1, 2, 3, 4 };\n",
2883 " v[0] = n;\n",
2884 " v[1] += n;\n",
2885 " v[2]++;\n",
2886 " *&v[3] = n;\n",
2887 " v4ui shifted = a >> b;\n",
2889 " shifted <<= b;\n",
2890 " *out = v + (v4si)shifted + (1 << b);\n",
2893 "}\n",
2894 "void refused(const v4si c) {\n",
2897 " c[0] = 1;\n",
2898 "}\n",
2899 ),
2900 );
2901 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
2902 assert!(result.messages[0].contains("assignment of read-only"), "{:?}", result.messages);
2903 }
2904
2905 #[test]
2917 fn a_record_that_asks_for_the_other_byte_order_swaps_every_scalar_it_holds() {
2918 let read = "int f(struct s *p) { return p->i; }\n";
2919 let big = "struct s { int i; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
2920 assert!(body(&format!("{big}{read}")).contains("bswap"), "{big}");
2921
2922 let armoured =
2923 "struct s { int i; } __attribute__((__scalar_storage_order__(\"big-endian\")));\n";
2924 assert!(body(&format!("{armoured}{read}")).contains("bswap"), "{armoured}");
2925
2926 let front = "struct __attribute__((scalar_storage_order(\"big-endian\"))) s { int i; };\n";
2927 assert!(body(&format!("{front}{read}")).contains("bswap"), "{front}");
2928
2929 let standard = "struct s { int i; } [[gnu::scalar_storage_order(\"big-endian\")]];\n";
2930 assert!(body(&format!("{standard}{read}")).contains("bswap"), "{standard}");
2931
2932 let same =
2933 "struct s { int i; } __attribute__((scalar_storage_order(\"little-endian\")));\n";
2934 assert!(!body(&format!("{same}{read}")).contains("bswap"), "{same}");
2935
2936 let byte = "struct s { char c; } __attribute__((scalar_storage_order(\"big-endian\")));\n";
2938 let source = format!("{byte}int f(struct s *p) {{ return p->c; }}\n");
2939 assert!(!body(&source).contains("bswap"), "{byte}");
2940
2941 tast(concat!(
2942 "struct s { int i; short h; char c; }",
2943 " __attribute__((scalar_storage_order(\"big-endian\")));\n",
2944 "_Static_assert(sizeof(struct s) == 8 && _Alignof(struct s) == 4, \"s\");\n",
2945 "_Static_assert(__builtin_offsetof(struct s, h) == 4, \"s.h\");\n",
2946 "_Static_assert(__builtin_offsetof(struct s, c) == 6, \"s.c\");\n",
2947 ));
2948 }
2949
2950 #[test]
2956 fn a_bit_field_in_one_of_those_records_is_counted_from_the_top_of_its_bytes() {
2957 let members = "short i : 12; char c1 : 1; char c2 : 1; char c3 : 1; char c4 : 1;";
2958 let read = "int f(struct s *p) { return p->i; }\n";
2959 let plain = format!("struct s {{ {members} }};\n{read}");
2960 let reversed = format!(
2961 "struct s {{ {members} }} __attribute__((scalar_storage_order(\"big-endian\")));\n\
2962 {read}"
2963 );
2964 assert!(body(&plain).contains("shl"), "{}", body(&plain));
2965 assert!(!body(&plain).contains("bswap"), "{}", body(&plain));
2966 let built = body(&reversed);
2969 assert!(built.contains("bswap"), "{built}");
2970 assert!(!built.contains("shl"), "{built}");
2971 assert!(built.contains("ashr"), "{built}");
2972 }
2973
2974 #[test]
2981 fn the_address_of_a_scalar_stored_the_other_way_round_is_refused() {
2982 let opts = options();
2983 let record = "struct s { int i; int a[2]; struct in { int n; } w; }\n\
2984 __attribute__((scalar_storage_order(\"big-endian\")));\n";
2985 let taken = format!("{record}int *f(struct s *p) {{ return &p->i; }}\n");
2986 assert_eq!(
2987 run(&opts, &taken).messages,
2988 ["/main.c:3:30: error: cannot take address of scalar with reverse storage order \
2989 [E0712]"]
2990 );
2991 let element = format!("{record}int *f(struct s *p) {{ return &p->a[0]; }}\n");
2992 let messages = run(&opts, &element).messages;
2993 assert!(messages[0].contains("[E0712]"), "{messages:?}");
2994
2995 let whole = format!("{record}int *f(struct s *p) {{ return (int *) &p->w; }}\n");
2996 assert_eq!(run(&opts, &whole).messages, Vec::<String>::new(), "{whole}");
2997 }
2998
2999 #[test]
3003 fn a_storage_order_that_names_neither_end_is_refused_with_the_two_words_that_are_taken() {
3004 let opts = options();
3005 let wrong = "struct s { int i; } __attribute__((scalar_storage_order(\"middle\")));\n";
3006 assert_eq!(
3007 run(&opts, wrong).messages,
3008 ["/main.c:1:36: error: 'scalar_storage_order' argument must be one of \"big-endian\" \
3009 or \"little-endian\" [E0688]"]
3010 );
3011 let bare = "struct s { int i; } __attribute__((scalar_storage_order));\n";
3012 let messages = run(&opts, bare).messages;
3013 assert!(messages[0].contains("[E0688]"), "{messages:?}");
3014 }
3015
3016 #[test]
3026 fn packing_is_what_decides_whether_a_bit_field_may_straddle_its_own_storage() {
3027 assert_eq!(bit_field_byte("struct s { int x : 12; char y : 6; };"), 2);
3029 assert_eq!(
3030 bit_field_byte("struct s { int x : 12; char y : 6; } __attribute__((packed));"),
3031 1
3032 );
3033 assert_eq!(
3034 bit_field_byte("struct s { int x : 12; __attribute__((packed)) char y : 6; };"),
3035 1
3036 );
3037 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { int x : 12; char y : 6; };"), 1);
3038 assert_eq!(bit_field_byte("struct s { char x; int y : 30; };"), 4);
3040 assert_eq!(bit_field_byte("struct s { char x; int y : 30; } __attribute__((packed));"), 1);
3041 assert_eq!(bit_field_byte("#pragma pack(4)\nstruct s { char x; int y : 30; };"), 1);
3043 assert_eq!(bit_field_byte("#pragma pack(2)\nstruct s { char x; int y : 30; };"), 1);
3044 }
3045
3046 fn bit_field_byte(record: &str) -> u64 {
3048 let source = format!("{record}\nint f(struct s *p) {{ return p->y; }}\n");
3049 let body = body(&source);
3050 let Some((before, _)) = body.split_once("ptr_add") else { return 0 };
3051 let (_, constant) = before.rsplit_once("iconst.i64 ").expect("an offset constant");
3052 constant.lines().next().expect("a line").trim().parse().expect("a byte offset")
3053 }
3054
3055 #[test]
3061 fn an_attribute_among_the_specifiers_is_kept_beside_the_ones_written_in_front() {
3062 tast(concat!(
3063 "struct a { char c; __attribute__((aligned(8))) int i; };\n",
3064 "_Static_assert(sizeof(struct a) == 16 && _Alignof(struct a) == 8, \"a\");\n",
3065 "_Static_assert(__builtin_offsetof(struct a, i) == 8, \"a.i\");\n",
3066 "struct b { char c; __attribute__((packed)) int i; };\n",
3067 "_Static_assert(sizeof(struct b) == 5 && _Alignof(struct b) == 1, \"b\");\n",
3068 "_Static_assert(__builtin_offsetof(struct b, i) == 1, \"b.i\");\n",
3069 "typedef struct { char c; int i; } __attribute__((packed)) c;\n",
3070 "_Static_assert(sizeof(c) == 5 && _Alignof(c) == 1, \"c\");\n",
3071 ));
3072 }
3073
3074 #[test]
3080 fn pragma_pack_caps_every_member_and_is_read_where_the_body_closes() {
3081 tast(concat!(
3082 "#pragma pack(1)\n",
3083 "struct A { char c; int i; };\n",
3084 "_Static_assert(sizeof(struct A) == 5 && _Alignof(struct A) == 1, \"A\");\n",
3085 "_Static_assert(__builtin_offsetof(struct A, i) == 1, \"A.i\");\n",
3086 "#pragma pack()\n",
3087 "struct B { char c; int i; };\n",
3088 "_Static_assert(sizeof(struct B) == 8 && _Alignof(struct B) == 4, \"B\");\n",
3089 "#pragma pack(2)\n",
3090 "struct C { char c; int i; double d; };\n",
3091 "_Static_assert(sizeof(struct C) == 14 && _Alignof(struct C) == 2, \"C\");\n",
3092 "_Static_assert(__builtin_offsetof(struct C, d) == 6, \"C.d\");\n",
3093 "struct K { char c; int i __attribute__((aligned(8))); };\n",
3095 "_Static_assert(sizeof(struct K) == 6 && _Alignof(struct K) == 2, \"K\");\n",
3096 "_Static_assert(__builtin_offsetof(struct K, i) == 2, \"K.i\");\n",
3097 "struct J { char c; int i; } __attribute__((aligned(8)));\n",
3099 "_Static_assert(sizeof(struct J) == 8 && _Alignof(struct J) == 8, \"J\");\n",
3100 "#pragma pack()\n",
3101 "#pragma pack(push, 1)\n",
3102 "struct D { char c; short s; };\n",
3103 "_Static_assert(sizeof(struct D) == 3 && _Alignof(struct D) == 1, \"D\");\n",
3104 "#pragma pack(pop)\n",
3105 "struct E { char c; short s; };\n",
3106 "_Static_assert(sizeof(struct E) == 4 && _Alignof(struct E) == 2, \"E\");\n",
3107 "struct H { char c;\n",
3109 "#pragma pack(1)\n",
3110 " int i; };\n",
3111 "_Static_assert(sizeof(struct H) == 5 && _Alignof(struct H) == 1, \"H\");\n",
3112 "#pragma pack(1)\n",
3113 "struct I { char c;\n",
3114 "#pragma pack()\n",
3115 " int i; };\n",
3116 "_Static_assert(sizeof(struct I) == 8 && _Alignof(struct I) == 4, \"I\");\n",
3117 "#pragma pack()\n",
3118 "#pragma pack(push, 8)\n",
3120 "#pragma pack(push, 1)\n",
3121 "struct P { char c; int i; };\n",
3122 "_Static_assert(sizeof(struct P) == 5 && _Alignof(struct P) == 1, \"P\");\n",
3123 "#pragma pack(pop)\n",
3124 "struct Q { char c; int i; };\n",
3125 "_Static_assert(sizeof(struct Q) == 8 && _Alignof(struct Q) == 4, \"Q\");\n",
3126 "#pragma pack(pop)\n",
3127 "#pragma pack(16)\n",
3129 "struct R { char c; int i; };\n",
3130 "_Static_assert(sizeof(struct R) == 8 && _Alignof(struct R) == 4, \"R\");\n",
3131 "#pragma pack()\n",
3132 "#pragma pack(1)\n",
3133 "struct S { char c; int i : 5; int j : 20; };\n",
3134 "_Static_assert(sizeof(struct S) == 5 && _Alignof(struct S) == 1, \"S\");\n",
3135 "union T { char c; int i; };\n",
3136 "_Static_assert(sizeof(union T) == 4 && _Alignof(union T) == 1, \"T\");\n",
3137 "#pragma pack()\n",
3138 ));
3139 }
3140
3141 #[test]
3145 fn a_pack_line_that_is_not_one_is_reported_in_the_words_gcc_uses() {
3146 let result = run(
3147 &options(),
3148 concat!(
3149 "#pragma pack 4\n",
3150 "#pragma pack(pop)\n",
3151 "#pragma pack(3)\n",
3152 "#pragma pack(1) junk\n",
3153 "#pragma pack(push, 1\n",
3154 "#pragma pack(x)\n",
3155 "#pragma pack(0)\n",
3158 "#pragma pack(push)\n",
3159 "struct s { char c; int i; };\n",
3160 "#pragma pack(pop)\n",
3161 "#pragma pack(pop, foo)\n",
3162 ),
3163 );
3164 let expected = [
3165 "missing `(` after `#pragma pack` - ignored",
3166 "`#pragma pack (pop)` encountered without matching `#pragma pack (push)`",
3167 "alignment must be a small power of two, not 3",
3168 "junk at end of `#pragma pack`",
3169 "malformed `#pragma pack(push[, id][, <n>])` - ignored",
3170 "unknown action `x` for `#pragma pack` - ignored",
3171 "`#pragma pack(pop, foo)` encountered without matching `#pragma pack(push, foo)`",
3172 ];
3173 assert_eq!(result.messages.len(), expected.len(), "{:?}", result.messages);
3174 for (message, want) in result.messages.iter().zip(expected) {
3175 assert!(message.contains(want), "expected {want:?} in {message:?}");
3176 }
3177 }
3178
3179 #[test]
3186 fn a_declaration_behind_an_empty_macro_is_not_eaten_by_the_pragma_above_it() {
3187 let result = run(
3188 &options(),
3189 concat!(
3190 "#pragma pack(push, 1)\n",
3191 "#pragma pack(pop)\n",
3192 "#define API\n",
3193 "API const char version[] = \"3.53.4\";\n",
3194 "const char *get(void) { return version; }\n",
3195 ),
3196 );
3197 assert!(result.messages.is_empty(), "{:?}", result.messages);
3198 }
3199
3200 #[test]
3204 fn the_wide_integer_answers_to_all_three_of_its_names() {
3205 let text = tast("__uint128_t a; __int128_t b; unsigned __int128 c;\n");
3206 assert!(text.contains("decl #0 a : unsigned __int128"), "{text}");
3207 assert!(text.contains("decl #1 b : __int128"), "{text}");
3208 assert!(text.contains("decl #2 c : unsigned __int128"), "{text}");
3209 }
3210
3211 #[test]
3212 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
3213 let text = tast("long f(int a, long b) { return a + b; }\n");
3217 assert!(text.contains("convert arithmetic"), "{text}");
3218 }
3219
3220 #[test]
3221 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
3222 for source in [
3223 "#error stop\n",
3224 "int f(void) { return 1 + ; }\n",
3225 "int f(void) { return undeclared; }\n",
3226 ] {
3227 let result = run(&options(), source);
3228 assert!(result.failed(), "expected this to fail:\n{source}");
3229 assert!(
3230 result.text().is_empty(),
3231 "a file that did not compile wrote a tree:\n{source}"
3232 );
3233 }
3234 }
3235
3236 #[test]
3237 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
3238 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
3242 assert_eq!(result.errors, 1, "{:?}", result.messages);
3243 }
3244
3245 #[test]
3246 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
3247 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
3251 assert_eq!(result.errors, 1, "{:?}", result.messages);
3252 }
3253
3254 #[test]
3255 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
3256 let source = "int f(void) { char c = 300; return c; }\n";
3257 let plain = run(&options(), source);
3258 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
3259 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
3260 assert!(!plain.text().is_empty(), "a warning is not a reason to write nothing");
3261
3262 let mut opts = options();
3263 opts.warnings_are_errors = true;
3264 let strict = run(&opts, source);
3265 assert!(strict.failed());
3266 assert!(strict.text().is_empty(), "and under -Werror it is a reason to write nothing");
3267 for message in &strict.messages {
3268 assert!(!message.contains("warning:"), "{message}");
3269 }
3270 }
3271
3272 #[test]
3273 fn w_drops_the_warning_before_werror_can_promote_it() {
3274 let source = "int f(void) { char c = 300; return c; }\n";
3275 let mut opts = options();
3276 opts.warnings = false;
3277 let quiet = run(&opts, source);
3278 assert_eq!(quiet.messages, Vec::<String>::new());
3279 assert_eq!(quiet.errors, 0);
3280 assert!(!quiet.text().is_empty(), "and the file still compiles");
3281
3282 opts.warnings_are_errors = true;
3285 let both = run(&opts, source);
3286 assert_eq!(both.messages, Vec::<String>::new());
3287 assert!(!both.failed(), "-w -Werror is not an error about a warning nobody saw");
3288 }
3289
3290 #[test]
3291 fn the_dialect_reaches_the_keywords_and_the_checking() {
3292 let source = "typeof(1) x;\n";
3295 let mut opts = options();
3296 opts.std = Std::C23;
3297 opts.gnu_extensions = false;
3298 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
3299
3300 opts.std = Std::C17;
3301 assert!(run(&opts, source).failed());
3302 }
3303
3304 #[test]
3305 fn asking_for_a_kind_that_is_not_written_yet_runs_the_front_end_and_writes_nothing() {
3306 let mut opts = options();
3307 opts.emit = EmitKind::Object;
3308 let result = run(&opts, "int x = 1;\n");
3309 assert!(!result.failed(), "{:?}", result.messages);
3310 assert!(result.text().is_empty());
3311 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
3314 }
3315
3316 fn mir(source: &str) -> String {
3318 let mut opts = options();
3319 opts.emit = EmitKind::MirFinal;
3320 let result = run(&opts, source);
3321 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3322 result.text().to_owned()
3323 }
3324
3325 #[test]
3331 fn a_function_goes_from_c_to_instructions_with_real_registers_in_them() {
3332 let text = mir("int add(int a, int b) { return a + b; }\n");
3333 assert!(text.starts_with("mfunc @add {"), "{text}");
3334 assert!(text.contains("x64.add_rr_32"), "{text}");
3335 assert!(text.contains("x64.ret"), "{text}");
3336 assert!(!text.contains('%'), "{text}");
3339 }
3340
3341 #[test]
3343 fn a_function_with_no_body_produces_no_machine_function() {
3344 let text = mir("int g(int);\nint f(int a) { return g(a); }\n");
3345 assert_eq!(text.matches("mfunc @").count(), 1, "{text}");
3346 assert!(text.contains("mfunc @f {"), "{text}");
3347 assert!(text.contains("x64.call"), "{text}");
3348 }
3349
3350 #[test]
3352 fn every_definition_in_the_file_is_generated_and_they_keep_their_order() {
3353 let text = mir("int a(int x) { return x; }\nint b(int x) { return x; }\n");
3354 let first = text.find("mfunc @a").expect("the first function");
3355 let second = text.find("mfunc @b").expect("the second function");
3356 assert!(first < second, "{text}");
3357 }
3358
3359 #[test]
3361 fn the_target_decides_which_convention_the_generated_code_follows() {
3362 let mut opts = options();
3363 opts.emit = EmitKind::MirFinal;
3364 let linux = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
3365 assert!(linux.contains("$rdi"), "{linux}");
3366
3367 opts.target = "x86_64-pc-windows-msvc".parse::<Triple>().unwrap();
3368 let windows = run(&opts, "int f(int a) { return a; }\n").text().to_owned();
3369 assert!(windows.contains("$rcx"), "{windows}");
3370 assert!(!windows.contains("$rdi"), "{windows}");
3371 }
3372
3373 #[test]
3381 fn a_tagged_member_with_no_name_is_a_member_on_windows_and_nothing_on_linux() {
3382 let source = concat!(
3383 "struct S { union U { int i; void *p; }; unsigned long tymed; };\n",
3384 "int size(void) { return sizeof(struct S); }\n",
3385 "int f(struct S *s) { s->i = 1; return s->i; }\n",
3386 );
3387
3388 let mut opts = options();
3389 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3390 let windows = run(&opts, source);
3391 assert!(windows.messages.is_empty(), "{:?}", windows.messages);
3392
3393 let linux = run(&options(), source);
3394 assert_eq!(linux.messages.len(), 3, "{:?}", linux.messages);
3395 assert!(linux.messages[0].contains("does not declare anything"), "{:?}", linux.messages);
3396
3397 let mut opts = options();
3400 opts.ms_extensions = Some(true);
3401 let asked = run(&opts, source);
3402 assert!(asked.messages.is_empty(), "{:?}", asked.messages);
3403 }
3404
3405 #[test]
3407 fn a_target_this_has_no_back_end_for_is_reported_rather_than_generated() {
3408 let mut opts = options();
3409 opts.emit = EmitKind::MirFinal;
3410 opts.target = "riscv64-unknown-linux-gnu".parse::<Triple>().unwrap();
3411 let result = run(&opts, "int f(int a) { return a; }\n");
3412 assert!(result.failed());
3413 assert!(result.messages[0].contains("no back end for riscv64"), "{:?}", result.messages);
3414 assert!(result.text().is_empty());
3415 }
3416
3417 #[test]
3420 fn an_aarch64_target_is_written_as_aarch64_assembly() {
3421 let mut opts = options();
3422 opts.emit = EmitKind::Asm;
3423 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3424 let source = "int g(int);\nint f(int a, int b) { return g(a) + b; }\n";
3425 let result = run(&opts, source);
3426 assert!(!result.failed(), "{:?}", result.messages);
3427 let text = result.text();
3428 for line in ["stp x29, x30, [sp, #-16]!", "mov x29, sp", "bl g", "ldp x29, x30, [sp], #16"]
3429 {
3430 assert!(text.contains(line), "{line} is not in\n{text}");
3431 }
3432 assert!(!text.contains('%'), "{text}");
3433 }
3434
3435 #[test]
3438 fn an_aarch64_target_reaches_an_object_file() {
3439 let mut opts = options();
3440 opts.emit = EmitKind::Object;
3441 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3442 let source = concat!(
3443 "int g(int);\n",
3444 "int table[4] = {1, 2, 3, 4};\n",
3445 "int f(int a, int b) { return g(a) + table[b & 3]; }\n",
3446 );
3447 let result = run(&opts, source);
3448 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3449 let bytes = match result.artifact {
3450 Artifact::Object { bytes, defines } => {
3451 assert_eq!(defines, ["f", "table"]);
3452 bytes
3453 }
3454 other => panic!("expected an object, got {other:?}"),
3455 };
3456 assert_eq!(&bytes[..4], b"\x7fELF");
3457 assert_eq!(&bytes[18..20], &183u16.to_le_bytes(), "EM_AARCH64");
3458
3459 opts.debug_info = true;
3462 let result = run(&opts, source);
3463 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3464 let bytes = match result.artifact {
3465 Artifact::Object { bytes, .. } => bytes,
3466 other => panic!("expected an object, got {other:?}"),
3467 };
3468 let has = |name: &[u8]| bytes.windows(name.len()).any(|at| at == name);
3469 assert!(has(b".debug_line\0") && has(b".debug_info\0"));
3470 assert!(!has(b"rucc_row"), "a row label reached the symbol table");
3471 }
3472
3473 #[test]
3476 fn the_aarch64_vector_type_names_are_declared_on_that_target_and_nowhere_else() {
3477 let mut opts = options();
3478 opts.emit = EmitKind::Asm;
3479 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3480 let source = "typedef __Float32x4_t f4;\n__SVFloat32_t sv(__SVFloat32_t, __SVBool_t);\n\
3481 int n = sizeof(f4) + sizeof(__Int8x8_t);\n\
3482 int f(f4 v) { int __Uint8x16_t = 3; return v[1] + __Uint8x16_t; }\n";
3483 let result = run(&opts, source);
3484 assert!(!result.failed(), "{:?}", result.messages);
3485 assert!(result.text().contains(".long\t24"), "{}", result.text());
3486 opts.target = "x86_64-unknown-linux-gnu".parse::<Triple>().unwrap();
3487 let result = run(&opts, "typedef __Float32x4_t f4;\n");
3488 assert!(result.failed());
3489 let result = run(&opts, "int __Float32x4_t = 1;\n");
3490 assert!(!result.failed(), "{:?}", result.messages);
3491 }
3492
3493 #[test]
3496 fn an_aarch64_result_in_memory_is_reached_through_x8() {
3497 let mut opts = options();
3498 opts.emit = EmitKind::Asm;
3499 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3500 let source = "struct big { long a, b, c; };\nstruct big make(long v);\n\
3501 long f(long v) { return make(v).c; }\n\
3502 struct big g(long v) { struct big b = { v, v, v }; return b; }\n";
3503 let result = run(&opts, source);
3504 assert!(!result.failed(), "{:?}", result.messages);
3505 let text = result.text();
3506 assert!(text.contains("x8"), "{text}");
3507 assert!(text.contains("bl make"), "{text}");
3508 }
3509
3510 #[test]
3513 fn an_aarch64_remainder_is_a_division_and_a_multiply_subtract() {
3514 let mut opts = options();
3515 opts.emit = EmitKind::Asm;
3516 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3517 let source = "int s(int a, int b) { return a % b; }\n\
3518 unsigned long u(unsigned long a, unsigned long b) { return a % b; }\n";
3519 let result = run(&opts, source);
3520 assert!(!result.failed(), "{:?}", result.messages);
3521 let text = result.text();
3522 let at = |what: &str| text.find(what).unwrap_or_else(|| panic!("{what} is not in\n{text}"));
3523 assert!(at("sdiv w") < at("msub w"), "{text}");
3524 assert!(at("udiv x") < at("msub x"), "{text}");
3525 }
3526
3527 #[test]
3530 fn an_aarch64_jump_table_is_reached_with_adr() {
3531 let mut opts = options();
3532 opts.emit = EmitKind::Asm;
3533 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3534 let source = "int f(int x) { switch (x) { case 0: return 10; case 1: return 21; \
3535 case 2: return 32; case 3: return 43; case 4: return 54; case 5: return 65; \
3536 case 6: return 76; case 7: return 87; case 8: return 98; case 9: return 9; \
3537 case 10: return 19; case 11: return 29; default: return 0; } }\n";
3538 let result = run(&opts, source);
3539 assert!(!result.failed(), "{:?}", result.messages);
3540 let text = result.text();
3541 let at = |what: &str| text.find(what).unwrap_or_else(|| panic!("{what} is not in\n{text}"));
3542 assert!(at("adr x") < at("ldrsw x"), "{text}");
3543 assert!(at("ldrsw x") < at("br x"), "{text}");
3544 assert!(text.contains("_j0:"), "{text}");
3545 assert!(text.contains(".long"), "{text}");
3546 }
3547
3548 #[test]
3552 fn an_aarch64_va_start_writes_the_five_fields_of_its_list() {
3553 let mut opts = options();
3554 opts.emit = EmitKind::Asm;
3555 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3556 let source = "typedef __builtin_va_list va_list;\n\
3557 int f(int n, ...) { va_list ap; __builtin_va_start(ap, n); \
3558 int x = __builtin_va_arg(ap, int); double d = __builtin_va_arg(ap, double); \
3559 __builtin_va_end(ap); return x + (int)d; }\n";
3560 let result = run(&opts, source);
3561 assert!(!result.failed(), "{:?}", result.messages);
3562 let text = result.text();
3563 assert!(text.contains("#-56"), "{text}");
3564 assert!(text.contains("#-128"), "{text}");
3565 assert!(text.contains("#24]"), "{text}");
3566 assert!(text.contains("#28]"), "{text}");
3567 assert!(text.contains("str q"), "{text}");
3568 }
3569
3570 #[test]
3573 fn an_aarch64_long_double_is_a_quad_in_a_vector_register() {
3574 let mut opts = options();
3575 opts.emit = EmitKind::Asm;
3576 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3577 let source = "void f(long double *p, long double x) { *p = *p + x; }\n";
3578 let result = run(&opts, source);
3579 assert!(!result.failed(), "{:?}", result.messages);
3580 let text = result.text();
3581 assert!(text.contains("ldr q"), "{text}");
3582 assert!(text.contains("str q"), "{text}");
3583 assert!(text.contains("__addtf3"), "{text}");
3584 }
3585
3586 #[test]
3589 fn an_aarch64_thread_local_is_reached_through_tpidr_el0() {
3590 let mut opts = options();
3591 opts.emit = EmitKind::Asm;
3592 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
3593 let source = "__thread int n;\nint *f(void) { return &n; }\n\
3594 void *g(void) { return __builtin_thread_pointer(); }\n";
3595 let result = run(&opts, source);
3596 assert!(!result.failed(), "{:?}", result.messages);
3597 let text = result.text();
3598 assert!(text.contains(":gottprel:n"), "{text}");
3599 assert!(text.contains(":gottprel_lo12:n]"), "{text}");
3600 assert_eq!(text.matches("mrs x").count(), 2, "{text}");
3601 assert!(text.contains("tpidr_el0"), "{text}");
3602 }
3603
3604 #[test]
3607 fn a_darwin_thread_local_is_reached_through_its_descriptor() {
3608 let source = "__thread int n = 5;\nint *f(void) { return &n; }\n";
3609 for (triple, wanted) in [
3610 ("aarch64-apple-darwin", &["_n@TLVPPAGE\n", "_n@TLVPPAGEOFF]\n", "\tblr x"][..]),
3611 ("x86_64-apple-darwin", &["_n@TLVP(%rip), %rdi\n", "\tcall\t*%"][..]),
3612 ] {
3613 let mut opts = options();
3614 opts.emit = EmitKind::Asm;
3615 opts.target = triple.parse::<Triple>().unwrap();
3616 let result = run(&opts, source);
3617 assert!(!result.failed(), "{triple}: {:?}", result.messages);
3618 let text = result.text();
3619 for want in wanted {
3620 assert!(text.contains(want), "{triple} wanted {want:?}:\n{text}");
3621 }
3622 assert!(text.contains("\n_n:\n\t.quad\t__tlv_bootstrap\n"), "{text}");
3623 assert!(!text.contains("tpidr_el0") && !text.contains("%fs"), "{text}");
3624 }
3625 }
3626
3627 #[test]
3629 fn the_thread_pointer_is_refused_on_darwin() {
3630 let mut opts = options();
3631 opts.emit = EmitKind::Asm;
3632 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3633 let result = run(&opts, "void *f(void) { return __builtin_thread_pointer(); }\n");
3634 assert!(result.failed());
3635 assert!(result.messages[0].contains("thread pointer"), "{:?}", result.messages);
3636 }
3637
3638 #[test]
3641 fn a_darwin_variadic_definition_saves_nothing_and_walks_the_stack() {
3642 let mut opts = options();
3643 opts.emit = EmitKind::Asm;
3644 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3645 let source = "int f(int n, ...) { __builtin_va_list ap; __builtin_va_start(ap, n);\n\
3646 int r = __builtin_va_arg(ap, int); __builtin_va_end(ap); return r; }\n";
3647 let result = run(&opts, source);
3648 assert!(!result.failed(), "{:?}", result.messages);
3649 let text = result.text();
3650 assert!(!text.contains("str q"), "{text}");
3651 assert!(!text.contains("x7"), "{text}");
3652 }
3653
3654 #[test]
3657 fn a_darwin_call_puts_its_variadic_arguments_in_memory() {
3658 let mut opts = options();
3659 opts.emit = EmitKind::Asm;
3660 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3661 let source = "int printf(const char *, ...);\n\
3662 int g(double x) { return printf(\"%d %f\", 7, x); }\n";
3663 let result = run(&opts, source);
3664 assert!(!result.failed(), "{:?}", result.messages);
3665 let text = result.text();
3666 assert!(text.contains("str d0, [sp, #8]"), "{text}");
3667 }
3668
3669 #[test]
3673 fn a_darwin_listing_is_one_apples_assembler_reads() {
3674 let mut opts = options();
3675 opts.emit = EmitKind::Asm;
3676 opts.target = "aarch64-apple-darwin".parse::<Triple>().unwrap();
3677 let source = "extern int ext;\n\
3678 int g[4];\n\
3679 int f(int i) { return g[i] + ext; }\n";
3680 let result = run(&opts, source);
3681 assert!(!result.failed(), "{:?}", result.messages);
3682 let text = result.text();
3683 assert!(text.contains(", _g@PAGE\n"), "{text}");
3684 assert!(text.contains(", _g@PAGEOFF\n"), "{text}");
3685 assert!(text.contains(", _ext@GOTPAGE\n"), "{text}");
3686 assert!(text.contains(", _ext@GOTPAGEOFF]\n"), "{text}");
3687 assert!(!text.contains(":lo12:"), "{text}");
3688 assert!(text.contains("\t.globl\t_g\n\t.zerofill\t__DATA,__bss,_g,16,2\n"), "{text}");
3689 }
3690
3691 #[test]
3697 fn a_signed_char_on_aarch64_is_widened_with_its_sign_before_it_is_added_to() {
3698 for target in ["aarch64-linux-gnu", "aarch64-apple-darwin"] {
3699 let mut opts = options();
3700 opts.emit = EmitKind::Asm;
3701 opts.target = target.parse::<Triple>().unwrap();
3702 let source = "int f(signed char *p) { return *p + 1; }\n\
3703 unsigned g(unsigned short *p) { return *p + 1u; }\n";
3704 let result = run(&opts, source);
3705 assert!(!result.failed(), "{:?}", result.messages);
3706 let text = result.text();
3707 let signed = text.contains("\tsxtb w") || text.contains("\tldrsb w");
3708 assert!(signed, "{target}: {text}");
3709 }
3710 }
3711
3712 #[test]
3724 fn a_construct_the_back_end_cannot_reach_yet_is_reported_against_its_function() {
3725 let mut opts = options();
3726 opts.emit = EmitKind::MirFinal;
3727 let source = "void a(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
3728 s; s.x = 1; v[0] = s.x; }\n\
3729 void b(int n) { int v[n]; struct __attribute__((aligned(32))) S { int x; } \
3730 s; s.x = 1; v[0] = s.x; }\n";
3731 let result = run(&opts, source);
3732 assert!(result.failed());
3733 assert_eq!(result.messages.len(), 2, "{:?}", result.messages);
3734 assert!(result.messages[0].contains("cannot generate code for 'a'"), "{:?}", result);
3735 assert!(result.messages[0].contains("wants more alignment"), "{:?}", result);
3736 assert!(result.messages[1].contains("cannot generate code for 'b'"), "{:?}", result);
3737 assert!(result.text().is_empty());
3738 }
3739
3740 #[test]
3748 fn a_variable_length_array_walks_its_pages_where_every_page_of_the_frame_is_to_be_touched() {
3749 let mut opts = options();
3750 opts.emit = EmitKind::MirFinal;
3751 let source = "void a(int n) { int v[n]; v[0] = 1; }\n";
3752 let plain = run(&opts, source);
3753 assert!(!plain.failed(), "{:?}", plain.messages);
3754 assert!(!plain.text().contains("cmp_set_a_64"), "{}", plain.text());
3755
3756 opts.stack_clash = true;
3757 let result = run(&opts, source);
3758 assert!(!result.failed(), "{:?}", result.messages);
3759 assert!(result.text().contains("cmp_set_a_64"), "{}", result.text());
3760 assert!(result.text().contains("or_mi_8"), "{}", result.text());
3761 }
3762
3763 #[test]
3773 fn a_function_that_keeps_a_frame_pointer_on_windows_reaches_an_object_file() {
3774 let mut opts = options();
3775 opts.emit = EmitKind::Object;
3776 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3777 let source = concat!(
3778 "void use(void *p);\n",
3779 "void array(int n) { int v[n]; v[0] = 1; use(v); }\n",
3780 "void taken(unsigned long n) { use(__builtin_alloca(n)); }\n",
3781 );
3782 let result = run(&opts, source);
3783 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3784 let bytes = match result.artifact {
3785 Artifact::Object { bytes, .. } => bytes,
3786 other => panic!("expected an object, got {other:?}"),
3787 };
3788 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3789
3790 let mut opts = options();
3793 opts.emit = EmitKind::Object;
3794 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3795 }
3796
3797 #[test]
3808 fn the_address_of_a_function_this_file_only_declares_reaches_a_windows_object() {
3809 let source = concat!(
3810 "void other(void *p);\n",
3811 "void takes(void (*f)(void *));\n",
3812 "void (*held)(void *);\n",
3813 "void pass(void) { takes(other); }\n",
3814 "void keep(void) { held = other; }\n",
3815 "void call(void) { other(0); }\n",
3816 );
3817 let mut opts = options();
3818 opts.emit = EmitKind::Object;
3819 opts.target = "x86_64-pc-windows-gnu".parse::<Triple>().unwrap();
3820 let result = run(&opts, source);
3821 assert_eq!(result.messages, Vec::<String>::new(), "{result:?}");
3822 let bytes = match result.artifact {
3823 Artifact::Object { bytes, .. } => bytes,
3824 other => panic!("expected an object, got {other:?}"),
3825 };
3826 assert_eq!(&bytes[..2], b"\x64\x86", "an object that says which machine it is for");
3827
3828 let mut opts = options();
3831 opts.emit = EmitKind::Object;
3832 assert_eq!(run(&opts, source).messages, Vec::<String>::new());
3833 }
3834
3835 #[test]
3849 fn an_opcode_with_no_name_in_the_rule_language_is_named_by_its_own_spelling() {
3850 let mut opts = options();
3851 opts.emit = EmitKind::MirFinal;
3852 let source =
3853 "long double f(int a) {\n __int128 wide = a;\n return (long double) wide;\n}\n";
3854 let result = run(&opts, source);
3855 assert!(result.failed());
3856 assert!(
3857 result.messages[0].contains("no rule lowers a `sext` producing a `i128`"),
3858 "{result:?}"
3859 );
3860 assert!(result.messages[0].contains(":2:"), "the line the widening is on: {result:?}");
3861 assert!(!result.messages[0].contains("this instruction"), "{result:?}");
3862 }
3863
3864 #[test]
3866 fn the_note_on_unfinished_work_points_at_the_issues_rather_than_at_the_plan() {
3867 let mut opts = options();
3868 opts.emit = EmitKind::MirFinal;
3869 let source = "long double f(int a) { __int128 wide = a; return (long double) wide; }\n";
3870 let result = run(&opts, source);
3871 assert!(result.failed());
3872 let note = result.messages.iter().find(|line| line.contains("note:")).expect("a note");
3873 assert!(note.contains("https://github.com/tamnd/rucc/issues"), "{note}");
3874 assert!(!note.contains("spec/17-milestones.md"), "{note}");
3875 }
3876
3877 #[test]
3879 fn the_frame_flags_on_the_command_line_reach_the_generated_frame() {
3880 let source = "int f(int a) { return a; }\n";
3881 assert!(!mir(source).contains("$rbp"), "a leaf needs no frame pointer when told so");
3882
3883 let mut opts = options();
3884 opts.emit = EmitKind::MirFinal;
3885 opts.frame_pointer = Some(true);
3886 let kept = run(&opts, source).text().to_owned();
3887 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
3888
3889 opts.frame_pointer = None;
3891 let kept = run(&opts, source).text().to_owned();
3892 assert!(kept.contains("x64.push_64 $rbp"), "{kept}");
3893 }
3894
3895 fn asm(source: &str) -> String {
3897 let mut opts = options();
3898 opts.emit = EmitKind::Asm;
3899 let result = run(&opts, source);
3900 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
3901 result.text().to_owned()
3902 }
3903
3904 #[test]
3911 fn a_function_goes_from_c_to_assembly_an_assembler_would_take() {
3912 let text = asm("int add(int a, int b) { return a + b; }\n");
3913 assert!(text.contains("\t.globl\tadd\n"), "{text}");
3914 assert!(text.contains("\t.type\tadd, @function\n"), "{text}");
3915 assert!(text.contains("\nadd:\n"), "{text}");
3916 assert!(text.contains("\taddl\t"), "{text}");
3917 assert!(text.contains("\tret\n"), "{text}");
3918 assert!(text.contains("\t.size\tadd, .-add\n"), "{text}");
3919 assert!(text.contains(".note.GNU-stack"), "{text}");
3922 }
3923
3924 #[test]
3930 fn a_call_through_a_function_pointer_goes_through_the_register_it_is_in() {
3931 let text = asm("int g(int);\nint f(int (*p)(int), int a) { return p(a) + g(a); }\n");
3932 assert!(text.contains("\tcall\t*%"), "{text}");
3933 assert!(text.contains("\tcall\tg\n"), "{text}");
3934 assert!(text.contains("%rdi"), "{text}");
3938 }
3939
3940 #[test]
3944 fn the_address_of_a_global_is_read_from_the_instruction_pointer() {
3945 let text = asm("extern int counter;\nint f(void) { return counter; }\n");
3946 assert!(text.contains("\tmovl\tcounter(%rip), %eax\n"), "{text}");
3947 }
3948
3949 #[test]
3958 fn a_branch_on_a_comparison_jumps_on_the_opposite_of_what_it_compared() {
3959 let arms = "return 1; return 2;";
3960 let signed = [("==", "jne"), ("!=", "je"), ("<", "jge"), ("<=", "jg"), (">", "jle")];
3961 for (operator, jump) in signed.into_iter().chain([(">=", "jl")]) {
3962 let text = asm(&format!("int f(int a, int b) {{ if (a {operator} b) {arms} }}\n"));
3963 assert!(
3964 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
3965 "{operator}: {text}"
3966 );
3967 assert!(!text.contains("\tset"), "{operator}: {text}");
3968 assert!(!text.contains("\ttest"), "{operator}: {text}");
3969 }
3970 let unsigned = [("<", "jae"), ("<=", "ja"), (">", "jbe"), (">=", "jb")];
3971 for (operator, jump) in unsigned {
3972 let source =
3973 format!("int f(unsigned a, unsigned b) {{ if (a {operator} b) {arms} }}\n");
3974 let text = asm(&source);
3975 assert!(
3976 text.contains(&format!("\tcmpl\t%esi, %edi\n\t{jump}\t")),
3977 "{operator}: {text}"
3978 );
3979 }
3980
3981 let text = asm("int f(int a) { if (a < 7) return 1; return 2; }\n");
3984 assert!(text.contains("\tcmpl\t$7, %edi\n\tjge\t"), "{text}");
3985 }
3986
3987 #[test]
3993 fn a_comparison_whose_answer_the_program_wanted_still_writes_a_byte() {
3994 let text = asm("int f(int a, int b) { return a < b; }\n");
3995 assert!(text.contains("\tsetl\t"), "{text}");
3996 }
3997
3998 fn optimized(source: &str) -> String {
4000 let mut opts = options();
4001 opts.emit = EmitKind::Asm;
4002 opts.opt_level = rucc_session::OptLevel::O2;
4003 let result = run(&opts, source);
4004 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4005 result.text().to_owned()
4006 }
4007
4008 #[test]
4010 fn opt_info_says_what_each_switch_became_and_a_forced_shape_is_what_it_says() {
4011 let arms: String = (0..40)
4012 .map(|k| format!("case {}: return g({k});", k * 17))
4013 .collect::<Vec<_>>()
4014 .join(" ");
4015 let source = format!("int g(int);\nint f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n");
4016 let said = |shape: Option<&str>| {
4017 let mut opts = options();
4018 opts.emit = EmitKind::Asm;
4019 opts.opt_level = rucc_session::OptLevel::O2;
4020 opts.opt_info = vec![String::new()];
4021 opts.switch_shape = shape.map(str::to_owned);
4022 let result = run(&opts, &source);
4023 assert_eq!(result.messages, Vec::<String>::new());
4024 let lines: Vec<String> = result
4025 .remarks
4026 .lines()
4027 .filter(|line| line.contains("[switch-lowering]"))
4028 .map(str::to_owned)
4029 .collect();
4030 assert_eq!(lines.len(), 1, "{}", result.remarks);
4031 lines[0].clone()
4032 };
4033 assert!(said(None).contains(": f: optimized: switch of 40 cases lowered as a tree;"));
4034 assert!(said(Some("table")).contains("lowered as a table;"));
4035 assert!(said(Some("walk")).contains("lowered as a walk;"));
4036 }
4037
4038 #[test]
4048 fn a_switch_whose_arms_are_a_function_of_the_label_is_a_range_check_and_arithmetic() {
4049 let arms: String =
4050 (0..16).map(|k| format!("case {k}: return {};", k + 1)).collect::<Vec<_>>().join(" ");
4051 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
4052 assert!(text.contains("\tcmpl\t$15, %edi\n\tja\t"), "{text}");
4053 assert!(text.contains("\taddl\t$1, %edi"), "{text}");
4054 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
4055 }
4056
4057 #[test]
4065 fn a_dense_switch_whose_arms_are_not_a_line_is_a_load_from_a_table() {
4066 let arms: String = (0..16)
4067 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
4068 .collect::<Vec<_>>()
4069 .join(" ");
4070 let text = optimized(&format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
4071 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
4072 assert!(!text.contains("\tjmp\t*"), "{text}");
4073 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
4074 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
4075 let section = text[..text.find("CSWTCH.0:").unwrap_or(0)].rfind("\t.section\t.rodata");
4076 assert!(section.is_some(), "{text}");
4077 assert_eq!(table.matches("\t.long\t").count(), 16, "{text}");
4078 assert!(table.contains("\t.long\t100\n"), "{text}");
4079 }
4080
4081 #[test]
4087 fn a_switch_whose_arms_give_strings_is_a_table_of_how_far_away_they_are() {
4088 let text = optimized(
4089 "const char *f(int k) { switch (k) { case 0: return \"zero\"; \
4090 case 1: return \"one\"; case 2: return \"two\"; case 3: return \"three\"; } \
4091 return \"many\"; }\n",
4092 );
4093 assert_eq!(text.matches("\tcmp").count(), 1, "{text}");
4094 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
4095 assert!(!text.contains(".data.rel.ro"), "{text}");
4096 let at = text.find("CSWTCH.0:").expect("the table is in the output");
4097 assert!(text[..at].rfind("\t.section\t.rodata").is_some(), "{text}");
4098 let table = &text[at..];
4099 assert_eq!(table.matches(" - .\n").count(), 4, "{text}");
4100 assert!(table.contains("\t.long\t.Lstr.1+4 - .\n"), "{text}");
4101 }
4102
4103 #[test]
4109 fn a_table_at_os_has_cells_as_narrow_as_its_answers() {
4110 let arms: String = (0..16)
4111 .map(|k| format!("case {k}: return {};", if k == 9 { 100 } else { k + 1 }))
4112 .collect::<Vec<_>>()
4113 .join(" ");
4114 let mut opts = options();
4115 opts.emit = EmitKind::Asm;
4116 opts.opt_level = rucc_session::OptLevel::Os;
4117 let result = run(&opts, &format!("int f(int x) {{ switch (x) {{ {arms} }} return 0; }}\n"));
4118 assert_eq!(result.messages, Vec::<String>::new());
4119 let text = result.text();
4120 let table = &text[text.find("CSWTCH.0:").expect("the table is in the output")..];
4121 assert_eq!(table.matches("\t.byte\t").count(), 16, "{text}");
4122 assert!(text.contains("\tmovsbl\t"), "{text}");
4123 }
4124
4125 #[test]
4132 fn a_table_that_covers_its_operand_has_no_range_check() {
4133 let text = optimized(
4134 "int f(unsigned x) { switch (x & 3) { case 0: return 5; case 1: return 9; \
4135 case 2: return 2; case 3: return 7; } return -1; }\n",
4136 );
4137 assert!(text.contains("leaq\tCSWTCH.0(%rip)"), "{text}");
4138 assert!(!text.contains("\tcmp"), "{text}");
4139 assert!(!text.contains("$-1"), "{text}");
4140 }
4141
4142 #[test]
4147 fn a_store_to_a_local_the_loop_just_read_is_a_conditional_move() {
4148 let text = optimized(
4149 "int f(const int *v, int n, int k) { int best[8] = {0}; \
4150 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; \
4151 return best[k & 7]; }\n",
4152 );
4153 assert!(text.contains("\tcmovgl"), "{text}");
4154 assert!(!text.contains("\tset"), "{text}");
4155 assert!(!text.contains("\ttestb"), "{text}");
4156 }
4157
4158 #[test]
4160 fn a_store_to_a_global_the_loop_just_read_keeps_its_branch() {
4161 let text = optimized(
4162 "int best[8]; void f(const int *v, int n) { \
4163 for (int i = 0; i < n; i++) if (v[i] > best[i & 7]) best[i & 7] = v[i]; }\n",
4164 );
4165 assert!(!text.contains("\tcmov"), "{text}");
4166 }
4167
4168 #[test]
4176 fn a_conversion_from_a_constant_double_is_the_number_it_converts_to() {
4177 let text = optimized("int f(void) { double d = 2.75; return (int) d; }\n");
4178 assert!(text.contains("movl\t$2, %eax"), "{text}");
4179 assert!(!text.contains("cvttsd2si"), "{text}");
4180 }
4181
4182 #[test]
4190 fn a_slot_of_a_read_only_table_is_the_value_the_table_holds() {
4191 let text =
4192 optimized("static const int t[4] = {10, 20, 30, 40};\nint f(void) { return t[2]; }\n");
4193 assert!(text.contains("movl\t$30, %eax"), "{text}");
4194 assert!(!text.contains("t(%rip)"), "{text}");
4195 }
4196
4197 #[test]
4200 fn a_byte_of_a_read_only_string_is_the_byte_the_string_spells() {
4201 let text = optimized("static const char s[] = \"abc\";\nint f(void) { return s[1]; }\n");
4202 assert!(text.contains("movl\t$98, %eax"), "{text}");
4203 }
4204
4205 #[test]
4209 fn a_table_that_is_not_read_only_keeps_its_load() {
4210 let text = optimized(
4211 "static int t[4] = {10, 20, 30, 40};\nvoid g(int x) { t[2] = x; }\nint f(void) { return t[2]; }\n",
4212 );
4213 assert!(!text.contains("movl\t$30, %eax"), "{text}");
4214 }
4215
4216 #[test]
4223 fn a_call_guarded_by_a_condition_a_read_only_object_settles_is_not_emitted() {
4224 let text = optimized(
4225 "void link_error(void);\nconst double one = 1.0;\nint main(void) { if ((int) one != 1) link_error(); return 0; }\n",
4226 );
4227 assert!(!text.contains("call\tlink_error"), "{text}");
4228 }
4229
4230 #[test]
4232 fn a_cast_between_a_pointer_and_an_integer_leaves_the_value_where_it_is() {
4233 let text = asm("long f(void *p) { return (long)p; }\n");
4234 for line in text.lines().filter(|line| line.starts_with('\t') && !line.contains('.')) {
4239 let mnemonic = line.split_whitespace().next().unwrap_or("");
4240 assert!(matches!(mnemonic, "movq" | "ret"), "{line} in\n{text}");
4241 }
4242 }
4243
4244 #[test]
4248 fn an_argument_past_the_last_register_is_read_out_of_the_caller_s_stack() {
4249 let six = "long a, long b, long c, long d, long e, long f";
4250 let text = asm(&format!("long f({six}, long g, long h) {{ return g + h; }}\n"));
4251
4252 assert!(text.contains("\tmovq\t8(%rsp), "), "{text}");
4259 assert!(text.contains("\taddq\t16(%rsp), "), "{text}");
4260
4261 let narrow = asm(&format!("int f({six}, int g) {{ return g; }}\n"));
4265 assert!(narrow.contains("\tmovl\t8(%rsp), "), "{narrow}");
4266 let eight =
4267 "double a, double b, double c, double d, double e, double f, double g, double h";
4268 let float = asm(&format!("double f({eight}, double i) {{ return i; }}\n"));
4269 assert!(float.contains("\tmovsd\t8(%rsp), "), "{float}");
4270 }
4271
4272 #[test]
4275 fn a_call_writes_the_arguments_with_no_register_left_at_the_stack_pointer() {
4276 let six = "1, 2, 3, 4, 5, 6";
4277 let decl = "long g(long, long, long, long, long, long, long, long);\n";
4278 let text = asm(&format!("{decl}long f(void) {{ return g({six}, 7, 8); }}\n"));
4279
4280 assert!(text.contains("\tmovq\t%"), "{text}");
4281 assert!(text.contains(", (%rsp)\n"), "{text}");
4282 assert!(text.contains(", 8(%rsp)\n"), "{text}");
4283 assert!(text.contains("\tsubq\t$"), "{text}");
4285
4286 let narrow = "int g(int, int, int, int, int, int, int);\n";
4288 let text = asm(&format!("{narrow}int f(void) {{ return g({six}, 7); }}\n"));
4289 assert!(text.contains("\tmovl\t%"), "{text}");
4290 assert!(text.contains(", (%rsp)\n"), "{text}");
4291 }
4292
4293 #[test]
4296 fn a_variadic_call_counts_registers_and_not_arguments() {
4297 let nine = "1., 2., 3., 4., 5., 6., 7., 8., 9.";
4298 let decl = "int g(int, ...);\n";
4299 let text = asm(&format!("{decl}int f(void) {{ return g(0, {nine}); }}\n"));
4300
4301 assert!(text.contains("\tmovl\t$8, "), "eight registers, not nine: {text}");
4302 assert!(text.contains("\tmovsd\t%"), "{text}");
4303 assert!(text.contains(", (%rsp)\n"), "{text}");
4304 }
4305
4306 #[test]
4311 fn a_variadic_function_writes_the_argument_registers_it_was_handed_into_its_frame() {
4312 let body =
4313 "__builtin_va_list ap; __builtin_va_start(ap, n); __builtin_va_end(ap); return n;";
4314 let text = asm(&format!("int f(int n, ...) {{ {body} }}\n"));
4315
4316 let stores = |mnemonic: &str| text.matches(&format!("\t{mnemonic}\t%")).count();
4319 assert!(text.contains(", 8(%r"), "the second slot, not the first: {text}");
4320 assert!(!text.contains(", 0(%r"), "{text}");
4321 assert_eq!(stores("movaps"), 8, "every vector register: {text}");
4324 assert_eq!(stores("movsd"), 0, "and the whole of each one: {text}");
4325
4326 assert!(text.contains("\tsubq\t$"), "{text}");
4328 }
4329
4330 #[test]
4333 fn va_start_writes_the_four_fields_the_psabi_describes() {
4334 let start = "__builtin_va_list ap; __builtin_va_start(ap, d);";
4335 let params = "int a, int b, int c, double d";
4336 let text = asm(&format!("int f({params}, ...) {{ {start} return a; }}\n"));
4337
4338 assert!(text.contains(" movl $24, "), "{text}");
4342 assert!(text.contains(" movl $64, "), "{text}");
4343 assert!(text.contains(", 8(%r"), "{text}");
4347 assert!(text.contains(", 16(%r"), "{text}");
4348 let frame: u32 = text
4349 .lines()
4350 .find_map(|line| line.trim().strip_prefix("subq $")?.split(',').next()?.parse().ok())
4351 .expect("a variadic function takes a frame for the save area");
4352 let above = |line: &str| {
4353 let at: u32 = line.trim().strip_prefix("leaq ")?.split('(').next()?.parse().ok()?;
4354 Some(at > frame)
4355 };
4356 assert!(text.lines().filter_map(above).any(|it| it), "{frame}: {text}");
4357 }
4358
4359 #[test]
4362 fn va_arg_branches_on_whether_the_argument_is_still_in_the_save_area() {
4363 let read = "__builtin_va_list ap; __builtin_va_start(ap, n);";
4364 let ints = format!("int f(int n, ...) {{ {read} return __builtin_va_arg(ap, int); }}\n");
4365 let text = asm(&ints);
4366
4367 assert!(text.contains("$40, "), "{text}");
4370 assert!(text.contains(" cmpl "), "{text}");
4371 assert!(text.contains(" ja "), "{text}");
4375
4376 let arg = "__builtin_va_arg(ap, double)";
4377 let text = asm(&format!("double f(int n, ...) {{ {read} return {arg}; }}\n"));
4378 assert!(text.contains("$160, "), "the last vector slot: {text}");
4379 }
4380
4381 #[test]
4384 fn a_structure_assignment_is_a_move_for_each_word_of_it() {
4385 let decl = "struct pair { long a, b; };\n";
4386 let body = "struct pair p = *q; return p.a + p.b;";
4387 let text = asm(&format!("{decl}long f(struct pair *q) {{ {body} }}\n"));
4388
4389 assert!(!text.contains("memcpy"), "nothing calls the library: {text}");
4390 assert!(!text.contains("\tcall"), "{text}");
4391 assert!(text.matches("\tmovq\t").count() >= 4, "two words each way: {text}");
4393 }
4394
4395 #[test]
4398 fn how_wide_a_word_of_a_copy_is_follows_the_alignment() {
4399 let decl = "struct bytes { char a[8]; };\n";
4400 let body = "struct bytes p = *q; return p.a[0];";
4401 let text = asm(&format!("{decl}int f(struct bytes *q) {{ {body} }}\n"));
4402
4403 assert!(text.matches("\tmovb\t").count() >= 16, "a byte at a time: {text}");
4405 }
4406
4407 #[test]
4410 fn the_part_of_an_initialiser_that_names_nothing_is_stored_as_zero() {
4411 let decl = "struct wide { long a, b, c; };\n";
4412 let text = asm(&format!("{decl}long f(void) {{ struct wide w = {{ 7 }}; return w.c; }}\n"));
4413
4414 assert!(!text.contains("memset"), "nothing calls the library: {text}");
4415 assert!(text.contains("\tmovq\t$0, ") || text.contains("\txorl\t"), "the zero: {text}");
4420 }
4421
4422 #[test]
4425 fn a_copy_too_large_to_unroll_calls_the_runtime() {
4426 let decl = "struct huge { char a[4096]; };\n";
4427 let mut opts = options();
4428 opts.emit = EmitKind::Asm;
4429 let source = format!("{decl}void f(struct huge *p, struct huge *q) {{ *p = *q; }}\n");
4430 let result = run(&opts, &source);
4431 assert!(!result.failed(), "{:?}", result.messages);
4432 let text = result.text();
4433 assert!(text.contains("call") && text.contains("memcpy"), "{text}");
4434 assert!(text.contains("4096"), "the size travels: {text}");
4437 }
4438
4439 #[test]
4446 fn a_structure_too_large_to_unroll_is_copied_into_the_argument_area_by_the_runtime() {
4447 let decl = "struct huge { char a[4096]; };\nint take(struct huge);\n";
4448 let text = asm(&format!("{decl}int f(struct huge *p) {{ return take(*p); }}\n"));
4449
4450 let copy = text.find("call\tmemcpy").expect("the copy");
4451 let call = text.find("call\ttake").expect("the call");
4452 assert!(copy < call, "the copy comes first: {text}");
4453 assert!(text.contains("movq\t%rsp, %rdi"), "the destination: {text}");
4458 assert!(text.contains("$4096, %edx"), "the size: {text}");
4459 }
4460
4461 #[test]
4464 fn a_realigned_frame_reads_them_through_the_frame_pointer() {
4465 let six = "long a, long b, long c, long d, long e, long f";
4466 let body = "_Alignas(32) long wide[4]; wide[0] = g; return wide[0];";
4467 let text = asm(&format!("long f({six}, long g) {{ {body} }}\n"));
4468
4469 assert!(text.contains("\tandq\t$-32, %rsp"), "{text}");
4473 assert!(text.contains("\tmovq\t16(%rbp), "), "{text}");
4474 assert!(!text.contains("\tmovq\t16(%rsp), "), "{text}");
4475 }
4476
4477 #[test]
4479 fn the_target_decides_how_the_assembly_is_spelled() {
4480 let mut opts = options();
4481 opts.emit = EmitKind::Asm;
4482 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
4483 let text = run(&opts, "int f(void) { return 0; }\n").text().to_owned();
4484 assert!(text.contains("__TEXT,__text"), "{text}");
4485 assert!(text.contains("\n_f:\n"), "{text}");
4486 assert!(!text.contains(".note.GNU-stack"), "{text}");
4487 }
4488
4489 fn obj(source: &str) -> Vec<u8> {
4491 let mut opts = options();
4492 opts.emit = EmitKind::Object;
4493 let result = run(&opts, source);
4494 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
4495 match result.artifact {
4496 Artifact::Object { bytes, .. } => bytes,
4497 other => panic!("expected an object, got {other:?}"),
4498 }
4499 }
4500
4501 #[test]
4507 fn a_function_goes_from_c_to_an_object_a_linker_would_take() {
4508 let bytes = obj("int add(int a, int b) { return a + b; }\n");
4509 assert_eq!(&bytes[..4], b"\x7fELF", "an object file starts by saying it is one");
4510 let text = asm("int add(int a, int b) { return a + b; }\n");
4511 assert!(
4512 text.contains("\taddl\t"),
4513 "and the listing of it is the same instructions:\n{text}"
4514 );
4515 }
4516
4517 #[test]
4519 fn a_variable_goes_from_c_to_the_section_it_belongs_in() {
4520 let text = asm("int counter = 42;\nstatic int hidden;\nconst int fixed = 7;\n");
4521 assert!(text.contains("\t.data\n\t.globl\tcounter\n"), "{text}");
4522 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
4523 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
4524 assert!(text.contains("\t.bss\n\t.p2align\t2\n"), "{text}");
4527 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
4528 assert!(!text.contains(".globl\thidden"), "{text}");
4529 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4532 }
4533
4534 #[test]
4541 fn a_bit_field_initializer_writes_every_byte_of_the_value_and_not_only_the_ones_that_are_set() {
4542 let text = asm("struct s { unsigned f : 20; } x = { 0x12300 };\n");
4543 assert!(text.contains("\t.data\n"), "there is something to write: {text}");
4544 assert!(text.contains("\nx:\n\t.ascii\t\"\\000#\\001\"\n"), "and it is the value: {text}");
4545
4546 let text = asm("struct s { unsigned a : 8; unsigned b : 8; } x = { 0, 3 };\n");
4549 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\003\"\n"), "{text}");
4550
4551 let text = asm("struct s { unsigned long long f : 40; } x = { 0x100000 };\n");
4554 assert!(text.contains("\nx:\n\t.ascii\t\"\\000\\000\\020\"\n\t.space\t5\n"), "{text}");
4555
4556 let text = asm("struct s { unsigned f : 20; } x = { 0 };\n");
4558 assert!(text.contains("\t.bss\n"), "an object of zeroes is zeroes: {text}");
4559 assert!(text.contains("\nx:\n\t.space\t4\n"), "{text}");
4560 }
4561
4562 #[test]
4564 fn a_string_literal_is_a_variable_with_a_name_no_program_could_write() {
4565 let text = asm("const char *f(void) { return \"hi\"; }\n");
4566 assert!(text.contains("\t.ascii\t\"hi\\000\"\n"), "{text}");
4567 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4568 let label = text
4569 .lines()
4570 .find(|line| line.starts_with(".Lstr"))
4571 .unwrap_or_else(|| panic!("a label for the literal in\n{text}"));
4572 assert!(!text.contains(&format!(".globl\t{}", label.trim_end_matches(':'))), "{text}");
4573 }
4574
4575 #[test]
4577 fn an_address_in_an_initializer_is_left_to_the_linker() {
4578 let source = "int counter;\nint *p = &counter;\n";
4579 let text = asm(source);
4580 assert!(text.contains("\np:\n\t.quad\tcounter\n"), "{text}");
4581 let bytes = obj(source);
4584 assert!(bytes.windows(8).any(|w| w == b"counter\0"), "the object has to name it");
4585 }
4586
4587 #[test]
4596 fn a_constant_holding_an_address_goes_in_the_section_the_loader_may_write_once() {
4597 let text = asm("static void a(void) {}\nstatic void b(void) {}\n\
4600 struct m { void (*x)(void); void (*y)(void); };\n\
4601 const struct m t = { a, b };\n");
4602 assert!(text.contains("\t.section\t.data.rel.ro.local,\"aw\",@progbits\n"), "{text}");
4603 assert!(text.contains("\nt:\n\t.quad\ta\n\t.quad\tb\n"), "{text}");
4604
4605 let text =
4608 asm("void a(void);\nstruct m { void (*x)(void); };\nconst struct m t = { a };\n");
4609 assert!(text.contains("\t.section\t.data.rel.ro,\"aw\",@progbits\n"), "{text}");
4610
4611 let text = asm("const int fixed = 7;\n");
4613 assert!(text.contains("\t.section\t.rodata\n"), "{text}");
4614 }
4615
4616 #[test]
4623 fn a_thread_local_variable_is_storage_a_thread_gets_a_copy_of_and_an_offset_into_it() {
4624 let text = asm("_Thread_local int x = 1;\nint read(void) { return x; }\n");
4625 assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
4628 assert!(text.contains("\t.type\tx, @tls_object\n"), "{text}");
4629 assert!(text.contains("x@GOTTPOFF(%rip)"), "{text}");
4632 assert!(text.contains("%fs:0"), "{text}");
4633 }
4634
4635 #[test]
4641 fn the_address_of_this_thread_s_own_storage_is_read_out_of_the_segment_register() {
4642 let text = asm("void *here(void) { return __builtin_thread_pointer(); }\n");
4643 assert!(text.contains("movq\t%fs:0, "), "{text}");
4644 assert!(!text.contains("GOTTPOFF"), "{text}");
4646 }
4647
4648 #[test]
4659 fn a_prefetch_is_one_of_four_instructions_and_the_locality_is_what_picks() {
4660 for (locality, wanted) in
4661 [(0, "prefetchnta"), (1, "prefetcht2"), (2, "prefetcht1"), (3, "prefetcht0")]
4662 {
4663 let source =
4664 format!("void warm(void *p) {{ __builtin_prefetch(p, 0, {locality}); }}\n");
4665 let text = asm(&source);
4666 assert!(text.contains(&format!("\t{wanted}\t")), "locality {locality}: {text}");
4667 }
4668 let text = asm("void warm(void *p) { __builtin_prefetch(p); }\n");
4670 assert!(text.contains("\tprefetcht0\t"), "{text}");
4671 let text = asm("void warm(void *p) { __builtin_prefetch(p, 1); }\n");
4674 assert!(text.contains("\tprefetcht0\t"), "{text}");
4675 assert!(!text.contains("prefetchw"), "{text}");
4676 }
4677
4678 #[test]
4681 fn an_aarch64_prefetch_is_a_prfm_that_says_the_locality_and_whether_it_writes() {
4682 let mut opts = options();
4683 opts.emit = EmitKind::Asm;
4684 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
4685 for (write, kind) in [(0, "pld"), (1, "pst")] {
4686 for (locality, wanted) in [(0, "l1strm"), (1, "l3keep"), (2, "l2keep"), (3, "l1keep")] {
4687 let source = format!(
4688 "void warm(void *p) {{ __builtin_prefetch(p, {write}, {locality}); }}\n"
4689 );
4690 let result = run(&opts, &source);
4691 assert_eq!(result.messages, Vec::<String>::new(), "{source}");
4692 let text = result.text();
4693 assert!(text.contains("prfm"), "{source}{text}");
4694 assert!(text.contains(&format!("{kind}{wanted}, [x0]")), "{source}{text}");
4695 }
4696 }
4697 }
4698
4699 #[test]
4710 fn a_trap_is_the_instruction_the_machine_has_no_meaning_for() {
4711 let text = asm("void stop(void) { __builtin_trap(); }\n");
4712 assert!(text.contains("\tud2\n"), "{text}");
4713 assert!(!text.contains("\tcall"), "a stop is not a call to anything: {text}");
4714
4715 let text = asm("int stop(int a) { __builtin_trap(); return a + 1; }\n");
4716 assert!(text.contains("\tud2\n"), "{text}");
4717 assert!(text.contains("\taddl\t"), "the block goes on after a stop: {text}");
4718 }
4719
4720 #[test]
4724 fn cpu_init_is_a_call_to_the_libgcc_function_that_fills_in_the_model() {
4725 let text = asm("void start(void) { __builtin_cpu_init(); }\n");
4726 assert!(text.contains("\tcall\t__cpu_indicator_init"), "{text}");
4727 assert!(!text.contains("__builtin_cpu_init"), "{text}");
4728 }
4729
4730 #[test]
4738 fn cpu_supports_is_a_bit_of_the_words_libgcc_fills_in() {
4739 let text = asm("int f(void) { return __builtin_cpu_supports(\"sse4.2\"); }\n");
4740 assert!(text.contains("__cpu_model"), "{text}");
4741 assert!(text.contains("12(%"), "the fourth word of the model: {text}");
4742 assert!(text.contains("$256"), "{text}");
4743 assert!(!text.contains("\tcall"), "the answer is a read and not a call: {text}");
4744
4745 let text = asm("int f(void) { return __builtin_cpu_supports(\"vpclmulqdq\"); }\n");
4746 assert!(text.contains("__cpu_features2"), "{text}");
4747 assert!(text.contains("$2,"), "{text}");
4748
4749 let text = asm("int f(void) { return __builtin_cpu_supports(\"xsave\"); }\n");
4750 assert!(text.contains("__cpu_features2"), "{text}");
4751 assert!(text.contains("4(%"), "the second word of the second object: {text}");
4752 assert!(text.contains("$131072"), "{text}");
4753
4754 let text = asm("int f(void) { return __builtin_cpu_supports(\"avx512vbmi2\"); }\n");
4755 assert!(text.contains("set"), "the top bit is answered as a one: {text}");
4756 }
4757
4758 #[test]
4761 fn cpu_is_compares_one_word_of_the_model_with_a_number() {
4762 let text = asm("int f(void) { return __builtin_cpu_is(\"amd\"); }\n");
4763 assert!(text.contains("__cpu_model"), "{text}");
4764 assert!(text.contains("$2,"), "{text}");
4765
4766 let text = asm("int f(void) { return __builtin_cpu_is(\"znver4\"); }\n");
4767 assert!(text.contains("8(%"), "the subtype is the third word: {text}");
4768 assert!(text.contains("$29,"), "{text}");
4769 }
4770
4771 #[test]
4775 fn a_cpu_builtin_takes_a_name_it_knows_written_as_a_literal() {
4776 let mut opts = options();
4777 opts.emit = EmitKind::Ir;
4778 for (source, wanted) in [
4779 (
4780 "int f(const char *s) { return __builtin_cpu_supports(s); }\n",
4781 "parameter to builtin must be a string constant or literal",
4782 ),
4783 (
4784 "int f(void) { return __builtin_cpu_supports(\"sse5\"); }\n",
4785 "parameter to builtin not valid: sse5",
4786 ),
4787 (
4788 "int f(void) { return __builtin_cpu_is(\"sse\"); }\n",
4789 "parameter to builtin not valid: sse",
4790 ),
4791 ] {
4792 let result = run(&opts, source);
4793 assert!(
4794 result.messages.iter().any(|m| m.contains(wanted)),
4795 "{source}{:?}",
4796 result.messages
4797 );
4798 }
4799 let text = asm("int f(void) { return __builtin_cpu_supports((const char *)\"avx2\"); }\n");
4801 assert!(text.contains("$1024"), "{text}");
4802
4803 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
4804 for source in [
4805 "void f(void) { __builtin_cpu_init(); }\n",
4806 "int f(void) { return __builtin_cpu_supports(\"sse4.2\"); }\n",
4807 ] {
4808 let result = run(&opts, source);
4809 assert!(
4810 result.messages.iter().any(|m| m.contains("only available on x86-64")),
4811 "{source}{:?}",
4812 result.messages
4813 );
4814 }
4815 }
4816
4817 #[test]
4829 fn assume_aligned_is_its_first_argument_and_keeps_the_rest() {
4830 let text = asm("void *aligned(char *p) { return __builtin_assume_aligned(p, 16); }\n");
4831 assert!(!text.contains("assume_aligned"), "{text}");
4832 assert!(!text.contains("\tcall"), "nothing is called for an alignment fact: {text}");
4833
4834 let source = "unsigned long width(void);\n\
4835 void *aligned(char *p) { return __builtin_assume_aligned(p, width()); }\n";
4836 let text = asm(source);
4837 assert!(!text.contains("assume_aligned"), "{text}");
4838 assert!(text.contains("width"), "the argument that is not the answer still runs: {text}");
4839 }
4840
4841 #[test]
4851 fn the_frame_address_is_the_frame_pointer_after_walking_that_many_links() {
4852 let text = asm("void *here(void) { return __builtin_frame_address(0); }\n");
4853 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
4854 assert!(text.contains("movq\t%rbp, %rax"), "{text}");
4855 assert!(!text.contains("\tcall"), "a frame address is not a call to anything: {text}");
4856
4857 let walk = |depth: u32| {
4858 let source = format!("void *up(void) {{ return __builtin_frame_address({depth}); }}\n");
4859 asm(&source).matches("movq\t(%r").count()
4860 };
4861 assert_eq!(walk(1), 1, "one link is one load");
4862 assert_eq!(walk(3), 3, "three links are three loads");
4863 }
4864
4865 #[test]
4875 fn the_return_address_is_one_word_above_the_frame_the_walk_ended_at() {
4876 let text = asm("void *back(void) { return __builtin_return_address(0); }\n");
4877 assert!(text.contains("pushq\t%rbp"), "a function that asks keeps a frame pointer: {text}");
4878 assert!(text.contains("movq\t8(%rbp), %rax"), "{text}");
4879 assert!(!text.contains("\tcall"), "a return address is not a call to anything: {text}");
4880
4881 let text = asm("void *back(void) { return __builtin_return_address(2); }\n");
4882 assert_eq!(text.matches("movq\t(%r").count(), 2, "two links are two loads: {text}");
4883 assert!(text.contains("movq\t8(%r"), "and the answer is above the last of them: {text}");
4884 }
4885
4886 #[test]
4897 fn a_depth_that_is_not_a_small_constant_is_refused() {
4898 let mut opts = options();
4899 opts.emit = EmitKind::Ir;
4900 for source in [
4901 "void *up(int n) { return __builtin_return_address(n); }\n",
4902 "void *up(void) { return __builtin_frame_address(1000); }\n",
4903 ] {
4904 let messages = run(&opts, source).messages;
4905 let named = messages.iter().any(|m| m.contains("E0705"));
4906 assert!(named, "expected a refusal in {messages:?}");
4907 }
4908 }
4909
4910 #[test]
4922 fn an_alloca_takes_the_bytes_off_the_stack_pointer_and_answers_where_they_are() {
4923 let text =
4924 asm("void use(void *p); void f(unsigned long n) { use(__builtin_alloca(n)); }\n");
4925 assert!(text.contains("andq\t$-16"), "the size is rounded up to sixteen: {text}");
4926 assert!(text.contains("subq\t%rdi, %rsp"), "and taken off the stack pointer: {text}");
4927 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
4928
4929 let plain = concat!(
4932 "extern void *alloca(__SIZE_TYPE__);\n",
4933 "void use(void *p);\n",
4934 "void f(unsigned long n) { use(alloca(n)); }\n",
4935 );
4936 let text = asm(plain);
4937 assert!(text.contains("subq\t%rdi, %rsp"), "the plain name is the same bytes: {text}");
4938 assert_eq!(text.matches("\tcall").count(), 1, "and is not a call either: {text}");
4939
4940 let own = concat!(
4943 "static void *alloca(unsigned long n) { return 0; }\n",
4944 "void *f(unsigned long n) { return alloca(n); }\n",
4945 );
4946 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
4947 }
4948
4949 #[test]
4964 fn a_builtin_the_program_never_declared_is_the_builtin_rather_than_the_one_c89_wrote_down() {
4965 let mut opts = options();
4969 opts.permissive = true;
4970 let undeclared = "void use(void *p);
4971void f(unsigned long n) { use(alloca(n)); }
4972";
4973 assert_eq!(
4974 run(&opts, undeclared).messages,
4975 [
4976 "/main.c:2:31: warning: implicit declaration of function 'alloca' [E0521]",
4977 "/main.c:2:31: warning: incompatible implicit declaration of built-in function \
4978 'alloca' [E0713]",
4979 ]
4980 );
4981
4982 opts.emit = EmitKind::Asm;
4983 let text = run(&opts, undeclared).text().to_owned();
4984 assert!(text.contains("subq\t%rdi, %rsp"), "the bytes come off the stack: {text}");
4985 assert_eq!(text.matches("\tcall").count(), 1, "the only call is the one written: {text}");
4986
4987 let string = "unsigned long f(void) { return strlen(\"abc\"); }\n";
4990 let text = run(&opts, string).text().to_owned();
4991 assert!(text.contains("call\tstrlen"), "strlen is still a call: {text}");
4992
4993 let own = concat!(
4996 "static void *alloca(unsigned long n) { return 0; }\n",
4997 "void *f(unsigned long n) { return alloca(n); }\n",
4998 );
4999 assert!(asm(own).contains("\tcall"), "a name the program took back is a call");
5000 }
5001
5002 #[test]
5012 fn the_bytes_an_alloca_took_are_still_there_at_the_end_of_the_block_that_took_them() {
5013 let inner = "{ use(__builtin_alloca(n)); }";
5014 for body in [inner.to_owned(), format!("int a[n]; {inner} use(a);")] {
5015 let source = format!("void use(void *p);\nvoid f(unsigned long n) {{ {body} }}\n");
5016 let text = asm(&source);
5017 for line in text.lines().filter(|line| line.trim_end().ends_with(", %rsp")) {
5021 let taking = line.contains("subq");
5022 let leaving = line.contains("%rbp");
5023 assert!(taking || leaving, "nothing puts the stack back: {line} in {text}");
5024 }
5025 }
5026 }
5027
5028 #[test]
5030 fn the_object_and_the_listing_are_two_spellings_of_one_compilation() {
5031 let source = "int callee(void); int g(void) { return callee(); }\n";
5035 let bytes = obj(source);
5036 assert!(
5037 bytes.windows(7).any(|w| w == b"callee\0"),
5038 "the object has to name the callee for the linker to find it"
5039 );
5040 let text = asm(source);
5041 assert!(text.contains("\tcall\tcallee\n"), "{text}");
5042 }
5043
5044 #[test]
5050 fn compiling_for_an_executable_produces_an_object_and_not_a_dump() {
5051 let mut opts = options();
5052 opts.emit = EmitKind::Executable;
5054 let result = run(&opts, "int main(void) { return 0; }\n");
5055 assert_eq!(result.messages, Vec::<String>::new());
5056 match result.artifact {
5057 Artifact::Object { bytes, .. } => assert_eq!(&bytes[..4], b"\x7fELF"),
5058 other => panic!("expected an object, got {other:?}"),
5059 }
5060 }
5061
5062 #[test]
5064 fn a_platform_with_no_object_writer_is_said_so_rather_than_written_as_elf() {
5065 let mut opts = options();
5066 opts.emit = EmitKind::Object;
5067 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
5068 let result = run(&opts, "int f(void) { return 0; }\n");
5069 assert!(result.failed(), "an object nobody can read is worse than a message");
5070 assert!(
5071 result.messages.iter().any(|m| m.contains("no object writer")),
5072 "{:?}",
5073 result.messages
5074 );
5075 }
5076
5077 fn ir(source: &str) -> String {
5079 let mut opts = options();
5080 opts.emit = EmitKind::Ir;
5081 let result = run(&opts, source);
5082 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5083 result.text().to_owned()
5084 }
5085
5086 fn errors(source: &str) -> Vec<String> {
5088 let mut opts = options();
5089 opts.emit = EmitKind::Ir;
5090 let result = run(&opts, source);
5091 assert!(result.failed(), "expected this to be refused:\n{source}");
5092 result.messages
5093 }
5094
5095 fn body(source: &str) -> String {
5097 let text = ir(source);
5098 let (_, rest) = text.split_once("{\n").expect("a function definition");
5099 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
5100 body.to_owned()
5101 }
5102
5103 #[test]
5111 fn gnu89_inline_is_what_decides_whether_a_bare_inline_definition_reaches_the_module() {
5112 let source = "inline int f(int x) { return x + 1; }\n";
5113 let with = |flag: bool| {
5114 let mut opts = options();
5115 opts.emit = EmitKind::Ir;
5116 opts.gnu89_inline = flag;
5117 let result = run(&opts, source);
5118 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5119 result.text().to_owned()
5120 };
5121
5122 assert!(!with(false).contains("block0"), "no body: {}", with(false));
5125
5126 assert!(with(true).contains("block0"), "a body: {}", with(true));
5129 }
5130
5131 #[test]
5138 fn an_access_through_a_type_names_the_type_it_went_through() {
5139 let source = "\
5140struct s { int a; float b; };\n\
5141union u { int i; float f; };\n\
5142int scalar(int *p) { return *p; }\n\
5143float member(struct s *p) { p->a = 1; return p->b; }\n\
5144int element(int *a, long i) { return a[i]; }\n\
5145float through_a_union(union u *p) { p->i = 1; return p->f; }\n";
5146 let text = ir(source);
5147 assert!(text.contains(r#"!0 = tbaa "char""#), "the root: {text}");
5148 assert!(text.contains(r#"tbaa "int", parent !0"#), "int under it: {text}");
5149 assert!(text.contains(r#"tbaa "float", parent !0"#), "float under it: {text}");
5150 let named = text.lines().filter(|line| line.contains(", tbaa !")).count();
5153 assert_eq!(named, 6, "six accesses: {text}");
5154 }
5155
5156 #[test]
5163 fn turning_strict_aliasing_off_leaves_the_type_off_every_access() {
5164 let source = "int punned(float *f, int *i) { *i = 1; *f = 2.0f; return *i; }\n";
5165 let mut opts = options();
5166 opts.emit = EmitKind::Ir;
5167 opts.strict_aliasing = false;
5168 let result = run(&opts, source);
5169 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5170 let text = result.text().to_owned();
5171 assert!(!text.contains("tbaa"), "not even the root: {text}");
5172 }
5173
5174 #[test]
5180 fn instrumenting_functions_calls_the_hooks_around_every_body_but_the_hooks() {
5181 let source = concat!(
5182 "#define NOCHK __attribute__((no_instrument_function))\n",
5183 "void __cyg_profile_func_enter(void *, void *) NOCHK;\n",
5184 "void __cyg_profile_func_exit(void *, void *) NOCHK;\n",
5185 "int calls;\n",
5186 "int pick(int x) { if (x) return 1; return 2; }\n",
5187 "void quiet(void) NOCHK;\n",
5188 "void quiet(void) { calls++; }\n",
5189 "void __cyg_profile_func_enter(void *fn, void *site) { calls++; }\n",
5190 "void __cyg_profile_func_exit(void *fn, void *site) { calls--; }\n",
5191 );
5192 let mut opts = options();
5193 opts.emit = EmitKind::Ir;
5194 opts.instrument_functions = true;
5195 let result = run(&opts, source);
5196 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5197 let text = result.text().to_owned();
5198 let body = |name: &str| -> String {
5199 let open = format!("func @{name}(");
5200 let start = text.find(&open).unwrap_or_else(|| panic!("no {name}: {text}"));
5201 let rest = &text[start..];
5202 rest[..rest.find("\n}").unwrap_or(rest.len())].to_owned()
5203 };
5204 let pick = body("pick");
5205 assert_eq!(pick.matches("call @__cyg_profile_func_enter(").count(), 1, "{pick}");
5206 assert_eq!(pick.matches("call @__cyg_profile_func_exit(").count(), 2, "{pick}");
5207 assert!(pick.contains("return_address"), "{pick}");
5208 assert!(pick.contains("global_addr @pick"), "{pick}");
5209 for quiet in ["quiet", "__cyg_profile_func_enter", "__cyg_profile_func_exit"] {
5210 assert!(!body(quiet).contains("call "), "{quiet} is left alone: {text}");
5211 }
5212
5213 opts.instrument_functions = false;
5214 let result = run(&opts, source);
5215 assert!(!result.text().contains("call @__cyg_profile"), "off unless asked for");
5216 }
5217
5218 #[test]
5224 fn a_call_an_unwind_would_leave_a_cleanup_behind_gets_a_landing_pad_under_exceptions() {
5225 let source = concat!(
5226 "void done(int *p);\n",
5227 "void work(void);\n",
5228 "void calls(void) { int x __attribute__((cleanup(done))) = 1; work(); }\n",
5229 "int quiet(int y) { int x __attribute__((cleanup(done))) = y; return x + 1; }\n",
5230 "void after(void) { { int x __attribute__((cleanup(done))) = 1; } work(); }\n",
5231 );
5232 let mut opts = options();
5233 opts.emit = EmitKind::Ir;
5234 let result = run(&opts, source);
5235 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
5236 assert!(!result.text().contains("landing"), "{}", result.text());
5237
5238 opts.exceptions = true;
5239 let result = run(&opts, source);
5240 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5241 let text = result.text();
5242 assert_eq!(text.matches("= landing").count(), 1, "only the call in calls: {text}");
5243 assert!(text.contains("_Unwind_Resume"), "{text}");
5244
5245 opts.emit = EmitKind::Asm;
5246 let result = run(&opts, source);
5247 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5248 let text = result.text();
5249 assert!(text.contains(".cfi_personality 0x9b,DW.ref.__gcc_personality_v0"), "{text}");
5250 assert!(text.contains(".cfi_lsda 0x1b,.LLSDA_calls"), "{text}");
5251 assert!(text.contains(".gcc_except_table"), "{text}");
5252 assert_eq!(text.matches(".cfi_lsda").count(), 1, "{text}");
5253
5254 opts.emit = EmitKind::Object;
5255 let result = run(&opts, source);
5256 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5257 let bytes = result.artifact.bytes();
5258 let has = |what: &[u8]| bytes.windows(what.len()).any(|window| window == what);
5259 assert!(has(b".gcc_except_table\0"), "the call site table has a section");
5260 assert!(has(b"zPLR\0"), "a header naming the personality routine");
5261 assert!(has(b"zR\0"), "and the plain one for the functions with no pad");
5262 assert!(has(b"DW.ref.__gcc_personality_v0\0"), "the pointer the header reads through");
5263
5264 opts.emit = EmitKind::Ir;
5265 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
5266 let result = run(&opts, source);
5267 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
5268 assert!(result.messages[0].contains("landing pad"), "{:?}", result.messages);
5269 assert!(result.messages[0].contains(":3:"), "the call in calls: {:?}", result.messages);
5270 }
5271
5272 #[test]
5278 fn an_asm_at_file_scope_with_an_instruction_in_it_is_assembled() {
5279 let source = concat!(
5280 "extern void vide(void);\n",
5281 "__asm__(\".text;.globl _us;_us:;movl $0x1234ABCD, %eax;ret\");\n",
5282 "__asm__(\"vide: ret\");\n",
5283 "unsigned short _us(void);\n",
5284 "int main(void) { vide(); return _us() == 0xABCD ? 0 : 1; }\n",
5285 );
5286 let mut opts = options();
5287 opts.emit = EmitKind::Ir;
5288 let result = run(&opts, source);
5289 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5290 assert_eq!(result.text().matches("module asm ").count(), 2, "{}", result.text());
5291
5292 opts.emit = EmitKind::Asm;
5293 let result = run(&opts, source);
5294 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5295 let text = result.text();
5296 assert!(text.contains("#APP\nvide: ret\n#NO_APP\n"), "{text}");
5297 let main = text.find("main:").expect("main");
5298 assert!(text.find("#NO_APP").expect("the markers") < main, "templates first: {text}");
5299
5300 opts.emit = EmitKind::Object;
5301 let result = run(&opts, source);
5302 assert_eq!(result.messages, Vec::<String>::new(), "{:?}", result.messages);
5303 let (bytes, defines) = match result.artifact {
5304 Artifact::Object { bytes, defines } => (bytes, defines),
5305 other => panic!("expected an object, got {other:?}"),
5306 };
5307 assert!(defines.iter().any(|name| name == "_us"), "{defines:?}");
5308 let us = [0xb8, 0xcd, 0xab, 0x34, 0x12, 0xc3];
5310 assert!(bytes.windows(us.len()).any(|window| window == us), "the template's bytes");
5311
5312 opts.target = "x86_64-apple-darwin".parse::<Triple>().unwrap();
5314 opts.emit = EmitKind::Ir;
5315 let result = run(&opts, source);
5316 assert!(!result.messages.is_empty(), "refused on Mach-O");
5317 assert!(result.messages[0].contains("the instruction 'movl'"), "{:?}", result.messages);
5318 }
5319
5320 #[test]
5328 fn a_bare_return_from_a_function_that_promised_a_value_gives_back_a_zero() {
5329 let mut opts = options();
5330 opts.emit = EmitKind::Ir;
5331 opts.std = Std::C89;
5332 let compiled = |source: &str| {
5333 let result = run(&opts, source);
5334 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
5335 result.text().to_owned()
5336 };
5337
5338 let text = compiled("int f(int x) { if (x) return; return 3; }\n");
5339 assert!(text.contains("iconst.i32 0\n return"), "zero goes back: {text}");
5340 assert!(!text.contains("unreachable"), "the branch that reached it is kept: {text}");
5341
5342 let text = compiled("double f(int x) { if (x) return; return 1.0; }\n");
5344 assert!(text.contains("fconst.f64 0x0\n return"), "a float zero goes back: {text}");
5345 }
5346
5347 #[test]
5355 fn a_call_to_a_name_nothing_declared_declares_it_as_c89_said_to() {
5356 let mut opts = options();
5357 opts.emit = EmitKind::Ir;
5358 opts.std = Std::C89;
5359 let compiled = |source: &str| {
5360 let result = run(&opts, source);
5361 assert_eq!(result.messages, Vec::<String>::new(), "C89 has nothing to say about this");
5362 result.text().to_owned()
5363 };
5364
5365 let text = compiled("int f(void) { return g(); }\n");
5367 assert!(text.contains("call @g"), "the call is to the name that was written: {text}");
5368 assert!(text.contains("i32"), "and it gives back an int: {text}");
5369
5370 let text = compiled("int f(char c) { return g(c); }\n");
5373 assert!(text.contains("sext.i32"), "the argument is promoted: {text}");
5374
5375 let mut opts = options();
5378 opts.std = Std::C89;
5379 let said = run(&opts, "int f(void) { return h; }\n").messages.join("\n");
5380 assert!(said.contains("'h' undeclared"), "not a call, so not declared: {said}");
5381 }
5382
5383 #[test]
5393 fn a_name_called_before_it_is_defined_still_gets_its_definition() {
5394 let mut opts = options();
5395 opts.emit = EmitKind::Ir;
5396 opts.std = Std::C89;
5397 let text = run(&opts, "int f(void) { return dummy(); }\ndummy () { return 7; }\n")
5398 .text()
5399 .to_owned();
5400 assert!(text.contains("func @f()"), "the caller is there: {text}");
5401 assert!(text.contains("func @dummy"), "and so is what it calls: {text}");
5402 assert!(text.contains("iconst.i32 7"), "with the body it was given: {text}");
5403 }
5404
5405 #[test]
5413 fn an_old_style_parameter_is_converted_from_what_the_call_promoted_it_to() {
5414 let mut opts = options();
5415 opts.emit = EmitKind::Ir;
5416 opts.std = Std::C89;
5417 let compiled = |source: &str| run(&opts, source).text().to_owned();
5418
5419 let text = compiled("f (c) unsigned char c; { return c; }\n");
5420 assert!(text.contains("func @f(i32"), "an int arrives: {text}");
5421 assert!(text.contains("trunc.i8"), "and is cut down to what was declared: {text}");
5422 assert!(text.contains("zext.i32"), "then read back unsigned: {text}");
5423
5424 let text = compiled("f (s) short s; { return s; }\n");
5426 assert!(text.contains("trunc.i16"), "cut down: {text}");
5427 assert!(text.contains("sext.i32"), "and read back signed: {text}");
5428
5429 let text = compiled("f (x) float x; { return x * 2; }\n");
5432 assert!(text.contains("func @f(f64"), "a double arrives: {text}");
5433 assert!(text.contains("fptrunc.f32"), "and is narrowed to the float: {text}");
5434
5435 let text = compiled("int f(unsigned char c) { return c; }\n");
5438 assert!(text.contains("func @f(i8)"), "the declared type arrives: {text}");
5439 assert!(!text.contains("trunc"), "so there is nothing to cut down: {text}");
5440 }
5441
5442 #[test]
5451 fn the_rules_gcc_promoted_are_decided_by_the_dialect_and_by_fpermissive() {
5452 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
5454 let cases = [
5455 ("static counted;\n", ["", "error", "warning", "error"]),
5456 ("int f(void) { return g(); }\n", ["", "error", "warning", "error"]),
5457 ("int f(x) { return x; }\n", ["", "error", "warning", "error"]),
5458 ("int *p;\nvoid h(void) { p = 1; }\n", ["warning", "error", "warning", "error"]),
5459 (
5460 "char *q;\nint *r;\nvoid k(void) { r = q; }\n",
5461 ["warning", "error", "warning", "error"],
5462 ),
5463 ("int f(void) { return; }\n", ["", "error", "warning", "error"]),
5464 ("void g(void) { return 1; }\n", ["warning", "error", "warning", "error"]),
5465 ];
5466
5467 for (source, wanted) in cases {
5468 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
5469 let mut opts = options();
5470 opts.std = std;
5471 opts.permissive = permissive;
5472 let said = run(&opts, source).messages.join("\n");
5473 let severity = if said.contains(": error: ") {
5474 "error"
5475 } else if said.contains(": warning: ") {
5476 "warning"
5477 } else {
5478 ""
5479 };
5480 let how = if permissive { " -fpermissive" } else { "" };
5481 assert_eq!(
5482 severity,
5483 wanted,
5484 "under -std={}{how}, {source} was answered with `{said}`",
5485 std.as_str()
5486 );
5487 if wanted.is_empty() {
5488 assert!(said.is_empty(), "nothing to say, but said `{said}`");
5489 }
5490 }
5491 }
5492 }
5493
5494 #[test]
5503 fn the_three_variadic_builtins_answer_a_bad_list_the_way_a_call_answers_a_bad_argument() {
5504 let modes = [(Std::C89, false), (Std::C17, false), (Std::C17, true), (Std::C23, false)];
5505 let cases = [
5506 (
5507 "int f(int n, ...) { char *p; return __builtin_va_arg(p, int); }\n",
5508 "first argument to 'va_arg' not of type 'va_list'",
5509 ["error", "error", "error", "error"],
5510 ),
5511 (
5512 "void f(int n, ...) { char *p; __builtin_va_start(p, n); }\n",
5513 "passing argument 1 of '__builtin_va_start' from incompatible pointer type",
5514 ["warning", "error", "warning", "error"],
5515 ),
5516 (
5517 "void f(int n, ...) { int x; __builtin_va_end(x); }\n",
5518 "passing argument 1 of '__builtin_va_end' makes pointer from integer without a \
5519 cast",
5520 ["warning", "error", "warning", "error"],
5521 ),
5522 (
5523 "void f(int n, ...) { __builtin_va_list a; char *p; __builtin_va_copy(a, p); }\n",
5524 "passing argument 2 of '__builtin_va_copy' from incompatible pointer type",
5525 ["warning", "error", "warning", "error"],
5526 ),
5527 ];
5528
5529 for (source, message, wanted) in cases {
5530 for (&(std, permissive), wanted) in modes.iter().zip(wanted) {
5531 let mut opts = options();
5532 opts.std = std;
5533 opts.permissive = permissive;
5534 let said = run(&opts, source).messages.join("\n");
5535 let how = if permissive { " -fpermissive" } else { "" };
5536 assert!(
5537 said.contains(&format!(": {wanted}: {message}")),
5538 "under -std={}{how}, {source} was answered with `{said}`",
5539 std.as_str()
5540 );
5541 }
5542 }
5543 }
5544
5545 fn safe_ir(tier: rucc_session::Safety, source: &str) -> String {
5547 let mut opts = options();
5548 opts.emit = EmitKind::Ir;
5549 opts.safety = tier;
5550 let result = run(&opts, source);
5551 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5552 result.text().to_owned()
5553 }
5554
5555 const READS_THROUGH_A_POINTER: &str = "int read(int *p) { return p[1]; }\n";
5556
5557 fn padded_ir(padding: Padding, source: &str) -> String {
5559 let mut opts = options();
5560 opts.emit = EmitKind::Ir;
5561 opts.safety = rucc_session::Safety::Detect;
5562 opts.padding = padding;
5563 let result = run(&opts, source);
5564 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5565 result.text().to_owned()
5566 }
5567
5568 const FILLS_A_RECORD_A_MEMBER_AT_A_TIME: &str = "struct padded { char tag; int value; };\n\
5569 void fill(struct padded *p) { p->tag = 1; p->value = 2; }\n";
5570
5571 #[test]
5572 fn a_record_filled_a_member_at_a_time_comes_out_whole_when_padding_does_not_participate() {
5573 let text = padded_ir(Padding::Ignored, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
5577 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
5578 }
5579
5580 #[test]
5581 fn a_store_says_only_what_it_wrote_when_padding_does_participate() {
5582 let text = padded_ir(Padding::Tracked, FILLS_A_RECORD_A_MEMBER_AT_A_TIME);
5585 assert!(!text.contains("owns"), "{text}");
5586 }
5587
5588 #[test]
5589 fn a_member_of_a_union_owns_nothing_after_it() {
5590 let text = padded_ir(
5594 Padding::Ignored,
5595 "union u { char tag; long wide; };\nvoid fill(union u *p) { p->tag = 1; }\n",
5596 );
5597 assert!(!text.contains("owns"), "{text}");
5598 }
5599
5600 #[test]
5601 fn an_inner_records_trailing_padding_reaches_the_outer_records() {
5602 let text = padded_ir(
5607 Padding::Ignored,
5608 "struct inner { char c; };\n\
5609 struct outer { struct inner in; int x; };\n\
5610 void fill(struct outer *p) { p->in.c = 1; p->x = 2; }\n",
5611 );
5612 assert_eq!(text.matches("owns 4").count(), 2, "{text}");
5613 }
5614
5615 #[test]
5616 fn a_build_that_did_not_ask_for_the_monitor_is_compiled_the_way_it_always_was() {
5617 let text = ir(READS_THROUGH_A_POINTER);
5621 assert!(!text.contains("check_"), "{text}");
5622 assert!(!text.contains("cap_of"), "{text}");
5623 }
5624
5625 #[test]
5626 fn asking_for_a_tier_puts_the_checks_in_before_the_optimizer_sees_them() {
5627 let text = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5628 assert!(text.contains("cap_of"), "{text}");
5629 assert!(text.contains("check_bounds"), "{text}");
5630 assert!(text.contains("check_live"), "{text}");
5631 assert!(text.contains("check_deriv"), "{text}");
5633 assert!(text.contains("check_type"), "{text}");
5635 }
5636
5637 #[test]
5638 fn the_three_tiers_that_are_not_off_all_check_the_same_accesses_so_far() {
5639 let detect = safe_ir(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5643 for tier in [rucc_session::Safety::Enforce, rucc_session::Safety::Kernel] {
5644 assert_eq!(safe_ir(tier, READS_THROUGH_A_POINTER), detect, "{tier}");
5645 }
5646 }
5647
5648 fn summary(tier: rucc_session::Safety, source: &str) -> String {
5650 let mut opts = options();
5651 opts.emit = EmitKind::SafetySummary;
5652 opts.safety = tier;
5653 let result = run(&opts, source);
5654 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5655 result.text().to_owned()
5656 }
5657
5658 #[test]
5659 fn the_summary_counts_the_checks_that_went_in_and_the_ones_still_standing() {
5660 let text = summary(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5661 assert!(text.contains("\"tier\": \"detect\""), "{text}");
5662 assert!(
5664 text.contains("\"bounds\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"),
5665 "{text}"
5666 );
5667 assert!(
5668 text.contains(
5669 "\"derivation\": { \"emitted\": 1, \"remaining\": 1, \"discharged\": 0 }"
5670 ),
5671 "{text}"
5672 );
5673 }
5674
5675 #[test]
5676 fn a_build_without_the_monitor_summarises_as_a_build_with_no_checks_in_it() {
5677 let text = summary(rucc_session::Safety::Off, READS_THROUGH_A_POINTER);
5681 assert!(text.contains("\"tier\": \"off\""), "{text}");
5682 assert!(
5683 text.contains("\"bounds\": { \"emitted\": 0, \"remaining\": 0, \"discharged\": 0 }"),
5684 "{text}"
5685 );
5686 }
5687
5688 #[test]
5689 fn a_call_the_boundary_models_is_counted_apart_from_one_it_does_not() {
5690 let text = summary(
5691 rucc_session::Safety::Detect,
5692 "void *memcpy(void *, const void *, unsigned long);\n\
5693 int puts(const char *);\n\
5694 void f(char *d, char *s) { memcpy(d, s, 4); puts(d); }\n",
5695 );
5696 assert!(text.contains("\"interposed\": 1"), "{text}");
5697 assert!(text.contains("\"puts\""), "{text}");
5698 assert!(!text.contains("__rucc_wrap_memcpy\""), "{text}");
5702 }
5703
5704 #[test]
5705 fn an_address_taken_of_a_library_function_is_counted_the_way_a_call_to_one_is() {
5706 let text = summary(
5711 rucc_session::Safety::Detect,
5712 "void *memcpy(void *, const void *, unsigned long);\n\
5713 int puts(const char *);\n\
5714 void *table[2] = { (void *)memcpy, (void *)puts };\n\
5715 void *f(int i) { return table[i]; }\n",
5716 );
5717 assert!(text.contains("\"interposed\": 1"), "{text}");
5718 assert!(text.contains("\"puts\""), "{text}");
5719 assert!(!text.contains("\"memcpy\""), "{text}");
5720 }
5721
5722 #[test]
5723 fn the_two_directions_a_pointer_crosses_the_boundary_are_counted_apart() {
5724 let text = summary(
5728 rucc_session::Safety::Detect,
5729 "void *notes_open(void);\n\
5730 char *f(char *p) { char *q = notes_open(); return q ? q : p; }\n",
5731 );
5732 assert!(text.contains("\"crossings\": { \"entered\": 1, \"returned\": 1 }"), "{text}");
5733 assert!(text.contains("\"notes_open\""), "{text}");
5734 }
5735
5736 #[test]
5737 fn a_static_function_nobody_takes_the_address_of_is_not_a_crossing() {
5738 let text = summary(
5741 rucc_session::Safety::Detect,
5742 "static int len(const char *p) { return p ? 1 : 0; }\n\
5743 int f(void) { return len(\"x\"); }\n",
5744 );
5745 assert!(text.contains("\"crossings\": { \"entered\": 0, \"returned\": 0 }"), "{text}");
5746 }
5747
5748 fn granules(source: &str) -> String {
5750 let mut opts = options();
5751 opts.emit = EmitKind::TypeGranules;
5752 let result = run(&opts, source);
5753 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5754 result.text().to_owned()
5755 }
5756
5757 #[test]
5758 fn the_granule_report_names_every_record_and_both_keyings() {
5759 let text = granules(
5760 "struct hot { char *p; int a; int b; };\n\
5761 int f(struct hot *h) { return h->a; }\n",
5762 );
5763 assert!(text.contains("struct hot"), "{text}");
5764 assert!(text.contains("every type distinct"), "{text}");
5767 assert!(text.contains("every pointer one type"), "{text}");
5768 assert!(text.contains("budget"), "{text}");
5769 }
5770
5771 #[test]
5772 fn a_record_nothing_uses_is_still_measured() {
5773 let text = granules("struct unused { long a; double b; };\nint f(void) { return 0; }\n");
5776 assert!(text.contains("struct unused"), "{text}");
5777 }
5778
5779 #[test]
5780 fn the_granule_report_stops_before_anything_is_lowered() {
5781 let text = granules(
5785 "struct wide { long double d; };\n\
5786 long double f(long double x) { return x * x; }\n",
5787 );
5788 assert!(text.contains("struct wide"), "{text}");
5789 }
5790
5791 #[test]
5792 fn a_witness_reaches_the_assembler_as_a_call_to_the_runtime() {
5793 let text = safe_asm(rucc_session::Safety::Detect, "char *f(char *p) { return p; }\n");
5796 assert!(text.contains("\tcall\t__rucc_cap_witness\n"), "{text}");
5797 }
5798
5799 #[test]
5800 fn a_pointer_turned_into_an_integer_is_on_the_trust_set() {
5801 let text = summary(
5802 rucc_session::Safety::Detect,
5803 "unsigned long f(int *p) { return (unsigned long) p; }\n",
5804 );
5805 assert!(text.contains("\"exposed\": 1"), "{text}");
5806 }
5807
5808 fn safe_asm(tier: rucc_session::Safety, source: &str) -> String {
5810 let mut opts = options();
5811 opts.emit = EmitKind::Asm;
5812 opts.safety = tier;
5813 let result = run(&opts, source);
5814 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
5815 result.text().to_owned()
5816 }
5817
5818 #[test]
5819 fn a_check_reaches_the_assembler_as_a_call_to_the_runtime() {
5820 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5821 assert!(text.contains("\tcall\t__rucc_check_bounds\n"), "{text}");
5822 assert!(text.contains("\tcall\t__rucc_check_live\n"), "{text}");
5823 assert!(text.contains("\tcall\t__rucc_check_deriv\n"), "{text}");
5824 assert!(text.contains("\tcall\t__rucc_check_typed_init\n"), "{text}");
5827 }
5828
5829 #[test]
5830 fn every_check_that_reached_the_assembler_has_a_row_describing_it() {
5831 let text = safe_asm(rucc_session::Safety::Detect, READS_THROUGH_A_POINTER);
5837 let section = format!("\t.section\t{},", rucc_safety::SECTION);
5838 assert_eq!(text.matches(§ion).count(), 4, "{text}");
5839 for index in 0..4 {
5840 let name = format!("__rucc_safety_desc_{index}");
5841 assert!(text.contains(&format!("{name}:\n")), "{text}");
5844 assert!(text.contains(&format!("{name}(%rip)")), "{text}");
5845 }
5846 assert!(!text.contains("__rucc_safety_desc_4"), "{text}");
5847 }
5848
5849 #[test]
5857 fn builtin_constant_p_is_folded_where_it_is_written_rather_than_called() {
5858 let text = ir(concat!(
5859 "int g;\n",
5860 "int a = __builtin_constant_p(1);\n",
5861 "int b = __builtin_constant_p(g);\n",
5862 "int c = __builtin_constant_p(\"abc\");\n",
5863 "int d = __builtin_constant_p(&g);\n",
5864 "int e = __builtin_constant_p(1.5);\n",
5865 "int h = __builtin_choose_expr(__builtin_constant_p(3), 11, 22);\n",
5866 ));
5867 assert!(text.contains("global @a : i32 = 1,"), "{text}");
5868 assert!(text.contains("global @b : i32 = 0,"), "{text}");
5869 assert!(text.contains("global @c : i32 = 1,"), "{text}");
5870 assert!(text.contains("global @d : i32 = 0,"), "{text}");
5871 assert!(text.contains("global @e : i32 = 1,"), "{text}");
5872 assert!(text.contains("global @h : i32 = 11,"), "{text}");
5873 assert!(!text.contains("__builtin_constant_p"), "it is not a call to anything:\n{text}");
5874
5875 let text = body("int f(void) { int i = 0; __builtin_constant_p(i++); return i; }\n");
5879 assert_eq!(text, "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 0\n return %0\n");
5880 }
5881
5882 #[test]
5891 fn a_call_to_a_library_builtin_reaches_the_library_function() {
5892 let text = body("void f(void) { __builtin_abort(); }\n");
5893 assert_eq!(text, "block0:\n call @abort() : ()\n return\n");
5894
5895 let text = ir("int f(const char *s) { return __builtin_puts(s) + __builtin_strlen(s); }\n");
5898 assert!(text.contains("call @puts(%0) : (ptr) -> i32"), "{text}");
5899 assert!(text.contains("call @strlen(%0) : (ptr) -> i64"), "{text}");
5900 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
5901 }
5902
5903 #[test]
5916 fn a_chk_builtin_reaches_the_checking_function_and_keeps_the_size() {
5917 let text = ir(concat!(
5918 "char d[8];\n",
5919 "void f(const char *s, unsigned long n) {\n",
5920 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
5921 " __builtin___strcpy_chk(d, s, __builtin_object_size(d, 1));\n",
5922 " __builtin___memset_chk(d, 0, n, 8);\n",
5923 "}\n",
5924 ));
5925 assert!(text.contains("call @__memcpy_chk("), "{text}");
5926 assert!(text.contains("call @__strcpy_chk("), "{text}");
5927 assert!(text.contains("call @__memset_chk("), "{text}");
5928 assert!(text.contains("iconst.i64 8"), "the object size reaches the call: {text}");
5929 assert!(!text.contains("__builtin_"), "the prefix is not part of any name here:\n{text}");
5930 }
5931
5932 #[test]
5940 fn a_checking_call_whose_size_says_nothing_is_known_is_the_plain_library_call() {
5941 let text = ir(concat!(
5942 "extern char *p;\n",
5943 "char d[8];\n",
5944 "void f(const char *s, unsigned long n) {\n",
5945 " __builtin___memcpy_chk(d, s, n, __builtin_object_size(d, 0));\n",
5946 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
5947 " __builtin___strcpy_chk(p, s, __builtin_object_size(p, 0));\n",
5948 " __builtin___stpncpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
5949 " __builtin___sprintf_chk(p, 1, __builtin_object_size(p, 0), s);\n",
5950 "}\n",
5951 ));
5952
5953 assert!(
5955 text.contains("call @__memcpy_chk(%2, %0, %1, %3) : (ptr, ptr, i64, i64)"),
5956 "{text}"
5957 );
5958
5959 assert!(text.contains("call @memcpy(%6, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
5962 assert!(text.contains("call @strcpy(%10, %0) : (ptr, ptr) -> ptr"), "{text}");
5963 assert!(text.contains("call @stpncpy(%14, %0, %1) : (ptr, ptr, i64) -> ptr"), "{text}");
5964
5965 assert!(text.contains("call @__sprintf_chk("), "{text}");
5968
5969 let asm = asm(concat!(
5972 "void f(char *p, const char *s, unsigned long n) {\n",
5973 " __builtin___memcpy_chk(p, s, n, __builtin_object_size(p, 0));\n",
5974 "}\n",
5975 ));
5976 assert!(asm.contains("call\tmemcpy"), "{asm}");
5977 assert!(!asm.contains("$-1"), "the size that went away leaves no instruction:\n{asm}");
5978 }
5979
5980 #[test]
5988 fn the_v_spellings_of_the_chk_family_take_the_list_a_va_list_parameter_holds() {
5989 let text = ir(concat!(
5990 "char d[64];\n",
5991 "int f(const char *fmt, ...) {\n",
5992 " __builtin_va_list ap;\n",
5993 " __builtin_va_start(ap, fmt);\n",
5994 " int n = __builtin___vsprintf_chk(d, 1, __builtin_object_size(d, 0), fmt, ap);\n",
5995 " __builtin_va_end(ap);\n",
5996 " return n;\n",
5997 "}\n",
5998 ));
5999 assert!(text.contains("call @__vsprintf_chk("), "{text}");
6000 assert!(text.contains("iconst.i64 64"), "the object size reaches the call: {text}");
6001 }
6002
6003 #[test]
6014 fn the_absolute_value_family_is_the_magnitude_and_not_a_call() {
6015 let text = body(concat!(
6016 "long long llabs(long long);\n",
6017 "long long f(long long x) { return llabs(x); }\n",
6018 ));
6019 assert!(text.contains("%1 = iconst.i64 63"), "{text}");
6020 assert!(text.contains("%2 = ashr %0, %1"), "{text}");
6021 assert!(text.contains("%3 = xor %0, %2"), "{text}");
6022 assert!(text.contains("%4 = sub %3, %2"), "{text}");
6023 assert!(!text.contains("call"), "the call does not happen:\n{text}");
6024
6025 let text = body("int abs(int);\nint f(int x) { return abs(x); }\n");
6028 assert!(text.contains("iconst.i32 31"), "{text}");
6029 let text = body("long labs(long);\nlong f(long x) { return labs(x); }\n");
6030 assert!(text.contains("iconst.i64 63"), "{text}");
6031
6032 let text = body("long long f(long long x) { return __builtin_llabs(x); }\n");
6035 assert!(!text.contains("call"), "{text}");
6036
6037 let text = ir(concat!(
6039 "long long llabs(long long b);\n",
6040 "long long g(long long x) { return llabs(x); }\n",
6041 "long long llabs(long long b) { return 7; }\n",
6042 ));
6043 assert!(!text.contains("call @llabs"), "{text}");
6044 }
6045
6046 #[test]
6053 fn a_byte_swap_is_arithmetic_and_not_a_call() {
6054 let text = body("unsigned f(unsigned x) { return __builtin_bswap32(x); }\n");
6055 assert_eq!(text, "block0(%0: i32):\n %1 = bswap %0\n return %1\n");
6056
6057 let text = body("unsigned f(unsigned char c) { return __builtin_bswap32(c); }\n");
6060 assert!(text.contains("zext.i32 %0"), "widened first: {text}");
6061 assert!(text.contains("bswap %1"), "and swapped at four bytes: {text}");
6062 }
6063
6064 #[test]
6070 fn the_byte_swaps_reverse_at_the_width_their_name_says() {
6071 for (name, ty, width) in [
6072 ("__builtin_bswap16", "unsigned short", "i16"),
6073 ("__builtin_bswap32", "unsigned", "i32"),
6074 ("__builtin_bswap64", "unsigned long long", "i64"),
6075 ] {
6076 let source = format!("{ty} f({ty} x) {{ return {name}(x); }}\n");
6077 let text = body(&source);
6078 assert_eq!(
6079 text,
6080 format!("block0(%0: {width}):\n %1 = bswap %0\n return %1\n"),
6081 "{name}"
6082 );
6083 }
6084 }
6085
6086 #[test]
6093 fn the_bit_counts_are_instructions_and_not_calls() {
6094 let text = body("int f(unsigned x) { return __builtin_clz(x); }\n");
6095 assert_eq!(text, "block0(%0: i32):\n %1 = ctlz %0\n return %1\n");
6096
6097 let text = body("int f(unsigned x) { return __builtin_ctz(x); }\n");
6098 assert_eq!(text, "block0(%0: i32):\n %1 = cttz %0\n return %1\n");
6099
6100 let text = body("int f(unsigned x) { return __builtin_popcount(x); }\n");
6101 assert_eq!(text, "block0(%0: i32):\n %1 = ctpop %0\n return %1\n");
6102 }
6103
6104 #[test]
6113 fn the_bit_counts_ask_about_the_width_their_name_says() {
6114 let text = body("int f(unsigned long long x) { return __builtin_clzll(x); }\n");
6115 assert!(text.starts_with("block0(%0: i64):"), "counted at eight bytes: {text}");
6116 assert!(text.contains("%1 = ctlz %0"), "{text}");
6117 assert!(text.contains("trunc.i32 %1"), "and answered in an int: {text}");
6118
6119 let text = body("int f(unsigned long long x) { return __builtin_clz(x); }\n");
6122 assert!(text.contains("trunc.i32 %0"), "narrowed to what was asked about: {text}");
6123 assert!(text.contains("ctlz %1"), "and counted there: {text}");
6124
6125 let text = body("int f(unsigned long x) { return __builtin_popcountl(x); }\n");
6126 assert!(text.contains("%1 = ctpop %0"), "{text}");
6127 assert!(!text.contains("call"), "{text}");
6128 }
6129
6130 #[test]
6135 fn a_parity_is_the_low_bit_of_the_set_bit_count() {
6136 let text = body("int f(unsigned x) { return __builtin_parity(x); }\n");
6137 assert!(text.contains("%1 = ctpop %0"), "{text}");
6138 assert!(text.contains("iconst.i32 1"), "{text}");
6139 assert!(text.contains("and %1, %2"), "the low bit of it: {text}");
6140 }
6141
6142 #[test]
6148 fn the_first_set_bit_is_one_based_and_zero_for_a_zero() {
6149 let text = body("int f(int x) { return __builtin_ffs(x); }\n");
6150 assert!(text.contains("%1 = cttz %0"), "{text}");
6151 assert!(text.contains("%4 = add %1, %2"), "one more than the count: {text}");
6152 assert!(text.contains("%5 = icmp ne %0, %3"), "whether there was a bit at all: {text}");
6153 assert!(text.contains("%7 = sub %3, %6"), "spread to a mask: {text}");
6154 assert!(text.contains("%8 = and %4, %7"), "and kept only then: {text}");
6155 assert!(!text.contains("br_if"), "no branch: {text}");
6156 }
6157
6158 #[test]
6168 fn the_redundant_sign_bit_count_is_instructions_and_not_a_call() {
6169 let text = body("int f(int x) { return __builtin_clrsb(x); }\n");
6170 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
6171 assert!(text.contains("%2 = ashr %0, %1"), "the sign over every bit: {text}");
6172 assert!(text.contains("%3 = xor %0, %2"), "folded onto it: {text}");
6173 assert!(text.contains("%5 = shl %3, %4"), "one less than the count: {text}");
6174 assert!(text.contains("%6 = or %5, %4"), "with something to count at zero: {text}");
6175 assert!(text.contains("%7 = ctlz %6"), "{text}");
6176 assert!(!text.contains("call"), "{text}");
6177 assert!(!text.contains("br_if"), "no branch: {text}");
6178 }
6179
6180 #[test]
6186 fn the_unsigned_absolute_value_family_answers_in_the_unsigned_type() {
6187 let text = body("unsigned f(int x) { return __builtin_uabs(x); }\n");
6188 assert!(text.contains("%1 = iconst.i32 31"), "{text}");
6189 assert!(text.contains("%4 = sub %3, %2"), "{text}");
6190 assert!(!text.contains("call"), "nothing declares uabs, so a call would not link: {text}");
6191
6192 let text = body("unsigned long long f(long long x) { return __builtin_ullabs(x); }\n");
6193 assert!(text.contains("iconst.i64 63"), "at the width the name says: {text}");
6194
6195 let text = body("int f(int x) { return __builtin_uabs(x) > 2147483647u; }\n");
6198 assert!(text.contains("icmp ugt"), "compared unsigned: {text}");
6199 }
6200
6201 #[test]
6209 fn the_widest_absolute_value_is_whichever_type_the_target_makes_intmax_t() {
6210 let text = body("long f(long x) { return __builtin_imaxabs(x); }\n");
6211 assert!(text.contains("iconst.i64 63"), "{text}");
6212 assert!(text.contains("%4 = sub %3, %2"), "{text}");
6213 assert!(!text.contains("call"), "{text}");
6214
6215 let text = body("unsigned long f(long x) { return __builtin_umaxabs(x); }\n");
6216 assert!(text.contains("iconst.i64 63"), "{text}");
6217 assert!(!text.contains("call"), "{text}");
6218 }
6219
6220 #[test]
6228 fn an_overflow_predicate_writes_nothing_and_answers_the_bit_the_check_would() {
6229 let text =
6230 body("int f(int a, int b) { return __builtin_add_overflow_p(a, b, (int) 0); }\n");
6231 assert!(text.contains("%2, %3 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
6232 assert!(!text.contains("store"), "nothing is written: {text}");
6233 assert!(!text.contains("call"), "{text}");
6234
6235 let text =
6238 body("int f(int a, int b) { return __builtin_mul_overflow_p(a, b, (long long) 0); }\n");
6239 assert!(text.contains("smul_overflow.(i64, i1)"), "{text}");
6240 assert!(!text.contains("store"), "{text}");
6241
6242 let text = body(concat!(
6245 "int g(void);\n",
6246 "int f(int a, int b) { return __builtin_sub_overflow_p(a, b, g()); }\n",
6247 ));
6248 assert!(!text.contains("call @g"), "the third argument is not evaluated: {text}");
6249 }
6250
6251 #[test]
6261 fn an_overflow_check_is_arithmetic_and_not_a_call() {
6262 let text =
6263 body("int f(int a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
6264 assert!(text.contains("%3, %4 = sadd_overflow.(i32, i1) %0, %1"), "{text}");
6265 assert!(text.contains("store %3 -> %2"), "{text}");
6266 assert!(!text.contains("call"), "{text}");
6267
6268 let text =
6269 body("int f(int a, int b, int *r) { return __builtin_sub_overflow(a, b, r); }\n");
6270 assert!(text.contains("ssub_overflow.(i32, i1) %0, %1"), "{text}");
6271
6272 let text =
6273 body("int f(int a, int b, int *r) { return __builtin_mul_overflow(a, b, r); }\n");
6274 assert!(text.contains("smul_overflow.(i32, i1) %0, %1"), "{text}");
6275
6276 let text = body(
6279 "int f(unsigned a, unsigned b, unsigned *r) { return __builtin_add_overflow(a, b, r); }\n",
6280 );
6281 assert!(text.contains("uadd_overflow.(i32, i1) %0, %1"), "{text}");
6282 }
6283
6284 #[test]
6292 fn an_overflow_check_is_done_at_a_type_that_holds_every_operand() {
6293 let text = body(
6294 "int f(unsigned a, int b, long long *r) { return __builtin_add_overflow(a, b, r); }\n",
6295 );
6296 assert!(text.contains("%3 = zext.i64 %0"), "the unsigned operand keeps its value: {text}");
6297 assert!(text.contains("%4 = sext.i64 %1"), "and so does the signed one: {text}");
6298 assert!(text.contains("sadd_overflow.(i64, i1) %3, %4"), "{text}");
6299
6300 let text = body(
6303 "int f(long long a, long long b, long long *r) { return __builtin_mul_overflow(a, b, r); }\n",
6304 );
6305 assert!(text.contains("smul_overflow.(i64, i1) %0, %1"), "{text}");
6306 assert!(!text.contains("sext."), "{text}");
6307 assert!(!text.contains("zext.i64"), "{text}");
6309 }
6310
6311 #[test]
6319 fn an_overflow_check_writes_the_wrapped_answer_whether_or_not_it_fit() {
6320 let text =
6321 body("int f(int a, int b, char *r) { return __builtin_sub_overflow(a, b, r); }\n");
6322 assert!(text.contains("%3, %4 = ssub_overflow.(i32, i1) %0, %1"), "{text}");
6323 assert!(text.contains("%5 = trunc.i8 %3"), "narrowed to where it goes: {text}");
6324 assert!(text.contains("%6 = sext.i32 %5"), "and back: {text}");
6325 assert!(text.contains("%7 = icmp ne %6, %3"), "which is whether it fit: {text}");
6326 assert!(text.contains("store %5 -> %2"), "the narrowed value is stored either way: {text}");
6327 assert!(text.contains("%8 = or %4, %7"), "and either bit is an overflow: {text}");
6328 }
6329
6330 #[test]
6337 fn a_call_needing_more_than_the_widest_type_still_compiles() {
6338 for name in ["add", "sub", "mul"] {
6339 let source = format!(
6340 "int f(unsigned __int128 a, long long b, __int128 *r) {{\n \
6341 return __builtin_{name}_overflow(a, b, r);\n}}\n"
6342 );
6343 let mut opts = options();
6344 opts.emit = EmitKind::MirFinal;
6345 assert!(!run(&opts, &source).failed(), "{name} was refused or stopped the back end");
6346 }
6347 }
6348
6349 #[test]
6352 fn an_overflow_check_over_something_that_is_not_an_integer_says_so() {
6353 let messages =
6354 errors("int f(double a, int b, int *r) { return __builtin_add_overflow(a, b, r); }\n");
6355 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
6356
6357 let messages =
6358 errors("int f(int a, int b, double *r) { return __builtin_add_overflow(a, b, r); }\n");
6359 assert!(messages.iter().any(|line| line.contains("E0671")), "{messages:?}");
6360 }
6361
6362 #[test]
6373 fn an_ordered_access_is_ordered_in_the_ir() {
6374 let text = body("int f(int *p) { return __atomic_load_n(p, 0); }\n");
6375 assert!(text.contains("atomic_load.i32 %0, align 4, relaxed"), "{text}");
6376
6377 let text = body("long f(long *p) { return __atomic_load_n(p, 2); }\n");
6378 assert!(text.contains("atomic_load.i64 %0, align 8, acquire"), "{text}");
6379
6380 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
6381 assert!(text.contains("atomic_store %1 -> %0, align 4, release"), "{text}");
6382
6383 let text = body("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
6384 assert!(text.contains("atomic_store %1 -> %0, align 4, seq_cst"), "{text}");
6385
6386 let text = body("void f(char *p, int v) { __atomic_store_n(p, v, 0); }\n");
6389 assert!(text.contains("trunc.i8 %1"), "{text}");
6390 assert!(text.contains("atomic_store %2 -> %0, align 1, relaxed"), "{text}");
6391 }
6392
6393 #[test]
6402 fn an_ordered_access_is_the_plain_instruction_on_this_machine() {
6403 let text = asm("int f(int *p) { return __atomic_load_n(p, 5); }\n");
6404 assert!(text.contains("movl\t(%rdi), %eax"), "{text}");
6405 assert!(!text.contains("mfence"), "a load needs no barrier here: {text}");
6406
6407 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 3); }\n");
6408 assert!(text.contains("movl\t%esi, (%rdi)"), "{text}");
6409 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
6410
6411 let text = asm("void f(int *p, int v) { __atomic_store_n(p, v, 5); }\n");
6412 let (before, after) = text.split_once("mfence").expect("a barrier: {text}");
6413 assert!(before.contains("movl\t%esi, (%rdi)"), "the store comes first: {text}");
6414 assert!(!after.contains("movl"), "and nothing else is between them: {text}");
6415 }
6416
6417 #[test]
6427 fn a_barrier_is_one_instruction_at_the_strongest_ordering_and_none_below_it() {
6428 assert!(asm("void f(void) { __atomic_thread_fence(5); }\n").contains("mfence"));
6429 assert!(asm("void f(void) { __sync_synchronize(); }\n").contains("mfence"));
6430
6431 for weaker in ["1", "2", "3", "4"] {
6432 let source = format!("void f(void) {{ __atomic_thread_fence({weaker}); }}\n");
6433 assert!(!asm(&source).contains("mfence"), "{weaker} costs nothing here");
6434 }
6435 }
6436
6437 #[test]
6447 fn the_three_x86_fences_are_the_barrier_the_strongest_ordering_gives() {
6448 for name in ["__builtin_ia32_sfence", "__builtin_ia32_lfence", "__builtin_ia32_mfence"] {
6449 let source = format!("void f(void) {{ {name}(); }}\n");
6450 assert!(asm(&source).contains("mfence"), "{name} is a barrier");
6451 let text = body(&source);
6452 assert!(text.contains("fence seq_cst"), "{name}: {text}");
6453 }
6454
6455 let result = run(&options(), "void f(void) { __builtin_ia32_sfence(1); }\n");
6456 assert_eq!(result.messages.len(), 1, "{:?}", result.messages);
6457 assert!(result.messages[0].contains("too many arguments"), "{:?}", result.messages);
6458 }
6459
6460 #[test]
6466 fn a_compare_and_exchange_is_one_instruction_answering_two_things() {
6467 let text =
6470 body("int f(int *p, int e, int d) { return __sync_val_compare_and_swap(p, e, d); }\n");
6471 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
6472 assert!(text.contains("return %3"), "the value it found: {text}");
6473
6474 let text =
6475 body("int f(int *p, int e, int d) { return __sync_bool_compare_and_swap(p, e, d); }\n");
6476 assert!(text.contains("%3, %4 = cmpxchg.(i32, i1) %0, %1, %2, align 4, seq_cst"), "{text}");
6477 assert!(text.contains("zext.i32 %4"), "whether it happened: {text}");
6478
6479 let text = body(
6482 "int f(int *p, int *e, int d) { return __atomic_compare_exchange_n(p, e, d, 0, 4, 2); }\n",
6483 );
6484 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
6485 assert!(text.contains("%4, %5 = cmpxchg.(i32, i1) %0, %3, %2, align 4, acq_rel"), "{text}");
6486 assert!(text.contains("br_if %5, block2, block1"), "{text}");
6487 assert!(text.contains("store %4 -> %1, align 4"), "{text}");
6488
6489 let text = body(
6492 "int f(int *p, int *e, int *d) { return __atomic_compare_exchange(p, e, d, 0, 5, 5); }\n",
6493 );
6494 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
6495 assert!(text.contains("%4 = load.i32 %2, align 4"), "{text}");
6496 assert!(text.contains("%5, %6 = cmpxchg.(i32, i1) %0, %3, %4, align 4, seq_cst"), "{text}");
6497 }
6498
6499 #[test]
6506 fn a_compare_and_exchange_is_a_locked_instruction_at_the_width_of_the_object() {
6507 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
6508 for (ty, suffix, reg) in widths {
6509 let source = format!(
6510 "int f({ty} *p, {ty} e, {ty} d) {{ return __sync_bool_compare_and_swap(p, e, d); }}\n"
6511 );
6512 let text = asm(&source);
6513 assert!(text.contains("\tlock\n"), "{ty}: {text}");
6514 assert!(text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
6515 assert!(text.contains("sete\t"), "{ty}: {text}");
6516 }
6517 let source =
6518 "int f(long *p, long e, long d) { return __sync_bool_compare_and_swap(p, e, d); }\n";
6519 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
6520
6521 for order in ["0", "2", "3", "4", "5"] {
6525 let call = format!("__atomic_compare_exchange_n(p, e, d, 0, {order}, 0)");
6526 let source = format!("int f(int *p, int *e, int d) {{ return {call}; }}\n");
6527 let text = asm(&source);
6528 assert!(text.contains("cmpxchgl\t"), "{order}: {text}");
6529 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
6530 }
6531 }
6532
6533 #[test]
6545 fn a_read_modify_write_is_one_instruction_and_the_arithmetic_a_name_asks_for() {
6546 let text = body("int f(int *p, int v) { return __atomic_fetch_add(p, v, 5); }\n");
6547 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
6548 assert!(text.contains("return %2"), "the value that was there: {text}");
6549
6550 let text = body("int f(int *p, int v) { return __atomic_add_fetch(p, v, 5); }\n");
6551 assert!(text.contains("%2 = atomic_rmw.i32 add %0, %1, align 4, seq_cst"), "{text}");
6552 assert!(text.contains("%3 = add %2, %1"), "and the value afterwards: {text}");
6553
6554 let text = body("int f(int *p, int v) { return __atomic_sub_fetch(p, v, 5); }\n");
6555 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
6556 assert!(text.contains("%3 = sub %2, %1"), "{text}");
6557
6558 let text = body("int f(int *p, int v) { return __sync_fetch_and_sub(p, v); }\n");
6560 assert!(text.contains("%2 = atomic_rmw.i32 sub %0, %1, align 4, seq_cst"), "{text}");
6561
6562 let text = body("int f(int *p, int v) { return __atomic_exchange_n(p, v, 5); }\n");
6565 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, seq_cst"), "{text}");
6566
6567 let text = body("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
6568 assert!(text.contains("%2 = atomic_rmw.i32 xchg %0, %1, align 4, acquire"), "{text}");
6569
6570 let text = body("void f(int *p) { __sync_lock_release(p); }\n");
6573 assert!(text.contains("release"), "{text}");
6574 assert!(text.contains("%1 = iconst.i32 0"), "{text}");
6575
6576 let text = body("void f(int *p, int guard) { __sync_lock_release(p, guard); }\n");
6580 assert!(text.contains("%2 = iconst.i32 0"), "{text}");
6581 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
6582
6583 let text = body("int f(int *p, int v) { return __atomic_fetch_and(p, v, 5); }\n");
6586 assert!(text.contains("%2 = atomic_rmw.i32 and %0, %1, align 4, seq_cst"), "{text}");
6587
6588 let text = body("int f(int *p, int v) { return __sync_or_and_fetch(p, v); }\n");
6589 assert!(text.contains("%2 = atomic_rmw.i32 or %0, %1, align 4, seq_cst"), "{text}");
6590 assert!(text.contains("%3 = or %2, %1"), "and the value afterwards: {text}");
6591
6592 let text = body("int f(int *p, int v) { return __atomic_nand_fetch(p, v, 5); }\n");
6595 assert!(text.contains("%2 = atomic_rmw.i32 nand %0, %1, align 4, seq_cst"), "{text}");
6596 assert!(text.contains("%3 = and %2, %1"), "{text}");
6597 assert!(text.contains("%4 = iconst.i32 -1"), "{text}");
6598 assert!(text.contains("%5 = xor %3, %4"), "{text}");
6599 }
6600
6601 #[test]
6612 fn a_bitwise_read_modify_write_is_a_loop_around_the_compare_and_exchange() {
6613 let widths = [("char", "b", "%dl"), ("short", "w", "%dx"), ("int", "l", "%edx")];
6614 for (ty, suffix, reg) in widths {
6615 for (name, call, insn) in [
6616 ("and", "__atomic_fetch_and(p, v, 5)", "and"),
6617 ("or", "__sync_fetch_and_or(p, v)", "or"),
6618 ("xor", "__atomic_xor_fetch(p, v, 5)", "xor"),
6619 ] {
6620 let source = format!("{ty} f({ty} *p, {ty} v) {{ return {call}; }}\n");
6621 let text = asm(&source);
6622 assert!(text.contains("\tlock\n"), "{ty} {name}: {text}");
6623 assert!(
6624 text.contains(&format!("cmpxchg{suffix}\t{reg}, (%rdi)")),
6625 "{ty} {name}: {text}"
6626 );
6627 assert!(text.contains(&format!("{insn}{suffix}\t")), "{ty} {name}: {text}");
6628 assert!(!text.contains("\txadd"), "{ty} {name} is not an add: {text}");
6630 assert!(!text.contains("\txchg"), "{ty} {name} is not an exchange: {text}");
6631 }
6632 }
6633 let source = "long f(long *p, long v) { return __atomic_fetch_or(p, v, 5); }\n";
6634 assert!(asm(source).contains("cmpxchgq\t%rdx, (%rdi)"), "{}", asm(source));
6635
6636 let text = asm("int f(int *p, int v) { return __sync_fetch_and_nand(p, v); }\n");
6640 assert!(text.contains("cmpxchgl\t"), "{text}");
6641 assert!(text.contains("andl\t"), "{text}");
6642 assert!(text.contains("notl\t"), "{text}");
6643 }
6644
6645 #[test]
6654 fn an_access_through_a_second_pointer_is_the_same_access_and_one_more() {
6655 let text = body("void f(int *p, int *r) { __atomic_load(p, r, 5); }\n");
6656 assert!(text.contains("%2 = atomic_load.i32 %0, align 4, seq_cst"), "{text}");
6657 assert!(text.contains("store %2 -> %1, align 4"), "and out through the place: {text}");
6658
6659 let text = body("void f(int *p, int *v) { __atomic_store(p, v, 3); }\n");
6660 assert!(text.contains("%2 = load.i32 %1, align 4"), "in through the place: {text}");
6661 assert!(text.contains("atomic_store %2 -> %0, align 4, release"), "{text}");
6662
6663 let text = body("void f(int *p, int *v, int *r) { __atomic_exchange(p, v, r, 5); }\n");
6666 assert!(text.contains("%3 = load.i32 %1, align 4"), "{text}");
6667 assert!(text.contains("%4 = atomic_rmw.i32 xchg %0, %3, align 4, seq_cst"), "{text}");
6668 assert!(text.contains("store %4 -> %2, align 4"), "{text}");
6669 }
6670
6671 #[test]
6682 fn a_flag_is_an_exchange_of_one_byte_and_a_store_of_a_zero_over_the_same_byte() {
6683 for pointer in ["char", "int", "void"] {
6684 let source = format!("int f({pointer} *p) {{ return __atomic_test_and_set(p, 5); }}\n");
6685 let text = body(&source);
6686 assert!(text.contains("%1 = iconst.i8 1"), "{pointer}: {text}");
6687 assert!(
6688 text.contains("%2 = atomic_rmw.i8 xchg %0, %1, align 1, seq_cst"),
6689 "{pointer}: {text}"
6690 );
6691 assert!(text.contains("%4 = icmp ne %2, %3"), "{pointer}: {text}");
6692
6693 let source = format!("void f({pointer} *p) {{ __atomic_clear(p, 3); }}\n");
6694 let text = body(&source);
6695 assert!(text.contains("atomic_store %2 -> %0, align 1, release"), "{pointer}: {text}");
6696 }
6697
6698 let text = asm("int f(int *p) { return __atomic_test_and_set(p, 5); }\n");
6701 assert!(text.contains("xchgb\t%al, (%rdi)"), "{text}");
6702 assert!(text.contains("setne\t"), "{text}");
6703 }
6704
6705 #[test]
6713 fn a_read_modify_write_is_an_exchange_or_a_locked_add_at_the_width_of_the_object() {
6714 let widths = [("char", "b", "%sil"), ("short", "w", "%si"), ("int", "l", "%esi")];
6715 for (ty, suffix, reg) in widths {
6716 let source =
6717 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_fetch_add(p, v, 5); }}\n");
6718 let text = asm(&source);
6719 assert!(text.contains("\tlock\n"), "{ty}: {text}");
6720 assert!(text.contains(&format!("xadd{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
6721
6722 let source =
6723 format!("{ty} f({ty} *p, {ty} v) {{ return __atomic_exchange_n(p, v, 5); }}\n");
6724 let text = asm(&source);
6725 assert!(text.contains(&format!("xchg{suffix}\t{reg}, (%rdi)")), "{ty}: {text}");
6726 assert!(!text.contains("\tlock\n"), "an exchange is locked already: {ty}: {text}");
6727 }
6728 let source = "long f(long *p, long v) { return __atomic_fetch_add(p, v, 5); }\n";
6729 assert!(asm(source).contains("xaddq\t%rsi, (%rdi)"), "{}", asm(source));
6730
6731 let source = "int f(int *p, int v) { return __atomic_fetch_sub(p, v, 5); }\n";
6734 let text = asm(source);
6735 assert!(text.contains("negl\t"), "{text}");
6736 assert!(text.contains("xaddl\t"), "{text}");
6737
6738 for order in ["0", "2", "3", "4", "5"] {
6741 let source =
6742 format!("int f(int *p, int v) {{ return __atomic_fetch_add(p, v, {order}); }}\n");
6743 let text = asm(&source);
6744 assert!(text.contains("xaddl\t"), "{order}: {text}");
6745 assert!(!text.contains("mfence"), "{order} needs no barrier here: {text}");
6746 }
6747
6748 let text = asm("int f(int *p, int v) { return __sync_lock_test_and_set(p, v); }\n");
6752 assert!(text.contains("xchgl\t%esi, (%rdi)"), "{text}");
6753 let text = asm("void f(int *p) { __sync_lock_release(p); }\n");
6760 assert!(text.contains("xorl\t%eax, %eax"), "{text}");
6761 assert!(text.contains("movl\t%eax, (%rdi)"), "{text}");
6762 assert!(!text.contains("mfence"), "a release store needs no barrier here: {text}");
6763 }
6764
6765 #[test]
6777 fn the_lock_free_questions_are_answered_as_constants() {
6778 for size in ["1", "2", "4", "8"] {
6779 let source =
6780 format!("int f(void) {{ return __atomic_always_lock_free({size}, 0); }}\n");
6781 let text = asm(&source);
6782 assert!(text.contains("movb\t$1, %al"), "{size} bytes is lock free: {text}");
6783 assert!(!text.contains("call"), "and is not a call: {text}");
6784 }
6785 for size in ["3", "16", "sizeof(long double)"] {
6786 let source = format!("int f(void) {{ return __atomic_is_lock_free({size}, 0); }}\n");
6787 let text = asm(&source);
6788 assert!(text.contains("movb\t$0, %al"), "{size} bytes is not: {text}");
6789 assert!(!text.contains("call"), "and is not a call either: {text}");
6790 }
6791
6792 let text = asm("int f(int n) { return __atomic_is_lock_free(n, 0); }\n");
6796 assert!(text.contains("movb\t$0, %al"), "a size nobody knows is not lock free: {text}");
6797 let text = asm("int f(int *p) { return __atomic_always_lock_free(8, p); }\n");
6798 assert!(text.contains("movb\t$0, %al"), "eight bytes at four is not: {text}");
6799 let text = asm("int f(long *p) { return __atomic_always_lock_free(8, p); }\n");
6800 assert!(text.contains("movb\t$1, %al"), "and at eight it is: {text}");
6801 }
6802
6803 #[test]
6815 fn a_memory_order_an_operation_cannot_carry_is_read_as_the_strongest() {
6816 let mut opts = options();
6817 opts.emit = EmitKind::Ir;
6818
6819 let acquire_store = run(&opts, "void f(int *p, int v) { __atomic_store_n(p, v, 2); }\n");
6820 assert!(acquire_store.text().contains("seq_cst"), "{:?}", acquire_store.text());
6821 assert!(acquire_store.messages[0].contains("[W0333]"), "{:?}", acquire_store.messages);
6822
6823 let nonsense = run(&opts, "int f(int *p) { return __atomic_load_n(p, 99); }\n");
6824 assert!(nonsense.text().contains("seq_cst"), "{:?}", nonsense.text());
6825 assert!(nonsense.messages[0].contains("[W0333]"), "{:?}", nonsense.messages);
6826
6827 let computed = run(&opts, "int f(int *p, int n) { return __atomic_load_n(p, n); }\n");
6828 assert!(computed.text().contains("seq_cst"), "{:?}", computed.text());
6829 assert_eq!(computed.messages, Vec::<String>::new(), "a computed order is not a mistake");
6830 }
6831
6832 #[test]
6844 fn a_conversion_between_a_float_and_the_widest_unsigned_integer_is_written_without_a_branch() {
6845 let text = asm("double f(unsigned long long x) { return (double)x; }\n");
6846 assert!(text.contains("cvtsi2sdq"), "the signed conversion is what runs: {text}");
6847 assert!(text.contains("shrq"), "with the value halved first: {text}");
6848 assert!(text.contains("addsd"), "and doubled after: {text}");
6849 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
6850
6851 let text = asm("unsigned long long f(double d) { return (unsigned long long)d; }\n");
6852 assert!(text.contains("cvttsd2siq"), "the signed conversion is what runs: {text}");
6853 assert!(text.contains("subsd"), "with half the range taken off first: {text}");
6854 assert!(text.contains("shlq\t$63"), "and the top bit put back: {text}");
6855 assert!(!text.contains("\tj"), "and no branch anywhere: {text}");
6856 }
6857
6858 #[test]
6869 fn a_plain_name_the_program_took_is_the_programs_own_function() {
6870 let taken = concat!(
6871 "static long long llabs(long long b) { return 7; }\n",
6872 "long long f(long long x) { return llabs(x); }\n",
6873 );
6874 assert!(ir(taken).contains("call @llabs"), "a static definition is the program's own");
6875
6876 let retyped = concat!("int llabs(int b);\n", "int f(int x) { return llabs(x); }\n",);
6877 assert!(ir(retyped).contains("call @llabs"), "another type is another function");
6878
6879 let plain = concat!(
6880 "long long llabs(long long b);\n",
6881 "long long f(long long x) { return llabs(x); }\n",
6882 );
6883 let mut opts = options();
6884 opts.emit = EmitKind::Ir;
6885 assert!(!run(&opts, plain).text().contains("call @llabs"), "the library's by default");
6886
6887 opts.builtins = false;
6888 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin");
6889
6890 opts.builtins = true;
6891 opts.no_builtin = vec!["llabs".to_owned()];
6892 assert!(run(&opts, plain).text().contains("call @llabs"), "-fno-builtin-llabs");
6893 let one = "long labs(long b);\nlong f(long x) { return labs(x); }\n";
6894 assert!(!run(&opts, one).text().contains("call @labs"), "one name and not the family");
6895
6896 opts.no_builtin = Vec::new();
6899 opts.builtins = false;
6900 let prefixed = "long long f(long long x) { return __builtin_llabs(x); }\n";
6901 assert!(!run(&opts, prefixed).text().contains("call @llabs"), "the prefix is a promise");
6902 }
6903
6904 #[test]
6917 fn the_hint_builtins_are_their_first_argument_and_the_hint_leaves_no_trace() {
6918 let text = ir(concat!(
6919 "long a = __builtin_expect(7, 1);\n",
6920 "long b = __builtin_expect_with_probability(9, 1, 0.9);\n",
6921 "unsigned long c = sizeof(__builtin_expect((char)1, 1));\n",
6922 ));
6923 assert!(text.contains("global @a : i64 = 7,"), "{text}");
6924 assert!(text.contains("global @b : i64 = 9,"), "{text}");
6925 assert!(text.contains("global @c : i64 = 8,"), "{text}");
6926 assert!(!text.contains("__builtin_expect"), "it is not a call to anything:\n{text}");
6927
6928 let text = body("long f(char c) { return __builtin_expect(c, 1); }\n");
6931 assert!(text.contains("sext"), "{text}");
6932
6933 let one = "block0:\n %0 = iconst.i32 0\n %1 = iconst.i32 1\n %2 = sext.i64 %1\n return %0\n";
6937 assert_eq!(body("int f(void) { int i = 0; __builtin_expect(1, i++); return i; }\n"), one);
6938 let source = "int g(void) { int i = 0; __builtin_expect_with_probability(1, i++, 0.5); return i; }\n";
6939 assert_eq!(body(source), one);
6940
6941 let kept = body("int f(int n) { int i = 0; __builtin_expect(n, i++); return i; }\n");
6946 assert!(kept.contains("add.nsw"), "the hint still runs: {kept}");
6947 assert!(kept.ends_with("return %3\n"), "and the answer is what it left behind: {kept}");
6948 let both = "int g(int n) { int i = 0; __builtin_expect_with_probability(n, i++, 0.5); return i; }\n";
6949 assert!(body(both).contains("add.nsw"), "and so does the one with three arguments");
6950 }
6951
6952 #[test]
6964 fn a_promise_that_control_does_not_arrive_writes_no_instruction() {
6965 let promised = "int f(int x) { if (x) return 1; __builtin_unreachable(); }\n";
6966 let text = ir(promised);
6967 assert!(text.contains(" unreachable_hint\n"), "{text}");
6968 assert!(!text.contains("call"), "it is not a call to anything:\n{text}");
6969
6970 let after = body("int g(int x) { __builtin_unreachable(); return x; }\n");
6974 assert!(after.contains("return"), "{after}");
6975
6976 let text = asm(promised);
6979 let mine = text.split_once("\nf:\n").expect("a definition").1;
6980 let mine = mine.split_once("\t.size").expect("a definition").0;
6981 let plain = asm("int f(int x) { if (x) return 1; }\n");
6982 let plain = plain.split_once("\nf:\n").expect("a definition").1;
6983 let plain = plain.split_once("\t.size").expect("a definition").0;
6984 assert_eq!(mine, plain);
6985 let last = mine.lines().rfind(|line| !line.trim_start().starts_with('.'));
6988 assert_eq!(last.map(str::trim), Some("ret"), "{mine}");
6989 assert!(!mine.contains("ud2"), "{mine}");
6990 }
6991
6992 #[test]
6999 fn a_library_builtin_is_diagnosed_under_the_name_the_program_wrote() {
7000 let mut opts = options();
7001 opts.emit = EmitKind::Ir;
7002 let messages = run(&opts, "void f(void) { __builtin_abort(1); }\n").messages;
7003 assert!(
7004 messages.iter().any(|m| m.contains("__builtin_abort")),
7005 "expected the written name in {messages:?}"
7006 );
7007 }
7008
7009 #[test]
7017 fn a_builtin_nothing_lowers_is_refused_by_name() {
7018 let mut opts = options();
7019 opts.emit = EmitKind::Ir;
7020 let builtin = "__atomic_signal_fence";
7021 let source = format!("int counter;\nint f(void) {{ return ({builtin}(5), 0); }}\n");
7022 let messages = run(&opts, &source).messages;
7023 let named = messages.iter().any(|m| m.contains(builtin) && m.contains("E0686"));
7024 assert!(named, "expected {builtin} to be refused by name in {messages:?}");
7025 }
7026
7027 #[test]
7036 fn what_is_refused_is_the_call_and_not_the_name() {
7037 let text = ir(concat!(
7038 "void __atomic_signal_fence(int order) { (void)order; }\n",
7039 "void f(void) { __atomic_signal_fence(5); }\n",
7040 ));
7041 assert!(text.contains("call @__atomic_signal_fence"), "{text}");
7042 }
7043
7044 #[test]
7053 fn the_object_size_of_an_address_is_what_the_layout_leaves_in_front_of_it() {
7054 let text = ir(concat!(
7055 "struct S { char a[8]; int n; char b[12]; };\n",
7056 "char g[32];\n",
7057 "struct S gs;\n",
7058 "unsigned long whole = __builtin_object_size(g, 0);\n",
7059 "unsigned long moved = __builtin_object_size(g + 4, 0);\n",
7060 "unsigned long back = __builtin_object_size(g + 30 - 2, 0);\n",
7061 "unsigned long outer = __builtin_object_size(gs.a, 0);\n",
7062 "unsigned long inner = __builtin_object_size(gs.a, 1);\n",
7063 "unsigned long scalar = __builtin_object_size(&gs.n, 1);\n",
7064 "unsigned long after = __builtin_object_size(&gs.n, 0);\n",
7065 "unsigned long into = __builtin_object_size(&gs.b[2], 1);\n",
7066 "unsigned long text = __builtin_object_size(\"hello\", 0);\n",
7067 "unsigned long dyn = __builtin_dynamic_object_size(gs.b, 1);\n",
7068 ));
7069 for (name, size) in [
7070 ("whole", 32),
7071 ("moved", 28),
7072 ("back", 4),
7073 ("outer", 24),
7074 ("inner", 8),
7075 ("scalar", 4),
7076 ("after", 16),
7077 ("into", 10),
7078 ("text", 6),
7079 ("dyn", 12),
7080 ] {
7081 let said = format!("global @{name} : i64 = {size},");
7082 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
7083 }
7084 }
7085
7086 #[test]
7094 fn the_object_behind_an_address_can_be_one_with_automatic_storage() {
7095 let text = body(concat!(
7096 "struct S { char a[8]; int n; char b[12]; };\n",
7097 "unsigned long f(void) {\n",
7098 " char loc[20];\n",
7099 " struct S ls;\n",
7100 " return __builtin_object_size(loc + 3, 0) + __builtin_object_size(ls.b + 2, 1);\n",
7101 "}\n",
7102 ));
7103 assert!(text.contains("iconst.i64 17"), "twenty bytes with three used: {text}");
7104 assert!(text.contains("iconst.i64 10"), "twelve bytes with two used: {text}");
7105 }
7106
7107 #[test]
7117 fn an_address_with_no_object_in_sight_answers_at_the_end_of_the_range_its_kind_asks_for() {
7118 let text = ir(concat!(
7119 "struct T { int n; char f[]; };\n",
7120 "extern char *p;\n",
7121 "extern struct T *t;\n",
7122 "unsigned long largest = __builtin_object_size(p, 0);\n",
7123 "unsigned long nearest = __builtin_object_size(p, 1);\n",
7124 "unsigned long least = __builtin_object_size(p, 2);\n",
7125 "unsigned long tight = __builtin_object_size(p, 3);\n",
7126 "unsigned long flex = __builtin_object_size(t->f, 1);\n",
7127 "int says = __builtin_object_size(p, 0) == (unsigned long)-1;\n",
7128 ));
7129 for name in ["largest", "nearest", "flex"] {
7130 let said = format!("global @{name} : i64 = -1,");
7134 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
7135 }
7136 for name in ["least", "tight"] {
7137 let said = format!("global @{name} : i64 = 0,");
7138 assert!(text.contains(&said), "expected `{said}` in:\n{text}");
7139 }
7140 assert!(text.contains("global @says : i32 = 1,"), "{text}");
7141 }
7142
7143 #[test]
7150 fn the_address_an_object_size_is_asked_about_is_not_evaluated() {
7151 let text = body(concat!(
7152 "extern char *side(void);\n",
7153 "unsigned long f(void) { return __builtin_object_size(side(), 0); }\n",
7154 ));
7155 assert!(!text.contains("call"), "nothing is called: {text}");
7156 }
7157
7158 #[test]
7163 fn a_kind_that_is_not_one_of_the_four_is_refused() {
7164 for source in [
7165 "extern char *p;\nextern int k;\nunsigned long f(void) ".to_owned()
7166 + "{ return __builtin_object_size(p, k); }\n",
7167 "extern char *p;\nunsigned long f(void) { return __builtin_object_size(p, 4); }\n"
7168 .to_owned(),
7169 "extern char *p;\nunsigned long f(void) ".to_owned()
7170 + "{ return __builtin_dynamic_object_size(p, -1); }\n",
7171 ] {
7172 let messages = errors(&source);
7173 let named = messages.iter().any(|m| m.contains("E0709") && m.contains("0 to 3"));
7174 assert!(named, "expected a complaint about the kind in {messages:?}");
7175 }
7176 }
7177
7178 #[test]
7184 fn the_pair_that_saves_a_place_lowers_to_the_two_markers() {
7185 let text = ir(concat!(
7186 "void *buf[5];\n",
7187 "int f(void) {\n",
7188 " if (__builtin_setjmp(buf)) return 2;\n",
7189 " return 1;\n",
7190 "}\n",
7191 "void g(void) { __builtin_longjmp(buf, 1); }\n",
7192 ));
7193 assert!(text.contains("= setjmp_marker.i32 %0\n"), "the save answers a value: {text}");
7194 assert!(text.contains(" longjmp_marker %0\n"), "the restore answers nothing: {text}");
7195 assert!(!text.contains("call @"), "neither of them is a call: {text}");
7196 }
7197
7198 #[test]
7206 fn a_local_of_a_function_that_saves_a_place_gets_a_slot() {
7207 let text = ir(concat!(
7208 "void *buf[5];\n",
7209 "int f(int x) { int a = 0; if (__builtin_setjmp(buf)) return a; a = 1; return x; }\n",
7210 "int g(int x) { int a = 0; if (x) return a; a = 1; return x; }\n",
7211 ));
7212 let (saves, plain) = text.split_once("func @g").expect("both functions");
7213 assert_eq!(saves.matches("= alloca").count(), 2, "the parameter and the local: {text}");
7214 assert!(saves.contains("store %9 -> %2"), "the local is written through: {text}");
7215 assert!(!plain.contains("alloca"), "nothing in the plain one needs a slot: {text}");
7216 }
7217
7218 #[test]
7227 fn a_value_live_across_sigsetjmp_keeps_its_spill_slot() {
7228 each_spill_slot_written_once(&across("int __sigsetjmp(sigjmp_buf, int);\n", "__sigsetjmp"));
7229 }
7230
7231 #[test]
7234 fn a_value_live_across_a_returns_twice_call_keeps_its_spill_slot() {
7235 let declared = "int save_here(sigjmp_buf, int) __attribute__((__returns_twice__));\n";
7236 each_spill_slot_written_once(&across(declared, "save_here"));
7237 }
7238
7239 #[test]
7247 fn a_value_live_across_setjmp_shares_its_slot_with_nothing_in_the_first_arm() {
7248 let source = concat!(
7249 "typedef long jmp_buf[25];\n",
7250 "int _setjmp(jmp_buf);\n",
7251 "void longjmp(jmp_buf, int) __attribute__((noreturn));\n",
7252 "int printf(const char *, ...);\n",
7253 "static jmp_buf *stack;\n",
7254 "static volatile long long sink;\n",
7255 "static int cells[64];\n",
7256 "static volatile int seed_in = 3;\n",
7257 "static void work(void) { longjmp(*stack, 1); }\n",
7258 "int main(void) {\n",
7259 " int seed = seed_in;\n",
7260 " int v = seed * 2;\n",
7261 " jmp_buf buf;\n",
7262 " if (_setjmp(buf) == 0) {\n",
7263 " stack = &buf;\n",
7264 " int *p = &cells[seed + 3];\n",
7265 " int a = v + 8;\n",
7266 " int b = seed * 2005;\n",
7267 " int *q = &cells[v + 1];\n",
7268 " work();\n",
7269 " sink = *p + a + b + *q;\n",
7270 " } else {\n",
7271 " printf(\"%d\\n\", v);\n",
7272 " }\n",
7273 " return 0;\n",
7274 "}\n",
7275 );
7276 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
7277 let mut opts = options();
7278 opts.emit = EmitKind::Asm;
7279 opts.opt_level = level;
7280 let result = run(&opts, source);
7281 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
7282 let text = result.text();
7283 let body = text.split_once("\nmain:\n").expect("the function").1;
7284 let lines: Vec<&str> = body.lines().map(str::trim).collect();
7285 let save = lines.iter().position(|l| *l == "call\t_setjmp").expect("the save");
7286 let jump = lines[save..]
7287 .iter()
7288 .position(|l| *l == "call\twork" || *l == "call\tlongjmp")
7289 .map(|at| save + at)
7290 .unwrap_or_else(|| panic!("the call that jumps back at {level:?}:\n{text}"));
7291 let printf = lines.iter().position(|l| *l == "call\tprintf").expect("the handler");
7292 let slot = lines[jump..printf]
7294 .iter()
7295 .rev()
7296 .find_map(|l| l.strip_suffix(", %rsi").or_else(|| l.strip_suffix(", %esi")))
7297 .and_then(|l| l.split_once('\t'))
7298 .map(|(_, place)| place)
7299 .filter(|place| place.ends_with("(%rsp)") || place.ends_with("(%rbp)"))
7300 .unwrap_or_else(|| panic!("the handler reads v from a slot at {level:?}:\n{text}"));
7301 let writes = |l: &&str| {
7302 !l.starts_with("cmp") && !l.starts_with("test") && l.ends_with(&format!(", {slot}"))
7303 };
7304 assert!(
7305 lines[..save].iter().any(writes),
7306 "{slot} is written before the save at {level:?}:\n{text}"
7307 );
7308 assert!(
7309 !lines[save..jump].iter().any(writes),
7310 "{slot} is written again before the jump at {level:?}:\n{text}"
7311 );
7312 }
7313 }
7314
7315 fn across(declared: &str, save: &str) -> String {
7318 asm(&format!(
7319 "typedef long sigjmp_buf[25];\n{declared}int id(int);\nvoid thrower(int);\n\
7320 int work(int n) {{\n\
7321 \x20 int v0 = id(n), v1 = id(n + 1), v2 = id(n + 2), v3 = id(n + 3), v4 = id(n + 4);\n\
7322 \x20 sigjmp_buf b;\n\
7323 \x20 if ({save}(b, 0) == 0) {{\n\
7324 \x20 int w0 = id(v0 + v1), w1 = id(v1 + v2), w2 = id(v2 + v3);\n\
7325 \x20 int w3 = id(v3 + v4), w4 = id(v4 + v0);\n\
7326 \x20 thrower(n);\n\
7327 \x20 return w0 ^ w1 ^ w2 ^ w3 ^ w4;\n\
7328 \x20 }}\n\
7329 \x20 return v0 + v1 + v2 + v3 + v4;\n\
7330 }}\n"
7331 ))
7332 }
7333
7334 fn each_spill_slot_written_once(text: &str) {
7336 let mut stored = Vec::new();
7337 for line in text.lines().map(str::trim) {
7338 let Some(operands) = line.strip_prefix("movq\t%") else { continue };
7339 if let Some((_, place)) = operands.split_once(", ") {
7340 if place.ends_with("(%rsp)") {
7341 assert!(!stored.contains(&place), "{place} is written twice:\n{text}");
7342 stored.push(place);
7343 }
7344 }
7345 }
7346 assert!(!stored.is_empty(), "something should have been spilled:\n{text}");
7347 }
7348
7349 #[test]
7358 fn the_save_writes_four_words_and_carries_on_in_a_new_block() {
7359 let text =
7360 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
7361 let body = text.split_once("\nf:\n").expect("the function").1;
7362 assert!(body.contains("\tmovq\t%rsp, %rbp\n"), "a frame pointer whatever: {text}");
7363 assert!(body.contains("\tsubq\t$8, %rsp\n"), "no red zone: {text}");
7364 assert!(body.contains("\tmovq\t%rbp, (%rax)\n"), "the frame pointer: {text}");
7365 assert!(body.contains("\tmovq\t%rsp, 16(%rax)\n"), "the stack pointer: {text}");
7366 assert!(body.contains("\tleaq\t.Lf_1(%rip), %rcx\n"), "where to come back to: {text}");
7367 assert!(body.contains("\tmovq\t%rcx, 8(%rax)\n"), "and that goes in the buffer: {text}");
7368 let back = body.split_once(".Lf_1:\n").expect("the block control comes back to").1;
7369 assert!(back.starts_with("\tmovq\t(%rsp), %rax\n"), "the answer is read back: {text}");
7370 }
7371
7372 #[test]
7380 fn a_save_destroys_every_register_the_allocator_hands_out() {
7381 let text =
7382 asm(concat!("void *buf[5];\n", "int f(void) { return __builtin_setjmp(buf); }\n",));
7383 for reg in ["%rbx", "%r12", "%r13", "%r14", "%r15"] {
7384 assert!(text.contains(&format!("\tpushq\t{reg}\n")), "{reg} is saved: {text}");
7385 assert!(text.contains(&format!("\tpopq\t{reg}\n")), "{reg} is restored: {text}");
7386 }
7387 }
7388
7389 #[test]
7396 fn the_restore_puts_the_frame_back_before_it_jumps() {
7397 for level in [rucc_session::OptLevel::O0, rucc_session::OptLevel::O2] {
7398 let mut opts = options();
7399 opts.emit = EmitKind::Asm;
7400 opts.opt_level = level;
7401 let source = "void *buf[5];\nvoid g(void) { __builtin_longjmp(buf, 1); }\n";
7402 let result = run(&opts, source);
7403 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile");
7404 let text = result.text().to_owned();
7405 let jump = text.find("\tjmp\t*%").unwrap_or_else(|| panic!("an indirect jump: {text}"));
7406 let stack = text.find(", %rsp\n").unwrap_or_else(|| panic!("the stack back: {text}"));
7407 let frame = text.find(", %rbp\n").unwrap_or_else(|| panic!("the frame back: {text}"));
7408 assert!(stack < jump, "the stack goes back first at {level:?}: {text}");
7409 assert!(frame < jump, "and so does the frame at {level:?}: {text}");
7410 }
7411 }
7412
7413 #[test]
7419 fn a_longjmp_whose_second_argument_is_not_one_is_turned_down() {
7420 for source in [
7421 "void *buf[5];\nvoid f(void) { __builtin_longjmp(buf, 0); }\n",
7422 "void *buf[5];\nextern int v;\nvoid f(void) { __builtin_longjmp(buf, v); }\n",
7423 ] {
7424 let messages = errors(source);
7425 let named = messages.iter().any(|m| m.contains("E0710"));
7426 assert!(named, "expected a complaint about the value in {messages:?}");
7427 }
7428 }
7429
7430 #[test]
7435 fn a_static_function_nothing_refers_to_is_not_emitted() {
7436 let text = ir("static int dropped(void) { return 1; }\n\
7437 static int kept(void) { return 2; }\n\
7438 int main(void) { return kept(); }\n");
7439 assert!(text.contains("func @kept"), "{text}");
7440 assert!(!text.contains("dropped"), "{text}");
7441 }
7442
7443 #[test]
7449 fn two_static_functions_that_only_call_each_other_are_both_dropped() {
7450 let text = ir("static int ping(void);\n\
7451 static int pong(void) { return ping(); }\n\
7452 static int ping(void) { return pong(); }\n\
7453 int main(void) { return 0; }\n");
7454 assert!(!text.contains("ping"), "{text}");
7455 assert!(!text.contains("pong"), "{text}");
7456 }
7457
7458 #[test]
7464 fn naming_a_static_function_anywhere_keeps_it() {
7465 let text = ir("static int by_address(void) { return 1; }\n\
7466 static int in_an_image(void) { return 2; }\n\
7467 static int deeper(void) { return 3; }\n\
7468 static int reaches_deeper(void) { return deeper(); }\n\
7469 static int (*table[1])(void) = {in_an_image};\n\
7470 int main(void) {\n\
7471 int (*p)(void) = by_address;\n\
7472 return p() + table[0]() + reaches_deeper();\n\
7473 }\n");
7474 for kept in ["by_address", "in_an_image", "deeper", "reaches_deeper"] {
7475 assert!(text.contains(&format!("func @{kept}")), "expected {kept} in:\n{text}");
7476 }
7477 }
7478
7479 #[test]
7485 fn an_attribute_keeps_a_static_function_nothing_refers_to() {
7486 for attribute in ["used", "retain", "constructor", "destructor", "__used__"] {
7487 let source = format!(
7488 "__attribute__(({attribute})) static int kept(void) {{ return 1; }}\n\
7489 int main(void) {{ return 0; }}\n"
7490 );
7491 let text = ir(&source);
7492 assert!(text.contains("func @kept"), "for {attribute}:\n{text}");
7493 }
7494 }
7495
7496 #[test]
7499 fn a_function_anything_could_call_is_emitted_without_being_called() {
7500 let text =
7501 ir("int nobody_here_calls_it(void) { return 1; }\nint main(void) { return 0; }\n");
7502 assert!(text.contains("func @nobody_here_calls_it"), "{text}");
7503 }
7504
7505 #[test]
7512 fn a_classification_c_has_an_operator_for_is_that_operator() {
7513 for (builtin, operator) in [
7514 ("__builtin_isgreater", "binary >"),
7515 ("__builtin_isgreaterequal", "binary >="),
7516 ("__builtin_isless", "binary <"),
7517 ("__builtin_islessequal", "binary <="),
7518 ] {
7519 let source = format!("int f(double x, double y) {{ return {builtin}(x, y); }}\n");
7520 let text = tast(&source);
7521 assert!(text.contains(&format!("{operator} : int")), "for {builtin}:\n{text}");
7522 }
7523 }
7524
7525 #[test]
7534 fn the_classification_builtins_are_comparisons_and_not_calls() {
7535 let text = body("int f(double x, double y) { return __builtin_isunordered(x, y); }\n");
7536 assert_eq!(
7537 text,
7538 "block0(%0: f64, %1: f64):\n %2 = fcmp uno %0, %1\n %3 = zext.i32 \
7539 %2\n return %3\n"
7540 );
7541
7542 let text = body("int f(double x, double y) { return __builtin_islessgreater(x, y); }\n");
7544 assert!(text.contains("fcmp one %0, %1"), "{text}");
7545
7546 let text = body("int f(double x) { return __builtin_isnan(x); }\n");
7547 assert!(text.contains("fcmp uno %0, %0"), "{text}");
7548
7549 let text = body("int f(double x) { return __builtin_isinf(x); }\n");
7550 assert!(text.contains("fconst.f64 0x7ff0000000000000"), "{text}");
7551 assert!(text.contains("fconst.f64 0xfff0000000000000"), "{text}");
7552 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
7553 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
7554 assert!(text.contains("%5 = or %3, %4"), "{text}");
7555
7556 let text = body("int f(double x) { return __builtin_isfinite(x); }\n");
7559 assert!(text.contains("%3 = fcmp olt %2, %0"), "{text}");
7560 assert!(text.contains("%4 = fcmp olt %0, %1"), "{text}");
7561 assert!(text.contains("%5 = and %3, %4"), "{text}");
7562
7563 let text = body("int f(double x) { return __builtin_signbit(x); }\n");
7564 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
7565 assert!(text.contains("icmp slt %1, %2"), "{text}");
7566
7567 let text = body("int f(long double x) { return __builtin_signbitl(x); }\n");
7571 assert!(text.contains("load.i16"), "{text}");
7572 assert!(text.contains("icmp slt"), "{text}");
7573 assert!(!text.contains("i80"), "{text}");
7574
7575 let text = body("double g(void);\nint f(void) { return __builtin_isnan(g()); }\n");
7578 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
7579 }
7580
7581 #[test]
7588 fn a_classification_spelling_that_names_a_width_converts_before_it_asks() {
7589 let text = ir(concat!(
7590 "int a = __builtin_isinff(1e300);\n",
7591 "int b = __builtin_isinf(1e300);\n",
7592 "int c = __builtin_isnan(0.0);\n",
7596 "int d = __builtin_signbit(-0.0);\n",
7597 "int e = __builtin_islessgreater(1.0, 2.0);\n",
7598 ));
7599 assert!(text.contains("global @a : i32 = 1,"), "{text}");
7600 assert!(text.contains("global @b : i32 = 0,"), "{text}");
7601 assert!(text.contains("global @c : i32 = 0,"), "{text}");
7602 assert!(text.contains("global @d : i32 = 1,"), "{text}");
7603 assert!(text.contains("global @e : i32 = 1,"), "{text}");
7604 }
7605
7606 #[test]
7608 fn a_classification_builtin_refuses_an_argument_that_is_not_floating_point() {
7609 let mut opts = options();
7610 opts.emit = EmitKind::Ir;
7611 let source = concat!(
7612 "int a(int x) { return __builtin_isnan(x); }\n",
7613 "int b(int x, int y) { return __builtin_isunordered(x, y); }\n",
7614 "int c(double x) { return __builtin_isnan(x, x); }\n",
7615 );
7616 let messages = run(&opts, source).messages;
7617 assert_eq!(
7618 messages,
7619 [
7620 "/main.c:1:23: error: non-floating-point argument in call to function \
7621 '__builtin_isnan' [E0685]",
7622 "/main.c:2:30: error: non-floating-point arguments in call to function \
7623 '__builtin_isunordered' [E0685]",
7624 "/main.c:3:26: error: too many arguments to function '__builtin_isnan' [E0511]",
7625 ]
7626 );
7627 }
7628
7629 #[test]
7638 fn the_last_three_classification_builtins_are_comparisons_and_not_calls() {
7639 let text = body("int f(double x) { return __builtin_isnormal(x); }\n");
7640 assert!(text.contains("%1 = bitcast.i64 %0"), "{text}");
7644 assert!(text.contains("%2 = iconst.i64 9223372036854775807"), "{text}");
7645 assert!(text.contains("%3 = and %1, %2"), "{text}");
7646 assert!(text.contains("%4 = iconst.i64 4503599627370496"), "{text}");
7647 assert!(text.contains("%5 = iconst.i64 9218868437227405312"), "{text}");
7648 assert!(text.contains("%6 = icmp uge %3, %4"), "{text}");
7649 assert!(text.contains("%7 = icmp ult %3, %5"), "{text}");
7650 assert!(text.contains("%8 = and %6, %7"), "{text}");
7651
7652 let text = body("int f(long double x) { return __builtin_isnormal(x); }\n");
7657 assert!(text.contains("fconst.f80 0x18000000000000000"), "{text}");
7658 assert!(text.contains("fconst.f80 0x7fff8000000000000000"), "{text}");
7659 assert!(text.contains("fcmp oge"), "{text}");
7660 assert!(text.contains("fcmp olt"), "{text}");
7661
7662 let text = body("int f(double x) { return __builtin_isinf_sign(x); }\n");
7663 assert!(text.contains("%3 = fcmp oeq %0, %1"), "{text}");
7664 assert!(text.contains("%4 = fcmp oeq %0, %2"), "{text}");
7665 assert!(text.contains("%7 = sub %5, %6"), "{text}");
7666
7667 let text = body("int f(double x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n");
7668 assert!(text.contains("fcmp uno %0, %0"), "{text}");
7669 assert!(text.contains("fcmp oeq %0, %6"), "{text}");
7670 assert_eq!(text.matches(" = zext.i32 ").count(), 4, "{text}");
7674 assert_eq!(text.matches(" = xor ").count(), 4, "{text}");
7675 assert!(!text.contains("call"), "{text}");
7676
7677 let text = body(concat!(
7680 "double g(void);\n",
7681 "int f(void) { return __builtin_fpclassify(0, 1, 2, 3, 4, g()); }\n",
7682 ));
7683 assert_eq!(text.matches("call @g()").count(), 1, "{text}");
7684 }
7685
7686 #[test]
7693 fn the_last_three_classification_builtins_fold_where_their_operand_is_a_constant() {
7694 let text = ir(concat!(
7695 "int a = __builtin_isnormal(1.0);\n",
7696 "int b = __builtin_isnormal(0.0);\n",
7697 "int c = __builtin_isnormal(1.0 / 0.0);\n",
7698 "int d = __builtin_isinf_sign(-1.0 / 0.0);\n",
7699 "int e = __builtin_isinf_sign(1.0);\n",
7700 "int g = __builtin_fpclassify(0, 1, 2, 3, 4, 0.0);\n",
7701 "int h = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0);\n",
7702 "int i = __builtin_fpclassify(0, 1, 2, 3, 4, 1.0 / 0.0);\n",
7703 ));
7704 assert!(text.contains("global @a : i32 = 1,"), "{text}");
7705 assert!(text.contains("global @b : i32 = 0,"), "{text}");
7706 assert!(text.contains("global @c : i32 = 0,"), "{text}");
7707 assert!(text.contains("global @d : i32 = -1,"), "{text}");
7708 assert!(text.contains("global @e : i32 = 0,"), "{text}");
7709 assert!(text.contains("global @g : i32 = 4,"), "{text}");
7710 assert!(text.contains("global @h : i32 = 2,"), "{text}");
7711 assert!(text.contains("global @i : i32 = 1,"), "{text}");
7712 }
7713
7714 #[test]
7720 fn fpclassify_refuses_an_answer_that_is_not_an_integer_constant() {
7721 let mut opts = options();
7722 opts.emit = EmitKind::Ir;
7723 let source = concat!(
7724 "int a(double x, int n) { return __builtin_fpclassify(0, 1, n, 3, 4, x); }\n",
7725 "int b(double x) { return __builtin_fpclassify(0, 1, 2, 3, x); }\n",
7726 "int c(int x) { return __builtin_fpclassify(0, 1, 2, 3, 4, x); }\n",
7727 );
7728 let messages = run(&opts, source).messages;
7729 assert_eq!(
7730 messages,
7731 [
7732 "/main.c:1:60: error: non-const integer argument 3 in call to function \
7733 '__builtin_fpclassify' [E0687]",
7734 "/main.c:2:26: error: too few arguments to function '__builtin_fpclassify' \
7735 [E0511]",
7736 "/main.c:3:23: error: non-floating-point argument in call to function \
7737 '__builtin_fpclassify' [E0685]",
7738 ]
7739 );
7740 }
7741
7742 #[test]
7750 fn a_builtin_whose_answer_is_a_constant_is_one_and_not_a_call() {
7751 let text = ir(concat!(
7752 "double a = __builtin_inf();\n",
7753 "float b = __builtin_huge_valf();\n",
7754 "long double c = __builtin_infl();\n",
7755 "double d = __builtin_huge_val();\n",
7756 ));
7757 assert!(text.contains("global @a : f64 = 0x7ff0000000000000,"), "{text}");
7758 assert!(text.contains("global @b : f32 = 0x7f800000,"), "{text}");
7759 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
7760 assert!(text.contains("global @d : f64 = 0x7ff0000000000000,"), "{text}");
7761 assert!(!text.contains("call"), "{text}");
7762 }
7763
7764 #[test]
7773 fn a_nan_is_written_with_the_payload_the_program_asked_for() {
7774 let text = ir(concat!(
7775 "double a = __builtin_nan(\"\");\n",
7776 "double b = __builtin_nan(\"0x1\");\n",
7777 "double c = __builtin_nan(\"010\");\n",
7779 "double d = __builtin_nans(\"\");\n",
7780 "double e = __builtin_nans(\"0x1\");\n",
7781 "float f = __builtin_nanf(\"0x1\");\n",
7782 "float g = __builtin_nansf(\"\");\n",
7783 "long double h = __builtin_nansl(\"\");\n",
7784 ));
7785 assert!(text.contains("global @a : f64 = 0x7ff8000000000000,"), "{text}");
7786 assert!(text.contains("global @b : f64 = 0x7ff8000000000001,"), "{text}");
7787 assert!(text.contains("global @c : f64 = 0x7ff8000000000008,"), "{text}");
7788 assert!(text.contains("global @d : f64 = 0x7ff4000000000000,"), "{text}");
7789 assert!(text.contains("global @e : f64 = 0x7ff0000000000001,"), "{text}");
7790 assert!(text.contains("global @f : f32 = 0x7fc00001,"), "{text}");
7791 assert!(text.contains("global @g : f32 = 0x7fa00000,"), "{text}");
7792 assert!(text.contains("f80 0x7fffa000000000000000"), "{text}");
7793
7794 let text = ir(concat!(
7797 "double f(const char *p) { return __builtin_nan(p); }\n",
7798 "double g(void) { return __builtin_nans(\"1x\"); }\n",
7799 ));
7800 assert_eq!(text.matches("call @nan(").count(), 1, "{text}");
7801 assert_eq!(text.matches("call @nans(").count(), 1, "{text}");
7802 }
7803
7804 #[test]
7812 fn the_length_and_the_order_of_a_string_literal_are_known_here() {
7813 let text = ir(concat!(
7814 "unsigned long a = __builtin_strlen(\"hello\");\n",
7815 "unsigned long b = __builtin_strlen(\"a\\0bc\");\n",
7816 "int c = __builtin_strcmp(\"X\", \"X\\376\") < 0;\n",
7817 "int d = __builtin_strcmp(\"abc\", \"abc\");\n",
7818 "int e = __builtin_strcmp(\"abc\", \"ab\") > 0;\n",
7819 ));
7820 assert!(text.contains("global @a : i64 = 5,"), "{text}");
7821 assert!(text.contains("global @b : i64 = 1,"), "{text}");
7822 assert!(text.contains("global @c : i32 = 1,"), "{text}");
7823 assert!(text.contains("global @d : i32 = 0,"), "{text}");
7824 assert!(text.contains("global @e : i32 = 1,"), "{text}");
7825 assert!(!text.contains("call"), "{text}");
7826
7827 let text = ir("unsigned long f(const char *p) { return __builtin_strlen(p); }\n");
7829 assert!(text.contains("call @strlen("), "{text}");
7830 }
7831
7832 #[test]
7839 fn a_sign_builtin_is_a_mask_over_the_bits_and_not_a_call() {
7840 let text = body("double f(double x) { return __builtin_fabs(x); }\n");
7841 assert!(text.contains("bitcast.i64 %0"), "{text}");
7842 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
7843 assert!(text.contains("and %1, %2"), "{text}");
7844 assert!(text.contains("bitcast.f64 %3"), "{text}");
7845 assert!(!text.contains("call"), "{text}");
7846
7847 let text = body("double f(double x, double y) { return __builtin_copysign(x, y); }\n");
7848 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
7849 assert!(text.contains("%8 = or %4, %7"), "{text}");
7850 assert!(!text.contains("call"), "{text}");
7851
7852 let text = body("long double f(long double x) { return __builtin_fabsl(x); }\n");
7855 assert!(text.contains("iconst.i16 32767"), "{text}");
7856 assert!(text.contains("load.f80"), "{text}");
7857 assert!(!text.contains("call"), "{text}");
7858
7859 let text = body("double f(float x) { return __builtin_fabs(x); }\n");
7862 assert!(text.contains("fpext.f64 %0"), "{text}");
7863 assert!(text.contains("bitcast.i64 %1"), "{text}");
7864 }
7865
7866 #[test]
7873 fn a_shuffle_picks_each_lane_by_the_low_bits_of_the_mask() {
7874 let text = body(concat!(
7875 "typedef int v2 __attribute__((vector_size(8)));\n",
7876 "void f(v2 *v, v2 m) { *v = __builtin_shuffle(*v, m); }\n",
7877 ));
7878 assert!(text.contains("memcpy"), "{text}");
7879 assert_eq!(text.matches("iconst.i32 1\n").count(), 2, "{text}");
7880 assert_eq!(text.matches(" = and ").count(), 2, "{text}");
7881 assert!(!text.contains("call"), "{text}");
7882
7883 let text = body(concat!(
7886 "typedef char v4 __attribute__((vector_size(4)));\n",
7887 "v4 f(v4 a, v4 b, v4 m) { return __builtin_shuffle(a, b, m); }\n",
7888 ));
7889 assert_eq!(text.matches("iconst.i32 7\n").count(), 4, "{text}");
7890 assert!(text.contains("zext.i32"), "{text}");
7891 assert!(!text.contains("call"), "{text}");
7892 }
7893
7894 #[test]
7898 fn the_arguments_a_function_was_called_with_are_saved_on_the_way_in() {
7899 let text =
7900 mir("void *f(int a, double b) { (void)a; (void)b; return __builtin_apply_args(); }\n");
7901 assert!(text.matches("x64.mov_mr_64").count() >= 7, "{text}");
7903 assert!(text.matches("x64.movaps_mr").count() >= 8, "{text}");
7904 for reg in ["$rdi", "$rsi", "$rdx", "$rcx", "$r8", "$r9", "$xmm0", "$xmm7"] {
7905 assert!(text.contains(reg), "{reg} is not saved in\n{text}");
7906 }
7907
7908 let text = mir("int f(int a) { return a; }\n");
7910 assert!(!text.contains("movaps_mr"), "{text}");
7911 }
7912
7913 #[test]
7917 fn a_call_built_from_saved_arguments_loads_every_argument_register() {
7918 let text = mir(concat!(
7919 "void *g(void *args, void (*h)()) {\n",
7920 " return __builtin_apply(h, args, 64);\n",
7921 "}\n",
7922 ));
7923 assert!(text.matches("x64.mov_rm_64").count() >= 7, "{text}");
7924 assert!(text.matches("x64.movaps_rm").count() >= 8, "{text}");
7925 assert!(text.contains("call"), "{text}");
7926 assert!(text.matches("x64.movaps_mr").count() >= 2, "{text}");
7928
7929 let mut opts = options();
7931 opts.emit = EmitKind::Ir;
7932 let result = run(
7933 &opts,
7934 "void *g(void *a, void (*h)(), int n) { return __builtin_apply(h, a, n); }\n",
7935 );
7936 assert!(result.failed(), "{:?}", result.messages);
7937 assert!(
7938 result
7939 .messages
7940 .iter()
7941 .any(|m| m.contains("the size given to '__builtin_apply' is a constant")),
7942 "{:?}",
7943 result.messages
7944 );
7945 }
7946
7947 #[test]
7949 fn a_shuffle_refuses_what_gcc_refuses() {
7950 let mut opts = options();
7951 opts.emit = EmitKind::Ir;
7952 let source = concat!(
7953 "typedef int v4 __attribute__((vector_size(16)));\n",
7954 "typedef float f4 __attribute__((vector_size(16)));\n",
7955 "typedef short s8 __attribute__((vector_size(16)));\n",
7956 "typedef long long l4 __attribute__((vector_size(32)));\n",
7957 "void a(v4 x, f4 m) { __builtin_shuffle(x, m); }\n",
7958 "void b(int x, v4 m) { __builtin_shuffle(x, m); }\n",
7959 "void c(v4 x, f4 y, v4 m) { __builtin_shuffle(x, y, m); }\n",
7960 "void d(v4 x, s8 m) { __builtin_shuffle(x, m); }\n",
7961 "void e(f4 x, l4 m) { __builtin_shuffle(x, m); }\n",
7962 "void g(v4 x) { __builtin_shuffle(x); }\n",
7963 );
7964 let messages = run(&opts, source).messages;
7965 let wanted = [
7966 "last argument must be an integer vector [E0715]",
7967 "arguments must be vectors [E0715]",
7968 "argument vectors must be of the same type [E0715]",
7969 "number of elements of the argument vector(s) and the mask vector should be the same \
7970 [E0715]",
7971 "argument vector(s) inner type must have the same size as inner type of the mask \
7972 [E0715]",
7973 "too few arguments to function '__builtin_shuffle' [E0511]",
7974 ];
7975 assert_eq!(messages.len(), wanted.len(), "{messages:?}");
7976 for (message, wanted) in messages.iter().zip(wanted) {
7977 assert!(message.ends_with(wanted), "{message}");
7978 }
7979 }
7980
7981 #[test]
7990 fn the_plain_math_names_are_the_same_mask_and_not_a_call() {
7991 let text =
7992 body(concat!("double fabs(double x);\n", "double f(double x) { return fabs(x); }\n",));
7993 assert!(text.contains("iconst.i64 9223372036854775807"), "{text}");
7994 assert!(!text.contains("call"), "{text}");
7995
7996 let text =
7997 body(concat!("float fabsf(float x);\n", "float f(float x) { return fabsf(x); }\n",));
7998 assert!(text.contains("bitcast.i32 %0"), "{text}");
7999 assert!(!text.contains("call"), "{text}");
8000
8001 let text = body(concat!(
8002 "double copysign(double x, double y);\n",
8003 "double f(double x, double y) { return copysign(x, y); }\n",
8004 ));
8005 assert!(text.contains("iconst.i64 -9223372036854775808"), "{text}");
8006 assert!(!text.contains("call"), "{text}");
8007
8008 let text = body(concat!(
8009 "float copysignf(float x, float y);\n",
8010 "float f(float x, float y) { return copysignf(x, y); }\n",
8011 ));
8012 assert!(!text.contains("call"), "{text}");
8013
8014 let text = ir(concat!(
8018 "long double fabsl(long double x);\n",
8019 "long double f(long double x) { return fabsl(x); }\n",
8020 ));
8021 assert!(text.contains("call @fabsl"), "{text}");
8022 }
8023
8024 #[test]
8032 fn a_plain_math_name_the_program_took_is_the_programs_own_function() {
8033 let taken = concat!(
8034 "static double fabs(double b) { return 7; }\n",
8035 "double f(double x) { return fabs(x); }\n",
8036 );
8037 assert!(ir(taken).contains("call @fabs"), "a static definition is the program's own");
8038
8039 let retyped = concat!("int fabs(int b);\n", "int f(int x) { return fabs(x); }\n");
8040 assert!(ir(retyped).contains("call @fabs"), "another type is another function");
8041
8042 let plain = concat!("double fabs(double b);\n", "double f(double x) { return fabs(x); }\n");
8043 let mut opts = options();
8044 opts.emit = EmitKind::Ir;
8045 assert!(!run(&opts, plain).text().contains("call @fabs"), "the library's by default");
8046
8047 opts.builtins = false;
8048 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin");
8049
8050 opts.builtins = true;
8051 opts.no_builtin = vec!["fabs".to_owned()];
8052 assert!(run(&opts, plain).text().contains("call @fabs"), "-fno-builtin-fabs");
8053 let one = concat!(
8054 "double copysign(double a, double b);\n",
8055 "double f(double x) { return copysign(x, 1.0); }\n",
8056 );
8057 assert!(!run(&opts, one).text().contains("call @copysign"), "one name and not the family");
8058
8059 opts.no_builtin = Vec::new();
8061 opts.builtins = false;
8062 let prefixed = "double f(double x) { return __builtin_fabs(x); }\n";
8063 assert!(!run(&opts, prefixed).text().contains("call @fabs"), "the prefix is not a library");
8064 }
8065
8066 #[test]
8075 fn the_sign_builtins_answer_a_zero_and_a_nan_the_way_the_bits_say() {
8076 let text = ir(concat!(
8077 "double a = __builtin_fabs(-3.5);\n",
8078 "double b = __builtin_copysign(1.0, -0.0);\n",
8079 "double c = __builtin_copysign(0.0, -2.0);\n",
8080 "double d = __builtin_copysign(-__builtin_nan(\"\"), 1.0);\n",
8082 "double e = __builtin_fabs(-__builtin_nan(\"0x1\"));\n",
8083 "float g = __builtin_copysignf(-0.0f, 2.0f);\n",
8084 "long double h = __builtin_copysignl(1.0L, -1.0L);\n",
8085 "long double i = __builtin_fabsl(-__builtin_infl());\n",
8086 ));
8087 assert!(text.contains("global @a : f64 = 0x400c000000000000,"), "{text}");
8088 assert!(text.contains("global @b : f64 = 0xbff0000000000000,"), "{text}");
8089 assert!(text.contains("global @c : f64 = 0x8000000000000000,"), "{text}");
8090 assert!(text.contains("global @d : f64 = 0x7ff8000000000000,"), "{text}");
8091 assert!(text.contains("global @e : f64 = 0x7ff8000000000001,"), "{text}");
8092 assert!(text.contains("global @g : f32 = 0x0,"), "{text}");
8093 assert!(text.contains("f80 0xbfff8000000000000000"), "{text}");
8094 assert!(text.contains("f80 0x7fff8000000000000000"), "{text}");
8095 }
8096
8097 #[test]
8104 fn the_sign_of_a_long_double_is_in_the_word_at_the_top_of_it() {
8105 for source in [
8106 "int f(long double x) { return __builtin_signbit(x); }\n",
8107 "long double f(long double x) { return __builtin_fabsl(x); }\n",
8108 "long double f(long double x, long double y) { return __builtin_copysignl(x, y); }\n",
8109 "int f(long double x) { return __builtin_isnormal(x); }\n",
8110 ] {
8111 let text = body(source);
8112 assert!(!text.contains("i80"), "{text}");
8113 assert!(text.contains("i16"), "{text}");
8114 }
8115 }
8116
8117 #[test]
8125 fn the_complex_builtins_are_the_halves_of_the_value_and_not_a_call() {
8126 let text = body("double f(_Complex double z) { return __builtin_creal(z); }\n");
8127 assert!(!text.contains("call"), "{text}");
8128 let text = body("double f(_Complex double z) { return __builtin_cimag(z); }\n");
8129 assert!(!text.contains("call"), "{text}");
8130
8131 let text = body("_Complex double f(_Complex double z) { return __builtin_conj(z); }\n");
8134 assert_eq!(text.matches("fneg").count(), 1, "{text}");
8135 assert!(!text.contains("call"), "{text}");
8136 let negated = body("_Complex double f(_Complex double z) { return -z; }\n");
8137 assert_eq!(negated.matches("fneg").count(), 2, "{negated}");
8138
8139 let written = body("_Complex double f(_Complex double z) { return ~z; }\n");
8142 assert_eq!(written, text, "the name and the operator are the same thing");
8143
8144 let text = body(concat!(
8146 "double creal(_Complex double z);\n",
8147 "double f(_Complex double z) { return creal(z); }\n",
8148 ));
8149 assert!(!text.contains("call"), "{text}");
8150 let text = body(concat!(
8151 "_Complex float conjf(_Complex float z);\n",
8152 "_Complex float f(_Complex float z) { return conjf(z); }\n",
8153 ));
8154 assert_eq!(text.matches("fneg").count(), 1, "{text}");
8155 assert!(!text.contains("call"), "{text}");
8156
8157 let taken = concat!(
8160 "static double creal(_Complex double z) { return 7; }\n",
8161 "double f(_Complex double z) { return creal(z); }\n",
8162 );
8163 assert!(ir(taken).contains("call @creal"), "a static definition is the program's own");
8164 let retyped = concat!("int cimag(int z);\n", "int f(int z) { return cimag(z); }\n");
8165 assert!(ir(retyped).contains("call @cimag"), "another type is another function");
8166 let plain = concat!(
8167 "double cimag(_Complex double z);\n",
8168 "double f(_Complex double z) { return cimag(z); }\n",
8169 );
8170 let mut opts = options();
8171 opts.emit = EmitKind::Ir;
8172 opts.builtins = false;
8173 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin");
8174 opts.builtins = true;
8175 opts.no_builtin = vec!["cimag".to_owned()];
8176 assert!(run(&opts, plain).text().contains("call @cimag"), "-fno-builtin-cimag");
8177
8178 let text = ir(concat!(
8180 "double a = __builtin_creal(1.5 + 2.5i);\n",
8181 "double b = __builtin_cimag(1.5 + 2.5i);\n",
8182 "_Complex double c = __builtin_conj(1.5 + 2.5i);\n",
8183 ));
8184 assert!(text.contains("global @a : f64 = 0x3ff8000000000000,"), "{text}");
8185 assert!(text.contains("global @b : f64 = 0x4004000000000000,"), "{text}");
8186 assert!(
8187 text.contains("{ f64 0x3ff8000000000000, f64 0xc004000000000000 }"),
8188 "the conjugate of a constant is the constant with the second half negated: {text}"
8189 );
8190 assert!(!text.contains("call"), "{text}");
8191 }
8192
8193 #[test]
8201 fn a_math_library_builtin_of_a_constant_is_the_answer_and_not_a_call() {
8202 let text = ir(concat!(
8203 "double a = __builtin_ceil(1.5);\n",
8204 "double b = __builtin_floor(1.5);\n",
8205 "double c = __builtin_trunc(-1.5);\n",
8206 "double d = __builtin_round(2.5);\n",
8209 "double e = __builtin_ceil(-0.5);\n",
8211 "double f = __builtin_fmax(1.0, 2.0);\n",
8212 "double g = __builtin_fmin(1.0, 2.0);\n",
8213 "float h = __builtin_ceilf(1.25f);\n",
8214 "double ceil(double x);\n",
8217 "double i = ceil(2.25);\n",
8218 ));
8219 assert!(text.contains("global @a : f64 = 0x4000000000000000,"), "{text}");
8220 assert!(text.contains("global @b : f64 = 0x3ff0000000000000,"), "{text}");
8221 assert!(text.contains("global @c : f64 = 0xbff0000000000000,"), "{text}");
8222 assert!(text.contains("global @d : f64 = 0x4008000000000000,"), "{text}");
8223 assert!(text.contains("global @e : f64 = 0x8000000000000000,"), "{text}");
8224 assert!(text.contains("global @f : f64 = 0x4000000000000000,"), "{text}");
8225 assert!(text.contains("global @g : f64 = 0x3ff0000000000000,"), "{text}");
8226 assert!(text.contains("global @h : f32 = 0x40000000,"), "{text}");
8227 assert!(text.contains("global @i : f64 = 0x4008000000000000,"), "{text}");
8228 assert!(!text.contains("call"), "{text}");
8229 }
8230
8231 #[test]
8239 fn a_math_library_builtin_of_anything_else_is_a_call_to_the_library() {
8240 let text = ir(concat!(
8241 "double f(double x) { return __builtin_ceil(x); }\n",
8242 "float g(float x) { return __builtin_floorf(x); }\n",
8243 "double h(double x, double y) { return __builtin_fmax(x, y); }\n",
8244 ));
8245 assert!(text.contains("call @ceil("), "{text}");
8246 assert!(text.contains("call @floorf("), "{text}");
8247 assert!(text.contains("call @fmax("), "{text}");
8248
8249 let text = ir(concat!(
8253 "double f(void) { return __builtin_rint(2.5); }\n",
8254 "double g(void) { return __builtin_nearbyint(2.5); }\n",
8255 ));
8256 assert!(text.contains("call @rint("), "{text}");
8257 assert!(text.contains("call @nearbyint("), "{text}");
8258
8259 let text = ir("double f(void) { return __builtin_fmin(__builtin_nan(\"\"), 1.0); }\n");
8262 assert!(text.contains("call @fmin("), "{text}");
8263
8264 let plain = concat!("double ceil(double x);\n", "double f(void) { return ceil(2.25); }\n");
8267 let mut opts = options();
8268 opts.emit = EmitKind::Ir;
8269 opts.no_builtin = vec!["ceil".to_owned()];
8270 assert!(run(&opts, plain).text().contains("call @ceil("), "-fno-builtin-ceil");
8271 }
8272
8273 #[test]
8280 fn a_constexpr_object_is_a_constant_wherever_one_is_required() {
8281 let text = ir(concat!(
8282 "constexpr int side = 4;\n",
8283 "constexpr int wider = side + 1;\n",
8284 "constexpr double half = 1.5;\n",
8285 "struct point { int x; int y; };\n",
8286 "constexpr struct point origin = { 5, 6 };\n",
8287 "int square[side * side];\n",
8288 "int rectangle[wider];\n",
8289 "int rounded[(int)half * 2];\n",
8290 "int across[origin.y];\n",
8291 "enum named { four = side };\n",
8292 "int e = four;\n",
8293 ));
8294 assert!(text.contains("global @square : bytes 64 ="), "{text}");
8295 assert!(text.contains("global @rectangle : bytes 20 ="), "{text}");
8296 assert!(text.contains("global @rounded : bytes 8 ="), "{text}");
8297 assert!(text.contains("global @across : bytes 24 ="), "{text}");
8298 assert!(text.contains("global @e : i32 = 4,"), "{text}");
8299
8300 let mut opts = options();
8303 opts.emit = EmitKind::Ir;
8304 let konst = "const int n = 1;\nint a[n];\n";
8305 let message = "/main.c:2:5: error: variably modified 'a' at file scope [E0538]";
8306 assert_eq!(run(&opts, konst).messages, [message]);
8307
8308 let subscript = "constexpr int t[3] = { 1, 2, 3 };\nint a[t[1]];\n";
8310 assert_eq!(run(&opts, subscript).messages, [message]);
8311
8312 let address = "constexpr int c = 3;\nint *p = &c;\n";
8314 let warning = "/main.c:2:6: warning: initialization discards 'const' qualifier from \
8315 pointer target type [E0514]";
8316 assert_eq!(run(&opts, address).messages, [warning]);
8317 }
8318
8319 #[test]
8328 fn a_member_whose_size_was_refused_is_not_a_flexible_array_member() {
8329 let mut opts = options();
8330 opts.emit = EmitKind::Ir;
8331
8332 let alone = "int k;\nextern struct D { int a[k]; } ed;\n";
8333 let message = "/main.c:2:23: error: variably modified 'a' at file scope [E0538]";
8334 assert_eq!(run(&opts, alone).messages, [message]);
8335
8336 let first = "int k;\nextern struct E { int a[k]; int b; } ee;\n";
8338 assert_eq!(run(&opts, first).messages, [message]);
8339
8340 let negative = "struct F { int a[-1]; };\n";
8343 let refused = "/main.c:1:18: error: size of array 'a' is negative [E0536]";
8344 assert_eq!(run(&opts, negative).messages, [refused]);
8345
8346 let flexible = "struct G { int a[]; };\n";
8349 let named = "/main.c:1:16: error: flexible array member in a struct with no named \
8350 members [E0554]";
8351 assert_eq!(run(&opts, flexible).messages, [named]);
8352 }
8353
8354 #[test]
8369 fn a_pointer_to_an_array_gains_a_qualifier_the_same_way_a_pointer_to_anything_else_does() {
8370 let mut opts = options();
8371 opts.emit = EmitKind::Ir;
8372 let prefix = "typedef unsigned int B[4];\nstruct H { B category[2]; };\n";
8373
8374 let adding = format!("{prefix}const B *f(struct H *h) {{ return &h->category[0]; }}\n");
8376 assert_eq!(run(&opts, &adding).messages, [] as [String; 0]);
8377
8378 let plain = concat!(
8381 "const unsigned int (*f(unsigned int (*p)[4]))[4] { return p; }\n",
8382 "const unsigned int (*g(unsigned int (*p)[2][3]))[2][3] { return p; }\n",
8383 );
8384 assert_eq!(run(&opts, plain).messages, [] as [String; 0]);
8385
8386 let dropping = format!("{prefix}B *f(const B *p) {{ return p; }}\n");
8389 let warning = "/main.c:3:27: warning: return discards 'const' qualifier from pointer target type \
8390 [E0514]";
8391 assert_eq!(run(&opts, &dropping).messages, [warning]);
8392
8393 let wrong = "const unsigned int (*f(unsigned short (*p)[4]))[4] { return p; }\n";
8396 let error = "/main.c:1:61: error: returning 'unsigned short (*)[4]' from a function with \
8397 incompatible return type 'const unsigned int (*)[4]' [E0512]";
8398 assert_eq!(run(&opts, wrong).messages, [error]);
8399 }
8400
8401 #[test]
8410 fn an_old_style_definition_takes_its_types_from_the_declarations_under_its_list() {
8411 let mut opts = options();
8414 opts.std = Std::C17;
8415 let source = concat!(
8416 "int add(a, b)\n",
8417 "int a;\n",
8418 "int b;\n",
8419 "{ return a + b; }\n",
8420 "int promoted(c)\n",
8421 "char c;\n",
8422 "{ return c; }\n",
8423 "int narrow(char);\n",
8424 "int narrow(c)\n",
8425 "char c;\n",
8426 "{ return c; }\n",
8427 "int first(a)\n",
8428 "int a[4];\n",
8429 "{ return a[0]; }\n",
8430 );
8431 let result = run(&opts, source);
8432 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
8433 let text = result.text();
8434 assert!(text.contains("add : int(int, int) function external defined"), "{text}");
8435 assert!(text.contains("promoted : int(int) function external defined"), "{text}");
8436 assert!(text.contains("c : char object automatic defined"), "{text}");
8438 assert!(text.contains("narrow : int(char) function external defined"), "{text}");
8439 assert!(text.contains("first : int(int *) function external defined"), "{text}");
8441 }
8442
8443 #[test]
8450 fn the_two_halves_of_an_old_style_parameter_list_have_to_agree() {
8451 let mut opts = options();
8452 opts.std = Std::C17;
8453 for (source, message) in [
8454 ("int f(a, a)\nint a;\n{ return a; }\n", "1:10: error: multiple parameters named 'a'"),
8455 (
8456 "int f(a)\nint a;\nint b;\n{ return a; }\n",
8457 "3:5: error: declaration for parameter 'b' but no such parameter",
8458 ),
8459 ("int f(a)\nint a;\nint a;\n{ return a; }\n", "3:5: error: redefinition of parameter"),
8460 ("int f(a)\nint a = 1;\n{ return a; }\n", "2:5: error: parameter 'a' is initialized"),
8461 (
8462 "int f(a)\nstatic int a;\n{ return a; }\n",
8463 "2:12: error: storage class specified for parameter 'a'",
8464 ),
8465 (
8466 "int f(char);\nint f(a)\nshort a;\n{ return a; }\n",
8467 "2:7: error: argument 'a' doesn't match prototype",
8468 ),
8469 ] {
8470 let result = run(&opts, source);
8471 assert!(result.failed(), "expected this to fail:\n{source}");
8472 assert!(result.messages[0].contains(message), "{:?}", result.messages);
8473 }
8474
8475 let implicit = "int f(a, b)\nint a;\n{ return a + b; }\n";
8478 let mut older = options();
8479 older.std = Std::C89;
8480 assert!(!run(&older, implicit).failed(), "{:?}", run(&older, implicit).messages);
8481 let result = run(&opts, implicit);
8482 assert!(
8483 result.messages[0].contains("1:10: error: type of 'b' defaults to 'int'"),
8484 "{:?}",
8485 result.messages
8486 );
8487
8488 let mut newer = options();
8492 newer.std = Std::C23;
8493 let plain = "int f(a)\nint a;\n{ return a; }\n";
8494 let result = run(&newer, plain);
8495 assert!(!result.failed(), "{:?}", result.messages);
8496 assert_eq!(
8497 result.messages,
8498 ["/main.c:1:5: warning: old-style function definition [E0412]"]
8499 );
8500 assert!(run(&opts, plain).messages.is_empty(), "and nothing to say in the dialects before");
8501 }
8502
8503 #[test]
8510 fn the_obsolete_designators_are_taken_and_are_pedantic_warnings() {
8511 let array = "int a[8] = { [3] 7 };\n";
8512 let member = "struct s { int x; } v = { x: 7 };\n";
8513 for source in [array, member] {
8514 let result = run(&options(), source);
8515 assert!(!result.failed(), "{:?}", result.messages);
8516 assert!(result.messages.is_empty(), "nothing to say: {:?}", result.messages);
8517 }
8518
8519 let mut asked = options();
8520 asked.pedantic = true;
8521 assert_eq!(
8522 run(&asked, array).messages,
8523 ["/main.c:1:18: warning: obsolete designator, write `[i] =` instead [E0415]"]
8524 );
8525 assert_eq!(
8526 run(&asked, member).messages,
8527 ["/main.c:1:27: warning: obsolete designator, write `.field =` instead [E0413]"]
8528 );
8529 }
8530
8531 #[test]
8538 fn a_type_is_refused_when_it_passes_the_largest_object_and_not_before() {
8539 let text = ir(concat!(
8540 "struct huge_struct { short buf[(1L << 62) - 256]; int a, b, c, d; };\n",
8541 "struct brim { char buf[9223372036854775807L]; };\n",
8542 "struct bitty { char buf[9223372036854775800L]; int x : 1; };\n",
8543 "unsigned long h = sizeof(struct huge_struct);\n",
8544 "unsigned long b = sizeof(struct brim);\n",
8545 "unsigned long y = sizeof(struct bitty);\n",
8546 ));
8547 assert!(text.contains("global @h : i64 = 9223372036854775312,"), "{text}");
8548 assert!(text.contains("global @b : i64 = 9223372036854775807,"), "{text}");
8549 assert!(text.contains("global @y : i64 = 9223372036854775804,"), "{text}");
8550
8551 let mut opts = options();
8552 opts.emit = EmitKind::Ir;
8553 let over = "struct over { char buf[9223372036854775800L]; char x[8]; };\n";
8554 let message = "/main.c:1:1: error: type 'struct over' is too large [E0560]";
8555 assert_eq!(run(&opts, over).messages, [message]);
8556 let array = "struct wide { short buf[1L << 62]; };\n";
8557 let message = "/main.c:1:25: error: size of array 'buf' exceeds \
8558 maximum object size '9223372036854775807' [E0537]";
8559 assert_eq!(run(&opts, array).messages[0], message);
8560 }
8561
8562 fn compile_bytes(source: &[u8]) -> Compiled {
8567 let mut opts = options();
8568 opts.emit = EmitKind::Ir;
8569 let mut fs = MemoryFileSystem::new();
8570 fs.insert("/main.c", source.to_vec());
8571 compile(&opts, "/main.c", &fs)
8572 }
8573
8574 #[test]
8581 fn a_byte_that_is_not_a_character_is_kept_in_a_literal_and_refused_outside_one() {
8582 let mut source = b"char s[] = \"a".to_vec();
8583 source.push(0xff);
8584 source.extend_from_slice(b"b\";\nchar c = '");
8585 source.push(0xff);
8586 source.extend_from_slice(b"';\n");
8587 let result = compile_bytes(&source);
8588 assert_eq!(result.messages, Vec::<String>::new(), "a raw byte in a literal is that byte");
8589 assert!(result.text().contains(r#"bytes "a\ffb\00""#), "{}", result.text());
8590 assert!(result.text().contains("global @c : i8 = -1,"), "{}", result.text());
8592
8593 let mut stray = b"int a".to_vec();
8594 stray.push(0xff);
8595 stray.extend_from_slice(b" = 1;\n");
8596 let result = compile_bytes(&stray);
8597 assert!(
8598 result.messages.iter().any(|m| m.contains("source is not valid UTF-8 here")),
8599 "{:?}",
8600 result.messages
8601 );
8602 }
8603
8604 #[test]
8605 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
8606 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
8607 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
8608 let expected = "\
8609func @add(i32, i32) -> i32, linkage(external) {
8610block0(%0: i32, %1: i32):
8611 %2 = add.nsw %0, %1
8612 return %2
8613}
8614";
8615 assert!(text.contains(expected), "{text}");
8616 }
8617
8618 #[test]
8619 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
8620 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
8621 assert!(!text.contains("alloca"), "{text}");
8622 assert!(!text.contains("load"), "{text}");
8623 assert!(!text.contains("store"), "{text}");
8624 }
8625
8626 #[test]
8627 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
8628 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
8629 let expected = "\
8630block0:
8631 %0 = alloca, size 4, align 4
8632 %1 = iconst.i32 1
8633 store %1 -> %0, align 4, tbaa !1
8634 %2 = call @g(%0) : (ptr) -> i32
8635 return %2
8636";
8637 assert_eq!(text, expected);
8638 }
8639
8640 #[test]
8641 fn a_loop_carries_what_it_changes_as_block_parameters() {
8642 let text = body(
8645 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
8646 return total;\n}\n",
8647 );
8648 assert!(!text.contains("alloca"), "{text}");
8649 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
8650 assert!(text.contains("jump block1("), "{text}");
8651 }
8652
8653 #[test]
8654 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
8655 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
8656 assert!(text.contains("icmp slt %0, %1"), "{text}");
8657 assert!(!text.contains("zext"), "{text}");
8658 }
8659
8660 #[test]
8661 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
8662 let text = body("int f(int a, int b) { return a && b; }\n");
8663 let expected = "\
8664block0(%0: i32, %1: i32):
8665 %2 = iconst.i32 0
8666 %3 = icmp ne %0, %2
8667 %4 = iconst.i1 0
8668 br_if %3, block1, block2(%4)
8669
8670block1:
8671 %5 = iconst.i32 0
8672 %6 = icmp ne %1, %5
8673 jump block2(%6)
8674
8675block2(%7: i1):
8676 %8 = zext.i32 %7
8677 return %8
8678";
8679 assert_eq!(text, expected);
8680 }
8681
8682 #[test]
8683 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
8684 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
8685 assert!(!text.contains("block3"), "{text}");
8688 assert!(!text.contains("iconst.i32 3"), "{text}");
8689 }
8690
8691 #[test]
8692 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
8693 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
8694 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
8695 assert!(body("int f(void) { }\n").contains("unreachable"));
8696 }
8697
8698 #[test]
8699 fn a_structure_is_copied_rather_than_held_in_a_value() {
8700 let text = body(
8701 "struct point { int x, y; };\n\
8702 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
8703 );
8704 assert!(text.contains("memcpy"), "{text}");
8705 }
8706
8707 #[test]
8708 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
8709 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
8710 assert!(text.contains("memset"), "{text}");
8711 }
8712
8713 #[test]
8714 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
8715 let text = body(
8716 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
8717 default: r = 4; } return r; }\n",
8718 );
8719 let expected = "\
8720block0(%0: i32):
8721 %1 = iconst.i32 0
8722 switch %0, block1, [1 => block2, 2 => block3(%1)]
8723
8724block1:
8725 %2 = iconst.i32 4
8726 jump block4(%2)
8727
8728block2:
8729 %3 = iconst.i32 1
8730 jump block3(%3)
8731
8732block3(%4: i32):
8733 %5 = iconst.i32 2
8734 %6 = add.nsw %4, %5
8735 jump block4(%6)
8736
8737block4(%7: i32):
8738 return %7
8739";
8740 assert_eq!(text, expected);
8741 }
8742
8743 #[test]
8744 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
8745 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
8748 assert!(text.contains("%2 = sub %0, %1"), "{text}");
8749 assert!(text.contains("icmp ule"), "{text}");
8750 assert!(!text.contains("switch"), "{text}");
8751 }
8752
8753 #[test]
8754 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
8755 let text = body(
8756 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
8757 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
8758 );
8759 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
8762 assert!(text.contains("block5:\n jump block7("), "{text}");
8763 assert!(text.contains("block6:\n jump block8("), "{text}");
8764 }
8765
8766 #[test]
8767 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
8768 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
8769 }
8770
8771 #[test]
8772 fn a_label_a_loop_is_only_entered_through_builds_the_loop_around_it() {
8773 let text = body(
8778 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
8779 return n; }\n",
8780 );
8781 assert!(text.contains("switch %0, block1(%1), [1 => block2, 2 => block3(%1)]"), "{text}");
8784 assert!(text.contains("block3(%3: i32):\n %4 = iconst.i32 1"), "{text}");
8785 assert!(text.contains("block4:\n jump block3("), "{text}");
8786 }
8787
8788 #[test]
8789 fn a_goto_into_a_loop_body_enters_it_without_the_test() {
8790 let text = body("int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n");
8793 assert!(text.starts_with("block0(%0: i32, %1: i32):\n jump block1(%1)"), "{text}");
8794 assert!(text.contains("block1(%2: i32):\n %3 = iconst.i32 1"), "{text}");
8795 assert!(text.contains("br_if %6, block2, block3"), "{text}");
8796 }
8797
8798 #[test]
8799 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
8800 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
8801 assert!(!text.contains("alloca"), "{text}");
8805 assert!(text.contains("block2(%4: i32):\n return %4"), "{text}");
8806 assert_eq!(text.matches("jump block2(").count(), 2, "{text}");
8807 }
8808
8809 #[test]
8810 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
8811 let text =
8812 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
8813 assert!(!text.contains("alloca"), "{text}");
8814 assert!(text.contains("block1(%2: i32):"), "{text}");
8815 assert!(text.contains("jump block1(%5)"), "{text}");
8816 }
8817
8818 #[test]
8819 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
8820 assert_eq!(
8823 body("int f(int x) { return x; spare: return 0; }\n"),
8824 "block0(%0: i32):\n return %0\n"
8825 );
8826 }
8827
8828 #[test]
8829 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
8830 let text = body(
8831 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
8832 );
8833 assert_eq!(
8836 text,
8837 "\
8838block0(%0: ptr):
8839 %1 = load.i8 %0, align 1
8840 %2 = iconst.i8 3
8841 %3 = ashr %1, %2
8842 %4 = sext.i32 %3
8843 return %4
8844"
8845 );
8846 }
8847
8848 #[test]
8849 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
8850 let text =
8854 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
8855 assert_eq!(
8856 text,
8857 "\
8858block0(%0: ptr, %1: i32):
8859 %2 = iconst.i32 16777215
8860 %3 = and %1, %2
8861 %4 = trunc.i16 %3
8862 store %4 -> %0, align 2
8863 %5 = iconst.i32 16
8864 %6 = lshr %3, %5
8865 %7 = trunc.i8 %6
8866 %8 = iconst.i64 2
8867 %9 = ptr_add %0, %8
8868 store %7 -> %9, align 1
8869 return
8870"
8871 );
8872 }
8873
8874 #[test]
8875 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
8876 let text =
8877 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
8878 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
8881 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
8882 }
8883
8884 #[test]
8885 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
8886 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
8889 assert_eq!(text.matches("ashr").count(), 0, "{text}");
8890 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
8891 }
8892
8893 #[test]
8894 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
8895 let text = body(
8899 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
8900 );
8901 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
8902 }
8903
8904 #[test]
8905 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
8906 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
8909 assert!(
8910 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
8911 "{text}"
8912 );
8913 }
8914
8915 #[test]
8916 fn an_initialized_flexible_array_member_makes_the_object_larger_than_its_type() {
8917 let text = ir(concat!(
8922 "struct a { int i; int j[]; } x = { 1, { 2, 0, 2, 3 } };\n",
8923 "struct b { char c; char p[]; } y = { 'o', \"wx\" };\n",
8924 "struct c { char c; char p[]; } z = { '9', { 'e', 'b' } };\n",
8925 "char s[2] = \"hi\";\n",
8926 ));
8927 assert!(
8928 text.contains("global @x : bytes 20 = { i32 1, i32 2, i32 0, i32 2, i32 3 }"),
8929 "{text}"
8930 );
8931 assert!(text.contains("global @y : bytes 4 = { i8 111, bytes \"wx\\00\" }"), "{text}");
8932 assert!(text.contains("global @z : bytes 3 = { i8 57, i8 101, i8 98 }"), "{text}");
8933 assert!(text.contains("global @s : bytes 2 = { bytes \"hi\" }"), "{text}");
8936 }
8937
8938 #[test]
8939 fn a_definition_takes_a_parameter_it_left_unnamed() {
8940 let text = ir("int f(int a, int) { return a; }\n");
8944 assert!(text.contains("func @f(i32, i32) -> i32"), "{text}");
8945 assert!(text.contains("block0(%0: i32, %1: i32):"), "{text}");
8946
8947 let text = ir("int g(int, int n) { return n; }\n");
8950 assert!(text.contains("block0(%0: i32, %1: i32):\n return %1\n"), "{text}");
8951 }
8952
8953 #[test]
8954 fn an_assignment_of_a_structure_is_the_object_it_wrote() {
8955 let text = body(concat!(
8960 "struct s { int f; int g; };\n",
8961 "void h(struct s *a, struct s *c, struct s *d, struct s *e)\n",
8962 "{ *d = *e = a[0] = *c; }\n",
8963 ));
8964 assert_eq!(text.matches("memcpy").count(), 3, "{text}");
8965 assert!(text.contains("memcpy %8, %1, size 8, align 4\n"), "{text}");
8966 assert!(text.contains("memcpy %3, %8, size 8, align 4\n"), "{text}");
8967 assert!(text.contains("memcpy %2, %3, size 8, align 4\n"), "{text}");
8968 }
8969
8970 #[test]
8971 fn a_string_literal_stops_at_the_end_of_the_array_it_is_filling() {
8972 let mut opts = options();
8977 opts.emit = EmitKind::Ir;
8978 let result = run(
8979 &opts,
8980 concat!(
8981 "const char a[2][3] = { \"1234\", \"xyz\" };\n",
8982 "static const char b[3][5] = { \"12345\", \"678\", \"9\" };\n",
8983 "union u { struct { char x[4]; char y[4]; }; struct { char z[8]; }; };\n",
8984 "const union u c = { { \"1234\", \"567\" } };\n",
8985 ),
8986 );
8987 let text = result.text();
8988 assert_eq!(
8989 result.messages,
8990 ["/main.c:1:24: warning: initializer-string for array of 'const char' is too long \
8991 (5 chars into 3 available) [E0637]"]
8992 );
8993 assert!(text.contains("global @a : bytes 6 = { bytes \"123\", bytes \"xyz\" }"), "{text}");
8994 assert!(
8995 text.contains(
8996 "global @b : bytes 15 = { bytes \"12345\", bytes \"678\\00\", zero 1, \
8997 bytes \"9\\00\", zero 3 }"
8998 ),
8999 "{text}"
9000 );
9001 assert!(
9004 text.contains("global @c : bytes 8 = { bytes \"1234\", bytes \"567\\00\" }"),
9005 "{text}"
9006 );
9007 }
9008
9009 #[test]
9010 fn a_cast_of_a_record_to_its_own_type_is_the_object_that_was_cast() {
9011 let text = body(concat!(
9015 "struct s { int a, b; };\nstruct v { struct s s; int t; };\n",
9016 "void g(struct v *);\n",
9017 "void f(struct s *p) { struct v w = { (struct s)*p, 5 }; g(&w); }\n",
9018 ));
9019 assert_eq!(text.matches("memcpy").count(), 1, "{text}");
9020 }
9021
9022 #[test]
9023 fn a_compound_literal_read_in_a_static_initializer_lays_its_bytes_into_the_image() {
9024 let text = ir(concat!(
9029 "struct s { int x; };\n",
9030 "struct t { struct s s; int o; } a = { (struct s){ 2 }, 3 };\n",
9031 "int n = (int){ 7 };\n",
9032 "struct u { struct s p; struct s q; } b = { (struct s){ 1 }, (struct s){ } };\n",
9033 ));
9034 assert!(text.contains("global @a : bytes 8 = { i32 2, i32 3 }"), "{text}");
9035 assert!(text.contains("global @n : i32 = 7,"), "{text}");
9036 assert!(text.contains("global @b : bytes 8 = { i32 1, zero 4 }"), "{text}");
9039 }
9040
9041 #[test]
9042 fn the_address_of_a_compound_literal_asks_for_the_object_it_points_at() {
9043 let text = ir("struct s { int x; };\nstruct s *q = &(struct s){ 9 };\n");
9047 assert!(text.contains("global @.Lanon.0 : i32 = 9, align 4, linkage(internal)"), "{text}");
9048 assert!(text.contains("global @q : bytes 8 = { addr.8 @.Lanon.0 }"), "{text}");
9049 }
9050
9051 #[test]
9052 fn an_object_of_no_size_at_all_has_an_image_with_nothing_in_it() {
9053 let text = ir("unsigned char foo[1][0];\n");
9057 assert!(text.contains("global @foo : bytes 0 = {}, align 1"), "{text}");
9058 }
9059
9060 #[test]
9061 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
9062 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
9065 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
9066 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
9067 }
9068
9069 #[test]
9070 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
9071 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
9075 assert!(
9076 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
9077 "{text}"
9078 );
9079 }
9080
9081 #[test]
9082 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
9083 let text = body(
9088 "\
9089struct s { int a, b; };
9090struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
9091",
9092 );
9093 assert!(text.contains("block3(%7: ptr)"), "{text}");
9095 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
9096 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
9097 }
9098
9099 #[test]
9107 fn the_left_side_of_a_conditional_with_no_middle_is_evaluated_once() {
9108 let text = body("int f(int i) { return ++i ?: 10; }\n");
9109 assert!(text.contains("jump block3(%2)"), "the arm is the value that was tested: {text}");
9110 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
9111
9112 let text = body("long f(int i) { return ++i ?: 10L; }\n");
9115 assert!(text.contains("%5 = sext.i64 %2"), "the arm widens what was tested: {text}");
9116 assert_eq!(text.matches("add.nsw").count(), 1, "incremented once: {text}");
9117
9118 let text = body("int g(void);\nint f(void) { return g() ?: 10; }\n");
9120 assert_eq!(text.matches("call @g").count(), 1, "called once: {text}");
9121
9122 let text = body("int f(int i) { return ++i ? ++i : 10; }\n");
9125 assert_eq!(text.matches("add.nsw").count(), 2, "incremented twice: {text}");
9126 }
9127
9128 #[test]
9129 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
9130 let text = ir("\
9134struct pair { int a, b; };
9135struct pair make(int a, int b);
9136struct pair twice(struct pair p) { return make(p.a, p.b); }
9137");
9138 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
9139 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
9140 }
9141
9142 #[test]
9143 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
9144 let text = ir("\
9148struct big { double v[8]; };
9149struct big grow(struct big b);
9150struct big twice(struct big b) { return grow(grow(b)); }
9151");
9152 assert!(
9153 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
9154 "{text}"
9155 );
9156 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
9157 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
9160 }
9161
9162 #[test]
9163 fn a_structure_passed_to_a_variadic_function_says_so_at_the_call() {
9164 let text = ir("\
9169struct big { double v[8]; };
9170struct pair { int a, b; };
9171int p(const char *, ...);
9172int f(struct big b, struct pair q) { return p(\"\", 1, b, q); }
9173");
9174 assert!(
9175 text.contains("call @p(%4, %5, %2 byval(64, align 8), %6) : (ptr, ...) -> i32"),
9176 "{text}"
9177 );
9178 }
9179
9180 #[test]
9181 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
9182 let body = body(
9185 "\
9186struct pair { int a, b; };
9187struct pair make(int a, int b);
9188int second(void) { return make(1, 2).b; }
9189",
9190 );
9191 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
9192 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
9193 }
9194
9195 #[test]
9196 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
9197 let source = "\
9201struct hfa { float x, y, z; };
9202int take(struct hfa h);
9203int give(struct hfa h) { return take(h); }
9204";
9205 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
9206 let mut opts = options();
9207 opts.emit = EmitKind::Ir;
9208 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
9209 let result = run(&opts, source);
9210 assert_eq!(result.messages, Vec::<String>::new());
9211 assert!(result.text().contains("func @take(f32, f32, f32) -> i32"), "{}", result.text());
9212 }
9213
9214 #[test]
9215 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
9216 let source = "\
9219int use(int *);
9220void f(int n) {
9221 {
9222 int a[n];
9223 use(a);
9224 }
9225 use(0);
9226}
9227";
9228 let body = body(source);
9229 assert!(body.contains("mul.nsw"), "{body}");
9230 assert!(body.contains("stacksave"), "{body}");
9231 assert!(body.contains("alloca %"), "{body}");
9232 assert!(body.contains("stackrestore"), "{body}");
9233 }
9234
9235 #[test]
9236 fn a_goto_out_of_the_scope_of_one_gives_its_stack_back_on_the_way() {
9237 let source = "\
9242int use(int *);
9243int f(int n) {
9244 {
9245 int a[n];
9246 if (use(a)) goto out;
9247 use(0);
9248 }
9249out:
9250 return 0;
9251}
9252";
9253 let body = body(source);
9254 assert_eq!(body.matches("stackrestore").count(), 2, "{body}");
9256 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
9257 assert!(after.starts_with(" %4\n jump block"), "{body}");
9258 }
9259
9260 #[test]
9261 fn a_goto_to_a_label_the_array_is_still_alive_at_leaves_the_stack_alone() {
9262 let source = "\
9266int use(int *);
9267int f(int n) {
9268 int a[n];
9269again:
9270 if (use(a)) goto again;
9271 return 0;
9272}
9273";
9274 let body = body(source);
9275 assert!(body.contains("stacksave"), "{body}");
9276 assert!(!body.contains("stackrestore"), "{body}");
9277 }
9278
9279 #[test]
9280 fn a_goto_back_to_a_label_in_front_of_one_gives_it_back_every_time_round() {
9281 let source = "\
9286int use(int *);
9287int f(int n) {
9288again:
9289 {
9290 int a[n];
9291 if (use(a)) goto again;
9292 }
9293 return 0;
9294}
9295";
9296 let body = body(source);
9297 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
9298 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
9299 assert!(after.starts_with(" %4\n jump block1\n"), "{body}");
9300 }
9301
9302 #[test]
9303 fn the_head_of_a_for_loop_is_a_scope_that_closes_where_the_loop_is_left() {
9304 let source = "\
9310int f(void);
9311void t(void) {
9312 int count = 10;
9313 for (; count--;) {
9314 int b[f()];
9315 int i;
9316 for (i = 0; i < f(); i++) {
9317 b[i] = count;
9318 }
9319 }
9320}
9321";
9322 let body = body(source);
9323 assert_eq!(body.matches("stacksave").count(), 1, "{body}");
9327 let (_, after) = body.split_once("stackrestore").expect("the stack is given back");
9328 let next = after.split("\n\n").next().expect("the block the restore is in");
9331 assert!(next.contains("jump block1("), "{body}");
9332 }
9333
9334 #[test]
9335 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
9336 let source = "\
9339unsigned long f(int n) {
9340 int a[n];
9341 n = 0;
9342 return sizeof a;
9343}
9344";
9345 let body = body(source);
9346 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
9348 }
9349
9350 #[test]
9351 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
9352 let source = "\
9355int use(int);
9356int f(int x) {
9357 return ({
9358 int t = use(x);
9359 t * t;
9360 });
9361}
9362";
9363 let expected = "\
9364block0(%0: i32):
9365 %1 = call @use(%0) : (i32) -> i32
9366 %2 = mul.nsw %1, %1
9367 return %2
9368";
9369 assert_eq!(body(source), expected);
9370 }
9371
9372 #[test]
9373 fn a_comma_whose_value_is_an_object_names_the_object_the_right_side_named() {
9374 let source = "\
9378struct pair { int a, b; };
9379void bail(void);
9380int f(struct pair p) {
9381 return (bail(), p).b;
9382}
9383";
9384 let expected = "\
9385block0(%0: i64):
9386 %1 = alloca, size 8, align 4
9387 store %0 -> %1, align 4
9388 call @bail() : ()
9389 %2 = iconst.i64 4
9390 %3 = ptr_add %1, %2
9391 %4 = load.i32 %3, align 4, tbaa !1
9392 return %4
9393";
9394 assert_eq!(body(source), expected);
9395 }
9396
9397 #[test]
9398 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
9399 let source = "int f(int x) { return ({ return x; 0; }); }\n";
9403 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
9404 }
9405
9406 #[test]
9407 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
9408 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
9412 let expected = "\
9413block0(%0: ptr):
9414 %1 = va_arg.f64 %0
9415 %2 = va_arg.f64 %0
9416 %3 = fadd %1, %2
9417 return %3
9418";
9419 assert_eq!(body(source), expected);
9420 }
9421
9422 #[test]
9423 fn one_that_reads_a_structure_answers_where_the_object_is() {
9424 let source = "\
9438struct s { int a; long b; };
9439long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.b; }
9440";
9441 let expected = "\
9442block0(%0: ptr):
9443 %1 = alloca, size 16, align 16
9444 %2 = va_object %0, size 16, align 8, in(int 8 at 0, int 8 at 8)
9445 memcpy %1, %2, size 16, align 8
9446 %3 = iconst.i64 8
9447 %4 = ptr_add %1, %3
9448 %5 = load.i64 %4, align 8, tbaa !1
9449 return %5
9450";
9451 assert_eq!(body(source), expected);
9452 }
9453
9454 #[test]
9458 fn the_classification_says_which_registers_the_object_arrived_in() {
9459 let source = "\
9460struct s { double a; double b; };
9461double f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a; }
9462";
9463 assert!(
9464 body(source)
9465 .contains("va_object %0, size 16, align 8, in(float f64 at 0, float f64 at 8)"),
9466 "{}",
9467 body(source)
9468 );
9469
9470 let big = "\
9471struct s { long a[4]; };
9472long f(__builtin_va_list ap) { struct s v = __builtin_va_arg(ap, struct s); return v.a[0]; }
9473";
9474 assert!(body(big).contains("va_object %0, size 32, align 8\n"), "{}", body(big));
9475 }
9476
9477 #[test]
9478 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
9479 let source = "\
9483int f(int c) {
9484 void *p = c ? &&one : &&two;
9485 goto *p;
9486one:
9487 return 1;
9488two:
9489 return 2;
9490}
9491";
9492 let expected = "\
9493block0(%0: i32):
9494 %1 = iconst.i32 0
9495 %2 = icmp ne %0, %1
9496 br_if %2, block1, block2
9497
9498block1:
9499 %3 = block_addr block3
9500 jump block4(%3)
9501
9502block2:
9503 %4 = block_addr block5
9504 jump block4(%4)
9505
9506block3:
9507 %5 = iconst.i32 1
9508 return %5
9509
9510block4(%6: ptr):
9511 indirect_br %6, block3, block5
9512
9513block5:
9514 %7 = iconst.i32 2
9515 return %7
9516";
9517 assert_eq!(body(source), expected);
9518 }
9519
9520 fn dispatch(labels: usize) -> String {
9523 let mask = labels - 1;
9524 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
9525 for index in 0..labels {
9526 source.push_str(&format!(" &&a{index},"));
9527 }
9528 source.push_str(" };\n\tint w = n, x = n + 1, y = n + 2, z = n + 3;\n");
9529 source.push_str(&format!("\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
9530 for index in 0..labels {
9531 let step = match index % 4 {
9532 0 => "w += x;",
9533 1 => "x += y;",
9534 2 => "y += z;",
9535 _ => "z += w;",
9536 };
9537 source.push_str(&format!("a{index}:\n\t{step}\n"));
9538 source.push_str("\tif (--n <= 0) return w + x + y + z;\n");
9539 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
9540 }
9541 source.push_str("}\n");
9542 source
9543 }
9544
9545 fn in_front_of_the_jump(text: &str) -> usize {
9547 let (before, _) = text.split_once("\tjmp\t*%").expect("a jump through a register");
9548 before.lines().rev().take_while(|line| line.starts_with("\tmov")).count()
9549 }
9550
9551 #[test]
9561 fn a_jump_through_a_register_writes_what_it_carries_and_not_the_whole_table() {
9562 let small = in_front_of_the_jump(&asm(&dispatch(4)));
9563 let large = in_front_of_the_jump(&asm(&dispatch(32)));
9564 assert_eq!(small, large, "eight times the labels and the same values in hand");
9565 assert!(large <= 8, "the values the loop keeps, and not a set of them per label: {large}");
9566 }
9567
9568 fn crowded(labels: usize) -> String {
9571 const VALUES: usize = 24;
9572 let mask = labels - 1;
9573 let mut source = String::from("int spin(int n)\n{\n\tstatic void *table[] = {");
9574 for index in 0..labels {
9575 source.push_str(&format!(" &&a{index},"));
9576 }
9577 source.push_str(" };\n\t");
9578 for value in 0..VALUES {
9579 source.push_str(&format!("int v{value} = n + {value}; "));
9580 }
9581 let sum: Vec<String> = (0..VALUES).map(|value| format!("v{value}")).collect();
9582 source.push_str(&format!("\n\tif (n < 0) return 0;\n\tgoto *table[n & {mask}];\n"));
9583 for index in 0..labels {
9584 let (to, from) = (index % VALUES, (index + 1) % VALUES);
9585 source.push_str(&format!("a{index}:\n\tv{to} += v{from};\n"));
9586 source.push_str(&format!("\tif (--n <= 0) return {};\n", sum.join(" + ")));
9587 source.push_str(&format!("\tgoto *table[n & {mask}];\n"));
9588 }
9589 source.push_str("}\n");
9590 source
9591 }
9592
9593 fn the_frame(text: &str) -> u64 {
9595 text.lines()
9596 .find_map(|line| {
9597 let (size, _) = line.strip_prefix("\tsubq\t$")?.split_once(", %rsp")?;
9598 size.parse().ok()
9599 })
9600 .expect("a function that opens a frame")
9601 }
9602
9603 #[test]
9612 fn a_frame_holds_what_is_wanted_at_once_and_not_a_slot_for_every_label() {
9613 let small = the_frame(&asm(&crowded(16)));
9614 let large = the_frame(&asm(&crowded(64)));
9615 assert_eq!(small, large, "four times the labels and the same values: {small}, {large}");
9616 }
9617
9618 #[test]
9627 fn a_template_that_names_its_own_registers_gets_the_ones_it_named() {
9628 let source = "void save(void *nlr) {
9629 __asm volatile (
9630 \"movq %%rsp, 32(%%rdi) \\n\"
9631 \"movq %%rbx, 40(%%rdi) \\n\"
9632 \"movq %%r12, 48(%%rdi) \\n\"
9633 : : \"D\" (nlr) : \"memory\");
9634}
9635";
9636 let text = asm(source);
9637 assert!(text.contains("\tmovq\t%rsp, 32(%rdi)\n"), "{text}");
9638 assert!(text.contains("\tmovq\t%rbx, 40(%rdi)\n"), "{text}");
9639 assert!(text.contains("\tmovq\t%r12, 48(%rdi)\n"), "{text}");
9640 }
9641
9642 #[test]
9643 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
9644 let source = "void **next(void);
9647void f(void) { goto *next(); }
9648";
9649 let expected = "\
9650block0:
9651 %0 = call @next() : () -> ptr
9652 unreachable
9653";
9654 assert_eq!(body(source), expected);
9655 }
9656
9657 #[test]
9658 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
9659 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
9662 let expected = "\
9663block0:
9664 inline_asm.volatile \"mfence\", \"\", \"memory\"()
9665 return
9666";
9667 assert_eq!(body(source), expected);
9668 }
9669
9670 #[test]
9671 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
9672 let source = "\
9675int f(int x, int y) {
9676 int r;
9677 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
9678 return r + y;
9679}
9680";
9681 let expected = "\
9682block0(%0: i32, %1: i32):
9683 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
9684 %4 = add.nsw %2, %3
9685 return %4
9686";
9687 assert_eq!(body(source), expected);
9688 }
9689
9690 #[test]
9691 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
9692 let source = "\
9697struct pair { int a, b; };
9698int f(int x) {
9699 int slot = x;
9700 struct pair p = { x, x };
9701 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
9702 return slot + p.a;
9703}
9704";
9705 let text = body(source);
9706 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
9707 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
9708 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
9709 }
9710
9711 #[test]
9712 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
9713 let source = "\
9718int f(int x) {
9719 int r = 7;
9720 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
9721 return r;
9722away:
9723 return r;
9724}
9725";
9726 let expected = "\
9727block0(%0: i32):
9728 %1 = iconst.i32 7
9729 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
9730
9731block1:
9732 return %2
9733
9734block2:
9735 return %1
9736";
9737 assert_eq!(body(source), expected);
9738 }
9739
9740 #[test]
9741 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
9742 let mut opts = options();
9746 opts.emit = EmitKind::Ir;
9747 for (source, expected) in [
9748 (
9749 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
9750 "output operand constraint lacks '='",
9751 ),
9752 (
9753 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
9754 "lvalue required in 'asm' statement",
9755 ),
9756 (
9757 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
9758 "read-only variable 'g' used as 'asm' output",
9759 ),
9760 (
9761 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
9762 "input operand constraint contains '='",
9763 ),
9764 (
9765 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
9766 "memory input 0 is not directly addressable",
9767 ),
9768 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
9769 (
9770 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
9771 "duplicate asm operand name 'a'",
9772 ),
9773 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
9774 ] {
9775 let result = run(&opts, source);
9776 assert!(result.failed(), "expected this to be reported:\n{source}");
9777 assert!(
9778 result.messages.iter().any(|m| m.contains(expected)),
9779 "{expected}\n{:?}",
9780 result.messages
9781 );
9782 }
9783 }
9784
9785 #[test]
9790 fn an_asm_at_file_scope_that_is_directives_becomes_the_objects_it_defines() {
9791 let text = ir(concat!(
9792 "__asm__(\n",
9793 " \".section .rodata\\n\"\n",
9794 " \".globl first\\n\"\n",
9795 " \".balign 8\\n\"\n",
9796 " \"first:\\n\"\n",
9797 " \".long 1\\n\"\n",
9798 " \".long 2\\n\"\n",
9799 " \".globl last\\n\"\n",
9800 " \"last:\\n\"\n",
9801 " \".quad last - first\\n\");\n",
9802 "extern const int first[];\n",
9803 "extern const long last;\n",
9804 ));
9805 assert!(text.contains("global @first : bytes 8 = { i32 1, i32 2 }, align 8"), "{text}");
9806 assert!(text.contains("global @last : i64 = 8"), "{text}");
9807 }
9808
9809 #[test]
9813 fn a_name_an_asm_at_file_scope_defined_is_not_undone_by_a_declaration_of_it() {
9814 let text = ir(concat!(
9815 "__asm__(\".data\\n.globl counter\\ncounter:\\n.long 7\\n\");\n",
9816 "extern int counter;\n",
9817 "int read(void) { return counter; }\n",
9818 ));
9819 assert!(text.contains("global @counter : i32 = 7"), "{text}");
9820 }
9821
9822 #[test]
9827 fn bytes_under_no_label_at_file_scope_are_a_global_in_front_of_the_label() {
9828 let text = ir(concat!(
9829 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n662:\\n",
9830 ".pushsection .data.ignore\\n.byte 7\\n.popsection\\n.byte 662b - 661b\\n\");\n",
9831 "extern unsigned char stuff[];\n",
9832 "int read(void) { return stuff[0]; }\n",
9833 ));
9834 let under = text.find("global @.Lasm.0 : i8 = 41").expect(&text);
9835 let named = text.find("global @stuff : i8 = 42").expect(&text);
9836 assert!(under < named, "the bytes under no label come first: {text}");
9837 assert!(text.contains("global @.Lasm.1 : i8 = 7, align 1, linkage(internal), section"));
9838 let after = text.find("global @.Lasm.2 : i8 = 1").expect(&text);
9843 assert!(named < after, "{text}");
9844 }
9845
9846 #[test]
9851 fn a_distance_from_here_at_file_scope_is_a_hole_naming_the_global_it_measures_to() {
9852 let text = ir(concat!(
9853 "__asm__(\".data\\n.byte 41\\nstuff:\\n661:\\n.byte 42\\n",
9854 ".pushsection .data.ignore\\n.long 661b - .\\n.popsection\\n\");\n",
9855 "extern unsigned char stuff[];\n",
9856 "int read(void) { return stuff[0]; }\n",
9857 ));
9858 assert!(text.contains("global @.Lasm.1 : bytes 4 = { away.4 @stuff }"), "{text}");
9862 }
9863
9864 #[test]
9869 fn a_set_at_file_scope_is_a_second_name_for_what_it_names() {
9870 let text = ir(concat!(
9871 "void base(void) {}\n",
9872 "__asm__(\".weak one\\n.set one, base\");\n",
9873 "__asm__(\".globl two\\n.set two, base\");\n",
9874 "__asm__(\".set three, base\");\n",
9875 "void three(void) {}\n",
9876 ));
9877 assert!(text.contains("alias @one = @base, linkage(weak)"), "{text}");
9878 assert!(text.contains("alias @two = @base"), "{text}");
9879 assert!(!text.contains("alias @three"), "a definition of the name wins: {text}");
9880 assert!(text.contains("func @three"), "{text}");
9881 }
9882
9883 #[test]
9888 fn a_set_of_a_name_this_file_does_not_define_says_so() {
9889 let messages = errors("__asm__(\".set here, elsewhere\");\n");
9890 assert!(
9891 messages
9892 .iter()
9893 .any(|m| m.contains("'here' is aliased to undefined symbol 'elsewhere'")
9894 && m.contains("E0697")),
9895 "{messages:?}"
9896 );
9897 }
9898
9899 #[test]
9902 fn an_incbin_at_file_scope_is_the_bytes_of_the_file_it_names() {
9903 let mut opts = options();
9904 opts.emit = EmitKind::Ir;
9905 let mut fs = MemoryFileSystem::new();
9906 fs.insert(
9907 "/main.c",
9908 b"__asm__(\".data\\n.globl blob\\nblob:\\n.incbin \\\"seed\\\"\\n\");\n".to_vec(),
9909 );
9910 fs.insert("seed", b"hi".to_vec());
9911 let result = compile(&opts, "/main.c", &fs);
9912 assert_eq!(result.messages, Vec::<String>::new());
9913 let text = result.text();
9914 assert!(text.contains("global @blob : bytes 2 = { bytes \"hi\" }"), "{text}");
9915 }
9916
9917 #[test]
9920 fn an_incbin_naming_a_file_that_is_not_there_says_which_file() {
9921 let messages = errors("__asm__(\".data\\nb:\\n.incbin \\\"nowhere\\\"\\n\");\n");
9922 assert!(
9923 messages
9924 .iter()
9925 .any(|m| m.contains("cannot open 'nowhere' for reading") && m.contains("E0702")),
9926 "{messages:?}"
9927 );
9928 }
9929
9930 #[test]
9934 fn a_directive_in_an_asm_at_file_scope_is_refused_rather_than_ignored() {
9935 let source = "__asm__(\".data\\n.set alias, 4\\n\");\n";
9936 let messages = errors(source);
9937 assert!(
9938 messages
9939 .iter()
9940 .any(|m| m.contains("not supported yet") && m.contains("in an `asm` at file scope")),
9941 "{source}\n{messages:?}"
9942 );
9943 }
9944
9945 #[test]
9955 fn a_naked_function_is_its_own_prologue_and_its_own_ending() {
9956 let text = asm(concat!(
9957 "unsigned nlr_push_tail(void *nlr);\n",
9958 "__attribute__((naked)) unsigned nlr_push(void *nlr) {\n",
9959 " __asm volatile(\n",
9960 " \"movq (%rsp), %rax\\n\"\n",
9961 " \"movq %rax, 16(%rdi)\\n\"\n",
9962 " \"movq %rbx, 40(%rdi)\\n\"\n",
9963 " \"jmp nlr_push_tail\\n\");\n",
9964 "}\n",
9965 ));
9966 assert!(text.contains("\tmovq\t(%rsp), %rax\n"), "{text}");
9967 assert!(text.contains("\tjmp\tnlr_push_tail\n"), "{text}");
9968 assert!(text.contains("\tud2\n"), "{text}");
9969 assert!(!text.contains("\tpushq\t"), "nothing is saved in front of it: {text}");
9970 assert!(!text.contains("\tret\n"), "the jump is where it ends: {text}");
9971 }
9972
9973 #[test]
9976 fn what_a_function_without_a_prologue_cannot_be_given_is_refused() {
9977 let mut opts = options();
9978 opts.emit = EmitKind::Asm;
9979 for (source, why) in [
9980 (
9981 "__attribute__((naked)) void f(void) { volatile long a[8]; a[0] = 1; }\n",
9982 "bytes of frame",
9983 ),
9984 (
9985 "__attribute__((naked)) void f(int n) { char a[n]; __asm(\"nop\" ::\"r\"(a)); }\n",
9986 "has no prologue to point a frame pointer at it with",
9987 ),
9988 ("void elsewhere(void); void f(void) { __asm(\"jmp elsewhere\"); }\n", "jumps out of"),
9989 ] {
9990 let result = run(&opts, source);
9991 assert!(result.failed(), "expected this to be refused:\n{source}");
9992 assert!(
9993 result.messages.iter().any(|message| message.contains(why)),
9994 "{:?}",
9995 result.messages
9996 );
9997 }
9998 }
9999
10000 #[test]
10001 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
10002 let mut opts = options();
10003 opts.emit = EmitKind::Ir;
10004 for source in [
10005 "int f(int n) { void *p = &&out; if (n) goto *p; { int a[n]; out: return 1; } }\n",
10006 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
10007 ] {
10008 let result = run(&opts, source);
10009 assert!(result.failed(), "expected this to be reported:\n{source}");
10010 assert!(
10011 result.messages.iter().any(|m| m.contains("not supported yet")),
10012 "{:?}",
10013 result.messages
10014 );
10015 }
10016 }
10017
10018 fn round_trip(source: &str) -> (String, String) {
10020 let printed = ir(source);
10021 let mut opts = options();
10022 opts.emit = EmitKind::Ir;
10023 let mut fs = MemoryFileSystem::new();
10024 fs.insert("/main.ir", printed.clone().into_bytes());
10025 let result = compile_ir(&opts, "/main.ir", &fs);
10026 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
10027 (printed, result.text().to_owned())
10028 }
10029
10030 #[test]
10031 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
10032 let (printed, again) = round_trip(
10036 "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",
10037 );
10038 assert_eq!(printed, again);
10039 }
10040
10041 #[test]
10042 fn ir_that_is_not_ir_says_which_line_stopped_it() {
10043 let mut opts = options();
10044 opts.emit = EmitKind::Ir;
10045 let mut fs = MemoryFileSystem::new();
10046 let text = "\
10047; ModuleID = 'a.c'
10048; format 0
10049target triple = \"x86_64-unknown-linux-gnu\"
10050target datalayout = \"e-p:64:64-i64:64-S128\"
10051
10052func @f(), linkage(external) {
10053block0:
10054 frobnicate
10055}
10056";
10057 fs.insert("/main.ir", text.as_bytes().to_vec());
10058 let result = compile_ir(&opts, "/main.ir", &fs);
10059 assert!(result.failed());
10060 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
10061 }
10062
10063 #[test]
10064 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
10065 let mut opts = options();
10068 opts.emit = EmitKind::Ir;
10069 let mut fs = MemoryFileSystem::new();
10070 let text = "\
10071; ModuleID = 'a.c'
10072; format 0
10073target triple = \"x86_64-unknown-linux-gnu\"
10074target datalayout = \"e-p:64:64-i64:64-S128\"
10075
10076func @f(), linkage(external) {
10077block0:
10078 %0 = iconst.i32 1
10079 return %0
10080}
10081";
10082 fs.insert("/main.ir", text.as_bytes().to_vec());
10083 let result = compile_ir(&opts, "/main.ir", &fs);
10084 assert!(result.failed());
10085 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
10086 }
10087
10088 #[test]
10089 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
10090 let mut fs = MemoryFileSystem::new();
10092 fs.insert("/main.ir", Vec::new());
10093 let result = compile_ir(&options(), "/main.ir", &fs);
10094 assert!(result.failed());
10095 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
10096 }
10097
10098 #[test]
10099 fn the_printed_ir_reads_back_as_the_same_module() {
10100 let text = ir("\
10103struct point { int x, y; };
10104static const char greeting[] = \"hi\";
10105int table[4] = { 1, 2, 3 };
10106int puts(const char *);
10107double half(double x) { return x / 2.0; }
10108int f(int n) {
10109 int total = 0;
10110 for (int i = 0; i < n; i++) {
10111 if (i == 3) continue;
10112 total += table[i];
10113 }
10114 switch (n) {
10115 case 0: total = 1;
10116 case 1: total++; break;
10117 default: total = -total;
10118 }
10119 struct point p = { total, 1 };
10120 int *q = &p.y;
10121 puts(greeting);
10122 return p.x + *q;
10123}
10124int dispatch(int c) {
10125 void *p = c ? &&one : &&two;
10126 goto *p;
10127one:
10128 return 1;
10129two:
10130 return 2;
10131}
10132int assembly(int x, int *p) {
10133 int r;
10134 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
10135 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
10136 return r;
10137away:
10138 return 0;
10139}
10140");
10141 let mut names = Interner::new();
10142 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
10143 assert_eq!(rucc_ir::print(&module, &names), text);
10144 }
10145
10146 #[test]
10147 fn what_save_temps_keeps_is_the_text_that_was_compiled_and_the_assembly_that_was_assembled() {
10148 let mut opts = options();
10152 opts.emit = EmitKind::Object;
10153 opts.save_temps = rucc_session::SaveTemps::Object;
10154 let result = run(&opts, "#define N 2\nint a[N];\n");
10155 assert_eq!(result.messages, Vec::<String>::new());
10156 let text = result.temps.preprocessed.expect("the preprocessed text");
10157 assert!(text.contains("int a[2];"), "{text}");
10158 assert!(text.starts_with("# 1 \"/main.c\""), "{text}");
10159 let asm = result.temps.assembly.expect("the assembly");
10160 assert!(asm.contains("a:"), "{asm}");
10161 assert!(matches!(result.artifact, Artifact::Object { .. }), "{:?}", result.artifact);
10162 }
10163
10164 #[test]
10165 fn nothing_is_kept_unless_the_flag_asked_for_it() {
10166 let mut opts = options();
10169 opts.emit = EmitKind::Object;
10170 assert_eq!(run(&opts, "int a;\n").temps, Temps::default());
10171 }
10172
10173 #[test]
10174 fn a_compilation_that_stops_before_the_back_end_keeps_the_text_and_no_assembly() {
10175 let mut opts = options();
10178 opts.emit = EmitKind::Ir;
10179 opts.save_temps = rucc_session::SaveTemps::Cwd;
10180 let result = run(&opts, "int a;\n");
10181 assert!(result.temps.preprocessed.is_some());
10182 assert_eq!(result.temps.assembly, None);
10183 }
10184
10185 fn span(from: u64, len: u64, held: rucc_debug::Held) -> rucc_debug::Span {
10187 rucc_debug::Span { from, len, held }
10188 }
10189
10190 #[test]
10191 fn two_stretches_that_meet_and_agree_come_out_as_one() {
10192 let one = span(0, 4, rucc_debug::Held::Reg(3));
10193 let two = span(4, 4, rucc_debug::Held::Reg(3));
10194 assert_eq!(settle(vec![two, one]), vec![span(0, 8, rucc_debug::Held::Reg(3))]);
10195 }
10196
10197 #[test]
10198 fn a_stretch_another_starts_inside_and_disagrees_with_ends_where_the_other_starts() {
10199 let one = span(0, 8, rucc_debug::Held::Reg(3));
10200 let two = span(4, 8, rucc_debug::Held::Reg(4));
10201 let settled = settle(vec![one, two]);
10204 assert_eq!(
10205 settled,
10206 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 8, rucc_debug::Held::Reg(4))]
10207 );
10208 }
10209
10210 #[test]
10211 fn a_stretch_cut_by_one_that_ends_first_does_not_come_back_after_it() {
10212 let one = span(0, 16, rucc_debug::Held::Reg(3));
10215 let two = span(4, 4, rucc_debug::Held::Reg(4));
10216 assert_eq!(
10217 settle(vec![one, two]),
10218 vec![span(0, 4, rucc_debug::Held::Reg(3)), span(4, 4, rucc_debug::Held::Reg(4))]
10219 );
10220 }
10221
10222 #[test]
10223 fn a_stretch_inside_another_that_agrees_with_it_cuts_nothing() {
10224 let one = span(0, 16, rucc_debug::Held::Reg(3));
10225 let two = span(4, 4, rucc_debug::Held::Reg(3));
10226 assert_eq!(settle(vec![one, two]), vec![span(0, 16, rucc_debug::Held::Reg(3))]);
10227 }
10228
10229 #[test]
10230 fn a_stretch_two_others_disagree_over_the_whole_of_says_nothing_at_all() {
10231 let one = span(0, 8, rucc_debug::Held::Reg(3));
10232 let two = span(0, 8, rucc_debug::Held::Frame(-16));
10233 assert_eq!(settle(vec![one, two]), Vec::new());
10234 }
10235
10236 #[test]
10237 fn stretches_with_a_gap_between_them_keep_the_gap() {
10238 let one = span(0, 4, rucc_debug::Held::Reg(3));
10239 let two = span(16, 4, rucc_debug::Held::Reg(3));
10240 assert_eq!(settle(vec![one, two]), vec![one, two]);
10241 }
10242
10243 fn extent(len: usize) -> rucc_object::Extent {
10245 rucc_object::Extent {
10246 name: "f".to_owned(),
10247 start: 0,
10248 len,
10249 align: 1,
10250 binding: rucc_object::Binding::Global,
10251 visibility: rucc_object::Visibility::Default,
10252 patch: None,
10253 landings: Vec::new(),
10254 }
10255 }
10256
10257 fn row(at: usize, lo: u32, hi: u32) -> rucc_asm::Row {
10259 let span = Span::new(lo, hi);
10260 rucc_asm::Row { at, span, inst: None }
10261 }
10262
10263 #[test]
10264 fn a_row_ends_where_the_next_address_begins() {
10265 let rows = [row(0, 0, 1), row(4, 1, 2), row(10, 2, 3)];
10266 assert_eq!(ends(&extent(16), &rows), vec![4, 10, 16]);
10267 }
10268
10269 #[test]
10270 fn rows_sharing_an_address_all_end_where_the_next_address_begins() {
10271 let rows = [row(0, 0, 1), row(4, 1, 2), row(4, 2, 3), row(4, 3, 4)];
10274 assert_eq!(ends(&extent(12), &rows), vec![4, 12, 12, 12]);
10275 }
10276
10277 #[test]
10278 fn the_rows_of_a_scope_that_are_next_to_each_other_come_out_as_one_stretch() {
10279 let rows = [row(0, 0, 4), row(4, 10, 14), row(8, 14, 18), row(12, 40, 44)];
10280 let ends = ends(&extent(16), &rows);
10281 let scope = Span::new(8, 20);
10282 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 8 }]);
10283 }
10284
10285 #[test]
10286 fn a_scope_the_back_end_split_in_two_comes_out_as_two_stretches() {
10287 let rows = [row(0, 10, 14), row(4, 40, 44), row(8, 14, 18)];
10288 let ends = ends(&extent(12), &rows);
10289 let scope = Span::new(8, 20);
10290 let over = spread(scope, &ends, &rows);
10291 assert_eq!(
10292 over,
10293 vec![rucc_debug::Reach { from: 0, len: 4 }, rucc_debug::Reach { from: 8, len: 4 }]
10294 );
10295 }
10296
10297 #[test]
10298 fn a_row_with_no_source_of_its_own_belongs_to_no_scope() {
10299 let rows = [rucc_asm::Row { at: 0, span: Span::DUMMY, inst: None }, row(4, 10, 14)];
10301 let ends = ends(&extent(8), &rows);
10302 let scope = Span::new(0, 20);
10303 assert_eq!(spread(scope, &ends, &rows), vec![rucc_debug::Reach { from: 4, len: 4 }]);
10304 }
10305
10306 fn scope(parent: Option<usize>, lo: u32, hi: u32) -> crate::shapes::Scope {
10308 let span = Span::new(lo, hi);
10309 crate::shapes::Scope { parent, span }
10310 }
10311
10312 #[test]
10313 fn a_function_gets_the_scopes_its_own_locals_are_in_and_nothing_else() {
10314 let scopes = [scope(None, 0, 10), scope(None, 20, 30), scope(Some(1), 22, 26)];
10316 let rows = [row(0, 22, 24), row(4, 26, 28)];
10317 let (out, at) = nests(&[Some(2)], &scopes, &extent(8), &rows);
10318 assert_eq!(at.get(&1), Some(&0));
10321 assert_eq!(at.get(&2), Some(&1));
10322 assert_eq!(at.get(&0), None);
10323 assert_eq!(out.len(), 2);
10324 assert_eq!(out[0].parent, None);
10325 assert_eq!(out[1].parent, Some(0));
10326 assert_eq!(out[0].over, vec![rucc_debug::Reach { from: 0, len: 8 }]);
10327 assert_eq!(out[1].over, vec![rucc_debug::Reach { from: 0, len: 4 }]);
10328 }
10329
10330 #[test]
10331 fn a_local_written_straight_into_the_body_pulls_no_scope_in() {
10332 let scopes = [scope(None, 20, 30)];
10333 let rows = [row(0, 22, 24)];
10334 let (out, at) = nests(&[None], &scopes, &extent(4), &rows);
10335 assert_eq!(out, Vec::new());
10336 assert!(at.is_empty());
10337 }
10338
10339 #[test]
10340 fn a_scope_whose_code_all_went_away_is_still_one_of_the_functions_scopes() {
10341 let scopes = [scope(None, 20, 30)];
10345 let rows = [row(0, 40, 44)];
10346 let (out, at) = nests(&[Some(0)], &scopes, &extent(4), &rows);
10347 assert_eq!(at.get(&0), Some(&0));
10348 assert_eq!(out.len(), 1);
10349 assert_eq!(out[0].over, Vec::new());
10350 }
10351}