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
181fn internal(message: &str) -> Diagnostic {
183 Diagnostic::error(format!("internal error: {message}"), Span::DUMMY)
184 .with_code("E0652")
185 .note("this is a bug in rucc rather than in the program, please report it", Span::DUMMY)
186}
187
188fn failure(message: String) -> Compiled {
191 Compiled { text: String::new(), messages: vec![format!("rucc: error: {message}")], errors: 1 }
192}
193
194#[cfg(test)]
195mod tests {
196 use rucc_session::{MemoryFileSystem, Std};
197 use rucc_target::Triple;
198
199 use super::*;
200
201 fn options() -> Options {
202 let mut opts = Options::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
203 opts.emit = EmitKind::Tast;
204 opts
205 }
206
207 fn run(opts: &Options, source: &str) -> Compiled {
208 let mut fs = MemoryFileSystem::new();
209 fs.insert("/main.c", source.to_owned().into_bytes());
210 compile(opts, "/main.c", &fs)
211 }
212
213 fn tast(source: &str) -> String {
215 let result = run(&options(), source);
216 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
217 result.text
218 }
219
220 #[test]
221 fn a_file_that_is_not_there_says_so_and_produces_nothing() {
222 let fs = MemoryFileSystem::new();
223 let result = compile(&options(), "/nope.c", &fs);
224 assert!(result.failed());
225 assert!(result.messages[0].contains("/nope.c"), "{:?}", result.messages);
226 assert!(result.text.is_empty());
227 }
228
229 #[test]
230 fn an_object_comes_out_with_its_type_its_linkage_and_how_much_of_a_definition_it_is() {
231 let text = tast("int x = 1;\n");
232 let expected = "\
233decl #0 x : int object external static defined
234 init
235 +0
236 const 1 : int
237";
238 assert_eq!(text, expected);
239 }
240
241 #[test]
242 fn the_macros_are_expanded_before_anything_is_parsed() {
243 let text = tast("#define N 2\nint a[N];\n");
247 assert!(text.starts_with("decl #0 a : int [2] object external static tentative"), "{text}");
248 }
249
250 #[test]
251 fn every_conversion_the_language_performs_is_a_node_in_the_output() {
252 let text = tast("long f(int a, long b) { return a + b; }\n");
256 assert!(text.contains("convert arithmetic"), "{text}");
257 }
258
259 #[test]
260 fn a_mistake_in_each_phase_reaches_the_caller_and_writes_no_tree() {
261 for source in [
262 "#error stop\n",
263 "int f(void) { return 1 + ; }\n",
264 "int f(void) { return undeclared; }\n",
265 ] {
266 let result = run(&options(), source);
267 assert!(result.failed(), "expected this to fail:\n{source}");
268 assert!(result.text.is_empty(), "a file that did not compile wrote a tree:\n{source}");
269 }
270 }
271
272 #[test]
273 fn one_undeclared_name_is_one_message_and_not_one_per_use() {
274 let result = run(&options(), "int f(void) { return nope + nope * nope; }\n");
278 assert_eq!(result.errors, 1, "{:?}", result.messages);
279 }
280
281 #[test]
282 fn a_declaration_the_parser_skipped_does_not_become_an_undeclared_name_as_well() {
283 let result = run(&options(), "int x = ;\nint f(void) { return x; }\n");
287 assert_eq!(result.errors, 1, "{:?}", result.messages);
288 }
289
290 #[test]
291 fn werror_turns_a_warning_into_an_error_in_the_count_and_in_the_word() {
292 let source = "int f(void) { char c = 300; return c; }\n";
293 let plain = run(&options(), source);
294 assert_eq!(plain.errors, 0, "{:?}", plain.messages);
295 assert_eq!(plain.messages.len(), 1, "expected a warning about the narrowed constant");
296 assert!(!plain.text.is_empty(), "a warning is not a reason to write nothing");
297
298 let mut opts = options();
299 opts.warnings_are_errors = true;
300 let strict = run(&opts, source);
301 assert!(strict.failed());
302 assert!(strict.text.is_empty(), "and under -Werror it is a reason to write nothing");
303 for message in &strict.messages {
304 assert!(!message.contains("warning:"), "{message}");
305 }
306 }
307
308 #[test]
309 fn the_dialect_reaches_the_keywords_and_the_checking() {
310 let source = "typeof(1) x;\n";
313 let mut opts = options();
314 opts.std = Std::C23;
315 opts.gnu_extensions = false;
316 assert!(!run(&opts, source).failed(), "{:?}", run(&opts, source).messages);
317
318 opts.std = Std::C17;
319 assert!(run(&opts, source).failed());
320 }
321
322 #[test]
323 fn asking_for_a_kind_that_is_not_written_yet_runs_the_front_end_and_writes_nothing() {
324 let mut opts = options();
325 opts.emit = EmitKind::MirFinal;
326 let result = run(&opts, "int x = 1;\n");
327 assert!(!result.failed(), "{:?}", result.messages);
328 assert!(result.text.is_empty());
329 assert!(run(&opts, "int f(void) { return undeclared; }\n").failed());
332 }
333
334 fn ir(source: &str) -> String {
336 let mut opts = options();
337 opts.emit = EmitKind::Ir;
338 let result = run(&opts, source);
339 assert_eq!(result.messages, Vec::<String>::new(), "expected this to compile:\n{source}");
340 result.text
341 }
342
343 fn body(source: &str) -> String {
345 let text = ir(source);
346 let (_, rest) = text.split_once("{\n").expect("a function definition");
347 let (body, _) = rest.rsplit_once("}\n").expect("a function definition");
348 body.to_owned()
349 }
350
351 #[test]
352 fn an_object_becomes_a_global_with_an_image_and_a_function_becomes_a_func() {
353 let text = ir("int x = 7;\nint add(int a, int b) { return a + b; }\n");
354 assert!(text.contains("global @x : i32 = 7, align 4, linkage(external)\n"), "{text}");
355 let expected = "\
356func @add(i32, i32) -> i32, linkage(external) {
357block0(%0: i32, %1: i32):
358 %2 = add.nsw %0, %1
359 return %2
360}
361";
362 assert!(text.contains(expected), "{text}");
363 }
364
365 #[test]
366 fn a_local_nothing_takes_the_address_of_is_a_value_and_never_a_stack_slot() {
367 let text = body("int f(int n) { int a = n + 1; int b = a * 2; return a + b; }\n");
368 assert!(!text.contains("alloca"), "{text}");
369 assert!(!text.contains("load"), "{text}");
370 assert!(!text.contains("store"), "{text}");
371 }
372
373 #[test]
374 fn a_local_whose_address_is_taken_gets_a_slot_in_the_entry_block() {
375 let text = body("int g(int *);\nint f(void) { int a = 1; return g(&a); }\n");
376 let expected = "\
377block0:
378 %0 = alloca, size 4, align 4
379 %1 = iconst.i32 1
380 store %1 -> %0, align 4
381 %2 = call @g(%0) : (ptr) -> i32
382 return %2
383";
384 assert_eq!(text, expected);
385 }
386
387 #[test]
388 fn a_loop_carries_what_it_changes_as_block_parameters() {
389 let text = body(
392 "int f(int n) {\n int total = 0;\n for (int i = 0; i < n; i++) total += i;\n \
393 return total;\n}\n",
394 );
395 assert!(!text.contains("alloca"), "{text}");
396 assert!(text.contains("block1(%3: i32, %4: i32):"), "{text}");
397 assert!(text.contains("jump block1("), "{text}");
398 }
399
400 #[test]
401 fn a_comparison_used_as_a_condition_is_not_widened_and_narrowed_again() {
402 let text = body("int f(int a, int b) { if (a < b) return 1; return 0; }\n");
403 assert!(text.contains("icmp slt %0, %1"), "{text}");
404 assert!(!text.contains("zext"), "{text}");
405 }
406
407 #[test]
408 fn the_right_side_of_a_short_circuit_is_in_a_block_of_its_own() {
409 let text = body("int f(int a, int b) { return a && b; }\n");
410 let expected = "\
411block0(%0: i32, %1: i32):
412 %2 = iconst.i32 0
413 %3 = icmp ne %0, %2
414 %4 = iconst.i1 0
415 br_if %3, block1, block2(%4)
416
417block1:
418 %5 = iconst.i32 0
419 %6 = icmp ne %1, %5
420 jump block2(%6)
421
422block2(%7: i1):
423 %8 = zext.i32 %7
424 return %8
425";
426 assert_eq!(text, expected);
427 }
428
429 #[test]
430 fn code_after_a_return_is_not_built_and_does_not_leave_an_empty_block_behind() {
431 let text = body("int f(int a) { if (a) return 1; else return 2; return 3; }\n");
432 assert!(!text.contains("block3"), "{text}");
435 assert!(!text.contains("iconst.i32 3"), "{text}");
436 }
437
438 #[test]
439 fn falling_off_the_end_returns_zero_from_main_and_nothing_from_a_void_function() {
440 assert!(body("int main(void) { }\n").contains("iconst.i32 0\n return"));
441 assert_eq!(body("void f(void) { }\n"), "block0:\n return\n");
442 assert!(body("int f(void) { }\n").contains("unreachable"));
443 }
444
445 #[test]
446 fn a_structure_is_copied_rather_than_held_in_a_value() {
447 let text = body(
448 "struct point { int x, y; };\n\
449 int f(void) { struct point p = { 1, 2 }; struct point q = p; return q.x; }\n",
450 );
451 assert!(text.contains("memcpy"), "{text}");
452 }
453
454 #[test]
455 fn an_initializer_that_leaves_part_of_an_object_unwritten_zeroes_it_first() {
456 let text = body("int f(void) { int a[4] = { 1 }; return a[3]; }\n");
457 assert!(text.contains("memset"), "{text}");
458 }
459
460 #[test]
461 fn a_switch_is_one_branch_and_a_case_that_falls_through_carries_what_it_wrote() {
462 let text = body(
463 "int f(int x) { int r = 0; switch (x) { case 1: r = 1; case 2: r += 2; break; \
464 default: r = 4; } return r; }\n",
465 );
466 let expected = "\
467block0(%0: i32):
468 %1 = iconst.i32 0
469 switch %0, block1, [1 => block2, 2 => block3(%1)]
470
471block1:
472 %2 = iconst.i32 4
473 jump block4(%2)
474
475block2:
476 %3 = iconst.i32 1
477 jump block3(%3)
478
479block3(%4: i32):
480 %5 = iconst.i32 2
481 %6 = add.nsw %4, %5
482 jump block4(%6)
483
484block4(%7: i32):
485 return %7
486";
487 assert_eq!(text, expected);
488 }
489
490 #[test]
491 fn a_case_range_is_tested_for_rather_than_put_in_the_table() {
492 let text = body("int f(int x) { switch (x) { case 1 ... 9: return 1; } return 0; }\n");
495 assert!(text.contains("%2 = sub %0, %1"), "{text}");
496 assert!(text.contains("icmp ule"), "{text}");
497 assert!(!text.contains("switch"), "{text}");
498 }
499
500 #[test]
501 fn break_leaves_the_switch_and_continue_leaves_the_loop_around_it() {
502 let text = body(
503 "int f(int n) { int t = 0; for (int i = 0; i < n; i++) { switch (i) { \
504 case 0: continue; case 1: break; default: t += i; } t++; } return t; }\n",
505 );
506 assert!(text.contains("switch %3, block4, [0 => block5, 1 => block6]"), "{text}");
509 assert!(text.contains("block5:\n jump block7("), "{text}");
510 assert!(text.contains("block6:\n jump block8("), "{text}");
511 }
512
513 #[test]
514 fn a_switch_with_nothing_to_branch_on_still_runs_what_comes_after_it() {
515 assert_eq!(body("void f(int x) { switch (x) { } }\n"), "block0(%0: i32):\n return\n");
516 }
517
518 #[test]
519 fn a_label_control_cannot_fall_into_is_reported_rather_than_dropped() {
520 let mut opts = options();
521 opts.emit = EmitKind::Ir;
522 for source in [
526 "int f(int x, int n) { switch (x) { case 1: break; while (n) { case 2: n--; } } \
527 return n; }\n",
528 "int f(int x, int n) { goto in; while (n) { in: n--; } return n; }\n",
529 ] {
530 let result = run(&opts, source);
531 assert!(result.failed(), "expected this to be reported:\n{source}");
532 assert!(
533 result.messages.iter().any(|m| m.contains("a label control cannot fall into")),
534 "{:?}",
535 result.messages
536 );
537 }
538 }
539
540 #[test]
541 fn a_goto_is_a_jump_to_the_block_the_label_starts() {
542 let text = body("int f(int x) { int r = 0; if (x) goto out; r = 1; out: return r; }\n");
543 assert!(!text.contains("alloca"), "{text}");
545 assert!(text.contains("block3(%4: i32):\n return %4"), "{text}");
546 assert_eq!(text.matches("jump block3(").count(), 2, "{text}");
547 }
548
549 #[test]
550 fn a_backward_goto_is_a_loop_and_carries_what_it_changes() {
551 let text =
552 body("int f(int n) { int i = 0; again: if (i < n) { i++; goto again; } return i; }\n");
553 assert!(!text.contains("alloca"), "{text}");
554 assert!(text.contains("block1(%2: i32):"), "{text}");
555 assert!(text.contains("jump block1(%5)"), "{text}");
556 }
557
558 #[test]
559 fn a_label_nothing_reaches_is_taken_out_rather_than_left_for_the_verifier() {
560 assert_eq!(
563 body("int f(int x) { return x; spare: return 0; }\n"),
564 "block0(%0: i32):\n return %0\n"
565 );
566 }
567
568 #[test]
569 fn what_the_walk_cannot_build_yet_is_reported_rather_than_mislowered() {
570 let mut opts = options();
571 opts.emit = EmitKind::Ir;
572 for source in [
573 "int f(int n) { int a[n]; a[0] = 1; return a[0]; }\n",
574 "int f(int x) { void *p = &&out; goto *p; out: return x; }\n",
575 "struct s { int a : 3; };\nint f(struct s *p) { return p->a; }\n",
576 "struct s { int a[4]; };\nint f(struct s v);\nint g(struct s v) { return f(v); }\n",
577 ] {
578 let result = run(&opts, source);
579 assert!(result.failed(), "expected this to be reported:\n{source}");
580 assert!(
581 result.messages.iter().any(|m| m.contains("not supported yet")),
582 "{:?}",
583 result.messages
584 );
585 }
586 }
587
588 #[test]
589 fn the_printed_ir_reads_back_as_the_same_module() {
590 let text = ir("\
593struct point { int x, y; };
594static const char greeting[] = \"hi\";
595int table[4] = { 1, 2, 3 };
596int puts(const char *);
597double half(double x) { return x / 2.0; }
598int f(int n) {
599 int total = 0;
600 for (int i = 0; i < n; i++) {
601 if (i == 3) continue;
602 total += table[i];
603 }
604 switch (n) {
605 case 0: total = 1;
606 case 1: total++; break;
607 default: total = -total;
608 }
609 struct point p = { total, 1 };
610 int *q = &p.y;
611 puts(greeting);
612 return p.x + *q;
613}
614");
615 let mut names = rucc_base::Interner::new();
616 let module = rucc_ir::parse(&text, &mut names).expect("the printer writes what it reads");
617 assert_eq!(rucc_ir::print(&module, &names), text);
618 }
619}