1use std::path::Path;
14
15use rucc_diag::{Diagnostic, Severity, Span};
16use rucc_lex::{Convert, Keywords, PpToken, convert};
17use rucc_sema::{Checker, Context as CheckContext};
18use rucc_session::{EmitKind, FileSystem, Options, Session};
19
20use crate::preprocess::render;
21
22#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct Compiled {
25 pub text: String,
27 pub messages: Vec<String>,
29 pub errors: u32,
31}
32
33impl Compiled {
34 #[must_use]
36 pub fn failed(&self) -> bool {
37 self.errors > 0
38 }
39}
40
41#[must_use]
54pub fn compile(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
55 let mut sess = Session::new(opts.clone());
56 let keywords = Keywords::new(&mut sess.interner, opts.std, opts.gnu_extensions);
60 let mut diagnostics: Vec<Diagnostic> = Vec::new();
61
62 let bytes = match fs.read(Path::new(name)) {
63 Ok(bytes) => bytes,
64 Err(e) => return failure(format!("{name}: {e}")),
65 };
66 let Ok(file) = sess.sources.add_shared(name, bytes, None) else {
67 return failure(format!("{name}: the source map has no room left for this file"));
68 };
69
70 let mut pp = rucc_pp::Preprocessor::new();
74 let predef = rucc_pp::Predef::for_options(opts);
75 let expanded: Vec<PpToken> = {
76 let mut cx = rucc_pp::Context::new(&mut sess.interner, &mut sess.sources, fs, &opts.search);
77 cx.lex = rucc_lex::Options::for_dialect(opts.std, opts.gnu_extensions);
78 if pp.predefine(&sess.target, &predef, &mut cx).is_err() {
79 return failure(format!("{name}: the source map has no room for the built in macros"));
80 }
81 pp.run(file, &mut cx).iter().map(|token| token.to_pp()).collect()
82 };
83 diagnostics.extend(pp.take_diagnostics());
84
85 let cx = Convert {
88 keywords: &keywords,
89 interner: &sess.interner,
90 target: &sess.target,
91 std: opts.std,
92 gnu: opts.gnu_extensions,
93 pedantic: opts.pedantic,
94 };
95 let (tokens, complaints) = convert(&expanded, &cx);
96 diagnostics.extend(complaints);
97
98 let parsed = rucc_parse::parse(
99 &tokens,
100 rucc_parse::Context {
101 interner: &sess.interner,
102 std: opts.std,
103 gnu: opts.gnu_extensions,
104 pedantic: opts.pedantic,
105 error_limit: opts.error_limit as usize,
106 },
107 );
108 let parse_failed = parsed.diagnostics.iter().any(|d| d.severity.is_fatal());
109 diagnostics.extend(parsed.diagnostics);
110
111 let mut text = String::new();
112 if !parse_failed {
113 let mut checker = Checker::new(
114 &parsed.ast,
115 CheckContext {
116 names: &sess.interner,
117 target: &sess.target,
118 std: opts.std,
119 gnu: opts.gnu_extensions,
120 pedantic: opts.pedantic,
121 error_limit: opts.error_limit as usize,
122 },
123 );
124 checker.check_unit();
125 let checked = checker.finish();
126 if !checked.failed() {
127 match opts.emit {
128 EmitKind::Tast => {
129 text = rucc_sema::print(&checked.tast, &checked.types, &sess.interner);
130 }
131 EmitKind::Ir => {
132 let lowered = rucc_lower::lower(
133 name,
134 rucc_lower::Context {
135 tast: &checked.tast,
136 types: &checked.types,
137 target: &sess.target,
138 names: &mut sess.interner,
139 },
140 );
141 let failed = lowered.diagnostics.iter().any(|d| d.severity.is_fatal());
145 if !failed {
146 if let Err(errors) = rucc_ir::verify(&lowered.module, &sess.interner) {
151 for error in errors {
152 diagnostics.push(internal(&format!("invalid IR, {error}")));
153 }
154 } else {
155 text = rucc_ir::print(&lowered.module, &sess.interner);
156 }
157 }
158 diagnostics.extend(lowered.diagnostics);
159 }
160 _ => {}
161 }
162 }
163 diagnostics.extend(checked.diagnostics);
164 }
165
166 let mut messages = Vec::with_capacity(diagnostics.len());
167 let mut errors = 0;
168 for diag in &diagnostics {
169 if diag.severity.is_fatal()
170 || (diag.severity == Severity::Warning && opts.warnings_are_errors)
171 {
172 errors += 1;
173 }
174 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
175 }
176 if errors > 0 {
177 text.clear();
179 }
180 Compiled { text, messages, errors }
181}
182
183#[must_use]
193pub fn compile_ir(opts: &Options, name: &str, fs: &dyn FileSystem) -> Compiled {
194 let mut sess = Session::new(opts.clone());
195 if opts.emit != EmitKind::Ir {
196 return failure(format!(
197 "{name}: an input of IR can only be emitted as IR, and `--emit={}` asks for what \
198 the C in front of it became",
199 opts.emit.as_str()
200 ));
201 }
202 let bytes = match fs.read(Path::new(name)) {
203 Ok(bytes) => bytes,
204 Err(e) => return failure(format!("{name}: {e}")),
205 };
206 let Ok(text) = std::str::from_utf8(bytes.as_slice()) else {
207 return failure(format!("{name}: this is not text, so it is not IR"));
208 };
209
210 let module = match rucc_ir::parse(text, &mut sess.interner) {
211 Ok(module) => module,
212 Err(error) => {
213 return failure(format!("{name}:{}: {}", error.line, error.message));
214 }
215 };
216 let mut diagnostics: Vec<Diagnostic> = Vec::new();
217 if let Err(errors) = rucc_ir::verify(&module, &sess.interner) {
218 for error in errors {
219 diagnostics.push(invalid(&format!("invalid IR, {error}")));
220 }
221 }
222 let mut messages = Vec::with_capacity(diagnostics.len());
223 for diag in &diagnostics {
224 messages.push(render(diag, &sess.sources, opts.warnings_are_errors));
225 }
226 let errors = u32::try_from(messages.len()).unwrap_or(u32::MAX);
227 let text = if errors > 0 { String::new() } else { rucc_ir::print(&module, &sess.interner) };
228 Compiled { text, messages, errors }
229}
230
231fn invalid(message: &str) -> Diagnostic {
233 Diagnostic::error(message.to_owned(), Span::DUMMY).with_code("E0661")
234}
235
236fn internal(message: &str) -> Diagnostic {
238 Diagnostic::error(format!("internal error: {message}"), Span::DUMMY)
239 .with_code("E0652")
240 .note("this is a bug in rucc rather than in the program, please report it", Span::DUMMY)
241}
242
243fn failure(message: String) -> Compiled {
246 Compiled { text: String::new(), messages: vec![format!("rucc: error: {message}")], errors: 1 }
247}
248
249#[cfg(test)]
250mod tests {
251 use rucc_session::{MemoryFileSystem, Std};
252 use rucc_target::Triple;
253
254 use super::*;
255
256 fn options() -> Options {
257 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
258 opts.emit = EmitKind::Tast;
259 opts
260 }
261
262 fn run(opts: &Options, source: &str) -> Compiled {
263 let mut fs = MemoryFileSystem::new();
264 fs.insert("/main.c", source.to_owned().into_bytes());
265 compile(opts, "/main.c", &fs)
266 }
267
268 fn freestanding() -> Options {
272 let mut opts = options();
273 opts.hosted = false;
274 opts.search.push_system(rucc_session::runtime::DIR);
275 opts
276 }
277
278 fn shipped(source: &str) -> String {
280 let result = run(&freestanding(), source);
281 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
282 result.text
283 }
284
285 fn tast(source: &str) -> String {
287 let result = run(&options(), source);
288 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
289 result.text
290 }
291
292 #[test]
293 fn the_shipped_stdarg_declares_a_list_and_the_four_operators() {
294 let text = shipped(concat!(
295 "#include <stdarg.h>\n",
296 "int sum(int n, ...) {\n",
297 " va_list ap, copy;\n",
298 " va_start(ap, n);\n",
299 " va_copy(copy, ap);\n",
300 " int total = va_arg(ap, int) + va_arg(copy, int);\n",
301 " va_end(ap);\n",
302 " va_end(copy);\n",
303 " return total;\n",
304 "}\n",
305 ));
306 assert!(text.contains("va-start"), "{text}");
307 assert!(text.contains("va-copy"), "{text}");
308 assert!(text.contains("va-arg"), "{text}");
309 assert!(text.contains("va-end"), "{text}");
310 }
311
312 #[test]
316 fn stdarg_hands_out_the_type_alone_when_that_is_all_that_was_asked_for() {
317 let text = shipped(concat!(
318 "#define __need___va_list\n",
319 "#include <stdarg.h>\n",
320 "int vprint(const char *f, __gnuc_va_list ap);\n",
321 "#ifdef va_start\n",
322 "#error va_start should not be defined\n",
323 "#endif\n",
324 "#ifdef _VA_LIST_DEFINED\n",
325 "#error va_list should not have been made\n",
326 "#endif\n",
327 ));
328 assert!(text.contains("vprint"), "{text}");
329 }
330
331 #[test]
334 fn stddef_answers_one_piece_at_a_time_and_the_next_request_still_gets_through() {
335 let text = shipped(concat!(
336 "#define __need_size_t\n",
337 "#include <stddef.h>\n",
338 "#ifdef offsetof\n",
339 "#error offsetof should not be defined yet\n",
340 "#endif\n",
341 "#define __need_ptrdiff_t\n",
342 "#include <stddef.h>\n",
343 "#include <stddef.h>\n",
344 "size_t a;\n",
345 "ptrdiff_t b;\n",
346 "wchar_t c;\n",
347 "max_align_t d;\n",
348 "void *e = NULL;\n",
349 "struct P { int x; long y; };\n",
350 "size_t f = offsetof(struct P, y);\n",
351 ));
352 assert!(text.contains("decl #0 a : unsigned long"), "{text}");
353 assert!(text.contains("decl #1 b : long"), "{text}");
354 }
355
356 #[test]
357 fn the_shipped_limits_and_float_are_the_targets_own_answers() {
358 let text = shipped(concat!(
359 "#include <limits.h>\n",
360 "#include <float.h>\n",
361 "int bits = CHAR_BIT;\n",
362 "long big = LONG_MAX;\n",
363 "int low = INT_MIN;\n",
364 "int radix = FLT_RADIX;\n",
365 "int digits = DBL_MANT_DIG;\n",
366 ));
367 assert!(text.contains("const 8 : int"), "{text}");
368 assert!(text.contains("const 9223372036854775807 : long"), "{text}");
369 assert!(text.contains("const 2 : int"), "{text}");
370 assert!(text.contains("const 53 : int"), "{text}");
371 }
372
373 #[test]
377 fn the_shipped_stdint_writes_the_whole_set_when_there_is_no_library_to_defer_to() {
378 let text = shipped(concat!(
379 "#include <stdint.h>\n",
380 "int64_t a = INT64_C(1);\n",
381 "uint_least16_t b;\n",
382 "intptr_t c;\n",
383 "uintmax_t d = UINTMAX_MAX;\n",
384 "int wide = sizeof(int_fast64_t);\n",
385 ));
386 assert!(text.contains("decl #0 a : long"), "{text}");
387 assert!(text.contains("decl #1 b : unsigned short"), "{text}");
388 assert!(text.contains("decl #2 c : long"), "{text}");
389 }
390
391 #[test]
392 fn the_three_formality_headers_still_have_to_work() {
393 let text = shipped(concat!(
394 "#include <stdbool.h>\n",
395 "#include <stdalign.h>\n",
396 "#include <iso646.h>\n",
397 "#include <stdnoreturn.h>\n",
398 "int t = true and not false;\n",
399 "_Alignas(16) char buf[16];\n",
400 "int a = alignof(long);\n",
401 ));
402 assert!(text.contains("decl #0 t : int"), "{text}");
403 assert!(text.contains("const 8 : unsigned long"), "{text}");
404 }
405
406 #[test]
409 fn every_shipped_header_can_be_included_twice() {
410 let mut source = String::new();
411 for _ in 0..2 {
412 for name in rucc_session::runtime::names() {
413 source.push_str(&format!("#include <{name}>\n"));
414 }
415 }
416 source.push_str("int x;\n");
417 let text = shipped(&source);
418 assert!(text.starts_with("decl #0 x : int"), "{text}");
419 }
420
421 #[test]
422 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
423 let fs = MemoryFileSystem::new();
424 let result = compile(&options(), "/nope.c", &fs);
425 assert!(result.failed());
426 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
427 assert!(result.text.is_empty());
428 }
429
430 #[test]
431 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
432 let text = tast("int x = 1;\n");
433 let expected = "\
434decl #0 x : int object external static defined
435 init
436 +0
437 const 1 : int
438";
439 assert_eq!(text, expected);
440 }
441
442 #[test]
443 fn the_macros_are_expanded_before_anything_is_parsed() {
444 let text = tast("#define N 2\nint a[N];\n");
448 assert!(text.starts_with("decl #0 a : int[2] object external static tentative"), "{text}");
449 }
450
451 #[test]
456 fn a_pragma_written_either_way_does_not_reach_the_parser() {
457 let text = tast(concat!(
458 "#pragma pack(4)\n",
459 "struct s { int a; };\n",
460 "#pragma pack()\n",
461 "int b;\n",
462 "_Pragma(\"GCC visibility push(default)\") int c;\n",
463 ));
464 assert!(text.contains("decl #0 b : int"), "{text}");
465 assert!(text.contains("decl #1 c : int"), "{text}");
466 }
467
468 #[test]
472 fn the_wide_integer_answers_to_all_three_of_its_names() {
473 let text = tast("__uint128_t a; __int128_t b; unsigned __int128 c;\n");
474 assert!(text.contains("decl #0 a : unsigned __int128"), "{text}");
475 assert!(text.contains("decl #1 b : __int128"), "{text}");
476 assert!(text.contains("decl #2 c : unsigned __int128"), "{text}");
477 }
478
479 #[test]
480 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
481 let text = tast("long f(int a, long b) { return a + b; }\n");
485 assert!(text.contains("convert arithmetic"), "{text}");
486 }
487
488 #[test]
489 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
490 for source in [
491 "#error stop\n",
492 "int f(void) { return 1 + ; }\n",
493 "int f(void) { return undeclared; }\n",
494 ] {
495 let result = run(&options(), source);
496 assert!(result.failed(), "expected this to fail:\n{source}");
497 assert!(result.text.is_empty(), "a file that did not compile wrote a tree:\n{source}");
498 }
499 }
500
501 #[test]
502 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
503 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
507 assert_eq!(result.errors, 1, "{:?}", result.messages);
508 }
509
510 #[test]
511 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
512 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
516 assert_eq!(result.errors, 1, "{:?}", result.messages);
517 }
518
519 #[test]
520 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
521 let source = "int f(void) { char c = 300; return c; }\n";
522 let plain = run(&options(), source);
523 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
524 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
525 assert!(!plain.text.is_empty(), "a warning is not a reason to write nothing");
526
527 let mut opts = options();
528 opts.warnings_are_errors = true;
529 let strict = run(&opts, source);
530 assert!(strict.failed());
531 assert!(strict.text.is_empty(), "and under -Werror it is a reason to write nothing");
532 for message in &strict.messages {
533 assert!(!message.contains("warning:"), "{message}");
534 }
535 }
536
537 #[test]
538 fn the_dialect_reaches_the_keywords_and_the_checking() {
539 let source = "typeof(1) x;\n";
542 let mut opts = options();
543 opts.std = Std::C23;
544 opts.gnu_extensions = false;
545 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
546
547 opts.std = Std::C17;
548 assert!(run(&opts, source).failed());
549 }
550
551 #[test]
552 fn asking_for_a_kind_that_is_not_written_yet_runs_the_front_end_and_writes_nothing() {
553 let mut opts = options();
554 opts.emit = EmitKind::MirFinal;
555 let result = run(&opts, "int x = 1;\n");
556 assert!(!result.failed(), "{:?}", result.messages);
557 assert!(result.text.is_empty());
558 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
561 }
562
563 fn ir(source: &str) -> String {
565 let mut opts = options();
566 opts.emit = EmitKind::Ir;
567 let result = run(&opts, source);
568 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
569 result.text
570 }
571
572 fn body(source: &str) -> String {
574 let text = ir(source);
575 let (_, rest) = text.split_once("{\n").expect("a function definition");
576 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
577 body.to_owned()
578 }
579
580 #[test]
581 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
582 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
583 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
584 let expected = "\
585func @add(i32, i32) -> i32, linkage(external) {
586block0(%0: i32, %1: i32):
587 %2 = add.nsw %0, %1
588 return %2
589}
590";
591 assert!(text.contains(expected), "{text}");
592 }
593
594 #[test]
595 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
596 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
597 assert!(!text.contains("alloca"), "{text}");
598 assert!(!text.contains("load"), "{text}");
599 assert!(!text.contains("store"), "{text}");
600 }
601
602 #[test]
603 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
604 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
605 let expected = "\
606block0:
607 %0 = alloca, size 4, align 4
608 %1 = iconst.i32 1
609 store %1 -> %0, align 4
610 %2 = call @g(%0) : (ptr) -> i32
611 return %2
612";
613 assert_eq!(text, expected);
614 }
615
616 #[test]
617 fn a_loop_carries_what_it_changes_as_block_parameters() {
618 let text = body(
621 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
622 return total;\n}\n",
623 );
624 assert!(!text.contains("alloca"), "{text}");
625 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
626 assert!(text.contains("jump block1("), "{text}");
627 }
628
629 #[test]
630 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
631 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
632 assert!(text.contains("icmp slt %0, %1"), "{text}");
633 assert!(!text.contains("zext"), "{text}");
634 }
635
636 #[test]
637 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
638 let text = body("int f(int a, int b) { return a && b; }\n");
639 let expected = "\
640block0(%0: i32, %1: i32):
641 %2 = iconst.i32 0
642 %3 = icmp ne %0, %2
643 %4 = iconst.i1 0
644 br_if %3, block1, block2(%4)
645
646block1:
647 %5 = iconst.i32 0
648 %6 = icmp ne %1, %5
649 jump block2(%6)
650
651block2(%7: i1):
652 %8 = zext.i32 %7
653 return %8
654";
655 assert_eq!(text, expected);
656 }
657
658 #[test]
659 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
660 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
661 assert!(!text.contains("block3"), "{text}");
664 assert!(!text.contains("iconst.i32 3"), "{text}");
665 }
666
667 #[test]
668 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
669 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
670 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
671 assert!(body("int f(void) { }\n").contains("unreachable"));
672 }
673
674 #[test]
675 fn a_structure_is_copied_rather_than_held_in_a_value() {
676 let text = body(
677 "struct point { int x, y; };\n\
678 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
679 );
680 assert!(text.contains("memcpy"), "{text}");
681 }
682
683 #[test]
684 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
685 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
686 assert!(text.contains("memset"), "{text}");
687 }
688
689 #[test]
690 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
691 let text = body(
692 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
693 default: r = 4; } return r; }\n",
694 );
695 let expected = "\
696block0(%0: i32):
697 %1 = iconst.i32 0
698 switch %0, block1, [1 => block2, 2 => block3(%1)]
699
700block1:
701 %2 = iconst.i32 4
702 jump block4(%2)
703
704block2:
705 %3 = iconst.i32 1
706 jump block3(%3)
707
708block3(%4: i32):
709 %5 = iconst.i32 2
710 %6 = add.nsw %4, %5
711 jump block4(%6)
712
713block4(%7: i32):
714 return %7
715";
716 assert_eq!(text, expected);
717 }
718
719 #[test]
720 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
721 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
724 assert!(text.contains("%2 = sub %0, %1"), "{text}");
725 assert!(text.contains("icmp ule"), "{text}");
726 assert!(!text.contains("switch"), "{text}");
727 }
728
729 #[test]
730 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
731 let text = body(
732 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
733 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
734 );
735 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
738 assert!(text.contains("block5:\n jump block7("), "{text}");
739 assert!(text.contains("block6:\n jump block8("), "{text}");
740 }
741
742 #[test]
743 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
744 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
745 }
746
747 #[test]
748 fn a_label_control_cannot_fall_into_is_reported_rather_than_dropped() {
749 let mut opts = options();
750 opts.emit = EmitKind::Ir;
751 for source in [
755 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
756 return n; }\n",
757 "int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n",
758 ] {
759 let result = run(&opts, source);
760 assert!(result.failed(), "expected this to be reported:\n{source}");
761 assert!(
762 result.messages.iter().any(|m| m.contains("a label control cannot fall into")),
763 "{:?}",
764 result.messages
765 );
766 }
767 }
768
769 #[test]
770 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
771 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
772 assert!(!text.contains("alloca"), "{text}");
774 assert!(text.contains("block3(%4: i32):\n return %4"), "{text}");
775 assert_eq!(text.matches("jump block3(").count(), 2, "{text}");
776 }
777
778 #[test]
779 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
780 let text =
781 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
782 assert!(!text.contains("alloca"), "{text}");
783 assert!(text.contains("block1(%2: i32):"), "{text}");
784 assert!(text.contains("jump block1(%5)"), "{text}");
785 }
786
787 #[test]
788 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
789 assert_eq!(
792 body("int f(int x) { return x; spare: return 0; }\n"),
793 "block0(%0: i32):\n return %0\n"
794 );
795 }
796
797 #[test]
798 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
799 let text = body(
800 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
801 );
802 assert_eq!(
805 text,
806 "\
807block0(%0: ptr):
808 %1 = load.i8 %0, align 1
809 %2 = iconst.i8 3
810 %3 = ashr %1, %2
811 %4 = sext.i32 %3
812 return %4
813"
814 );
815 }
816
817 #[test]
818 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
819 let text =
823 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
824 assert_eq!(
825 text,
826 "\
827block0(%0: ptr, %1: i32):
828 %2 = iconst.i32 16777215
829 %3 = and %1, %2
830 %4 = trunc.i16 %3
831 store %4 -> %0, align 2
832 %5 = iconst.i32 16
833 %6 = lshr %3, %5
834 %7 = trunc.i8 %6
835 %8 = iconst.i64 2
836 %9 = ptr_add %0, %8
837 store %7 -> %9, align 1
838 return
839"
840 );
841 }
842
843 #[test]
844 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
845 let text =
846 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
847 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
850 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
851 }
852
853 #[test]
854 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
855 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
858 assert_eq!(text.matches("ashr").count(), 0, "{text}");
859 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
860 }
861
862 #[test]
863 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
864 let text = body(
868 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
869 );
870 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
871 }
872
873 #[test]
874 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
875 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
878 assert!(
879 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
880 "{text}"
881 );
882 }
883
884 #[test]
885 fn a_null_pointer_in_an_image_is_the_bits_an_address_has_room_for() {
886 let text = ir("void *p = 0;\nchar *q = (char *) 4096;\n");
889 assert!(text.contains("global @p : i64 = 0, align 8"), "{text}");
890 assert!(text.contains("global @q : i64 = 4096, align 8"), "{text}");
891 }
892
893 #[test]
894 fn an_object_another_module_defines_may_be_one_that_cannot_be_written_through() {
895 let text = ir("extern const int limit;\nint f(void) { return limit; }\n");
899 assert!(
900 text.contains("global @limit : bytes 4, align 4, linkage(external), constant"),
901 "{text}"
902 );
903 }
904
905 #[test]
906 fn a_conditional_whose_value_is_an_object_answers_where_the_object_is() {
907 let text = body(
912 "\
913struct s { int a, b; };
914struct s pick(int c, struct s x, struct s y) { return c ? x : y; }
915",
916 );
917 assert!(text.contains("block3(%7: ptr)"), "{text}");
919 assert!(text.contains("jump block3(%3)") && text.contains("jump block3(%4)"), "{text}");
920 assert!(!text.contains("memcpy"), "the arms are joined rather than copied: {text}");
921 }
922
923 #[test]
924 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
925 let text = ir("\
929struct pair { int a, b; };
930struct pair make(int a, int b);
931struct pair twice(struct pair p) { return make(p.a, p.b); }
932");
933 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
934 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
935 }
936
937 #[test]
938 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
939 let text = ir("\
943struct big { double v[8]; };
944struct big grow(struct big b);
945struct big twice(struct big b) { return grow(grow(b)); }
946");
947 assert!(
948 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
949 "{text}"
950 );
951 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
952 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
955 }
956
957 #[test]
958 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
959 let body = body(
962 "\
963struct pair { int a, b; };
964struct pair make(int a, int b);
965int second(void) { return make(1, 2).b; }
966",
967 );
968 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
969 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
970 }
971
972 #[test]
973 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
974 let source = "\
978struct hfa { float x, y, z; };
979int take(struct hfa h);
980int give(struct hfa h) { return take(h); }
981";
982 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
983 let mut opts = options();
984 opts.emit = EmitKind::Ir;
985 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
986 let result = run(&opts, source);
987 assert_eq!(result.messages, Vec::<String>::new());
988 assert!(result.text.contains("func @take(f32, f32, f32) -> i32"), "{}", result.text);
989 }
990
991 #[test]
992 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
993 let source = "\
996int use(int *);
997void f(int n) {
998 {
999 int a[n];
1000 use(a);
1001 }
1002 use(0);
1003}
1004";
1005 let body = body(source);
1006 assert!(body.contains("mul.nsw"), "{body}");
1007 assert!(body.contains("stacksave"), "{body}");
1008 assert!(body.contains("alloca %"), "{body}");
1009 assert!(body.contains("stackrestore"), "{body}");
1010 }
1011
1012 #[test]
1013 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
1014 let source = "\
1017unsigned long f(int n) {
1018 int a[n];
1019 n = 0;
1020 return sizeof a;
1021}
1022";
1023 let body = body(source);
1024 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
1026 }
1027
1028 #[test]
1029 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
1030 let source = "\
1033int use(int);
1034int f(int x) {
1035 return ({
1036 int t = use(x);
1037 t * t;
1038 });
1039}
1040";
1041 let expected = "\
1042block0(%0: i32):
1043 %1 = call @use(%0) : (i32) -> i32
1044 %2 = mul.nsw %1, %1
1045 return %2
1046";
1047 assert_eq!(body(source), expected);
1048 }
1049
1050 #[test]
1051 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
1052 let source = "int f(int x) { return ({ return x; 0; }); }\n";
1056 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
1057 }
1058
1059 #[test]
1060 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
1061 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
1065 let expected = "\
1066block0(%0: ptr):
1067 %1 = va_arg.f64 %0
1068 %2 = va_arg.f64 %0
1069 %3 = fadd %1, %2
1070 return %3
1071";
1072 assert_eq!(body(source), expected);
1073 }
1074
1075 #[test]
1076 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
1077 let source = "\
1081int f(int c) {
1082 void *p = c ? &&one : &&two;
1083 goto *p;
1084one:
1085 return 1;
1086two:
1087 return 2;
1088}
1089";
1090 let expected = "\
1091block0(%0: i32):
1092 %1 = iconst.i32 0
1093 %2 = icmp ne %0, %1
1094 br_if %2, block1, block2
1095
1096block1:
1097 %3 = block_addr block3
1098 jump block4(%3)
1099
1100block2:
1101 %4 = block_addr block5
1102 jump block4(%4)
1103
1104block3:
1105 %5 = iconst.i32 1
1106 return %5
1107
1108block4(%6: ptr):
1109 indirect_br %6, block3, block5
1110
1111block5:
1112 %7 = iconst.i32 2
1113 return %7
1114";
1115 assert_eq!(body(source), expected);
1116 }
1117
1118 #[test]
1119 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
1120 let source = "void **next(void);
1123void f(void) { goto *next(); }
1124";
1125 let expected = "\
1126block0:
1127 %0 = call @next() : () -> ptr
1128 unreachable
1129";
1130 assert_eq!(body(source), expected);
1131 }
1132
1133 #[test]
1134 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
1135 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
1138 let expected = "\
1139block0:
1140 inline_asm.volatile \"mfence\", \"\", \"memory\"()
1141 return
1142";
1143 assert_eq!(body(source), expected);
1144 }
1145
1146 #[test]
1147 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
1148 let source = "\
1151int f(int x, int y) {
1152 int r;
1153 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
1154 return r + y;
1155}
1156";
1157 let expected = "\
1158block0(%0: i32, %1: i32):
1159 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
1160 %4 = add.nsw %2, %3
1161 return %4
1162";
1163 assert_eq!(body(source), expected);
1164 }
1165
1166 #[test]
1167 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
1168 let source = "\
1173struct pair { int a, b; };
1174int f(int x) {
1175 int slot = x;
1176 struct pair p = { x, x };
1177 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
1178 return slot + p.a;
1179}
1180";
1181 let text = body(source);
1182 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
1183 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
1184 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
1185 }
1186
1187 #[test]
1188 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
1189 let source = "\
1194int f(int x) {
1195 int r = 7;
1196 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
1197 return r;
1198away:
1199 return r;
1200}
1201";
1202 let expected = "\
1203block0(%0: i32):
1204 %1 = iconst.i32 7
1205 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
1206
1207block1:
1208 return %2
1209
1210block2:
1211 return %1
1212";
1213 assert_eq!(body(source), expected);
1214 }
1215
1216 #[test]
1217 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
1218 let mut opts = options();
1222 opts.emit = EmitKind::Ir;
1223 for (source, expected) in [
1224 (
1225 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
1226 "output operand constraint lacks '='",
1227 ),
1228 (
1229 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
1230 "lvalue required in 'asm' statement",
1231 ),
1232 (
1233 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
1234 "read-only variable 'g' used as 'asm' output",
1235 ),
1236 (
1237 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
1238 "input operand constraint contains '='",
1239 ),
1240 (
1241 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
1242 "memory input 0 is not directly addressable",
1243 ),
1244 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
1245 (
1246 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
1247 "duplicate asm operand name 'a'",
1248 ),
1249 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
1250 ] {
1251 let result = run(&opts, source);
1252 assert!(result.failed(), "expected this to be reported:\n{source}");
1253 assert!(
1254 result.messages.iter().any(|m| m.contains(expected)),
1255 "{expected}\n{:?}",
1256 result.messages
1257 );
1258 }
1259 }
1260
1261 #[test]
1262 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
1263 let mut opts = options();
1264 opts.emit = EmitKind::Ir;
1265 for source in [
1266 "int f(int n) { int a[n]; goto out; out: return a[0]; }\n",
1267 "int f(int n) { int a[n]; void *p = &&out; goto *p; out: return a[0]; }\n",
1268 "struct s { double a[8]; };\nint p(const char *, ...);\nint g(struct s v) { return p(\"\", v); }\n",
1269 "struct s { int a; };\nstruct s f(__builtin_va_list ap) { return __builtin_va_arg(ap, struct s); }\n",
1270 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
1271 ] {
1272 let result = run(&opts, source);
1273 assert!(result.failed(), "expected this to be reported:\n{source}");
1274 assert!(
1275 result.messages.iter().any(|m| m.contains("not supported yet")),
1276 "{:?}",
1277 result.messages
1278 );
1279 }
1280 }
1281
1282 fn round_trip(source: &str) -> (String, String) {
1284 let printed = ir(source);
1285 let mut opts = options();
1286 opts.emit = EmitKind::Ir;
1287 let mut fs = MemoryFileSystem::new();
1288 fs.insert("/main.ir", printed.clone().into_bytes());
1289 let result = compile_ir(&opts, "/main.ir", &fs);
1290 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
1291 (printed, result.text)
1292 }
1293
1294 #[test]
1295 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
1296 let (printed, again) = round_trip(
1300 "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",
1301 );
1302 assert_eq!(printed, again);
1303 }
1304
1305 #[test]
1306 fn ir_that_is_not_ir_says_which_line_stopped_it() {
1307 let mut opts = options();
1308 opts.emit = EmitKind::Ir;
1309 let mut fs = MemoryFileSystem::new();
1310 let text = "\
1311; ModuleID = 'a.c'
1312; format 0
1313target triple = \"x86_64-unknown-linux-gnu\"
1314target datalayout = \"e-p:64:64-i64:64-S128\"
1315
1316func @f(), linkage(external) {
1317block0:
1318 frobnicate
1319}
1320";
1321 fs.insert("/main.ir", text.as_bytes().to_vec());
1322 let result = compile_ir(&opts, "/main.ir", &fs);
1323 assert!(result.failed());
1324 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
1325 }
1326
1327 #[test]
1328 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
1329 let mut opts = options();
1332 opts.emit = EmitKind::Ir;
1333 let mut fs = MemoryFileSystem::new();
1334 let text = "\
1335; ModuleID = 'a.c'
1336; format 0
1337target triple = \"x86_64-unknown-linux-gnu\"
1338target datalayout = \"e-p:64:64-i64:64-S128\"
1339
1340func @f(), linkage(external) {
1341block0:
1342 %0 = iconst.i32 1
1343 return %0
1344}
1345";
1346 fs.insert("/main.ir", text.as_bytes().to_vec());
1347 let result = compile_ir(&opts, "/main.ir", &fs);
1348 assert!(result.failed());
1349 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
1350 }
1351
1352 #[test]
1353 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
1354 let mut fs = MemoryFileSystem::new();
1356 fs.insert("/main.ir", Vec::new());
1357 let result = compile_ir(&options(), "/main.ir", &fs);
1358 assert!(result.failed());
1359 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
1360 }
1361
1362 #[test]
1363 fn the_printed_ir_reads_back_as_the_same_module() {
1364 let text = ir("\
1367struct point { int x, y; };
1368static const char greeting[] = \"hi\";
1369int table[4] = { 1, 2, 3 };
1370int puts(const char *);
1371double half(double x) { return x / 2.0; }
1372int f(int n) {
1373 int total = 0;
1374 for (int i = 0; i < n; i++) {
1375 if (i == 3) continue;
1376 total += table[i];
1377 }
1378 switch (n) {
1379 case 0: total = 1;
1380 case 1: total++; break;
1381 default: total = -total;
1382 }
1383 struct point p = { total, 1 };
1384 int *q = &p.y;
1385 puts(greeting);
1386 return p.x + *q;
1387}
1388int dispatch(int c) {
1389 void *p = c ? &&one : &&two;
1390 goto *p;
1391one:
1392 return 1;
1393two:
1394 return 2;
1395}
1396int assembly(int x, int *p) {
1397 int r;
1398 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
1399 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
1400 return r;
1401away:
1402 return 0;
1403}
1404");
1405 let mut names = rucc_base::Interner::new();
1406 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
1407 assert_eq!(rucc_ir::print(&module, &names), text);
1408 }
1409}