diff --git a/src/backend.rs b/src/backend.rs index c6f217b..c6cc556 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -34,6 +34,7 @@ mod runtime; pub use self::error::BackendError; pub use self::runtime::{RuntimeFunctionError, RuntimeFunctions}; use crate::syntax::ConstantType; +use cranelift_codegen::entity::EntityRef; use cranelift_codegen::settings::Configurable; use cranelift_codegen::{isa, settings}; use cranelift_jit::{JITBuilder, JITModule}; @@ -62,6 +63,7 @@ pub struct Backend { defined_symbols: HashMap, (DataId, ConstantType)>, output_buffer: Option, platform: Triple, + next_variable: usize, } impl Backend { @@ -93,6 +95,7 @@ impl Backend { defined_symbols: HashMap::new(), output_buffer, platform: Triple::host(), + next_variable: 23, }) } @@ -132,6 +135,7 @@ impl Backend { defined_symbols: HashMap::new(), output_buffer: None, platform, + next_variable: 23, }) } @@ -187,6 +191,32 @@ impl Backend { Ok(id) } + pub fn string_reference(&mut self, string: &str) -> Result { + 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`. /// /// As suggested, returns `null` in the case where the user has not provided an diff --git a/src/backend/into_crane.rs b/src/backend/into_crane.rs index 1b409f6..960cd55 100644 --- a/src/backend/into_crane.rs +++ b/src/backend/into_crane.rs @@ -18,18 +18,25 @@ use crate::backend::Backend; /// 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. -struct ReferenceBuilder { - ir_type: ConstantType, - cranelift_type: cranelift_codegen::ir::Type, - local_data: GlobalValue, +enum ReferenceBuilder { + Global(ConstantType, GlobalValue), + Local(ConstantType, cranelift_frontend::Variable), } impl ReferenceBuilder { fn refer_to(&self, builder: &mut FunctionBuilder) -> (entities::Value, ConstantType) { - let value = builder - .ins() - .symbol_value(self.cranelift_type, self.local_data); - (value, self.ir_type) + match self { + ReferenceBuilder::Global(ty, gv) => { + let cranelift_type = ir::Type::from(*ty); + 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 Backend { 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 @@ -143,15 +154,7 @@ impl Backend { // elsewhere in the program. 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 cranelift_type = ir::Type::from(*ty); - variables.insert( - name.clone(), - ReferenceBuilder { - cranelift_type, - local_data, - ir_type: *ty, - }, - ); + variables.insert(name.clone(), ReferenceBuilder::Global(*ty, local_data)); } // Once we have these, we're going to actually push a level of scope and @@ -177,7 +180,11 @@ impl Backend { // 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 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.finalize(); @@ -320,13 +327,10 @@ impl Backend { let buffer_ptr = builder.ins().iconst(types::I64, buffer_ptr as i64); // Get a reference to the string we want to print. - let string_data_id = self - .defined_strings - .get(var.as_ref()) - .ok_or_else(|| BackendError::UnknownString(var.clone()))?; + let string_data_id = self.string_reference(var.as_ref())?; let local_name_ref = self .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); // Look up the value for the variable. Because this might be a @@ -360,7 +364,16 @@ impl Backend { 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 Backend { }, } } - - // 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, - // ) -> Result { - // 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, (Variable, ConstantType)>, -// global_variables: &HashMap, -// ) -> 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` trait implementation -//// for `ValueOrRef` to compile this via the `Expression` logic, above. -//impl ValueOrRef { -// fn into_crane( -// self, -// builder: &mut FunctionBuilder, -// local_variables: &HashMap, (Variable, ConstantType)>, -// global_variables: &HashMap, -// ) -> 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)) -// } -// } -// } -//} -// diff --git a/src/bin/ngrun.rs b/src/bin/ngrun.rs index 9230f63..2296a06 100644 --- a/src/bin/ngrun.rs +++ b/src/bin/ngrun.rs @@ -1,5 +1,6 @@ use clap::Parser; use codespan_reporting::files::SimpleFiles; +use ngr::backend::Backend; use ngr::eval::Value; use ngr::syntax; use ngr::type_infer::TypeInferenceResult; @@ -70,5 +71,12 @@ fn main() -> Result<(), anyhow::Error> { return Ok(()); } - unimplemented!(); -} \ No newline at end of file + 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(()) +}