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:
Griffin Smith 2021-03-19 20:46:19 -04:00 committed by glittershark
parent fec6595d21
commit 2c838ab845
9 changed files with 147 additions and 28 deletions

View file

@ -3,3 +3,4 @@
functions functions
simple simple
externs

View file

@ -0,0 +1,5 @@
extern puts : fn cstring -> int
fn main =
let _ = puts "foobar"
in 0

View file

@ -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)?,
}),
} }
} }
} }

View file

@ -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>,
},
} }
//// ////

View file

@ -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> {

View file

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

View file

@ -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),

View file

@ -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,
}))
}
} }
} }

View file

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