feat(gs/achilles): Implement extern decls, for glibc functions
Implement extern decls, which codegen to LLVM as forward-declared functions, and use these as a hook into calling glibc functions. We can print to the terminal now! The integration tests can test this now. Change-Id: I70af4546b417b888ad9fbb18798db240f77f4e71 Reviewed-on: https://cl.tvl.fyi/c/depot/+/2614 Tested-by: BuildkiteCI Reviewed-by: glittershark <grfn@gws.fyi>
This commit is contained in:
parent
fec6595d21
commit
2c838ab845
9 changed files with 147 additions and 28 deletions
1
users/glittershark/achilles/ach/.gitignore
vendored
1
users/glittershark/achilles/ach/.gitignore
vendored
|
@ -3,3 +3,4 @@
|
||||||
|
|
||||||
functions
|
functions
|
||||||
simple
|
simple
|
||||||
|
externs
|
||||||
|
|
5
users/glittershark/achilles/ach/externs.ach
Normal file
5
users/glittershark/achilles/ach/externs.ach
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
extern puts : fn cstring -> int
|
||||||
|
|
||||||
|
fn main =
|
||||||
|
let _ = puts "foobar"
|
||||||
|
in 0
|
|
@ -219,12 +219,19 @@ pub enum Decl<'a, T> {
|
||||||
body: Box<Expr<'a, T>>,
|
body: Box<Expr<'a, T>>,
|
||||||
type_: T,
|
type_: T,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Extern {
|
||||||
|
name: Ident<'a>,
|
||||||
|
arg_types: Vec<T>,
|
||||||
|
ret_type: T,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> Decl<'a, T> {
|
impl<'a, T> Decl<'a, T> {
|
||||||
pub fn type_(&self) -> &T {
|
pub fn type_(&self) -> Option<&T> {
|
||||||
match self {
|
match self {
|
||||||
Decl::Fun { type_, .. } => type_,
|
Decl::Fun { type_, .. } => Some(type_),
|
||||||
|
Decl::Extern { .. } => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,6 +254,15 @@ impl<'a, T> Decl<'a, T> {
|
||||||
body: Box::new(body.traverse_type(f.clone())?),
|
body: Box::new(body.traverse_type(f.clone())?),
|
||||||
type_: f(type_)?,
|
type_: f(type_)?,
|
||||||
}),
|
}),
|
||||||
|
Decl::Extern {
|
||||||
|
name,
|
||||||
|
arg_types,
|
||||||
|
ret_type,
|
||||||
|
} => Ok(Decl::Extern {
|
||||||
|
name,
|
||||||
|
arg_types: arg_types.into_iter().map(f.clone()).try_collect()?,
|
||||||
|
ret_type: f(ret_type)?,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -265,8 +265,18 @@ impl<'a> Fun<'a> {
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub enum Decl<'a> {
|
pub enum Decl<'a> {
|
||||||
Fun { name: Ident<'a>, body: Fun<'a> },
|
Fun {
|
||||||
Ascription { name: Ident<'a>, type_: Type<'a> },
|
name: Ident<'a>,
|
||||||
|
body: Fun<'a>,
|
||||||
|
},
|
||||||
|
Ascription {
|
||||||
|
name: Ident<'a>,
|
||||||
|
type_: Type<'a>,
|
||||||
|
},
|
||||||
|
Extern {
|
||||||
|
name: Ident<'a>,
|
||||||
|
type_: FunctionType<'a>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
////
|
////
|
||||||
|
|
|
@ -9,7 +9,7 @@ use inkwell::module::Module;
|
||||||
use inkwell::support::LLVMString;
|
use inkwell::support::LLVMString;
|
||||||
use inkwell::types::{BasicType, BasicTypeEnum, FunctionType, IntType};
|
use inkwell::types::{BasicType, BasicTypeEnum, FunctionType, IntType};
|
||||||
use inkwell::values::{AnyValueEnum, BasicValueEnum, FunctionValue};
|
use inkwell::values::{AnyValueEnum, BasicValueEnum, FunctionValue};
|
||||||
use inkwell::IntPredicate;
|
use inkwell::{AddressSpace, IntPredicate};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::ast::hir::{Binding, Decl, Expr};
|
use crate::ast::hir::{Binding, Decl, Expr};
|
||||||
|
@ -249,6 +249,26 @@ impl<'ctx, 'ast> Codegen<'ctx, 'ast> {
|
||||||
Ok(self.finish_function(&res))
|
Ok(self.finish_function(&res))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn codegen_extern(
|
||||||
|
&mut self,
|
||||||
|
name: &str,
|
||||||
|
args: &'ast [Type],
|
||||||
|
ret: &'ast Type,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.module.add_function(
|
||||||
|
name,
|
||||||
|
self.codegen_type(ret).fn_type(
|
||||||
|
&args
|
||||||
|
.iter()
|
||||||
|
.map(|t| self.codegen_type(t))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn codegen_decl(&mut self, decl: &'ast Decl<'ast, Type>) -> Result<()> {
|
pub fn codegen_decl(&mut self, decl: &'ast Decl<'ast, Type>) -> Result<()> {
|
||||||
match decl {
|
match decl {
|
||||||
Decl::Fun {
|
Decl::Fun {
|
||||||
|
@ -257,6 +277,11 @@ impl<'ctx, 'ast> Codegen<'ctx, 'ast> {
|
||||||
self.codegen_function(name.into(), args, body)?;
|
self.codegen_function(name.into(), args, body)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Decl::Extern {
|
||||||
|
name,
|
||||||
|
arg_types,
|
||||||
|
ret_type,
|
||||||
|
} => self.codegen_extern(name.into(), arg_types, ret_type),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,7 +299,18 @@ impl<'ctx, 'ast> Codegen<'ctx, 'ast> {
|
||||||
|
|
||||||
fn codegen_type(&self, type_: &'ast Type) -> BasicTypeEnum<'ctx> {
|
fn codegen_type(&self, type_: &'ast Type) -> BasicTypeEnum<'ctx> {
|
||||||
// TODO
|
// TODO
|
||||||
self.context.i64_type().into()
|
match type_ {
|
||||||
|
Type::Int => self.context.i64_type().into(),
|
||||||
|
Type::Float => self.context.f64_type().into(),
|
||||||
|
Type::Bool => self.context.bool_type().into(),
|
||||||
|
Type::CString => self
|
||||||
|
.context
|
||||||
|
.i8_type()
|
||||||
|
.ptr_type(AddressSpace::Generic)
|
||||||
|
.into(),
|
||||||
|
Type::Function(_) => todo!(),
|
||||||
|
Type::Var(_) => unreachable!(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn codegen_int_type(&self, type_: &'ast Type) -> IntType<'ctx> {
|
fn codegen_int_type(&self, type_: &'ast Type) -> IntType<'ctx> {
|
||||||
|
|
|
@ -9,6 +9,7 @@ mod type_;
|
||||||
|
|
||||||
use crate::ast::{Arg, Decl, Fun, Ident};
|
use crate::ast::{Arg, Decl, Fun, Ident};
|
||||||
pub use expr::expr;
|
pub use expr::expr;
|
||||||
|
use type_::function_type;
|
||||||
pub use type_::type_;
|
pub use type_::type_;
|
||||||
|
|
||||||
pub type Error = nom::Err<nom::error::Error<String>>;
|
pub type Error = nom::Err<nom::error::Error<String>>;
|
||||||
|
@ -79,9 +80,24 @@ named!(arg(&str) -> Arg, alt!(
|
||||||
ascripted_arg
|
ascripted_arg
|
||||||
));
|
));
|
||||||
|
|
||||||
|
named!(extern_decl(&str) -> Decl, do_parse!(
|
||||||
|
complete!(tag!("extern"))
|
||||||
|
>> multispace1
|
||||||
|
>> name: ident
|
||||||
|
>> multispace0
|
||||||
|
>> char!(':')
|
||||||
|
>> multispace0
|
||||||
|
>> type_: function_type
|
||||||
|
>> multispace0
|
||||||
|
>> (Decl::Extern {
|
||||||
|
name,
|
||||||
|
type_
|
||||||
|
})
|
||||||
|
));
|
||||||
|
|
||||||
named!(fun_decl(&str) -> Decl, do_parse!(
|
named!(fun_decl(&str) -> Decl, do_parse!(
|
||||||
complete!(tag!("fn"))
|
complete!(tag!("fn"))
|
||||||
>> multispace0
|
>> multispace1
|
||||||
>> name: ident
|
>> name: ident
|
||||||
>> multispace1
|
>> multispace1
|
||||||
>> args: separated_list0!(multispace1, arg)
|
>> args: separated_list0!(multispace1, arg)
|
||||||
|
@ -112,7 +128,8 @@ named!(ascription_decl(&str) -> Decl, do_parse!(
|
||||||
|
|
||||||
named!(pub decl(&str) -> Decl, alt!(
|
named!(pub decl(&str) -> Decl, alt!(
|
||||||
ascription_decl |
|
ascription_decl |
|
||||||
fun_decl
|
fun_decl |
|
||||||
|
extern_decl
|
||||||
));
|
));
|
||||||
|
|
||||||
named!(pub toplevel(&str) -> Vec<Decl>, terminated!(many0!(decl), multispace0));
|
named!(pub toplevel(&str) -> Vec<Decl>, terminated!(many0!(decl), multispace0));
|
||||||
|
|
|
@ -4,7 +4,7 @@ use nom::{alt, delimited, do_parse, map, named, opt, separated_list0, tag, termi
|
||||||
use super::ident;
|
use super::ident;
|
||||||
use crate::ast::{FunctionType, Type};
|
use crate::ast::{FunctionType, Type};
|
||||||
|
|
||||||
named!(function_type(&str) -> Type, do_parse!(
|
named!(pub function_type(&str) -> FunctionType, do_parse!(
|
||||||
tag!("fn")
|
tag!("fn")
|
||||||
>> multispace1
|
>> multispace1
|
||||||
>> args: map!(opt!(terminated!(separated_list0!(
|
>> args: map!(opt!(terminated!(separated_list0!(
|
||||||
|
@ -18,10 +18,10 @@ named!(function_type(&str) -> Type, do_parse!(
|
||||||
>> tag!("->")
|
>> tag!("->")
|
||||||
>> multispace1
|
>> multispace1
|
||||||
>> ret: type_
|
>> ret: type_
|
||||||
>> (Type::Function(FunctionType {
|
>> (FunctionType {
|
||||||
args,
|
args,
|
||||||
ret: Box::new(ret)
|
ret: Box::new(ret)
|
||||||
}))
|
})
|
||||||
));
|
));
|
||||||
|
|
||||||
named!(pub type_(&str) -> Type, alt!(
|
named!(pub type_(&str) -> Type, alt!(
|
||||||
|
@ -29,7 +29,7 @@ named!(pub type_(&str) -> Type, alt!(
|
||||||
tag!("float") => { |_| Type::Float } |
|
tag!("float") => { |_| Type::Float } |
|
||||||
tag!("bool") => { |_| Type::Bool } |
|
tag!("bool") => { |_| Type::Bool } |
|
||||||
tag!("cstring") => { |_| Type::CString } |
|
tag!("cstring") => { |_| Type::CString } |
|
||||||
function_type |
|
function_type => { |ft| Type::Function(ft) }|
|
||||||
ident => { |id| Type::Var(id) } |
|
ident => { |id| Type::Var(id) } |
|
||||||
delimited!(
|
delimited!(
|
||||||
tuple!(tag!("("), multispace0),
|
tuple!(tag!("("), multispace0),
|
||||||
|
|
|
@ -337,6 +337,19 @@ impl<'ast> Typechecker<'ast> {
|
||||||
self.env.set(name.clone(), type_);
|
self.env.set(name.clone(), type_);
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
ast::Decl::Extern { name, type_ } => {
|
||||||
|
let type_ = self.type_from_ast_type(ast::Type::Function(type_));
|
||||||
|
self.env.set(name.clone(), type_.clone());
|
||||||
|
let (arg_types, ret_type) = match type_ {
|
||||||
|
Type::Fun { args, ret } => (args, *ret),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
Ok(Some(hir::Decl::Extern {
|
||||||
|
name,
|
||||||
|
arg_types,
|
||||||
|
ret_type,
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,30 @@ use std::process::Command;
|
||||||
|
|
||||||
use crate_root::root;
|
use crate_root::root;
|
||||||
|
|
||||||
const FIXTURES: &[(&str, i32)] = &[("simple", 5), ("functions", 9)];
|
struct Fixture {
|
||||||
|
name: &'static str,
|
||||||
|
exit_code: i32,
|
||||||
|
expected_output: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
const FIXTURES: &[Fixture] = &[
|
||||||
|
Fixture {
|
||||||
|
name: "simple",
|
||||||
|
exit_code: 5,
|
||||||
|
expected_output: "",
|
||||||
|
},
|
||||||
|
// TODO(grfn): needs monomorphization
|
||||||
|
// Fixture {
|
||||||
|
// name: "functions",
|
||||||
|
// exit_code: 9,
|
||||||
|
// expected_output: "",
|
||||||
|
// },
|
||||||
|
Fixture {
|
||||||
|
name: "externs",
|
||||||
|
exit_code: 0,
|
||||||
|
expected_output: "foobar\n",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn compile_and_run_files() {
|
fn compile_and_run_files() {
|
||||||
|
@ -21,13 +44,18 @@ fn compile_and_run_files() {
|
||||||
"make clean failed"
|
"make clean failed"
|
||||||
);
|
);
|
||||||
|
|
||||||
for (fixture, exit_code) in FIXTURES {
|
for Fixture {
|
||||||
println!(">>> Testing: {}", fixture);
|
name,
|
||||||
|
exit_code,
|
||||||
|
expected_output,
|
||||||
|
} in FIXTURES
|
||||||
|
{
|
||||||
|
println!(">>> Testing: {}", name);
|
||||||
|
|
||||||
println!(" Running: `make {}`", fixture);
|
println!(" Running: `make {}`", name);
|
||||||
assert!(
|
assert!(
|
||||||
Command::new("make")
|
Command::new("make")
|
||||||
.arg(fixture)
|
.arg(name)
|
||||||
.current_dir(&ach)
|
.current_dir(&ach)
|
||||||
.spawn()
|
.spawn()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -37,18 +65,11 @@ fn compile_and_run_files() {
|
||||||
"make failed"
|
"make failed"
|
||||||
);
|
);
|
||||||
|
|
||||||
let out_path = ach.join(fixture);
|
let out_path = ach.join(name);
|
||||||
println!(" Running: `{}`", out_path.to_str().unwrap());
|
println!(" Running: `{}`", out_path.to_str().unwrap());
|
||||||
assert_eq!(
|
let output = Command::new(out_path).output().unwrap();
|
||||||
Command::new(out_path)
|
assert_eq!(output.status.code().unwrap(), *exit_code,);
|
||||||
.spawn()
|
assert_eq!(output.stdout, expected_output.as_bytes());
|
||||||
.unwrap()
|
|
||||||
.wait()
|
|
||||||
.unwrap()
|
|
||||||
.code()
|
|
||||||
.unwrap(),
|
|
||||||
*exit_code,
|
|
||||||
);
|
|
||||||
println!(" OK");
|
println!(" OK");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue