Skip to content
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

Add basic Test harness #200

Merged
merged 6 commits into from
Mar 10, 2025
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
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,9 @@ jobs:
- name: Rustup toolchain install
uses: dtolnay/[email protected]
- uses: homebrew/actions/setup-homebrew@master
- name: Retreive cached dependecies
uses: Swatinem/rust-cache@v2
# makes melior be bugged with ods due to paths changing probably
#- name: Retreive cached dependecies
#uses: Swatinem/rust-cache@v2
- name: install llvm
run: brew install llvm@19
- name: Run tests
Expand Down
1 change: 1 addition & 0 deletions 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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ toml = "0.8.19"
test-case = "3.3.1"
typed-generational-arena = "0.2.6"

libloading = "0.8"

[build-dependencies]
lalrpop = "0.22.0"

Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ clean:
.PHONY: test
test: check-deps
cargo test --all-targets --all-features && \
echo "Testing example concrete project" && cd examples/project && cargo run -- build \
&& cd ../../std && cargo run -- build
echo "Testing example concrete project" && cd examples/project && cargo run -- build && \
echo "Testing concrete std" && cd ../../std && cargo run -- test

.PHONY: coverage
coverage: check-deps
Expand Down
19 changes: 19 additions & 0 deletions src/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,25 @@ pub fn lowering_error_to_report(error: LoweringError) -> Report<'static, FileSpa
.with_message(format!("expected type {}", expected))
.finish()
}
LoweringError::InvalidUnaryOp {
found_span: span,
found,
path,
} => {
let path = path.display().to_string();
let filespan = FileSpan::new(path.clone(), span.from..span.to);
let labels = vec![
Label::new(filespan.clone())
.with_message(format!("Invalid binary operation type '{}'", found))
.with_color(colors.next()),
];

Report::build(ReportKind::Error, filespan.clone())
.with_code("InvalidUnaryOp")
.with_labels(labels)
.with_message(format!("invalid binary operation type {}", found))
.finish()
}
LoweringError::UseOfUndeclaredVariable { span, name, path } => {
let path = path.display().to_string();
let filespan = FileSpan::new(path, span.from..span.to);
Expand Down
54 changes: 52 additions & 2 deletions src/codegen/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use std::collections::HashMap;

use crate::ir::{
AdtKind, BinOp, ConcreteIntrinsic, ConstValue, FnIndex, Function, IR, LocalKind, Module,
ModuleIndex, Operand, Place, PlaceElem, Rvalue, Span, Type as IRType, TypeIndex, ValueTree,
ModuleIndex, Operand, Place, PlaceElem, Rvalue, Span, Type as IRType, TypeIndex, UnOp,
ValueTree,
};
use ariadne::Source;
use melior::helpers::{ArithBlockExt, BuiltinBlockExt, GepIndex, LlvmBlockExt};
Expand Down Expand Up @@ -497,7 +498,7 @@ fn compile_rvalue<'c: 'b, 'b>(
Rvalue::Use(info) => compile_load_operand(ctx, block, info, locals)?,
Rvalue::LogicOp(_, _) => todo!(),
Rvalue::BinaryOp(op, (lhs, rhs)) => compile_binop(ctx, block, op, lhs, rhs, locals)?,
Rvalue::UnaryOp(_, _) => todo!(),
Rvalue::UnaryOp(op, lhs) => compile_unop(ctx, block, op, lhs, locals)?,
Rvalue::Ref(_mutability, place) => {
let mut value = locals[&place.local];
let mut local_type_idx = ctx.get_fn_body().locals[place.local].ty;
Expand Down Expand Up @@ -999,6 +1000,55 @@ fn compile_binop<'c: 'b, 'b>(
})
}

