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 tast(source: &str) -> String {
268 let result = run(&options(), source);
269 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
270 result.text
271 }
272
273 #[test]
274 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
275 let fs = MemoryFileSystem::new();
276 let result = compile(&options(), "/nope.c", &fs);
277 assert!(result.failed());
278 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
279 assert!(result.text.is_empty());
280 }
281
282 #[test]
283 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
284 let text = tast("int x = 1;\n");
285 let expected = "\
286decl #0 x : int object external static defined
287 init
288 +0
289 const 1 : int
290";
291 assert_eq!(text, expected);
292 }
293
294 #[test]
295 fn the_macros_are_expanded_before_anything_is_parsed() {
296 let text = tast("#define N 2\nint a[N];\n");
300 assert!(text.starts_with("decl #0 a : int [2] object external static tentative"), "{text}");
301 }
302
303 #[test]
304 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
305 let text = tast("long f(int a, long b) { return a + b; }\n");
309 assert!(text.contains("convert arithmetic"), "{text}");
310 }
311
312 #[test]
313 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
314 for source in [
315 "#error stop\n",
316 "int f(void) { return 1 + ; }\n",
317 "int f(void) { return undeclared; }\n",
318 ] {
319 let result = run(&options(), source);
320 assert!(result.failed(), "expected this to fail:\n{source}");
321 assert!(result.text.is_empty(), "a file that did not compile wrote a tree:\n{source}");
322 }
323 }
324
325 #[test]
326 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
327 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
331 assert_eq!(result.errors, 1, "{:?}", result.messages);
332 }
333
334 #[test]
335 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
336 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
340 assert_eq!(result.errors, 1, "{:?}", result.messages);
341 }
342
343 #[test]
344 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
345 let source = "int f(void) { char c = 300; return c; }\n";
346 let plain = run(&options(), source);
347 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
348 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
349 assert!(!plain.text.is_empty(), "a warning is not a reason to write nothing");
350
351 let mut opts = options();
352 opts.warnings_are_errors = true;
353 let strict = run(&opts, source);
354 assert!(strict.failed());
355 assert!(strict.text.is_empty(), "and under -Werror it is a reason to write nothing");
356 for message in &strict.messages {
357 assert!(!message.contains("warning:"), "{message}");
358 }
359 }
360
361 #[test]
362 fn the_dialect_reaches_the_keywords_and_the_checking() {
363 let source = "typeof(1) x;\n";
366 let mut opts = options();
367 opts.std = Std::C23;
368 opts.gnu_extensions = false;
369 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
370
371 opts.std = Std::C17;
372 assert!(run(&opts, source).failed());
373 }
374
375 #[test]
376 fn asking_for_a_kind_that_is_not_written_yet_runs_the_front_end_and_writes_nothing() {
377 let mut opts = options();
378 opts.emit = EmitKind::MirFinal;
379 let result = run(&opts, "int x = 1;\n");
380 assert!(!result.failed(), "{:?}", result.messages);
381 assert!(result.text.is_empty());
382 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
385 }
386
387 fn ir(source: &str) -> String {
389 let mut opts = options();
390 opts.emit = EmitKind::Ir;
391 let result = run(&opts, source);
392 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
393 result.text
394 }
395
396 fn body(source: &str) -> String {
398 let text = ir(source);
399 let (_, rest) = text.split_once("{\n").expect("a function definition");
400 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
401 body.to_owned()
402 }
403
404 #[test]
405 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
406 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
407 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
408 let expected = "\
409func @add(i32, i32) -> i32, linkage(external) {
410block0(%0: i32, %1: i32):
411 %2 = add.nsw %0, %1
412 return %2
413}
414";
415 assert!(text.contains(expected), "{text}");
416 }
417
418 #[test]
419 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
420 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
421 assert!(!text.contains("alloca"), "{text}");
422 assert!(!text.contains("load"), "{text}");
423 assert!(!text.contains("store"), "{text}");
424 }
425
426 #[test]
427 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
428 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
429 let expected = "\
430block0:
431 %0 = alloca, size 4, align 4
432 %1 = iconst.i32 1
433 store %1 -> %0, align 4
434 %2 = call @g(%0) : (ptr) -> i32
435 return %2
436";
437 assert_eq!(text, expected);
438 }
439
440 #[test]
441 fn a_loop_carries_what_it_changes_as_block_parameters() {
442 let text = body(
445 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
446 return total;\n}\n",
447 );
448 assert!(!text.contains("alloca"), "{text}");
449 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
450 assert!(text.contains("jump block1("), "{text}");
451 }
452
453 #[test]
454 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
455 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
456 assert!(text.contains("icmp slt %0, %1"), "{text}");
457 assert!(!text.contains("zext"), "{text}");
458 }
459
460 #[test]
461 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
462 let text = body("int f(int a, int b) { return a && b; }\n");
463 let expected = "\
464block0(%0: i32, %1: i32):
465 %2 = iconst.i32 0
466 %3 = icmp ne %0, %2
467 %4 = iconst.i1 0
468 br_if %3, block1, block2(%4)
469
470block1:
471 %5 = iconst.i32 0
472 %6 = icmp ne %1, %5
473 jump block2(%6)
474
475block2(%7: i1):
476 %8 = zext.i32 %7
477 return %8
478";
479 assert_eq!(text, expected);
480 }
481
482 #[test]
483 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
484 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
485 assert!(!text.contains("block3"), "{text}");
488 assert!(!text.contains("iconst.i32 3"), "{text}");
489 }
490
491 #[test]
492 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
493 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
494 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
495 assert!(body("int f(void) { }\n").contains("unreachable"));
496 }
497
498 #[test]
499 fn a_structure_is_copied_rather_than_held_in_a_value() {
500 let text = body(
501 "struct point { int x, y; };\n\
502 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
503 );
504 assert!(text.contains("memcpy"), "{text}");
505 }
506
507 #[test]
508 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
509 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
510 assert!(text.contains("memset"), "{text}");
511 }
512
513 #[test]
514 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
515 let text = body(
516 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
517 default: r = 4; } return r; }\n",
518 );
519 let expected = "\
520block0(%0: i32):
521 %1 = iconst.i32 0
522 switch %0, block1, [1 => block2, 2 => block3(%1)]
523
524block1:
525 %2 = iconst.i32 4
526 jump block4(%2)
527
528block2:
529 %3 = iconst.i32 1
530 jump block3(%3)
531
532block3(%4: i32):
533 %5 = iconst.i32 2
534 %6 = add.nsw %4, %5
535 jump block4(%6)
536
537block4(%7: i32):
538 return %7
539";
540 assert_eq!(text, expected);
541 }
542
543 #[test]
544 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
545 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
548 assert!(text.contains("%2 = sub %0, %1"), "{text}");
549 assert!(text.contains("icmp ule"), "{text}");
550 assert!(!text.contains("switch"), "{text}");
551 }
552
553 #[test]
554 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
555 let text = body(
556 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
557 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
558 );
559 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
562 assert!(text.contains("block5:\n jump block7("), "{text}");
563 assert!(text.contains("block6:\n jump block8("), "{text}");
564 }
565
566 #[test]
567 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
568 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
569 }
570
571 #[test]
572 fn a_label_control_cannot_fall_into_is_reported_rather_than_dropped() {
573 let mut opts = options();
574 opts.emit = EmitKind::Ir;
575 for source in [
579 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
580 return n; }\n",
581 "int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n",
582 ] {
583 let result = run(&opts, source);
584 assert!(result.failed(), "expected this to be reported:\n{source}");
585 assert!(
586 result.messages.iter().any(|m| m.contains("a label control cannot fall into")),
587 "{:?}",
588 result.messages
589 );
590 }
591 }
592
593 #[test]
594 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
595 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
596 assert!(!text.contains("alloca"), "{text}");
598 assert!(text.contains("block3(%4: i32):\n return %4"), "{text}");
599 assert_eq!(text.matches("jump block3(").count(), 2, "{text}");
600 }
601
602 #[test]
603 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
604 let text =
605 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
606 assert!(!text.contains("alloca"), "{text}");
607 assert!(text.contains("block1(%2: i32):"), "{text}");
608 assert!(text.contains("jump block1(%5)"), "{text}");
609 }
610
611 #[test]
612 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
613 assert_eq!(
616 body("int f(int x) { return x; spare: return 0; }\n"),
617 "block0(%0: i32):\n return %0\n"
618 );
619 }
620
621 #[test]
622 fn a_bit_field_is_read_by_loading_the_bytes_it_lies_in_and_shifting() {
623 let text = body(
624 "struct s { unsigned a : 3; signed b : 5; };\nint f(struct s *p) { return p->b; }\n",
625 );
626 assert_eq!(
629 text,
630 "\
631block0(%0: ptr):
632 %1 = load.i8 %0, align 1
633 %2 = iconst.i8 3
634 %3 = ashr %1, %2
635 %4 = sext.i32 %3
636 return %4
637"
638 );
639 }
640
641 #[test]
642 fn a_store_to_a_bit_field_does_not_write_a_byte_it_has_no_bit_in() {
643 let text =
647 body("struct s { int a : 24; char c; };\nvoid f(struct s *p, int v) { p->a = v; }\n");
648 assert_eq!(
649 text,
650 "\
651block0(%0: ptr, %1: i32):
652 %2 = iconst.i32 16777215
653 %3 = and %1, %2
654 %4 = trunc.i16 %3
655 store %4 -> %0, align 2
656 %5 = iconst.i32 16
657 %6 = lshr %3, %5
658 %7 = trunc.i8 %6
659 %8 = iconst.i64 2
660 %9 = ptr_add %0, %8
661 store %7 -> %9, align 1
662 return
663"
664 );
665 }
666
667 #[test]
668 fn what_an_assignment_to_a_bit_field_is_worth_is_what_fits_in_it() {
669 let text =
670 body("struct s { unsigned b : 5; };\nunsigned f(struct s *p) { return p->b = 33; }\n");
671 assert!(text.contains("%3 = iconst.i8 31\n %4 = and %2, %3"), "{text}");
674 assert!(text.ends_with("%9 = zext.i32 %4\n return %9\n"), "{text}");
675 }
676
677 #[test]
678 fn an_assignment_a_statement_throws_away_builds_none_of_what_it_is_worth() {
679 let text = body("struct s { signed b : 5; };\nvoid f(struct s *p) { p->b = 3; }\n");
682 assert_eq!(text.matches("ashr").count(), 0, "{text}");
683 assert!(text.ends_with("store %8 -> %0, align 1\n return\n"), "{text}");
684 }
685
686 #[test]
687 fn a_bit_field_in_an_initializer_goes_in_over_bytes_that_were_zeroed_first() {
688 let text = body(
692 "struct s { int a : 3; int b; };\nint f(void) { struct s v = { 1 }; return v.b; }\n",
693 );
694 assert!(text.contains("memset %0, %1, size 8, align 4"), "{text}");
695 }
696
697 #[test]
698 fn the_image_of_a_static_bit_field_is_the_bytes_the_fields_share() {
699 let text = ir("struct s { unsigned a : 3; unsigned b : 5; } g = { 1, 2 };\n");
702 assert!(
703 text.contains("global @g : bytes 4 = { bytes \"\\11\", zero 3 }, align 4"),
704 "{text}"
705 );
706 }
707
708 #[test]
709 fn a_structure_that_fits_in_registers_travels_as_the_registers_it_fits_in() {
710 let text = ir("\
714struct pair { int a, b; };
715struct pair make(int a, int b);
716struct pair twice(struct pair p) { return make(p.a, p.b); }
717");
718 assert!(text.contains("func @make(i32, i32) -> i64"), "{text}");
719 assert!(text.contains("func @twice(i64) -> i64"), "{text}");
720 }
721
722 #[test]
723 fn a_structure_too_large_for_the_registers_travels_as_where_its_bytes_are() {
724 let text = ir("\
728struct big { double v[8]; };
729struct big grow(struct big b);
730struct big twice(struct big b) { return grow(grow(b)); }
731");
732 assert!(
733 text.contains("func @grow(ptr sret(64, align 8), ptr byval(64, align 8))"),
734 "{text}"
735 );
736 assert!(text.contains("block0(%0: ptr, %1: ptr):"), "{text}");
737 assert_eq!(text.matches("call @grow").count(), 2, "{text}");
740 }
741
742 #[test]
743 fn what_a_call_produced_is_somewhere_before_anything_is_read_out_of_it() {
744 let body = body(
747 "\
748struct pair { int a, b; };
749struct pair make(int a, int b);
750int second(void) { return make(1, 2).b; }
751",
752 );
753 assert!(body.starts_with("block0:\n %0 = alloca, size 8, align 4\n"), "{body}");
754 assert!(body.contains("store %3 -> %0, align 4\n"), "{body}");
755 }
756
757 #[test]
758 fn a_structure_of_floats_travels_in_floating_point_registers_on_aarch64() {
759 let source = "\
763struct hfa { float x, y, z; };
764int take(struct hfa h);
765int give(struct hfa h) { return take(h); }
766";
767 assert!(ir(source).contains("func @take(f64, f32) -> i32"), "{}", ir(source));
768 let mut opts = options();
769 opts.emit = EmitKind::Ir;
770 opts.target = "aarch64-unknown-linux-gnu".parse::<Triple>().unwrap();
771 let result = run(&opts, source);
772 assert_eq!(result.messages, Vec::<String>::new());
773 assert!(result.text.contains("func @take(f32, f32, f32) -> i32"), "{}", result.text);
774 }
775
776 #[test]
777 fn an_array_whose_length_is_not_a_constant_is_a_slot_made_where_its_declaration_is() {
778 let source = "\
781int use(int *);
782void f(int n) {
783 {
784 int a[n];
785 use(a);
786 }
787 use(0);
788}
789";
790 let body = body(source);
791 assert!(body.contains("mul.nsw"), "{body}");
792 assert!(body.contains("stacksave"), "{body}");
793 assert!(body.contains("alloca %"), "{body}");
794 assert!(body.contains("stackrestore"), "{body}");
795 }
796
797 #[test]
798 fn how_long_one_of_those_is_was_decided_where_it_was_declared_and_not_where_it_is_asked() {
799 let source = "\
802unsigned long f(int n) {
803 int a[n];
804 n = 0;
805 return sizeof a;
806}
807";
808 let body = body(source);
809 assert_eq!(body.matches("sext.i64 %0").count(), 2, "{body}");
811 }
812
813 #[test]
814 fn a_block_in_the_middle_of_an_expression_is_walked_where_the_expression_is() {
815 let source = "\
818int use(int);
819int f(int x) {
820 return ({
821 int t = use(x);
822 t * t;
823 });
824}
825";
826 let expected = "\
827block0(%0: i32):
828 %1 = call @use(%0) : (i32) -> i32
829 %2 = mul.nsw %1, %1
830 return %2
831";
832 assert_eq!(body(source), expected);
833 }
834
835 #[test]
836 fn one_of_those_that_control_never_leaves_is_lowered_and_what_follows_it_is_dropped() {
837 let source = "int f(int x) { return ({ return x; 0; }); }\n";
841 assert_eq!(body(source), "block0(%0: i32):\n return %0\n");
842 }
843
844 #[test]
845 fn one_argument_off_a_variable_argument_list_stays_an_intrinsic() {
846 let source = "double f(__builtin_va_list ap) { return __builtin_va_arg(ap, double) + __builtin_va_arg(ap, double); }\n";
850 let expected = "\
851block0(%0: ptr):
852 %1 = va_arg.f64 %0
853 %2 = va_arg.f64 %0
854 %3 = fadd %1, %2
855 return %3
856";
857 assert_eq!(body(source), expected);
858 }
859
860 #[test]
861 fn a_jump_to_an_address_branches_to_every_label_the_function_takes_the_address_of() {
862 let source = "\
866int f(int c) {
867 void *p = c ? &&one : &&two;
868 goto *p;
869one:
870 return 1;
871two:
872 return 2;
873}
874";
875 let expected = "\
876block0(%0: i32):
877 %1 = iconst.i32 0
878 %2 = icmp ne %0, %1
879 br_if %2, block1, block2
880
881block1:
882 %3 = block_addr block3
883 jump block4(%3)
884
885block2:
886 %4 = block_addr block5
887 jump block4(%4)
888
889block3:
890 %5 = iconst.i32 1
891 return %5
892
893block4(%6: ptr):
894 indirect_br %6, block3, block5
895
896block5:
897 %7 = iconst.i32 2
898 return %7
899";
900 assert_eq!(body(source), expected);
901 }
902
903 #[test]
904 fn a_jump_to_an_address_no_label_in_the_function_has_arrives_nowhere() {
905 let source = "void **next(void);
908void f(void) { goto *next(); }
909";
910 let expected = "\
911block0:
912 %0 = call @next() : () -> ptr
913 unreachable
914";
915 assert_eq!(body(source), expected);
916 }
917
918 #[test]
919 fn an_asm_with_no_operands_is_volatile_and_the_clobbers_are_the_whole_of_what_it_says() {
920 let source = "void f(void) { __asm__(\"mfence\" ::: \"memory\"); }\n";
923 let expected = "\
924block0:
925 inline_asm.volatile \"mfence\", \"\", \"memory\"()
926 return
927";
928 assert_eq!(body(source), expected);
929 }
930
931 #[test]
932 fn the_constraints_are_one_list_in_the_order_the_template_counts_the_operands() {
933 let source = "\
936int f(int x, int y) {
937 int r;
938 __asm__(\"addl %2, %0\" : \"=r\"(r), \"+r\"(y) : \"r\"(x));
939 return r + y;
940}
941";
942 let expected = "\
943block0(%0: i32, %1: i32):
944 %2, %3 = inline_asm.(i32, i32) \"addl %2, %0\", \"=r,+r,r\", \"\"(%1, %0)
945 %4 = add.nsw %2, %3
946 return %4
947";
948 assert_eq!(body(source), expected);
949 }
950
951 #[test]
952 fn a_memory_operand_travels_as_the_address_of_an_object_that_is_given_a_slot() {
953 let source = "\
958struct pair { int a, b; };
959int f(int x) {
960 int slot = x;
961 struct pair p = { x, x };
962 __asm__(\"incl %0\" : \"+m\"(slot), \"=m\"(p));
963 return slot + p.a;
964}
965";
966 let text = body(source);
967 assert!(text.contains("inline_asm \"incl %0\", \"+m,=m\", \"\"(%1, %2)\n"), "{text}");
968 assert!(text.contains("%1 = alloca, size 4, align 4\n"), "{text}");
969 assert!(text.contains("%2 = alloca, size 8, align 4\n"), "{text}");
970 }
971
972 #[test]
973 fn an_asm_goto_falls_through_to_its_first_target_and_writes_its_outputs_there() {
974 let source = "\
979int f(int x) {
980 int r = 7;
981 __asm__ goto(\"cbnz %0, %l1\" : \"=r\"(r) : \"r\"(x) :: away);
982 return r;
983away:
984 return r;
985}
986";
987 let expected = "\
988block0(%0: i32):
989 %1 = iconst.i32 7
990 %2 = inline_asm.volatile \"cbnz %0, %l1\", \"=r,r\", \"\"(%0), labels [block1, block2]
991
992block1:
993 return %2
994
995block2:
996 return %1
997";
998 assert_eq!(body(source), expected);
999 }
1000
1001 #[test]
1002 fn an_asm_statement_that_is_not_well_formed_is_reported_in_the_words_gcc_uses() {
1003 let mut opts = options();
1007 opts.emit = EmitKind::Ir;
1008 for (source, expected) in [
1009 (
1010 "void f(int x) { __asm__(\"\" : \"r\"(x)); }\n",
1011 "output operand constraint lacks '='",
1012 ),
1013 (
1014 "void f(int x) { __asm__(\"\" : \"=r\"(x + 1)); }\n",
1015 "lvalue required in 'asm' statement",
1016 ),
1017 (
1018 "const int g = 1;\nvoid f(void) { __asm__(\"\" : \"=r\"(g)); }\n",
1019 "read-only variable 'g' used as 'asm' output",
1020 ),
1021 (
1022 "void f(int x) { __asm__(\"\" : : \"=r\"(x)); }\n",
1023 "input operand constraint contains '='",
1024 ),
1025 (
1026 "void f(void) { __asm__(\"\" : : \"m\"(1)); }\n",
1027 "memory input 0 is not directly addressable",
1028 ),
1029 ("void f(void) { __asm__(L\"\"); }\n", "wide string literal in 'asm'"),
1030 (
1031 "void f(int x, int y) { __asm__(\"\" : [a] \"=r\"(x) : [a] \"r\"(y)); }\n",
1032 "duplicate asm operand name 'a'",
1033 ),
1034 ("void f(int x) { __asm__(\"%[in]\" : \"=r\"(x)); }\n", "undefined named operand 'in'"),
1035 ] {
1036 let result = run(&opts, source);
1037 assert!(result.failed(), "expected this to be reported:\n{source}");
1038 assert!(
1039 result.messages.iter().any(|m| m.contains(expected)),
1040 "{expected}\n{:?}",
1041 result.messages
1042 );
1043 }
1044 }
1045
1046 #[test]
1047 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
1048 let mut opts = options();
1049 opts.emit = EmitKind::Ir;
1050 for source in [
1051 "int f(int n) { int a[n]; goto out; out: return a[0]; }\n",
1052 "int f(int n) { int a[n]; void *p = &&out; goto *p; out: return a[0]; }\n",
1053 "struct s { double a[8]; };\nint p(const char *, ...);\nint g(struct s v) { return p(\"\", v); }\n",
1054 "struct s { int a; };\nstruct s f(__builtin_va_list ap) { return __builtin_va_arg(ap, struct s); }\n",
1055 "int f(int n) { int a[n]; __asm__ goto(\"\" ::::out); out: return a[0]; }\n",
1056 ] {
1057 let result = run(&opts, source);
1058 assert!(result.failed(), "expected this to be reported:\n{source}");
1059 assert!(
1060 result.messages.iter().any(|m| m.contains("not supported yet")),
1061 "{:?}",
1062 result.messages
1063 );
1064 }
1065 }
1066
1067 fn round_trip(source: &str) -> (String, String) {
1069 let printed = ir(source);
1070 let mut opts = options();
1071 opts.emit = EmitKind::Ir;
1072 let mut fs = MemoryFileSystem::new();
1073 fs.insert("/main.ir", printed.clone().into_bytes());
1074 let result = compile_ir(&opts, "/main.ir", &fs);
1075 assert_eq!(result.messages, Vec::<String>::new(), "expected this to read back:\n{printed}");
1076 (printed, result.text)
1077 }
1078
1079 #[test]
1080 fn ir_that_arrives_as_an_input_is_read_back_and_written_out_the_same() {
1081 let (printed, again) = round_trip(
1085 "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",
1086 );
1087 assert_eq!(printed, again);
1088 }
1089
1090 #[test]
1091 fn ir_that_is_not_ir_says_which_line_stopped_it() {
1092 let mut opts = options();
1093 opts.emit = EmitKind::Ir;
1094 let mut fs = MemoryFileSystem::new();
1095 let text = "\
1096; ModuleID = 'a.c'
1097; format 0
1098target triple = \"x86_64-unknown-linux-gnu\"
1099target datalayout = \"e-p:64:64-i64:64-S128\"
1100
1101func @f(), linkage(external) {
1102block0:
1103 frobnicate
1104}
1105";
1106 fs.insert("/main.ir", text.as_bytes().to_vec());
1107 let result = compile_ir(&opts, "/main.ir", &fs);
1108 assert!(result.failed());
1109 assert!(result.messages[0].contains("/main.ir:8"), "{:?}", result.messages);
1110 }
1111
1112 #[test]
1113 fn ir_that_reads_but_does_not_hold_together_is_reported_by_the_verifier() {
1114 let mut opts = options();
1117 opts.emit = EmitKind::Ir;
1118 let mut fs = MemoryFileSystem::new();
1119 let text = "\
1120; ModuleID = 'a.c'
1121; format 0
1122target triple = \"x86_64-unknown-linux-gnu\"
1123target datalayout = \"e-p:64:64-i64:64-S128\"
1124
1125func @f(), linkage(external) {
1126block0:
1127 %0 = iconst.i32 1
1128 return %0
1129}
1130";
1131 fs.insert("/main.ir", text.as_bytes().to_vec());
1132 let result = compile_ir(&opts, "/main.ir", &fs);
1133 assert!(result.failed());
1134 assert!(result.messages[0].contains("invalid IR"), "{:?}", result.messages);
1135 }
1136
1137 #[test]
1138 fn a_typed_tree_is_not_something_an_input_of_ir_can_produce() {
1139 let mut fs = MemoryFileSystem::new();
1141 fs.insert("/main.ir", Vec::new());
1142 let result = compile_ir(&options(), "/main.ir", &fs);
1143 assert!(result.failed());
1144 assert!(result.messages[0].contains("can only be emitted as IR"), "{:?}", result.messages);
1145 }
1146
1147 #[test]
1148 fn the_printed_ir_reads_back_as_the_same_module() {
1149 let text = ir("\
1152struct point { int x, y; };
1153static const char greeting[] = \"hi\";
1154int table[4] = { 1, 2, 3 };
1155int puts(const char *);
1156double half(double x) { return x / 2.0; }
1157int f(int n) {
1158 int total = 0;
1159 for (int i = 0; i < n; i++) {
1160 if (i == 3) continue;
1161 total += table[i];
1162 }
1163 switch (n) {
1164 case 0: total = 1;
1165 case 1: total++; break;
1166 default: total = -total;
1167 }
1168 struct point p = { total, 1 };
1169 int *q = &p.y;
1170 puts(greeting);
1171 return p.x + *q;
1172}
1173int dispatch(int c) {
1174 void *p = c ? &&one : &&two;
1175 goto *p;
1176one:
1177 return 1;
1178two:
1179 return 2;
1180}
1181int assembly(int x, int *p) {
1182 int r;
1183 __asm__ volatile(\"xadd %0, %2\" : \"=r\"(r), \"+m\"(*p) : \"0\"(x) : \"cc\");
1184 __asm__ goto(\"cbnz %0, %l1\" : : \"r\"(r) : : away);
1185 return r;
1186away:
1187 return 0;
1188}
1189");
1190 let mut names = rucc_base::Interner::new();
1191 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
1192 assert_eq!(rucc_ir::print(&module, &names), text);
1193 }
1194}