λ Support functions! #5
@@ -34,6 +34,7 @@ mod runtime;
|
|||||||
pub use self::error::BackendError;
|
pub use self::error::BackendError;
|
||||||
pub use self::runtime::{RuntimeFunctionError, RuntimeFunctions};
|
pub use self::runtime::{RuntimeFunctionError, RuntimeFunctions};
|
||||||
use crate::syntax::ConstantType;
|
use crate::syntax::ConstantType;
|
||||||
|
use cranelift_codegen::entity::EntityRef;
|
||||||
use cranelift_codegen::settings::Configurable;
|
use cranelift_codegen::settings::Configurable;
|
||||||
use cranelift_codegen::{isa, settings};
|
use cranelift_codegen::{isa, settings};
|
||||||
use cranelift_jit::{JITBuilder, JITModule};
|
use cranelift_jit::{JITBuilder, JITModule};
|
||||||
@@ -62,6 +63,7 @@ pub struct Backend<M: Module> {
|
|||||||
defined_symbols: HashMap<ArcIntern<String>, (DataId, ConstantType)>,
|
defined_symbols: HashMap<ArcIntern<String>, (DataId, ConstantType)>,
|
||||||
output_buffer: Option<String>,
|
output_buffer: Option<String>,
|
||||||
platform: Triple,
|
platform: Triple,
|
||||||
|
next_variable: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Backend<JITModule> {
|
impl Backend<JITModule> {
|
||||||
@@ -93,6 +95,7 @@ impl Backend<JITModule> {
|
|||||||
defined_symbols: HashMap::new(),
|
defined_symbols: HashMap::new(),
|
||||||
output_buffer,
|
output_buffer,
|
||||||
platform: Triple::host(),
|
platform: Triple::host(),
|
||||||
|
next_variable: 23,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,6 +135,7 @@ impl Backend<ObjectModule> {
|
|||||||
defined_symbols: HashMap::new(),
|
defined_symbols: HashMap::new(),
|
||||||
output_buffer: None,
|
output_buffer: None,
|
||||||
platform,
|
platform,
|
||||||
|
next_variable: 23,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,6 +191,32 @@ impl<M: Module> Backend<M> {
|
|||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn string_reference(&mut self, string: &str) -> Result<DataId, BackendError> {
|
||||||
|
match self.defined_strings.get(string) {
|
||||||
|
Some(x) => Ok(*x),
|
||||||
|
None => self.define_string(string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset the local variable counter, because we're going to start a new
|
||||||
|
/// function.
|
||||||
|
pub fn reset_local_variable_tracker(&mut self) {
|
||||||
|
self.next_variable = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Declare a local variable with the given name and type.
|
||||||
|
///
|
||||||
|
/// This variable should only be used to reference variables within the current
|
||||||
|
/// function. If you try to reference a variable from another function, random
|
||||||
|
/// things could happen; hopefully, Cranelift will yell at you, but there's a
|
||||||
|
/// small chance everything would work out and you'd end up referencing something
|
||||||
|
/// unexpected.
|
||||||
|
pub fn generate_local(&mut self) -> cranelift_frontend::Variable {
|
||||||
|
let var = cranelift_frontend::Variable::new(self.next_variable);
|
||||||
|
self.next_variable += 1;
|
||||||
|
var
|
||||||
|
}
|
||||||
|
|
||||||
/// Get a pointer to the output buffer for `print`ing, or `null`.
|
/// Get a pointer to the output buffer for `print`ing, or `null`.
|
||||||
///
|
///
|
||||||
/// As suggested, returns `null` in the case where the user has not provided an
|
/// As suggested, returns `null` in the case where the user has not provided an
|
||||||
|
|||||||
@@ -18,18 +18,25 @@ use crate::backend::Backend;
|
|||||||
/// from a variable to "what to do if you want to reference this variable", which is
|
/// 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
|
/// 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.
|
/// the type of that function is a little bit annoying, we summarize it here.
|
||||||
struct ReferenceBuilder {
|
enum ReferenceBuilder {
|
||||||
ir_type: ConstantType,
|
Global(ConstantType, GlobalValue),
|
||||||
cranelift_type: cranelift_codegen::ir::Type,
|
Local(ConstantType, cranelift_frontend::Variable),
|
||||||
local_data: GlobalValue,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReferenceBuilder {
|
impl ReferenceBuilder {
|
||||||
fn refer_to(&self, builder: &mut FunctionBuilder) -> (entities::Value, ConstantType) {
|
fn refer_to(&self, builder: &mut FunctionBuilder) -> (entities::Value, ConstantType) {
|
||||||
let value = builder
|
match self {
|
||||||
.ins()
|
ReferenceBuilder::Global(ty, gv) => {
|
||||||
.symbol_value(self.cranelift_type, self.local_data);
|
let cranelift_type = ir::Type::from(*ty);
|
||||||
(value, self.ir_type)
|
let value = builder.ins().symbol_value(cranelift_type, *gv);
|
||||||
|
(value, *ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
ReferenceBuilder::Local(ty, var) => {
|
||||||
|
let value = builder.use_var(*var);
|
||||||
|
(value, *ty)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,6 +121,10 @@ impl<M: Module> Backend<M> {
|
|||||||
call_conv: CallConv::triple_default(&self.platform),
|
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
|
// 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
|
// return to the user. For now, we declare all functions defined by this
|
||||||
// function as public/global/exported, although we may want to reconsider
|
// function as public/global/exported, although we may want to reconsider
|
||||||
@@ -143,15 +154,7 @@ impl<M: Module> Backend<M> {
|
|||||||
// elsewhere in the program.
|
// elsewhere in the program.
|
||||||
for (name, (data_id, ty)) in self.defined_symbols.iter() {
|
for (name, (data_id, ty)) in self.defined_symbols.iter() {
|
||||||
let local_data = self.module.declare_data_in_func(*data_id, &mut ctx.func);
|
let local_data = self.module.declare_data_in_func(*data_id, &mut ctx.func);
|
||||||
let cranelift_type = ir::Type::from(*ty);
|
variables.insert(name.clone(), ReferenceBuilder::Global(*ty, local_data));
|
||||||
variables.insert(
|
|
||||||
name.clone(),
|
|
||||||
ReferenceBuilder {
|
|
||||||
cranelift_type,
|
|
||||||
local_data,
|
|
||||||
ir_type: *ty,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Once we have these, we're going to actually push a level of scope and
|
// Once we have these, we're going to actually push a level of scope and
|
||||||
@@ -177,7 +180,11 @@ impl<M: Module> Backend<M> {
|
|||||||
// the equivalent of Rust's `return;`). We then seal the block (which lets Cranelift
|
// the equivalent of Rust's `return;`). We then seal the block (which lets Cranelift
|
||||||
// know that the block is done), and then finalize the function (which lets Cranelift
|
// know that the block is done), and then finalize the function (which lets Cranelift
|
||||||
// know we're done with the function).
|
// know we're done with the function).
|
||||||
builder.ins().return_(&[value]);
|
if return_type == Type::Primitive(PrimitiveType::Void) {
|
||||||
|
builder.ins().return_(&[]);
|
||||||
|
} else {
|
||||||
|
builder.ins().return_(&[value]);
|
||||||
|
};
|
||||||
builder.seal_block(main_block);
|
builder.seal_block(main_block);
|
||||||
builder.finalize();
|
builder.finalize();
|
||||||
|
|
||||||
@@ -320,13 +327,10 @@ impl<M: Module> Backend<M> {
|
|||||||
let buffer_ptr = builder.ins().iconst(types::I64, buffer_ptr as i64);
|
let buffer_ptr = builder.ins().iconst(types::I64, buffer_ptr as i64);
|
||||||
|
|
||||||
// Get a reference to the string we want to print.
|
// Get a reference to the string we want to print.
|
||||||
let string_data_id = self
|
let string_data_id = self.string_reference(var.as_ref())?;
|
||||||
.defined_strings
|
|
||||||
.get(var.as_ref())
|
|
||||||
.ok_or_else(|| BackendError::UnknownString(var.clone()))?;
|
|
||||||
let local_name_ref = self
|
let local_name_ref = self
|
||||||
.module
|
.module
|
||||||
.declare_data_in_func(*string_data_id, builder.func);
|
.declare_data_in_func(string_data_id, builder.func);
|
||||||
let name_ptr = builder.ins().symbol_value(types::I64, local_name_ref);
|
let name_ptr = builder.ins().symbol_value(types::I64, local_name_ref);
|
||||||
|
|
||||||
// Look up the value for the variable. Because this might be a
|
// Look up the value for the variable. Because this might be a
|
||||||
@@ -360,7 +364,16 @@ impl<M: Module> Backend<M> {
|
|||||||
Ok((builder.ins().iconst(types::I8, 0), ConstantType::I8))
|
Ok((builder.ins().iconst(types::I8, 0), ConstantType::I8))
|
||||||
}
|
}
|
||||||
|
|
||||||
Expression::Bind(_, _, _, _) => unimplemented!(),
|
Expression::Bind(_, name, _, expr) => {
|
||||||
|
let (value, value_type) = self.compile_expression(*expr, variables, builder)?;
|
||||||
|
let ir_type = ir::Type::from(value_type);
|
||||||
|
let variable = self.generate_local();
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,211 +422,4 @@ impl<M: Module> Backend<M> {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compiling a function is just compiling each of the statements in order.
|
|
||||||
// At the moment, we do the pattern match for statements here, and then
|
|
||||||
// directly compile the statements. If/when we add more statement forms,
|
|
||||||
// this is likely to become more cumbersome, and we'll want to separate
|
|
||||||
// these off. But for now, given the amount of tables we keep around to track
|
|
||||||
// state, it's easier to just include them.
|
|
||||||
// for item in program.items.drain(..) {
|
|
||||||
// match item {
|
|
||||||
// TopLevel::Function(_, _, _) => unimplemented!(),
|
|
||||||
//
|
|
||||||
// // Print statements are fairly easy to compile: we just lookup the
|
|
||||||
// // output buffer, the address of the string to print, and the value
|
|
||||||
// // of whatever variable we're printing. Then we just call print.
|
|
||||||
// TopLevel::Statement(Statement::Print(ann, t, var)) => {
|
|
||||||
// // Get the output buffer (or null) from our general compilation context.
|
|
||||||
// let buffer_ptr = self.output_buffer_ptr();
|
|
||||||
// let buffer_ptr = builder.ins().iconst(types::I64, buffer_ptr as i64);
|
|
||||||
//
|
|
||||||
// // Get a reference to the string we want to print.
|
|
||||||
// let local_name_ref = string_table.get(&var).unwrap();
|
|
||||||
// let name_ptr = builder.ins().symbol_value(types::I64, *local_name_ref);
|
|
||||||
//
|
|
||||||
// // 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 (val, vtype) = ValueOrRef::Ref(ann, t, var).into_crane(
|
|
||||||
// &mut builder,
|
|
||||||
// &variable_table,
|
|
||||||
// &pre_defined_symbols,
|
|
||||||
// )?;
|
|
||||||
//
|
|
||||||
// let vtype_repr = builder.ins().iconst(types::I64, vtype as i64);
|
|
||||||
//
|
|
||||||
// let casted_val = match vtype {
|
|
||||||
// ConstantType::U64 | ConstantType::I64 => val,
|
|
||||||
// ConstantType::I8 | ConstantType::I16 | ConstantType::I32 => {
|
|
||||||
// builder.ins().sextend(types::I64, val)
|
|
||||||
// }
|
|
||||||
// ConstantType::U8 | ConstantType::U16 | ConstantType::U32 => {
|
|
||||||
// builder.ins().uextend(types::I64, val)
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// // Finally, we can generate the call to print.
|
|
||||||
// builder.ins().call(
|
|
||||||
// print_func_ref,
|
|
||||||
// &[buffer_ptr, name_ptr, vtype_repr, casted_val],
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Variable binding is a little more con
|
|
||||||
// TopLevel::Statement(Statement::Binding(_, var_name, _, value)) => {
|
|
||||||
// // Kick off to the `Expression` implementation to see what value we're going
|
|
||||||
// // to bind to this variable.
|
|
||||||
// let (val, etype) =
|
|
||||||
// value.into_crane(&mut builder, &variable_table, &pre_defined_symbols)?;
|
|
||||||
//
|
|
||||||
// // Now the question is: is this a local variable, or a global one?
|
|
||||||
// if let Some((global_id, ctype)) = pre_defined_symbols.get(var_name.as_str()) {
|
|
||||||
// // It's a global variable! In this case, we assume that someone has already
|
|
||||||
// // dedicated some space in memory to store this value. We look this location
|
|
||||||
// // up, and then tell Cranelift to store the value there.
|
|
||||||
// assert_eq!(etype, *ctype);
|
|
||||||
// let val_ptr = builder
|
|
||||||
// .ins()
|
|
||||||
// .symbol_value(ir::Type::from(*ctype), *global_id);
|
|
||||||
// builder.ins().store(MemFlags::new(), val, val_ptr, 0);
|
|
||||||
// } else {
|
|
||||||
// // It's a local variable! In this case, we need to allocate a new Cranelift
|
|
||||||
// // `Variable` for this variable, which we do using our `next_var_num` counter.
|
|
||||||
// // (While we're doing this, we also increment `next_var_num`, so that we get
|
|
||||||
// // a fresh `Variable` next time. This is one of those very narrow cases in which
|
|
||||||
// // I wish Rust had an increment expression.)
|
|
||||||
// let var = Variable::new(next_var_num);
|
|
||||||
// next_var_num += 1;
|
|
||||||
//
|
|
||||||
// // We can add the variable directly to our local variable map; it's `Copy`.
|
|
||||||
// variable_table.insert(var_name, (var, etype));
|
|
||||||
//
|
|
||||||
// // Now we tell Cranelift about our new variable!
|
|
||||||
// builder.declare_var(var, ir::Type::from(etype));
|
|
||||||
// builder.def_var(var, val);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Build the string table for use in referencing strings later.
|
|
||||||
//
|
|
||||||
// This function is slightly smart, in that it only puts strings in the table that
|
|
||||||
// are used by the `Program`. (Thanks to `Progam::strings()`!) If the strings have
|
|
||||||
// been declared globally, via `Backend::define_string()`, we will re-use that data.
|
|
||||||
// Otherwise, this will define the string for you.
|
|
||||||
// fn build_string_table(
|
|
||||||
// &mut self,
|
|
||||||
// func: &mut Function,
|
|
||||||
// program: &Expression<Type>,
|
|
||||||
// ) -> Result<StringTable, BackendError> {
|
|
||||||
// let mut string_table = HashMap::new();
|
|
||||||
//
|
|
||||||
// for interned_value in program.strings().drain() {
|
|
||||||
// let global_id = match self.defined_strings.get(interned_value.as_str()) {
|
|
||||||
// Some(x) => *x,
|
|
||||||
// None => self.define_string(interned_value.as_str())?,
|
|
||||||
// };
|
|
||||||
// let local_data = self.module.declare_data_in_func(global_id, func);
|
|
||||||
// string_table.insert(interned_value, local_data);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Ok(string_table)
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//impl Expression {
|
|
||||||
// fn into_crane(
|
|
||||||
// self,
|
|
||||||
// builder: &mut FunctionBuilder,
|
|
||||||
// local_variables: &HashMap<ArcIntern<String>, (Variable, ConstantType)>,
|
|
||||||
// global_variables: &HashMap<String, (GlobalValue, ConstantType)>,
|
|
||||||
// ) -> Result<(entities::Value, ConstantType), BackendError> {
|
|
||||||
// match self {
|
|
||||||
// Expression::Atomic(x) => x.into_crane(builder, local_variables, global_variables),
|
|
||||||
//
|
|
||||||
// Expression::Cast(_, target_type, expr) => {
|
|
||||||
// let (val, val_type) =
|
|
||||||
// expr.into_crane(builder, local_variables, global_variables)?;
|
|
||||||
//
|
|
||||||
// match (val_type, &target_type) {
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Expression::Primitive(_, _, prim, mut vals) => {
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//// Just to avoid duplication, this just leverages the `From<ValueOrRef>` trait implementation
|
|
||||||
//// for `ValueOrRef` to compile this via the `Expression` logic, above.
|
|
||||||
//impl ValueOrRef {
|
|
||||||
// fn into_crane(
|
|
||||||
// self,
|
|
||||||
// builder: &mut FunctionBuilder,
|
|
||||||
// local_variables: &HashMap<ArcIntern<String>, (Variable, ConstantType)>,
|
|
||||||
// global_variables: &HashMap<String, (GlobalValue, ConstantType)>,
|
|
||||||
// ) -> Result<(entities::Value, ConstantType), BackendError> {
|
|
||||||
// match self {
|
|
||||||
// // Values are pretty straightforward to compile, mostly because we only
|
|
||||||
// // have one type of variable, and it's an integer type.
|
|
||||||
// ValueOrRef::Value(_, _, val) => match val {
|
|
||||||
// Value::I8(_, v) => {
|
|
||||||
// Ok((builder.ins().iconst(types::I8, v as i64), ConstantType::I8))
|
|
||||||
// }
|
|
||||||
// Value::I16(_, v) => Ok((
|
|
||||||
// builder.ins().iconst(types::I16, v as i64),
|
|
||||||
// ConstantType::I16,
|
|
||||||
// )),
|
|
||||||
// Value::I32(_, v) => Ok((
|
|
||||||
// builder.ins().iconst(types::I32, v as i64),
|
|
||||||
// ConstantType::I32,
|
|
||||||
// )),
|
|
||||||
// Value::I64(_, v) => Ok((builder.ins().iconst(types::I64, v), ConstantType::I64)),
|
|
||||||
// Value::U8(_, v) => {
|
|
||||||
// Ok((builder.ins().iconst(types::I8, v as i64), ConstantType::U8))
|
|
||||||
// }
|
|
||||||
// Value::U16(_, v) => Ok((
|
|
||||||
// builder.ins().iconst(types::I16, v as i64),
|
|
||||||
// ConstantType::U16,
|
|
||||||
// )),
|
|
||||||
// Value::U32(_, v) => Ok((
|
|
||||||
// builder.ins().iconst(types::I32, v as i64),
|
|
||||||
// ConstantType::U32,
|
|
||||||
// )),
|
|
||||||
// Value::U64(_, v) => Ok((
|
|
||||||
// builder.ins().iconst(types::I64, v as i64),
|
|
||||||
// ConstantType::U64,
|
|
||||||
// )),
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// ValueOrRef::Ref(_, _, name) => {
|
|
||||||
// // first we see if this is a local variable (which is nicer, from an
|
|
||||||
// // optimization point of view.)
|
|
||||||
// if let Some((local_var, etype)) = local_variables.get(&name) {
|
|
||||||
// return Ok((builder.use_var(*local_var), *etype));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // then we check to see if this is a global reference, which requires us to
|
|
||||||
// // first lookup where the value is stored, and then load it.
|
|
||||||
// if let Some((global_var, etype)) = global_variables.get(name.as_ref()) {
|
|
||||||
// let cranelift_type = ir::Type::from(*etype);
|
|
||||||
// let val_ptr = builder.ins().symbol_value(cranelift_type, *global_var);
|
|
||||||
// return Ok((
|
|
||||||
// builder
|
|
||||||
// .ins()
|
|
||||||
// .load(cranelift_type, MemFlags::new(), val_ptr, 0),
|
|
||||||
// *etype,
|
|
||||||
// ));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // this should never happen, because we should have made sure that there are
|
|
||||||
// // no unbound variables a long time before this. but still ...
|
|
||||||
// Err(BackendError::VariableLookupFailure(name))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use codespan_reporting::files::SimpleFiles;
|
use codespan_reporting::files::SimpleFiles;
|
||||||
|
use ngr::backend::Backend;
|
||||||
use ngr::eval::Value;
|
use ngr::eval::Value;
|
||||||
use ngr::syntax;
|
use ngr::syntax;
|
||||||
use ngr::type_infer::TypeInferenceResult;
|
use ngr::type_infer::TypeInferenceResult;
|
||||||
@@ -70,5 +71,12 @@ fn main() -> Result<(), anyhow::Error> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
unimplemented!();
|
let mut backend = Backend::jit(None)?;
|
||||||
}
|
let function_id = backend.compile_program("gogogo", ir)?;
|
||||||
|
backend.module.finalize_definitions()?;
|
||||||
|
let compiled_bytes = backend.bytes(function_id);
|
||||||
|
let compiled_function = unsafe { std::mem::transmute::<_, fn() -> ()>(compiled_bytes) };
|
||||||
|
compiled_function();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user