This commit is contained in:
Michael Zhang 2022-07-18 03:21:39 -05:00
parent dbb51fd1d0
commit f7e4bfa8cf
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
12 changed files with 55 additions and 409 deletions

View file

@ -3,6 +3,14 @@ name = "e0"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "e0c"
path = "./bin/e0c.rs"
[[bin]]
name = "e0pkg"
path = "./bin/e0pkg.rs"
[dependencies]
anyhow = "1.0.56"
clap = { version = "3.1.8", features = ["derive"] }

View file

@ -1,23 +1,12 @@
#[macro_use]
extern crate lalrpop_util;
#[macro_use]
extern crate anyhow;
lalrpop_mod!(parser);
mod ast;
pub mod codegen;
mod utils;
use std::fs::{self, File};
use std::io::Write;
use std::path::PathBuf;
use anyhow::Result;
use clap::Parser;
use crate::codegen::{llvm_ir::LlvmIrCodegen, CodegenBackend};
use crate::parser::ProgramParser;
use e0::codegen::CodegenBackend;
use e0::codegen::llvm_ir::LlvmIrCodegen;
use e0::parser::ProgramParser;
#[derive(Debug, Parser)]
struct Opt {
@ -40,7 +29,7 @@ fn main() -> Result<()> {
let parser = ProgramParser::new();
let ast = parser.parse(&contents).unwrap();
let typed_ast = ast::typed::convert(ast)?;
let typed_ast = e0::ast::typed::convert(ast)?;
if let Some(path) = opts.emit_ast {
let mut file = File::create(&path)?;

18
bin/e0pkg.rs Normal file
View file

@ -0,0 +1,18 @@
use clap::{Parser, Subcommand};
#[derive(Debug, Parser)]
struct Opt {
#[clap(subcommand)]
command: Command,
}
#[derive(Debug, Subcommand)]
enum Command {
/// Compile an e0 package
#[clap(name = "build", alias = "b")]
Build,
}
fn main() {
let opts = Opt::parse();
}

View file

@ -1,6 +1,6 @@
{ fenix, makeRustPlatform, lib, nix-gitignore, libffi, libgcc }:
{ toolchain, makeRustPlatform, lib, nix-gitignore, libffi, libgcc }:
let rustPlatform = makeRustPlatform { inherit (fenix.minimal) cargo rustc; };
let rustPlatform = makeRustPlatform { inherit (toolchain) cargo rustc; };
in rustPlatform.buildRustPackage {
name = "e0";

View file

@ -12,14 +12,17 @@
inherit system;
overlays = [ fenix.overlay ];
};
myPkgs = rec { e0 = pkgs.callPackage ./. { }; };
toolchain = pkgs.fenix.complete;
myPkgs = rec { e0 = pkgs.callPackage ./. { inherit toolchain; }; };
in rec {
devShell = pkgs.mkShell {
packages = with pkgs;
with pkgs.llvmPackages_12; [
cargo-edit
cargo-watch
clangUseLLVM
(toolchain.withComponents [ "clippy" "rustc" "rust-src" ])
];
inputsFrom = with myPkgs; [ e0 ];
CARGO_UNSTABLE_SPARSE_REGISTRY = "true";

View file

@ -1,25 +0,0 @@
#!/usr/bin/env bash
set -e
SOURCE=$?
BITCODE=$(mktemp -d)
OUTPUT=./a.out
usage() { echo "$0 options:" && grep " .)\ #" $0; exit 0; }
[ $# -eq 0 ] && usage
while getopts ":hs:p:" arg; do
case $arg in
o) # Binary output
OUTPUT=${OPTARG}
;;
h | *) # Display help.
usage
exit 0
;;
esac
done
cargo run -- -o $BITCODE $SOURCE
clang -o $OUTPUT $BITCODE

View file

@ -1,12 +0,0 @@
use anyhow::Result;
use cranelift_codegen::Context as ClifContext;
use super::{Decl, Type};
pub fn convert(
file_name: String,
context: &Context,
program: Vec<Decl<Type>>,
) -> Result<ClifContext> {
todo!()
}

View file

@ -1,349 +0,0 @@
use std::collections::HashMap;
use anyhow::Result;
use inkwell::{
builder::Builder,
context::Context,
module::Module,
types::{BasicMetadataTypeEnum, BasicTypeEnum, FunctionType},
values::{BasicValue, BasicValueEnum, FunctionValue, IntValue},
IntPredicate,
};
use crate::utils::{
convert_type_to_metadata_type, convert_value_to_metadata_value,
};
use super::{Decl, ElseClause, Expr, ExprKind, IfElse, Op, Stmt, Type};
impl Expr<Type> {
fn into_llvm<'ctx>(
&self,
context: &'ctx Context,
builder: &'ctx Builder,
env: &Env<'_, 'ctx>,
) -> Result<BasicValueEnum<'ctx>> {
let int_ty = context.i64_type();
let bool_ty = context.bool_type();
match &self.kind {
ExprKind::Var(name) => Ok(match env.lookup(&name) {
Some(v) => match v.kind {
EnvValueKind::Local(l) => l,
EnvValueKind::Func(f) => {
let ptr = f.as_global_value().as_pointer_value();
builder.build_load(ptr, "")
}
},
None => bail!("Unbound name {name:?}"),
}),
ExprKind::Int(n) => {
Ok(BasicValueEnum::IntValue(int_ty.const_int(*n as u64, false)))
}
ExprKind::BinOp(left, op, right) => {
if !op.check_types(&left.ty, &right.ty) {
// TODO: detailed error message
bail!("Invalid types on operation.");
}
match op {
Op::Plus => {
let left_val =
left.into_llvm(context, builder, env)?.into_int_value();
let right_val =
right.into_llvm(context, builder, env)?.into_int_value();
let result: IntValue =
builder.build_int_add(left_val, right_val, "");
Ok(BasicValueEnum::IntValue(result))
}
Op::LessThan => {
let left_val =
left.into_llvm(context, builder, env)?.into_int_value();
let right_val =
right.into_llvm(context, builder, env)?.into_int_value();
let result: IntValue = builder.build_int_compare(
IntPredicate::SLT,
left_val,
right_val,
"",
);
Ok(BasicValueEnum::IntValue(result))
}
Op::GreaterThan => {
let left_val =
left.into_llvm(context, builder, env)?.into_int_value();
let right_val =
right.into_llvm(context, builder, env)?.into_int_value();
let result: IntValue = builder.build_int_compare(
IntPredicate::SGT,
left_val,
right_val,
"",
);
Ok(BasicValueEnum::IntValue(result))
}
}
}
ExprKind::Call(func, args) => match env.lookup(func) {
Some(EnvValue {
ty: func_ty,
kind: EnvValueKind::Func(func_ptr),
}) => {
let args_llvm = args
.iter()
.map(|arg| {
arg
.into_llvm(context, builder, env)
.map(convert_value_to_metadata_value)
})
.collect::<Result<Vec<_>>>()?;
let call_site =
builder.build_call(*func_ptr, args_llvm.as_slice(), "");
let value = call_site.try_as_basic_value().unwrap_left();
Ok(value)
}
_ => bail!("No function with name {func:?}"),
},
}
}
}
impl Type {
pub fn into_llvm_basic_type<'ctx>(
&self,
context: &'ctx Context,
) -> BasicTypeEnum<'ctx> {
match self {
Type::Int => BasicTypeEnum::IntType(context.i64_type()),
Type::Bool => BasicTypeEnum::IntType(context.bool_type()),
_ => panic!("Tried to convert a function type into a LLVM basic type"),
}
}
}
fn fn_type_basic<'ctx>(
return_ty: BasicTypeEnum<'ctx>,
args: &[BasicMetadataTypeEnum<'ctx>],
is_var_args: bool,
) -> FunctionType<'ctx> {
match return_ty {
BasicTypeEnum::ArrayType(ty) => ty.fn_type(args, is_var_args),
BasicTypeEnum::FloatType(ty) => ty.fn_type(args, is_var_args),
BasicTypeEnum::IntType(ty) => ty.fn_type(args, is_var_args),
BasicTypeEnum::PointerType(ty) => ty.fn_type(args, is_var_args),
BasicTypeEnum::StructType(ty) => ty.fn_type(args, is_var_args),
BasicTypeEnum::VectorType(ty) => ty.fn_type(args, is_var_args),
}
}
#[derive(Debug)]
enum EnvValueKind<'ctx> {
Func(FunctionValue<'ctx>),
Local(BasicValueEnum<'ctx>),
}
#[derive(Debug)]
struct EnvValue<'a, 'ctx> {
ty: &'a Type,
kind: EnvValueKind<'ctx>,
}
#[derive(Debug, Default)]
struct Env<'a, 'ctx> {
parent: Option<&'a Env<'a, 'ctx>>,
local_type_map: HashMap<String, EnvValue<'a, 'ctx>>,
}
impl<'a, 'ctx> Env<'a, 'ctx> {
pub fn lookup(&self, name: impl AsRef<str>) -> Option<&EnvValue<'a, 'ctx>> {
match self.local_type_map.get(name.as_ref()) {
Some(v) => Some(v),
None => match &self.parent {
Some(p) => p.lookup(name),
None => None,
},
}
}
}
fn convert_if_else(
context: &Context,
module: &Module,
builder: &Builder,
function: &FunctionValue,
if_else: &IfElse<Type>,
env: &Env,
) -> Result<()> {
let success_branch = context.append_basic_block(*function, "success");
let fail_branch = context.append_basic_block(*function, "fail");
let exit_block = context.append_basic_block(*function, "exit");
let cond = if_else
.cond
.into_llvm(context, builder, env)?
.into_int_value();
builder.build_conditional_branch(cond, success_branch, fail_branch);
// build success branch
builder.position_at_end(success_branch);
convert_stmts(context, module, function, builder, env, &if_else.body)?;
// builder.build_unconditional_branch(exit_branch);
// build fail branch
builder.position_at_end(fail_branch);
match &if_else.else_clause {
Some(ElseClause::If(if_else2)) => {
convert_if_else(context, module, builder, function, if_else2, env)?;
}
Some(ElseClause::Body(body)) => {
convert_stmts(context, module, function, builder, env, body)?;
}
None => {}
}
builder.position_at_end(fail_branch);
builder.build_unconditional_branch(exit_block);
builder.position_at_end(exit_block);
Ok(())
}
fn convert_stmts(
context: &Context,
module: &Module,
function: &FunctionValue,
builder: &Builder,
parent_env: &Env,
stmts: impl AsRef<[Stmt<Type>]>,
) -> Result<()> {
let stmts = stmts.as_ref();
let mut scope_env = Env {
parent: Some(parent_env),
..Default::default()
};
for stmt in stmts.iter() {
match stmt {
Stmt::Let(name, _, expr) => {
let ty = &expr.ty;
let llvm_ty = ty.into_llvm_basic_type(context);
// Empty variable name gets LLVM to generate a unique name
let alloca = builder.build_alloca(llvm_ty, "");
let expr_val = expr.into_llvm(context, builder, &scope_env)?;
builder.build_store(alloca, expr_val);
scope_env.local_type_map.insert(
name.clone(),
EnvValue {
ty,
kind: EnvValueKind::Local(alloca.as_basic_value_enum()),
},
);
}
Stmt::Return(ret_val) => {
if let Some(ret_val) = ret_val {
let value = ret_val.into_llvm(context, builder, &scope_env)?;
builder.build_return(Some(&value));
} else {
builder.build_return(None);
}
}
Stmt::IfElse(if_else) => {
convert_if_else(
context, module, builder, function, if_else, &scope_env,
)?;
}
}
}
Ok(())
}
pub fn convert(
file_name: String,
context: &Context,
program: Vec<Decl<Type>>,
) -> Result<Module> {
let module = context.create_module(&file_name);
let builder = context.create_builder();
let mut env = Env::default();
for func in program.iter().filter_map(|decl| match decl {
Decl::Func(v) => Some(v),
_ => None,
}) {
let return_ty = func.return_ty.into_llvm_basic_type(context);
let args_ty = func
.args
.iter()
.map(|arg| {
convert_type_to_metadata_type(arg.ty.into_llvm_basic_type(context))
})
.collect::<Vec<_>>();
let llvm_func_ty = fn_type_basic(return_ty, &args_ty, false);
let llvm_func = module.add_function(&func.name, llvm_func_ty, None);
env.local_type_map.insert(
func.name.clone(),
EnvValue {
ty: &func.ty,
kind: EnvValueKind::Func(llvm_func),
},
);
}
for func in program.iter().filter_map(|decl| match decl {
Decl::Func(v) => Some(v),
_ => None,
}) {
let llvm_func = match env.local_type_map.get(&func.name) {
Some(EnvValue {
kind: EnvValueKind::Func(func),
..
}) => func.clone(),
_ => unreachable!(),
};
let entry_block = context.append_basic_block(llvm_func, "entry");
builder.position_at_end(entry_block);
let mut scoped_env = Env {
parent: Some(&env),
local_type_map: HashMap::new(),
};
for (arg, param) in func.args.iter().zip(llvm_func.get_params().into_iter())
{
scoped_env.local_type_map.insert(
arg.name.clone(),
EnvValue {
ty: &arg.ty,
kind: EnvValueKind::Local(param),
},
);
}
convert_stmts(
&context,
&module,
&llvm_func,
&builder,
&scoped_env,
&func.stmts,
)?;
}
Ok(module)
}

View file

@ -1,5 +1,3 @@
// pub mod llvm;
// pub mod cranelift;
pub mod typed;
#[derive(Debug)]
@ -64,9 +62,11 @@ pub enum Op {
}
impl Op {
/// Check that the types match the current operation. If so, return the type
/// that is expected as a result. Otherwise, return None.
pub fn check_types(&self, left_ty: &Type, right_ty: &Type) -> Option<Type> {
// TODO: since only binops work on integers right now, just check that the two sides are both
// integers. this will have to change once && gets added.
// TODO: since only binops work on integers right now, just check that the
// two sides are both integers. this will have to change once && gets added.
match (left_ty, right_ty) {
(Type::Int, Type::Int) => Some(Type::Int),
_ => None,

View file

@ -2,7 +2,7 @@ use std::io::Write;
use anyhow::Result;
use crate::ast::{ElseClause, IfElse, Stmt, Type};
use crate::ast::{ElseClause, IfElse, Type};
use super::LlvmIrCodegen;

10
src/lib.rs Normal file
View file

@ -0,0 +1,10 @@
#[macro_use]
extern crate lalrpop_util;
#[macro_use]
extern crate anyhow;
lalrpop_mod!(pub parser);
pub mod ast;
pub mod codegen;
mod utils;

4
std/e0pkg.yml Normal file
View file

@ -0,0 +1,4 @@
name: std
version: 0.1.0
authors:
- Michael Zhang <mail@mzhang.io>