use crate::backend::error::BackendError; use crate::backend::Backend; use crate::eval::PrimitiveType; use crate::ir::{Expression, Name, Primitive, Program, Type, Value, ValueOrRef}; use crate::syntax::{ConstantType, Location}; use cranelift_codegen::ir::{ 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::{DataDescription, FuncId, Linkage, Module}; use std::collections::{hash_map, HashMap}; const VOID_REPR_TYPE: types::Type = types::I64; /// 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. pub enum ReferenceBuilder { Global(types::Type, GlobalValue), Local(types::Type, cranelift_frontend::Variable), Argument(types::Type, entities::Value), } impl Backend { /// Translate the given IR type into an ABI parameter type for cranelift, as /// best as possible. fn translate_type(&self, t: &Type) -> AbiParam { let (value_type, extension) = match t { Type::Function(_, _) | Type::Structure(_) => ( types::Type::triple_pointer_type(&self.platform), ir::ArgumentExtension::None, ), Type::Primitive(PrimitiveType::Void) => (types::I8, ir::ArgumentExtension::None), // FIXME? Type::Primitive(PrimitiveType::I8) => (types::I8, ir::ArgumentExtension::Sext), Type::Primitive(PrimitiveType::I16) => (types::I16, ir::ArgumentExtension::Sext), Type::Primitive(PrimitiveType::I32) => (types::I32, ir::ArgumentExtension::Sext), Type::Primitive(PrimitiveType::I64) => (types::I64, ir::ArgumentExtension::Sext), Type::Primitive(PrimitiveType::U8) => (types::I8, ir::ArgumentExtension::Uext), Type::Primitive(PrimitiveType::U16) => (types::I16, ir::ArgumentExtension::Uext), Type::Primitive(PrimitiveType::U32) => (types::I32, ir::ArgumentExtension::Uext), Type::Primitive(PrimitiveType::U64) => (types::I64, ir::ArgumentExtension::Uext), }; AbiParam { value_type, purpose: ir::ArgumentPurpose::Normal, extension, } } /// Compile the given program. /// /// The returned value is a `FuncId` that represents a function that runs all the statements /// found in the program, which will be compiled using the given function name. (If there /// are no such statements, the function will do nothing.) pub fn compile_program( &mut self, function_name: Name, program: Program, ) -> Result { 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.current_name(), 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.current_name(), Linkage::Export, true, false, )?; tracing::info!(name = %top_level_name, data_type = %pt, "defining top-level data"); self.module.define_data(data_id, &pt.blank_data())?; let constant_type = ConstantType::from(pt); self.defined_symbols .insert(top_level_name, (data_id, constant_type.into())); } Type::Structure(mut fields) => { let data_id = self.module.declare_data( top_level_name.current_name(), Linkage::Export, true, false, )?; tracing::info!(name = %top_level_name, "defining top-level data structure"); self.module.define_data(data_id, &fields.blank_data())?; let pointer = self.module.target_config().pointer_type(); self.defined_symbols .insert(top_level_name, (data_id, pointer)); } } } let void = Type::Primitive(PrimitiveType::Void); let main_func_id = self.declare_function( function_name.current_name(), Linkage::Export, vec![], void.clone(), )?; self.defined_functions .insert(function_name.clone(), main_func_id); for (_, function) in program.functions.into_iter() { let func_id = self.compile_function( &mut variables, function.name.clone(), &function.arguments, function.return_type, function.body, )?; self.defined_functions.insert(function.name, func_id); } self.compile_function( &mut variables, function_name, &[], void.clone(), program.body, ) } fn declare_function( &mut self, name: &str, linkage: Linkage, argument_types: Vec, return_type: Type, ) -> Result { tracing::info!(linkage = ?linkage, name, "Declaring function"); 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. #[tracing::instrument(level = "debug", skip(self, variables, body))] pub fn compile_function( &mut self, variables: &mut HashMap, function_name: Name, arguments: &[(Name, Type)], return_type: Type, body: Expression, ) -> Result { // 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() .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. let func_id = match self.defined_functions.entry(function_name.clone()) { hash_map::Entry::Occupied(entry) => *entry.get(), hash_map::Entry::Vacant(vac) => { tracing::warn!(name = ?function_name.current_name(), "compiling undeclared function"); let func_id = self.module.declare_function( function_name.current_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. // Since we're only generating one function per `Program`, this makes // complete sense. However, in the future, we may want to revisit this // decision. let mut ctx = Context::new(); let user_func_name = UserFuncName::user(0, func_id.as_u32()); ctx.func = Function::with_name_signature(user_func_name, basic_signature); // 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() { let local_data = self.module.declare_data_in_func(*data_id, &mut ctx.func); variables.insert(name.clone(), ReferenceBuilder::Global(*ty, local_data)); } // Finally (!), we generate the function builder that we're going to use to // make this function! let mut fctx = FunctionBuilderContext::new(); let mut builder = FunctionBuilder::new(&mut ctx.func, &mut fctx); // Make the initial block to put instructions in. Later, when we have control // flow, we might add more blocks after this one. But, for now, we only have // the one block. let main_block = builder.create_block(); // add the block parameters, which should be the function parameters for (name, ty) in arguments.iter() { let constant_type = self.translate_type(ty).value_type; let value = builder.append_block_param(main_block, constant_type); variables.insert( name.clone(), ReferenceBuilder::Argument(constant_type, value), ); } builder.switch_to_block(main_block); 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 // know that the block is done), and then finalize the function (which lets Cranelift // know we're done with the function). if return_type == Type::Primitive(PrimitiveType::Void) { builder.ins().return_(&[]); } else { builder.ins().return_(&[value]); }; builder.seal_block(main_block); builder.finalize(); // This is a little odd. We want to tell the rest of Cranelift about this function, // so we register it using the function ID and our builder context. However, the // result of this function isn't actually super helpful. So we ignore it, unless // it's an error. self.module.define_function(func_id, &mut ctx)?; // done! Ok(func_id) } /// Compile an expression, returning the Cranelift Value for the expression and /// its type. #[tracing::instrument(level = "trace", skip(self, variables, builder))] fn compile_expression( &mut self, expr: Expression, variables: &mut HashMap, builder: &mut FunctionBuilder, ) -> Result<(entities::Value, types::Type), BackendError> { match expr { Expression::Atomic(x) => self.compile_value_or_ref(x, variables, builder), Expression::Cast(_, target_type, valref) => { let val_is_signed = valref.type_of().is_signed(); let (val, val_type) = self.compile_value_or_ref(valref, variables, builder)?; match (val_type, &target_type) { (types::I8, Type::Primitive(PrimitiveType::I8)) => Ok((val, val_type)), (types::I8, Type::Primitive(PrimitiveType::I16)) => { if val_is_signed { Ok((builder.ins().sextend(types::I16, val), types::I16)) } else { Ok((builder.ins().uextend(types::I16, val), types::I16)) } } (types::I8, Type::Primitive(PrimitiveType::I32)) => { if val_is_signed { Ok((builder.ins().sextend(types::I32, val), types::I32)) } else { Ok((builder.ins().uextend(types::I32, val), types::I32)) } } (types::I8, Type::Primitive(PrimitiveType::I64)) => { if val_is_signed { Ok((builder.ins().sextend(types::I64, val), types::I64)) } else { Ok((builder.ins().uextend(types::I64, val), types::I64)) } } (types::I16, Type::Primitive(PrimitiveType::I16)) => Ok((val, val_type)), (types::I16, Type::Primitive(PrimitiveType::I32)) => { if val_is_signed { Ok((builder.ins().sextend(types::I32, val), types::I32)) } else { Ok((builder.ins().uextend(types::I32, val), types::I32)) } } (types::I16, Type::Primitive(PrimitiveType::I64)) => { if val_is_signed { Ok((builder.ins().sextend(types::I64, val), types::I64)) } else { Ok((builder.ins().uextend(types::I64, val), types::I64)) } } (types::I32, Type::Primitive(PrimitiveType::I32)) => Ok((val, val_type)), (types::I32, Type::Primitive(PrimitiveType::I64)) => { if val_is_signed { Ok((builder.ins().sextend(types::I64, val), types::I64)) } else { Ok((builder.ins().uextend(types::I64, val), types::I64)) } } (types::I64, Type::Primitive(PrimitiveType::I64)) => Ok((val, val_type)), (types::I8, Type::Primitive(PrimitiveType::U8)) => Ok((val, val_type)), (types::I8, Type::Primitive(PrimitiveType::U16)) => { Ok((builder.ins().uextend(types::I16, val), types::I16)) } (types::I8, Type::Primitive(PrimitiveType::U32)) => { Ok((builder.ins().uextend(types::I32, val), types::I32)) } (types::I8, Type::Primitive(PrimitiveType::U64)) => { Ok((builder.ins().uextend(types::I64, val), types::I64)) } (types::I16, Type::Primitive(PrimitiveType::U16)) => Ok((val, val_type)), (types::I16, Type::Primitive(PrimitiveType::U32)) => { Ok((builder.ins().uextend(types::I32, val), types::I32)) } (types::I16, Type::Primitive(PrimitiveType::U64)) => { Ok((builder.ins().uextend(types::I64, val), types::I64)) } (types::I32, Type::Primitive(PrimitiveType::U32)) => Ok((val, val_type)), (types::I32, Type::Primitive(PrimitiveType::U64)) => { Ok((builder.ins().uextend(types::I64, val), types::I64)) } (types::I64, Type::Primitive(PrimitiveType::U64)) => Ok((val, val_type)), (types::I64, Type::Primitive(PrimitiveType::Void)) => { Ok((builder.ins().iconst(types::I64, 0), VOID_REPR_TYPE)) } _ => Err(BackendError::InvalidTypeCast { from: val_type, to: target_type, }), } } Expression::Construct(_, ty, _, fields) => { let Type::Structure(type_fields) = ty else { panic!("Got to backend with non-structure type in structure construction?!"); }; let global_allocator = Name::new("__global_allocation_pointer__", Location::manufactured()); let Some(ReferenceBuilder::Global(_, allocator_variable)) = variables.get(&global_allocator) else { panic!("Couldn't find global allocation pointer"); }; let pointer_to = self.module.target_config().pointer_type(); let allocator_pointer = builder.ins().symbol_value(pointer_to, *allocator_variable); let structure = builder .ins() .load(pointer_to, MemFlags::new(), allocator_pointer, 0); let structure_size = builder .ins() .iconst(pointer_to, type_fields.object_size() as i64); let updated_allocator_value = builder.ins().iadd(structure, structure_size); builder.ins().store( MemFlags::new(), updated_allocator_value, allocator_pointer, 0, ); for (field_name, field_value) in fields.into_iter() { let (field_value, field_cranelift_type) = self.compile_value_or_ref(field_value, variables, builder)?; let Some((field_internal_type, offset)) = type_fields.field_type_and_offset(&field_name) else { panic!("Got to backend with mismatched construction and type definition"); }; assert_eq!( field_cranelift_type, self.translate_type(field_internal_type).value_type ); builder .ins() .store(MemFlags::new(), field_value, structure, offset); } Ok((structure, self.module.target_config().pointer_type())) } Expression::FieldRef(_, _, struct_type, val, field) => { let (structure, _) = self.compile_value_or_ref(val, variables, builder)?; let Type::Structure(fields) = struct_type else { panic!("Got to backend with non-structure type in field reference?!"); }; let Some((field_type, offset)) = fields.field_type_and_offset(&field) else { panic!("Got to backend with invalid field for structure type?!"); }; let field_cranelift_type = self.translate_type(field_type).value_type; let value = builder .ins() .load(field_cranelift_type, MemFlags::new(), structure, offset); Ok((value, field_cranelift_type)) } Expression::Block(_, _, mut exprs) => match exprs.pop() { None => Ok((builder.ins().iconst(types::I64, 0), VOID_REPR_TYPE)), Some(last) => { for inner in exprs { // we can ignore all of these return values and such, because we // don't actually use them anywhere self.compile_expression(inner, variables, builder)?; } // instead, we just return the last one self.compile_expression(last, variables, builder) } }, Expression::Bind(_, name, _, expr) => { let (value, value_type) = self.compile_expression(*expr, variables, builder)?; let variable = self.generate_local(); match variables.get(&name) { Some(ReferenceBuilder::Global(_, global_value)) => { let pointer = self.module.target_config().pointer_type(); let pointer_to = builder.ins().symbol_value(pointer, *global_value); builder.ins().store(MemFlags::new(), value, pointer_to, 0); } Some(ReferenceBuilder::Argument(_, _)) => { panic!("Attempt to mutate an argument {}", name) } Some(ReferenceBuilder::Local(_, _)) => { panic!("Attempt to mutate local {}", name); } None => { builder.declare_var(variable, value_type); builder.def_var(variable, value); variables.insert(name, ReferenceBuilder::Local(value_type, variable)); } } Ok((value, value_type)) } Expression::Call(_, final_type, function, args) => { // Get a reference to the string we want to print. let var_name = match args[0] { ValueOrRef::Ref(_, _, ref name) => name.to_string(), ValueOrRef::Value(_, _, _) => "".to_string(), ValueOrRef::Primitive(_, _, n) => format!("", n), }; let var_type = args[0].type_of(); let (arguments, argument_types): (Vec<_>, Vec<_>) = args .into_iter() .map(|x| self.compile_value_or_ref(x, variables, builder)) .collect::, BackendError>>()? .into_iter() .unzip(); match function { ValueOrRef::Value(_, _, _) => { panic!("Can't use a value for a function") } ValueOrRef::Ref(_, result_type, name) => { match self.defined_functions.get(&name) { None => panic!("Couldn't find function {} to call", name), Some(function) => { let func_ref = self.module.declare_func_in_func(*function, builder.func); let call = builder.ins().call(func_ref, &arguments); let results = builder.inst_results(call); match results { [] => Ok((builder.ins().iconst(types::I64, 0), VOID_REPR_TYPE)), [result] => { Ok((*result, self.translate_type(&result_type).value_type)) } _ => panic!("don't support multi-value returns yet"), } } } } ValueOrRef::Primitive(_, _, prim) => match prim { Primitive::Plus => { assert_eq!(2, arguments.len()); Ok(( builder.ins().iadd(arguments[0], arguments[1]), argument_types[0], )) } Primitive::Minus => { assert_eq!(2, arguments.len()); Ok(( builder.ins().isub(arguments[0], arguments[1]), argument_types[0], )) } Primitive::Times => { assert_eq!(2, arguments.len()); Ok(( builder.ins().imul(arguments[0], arguments[1]), argument_types[0], )) } Primitive::Divide if final_type.is_signed() => { assert_eq!(2, arguments.len()); Ok(( builder.ins().sdiv(arguments[0], arguments[1]), argument_types[0], )) } Primitive::Divide => { assert_eq!(2, arguments.len()); Ok(( builder.ins().udiv(arguments[0], arguments[1]), argument_types[0], )) } Primitive::Negate => { assert_eq!(1, arguments.len()); Ok((builder.ins().ineg(arguments[0]), argument_types[0])) } Primitive::Print => { // 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); assert_eq!(1, arguments.len()); let string_data_id = self.string_reference(&var_name)?; let local_name_ref = self .module .declare_data_in_func(string_data_id, builder.func); let name_ptr = builder.ins().symbol_value(types::I64, local_name_ref); let (repr_val, casted_val) = match var_type { Type::Structure(_) => (ConstantType::I64 as i64, arguments[0]), Type::Function(_, _) => (ConstantType::I64 as i64, arguments[0]), Type::Primitive(pt) => { let constant_type = pt.into(); let new_val = match constant_type { ConstantType::U64 | ConstantType::I64 | ConstantType::Void => arguments[0], ConstantType::I8 | ConstantType::I16 | ConstantType::I32 => { builder.ins().sextend(types::I64, arguments[0]) } ConstantType::U8 | ConstantType::U16 | ConstantType::U32 => { builder.ins().uextend(types::I64, arguments[0]) } }; (constant_type as i64, new_val) } }; let vtype_repr = builder.ins().iconst(types::I64, repr_val); // Finally, we can generate the call to print. let print_func_ref = self.runtime_functions.include_runtime_function( "print", &mut self.module, builder.func, )?; builder.ins().call( print_func_ref, &[buffer_ptr, name_ptr, vtype_repr, casted_val], ); Ok((builder.ins().iconst(types::I64, 0), VOID_REPR_TYPE)) } }, } } } } /// Compile a value or reference into Cranelift, returning the Cranelift Value for /// the expression and its type. #[tracing::instrument(level = "trace", skip(self, variables, builder))] fn compile_value_or_ref( &self, value_or_ref: ValueOrRef, variables: &HashMap, builder: &mut FunctionBuilder, ) -> Result<(entities::Value, types::Type), BackendError> { match value_or_ref { ValueOrRef::Value(_, _, val) => match val { Value::I8(_, v) => { // Cranelift does a funny thing where it checks you aren't using bits in the I64 // we provide above the size of the type we provide. So, in this case, we can only // set the low 8 bits of the i64. This restriction creates a bit of a problem when // casting direction from i8 to i64, because Rust will (helpfully) sign-extend the // negative number for us. Which sets the high bits, which makes Cranelift unhappy. // So first we cast the i8 as u8, to get rid of the whole concept of sign extension, // and *then* we cast to i64. Ok((builder.ins().iconst(types::I8, v as u8 as i64), types::I8)) } Value::I16(_, v) => Ok(( // see above note for the "... as ... as" builder.ins().iconst(types::I16, v as u16 as i64), types::I16, )), Value::I32(_, v) => Ok(( // see above note for the "... as ... as" builder.ins().iconst(types::I32, v as u32 as i64), types::I32, )), Value::I64(_, v) => Ok((builder.ins().iconst(types::I64, v), types::I64)), Value::U8(_, v) => Ok((builder.ins().iconst(types::I8, v as i64), types::I8)), Value::U16(_, v) => Ok((builder.ins().iconst(types::I16, v as i64), types::I16)), Value::U32(_, v) => Ok((builder.ins().iconst(types::I32, v as i64), types::I32)), Value::U64(_, v) => Ok((builder.ins().iconst(types::I64, v as i64), types::I64)), Value::Void => Ok((builder.ins().iconst(types::I64, 0i64), VOID_REPR_TYPE)), }, ValueOrRef::Ref(_, _, name) => match variables.get(&name) { None => Err(BackendError::VariableLookupFailure(name)), Some(ReferenceBuilder::Global(ty, gv)) => { let pointer_to = self.module.target_config().pointer_type(); let pointer_value = builder.ins().symbol_value(pointer_to, *gv); let value = builder.ins().load(*ty, MemFlags::new(), pointer_value, 0); Ok((value, *ty)) } Some(ReferenceBuilder::Argument(ctype, val)) => Ok((*val, *ctype)), Some(ReferenceBuilder::Local(ctype, var)) => { let value = builder.use_var(*var); Ok((value, *ctype)) } }, ValueOrRef::Primitive(_, _, _) => { unimplemented!() } } } } 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 => (8, 8), PrimitiveType::I8 => (1, 1), PrimitiveType::I16 => (2, 2), PrimitiveType::I32 => (4, 4), PrimitiveType::I64 => (8, 8), }; let mut result = DataDescription::new(); result.define_zeroinit(size); result.set_align(alignment); result } }