Skip to content

Minimal codegen support: numbers and function parameters #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Oct 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,27 @@

C based backend for rustc

[![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)

This a C codegen backend for rustc, which lowers Rust MIR to C code and compiles
it with a C compiler.

This code is still highly experimental and not ready for production use.

## Try it

In the root directory of the project, run the following command:

```bash
./y rustc examples/basic_math.rs
./build/basic_math
```

The usage of `./y` can be viewed from `./y help`.

Note: only Linux is supported at the moment. `clang` is required to compile C code,
and LLVM FileCheck is required to test the codegen.

## License

This project is licensed under a dual license: MIT or Apache 2.0.
20 changes: 14 additions & 6 deletions bootstrap/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions bootstrap/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ color-print = "0.3.6"
env_logger = "0.11.5"
glob = "0.3.1"
log = "0.4.22"
regex = "1.11.1"
similar = "2.6.0"
which = "6.0.1"
2 changes: 1 addition & 1 deletion bootstrap/src/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ impl Run for FmtCommand {
.args(["--manifest-path", "crates/Cargo.toml"])
.arg("--all"),
);
for file in glob("example/**/*.rs").unwrap() {
for file in glob("examples/**/*.rs").unwrap() {
self.perform(Command::new("rustfmt").args(["--edition", "2021"]).arg(file.unwrap()));
}
for file in glob("tests/**/*.rs").unwrap() {
Expand Down
157 changes: 124 additions & 33 deletions bootstrap/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@ use anstream::{eprint as print, eprintln as println};
use clap::Args;
use color_print::{cprint, cprintln};
use glob::glob;
use similar::{ChangeTag, TextDiff};
use which::which;

use crate::manifest::Manifest;
use crate::Run;

/// Run tests
#[derive(Args, Debug)]
pub struct TestCommand {}
pub struct TestCommand {
/// Update the blessed output
#[clap(long)]
pub bless: bool,
}

impl Run for TestCommand {
fn run(&self, manifest: &Manifest) {
Expand All @@ -37,12 +42,21 @@ impl Run for TestCommand {
TestType::FileCheck => {
cprint!("File checking {}...", testcase.name);
testcase.build(manifest);
filechecker.run(&testcase.source, &testcase.output);
filechecker.run(&testcase);
}
TestType::Bless => {
cprint!("Blessing {}...", testcase.name);
testcase.build(manifest);
bless(self.bless, &testcase);
}
TestType::Compile => {
cprint!("Compiling {}...", testcase.name);
testcase.build(manifest);
}
TestType::CompileLib => {
cprint!("Compiling lib {}...", testcase.name);
testcase.build_lib(manifest);
}
}
cprintln!("<g>OK</g>");
}
Expand All @@ -51,58 +65,119 @@ impl Run for TestCommand {

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

// Examples
for case in glob("example/*.rs").unwrap() {
for case in glob("examples/*.rs").unwrap() {
let case = case.unwrap();
let filename = case.file_stem().unwrap();
if filename == "mini_core" {
continue;
}
let name = format!("example/{}", filename.to_string_lossy());
let output = manifest.out_dir.join("example").join(filename);
result.push(TestCase { name, source: case, output, test: TestType::Compile })
let name = format!("examples/{}", filename.to_string_lossy());
let output_file = manifest.out_dir.join("examples").join(filename);
tests.push(TestCase { name, source: case, output_file, test: TestType::Compile })
}

// Codegen tests
for case in glob("tests/codegen/*.rs").unwrap() {
let case = case.unwrap();
let filename = case.file_stem().unwrap();
let name = format!("codegen/{}", filename.to_string_lossy());
let output = manifest.out_dir.join("tests/codegen").join(filename);
result.push(TestCase { name, source: case, output, test: TestType::FileCheck })
let output_file = manifest.out_dir.join("tests/codegen").join(filename);
tests.push(TestCase { name, source: case, output_file, test: TestType::FileCheck })
}

// Bless tests - the output should be the same as the last run
for case in glob("tests/bless/*.rs").unwrap() {
let case = case.unwrap();
let filename = case.file_stem().unwrap();
let name = format!("bless/{}", filename.to_string_lossy());
let output_file = manifest.out_dir.join("tests/bless").join(filename);
tests.push(TestCase { name, source: case, output_file, test: TestType::Bless })
}

// Collect test-auxiliary
let aux_use = regex::Regex::new(r"^//@\s*aux-build:(?P<fname>.*)").unwrap();
let mut auxiliary = vec![];
for case in tests.iter() {
let source = std::fs::read_to_string(&case.source).unwrap();
for cap in aux_use.captures_iter(&source) {
let fname = cap.name("fname").unwrap().as_str();
let source = Path::new("tests/auxiliary").join(fname);
let filename = source.file_stem().unwrap();
let name = format!("auxiliary/{}", filename.to_string_lossy());
let output_file = manifest.out_dir.join(filename); // aux files are output to the base directory
auxiliary.push(TestCase { name, source, output_file, test: TestType::CompileLib })
}
}

result
// Compile auxiliary before the tests
let mut cases = auxiliary;
cases.extend(tests);
cases
}
}

pub enum TestType {
/// Test an executable can be compiled
Compile,
/// Test a library can be compiled
CompileLib,
/// Run LLVM FileCheck on the generated code
FileCheck,
/// Bless test - the output should be the same as the last run
Bless,
}

pub struct TestCase {
pub name: String,
pub source: PathBuf,
pub output: PathBuf,
pub output_file: PathBuf,
pub test: TestType,
}

impl TestCase {
pub fn build(&self, manifest: &Manifest) {
std::fs::create_dir_all(self.output.parent().unwrap()).unwrap();
let output_dir = self.output_file.parent().unwrap();
std::fs::create_dir_all(output_dir).unwrap();
let mut command = manifest.rustc();
command
.args(["--crate-type", "bin"])
.arg("-O")
.arg(&self.source)
.arg("-o")
.arg(&self.output);
.arg(&self.output_file);
log::debug!("running {:?}", command);
command.status().unwrap();
}

pub fn build_lib(&self, manifest: &Manifest) {
let output_dir = self.output_file.parent().unwrap();
std::fs::create_dir_all(output_dir).unwrap();
let mut command = manifest.rustc();
command
.args(["--crate-type", "lib"])
.arg("-O")
.arg(&self.source)
.arg("--out-dir") // we use `--out-dir` to integrate with the default name convention
.arg(output_dir); // so here we ignore the filename and just use the directory
log::debug!("running {:?}", command);
command.status().unwrap();
}

/// Get the generated C file f
pub fn generated(&self) -> PathBuf {
let case = self.source.file_stem().unwrap().to_string_lossy();
let generated = std::fs::read_dir(self.output_file.parent().unwrap())
.unwrap()
.filter_map(|entry| entry.ok())
.find(|entry| {
let filename = entry.file_name();
let filename = filename.to_string_lossy();
filename.ends_with(".c") && filename.starts_with(case.as_ref())
});

assert!(generated.is_some(), "could not find {case}'s generated file");
generated.unwrap().path()
}
}

struct FileChecker {
Expand All @@ -126,25 +201,41 @@ impl FileChecker {
Self { filecheck }
}

fn run(&self, source: &Path, output: &Path) {
let case = source.file_stem().unwrap().to_string_lossy();
let generated = std::fs::read_dir(output.parent().unwrap())
.unwrap()
.filter_map(|entry| entry.ok())
.find(|entry| {
let filename = entry.file_name();
let filename = filename.to_string_lossy();
filename.ends_with(".c") && filename.starts_with(case.as_ref())
});

assert!(generated.is_some(), "could not find {case}'s generated file");
let generated = generated.unwrap();

let generated = File::open(generated.path()).unwrap();
fn run(&self, case: &TestCase) {
let generated = File::open(case.generated()).unwrap();
let mut command = std::process::Command::new(&self.filecheck);
command.arg(source).stdin(generated);
command.arg(&case.source).stdin(generated);
log::debug!("running {:?}", command);
let output = command.output().unwrap();
assert!(output.status.success(), "failed to run FileCheck on {case}");
assert!(
output.status.success(),
"failed to run FileCheck on {}",
case.source.file_stem().unwrap().to_string_lossy()
);
}
}

fn bless(update: bool, case: &TestCase) {
let output = case.generated();
let blessed = case.source.with_extension("c");
if update {
std::fs::copy(output, blessed).unwrap();
} else {
let output = std::fs::read_to_string(output).unwrap();
let blessed = std::fs::read_to_string(blessed).unwrap();

let diff = TextDiff::from_lines(&blessed, &output);
if diff.ratio() < 1.0 {
cprintln!("<r,s>output does not match blessed output</r,s>");
for change in diff.iter_all_changes() {
let lineno = change.old_index().unwrap_or(change.new_index().unwrap_or(0));
match change.tag() {
ChangeTag::Equal => print!(" {:4}| {}", lineno, change),
ChangeTag::Insert => cprint!("<g>+{:4}| {}</g>", lineno, change),
ChangeTag::Delete => cprint!("<r>-{:4}| {}</r>", lineno, change),
}
}
std::process::exit(1);
}
}
}
3 changes: 3 additions & 0 deletions crates/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions crates/rustc_codegen_c/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
[package]
name = "rustc_codegen_c"
version = "0.1.0"
edition = "2021"
version.workspace = true

[lib]
crate-type = ["dylib"]

[dependencies]
rustc_codegen_c_ast = { path = "../rustc_codegen_c_ast" }

# This package uses rustc crates.
[package.metadata.rust-analyzer]
rustc_private=true
rustc_private = true
Loading