Skip to content

feat: add basic support for function call #12

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 9 commits into from
Feb 26, 2025
Merged
Show file tree
Hide file tree
Changes from 7 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
2 changes: 1 addition & 1 deletion bootstrap/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ trait Run {
output
}
// Command ran but did not complete
Ok(output) => panic!("command failed: {output:?}"),
Ok(output) => panic!("command failed: {}", String::from_utf8_lossy(&output.stderr)),
Err(e) => panic!("command failed: {e:?}"),
}
}
Expand Down
266 changes: 222 additions & 44 deletions bootstrap/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ impl Run for TestCommand {
self.log_action_context("source", &testcase.source.display());
self.log_action_context("output", &testcase.output_file.display());
testcase.build(manifest);
bless(self.bless, &testcase);
self.bless(self.bless, &testcase);
}
TestType::Compile => {
self.log_action_start("TEST Compile", &testcase.name);
Expand All @@ -73,6 +73,7 @@ impl Run for TestCommand {
testcase.build_lib(manifest);
}
}
self.check_and_run_directives(&testcase);
}
}

Expand All @@ -84,7 +85,6 @@ impl Run for TestCommand {
impl TestCommand {
pub fn collect_testcases(&self, manifest: &Manifest) -> Vec<TestCase> {
let mut cases = vec![];

let verbose = self.verbose;

// Examples
Expand All @@ -107,7 +107,7 @@ impl TestCommand {
cases.push(testcase);
}

// Bless tests - the output should be the same as the last run
// Bless tests
for case in glob("tests/bless/*.rs").unwrap() {
let case = case.unwrap();
let filename = case.file_stem().unwrap();
Expand All @@ -117,27 +117,26 @@ impl TestCommand {
cases.push(testcase);
}

// Collect test-auxiliary
let aux_use = regex::Regex::new(r"(?m)//@\s*aux-build:(?P<fname>.*)").unwrap();
// Collect and process auxiliary builds from directives
let mut auxiliaries = vec![];
for case in cases.iter() {
let content = std::fs::read_to_string(&case.source).unwrap();
for cap in aux_use.captures_iter(&content) {
println!("{:?}", case.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());

// deduplication
if auxiliaries.iter().any(|aux: &TestCase| aux.name == name) {
continue;
let directives = case.parse_directives();
for directive in directives {
if let TestDirective::AuxBuild(fname) = directive {
let source = Path::new("tests/auxiliary").join(&fname);
let filename = source.file_stem().unwrap();
let name = format!("auxiliary/{}", filename.to_string_lossy());

// deduplication
if auxiliaries.iter().any(|aux: &TestCase| aux.name == name) {
continue;
}

let output_file = manifest.out_dir.join(filename); // aux files are output to the base directory
let testcase =
TestCase::new(name, source, output_file, TestType::CompileLib, verbose);
auxiliaries.push(testcase);
}

let output_file = manifest.out_dir.join(filename); // aux files are output to the base directory
let testcase =
TestCase::new(name, source, output_file, TestType::CompileLib, verbose);
auxiliaries.push(testcase);
}
}

Expand All @@ -146,6 +145,141 @@ impl TestCommand {
testcases.extend(cases);
testcases
}

fn bless(&self, update: bool, case: &TestCase) {
let output = case.generated();
let blessed = case.source.with_extension("c");

self.log_action_context("checking", &blessed.display());
if update {
self.log_action_context("updating", &blessed.display());
std::fs::copy(output, &blessed).unwrap();
self.log_action_context("result", "updated");
} 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);
}
self.log_action_context("result", "passed");
}
}

/// Run a runtime test and check its output against directives
fn check_and_run_directives(&self, testcase: &TestCase) {
// Parse directives from source
let directives = testcase.parse_directives();
self.log_action_context("directives", &format!("found {} directives", directives.len()));

let mut runpass = false;
let mut exitcode = None;
let mut stdout = None;
let mut stderr = None;

// Check each directive
for directive in directives {
match directive {
TestDirective::RunPass => runpass = true,
TestDirective::CheckStdout(expected) => stdout = Some(expected),
TestDirective::CheckStderr(expected) => stderr = Some(expected),
TestDirective::ExitCode(expected) => exitcode = Some(expected),
TestDirective::AuxBuild(_) => {
// AuxBuild directives are handled during test collection
// No need to check them during test execution
}
}
}

if !runpass && (exitcode.is_some() | stdout.is_some() | stderr.is_some()) {
panic!("Directives conflicts, lack of '//@ run-pass'");
}

if runpass {
self.run_and_check_output(testcase, exitcode, stdout, stderr);
}

self.log_action_context("result", "all checks passed");
}

fn run_and_check_output(
&self,
testcase: &TestCase,
expected_exit: Option<i32>,
expected_stdout: Option<String>,
expected_stderr: Option<String>,
) {
// Run the test
self.log_action_context("running", &testcase.output_file.display());
let output = std::process::Command::new(&testcase.output_file)
.output()
.unwrap_or_else(|e| panic!("failed to run {}: {}", testcase.output_file.display(), e));

// Get actual outputs
let actual_return = output.status.code().unwrap_or_else(|| {
panic!("Process terminated by signal: {}", testcase.output_file.display())
});
let actual_stdout = String::from_utf8_lossy(&output.stdout).into_owned();
let actual_stderr = String::from_utf8_lossy(&output.stderr).into_owned();

{
let expected_exit = expected_exit.unwrap_or(0);
self.log_action_context("checking exit code", &expected_exit.to_string());
if actual_return != expected_exit {
cprintln!("<r,s>exit code does not match expected value</r,s>");
cprintln!("expected: {}", expected_exit);
cprintln!("actual: {}", actual_return);
std::process::exit(1);
}
self.log_action_context("exit code", "passed");
}

if let Some(expected_stdout) = expected_stdout {
self.log_action_context("checking stdout", &expected_stdout);
let diff = TextDiff::from_lines(&expected_stdout, &actual_stdout);
if diff.ratio() < 1.0 {
cprintln!("<r,s>stdout does not match expected 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);
}
self.log_action_context("stdout", "passed");
}

if let Some(expected_stderr) = expected_stderr {
self.log_action_context("checking stderr", &expected_stderr);
let diff = TextDiff::from_lines(&expected_stderr, &actual_stderr);
if diff.ratio() < 1.0 {
cprintln!("<r,s>stderr does not match expected 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);
}
self.log_action_context("stderr", "passed");
}
}
}

#[derive(Debug)]
Expand All @@ -159,6 +293,7 @@ pub enum TestType {
/// Bless test - the output should be the same as the last run
Bless,
}

impl TestType {
pub fn as_str(&self) -> &'static str {
match self {
Expand Down Expand Up @@ -241,6 +376,58 @@ impl TestCase {
assert!(generated.is_some(), "could not find {case}'s generated file");
generated.unwrap().path()
}

/// Parse test directives from the source file
fn parse_directives(&self) -> Vec<TestDirective> {
let source = std::fs::read_to_string(&self.source)
.unwrap_or_else(|e| panic!("failed to read {}: {}", self.source.display(), e));

let mut directives = Vec::new();

// Regular expressions for matching directives
let run_pass = regex::Regex::new(r"^//@\s*run-pass").unwrap();
let stdout_re = regex::Regex::new(r"^//@\s*check-stdout:\s*(.*)").unwrap();
let stderr_re = regex::Regex::new(r"^//@\s*check-stderr:\s*(.*)").unwrap();
let exit_re = regex::Regex::new(r"^//@\s*exit-code:\s*(\d+)").unwrap();
let aux_re = regex::Regex::new(r"^//@\s*aux-build:\s*(.*)").unwrap();
// Regex to match any directive pattern
let directive_re = regex::Regex::new(r"^//@\s*([^:]+)").unwrap();

for (line_num, line) in source.lines().enumerate() {
if let Some(_cap) = run_pass.captures(line) {
directives.push(TestDirective::RunPass);
} else if let Some(cap) = stdout_re.captures(line) {
let content = cap[1].trim().to_string();
directives.push(TestDirective::CheckStdout(content));
} else if let Some(cap) = stderr_re.captures(line) {
let content = cap[1].trim().to_string();
directives.push(TestDirective::CheckStderr(content));
} else if let Some(cap) = exit_re.captures(line) {
if let Ok(code) = cap[1].parse() {
directives.push(TestDirective::ExitCode(code));
} else {
panic!(
"{}:{}: invalid exit code in directive",
self.source.display(),
line_num + 1
);
}
} else if let Some(cap) = aux_re.captures(line) {
let fname = cap[1].trim().to_string();
directives.push(TestDirective::AuxBuild(fname));
} else if let Some(cap) = directive_re.captures(line) {
let directive_name = cap[1].trim();
panic!(
"{}:{}: unknown directive '{}', supported directives are: check-stdout, check-stderr, exit-code, aux-build",
self.source.display(),
line_num + 1,
directive_name
);
}
}

directives
}
}

struct FileChecker {
Expand Down Expand Up @@ -288,27 +475,18 @@ impl FileChecker {
}
}

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);
}
}
/// Test directives that can appear in source files
#[derive(Debug)]
enum TestDirective {
/// Compile and run a testcase,
/// expect a success (exit with 0)
RunPass,
/// Expected stdout content
CheckStdout(String),
/// Expected stderr content
CheckStderr(String),
/// Expected exit code
ExitCode(i32),
/// Auxiliary build requirement
AuxBuild(String),
}
14 changes: 13 additions & 1 deletion crates/rustc_codegen_c/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -714,7 +714,19 @@ impl<'a, 'tcx, 'mx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx, 'mx> {
funclet: Option<&Self::Funclet>,
instance: Option<rustc_middle::ty::Instance<'tcx>>,
) -> Self::Value {
todo!()
use crate::rustc_codegen_ssa::traits::LayoutTypeMethods;

let fn_abi = fn_abi.unwrap();
let ret_ty = self.cx.immediate_backend_type(fn_abi.ret.layout);

let args = args.iter().map(|v| self.mcx.value(*v)).collect();

let call = self.mcx.call(self.mcx.value(llfn), args);
let ret = self.bb.0.next_local_var();

self.bb.0.push_stmt(self.mcx.decl_stmt(self.mcx.var(ret, ret_ty, Some(call))));

ret
}

fn zext(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value {
Expand Down
3 changes: 2 additions & 1 deletion crates/rustc_codegen_c/src/context/layout_type.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use rustc_abi::Abi;
use rustc_codegen_c_ast::ty::CTy;
use rustc_codegen_ssa::traits::LayoutTypeMethods;
use rustc_middle::ty::layout::TyAndLayout;
use rustc_middle::ty::Ty;
Expand All @@ -17,7 +18,7 @@ impl<'tcx, 'mx> LayoutTypeMethods<'tcx> for CodegenCx<'tcx, 'mx> {
}

fn fn_decl_backend_type(&self, fn_abi: &FnAbi<'tcx, Ty<'tcx>>) -> Self::Type {
todo!()
CTy::Void
}

fn fn_ptr_backend_type(&self, fn_abi: &FnAbi<'tcx, Ty<'tcx>>) -> Self::Type {
Expand Down
7 changes: 6 additions & 1 deletion crates/rustc_codegen_c/src/context/misc.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::cell::RefCell;

use rustc_codegen_c_ast::expr::CValue;
use rustc_codegen_ssa::traits::MiscMethods;
use rustc_hash::FxHashMap;
use rustc_middle::mir::mono::CodegenUnit;
Expand All @@ -19,7 +20,11 @@ impl<'tcx, 'mx> MiscMethods<'tcx> for CodegenCx<'tcx, 'mx> {
}

fn get_fn_addr(&self, instance: Instance<'tcx>) -> Self::Value {
todo!()
let funcs = self.mcx.module().funcs.borrow();
let path = self.tcx.def_path_debug_str(instance.def_id());
let name = path.split("::").last().unwrap();
let func = funcs.iter().find(|f| f.0.name == name).unwrap();
CValue::Func(func.0.name)
}

fn eh_personality(&self) -> Self::Value {
Expand Down
Loading