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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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