Skip to content

Commit 763d3eb

Browse files
authored
Minimal codegen support: numbers and function parameters (#2)
* chore: readme * feat: function signature * feat: function params * feat: return value * feat: integer types * feat: integer cast
1 parent aeb8fe5 commit 763d3eb

36 files changed

+1912
-60
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,27 @@
22

33
C based backend for rustc
44

5+
[![CI](https://github.com/rust-lang/rustc_codegen_c/actions/workflows/ci.yml/badge.svg)](https://github.com/rust-lang/rustc_codegen_c/actions/workflows/ci.yml)
6+
7+
This a C codegen backend for rustc, which lowers Rust MIR to C code and compiles
8+
it with a C compiler.
9+
10+
This code is still highly experimental and not ready for production use.
11+
12+
## Try it
13+
14+
In the root directory of the project, run the following command:
15+
16+
```bash
17+
./y rustc examples/basic_math.rs
18+
./build/basic_math
19+
```
20+
21+
The usage of `./y` can be viewed from `./y help`.
22+
23+
Note: only Linux is supported at the moment. `clang` is required to compile C code,
24+
and LLVM FileCheck is required to test the codegen.
25+
526
## License
627

728
This project is licensed under a dual license: MIT or Apache 2.0.

bootstrap/Cargo.lock

Lines changed: 14 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bootstrap/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,6 @@ color-print = "0.3.6"
1010
env_logger = "0.11.5"
1111
glob = "0.3.1"
1212
log = "0.4.22"
13+
regex = "1.11.1"
14+
similar = "2.6.0"
1315
which = "6.0.1"

bootstrap/src/fmt.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ impl Run for FmtCommand {
2323
.args(["--manifest-path", "crates/Cargo.toml"])
2424
.arg("--all"),
2525
);
26-
for file in glob("example/**/*.rs").unwrap() {
26+
for file in glob("examples/**/*.rs").unwrap() {
2727
self.perform(Command::new("rustfmt").args(["--edition", "2021"]).arg(file.unwrap()));
2828
}
2929
for file in glob("tests/**/*.rs").unwrap() {

bootstrap/src/test.rs

Lines changed: 124 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,19 @@ use anstream::{eprint as print, eprintln as println};
55
use clap::Args;
66
use color_print::{cprint, cprintln};
77
use glob::glob;
8+
use similar::{ChangeTag, TextDiff};
89
use which::which;
910

1011
use crate::manifest::Manifest;
1112
use crate::Run;
1213

1314
/// Run tests
1415
#[derive(Args, Debug)]
15-
pub struct TestCommand {}
16+
pub struct TestCommand {
17+
/// Update the blessed output
18+
#[clap(long)]
19+
pub bless: bool,
20+
}
1621

1722
impl Run for TestCommand {
1823
fn run(&self, manifest: &Manifest) {
@@ -37,12 +42,21 @@ impl Run for TestCommand {
3742
TestType::FileCheck => {
3843
cprint!("File checking {}...", testcase.name);
3944
testcase.build(manifest);
40-
filechecker.run(&testcase.source, &testcase.output);
45+
filechecker.run(&testcase);
46+
}
47+
TestType::Bless => {
48+
cprint!("Blessing {}...", testcase.name);
49+
testcase.build(manifest);
50+
bless(self.bless, &testcase);
4151
}
4252
TestType::Compile => {
4353
cprint!("Compiling {}...", testcase.name);
4454
testcase.build(manifest);
4555
}
56+
TestType::CompileLib => {
57+
cprint!("Compiling lib {}...", testcase.name);
58+
testcase.build_lib(manifest);
59+
}
4660
}
4761
cprintln!("<g>OK</g>");
4862
}
@@ -51,58 +65,119 @@ impl Run for TestCommand {
5165

5266
impl TestCommand {
5367
pub fn collect_testcases(&self, manifest: &Manifest) -> Vec<TestCase> {
54-
let mut result = vec![];
68+
let mut tests = vec![];
5569

5670
// Examples
57-
for case in glob("example/*.rs").unwrap() {
71+
for case in glob("examples/*.rs").unwrap() {
5872
let case = case.unwrap();
5973
let filename = case.file_stem().unwrap();
60-
if filename == "mini_core" {
61-
continue;
62-
}
63-
let name = format!("example/{}", filename.to_string_lossy());
64-
let output = manifest.out_dir.join("example").join(filename);
65-
result.push(TestCase { name, source: case, output, test: TestType::Compile })
74+
let name = format!("examples/{}", filename.to_string_lossy());
75+
let output_file = manifest.out_dir.join("examples").join(filename);
76+
tests.push(TestCase { name, source: case, output_file, test: TestType::Compile })
6677
}
6778

6879
// Codegen tests
6980
for case in glob("tests/codegen/*.rs").unwrap() {
7081
let case = case.unwrap();
7182
let filename = case.file_stem().unwrap();
7283
let name = format!("codegen/{}", filename.to_string_lossy());
73-
let output = manifest.out_dir.join("tests/codegen").join(filename);
74-
result.push(TestCase { name, source: case, output, test: TestType::FileCheck })
84+
let output_file = manifest.out_dir.join("tests/codegen").join(filename);
85+
tests.push(TestCase { name, source: case, output_file, test: TestType::FileCheck })
86+
}
87+
88+
// Bless tests - the output should be the same as the last run
89+
for case in glob("tests/bless/*.rs").unwrap() {
90+
let case = case.unwrap();
91+
let filename = case.file_stem().unwrap();
92+
let name = format!("bless/{}", filename.to_string_lossy());
93+
let output_file = manifest.out_dir.join("tests/bless").join(filename);
94+
tests.push(TestCase { name, source: case, output_file, test: TestType::Bless })
95+
}
96+
97+
// Collect test-auxiliary
98+
let aux_use = regex::Regex::new(r"^//@\s*aux-build:(?P<fname>.*)").unwrap();
99+
let mut auxiliary = vec![];
100+
for case in tests.iter() {
101+
let source = std::fs::read_to_string(&case.source).unwrap();
102+
for cap in aux_use.captures_iter(&source) {
103+
let fname = cap.name("fname").unwrap().as_str();
104+
let source = Path::new("tests/auxiliary").join(fname);
105+
let filename = source.file_stem().unwrap();
106+
let name = format!("auxiliary/{}", filename.to_string_lossy());
107+
let output_file = manifest.out_dir.join(filename); // aux files are output to the base directory
108+
auxiliary.push(TestCase { name, source, output_file, test: TestType::CompileLib })
109+
}
75110
}
76111

77-
result
112+
// Compile auxiliary before the tests
113+
let mut cases = auxiliary;
114+
cases.extend(tests);
115+
cases
78116
}
79117
}
80118

81119
pub enum TestType {
120+
/// Test an executable can be compiled
82121
Compile,
122+
/// Test a library can be compiled
123+
CompileLib,
124+
/// Run LLVM FileCheck on the generated code
83125
FileCheck,
126+
/// Bless test - the output should be the same as the last run
127+
Bless,
84128
}
85129

86130
pub struct TestCase {
87131
pub name: String,
88132
pub source: PathBuf,
89-
pub output: PathBuf,
133+
pub output_file: PathBuf,
90134
pub test: TestType,
91135
}
92136

93137
impl TestCase {
94138
pub fn build(&self, manifest: &Manifest) {
95-
std::fs::create_dir_all(self.output.parent().unwrap()).unwrap();
139+
let output_dir = self.output_file.parent().unwrap();
140+
std::fs::create_dir_all(output_dir).unwrap();
96141
let mut command = manifest.rustc();
97142
command
98143
.args(["--crate-type", "bin"])
99144
.arg("-O")
100145
.arg(&self.source)
101146
.arg("-o")
102-
.arg(&self.output);
147+
.arg(&self.output_file);
103148
log::debug!("running {:?}", command);
104149
command.status().unwrap();
105150
}
151+
152+
pub fn build_lib(&self, manifest: &Manifest) {
153+
let output_dir = self.output_file.parent().unwrap();
154+
std::fs::create_dir_all(output_dir).unwrap();
155+
let mut command = manifest.rustc();
156+
command
157+
.args(["--crate-type", "lib"])
158+
.arg("-O")
159+
.arg(&self.source)
160+
.arg("--out-dir") // we use `--out-dir` to integrate with the default name convention
161+
.arg(output_dir); // so here we ignore the filename and just use the directory
162+
log::debug!("running {:?}", command);
163+
command.status().unwrap();
164+
}
165+
166+
/// Get the generated C file f
167+
pub fn generated(&self) -> PathBuf {
168+
let case = self.source.file_stem().unwrap().to_string_lossy();
169+
let generated = std::fs::read_dir(self.output_file.parent().unwrap())
170+
.unwrap()
171+
.filter_map(|entry| entry.ok())
172+
.find(|entry| {
173+
let filename = entry.file_name();
174+
let filename = filename.to_string_lossy();
175+
filename.ends_with(".c") && filename.starts_with(case.as_ref())
176+
});
177+
178+
assert!(generated.is_some(), "could not find {case}'s generated file");
179+
generated.unwrap().path()
180+
}
106181
}
107182

108183
struct FileChecker {
@@ -126,25 +201,41 @@ impl FileChecker {
126201
Self { filecheck }
127202
}
128203

129-
fn run(&self, source: &Path, output: &Path) {
130-
let case = source.file_stem().unwrap().to_string_lossy();
131-
let generated = std::fs::read_dir(output.parent().unwrap())
132-
.unwrap()
133-
.filter_map(|entry| entry.ok())
134-
.find(|entry| {
135-
let filename = entry.file_name();
136-
let filename = filename.to_string_lossy();
137-
filename.ends_with(".c") && filename.starts_with(case.as_ref())
138-
});
139-
140-
assert!(generated.is_some(), "could not find {case}'s generated file");
141-
let generated = generated.unwrap();
142-
143-
let generated = File::open(generated.path()).unwrap();
204+
fn run(&self, case: &TestCase) {
205+
let generated = File::open(case.generated()).unwrap();
144206
let mut command = std::process::Command::new(&self.filecheck);
145-
command.arg(source).stdin(generated);
207+
command.arg(&case.source).stdin(generated);
146208
log::debug!("running {:?}", command);
147209
let output = command.output().unwrap();
148-
assert!(output.status.success(), "failed to run FileCheck on {case}");
210+
assert!(
211+
output.status.success(),
212+
"failed to run FileCheck on {}",
213+
case.source.file_stem().unwrap().to_string_lossy()
214+
);
215+
}
216+
}
217+
218+
fn bless(update: bool, case: &TestCase) {
219+
let output = case.generated();
220+
let blessed = case.source.with_extension("c");
221+
if update {
222+
std::fs::copy(output, blessed).unwrap();
223+
} else {
224+
let output = std::fs::read_to_string(output).unwrap();
225+
let blessed = std::fs::read_to_string(blessed).unwrap();
226+
227+
let diff = TextDiff::from_lines(&blessed, &output);
228+
if diff.ratio() < 1.0 {
229+
cprintln!("<r,s>output does not match blessed output</r,s>");
230+
for change in diff.iter_all_changes() {
231+
let lineno = change.old_index().unwrap_or(change.new_index().unwrap_or(0));
232+
match change.tag() {
233+
ChangeTag::Equal => print!(" {:4}| {}", lineno, change),
234+
ChangeTag::Insert => cprint!("<g>+{:4}| {}</g>", lineno, change),
235+
ChangeTag::Delete => cprint!("<r>-{:4}| {}</r>", lineno, change),
236+
}
237+
}
238+
std::process::exit(1);
239+
}
149240
}
150241
}

crates/Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/rustc_codegen_c/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
[package]
22
name = "rustc_codegen_c"
3-
version = "0.1.0"
43
edition = "2021"
4+
version.workspace = true
55

66
[lib]
77
crate-type = ["dylib"]
88

99
[dependencies]
10+
rustc_codegen_c_ast = { path = "../rustc_codegen_c_ast" }
1011

1112
# This package uses rustc crates.
1213
[package.metadata.rust-analyzer]
13-
rustc_private=true
14+
rustc_private = true

0 commit comments

Comments
 (0)