/// Compiles a unary operation.
fn compile_unop<'c: 'b, 'b>(
ctx: &'c FunctionCodegenCtx,
block: &'b Block<'c>,
op: &UnOp,
lhs: &Operand,
locals: &HashMap<usize, Value<'c, '_>>,
) -> Result<(Value<'c, 'b>, TypeIndex), CodegenError> {
let (lhs, lhs_type_idx) = compile_load_operand(ctx, block, lhs, locals)?;
let location = Location::unknown(ctx.context());
let lhs_ty = ctx.module.get_type(lhs_type_idx);
let lhs_type = compile_type(ctx.module, &lhs_ty);

let is_float = matches!(lhs_ty, IRType::Float(_));

Ok(match op {
UnOp::Not => {
let k0 = block.const_int_from_type(ctx.context(), location, 0, lhs_type)?;
let value = if is_float {
block.append_op_result(arith::cmpf(
ctx.context(),
arith::CmpfPredicate::Ueq,
lhs,
k0,
location,
))?
} else {
block.append_op_result(arith::cmpi(
ctx.context(),
arith::CmpiPredicate::Eq,
lhs,
k0,
location,
))?
};
(value, lhs_type_idx)
}
UnOp::Neg => {
let value = if is_float {
block.append_op_result(arith::negf(lhs, location))?
} else {
let k1 = block.const_int_from_type(ctx.context(), location, -1, lhs_type)?;
block.append_op_result(arith::muli(lhs, k1, location))?
};
(value, lhs_type_idx)
}
})
}

fn compile_load_operand<'c: 'b, 'b>(
ctx: &'c FunctionCodegenCtx,
block: &'b Block<'c>,
Expand Down
119 changes: 100 additions & 19 deletions src/driver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use owo_colors::OwoColorize;
use std::io::Read;
use std::os::unix::process::CommandExt;
use std::path::Path;
use std::sync::Arc;
use std::{collections::HashMap, fs::File, path::PathBuf, time::Instant};
use tracing::debug;

Expand Down Expand Up @@ -49,6 +50,8 @@ enum Commands {
Build(BuildArgs),
/// Run a project or file
Run(BuildArgs),
/// Test a project or file.
Test(BuildArgs),
}

#[derive(Args, Debug)]
Expand Down Expand Up @@ -219,11 +222,11 @@ pub fn main() -> Result<()> {
path.join("src").join("main.con"),
format!(
r#"
mod {} {{
pub fn main() -> i32 {{
return 0;
}}
}}"#,
mod {} {{
pub fn main() -> i32 {{
return 0;
}}
}}"#,
name
),
)?;
Expand All @@ -232,11 +235,11 @@ mod {} {{
path.join("src").join("lib.con"),
format!(
r#"
mod {} {{
pub fn hello_world() -> i32 {{
return 0;
}}
}}"#,
mod {} {{
pub fn hello_world() -> i32 {{
return 0;
}}
}}"#,
name
),
)?;
Expand Down Expand Up @@ -272,15 +275,77 @@ mod {} {{
handle_build(args)?;
}
Commands::Run(args) => {
let output = handle_build(args)?;
let output = handle_build(args)?.0;
println!();
Err(std::process::Command::new(output).exec())?;
}
Commands::Test(mut args) => {
args.lib = true;
let (output, tests) = handle_build(args)?;
println!();

let tests = Arc::new(tests);

println!("Running {} tests", tests.len());

let mut passed = 0;

let lib = Arc::new(unsafe {
libloading::os::unix::Library::new(output).expect("failed to load")
});

for test in tests.iter() {
print!("test {} ... ", test.symbol);
let test_fn = unsafe {
lib.get::<unsafe extern "C" fn() -> i32>(test.mangled_symbol.as_bytes())
};

if test_fn.is_err() {
println!("{}", "err".red());
eprintln!("Symbol not found: {:?}", test_fn);
continue;
}

let test_fn = test_fn.unwrap();

let result = unsafe { (test_fn)() };

if result == 0 {
passed += 1;
println!("{}", "ok".green());
} else {
println!("{}", "err".red());
}
}

println!();
if !tests.is_empty() {
println!(
"test result: {}. {} passed; {} failed; ({:.2}%)",
if passed == tests.len() {
"ok".green().to_string()
} else {
"err".red().to_string()
},
passed,
tests.len() - passed,
((passed as f64 / tests.len() as f64) * 100.0).bold()
);
}

return Ok(());
}
}

Ok(())
}

