714 lines
32 KiB
Rust
714 lines
32 KiB
Rust
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<M: Module> Backend<M> {
|
|
/// 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<Type>,
|
|
) -> Result<FuncId, BackendError> {
|
|
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<Type>,
|
|
return_type: Type,
|
|
) -> Result<FuncId, cranelift_module::ModuleError> {
|
|
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<Name, ReferenceBuilder>,
|
|
function_name: Name,
|
|
arguments: &[(Name, 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()
|
|
.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<Type>,
|
|
variables: &mut HashMap<Name, ReferenceBuilder>,
|
|
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(_, _, _) => "<unknown>".to_string(),
|
|
ValueOrRef::Primitive(_, _, n) => format!("<primitive {}>", 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::<Result<Vec<(_, _)>, 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<Type>,
|
|
variables: &HashMap<Name, ReferenceBuilder>,
|
|
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
|
|
}
|
|
}
|