feat: better cli and add tests

This commit is contained in:
Edgar 2024-02-14 08:53:47 +01:00
parent 4a83cfd6f9
commit 6fc3e2ba6d
No known key found for this signature in database
GPG key ID: 70ADAE8F35904387
12 changed files with 361 additions and 64 deletions

101
Cargo.lock generated
View file

@ -154,17 +154,14 @@ version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
[[package]]
name = "bumpalo"
version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
[[package]]
name = "cc"
version = "1.0.85"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b918671670962b48bc23753aef0c51d072dca6f52f01f800854ada6ddb7f7d3"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"libc",
]
[[package]]
name = "cfg-if"
@ -325,8 +322,6 @@ dependencies = [
name = "edlang_codegen_llvm"
version = "0.0.1-alpha.3"
dependencies = [
"bumpalo",
"cc",
"edlang_ir",
"edlang_parser",
"edlang_session",
@ -350,6 +345,8 @@ dependencies = [
"edlang_lowering",
"edlang_parser",
"edlang_session",
"tempfile",
"test-case",
"tracing",
"tracing-subscriber",
]
@ -416,6 +413,16 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "eyre"
version = "0.6.12"
@ -426,6 +433,12 @@ dependencies = [
"once_cell",
]
[[package]]
name = "fastrand"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
[[package]]
name = "fixedbitset"
version = "0.4.2"
@ -469,9 +482,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3"
checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd"
[[package]]
name = "indenter"
@ -596,6 +609,12 @@ dependencies = [
"redox_syscall",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "llvm-sys"
version = "170.0.1"
@ -886,6 +905,19 @@ version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustix"
version = "0.38.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
dependencies = [
"bitflags 2.4.2",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
]
[[package]]
name = "rustversion"
version = "1.0.14"
@ -955,6 +987,18 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67"
dependencies = [
"cfg-if",
"fastrand",
"rustix",
"windows-sys 0.52.0",
]
[[package]]
name = "term"
version = "0.7.0"
@ -966,6 +1010,39 @@ dependencies = [
"winapi",
]
[[package]]
name = "test-case"
version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8"
dependencies = [
"test-case-macros",
]
[[package]]
name = "test-case-core"
version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f"
dependencies = [
"cfg-if",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "test-case-macros"
version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
dependencies = [
"proc-macro2",
"quote",
"syn",
"test-case-core",
]
[[package]]
name = "thiserror"
version = "1.0.57"

View file

@ -13,7 +13,6 @@ repository = "https://github.com/edg-l/edlang"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bumpalo = { version = "3.14.0", features = ["std"] }
edlang_ir = { version = "0.0.1-alpha.3", path = "../edlang_ir" }
edlang_parser = { version = "0.0.1-alpha.3", path = "../edlang_parser" }
edlang_session = { version = "0.0.1-alpha.3", path = "../edlang_session" }
@ -21,6 +20,3 @@ llvm-sys = "170.0.1"
inkwell = { git = "https://github.com/TheDan64/inkwell", rev = "c044e3cd8d92972ca75b374fb6c5a2794f5b53ca", features = ["llvm17-0"] }
tracing = { workspace = true }
edlang_span = { version = "0.0.1-alpha.3", path = "../edlang_span" }
[build-dependencies]
cc = "1.0.83"

View file

@ -17,7 +17,7 @@ use inkwell::{
};
use ir::{LocalKind, ModuleBody, ProgramBody, TypeInfo, ValueTree};
use llvm_sys::debuginfo::LLVMDIFlagPublic;
use tracing::info;
use tracing::{info, trace};
#[derive(Debug, Clone, Copy)]
struct CompileCtx<'a> {
@ -78,8 +78,17 @@ pub fn compile(session: &Session, program: &ProgramBody) -> Result<PathBuf, Box<
&triple,
cpu_name.to_str()?,
cpu_features.to_str()?,
inkwell::OptimizationLevel::Aggressive,
inkwell::targets::RelocMode::Default,
match session.optlevel {
edlang_session::OptLevel::None => inkwell::OptimizationLevel::None,
edlang_session::OptLevel::Less => inkwell::OptimizationLevel::Less,
edlang_session::OptLevel::Default => inkwell::OptimizationLevel::Default,
edlang_session::OptLevel::Aggressive => inkwell::OptimizationLevel::Aggressive,
},
if session.library {
inkwell::targets::RelocMode::DynamicNoPic
} else {
inkwell::targets::RelocMode::Default
},
inkwell::targets::CodeModel::Default,
)
.unwrap();
@ -131,15 +140,20 @@ pub fn compile(session: &Session, program: &ProgramBody) -> Result<PathBuf, Box<
module_ctx.di_builder.finalize();
module_ctx.module.verify()?;
module_ctx
.module
.print_to_file(session.output_file.with_extension("ll"))?;
if session.output_llvm {
module_ctx
.module
.print_to_file(session.output_file.with_extension("ll"))?;
}
if session.output_asm {
machine.write_to_file(
&module_ctx.module,
inkwell::targets::FileType::Assembly,
&session.output_file.with_extension("asm"),
)?;
}
machine.write_to_file(
&module_ctx.module,
inkwell::targets::FileType::Assembly,
&session.output_file.with_extension("asm"),
)?;
machine.write_to_file(
&module_ctx.module,
inkwell::targets::FileType::Object,
@ -154,7 +168,7 @@ pub fn compile(session: &Session, program: &ProgramBody) -> Result<PathBuf, Box<
fn compile_module(ctx: &mut ModuleCompileCtx, module_id: DefId) {
let module = ctx.ctx.program.modules.get(&module_id).unwrap();
info!("compiling module");
trace!("compiling module");
for id in module.functions.iter() {
compile_fn_signature(ctx, *id);
}
@ -167,7 +181,7 @@ fn compile_module(ctx: &mut ModuleCompileCtx, module_id: DefId) {
fn compile_fn_signature(ctx: &ModuleCompileCtx<'_, '_>, fn_id: DefId) {
let (arg_types, ret_type) = ctx.ctx.program.function_signatures.get(&fn_id).unwrap();
let body = ctx.ctx.program.functions.get(&fn_id).unwrap();
info!("compiling fn sig: {}", body.name);
trace!("compiling fn sig: {}", body.name);
let args: Vec<BasicMetadataTypeEnum> = arg_types
.iter()
@ -242,7 +256,7 @@ fn compile_fn_signature(ctx: &ModuleCompileCtx<'_, '_>, fn_id: DefId) {
fn compile_fn(ctx: &ModuleCompileCtx, fn_id: DefId) -> Result<(), BuilderError> {
let body = ctx.ctx.program.functions.get(&fn_id).unwrap();
info!("compiling fn body: {}", body.name);
trace!("compiling fn body: {}", body.name);
let fn_value = ctx.module.get_function(&body.name).unwrap();
let di_program = fn_value.get_subprogram().unwrap();
@ -343,14 +357,14 @@ fn compile_fn(ctx: &ModuleCompileCtx, fn_id: DefId) -> Result<(), BuilderError>
ctx.builder.build_unconditional_branch(blocks[0])?;
for (block, llvm_block) in body.blocks.iter().zip(&blocks) {
info!("compiling block");
trace!("compiling block");
ctx.builder.position_at_end(*llvm_block);
for stmt in &block.statements {
if let Some(span) = stmt.span {
debug_loc = ctx.set_debug_loc(debug_loc.get_scope(), span);
}
info!("compiling stmt");
trace!("compiling stmt");
match &stmt.kind {
ir::StatementKind::Assign(place, rvalue) => {
let local = &body.locals[place.local];
@ -409,7 +423,7 @@ fn compile_fn(ctx: &ModuleCompileCtx, fn_id: DefId) -> Result<(), BuilderError>
}
}
info!("compiling terminator");
trace!("compiling terminator");
match &block.terminator {
ir::Terminator::Target(id) => {
ctx.builder.build_unconditional_branch(blocks[*id])?;
@ -429,18 +443,14 @@ fn compile_fn(ctx: &ModuleCompileCtx, fn_id: DefId) -> Result<(), BuilderError>
discriminator,
targets,
} => {
let (condition, condition_ty) =
let (condition, _condition_ty) =
compile_load_operand(ctx, fn_id, &locals, discriminator)?;
let cond = condition.into_int_value();
dbg!(&cond);
dbg!(&condition_ty);
let mut cases = Vec::new();
for (value, target) in targets.values.iter().zip(targets.targets.iter()) {
let target = *target;
let ty_kind = value.get_type();
dbg!(&ty_kind);
let block = blocks[target];
let value = compile_value(
ctx,
@ -451,7 +461,6 @@ fn compile_fn(ctx: &ModuleCompileCtx, fn_id: DefId) -> Result<(), BuilderError>
},
)?
.into_int_value();
dbg!(&value);
cases.push((value, block));
}

View file

@ -25,3 +25,7 @@ edlang_parser = { version = "0.0.1-alpha.3", path = "../edlang_parser" }
edlang_session = { version = "0.0.1-alpha.3", path = "../edlang_session" }
tracing = { workspace = true }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
[dev-dependencies]
tempfile = "3.10.0"
test-case = "3.3.1"

View file

@ -7,7 +7,7 @@ use edlang_lowering::lower_modules;
use edlang_session::{DebugInfo, OptLevel, Session};
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None, bin_name = "edlang")]
#[command(author, version, about = "edlang compiler driver", long_about = None, bin_name = "edlang")]
pub struct CompilerArgs {
/// The input file.
input: PathBuf,
@ -16,18 +16,33 @@ pub struct CompilerArgs {
#[arg(short, long, default_value_t = false)]
release: bool,
/// Set the optimization level, 0,1,2,3
#[arg(short = 'O', long)]
optlevel: Option<u8>,
/// Always add debug info
#[arg(long)]
debug_info: Option<bool>,
/// Build as a library.
#[arg(short, long, default_value_t = false)]
library: bool,
#[arg(long, default_value_t = false)]
mlir: bool,
/// Print the edlang AST
#[arg(long, default_value_t = false)]
ast: bool,
/// Print the edlang IR
#[arg(long, default_value_t = false)]
ir: bool,
/// Output llvm ir
#[arg(long, default_value_t = false)]
llvm: bool,
/// Output asm
#[arg(long, default_value_t = false)]
asm: bool,
}
pub fn main() -> Result<(), Box<dyn Error>> {
@ -59,19 +74,34 @@ pub fn main() -> Result<(), Box<dyn Error>> {
}
let output_file = target_dir.join(PathBuf::from(args.input.file_name().unwrap()));
let output_file = if args.library {
output_file.with_extension("so")
output_file.with_extension(Session::get_platform_library_ext())
} else if cfg!(target_os = "windows") {
output_file.with_extension("exe")
} else {
output_file.with_extension("")
};
let session = Session {
file_path: args.input,
debug_info: if args.release {
debug_info: if let Some(debug_info) = args.debug_info {
if debug_info {
DebugInfo::Full
} else {
DebugInfo::None
}
} else if args.release {
DebugInfo::None
} else {
DebugInfo::Full
},
optlevel: if args.release {
optlevel: if let Some(optlevel) = args.optlevel {
match optlevel {
0 => OptLevel::None,
1 => OptLevel::Less,
2 => OptLevel::Default,
_ => OptLevel::Aggressive,
}
} else if args.release {
OptLevel::Aggressive
} else {
OptLevel::None
@ -80,8 +110,15 @@ pub fn main() -> Result<(), Box<dyn Error>> {
library: args.library,
target_dir,
output_file,
output_asm: args.asm,
output_llvm: args.llvm,
};
tracing::debug!("Compiling with session: {:#?}", session);
tracing::debug!("Input file: {:#?}", session.file_path);
tracing::debug!("Target dir: {:#?}", session.target_dir);
tracing::debug!("Output file: {:#?}", session.output_file);
tracing::debug!("Is library: {:#?}", session.library);
tracing::debug!("Optlevel: {:#?}", session.optlevel);
tracing::debug!("Debug Info: {:#?}", session.debug_info);
if args.ast {
println!("{:#?}", module);
@ -98,9 +135,9 @@ pub fn main() -> Result<(), Box<dyn Error>> {
let object_path = edlang_codegen_llvm::compile(&session, &program_ir)?;
if session.library {
link_shared_lib(&object_path, &session.output_file.with_extension("so"))?;
link_shared_lib(&object_path, &session.output_file)?;
} else {
link_binary(&object_path, &session.output_file.with_extension(""))?;
link_binary(&object_path, &session.output_file)?;
}
let elapsed = start_time.elapsed();

View file

@ -0,0 +1,88 @@
use std::{
borrow::Cow,
fmt,
path::{Path, PathBuf},
process::Output,
};
use ariadne::Source;
use edlang_codegen_llvm::linker::{link_binary, link_shared_lib};
use edlang_lowering::lower_modules;
use edlang_session::{DebugInfo, OptLevel, Session};
use tempfile::TempDir;
#[derive(Debug, Clone)]
struct TestError(Cow<'static, str>);
impl fmt::Display for TestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for TestError {}
#[derive(Debug)]
pub struct CompileResult {
pub folder: TempDir,
pub object_file: PathBuf,
pub binary_file: PathBuf,
}
pub fn compile_program(
source: &str,
name: &str,
library: bool,
) -> Result<CompileResult, Box<dyn std::error::Error>> {
let module = edlang_parser::parse_ast(source).unwrap();
let test_dir = tempfile::tempdir()?;
let test_dir_path = test_dir.path();
let target_dir = test_dir_path.join("build_artifacts/");
if !target_dir.exists() {
std::fs::create_dir_all(&target_dir)?;
}
let output_file = target_dir.join(PathBuf::from(name));
let output_file = if library {
output_file.with_extension(Session::get_platform_library_ext())
} else if cfg!(target_os = "windows") {
output_file.with_extension("exe")
} else {
output_file.with_extension("")
};
let session = Session {
file_path: PathBuf::from(name),
debug_info: DebugInfo::Full,
optlevel: OptLevel::Default,
source: Source::from(source.to_string()),
library,
target_dir,
output_file,
output_llvm: false,
output_asm: false,
};
let program_ir = lower_modules(&[module]);
let object_path = edlang_codegen_llvm::compile(&session, &program_ir)?;
if library {
link_shared_lib(&object_path, &session.output_file)?;
} else {
link_binary(&object_path, &session.output_file)?;
}
Ok(CompileResult {
folder: test_dir,
object_file: object_path,
binary_file: session.output_file,
})
}
pub fn run_program(program: &Path, args: &[&str]) -> Result<Output, std::io::Error> {
std::process::Command::new(program)
.args(args)
.spawn()?
.wait_with_output()
}

View file

@ -0,0 +1,21 @@
use crate::common::{compile_program, run_program};
use test_case::test_case;
mod common;
#[test_case(include_str!("programs/simple.ed"), "simple", false, 0, &["1"] ; "simple.ed 1")]
#[test_case(include_str!("programs/simple.ed"), "simple", false, 1, &["a", "b"] ; "simple.ed 3")]
#[test_case(include_str!("programs/basic_ifs.ed"), "basic_ifs", false, 9, &[] ; "basic_ifs")]
fn example_tests(source: &str, name: &str, is_library: bool, status_code: i32, args: &[&str]) {
let program = compile_program(source, name, is_library).unwrap();
assert!(program.binary_file.exists(), "program not compiled");
let result = run_program(&program.binary_file, args).unwrap();
assert_eq!(
result.status.code().unwrap(),
status_code,
"Program {} returned a unexpected status code",
name
);
}

View file

@ -0,0 +1,19 @@
mod Main {
fn add(a: i32, b: i32) -> i32 {
return a + b;
}
fn check(a: i32) -> i32 {
if a == 2 {
return a;
} else {
return 0;
}
}
pub fn main() -> i32 {
let x: i32 = 2 + 3;
let y: i32 = add(x, 4);
return y;
}
}

View file

@ -0,0 +1,11 @@
mod Main {
pub fn main(argc: i64) -> i64 {
let mut a: i64 = 0;
if argc > 2 {
a = 1;
}
return a;
}
}

View file

@ -210,12 +210,20 @@ fn lower_if_stmt(builder: &mut BodyBuilder, info: &ast::IfStmt, ret_type: &TypeI
}
// keet idx to change terminator
let last_then_block_idx = builder.body.blocks.len();
let statements = std::mem::take(&mut builder.statements);
builder.body.blocks.push(BasicBlock {
statements: statements.into(),
terminator: Terminator::Unreachable,
});
let last_then_block_idx = if !matches!(
builder.body.blocks.last().unwrap().terminator,
Terminator::Return
) {
let idx = builder.body.blocks.len();
let statements = std::mem::take(&mut builder.statements);
builder.body.blocks.push(BasicBlock {
statements: statements.into(),
terminator: Terminator::Unreachable,
});
Some(idx)
} else {
None
};
let first_else_block_idx = builder.body.blocks.len();
@ -225,12 +233,20 @@ fn lower_if_stmt(builder: &mut BodyBuilder, info: &ast::IfStmt, ret_type: &TypeI
}
}
let last_else_block_idx = builder.body.blocks.len();
let statements = std::mem::take(&mut builder.statements);
builder.body.blocks.push(BasicBlock {
statements: statements.into(),
terminator: Terminator::Unreachable,
});
let last_else_block_idx = if !matches!(
builder.body.blocks.last().unwrap().terminator,
Terminator::Return
) {
let idx = builder.body.blocks.len();
let statements = std::mem::take(&mut builder.statements);
builder.body.blocks.push(BasicBlock {
statements: statements.into(),
terminator: Terminator::Unreachable,
});
Some(idx)
} else {
None
};
let targets = SwitchTarget {
values: vec![TypeKind::Bool.get_falsy_value()],
@ -244,8 +260,13 @@ fn lower_if_stmt(builder: &mut BodyBuilder, info: &ast::IfStmt, ret_type: &TypeI
builder.body.blocks[current_block_idx].terminator = kind;
let next_block_idx = builder.body.blocks.len();
builder.body.blocks[last_then_block_idx].terminator = Terminator::Target(next_block_idx);
builder.body.blocks[last_else_block_idx].terminator = Terminator::Target(next_block_idx);
if let Some(idx) = last_then_block_idx {
builder.body.blocks[idx].terminator = Terminator::Target(next_block_idx);
}
if let Some(idx) = last_else_block_idx {
builder.body.blocks[idx].terminator = Terminator::Target(next_block_idx);
}
}
fn lower_let(builder: &mut BodyBuilder, info: &ast::LetStmt) {

View file

@ -12,6 +12,20 @@ pub struct Session {
/// The directory where to store artifacts and intermediate files such as object files.
pub target_dir: PathBuf,
pub output_file: PathBuf,
pub output_llvm: bool,
pub output_asm: bool,
}
impl Session {
pub fn get_platform_library_ext() -> &'static str {
if cfg!(target_os = "macos") {
"dylib"
} else if cfg!(target_os = "windows") {
"dll"
} else {
"so"
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Hash)]

View file

@ -11,7 +11,7 @@ mod Main {
}
}
fn main() -> i32 {
pub fn main(argc: i32) -> i32 {
let x: i32 = 2 + 3;
let y: i32 = add(x, 4);
return y;