#[derive(Debug, Clone)]
pub struct TestInfo {
pub mangled_symbol: String,
pub symbol: String,
}

fn handle_build(
BuildArgs {
path,
Expand All @@ -295,7 +360,7 @@ fn handle_build(
lib,
check,
}: BuildArgs,
) -> Result<PathBuf> {
) -> Result<(PathBuf, Vec<TestInfo>)> {
match path {
// Single file compilation
Some(input) => {
Expand Down Expand Up @@ -332,7 +397,7 @@ fn handle_build(
);

let start = Instant::now();
let object = compile(&compile_args)?;
let (object, tests) = compile(&compile_args)?;

if lib {
link_shared_lib(&[object.clone()], &output)?;
Expand All @@ -352,7 +417,7 @@ fn handle_build(
if release { "release" } else { "dev" },
);

Ok(output)
Ok((output, tests))
}
// Project compilation.
None => {
Expand Down Expand Up @@ -393,7 +458,7 @@ fn handle_build(
if !target_dir.exists() {
std::fs::create_dir_all(&target_dir)?;
}
let output = target_dir.join(config.package.name);
let mut output = target_dir.join(config.package.name);
let (profile, profile_name) = if let Some(profile) = profile {
(
config
Expand Down Expand Up @@ -425,6 +490,8 @@ fn handle_build(

let start = Instant::now();

let mut tests = Vec::new();

for file in [main_ed, lib_ed] {
if file.exists() {
let is_lib = file.file_stem().unwrap() == "lib";
Expand Down Expand Up @@ -452,13 +519,18 @@ fn handle_build(
mlir,
check,
};
let object = compile(&compile_args)?;
let (object, file_tests) = compile(&compile_args)?;
tests.extend(file_tests);

if compile_args.library {
link_shared_lib(&[object], &compile_args.output)?;
} else {
link_binary(&[object], &compile_args.output)?;
}

if is_lib {
output = compile_args.output;
}
}
}
let elapsed = start.elapsed();
Expand All @@ -478,7 +550,7 @@ fn handle_build(
}
);

Ok(output)
Ok((output, tests))
}
}
}
Expand Down Expand Up @@ -560,7 +632,7 @@ pub fn parse_file(mut path: PathBuf, db: &dyn salsa::Database) -> Result<Compila
Ok(compile_unit)
}

pub fn compile(args: &CompilerArgs) -> Result<PathBuf> {
pub fn compile(args: &CompilerArgs) -> Result<(PathBuf, Vec<TestInfo>)> {
let start_time = Instant::now();

let db = crate::driver::db::DatabaseImpl::default();
Expand Down Expand Up @@ -632,5 +704,14 @@ pub fn compile(args: &CompilerArgs) -> Result<PathBuf> {
let elapsed = start_time.elapsed();
tracing::debug!("Done in {:?}", elapsed);

Ok(object_path)
let mut test_names = Vec::new();
for t in &compile_unit_ir.tests {
let f = compile_unit_ir.functions[*t].as_ref().unwrap();
test_names.push(TestInfo {
mangled_symbol: f.name.clone(),
symbol: f.debug_name.clone().unwrap(),
});
}

Ok((object_path, test_names))
}
6 changes: 6 additions & 0 deletions src/ir/lowering/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ pub enum LoweringError {
expected_span: Option<Span>,
path: PathBuf,
},
#[error("invalid unary op on given type")]
InvalidUnaryOp {
found_span: Span,
found: String,
path: PathBuf,
},
#[error("extern function {name:?} has a body")]
ExternFnWithBody {
span: Span,
Expand Down
Loading
Loading