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