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
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
2 changes: 1 addition & 1 deletion bootstrap/src/main.rs
Original file line number Diff line number Diff line change
@@ -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:?}"),
}
}
266 changes: 222 additions & 44 deletions bootstrap/src/test.rs
Original file line number Diff line number Diff line change
@@ -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);
@@ -73,6 +73,7 @@ impl Run for TestCommand {
testcase.build_lib(manifest);
}
}
self.check_and_run_directives(&testcase);
}
}

@@ -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
@@ -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();
@@ -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);
}
}

@@ -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)]
@@ -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 {
@@ -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 {
@@ -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
@@ -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 {
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;
@@ -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 {
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;
@@ -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 {
Loading