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
|
||||
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>>,
|
||||
type_: T,
|
||||
},
|
||||
|
||||
Extern {
|
||||
name: Ident<'a>,
|
||||
arg_types: Vec<T>,
|
||||
ret_type: T,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a, T> Decl<'a, T> {
|
||||
pub fn type_(&self) -> &T {
|
||||
pub fn type_(&self) -> Option<&T> {
|
||||
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())?),
|
||||
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)]
|
||||
pub enum Decl<'a> {
|
||||
Fun { name: Ident<'a>, body: Fun<'a> },
|
||||
Ascription { name: Ident<'a>, type_: Type<'a> },
|
||||
Fun {
|
||||
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::types::{BasicType, BasicTypeEnum, FunctionType, IntType};
|
||||
use inkwell::values::{AnyValueEnum, BasicValueEnum, FunctionValue};
|
||||
use inkwell::IntPredicate;
|
||||
use inkwell::{AddressSpace, IntPredicate};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::ast::hir::{Binding, Decl, Expr};
|
||||
|
@ -249,6 +249,26 @@ impl<'ctx, 'ast> Codegen<'ctx, 'ast> {
|
|||
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<()> {
|
||||
match decl {
|
||||
Decl::Fun {
|
||||
|
@ -257,6 +277,11 @@ impl<'ctx, 'ast> Codegen<'ctx, 'ast> {
|
|||
self.codegen_function(name.into(), args, body)?;
|
||||
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> {
|
||||
// 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> {
|
||||
|
|
|
@ -9,6 +9,7 @@ mod type_;
|
|||
|
||||
use crate::ast::{Arg, Decl, Fun, Ident};
|
||||
pub use expr::expr;
|
||||
use type_::function_type;
|
||||
pub use type_::type_;
|
||||
|
||||
pub type Error = nom::Err<nom::error::Error<String>>;
|
||||
|
@ -79,9 +80,24 @@ named!(arg(&str) -> Arg, alt!(
|
|||
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!(
|
||||
complete!(tag!("fn"))
|
||||
>> multispace0
|
||||
>> multispace1
|
||||
>> name: ident
|
||||
>> multispace1
|
||||
>> args: separated_list0!(multispace1, arg)
|
||||
|
@ -112,7 +128,8 @@ named!(ascription_decl(&str) -> Decl, do_parse!(
|
|||
|
||||
named!(pub decl(&str) -> Decl, alt!(
|
||||
ascription_decl |
|
||||
fun_decl
|
||||
fun_decl |
|
||||
extern_decl
|
||||
));
|
||||
|
||||
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 crate::ast::{FunctionType, Type};
|
||||
|
||||
named!(function_type(&str) -> Type, do_parse!(
|
||||
named!(pub function_type(&str) -> FunctionType, do_parse!(
|
||||
tag!("fn")
|
||||
>> multispace1
|
||||
>> args: map!(opt!(terminated!(separated_list0!(
|
||||
|
@ -18,10 +18,10 @@ named!(function_type(&str) -> Type, do_parse!(
|
|||
>> tag!("->")
|
||||
>> multispace1
|
||||
>> ret: type_
|
||||
>> (Type::Function(FunctionType {
|
||||
>> (FunctionType {
|
||||
args,
|
||||
ret: Box::new(ret)
|
||||
}))
|
||||
})
|
||||
));
|
||||
|
||||
named!(pub type_(&str) -> Type, alt!(
|
||||
|
@ -29,7 +29,7 @@ named!(pub type_(&str) -> Type, alt!(
|
|||
tag!("float") => { |_| Type::Float } |
|
||||
tag!("bool") => { |_| Type::Bool } |
|
||||
tag!("cstring") => { |_| Type::CString } |
|
||||
function_type |
|
||||
function_type => { |ft| Type::Function(ft) }|
|
||||
ident => { |id| Type::Var(id) } |
|
||||
delimited!(
|
||||
tuple!(tag!("("), multispace0),
|
||||
|
|
|
@ -337,6 +337,19 @@ impl<'ast> Typechecker<'ast> {
|
|||
self.env.set(name.clone(), type_);
|
||||
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;
|
||||
|
||||
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]
|
||||
fn compile_and_run_files() {
|
||||
|
@ -21,13 +44,18 @@ fn compile_and_run_files() {
|
|||
"make clean failed"
|
||||
);
|
||||
|
||||
for (fixture, exit_code) in FIXTURES {
|
||||
println!(">>> Testing: {}", fixture);
|
||||
for Fixture {
|
||||
name,
|
||||
exit_code,
|
||||
expected_output,
|
||||
} in FIXTURES
|
||||
{
|
||||
println!(">>> Testing: {}", name);
|
||||
|
||||
println!(" Running: `make {}`", fixture);
|
||||
println!(" Running: `make {}`", name);
|
||||
assert!(
|
||||
Command::new("make")
|
||||
.arg(fixture)
|
||||
.arg(name)
|
||||
.current_dir(&ach)
|
||||
.spawn()
|
||||
.unwrap()
|
||||
|
@ -37,18 +65,11 @@ fn compile_and_run_files() {
|
|||
"make failed"
|
||||
);
|
||||
|
||||
let out_path = ach.join(fixture);
|
||||
let out_path = ach.join(name);
|
||||
println!(" Running: `{}`", out_path.to_str().unwrap());
|
||||
assert_eq!(
|
||||
Command::new(out_path)
|
||||
.spawn()
|
||||
.unwrap()
|
||||
.wait()
|
||||
.unwrap()
|
||||
.code()
|
||||
.unwrap(),
|
||||
*exit_code,
|
||||
);
|
||||
let output = Command::new(out_path).output().unwrap();
|
||||
assert_eq!(output.status.code().unwrap(), *exit_code,);
|
||||
assert_eq!(output.stdout, expected_output.as_bytes());
|
||||
println!(" OK");
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue