jit works

This commit is contained in:
2024-02-02 10:31:13 -08:00
parent 7ebb31b42f
commit 4ba196d2a6
21 changed files with 477 additions and 185 deletions

View File

@@ -1,24 +1,23 @@
use crate::backend::error::BackendError;
use crate::backend::Backend;
use crate::eval::PrimitiveType;
use crate::ir::{Expression, Primitive, Program, TopLevel, Type, Value, ValueOrRef, Variable};
use crate::syntax::{ConstantType, Location};
use crate::util::scoped_map::ScopedMap;
use cranelift_codegen::ir::{
self, entities, types, AbiParam, Function, GlobalValue, InstBuilder, Signature, UserFuncName,
self, entities, types, AbiParam, Function, GlobalValue, InstBuilder, MemFlags, Signature, UserFuncName
};
use cranelift_codegen::isa::CallConv;
use cranelift_codegen::Context;
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
use cranelift_module::{FuncId, Linkage, Module};
use cranelift_module::{DataDescription, FuncId, Linkage, Module};
use internment::ArcIntern;
use crate::backend::error::BackendError;
use crate::backend::Backend;
use std::collections::{hash_map, HashMap};
/// When we're talking about variables, it's handy to just have a table that points
/// from a variable to "what to do if you want to reference this variable", which is
/// agnostic about whether the variable is local, global, an argument, etc. Since
/// the type of that function is a little bit annoying, we summarize it here.
enum ReferenceBuilder {
pub enum ReferenceBuilder {
Global(ConstantType, GlobalValue),
Local(ConstantType, cranelift_frontend::Variable),
Argument(ConstantType, entities::Value),
@@ -29,7 +28,8 @@ impl ReferenceBuilder {
match self {
ReferenceBuilder::Global(ty, gv) => {
let cranelift_type = ir::Type::from(*ty);
let value = builder.ins().symbol_value(cranelift_type, *gv);
let ptr_value = builder.ins().symbol_value(types::I64, *gv);
let value = builder.ins().load(cranelift_type, MemFlags::new(), ptr_value, 0);
(value, *ty)
}
@@ -81,11 +81,44 @@ impl<M: Module> Backend<M> {
program: Program<Type>,
) -> Result<FuncId, BackendError> {
let mut generated_body = vec![];
let mut variables = HashMap::new();
for (top_level_name, top_level_type) in program.get_top_level_variables() {
match top_level_type {
Type::Function(argument_types, return_type) => {
let func_id = self.declare_function(
top_level_name.as_str(),
Linkage::Export,
argument_types,
*return_type,
)?;
self.defined_functions.insert(top_level_name, func_id);
}
Type::Primitive(pt) => {
let data_id = self.module.declare_data(
top_level_name.as_str(),
Linkage::Export,
true,
false,
)?;
self.module.define_data(data_id, &pt.blank_data())?;
self.defined_symbols
.insert(top_level_name, (data_id, pt.into()));
}
}
}
let void = Type::Primitive(PrimitiveType::Void);
let main_func_id =
self.declare_function(function_name, Linkage::Export, vec![], void.clone())?;
self.defined_functions
.insert(ArcIntern::new(function_name.to_string()), main_func_id);
for item in program.items {
match item {
TopLevel::Function(name, args, rettype, body) => {
self.compile_function(name.as_str(), &args, rettype, body)?;
self.compile_function(&mut variables, name.as_str(), &args, rettype, body)?;
}
TopLevel::Statement(stmt) => {
@@ -94,8 +127,8 @@ impl<M: Module> Backend<M> {
}
}
let void = Type::Primitive(PrimitiveType::Void);
self.compile_function(
&mut variables,
function_name,
&[],
void.clone(),
@@ -103,14 +136,47 @@ impl<M: Module> Backend<M> {
)
}
fn declare_function(
&mut self,
name: &str,
linkage: Linkage,
argument_types: Vec<Type>,
return_type: Type,
) -> Result<FuncId, cranelift_module::ModuleError> {
let basic_signature = Signature {
params: argument_types
.iter()
.map(|t| self.translate_type(t))
.collect(),
returns: if return_type == Type::Primitive(PrimitiveType::Void) {
vec![]
} else {
vec![self.translate_type(&return_type)]
},
call_conv: CallConv::triple_default(&self.platform),
};
// this generates the handle for the function that we'll eventually want to
// return to the user. For now, we declare all functions defined by this
// function as public/global/exported, although we may want to reconsider
// this decision later.
self.module
.declare_function(name, linkage, &basic_signature)
}
/// Compile the given function.
pub fn compile_function(
&mut self,
variables: &mut HashMap<Variable, ReferenceBuilder>,
function_name: &str,
arguments: &[(Variable, Type)],
return_type: Type,
body: Expression<Type>,
) -> Result<FuncId, BackendError> {
// reset the next variable counter. this value shouldn't matter; hopefully
// we won't be using close to 2^32 variables!
self.reset_local_variable_tracker();
let basic_signature = Signature {
params: arguments
.iter()
@@ -124,17 +190,23 @@ impl<M: Module> Backend<M> {
call_conv: CallConv::triple_default(&self.platform),
};
// reset the next variable counter. this value shouldn't matter; hopefully
// we won't be using close to 2^32 variables!
self.reset_local_variable_tracker();
// this generates the handle for the function that we'll eventually want to
// return to the user. For now, we declare all functions defined by this
// function as public/global/exported, although we may want to reconsider
// this decision later.
let func_id =
self.module
.declare_function(function_name, Linkage::Export, &basic_signature)?;
let interned_name = ArcIntern::new(function_name.to_string());
let func_id = match self.defined_functions.entry(interned_name) {
hash_map::Entry::Occupied(entry) => *entry.get(),
hash_map::Entry::Vacant(vac) => {
let func_id = self.module.declare_function(
function_name,
Linkage::Export,
&basic_signature,
)?;
vac.insert(func_id);
func_id
}
};
// Next we have to generate the compilation context for the rest of this
// function. Currently, we generate a fresh context for every function.
@@ -145,14 +217,6 @@ impl<M: Module> Backend<M> {
let user_func_name = UserFuncName::user(0, func_id.as_u32());
ctx.func = Function::with_name_signature(user_func_name, basic_signature);
// Let's start creating the variable table we'll use when we're dereferencing
// them later. This table is a little interesting because instead of pointing
// from data to data, we're going to point from data (the variable) to an
// action to take if we encounter that variable at some later point. This
// makes it nice and easy to have many different ways to access data, such
// as globals, function arguments, etc.
let mut variables: ScopedMap<ArcIntern<String>, ReferenceBuilder> = ScopedMap::new();
// At the outer-most scope of things, we'll put global variables we've defined
// elsewhere in the program.
for (name, (data_id, ty)) in self.defined_symbols.iter() {
@@ -160,12 +224,6 @@ impl<M: Module> Backend<M> {
variables.insert(name.clone(), ReferenceBuilder::Global(*ty, local_data));
}
// Once we have these, we're going to actually push a level of scope and
// add our arguments. We push scope because if there happen to be any with
// the same name (their shouldn't be, but just in case), we want the arguments
// to win.
variables.new_scope();
// Finally (!), we generate the function builder that we're going to use to
// make this function!
let mut fctx = FunctionBuilderContext::new();
@@ -192,7 +250,7 @@ impl<M: Module> Backend<M> {
builder.switch_to_block(main_block);
let (value, _) = self.compile_expression(body, &mut variables, &mut builder)?;
let (value, _) = self.compile_expression(body, variables, &mut builder)?;
// Now that we're done, inject a return function (one with no actual value; basically
// the equivalent of Rust's `return;`). We then seal the block (which lets Cranelift
@@ -221,7 +279,7 @@ impl<M: Module> Backend<M> {
fn compile_expression(
&mut self,
expr: Expression<Type>,
variables: &mut ScopedMap<Variable, ReferenceBuilder>,
variables: &mut HashMap<Variable, ReferenceBuilder>,
builder: &mut FunctionBuilder,
) -> Result<(entities::Value, ConstantType), BackendError> {
match expr {
@@ -282,6 +340,29 @@ impl<M: Module> Backend<M> {
(ConstantType::U64, Type::Primitive(PrimitiveType::U64)) => Ok((val, val_type)),
(ConstantType::Void, Type::Primitive(PrimitiveType::Void)) => {
Ok((val, val_type))
}
(ConstantType::U8, Type::Primitive(PrimitiveType::I16)) => {
Ok((builder.ins().uextend(types::I16, val), ConstantType::I16))
}
(ConstantType::U8, Type::Primitive(PrimitiveType::I32)) => {
Ok((builder.ins().uextend(types::I32, val), ConstantType::I32))
}
(ConstantType::U8, Type::Primitive(PrimitiveType::I64)) => {
Ok((builder.ins().uextend(types::I64, val), ConstantType::I64))
}
(ConstantType::U16, Type::Primitive(PrimitiveType::I32)) => {
Ok((builder.ins().uextend(types::I32, val), ConstantType::I32))
}
(ConstantType::U16, Type::Primitive(PrimitiveType::I64)) => {
Ok((builder.ins().uextend(types::I64, val), ConstantType::I64))
}
(ConstantType::U32, Type::Primitive(PrimitiveType::I64)) => {
Ok((builder.ins().uextend(types::I64, val), ConstantType::I64))
}
_ => Err(BackendError::InvalidTypeCast {
from: val_type.into(),
to: target_type,
@@ -327,7 +408,7 @@ impl<M: Module> Backend<M> {
}
Expression::Block(_, _, mut exprs) => match exprs.pop() {
None => Ok((builder.ins().iconst(types::I8, 0), ConstantType::I8)),
None => Ok((builder.ins().iconst(types::I64, 0), ConstantType::Void)),
Some(last) => {
for inner in exprs {
// we can ignore all of these return values and such, because we
@@ -354,7 +435,7 @@ impl<M: Module> Backend<M> {
// Look up the value for the variable. Because this might be a
// global variable (and that requires special logic), we just turn
// this into an `Expression` and re-use the logic in that implementation.
let fake_ref = ValueOrRef::Ref(ann, Type::Primitive(PrimitiveType::U8), var);
let fake_ref = ValueOrRef::Ref(ann, Type::Primitive(PrimitiveType::U8), var.clone());
let (val, vtype) = self.compile_value_or_ref(fake_ref, variables, builder)?;
let vtype_repr = builder.ins().iconst(types::I64, vtype as i64);
@@ -379,7 +460,7 @@ impl<M: Module> Backend<M> {
print_func_ref,
&[buffer_ptr, name_ptr, vtype_repr, casted_val],
);
Ok((builder.ins().iconst(types::I8, 0), ConstantType::I8))
Ok((builder.ins().iconst(types::I64, 0), ConstantType::Void))
}
Expression::Bind(_, name, _, expr) => {
@@ -390,7 +471,7 @@ impl<M: Module> Backend<M> {
builder.declare_var(variable, ir_type);
builder.def_var(variable, value);
variables.insert(name, ReferenceBuilder::Local(value_type, variable));
Ok((builder.ins().iconst(types::I8, 0), ConstantType::I8))
Ok((builder.ins().iconst(types::I64, 0), ConstantType::Void))
}
}
}
@@ -400,7 +481,7 @@ impl<M: Module> Backend<M> {
fn compile_value_or_ref(
&self,
valref: ValueOrRef<Type>,
variables: &ScopedMap<Variable, ReferenceBuilder>,
variables: &HashMap<Variable, ReferenceBuilder>,
builder: &mut FunctionBuilder,
) -> Result<(entities::Value, ConstantType), BackendError> {
match valref {
@@ -453,3 +534,23 @@ impl<M: Module> Backend<M> {
}
}
}
impl PrimitiveType {
fn blank_data(&self) -> DataDescription {
let (size, alignment) = match self {
PrimitiveType::Void => (8, 8),
PrimitiveType::U8 => (1, 1),
PrimitiveType::U16 => (2, 2),
PrimitiveType::U32 => (4, 4),
PrimitiveType::U64 => (4, 4),
PrimitiveType::I8 => (1, 1),
PrimitiveType::I16 => (2, 2),
PrimitiveType::I32 => (4, 4),
PrimitiveType::I64 => (4, 4),
};
let mut result = DataDescription::new();
result.define_zeroinit(size);
result.set_align(alignment);
result
}
}