diff options
| author | Michael Williamson <mike@zwobble.org> | 2026-04-05 21:57:10 +0100 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2026-04-05 21:57:19 +0100 |
| commit | cb298210348c0a1c160ea208eb5f597765de0d31 (patch) | |
| tree | 631c99e48ef5fe5060c6c4d4f174bdcd1fcc07d6 /patio-to-cranelift | |
| parent | e937b17b276ca98950c5c2e09e646ccd9338278c (diff) | |
Create separate codegen module
Diffstat (limited to 'patio-to-cranelift')
| -rw-r--r-- | patio-to-cranelift/src/codegen/context.rs (renamed from patio-to-cranelift/src/context.rs) | 0 | ||||
| -rw-r--r-- | patio-to-cranelift/src/codegen/mod.rs | 1080 | ||||
| -rw-r--r-- | patio-to-cranelift/src/lib.rs | 1076 |
3 files changed, 1085 insertions, 1071 deletions
diff --git a/patio-to-cranelift/src/context.rs b/patio-to-cranelift/src/codegen/context.rs index 09b08173..09b08173 100644 --- a/patio-to-cranelift/src/context.rs +++ b/patio-to-cranelift/src/codegen/context.rs diff --git a/patio-to-cranelift/src/codegen/mod.rs b/patio-to-cranelift/src/codegen/mod.rs new file mode 100644 index 00000000..aee8e9d8 --- /dev/null +++ b/patio-to-cranelift/src/codegen/mod.rs @@ -0,0 +1,1080 @@ +use std::{io::Write, path::Path}; + +use cranelift::prelude::InstBuilder; + +use crate::{ + codegen::context::{CompilerContext, CompilerFunctionContext, Mutability}, + errors::CompileError, + patio::{ + compilation_unit::{ + PatioCompilationUnit, PatioCompilationUnitElement, PatioDataElementImmutableDefinition, + PatioDataElementMutableDefinition, PatioFunctionDeclaration, PatioFunctionDefinition, + PatioLocal, PatioLocalType, PatioVisibility, + }, + instructions::{ + PatioBlock, PatioBoolToInt, PatioCall, PatioIf, PatioInstruction, PatioIntAdd, + PatioIntEquals, PatioIntNotEqual, PatioIntSub, PatioIntToBool, PatioLocalStore, + PatioMemoryLoad, PatioMemoryStore, PatioResume, PatioReturn, PatioReturnVoid, + PatioVarLoad, PatioVarStore, + }, + operands::{PatioLocalRef, PatioOperand}, + types::{PatioDataLayout, PatioType}, + values::{PatioDataPtrValue, PatioDataValue, PatioIntValue}, + }, +}; + +mod context; + +pub(crate) fn compile_compilation_unit( + compilation_unit: PatioCompilationUnit, + source_path: &Path, + target_triple: target_lexicon::Triple, +) -> Result<Vec<u8>, CompileError> { + let mut context = CompilerContext::for_object(source_path, target_triple)?; + + for element in &compilation_unit.elements { + declare_compilation_unit_element(&element, &mut context); + } + + for element in &compilation_unit.elements { + define_compilation_unit_element(&element, &mut context); + } + + let object = context.into_object(); + object.emit().map_err(CompileError::ObjectEmitFailed) +} + +fn declare_compilation_unit_element( + element: &PatioCompilationUnitElement, + context: &mut CompilerContext, +) { + match element { + PatioCompilationUnitElement::DataElementImmutableDefinition( + data_element_immutable_definition, + ) => { + declare_data_element_immutable_definition(data_element_immutable_definition, context); + } + + PatioCompilationUnitElement::DataElementMutableDefinition( + data_element_mutable_definition, + ) => { + declare_data_element_mutable_definition(data_element_mutable_definition, context); + } + + PatioCompilationUnitElement::FunctionDeclaration(function_declaration) => { + declare_function_declaration(function_declaration, context); + } + + PatioCompilationUnitElement::FunctionDefinition(function_definition) => { + declare_function_definition(function_definition, context); + } + } +} + +fn define_compilation_unit_element( + element: &PatioCompilationUnitElement, + context: &mut CompilerContext, +) { + match element { + PatioCompilationUnitElement::DataElementImmutableDefinition( + data_element_immutable_definition, + ) => { + define_data_element_immutable_definition(data_element_immutable_definition, context); + } + + PatioCompilationUnitElement::DataElementMutableDefinition( + data_element_mutable_definition, + ) => { + define_data_element_mutable_definition(data_element_mutable_definition, context); + } + + PatioCompilationUnitElement::FunctionDeclaration(_) => {} + + PatioCompilationUnitElement::FunctionDefinition(function_definition) => { + define_function_definition(&function_definition, context); + } + } +} + +fn declare_data_element_immutable_definition( + data_element_immutable_definition: &PatioDataElementImmutableDefinition, + context: &mut CompilerContext, +) { + context.declare_data( + &data_element_immutable_definition.name, + patio_visibility_to_cranelift_linkage(data_element_immutable_definition.visibility), + Mutability::Immutable, + ); +} + +fn define_data_element_immutable_definition( + data_element_immutable_definition: &PatioDataElementImmutableDefinition, + context: &mut CompilerContext, +) { + let bytes = data_value_to_bytes( + &data_element_immutable_definition.value, + &context.target_triple, + ); + context.define_data(&data_element_immutable_definition.name, bytes); +} + +fn declare_data_element_mutable_definition( + data_element_mutable_definition: &PatioDataElementMutableDefinition, + context: &mut CompilerContext, +) { + context.declare_data( + &data_element_mutable_definition.name, + patio_visibility_to_cranelift_linkage(data_element_mutable_definition.visibility), + Mutability::Mutable, + ); +} + +fn define_data_element_mutable_definition( + data_element_mutable_definition: &PatioDataElementMutableDefinition, + context: &mut CompilerContext, +) { + let bytes = data_value_to_bytes( + &data_element_mutable_definition.initial_value, + &context.target_triple, + ); + context.define_data(&data_element_mutable_definition.name, bytes); +} + +fn declare_function_declaration( + function_declaration: &PatioFunctionDeclaration, + context: &mut CompilerContext, +) { + let cranelift_signature = cranelift_signature( + function_declaration.params.iter(), + function_declaration.return_type, + &context.target_triple, + ); + + context.declare_function( + &function_declaration.name, + cranelift_module::Linkage::Import, + &cranelift_signature, + ); +} + +fn declare_function_definition( + function_definition: &PatioFunctionDefinition, + context: &mut CompilerContext, +) { + let cranelift_signature = cranelift_signature( + function_definition.locals.iter().map(Into::into), + function_definition.return_type, + &context.target_triple, + ); + + let cranelift_linkage = patio_visibility_to_cranelift_linkage(function_definition.visibility); + + context.declare_function( + &function_definition.name, + cranelift_linkage, + &cranelift_signature, + ); +} + +fn define_function_definition( + function_definition: &PatioFunctionDefinition, + context: &mut CompilerContext, +) { + let cranelift_signature = cranelift_signature( + function_definition.locals.iter().map(Into::into), + function_definition.return_type, + &context.target_triple, + ); + + context.enter_function( + &function_definition.name, + cranelift_signature, + |function_context| { + for local in &function_definition.locals { + match local.local_type { + PatioLocalType::Param => {} + + PatioLocalType::Val => { + let cranelift_type = patio_type_to_cranelift_type( + local.type_, + &function_context.target_triple, + ); + function_context.declare_var(&local.name, cranelift_type); + } + + PatioLocalType::Var => { + let cranelift_type = patio_type_to_cranelift_type( + local.type_, + &function_context.target_triple, + ); + function_context.create_stack_slot(&local.name, cranelift_type); + } + } + } + + for instruction in &function_definition.body { + compile_instruction(instruction, function_context) + } + }, + ); +} + +fn compile_instruction(instruction: &PatioInstruction, context: &mut CompilerFunctionContext<'_>) { + match instruction { + PatioInstruction::Block(block) => { + compile_block(block, context); + } + + PatioInstruction::BoolToInt(bool_to_int) => { + compile_bool_to_int(bool_to_int, context); + } + + PatioInstruction::Call(call) => { + compile_call(call, context); + } + + PatioInstruction::If(if_) => { + compile_if(if_, context); + } + + PatioInstruction::IntAdd(int_add) => { + compile_int_add(int_add, context); + } + + PatioInstruction::IntEquals(int_equals) => { + compile_int_equals(int_equals, context); + } + + PatioInstruction::IntNotEqual(int_not_equal) => { + compile_int_not_equal(int_not_equal, context); + } + + PatioInstruction::IntSub(int_sub) => { + compile_int_sub(int_sub, context); + } + + PatioInstruction::IntToBool(int_to_bool) => { + compile_int_to_bool(int_to_bool, context); + } + + PatioInstruction::LocalStore(local_store) => { + compile_local_store(local_store, context); + } + + PatioInstruction::MemoryLoad(memory_load) => { + compile_memory_load(memory_load, context); + } + + PatioInstruction::MemoryStore(memory_store) => { + compile_memory_store(memory_store, context); + } + + PatioInstruction::Resume(resume) => { + compile_resume(resume, context); + } + + PatioInstruction::Return(return_) => { + compile_return(return_, context); + } + + PatioInstruction::ReturnVoid(return_void) => { + compile_return_void(return_void, context); + } + + PatioInstruction::VarLoad(var_load) => { + compile_var_load(var_load, context); + } + + PatioInstruction::VarStore(var_store) => { + compile_var_store(var_store, context); + } + } +} + +fn compile_block( + block: &PatioBlock, + context: &mut CompilerFunctionContext<'_>, +) -> Vec<cranelift_codegen::ir::Value> { + for instruction in &block.body { + compile_instruction(instruction, context); + } + + block + .results + .iter() + .map(|result| compile_operand(&result, context)) + .collect() +} + +fn compile_bool_to_int(bool_to_int: &PatioBoolToInt, context: &mut CompilerFunctionContext<'_>) { + let cranelift_value = compile_operand(&bool_to_int.value, context); + + let cranelift_result = context + .function_builder + .ins() + .uextend(cranelift_int_type(&context.target_triple), cranelift_value); + + def_destination(&bool_to_int.destination, cranelift_result, context) +} + +fn compile_call(call: &PatioCall, context: &mut CompilerFunctionContext<'_>) { + let cranelift_args = compile_operands(call.args.iter(), context); + + let func_ref = context.func_id(&call.function_name); + context + .function_builder + .ins() + .call(func_ref, &cranelift_args); +} + +fn compile_if(if_: &PatioIf, context: &mut CompilerFunctionContext<'_>) { + let cranelift_condition = compile_operand(&if_.condition, context); + let cranelift_then_block = context.function_builder.create_block(); + let cranelift_else_block = context.function_builder.create_block(); + let cranelift_end_block = context.function_builder.create_block(); + let cranelift_end_params: Vec<_> = if_ + .destinations + .iter() + .map(|destination| { + let cranelift_type = context.var_type(&destination.name); + context + .function_builder + .append_block_param(cranelift_end_block, cranelift_type) + }) + .collect(); + + context.function_builder.ins().brif( + cranelift_condition, + cranelift_then_block, + &[], + cranelift_else_block, + &[], + ); + context + .function_builder + .seal_block(context.function_builder.current_block().unwrap()); + + context + .function_builder + .switch_to_block(cranelift_then_block); + let cranelift_then_results = compile_block(&if_.true_body, context); + context.function_builder.ins().jump( + cranelift_end_block, + cranelift_block_args(cranelift_then_results).iter(), + ); + context.function_builder.seal_block(cranelift_then_block); + + context + .function_builder + .switch_to_block(cranelift_else_block); + let cranelift_else_results = compile_block(&if_.false_body, context); + context.function_builder.ins().jump( + cranelift_end_block, + cranelift_block_args(cranelift_else_results).iter(), + ); + context.function_builder.seal_block(cranelift_else_block); + + context + .function_builder + .switch_to_block(cranelift_end_block); + + for (destination, cranelift_end_param) in if_.destinations.iter().zip(cranelift_end_params) { + def_destination(destination, cranelift_end_param, context); + } +} + +fn compile_int_add(int_add: &PatioIntAdd, context: &mut CompilerFunctionContext<'_>) { + let cranelift_left = compile_operand(&int_add.left, context); + let cranelift_right = compile_operand(&int_add.right, context); + let cranelift_result = context + .function_builder + .ins() + .iadd(cranelift_left, cranelift_right); + + def_destination(&int_add.destination, cranelift_result, context); +} + +fn compile_int_equals(int_equals: &PatioIntEquals, context: &mut CompilerFunctionContext<'_>) { + let cranelift_left = compile_operand(&int_equals.left, context); + let cranelift_right = compile_operand(&int_equals.right, context); + let cranelift_result = context.function_builder.ins().icmp( + cranelift_codegen::ir::condcodes::IntCC::Equal, + cranelift_left, + cranelift_right, + ); + + def_destination(&int_equals.destination, cranelift_result, context); +} + +fn compile_int_not_equal( + int_not_equal: &PatioIntNotEqual, + context: &mut CompilerFunctionContext<'_>, +) { + let cranelift_left = compile_operand(&int_not_equal.left, context); + let cranelift_right = compile_operand(&int_not_equal.right, context); + let cranelift_result = context.function_builder.ins().icmp( + cranelift_codegen::ir::condcodes::IntCC::NotEqual, + cranelift_left, + cranelift_right, + ); + + def_destination(&int_not_equal.destination, cranelift_result, context); +} + +fn compile_int_sub(int_sub: &PatioIntSub, context: &mut CompilerFunctionContext<'_>) { + let cranelift_left = compile_operand(&int_sub.left, context); + let cranelift_right = compile_operand(&int_sub.right, context); + let cranelift_result = context + .function_builder + .ins() + .isub(cranelift_left, cranelift_right); + + def_destination(&int_sub.destination, cranelift_result, context); +} + +fn compile_int_to_bool(int_to_bool: &PatioIntToBool, context: &mut CompilerFunctionContext<'_>) { + let cranelift_value = compile_operand(&int_to_bool.value, context); + + let cranelift_result = context + .function_builder + .ins() + .ireduce(CRANELIFT_BOOL_TYPE, cranelift_value); + + def_destination(&int_to_bool.destination, cranelift_result, context); +} + +fn compile_local_store(local_store: &PatioLocalStore, context: &mut CompilerFunctionContext<'_>) { + let cranelift_value = compile_operand(&local_store.value, context); + def_destination(&local_store.destination, cranelift_value, context); +} + +fn compile_memory_load(memory_load: &PatioMemoryLoad, context: &mut CompilerFunctionContext<'_>) { + let value_layout = data_layout_element_at(&memory_load.layout, &memory_load.element_offsets); + let value_type = data_layout_to_type(value_layout); + let cranelift_type = patio_type_to_cranelift_type(value_type, &context.target_triple); + + let cranelift_pointer = compile_operand(&memory_load.pointer, context); + let cranelift_flags = cranelift_codegen::ir::MemFlags::new(); + let byte_offset = data_layout_byte_offset( + &memory_load.layout, + &memory_load.element_offsets, + &context.target_triple, + ); + let cranelift_offset = + cranelift_codegen::ir::immediates::Offset32::new(byte_offset.try_into().unwrap()); + let cranelift_value = context.function_builder.ins().load( + cranelift_type, + cranelift_flags, + cranelift_pointer, + cranelift_offset, + ); + + def_destination(&memory_load.destination, cranelift_value, context); +} + +fn compile_memory_store( + memory_store: &PatioMemoryStore, + context: &mut CompilerFunctionContext<'_>, +) { + let cranelift_value = compile_operand(&memory_store.value, context); + let cranelift_pointer = compile_operand(&memory_store.pointer, context); + let cranelift_flags = cranelift_codegen::ir::MemFlags::new(); + let byte_offset = data_layout_byte_offset( + &memory_store.layout, + &memory_store.element_offsets, + &context.target_triple, + ); + let cranelift_offset = + cranelift_codegen::ir::immediates::Offset32::new(byte_offset.try_into().unwrap()); + + context.function_builder.ins().store( + cranelift_flags, + cranelift_value, + cranelift_pointer, + cranelift_offset, + ); +} + +fn compile_resume(resume: &PatioResume, context: &mut CompilerFunctionContext<'_>) { + let cranelift_value = compile_operand(&resume.value, context); + + context.function_builder.ins().return_(&[cranelift_value]); +} + +fn compile_return(return_: &PatioReturn, context: &mut CompilerFunctionContext<'_>) { + let cranelift_value = compile_operand(&return_.value, context); + + context.function_builder.ins().return_(&[cranelift_value]); +} + +fn compile_return_void(_return_void: &PatioReturnVoid, context: &mut CompilerFunctionContext<'_>) { + context.function_builder.ins().return_(&[]); +} + +fn compile_var_load(var_load: &PatioVarLoad, context: &mut CompilerFunctionContext<'_>) { + let stack_slot = context.stack_slot(&var_load.var.name); + let type_ = context.stack_slot_type(&var_load.var.name); + let value = context.function_builder.ins().stack_load( + type_, + stack_slot, + cranelift_codegen::ir::immediates::Offset32::new(0), + ); + def_destination(&var_load.destination, value, context); +} + +fn compile_var_store(var_store: &PatioVarStore, context: &mut CompilerFunctionContext<'_>) { + let stack_slot = context.stack_slot(&var_store.var.name); + let value = compile_operand(&var_store.value, context); + context.function_builder.ins().stack_store( + value, + stack_slot, + cranelift_codegen::ir::immediates::Offset32::new(0), + ); +} + +fn compile_operands<'a>( + operands: impl Iterator<Item = &'a PatioOperand>, + context: &mut CompilerFunctionContext<'_>, +) -> Vec<cranelift::prelude::Value> { + operands + .map(|operand| compile_operand(operand, context)) + .collect() +} + +fn compile_operand( + operand: &PatioOperand, + context: &mut CompilerFunctionContext<'_>, +) -> cranelift::prelude::Value { + match operand { + PatioOperand::BoolConst(value) => { + let integer_value = if *value { 1 } else { 0 }; + + context + .function_builder + .ins() + .iconst(CRANELIFT_BOOL_TYPE, integer_value) + } + + PatioOperand::DataPtrConst(value) => compile_data_ptr_value(&value, context), + + PatioOperand::IntConst(value) => { + let cranelift_int_type = cranelift_int_type(&context.target_triple); + compile_int_value(&value, cranelift_int_type, context) + } + + PatioOperand::Int32Const(value) => { + compile_int_value(&value, CRANELIFT_INT_32_TYPE, context) + } + + PatioOperand::LocalRef(local_ref) => { + let cranelift_variable = context.var(&local_ref.name); + context.function_builder.use_var(cranelift_variable) + } + + _ => { + todo!("Unhandled operand: {operand:?}",) + } + } +} + +fn compile_data_ptr_value( + data_ptr_value: &PatioDataPtrValue, + context: &mut CompilerFunctionContext<'_>, +) -> cranelift_codegen::ir::Value { + let data_ptr_type = context.pointer_type(); + + match data_ptr_value { + PatioDataPtrValue::DataElement { name } => { + let global_value = context.data_ptr(name); + context + .function_builder + .ins() + .global_value(data_ptr_type, global_value) + } + + PatioDataPtrValue::Null => context.function_builder.ins().iconst(data_ptr_type, 0), + } +} + +fn compile_int_value( + int_value: &PatioIntValue, + cranelift_type: cranelift::prelude::Type, + context: &mut CompilerFunctionContext<'_>, +) -> cranelift::prelude::Value { + let value = eval_int_value(int_value, &context.target_triple); + + context + .function_builder + .ins() + .iconst(cranelift_type, i64::from(value)) +} + +fn cranelift_block_args( + values: impl IntoIterator<Item = cranelift_codegen::ir::Value>, +) -> Vec<cranelift_codegen::ir::BlockArg> { + values + .into_iter() + .map(|cranelift_result| cranelift_codegen::ir::BlockArg::Value(cranelift_result)) + .collect() +} + +fn def_destination( + destination: &PatioLocalRef, + value: cranelift_codegen::ir::Value, + context: &mut CompilerFunctionContext<'_>, +) { + let cranelift_destination = context.var(&destination.name); + context + .function_builder + .def_var(cranelift_destination, value); +} + +fn cranelift_signature<'a>( + locals: impl Iterator<Item = &'a PatioLocal>, + return_type: PatioType, + target_triple: &target_lexicon::Triple, +) -> cranelift::prelude::Signature { + let mut signature = + cranelift::prelude::Signature::new(cranelift_codegen::isa::CallConv::SystemV); + + for local in locals { + if local.local_type == PatioLocalType::Param { + let param_type = patio_type_to_cranelift_type(local.type_, &target_triple); + let param = cranelift::prelude::AbiParam::new(param_type); + signature.params.push(param); + } + } + + if return_type != PatioType::Void { + let cranelift_return_type = patio_type_to_cranelift_type(return_type, &target_triple); + let returns = cranelift::prelude::AbiParam::new(cranelift_return_type); + signature.returns.push(returns); + } + + signature +} + +fn patio_visibility_to_cranelift_linkage(visibility: PatioVisibility) -> cranelift_module::Linkage { + match visibility { + PatioVisibility::Private => cranelift_module::Linkage::Local, + PatioVisibility::Public => cranelift_module::Linkage::Export, + } +} + +fn patio_type_to_cranelift_type( + patio_type: PatioType, + target_triple: &target_lexicon::Triple, +) -> cranelift::prelude::Type { + match patio_type { + PatioType::Bool => CRANELIFT_BOOL_TYPE, + + PatioType::Byte => CRANELIFT_BYTE_TYPE, + + PatioType::DataPtr => cranelift::prelude::Type::triple_pointer_type(&target_triple), + + PatioType::FunctionPtr => cranelift::prelude::Type::triple_pointer_type(&target_triple), + + PatioType::Int => cranelift_int_type(target_triple), + + PatioType::Int32 => CRANELIFT_INT_32_TYPE, + + PatioType::Void => todo!(), + } +} + +const CRANELIFT_BOOL_TYPE: cranelift_codegen::ir::Type = cranelift_codegen::ir::types::I8; +const CRANELIFT_BYTE_TYPE: cranelift_codegen::ir::Type = cranelift_codegen::ir::types::I8; + +fn cranelift_int_type(target_triple: &target_lexicon::Triple) -> cranelift_codegen::ir::Type { + // TODO: this assumes that pointers are at least as wide as i32s: + // probably safe, but we should check. + // TODO: this relies on cranelift pointer types just being integers. + cranelift::prelude::Type::triple_pointer_type(&target_triple) +} + +const CRANELIFT_INT_32_TYPE: cranelift_codegen::ir::Type = cranelift_codegen::ir::types::I32; + +fn data_layout_byte_size(layout: &PatioDataLayout, target_triple: &target_lexicon::Triple) -> u32 { + match layout { + PatioDataLayout::Array { element, size } => { + data_layout_byte_size(element, target_triple) * size + } + + PatioDataLayout::Bool => CRANELIFT_BOOL_TYPE.bytes(), + + PatioDataLayout::Byte => CRANELIFT_BYTE_TYPE.bytes(), + + PatioDataLayout::DataPtr => { + cranelift::prelude::Type::triple_pointer_type(&target_triple).bytes() + } + + PatioDataLayout::FunctionPtr => { + cranelift::prelude::Type::triple_pointer_type(&target_triple).bytes() + } + + PatioDataLayout::Int => cranelift_int_type(target_triple).bytes(), + + PatioDataLayout::Int32 => CRANELIFT_INT_32_TYPE.bytes(), + + PatioDataLayout::Struct { elements } => elements + .iter() + .map(|element| data_layout_byte_size(element, target_triple)) + .sum(), + } +} + +#[cfg(test)] +mod data_layout_byte_size_test { + use std::str::FromStr; + + use crate::patio::types::PatioDataLayout; + + fn data_layout_byte_size(layout: &PatioDataLayout) -> u32 { + super::data_layout_byte_size( + layout, + &target_lexicon::Triple::from_str("x86_64-unknown-linux-gnu").unwrap(), + ) + } + + #[test] + fn bool_is_one_byte() { + assert_eq!(data_layout_byte_size(&PatioDataLayout::Bool), 1); + } + + #[test] + fn byte_is_one_byte() { + assert_eq!(data_layout_byte_size(&PatioDataLayout::Byte), 1); + } + + #[test] + fn data_ptr_is_width_of_pointer() { + assert_eq!(data_layout_byte_size(&PatioDataLayout::DataPtr), 8); + } + + #[test] + fn function_ptr_is_width_of_pointer() { + assert_eq!(data_layout_byte_size(&PatioDataLayout::FunctionPtr), 8); + } + + #[test] + fn int_is_width_of_pointer_and_at_least_32_bits() { + assert_eq!(data_layout_byte_size(&PatioDataLayout::Int), 8); + } + + #[test] + fn int32_is_four_bytes() { + assert_eq!(data_layout_byte_size(&PatioDataLayout::Int32), 4); + } + + #[test] + fn array_size_is_size_of_each_element_multiplied_by_number_of_elements() { + assert_eq!( + data_layout_byte_size(&PatioDataLayout::array(PatioDataLayout::Int32, 7)), + 28 + ); + } + + #[test] + fn struct_size_is_sum_of_element_sizes() { + assert_eq!( + data_layout_byte_size(&PatioDataLayout::struct_(vec![ + PatioDataLayout::Int32, + PatioDataLayout::Byte + ])), + 5 + ); + } +} + +fn data_layout_byte_offset( + layout: &PatioDataLayout, + element_offsets: &[u32], + target_triple: &target_lexicon::Triple, +) -> u32 { + let mut byte_offset = 0; + let mut current_element = layout; + + for element_offset in element_offsets { + match current_element { + PatioDataLayout::Array { element, size: _ } => { + byte_offset += data_layout_byte_size(element, target_triple) * element_offset; + current_element = element; + } + + PatioDataLayout::Bool => panic!("offset not allowed for bool"), + PatioDataLayout::Byte => panic!("offset not allowed for byte"), + PatioDataLayout::DataPtr => panic!("offset not allowed for data ptr"), + PatioDataLayout::FunctionPtr => panic!("offset not allowed for function ptr"), + PatioDataLayout::Int => panic!("offset not allowed for int"), + PatioDataLayout::Int32 => panic!("offset not allowed for int32"), + + PatioDataLayout::Struct { elements } => { + for element in elements.iter().take((*element_offset).try_into().unwrap()) { + byte_offset += data_layout_byte_size(element, target_triple); + } + + current_element = &elements[usize::try_from(*element_offset).unwrap()]; + } + } + } + + byte_offset +} + +#[cfg(test)] +mod data_layout_byte_offset_test { + use std::str::FromStr; + + use crate::patio::types::PatioDataLayout; + + fn data_layout_byte_offset(layout: &PatioDataLayout, element_offsets: Vec<u32>) -> u32 { + super::data_layout_byte_offset( + layout, + &element_offsets, + &target_lexicon::Triple::from_str("x86_64-unknown-linux-gnu").unwrap(), + ) + } + + #[test] + fn byte_offset_of_first_element_of_array_is_zero() { + let layout = PatioDataLayout::array(PatioDataLayout::Int32, 2); + + let byte_offset = data_layout_byte_offset(&layout, vec![0]); + + assert_eq!(byte_offset, 0); + } + + #[test] + fn byte_offset_of_element_of_array_is_size_of_skipped_elements() { + let layout = PatioDataLayout::array(PatioDataLayout::Int32, 5); + + let byte_offset = data_layout_byte_offset(&layout, vec![2]); + + assert_eq!(byte_offset, 8); + } + + #[test] + fn byte_offset_of_element_of_element_of_array_is_size_of_skipped_elements() { + let layout = PatioDataLayout::array(PatioDataLayout::array(PatioDataLayout::Byte, 7), 5); + + let byte_offset = data_layout_byte_offset(&layout, vec![2, 3]); + + assert_eq!(byte_offset, 17); + } + + #[test] + fn byte_offset_of_first_element_of_struct_is_zero() { + let layout = PatioDataLayout::struct_(vec![ + PatioDataLayout::Int32, + PatioDataLayout::Byte, + PatioDataLayout::DataPtr, + ]); + + let byte_offset = data_layout_byte_offset(&layout, vec![0]); + + assert_eq!(byte_offset, 0); + } + + #[test] + fn byte_offset_of_element_of_struct_is_size_of_skipped_elements() { + let layout = PatioDataLayout::struct_(vec![ + PatioDataLayout::Int32, + PatioDataLayout::Byte, + PatioDataLayout::DataPtr, + ]); + + let byte_offset = data_layout_byte_offset(&layout, vec![2]); + + assert_eq!(byte_offset, 5); + } + + #[test] + fn byte_offset_of_element_of_element_of_struct_is_size_of_skipped_elements() { + let layout = PatioDataLayout::struct_(vec![ + PatioDataLayout::Int32, + PatioDataLayout::struct_(vec![ + PatioDataLayout::Byte, + PatioDataLayout::DataPtr, + PatioDataLayout::Byte, + ]), + PatioDataLayout::Int32, + ]); + + let byte_offset = data_layout_byte_offset(&layout, vec![1, 2]); + + assert_eq!(byte_offset, 13); + } +} + +fn data_layout_element_at<'a>( + layout: &'a PatioDataLayout, + element_offsets: &[u32], +) -> &'a PatioDataLayout { + let mut current_element = layout; + + for element_offset in element_offsets { + match current_element { + PatioDataLayout::Array { element, size: _ } => { + current_element = element; + } + + PatioDataLayout::Bool => panic!("offset not allowed for bool"), + PatioDataLayout::Byte => panic!("offset not allowed for byte"), + PatioDataLayout::DataPtr => panic!("offset not allowed for data ptr"), + PatioDataLayout::FunctionPtr => panic!("offset not allowed for function ptr"), + PatioDataLayout::Int => panic!("offset not allowed for int"), + PatioDataLayout::Int32 => panic!("offset not allowed for int32"), + + PatioDataLayout::Struct { elements } => { + current_element = &elements[usize::try_from(*element_offset).unwrap()]; + } + } + } + + current_element +} + +#[cfg(test)] +mod data_layout_element_at_test { + use crate::patio::types::PatioDataLayout; + + fn data_layout_element_at( + layout: &PatioDataLayout, + element_offsets: Vec<u32>, + ) -> &PatioDataLayout { + super::data_layout_element_at(layout, &element_offsets) + } + + #[test] + fn element_of_array_is_element_layout() { + let layout = PatioDataLayout::array(PatioDataLayout::Int32, 2); + + let element = data_layout_element_at(&layout, vec![0]); + + assert_eq!(element, &PatioDataLayout::Int32); + } + + #[test] + fn element_of_element_of_array() { + let layout = PatioDataLayout::array(PatioDataLayout::array(PatioDataLayout::Byte, 7), 5); + + let element = data_layout_element_at(&layout, vec![2, 3]); + + assert_eq!(element, &PatioDataLayout::Byte); + } + + #[test] + fn first_element_of_struct() { + let layout = PatioDataLayout::struct_(vec![ + PatioDataLayout::Int32, + PatioDataLayout::Byte, + PatioDataLayout::DataPtr, + ]); + + let element = data_layout_element_at(&layout, vec![0]); + + assert_eq!(element, &PatioDataLayout::Int32); + } + + #[test] + fn non_first_element_of_struct() { + let layout = PatioDataLayout::struct_(vec![ + PatioDataLayout::Int32, + PatioDataLayout::Byte, + PatioDataLayout::DataPtr, + ]); + + let element = data_layout_element_at(&layout, vec![2]); + + assert_eq!(element, &PatioDataLayout::DataPtr); + } + + #[test] + fn element_of_element_of_struct() { + let layout = PatioDataLayout::struct_(vec![ + PatioDataLayout::Int32, + PatioDataLayout::struct_(vec![ + PatioDataLayout::Byte, + PatioDataLayout::DataPtr, + PatioDataLayout::Byte, + ]), + PatioDataLayout::Int32, + ]); + + let element = data_layout_element_at(&layout, vec![1, 2]); + + assert_eq!(element, &PatioDataLayout::Byte); + } +} + +fn data_layout_to_type(layout: &PatioDataLayout) -> PatioType { + match layout { + PatioDataLayout::Array { .. } => panic!("array cannot be represented as type"), + PatioDataLayout::Bool => PatioType::Bool, + PatioDataLayout::Byte => PatioType::Byte, + PatioDataLayout::DataPtr => PatioType::DataPtr, + PatioDataLayout::FunctionPtr => PatioType::FunctionPtr, + PatioDataLayout::Int => PatioType::Int, + PatioDataLayout::Int32 => PatioType::Int32, + PatioDataLayout::Struct { .. } => panic!("struct cannot be represented as type"), + } +} + +fn data_value_to_bytes( + value: &PatioDataValue, + target_triple: &target_lexicon::Triple, +) -> Box<[u8]> { + let mut bytes = Vec::with_capacity( + data_layout_byte_size(&value.layout(), target_triple) + .try_into() + .unwrap(), + ); + + write_data_value(value, &mut bytes, target_triple); + + bytes.into_boxed_slice() +} + +fn write_data_value( + value: &PatioDataValue, + bytes: &mut Vec<u8>, + target_triple: &target_lexicon::Triple, +) { + match value { + PatioDataValue::Array { + element_layout: _, + elements: _, + } => todo!(), + PatioDataValue::Bool(_) => todo!(), + PatioDataValue::Byte(_) => todo!(), + PatioDataValue::DataPtr(_) => todo!(), + PatioDataValue::Int(int_value) => { + // TODO: handle 32-bit int + let value: i64 = eval_int_value(int_value, target_triple).into(); + bytes.write_all(&value.to_le_bytes()).unwrap(); + } + PatioDataValue::Int32(_) => todo!(), + PatioDataValue::Struct { elements } => { + for element in elements { + write_data_value(element, bytes, target_triple); + } + } + } +} + +fn eval_int_value(value: &PatioIntValue, target_triple: &target_lexicon::Triple) -> i32 { + match value { + PatioIntValue::ByteOffsetOf { + layout, + element_offsets, + } => data_layout_byte_offset(layout, element_offsets, target_triple) + .try_into() + .unwrap(), + + PatioIntValue::ByteSizeOf { layout } => data_layout_byte_size(layout, target_triple) + .try_into() + .unwrap(), + + PatioIntValue::Literal { value } => *value, + } +} diff --git a/patio-to-cranelift/src/lib.rs b/patio-to-cranelift/src/lib.rs index a93e4a32..22afabba 100644 --- a/patio-to-cranelift/src/lib.rs +++ b/patio-to-cranelift/src/lib.rs @@ -1,27 +1,6 @@ -use std::{io::Write, path::Path, str::FromStr}; +use std::{path::Path, str::FromStr}; -use cranelift::prelude::InstBuilder; - -use crate::{ - context::{CompilerContext, CompilerFunctionContext, Mutability}, - errors::CompileError, - patio::{ - compilation_unit::{ - PatioCompilationUnit, PatioCompilationUnitElement, PatioDataElementImmutableDefinition, - PatioDataElementMutableDefinition, PatioFunctionDeclaration, PatioFunctionDefinition, - PatioLocal, PatioLocalType, PatioVisibility, - }, - instructions::{ - PatioBlock, PatioBoolToInt, PatioCall, PatioIf, PatioInstruction, PatioIntAdd, - PatioIntEquals, PatioIntNotEqual, PatioIntSub, PatioIntToBool, PatioLocalStore, - PatioMemoryLoad, PatioMemoryStore, PatioResume, PatioReturn, PatioReturnVoid, - PatioVarLoad, PatioVarStore, - }, - operands::{PatioLocalRef, PatioOperand}, - types::{PatioDataLayout, PatioType}, - values::{PatioDataPtrValue, PatioDataValue, PatioIntValue}, - }, -}; +use crate::{errors::CompileError, patio::compilation_unit::PatioCompilationUnit}; #[allow(dead_code, unused_imports)] #[path = "./gen/patio_generated.rs"] @@ -31,7 +10,7 @@ mod patio_generated; #[path = "./gen/sources_generated.rs"] mod sources_generated; -mod context; +mod codegen; mod errors; mod patio; @@ -46,1056 +25,11 @@ pub fn compile(source_path: &Path, target_str: &str) -> Result<(), CompileError> let target_triple = target_lexicon::Triple::from_str(target_str).map_err(CompileError::ParseTargetTriple)?; - let mut context = CompilerContext::for_object(source_path, target_triple)?; - - for element in &compilation_unit.elements { - declare_compilation_unit_element(&element, &mut context); - } - - for element in &compilation_unit.elements { - define_compilation_unit_element(&element, &mut context); - } - - let object = context.into_object(); - let object_bytes = object.emit().map_err(CompileError::ObjectEmitFailed)?; + let object_bytes = + codegen::compile_compilation_unit(compilation_unit, source_path, target_triple)?; let destination_path = source_path.with_extension("o"); std::fs::write(destination_path, object_bytes).map_err(CompileError::WriteObjectFile)?; Ok(()) } - -fn declare_compilation_unit_element( - element: &PatioCompilationUnitElement, - context: &mut CompilerContext, -) { - match element { - PatioCompilationUnitElement::DataElementImmutableDefinition( - data_element_immutable_definition, - ) => { - declare_data_element_immutable_definition(data_element_immutable_definition, context); - } - - PatioCompilationUnitElement::DataElementMutableDefinition( - data_element_mutable_definition, - ) => { - declare_data_element_mutable_definition(data_element_mutable_definition, context); - } - - PatioCompilationUnitElement::FunctionDeclaration(function_declaration) => { - declare_function_declaration(function_declaration, context); - } - - PatioCompilationUnitElement::FunctionDefinition(function_definition) => { - declare_function_definition(function_definition, context); - } - } -} - -fn define_compilation_unit_element( - element: &PatioCompilationUnitElement, - context: &mut CompilerContext, -) { - match element { - PatioCompilationUnitElement::DataElementImmutableDefinition( - data_element_immutable_definition, - ) => { - define_data_element_immutable_definition(data_element_immutable_definition, context); - } - - PatioCompilationUnitElement::DataElementMutableDefinition( - data_element_mutable_definition, - ) => { - define_data_element_mutable_definition(data_element_mutable_definition, context); - } - - PatioCompilationUnitElement::FunctionDeclaration(_) => {} - - PatioCompilationUnitElement::FunctionDefinition(function_definition) => { - define_function_definition(&function_definition, context); - } - } -} - -fn declare_data_element_immutable_definition( - data_element_immutable_definition: &PatioDataElementImmutableDefinition, - context: &mut CompilerContext, -) { - context.declare_data( - &data_element_immutable_definition.name, - patio_visibility_to_cranelift_linkage(data_element_immutable_definition.visibility), - Mutability::Immutable, - ); -} - -fn define_data_element_immutable_definition( - data_element_immutable_definition: &PatioDataElementImmutableDefinition, - context: &mut CompilerContext, -) { - let bytes = data_value_to_bytes( - &data_element_immutable_definition.value, - &context.target_triple, - ); - context.define_data(&data_element_immutable_definition.name, bytes); -} - -fn declare_data_element_mutable_definition( - data_element_mutable_definition: &PatioDataElementMutableDefinition, - context: &mut CompilerContext, -) { - context.declare_data( - &data_element_mutable_definition.name, - patio_visibility_to_cranelift_linkage(data_element_mutable_definition.visibility), - Mutability::Mutable, - ); -} - -fn define_data_element_mutable_definition( - data_element_mutable_definition: &PatioDataElementMutableDefinition, - context: &mut CompilerContext, -) { - let bytes = data_value_to_bytes( - &data_element_mutable_definition.initial_value, - &context.target_triple, - ); - context.define_data(&data_element_mutable_definition.name, bytes); -} - -fn declare_function_declaration( - function_declaration: &PatioFunctionDeclaration, - context: &mut CompilerContext, -) { - let cranelift_signature = cranelift_signature( - function_declaration.params.iter(), - function_declaration.return_type, - &context.target_triple, - ); - - context.declare_function( - &function_declaration.name, - cranelift_module::Linkage::Import, - &cranelift_signature, - ); -} - -fn declare_function_definition( - function_definition: &PatioFunctionDefinition, - context: &mut CompilerContext, -) { - let cranelift_signature = cranelift_signature( - function_definition.locals.iter().map(Into::into), - function_definition.return_type, - &context.target_triple, - ); - - let cranelift_linkage = patio_visibility_to_cranelift_linkage(function_definition.visibility); - - context.declare_function( - &function_definition.name, - cranelift_linkage, - &cranelift_signature, - ); -} - -fn define_function_definition( - function_definition: &PatioFunctionDefinition, - context: &mut CompilerContext, -) { - let cranelift_signature = cranelift_signature( - function_definition.locals.iter().map(Into::into), - function_definition.return_type, - &context.target_triple, - ); - - context.enter_function( - &function_definition.name, - cranelift_signature, - |function_context| { - for local in &function_definition.locals { - match local.local_type { - PatioLocalType::Param => {} - - PatioLocalType::Val => { - let cranelift_type = patio_type_to_cranelift_type( - local.type_, - &function_context.target_triple, - ); - function_context.declare_var(&local.name, cranelift_type); - } - - PatioLocalType::Var => { - let cranelift_type = patio_type_to_cranelift_type( - local.type_, - &function_context.target_triple, - ); - function_context.create_stack_slot(&local.name, cranelift_type); - } - } - } - - for instruction in &function_definition.body { - compile_instruction(instruction, function_context) - } - }, - ); -} - -fn compile_instruction(instruction: &PatioInstruction, context: &mut CompilerFunctionContext<'_>) { - match instruction { - PatioInstruction::Block(block) => { - compile_block(block, context); - } - - PatioInstruction::BoolToInt(bool_to_int) => { - compile_bool_to_int(bool_to_int, context); - } - - PatioInstruction::Call(call) => { - compile_call(call, context); - } - - PatioInstruction::If(if_) => { - compile_if(if_, context); - } - - PatioInstruction::IntAdd(int_add) => { - compile_int_add(int_add, context); - } - - PatioInstruction::IntEquals(int_equals) => { - compile_int_equals(int_equals, context); - } - - PatioInstruction::IntNotEqual(int_not_equal) => { - compile_int_not_equal(int_not_equal, context); - } - - PatioInstruction::IntSub(int_sub) => { - compile_int_sub(int_sub, context); - } - - PatioInstruction::IntToBool(int_to_bool) => { - compile_int_to_bool(int_to_bool, context); - } - - PatioInstruction::LocalStore(local_store) => { - compile_local_store(local_store, context); - } - - PatioInstruction::MemoryLoad(memory_load) => { - compile_memory_load(memory_load, context); - } - - PatioInstruction::MemoryStore(memory_store) => { - compile_memory_store(memory_store, context); - } - - PatioInstruction::Resume(resume) => { - compile_resume(resume, context); - } - - PatioInstruction::Return(return_) => { - compile_return(return_, context); - } - - PatioInstruction::ReturnVoid(return_void) => { - compile_return_void(return_void, context); - } - - PatioInstruction::VarLoad(var_load) => { - compile_var_load(var_load, context); - } - - PatioInstruction::VarStore(var_store) => { - compile_var_store(var_store, context); - } - } -} - -fn compile_block( - block: &PatioBlock, - context: &mut CompilerFunctionContext<'_>, -) -> Vec<cranelift_codegen::ir::Value> { - for instruction in &block.body { - compile_instruction(instruction, context); - } - - block - .results - .iter() - .map(|result| compile_operand(&result, context)) - .collect() -} - -fn compile_bool_to_int(bool_to_int: &PatioBoolToInt, context: &mut CompilerFunctionContext<'_>) { - let cranelift_value = compile_operand(&bool_to_int.value, context); - - let cranelift_result = context - .function_builder - .ins() - .uextend(cranelift_int_type(&context.target_triple), cranelift_value); - - def_destination(&bool_to_int.destination, cranelift_result, context) -} - -fn compile_call(call: &PatioCall, context: &mut CompilerFunctionContext<'_>) { - let cranelift_args = compile_operands(call.args.iter(), context); - - let func_ref = context.func_id(&call.function_name); - context - .function_builder - .ins() - .call(func_ref, &cranelift_args); -} - -fn compile_if(if_: &PatioIf, context: &mut CompilerFunctionContext<'_>) { - let cranelift_condition = compile_operand(&if_.condition, context); - let cranelift_then_block = context.function_builder.create_block(); - let cranelift_else_block = context.function_builder.create_block(); - let cranelift_end_block = context.function_builder.create_block(); - let cranelift_end_params: Vec<_> = if_ - .destinations - .iter() - .map(|destination| { - let cranelift_type = context.var_type(&destination.name); - context - .function_builder - .append_block_param(cranelift_end_block, cranelift_type) - }) - .collect(); - - context.function_builder.ins().brif( - cranelift_condition, - cranelift_then_block, - &[], - cranelift_else_block, - &[], - ); - context - .function_builder - .seal_block(context.function_builder.current_block().unwrap()); - - context - .function_builder - .switch_to_block(cranelift_then_block); - let cranelift_then_results = compile_block(&if_.true_body, context); - context.function_builder.ins().jump( - cranelift_end_block, - cranelift_block_args(cranelift_then_results).iter(), - ); - context.function_builder.seal_block(cranelift_then_block); - - context - .function_builder - .switch_to_block(cranelift_else_block); - let cranelift_else_results = compile_block(&if_.false_body, context); - context.function_builder.ins().jump( - cranelift_end_block, - cranelift_block_args(cranelift_else_results).iter(), - ); - context.function_builder.seal_block(cranelift_else_block); - - context - .function_builder - .switch_to_block(cranelift_end_block); - - for (destination, cranelift_end_param) in if_.destinations.iter().zip(cranelift_end_params) { - def_destination(destination, cranelift_end_param, context); - } -} - -fn compile_int_add(int_add: &PatioIntAdd, context: &mut CompilerFunctionContext<'_>) { - let cranelift_left = compile_operand(&int_add.left, context); - let cranelift_right = compile_operand(&int_add.right, context); - let cranelift_result = context - .function_builder - .ins() - .iadd(cranelift_left, cranelift_right); - - def_destination(&int_add.destination, cranelift_result, context); -} - -fn compile_int_equals(int_equals: &PatioIntEquals, context: &mut CompilerFunctionContext<'_>) { - let cranelift_left = compile_operand(&int_equals.left, context); - let cranelift_right = compile_operand(&int_equals.right, context); - let cranelift_result = context.function_builder.ins().icmp( - cranelift_codegen::ir::condcodes::IntCC::Equal, - cranelift_left, - cranelift_right, - ); - - def_destination(&int_equals.destination, cranelift_result, context); -} - -fn compile_int_not_equal( - int_not_equal: &PatioIntNotEqual, - context: &mut CompilerFunctionContext<'_>, -) { - let cranelift_left = compile_operand(&int_not_equal.left, context); - let cranelift_right = compile_operand(&int_not_equal.right, context); - let cranelift_result = context.function_builder.ins().icmp( - cranelift_codegen::ir::condcodes::IntCC::NotEqual, - cranelift_left, - cranelift_right, - ); - - def_destination(&int_not_equal.destination, cranelift_result, context); -} - -fn compile_int_sub(int_sub: &PatioIntSub, context: &mut CompilerFunctionContext<'_>) { - let cranelift_left = compile_operand(&int_sub.left, context); - let cranelift_right = compile_operand(&int_sub.right, context); - let cranelift_result = context - .function_builder - .ins() - .isub(cranelift_left, cranelift_right); - - def_destination(&int_sub.destination, cranelift_result, context); -} - -fn compile_int_to_bool(int_to_bool: &PatioIntToBool, context: &mut CompilerFunctionContext<'_>) { - let cranelift_value = compile_operand(&int_to_bool.value, context); - - let cranelift_result = context - .function_builder - .ins() - .ireduce(CRANELIFT_BOOL_TYPE, cranelift_value); - - def_destination(&int_to_bool.destination, cranelift_result, context); -} - -fn compile_local_store(local_store: &PatioLocalStore, context: &mut CompilerFunctionContext<'_>) { - let cranelift_value = compile_operand(&local_store.value, context); - def_destination(&local_store.destination, cranelift_value, context); -} - -fn compile_memory_load(memory_load: &PatioMemoryLoad, context: &mut CompilerFunctionContext<'_>) { - let value_layout = data_layout_element_at(&memory_load.layout, &memory_load.element_offsets); - let value_type = data_layout_to_type(value_layout); - let cranelift_type = patio_type_to_cranelift_type(value_type, &context.target_triple); - - let cranelift_pointer = compile_operand(&memory_load.pointer, context); - let cranelift_flags = cranelift_codegen::ir::MemFlags::new(); - let byte_offset = data_layout_byte_offset( - &memory_load.layout, - &memory_load.element_offsets, - &context.target_triple, - ); - let cranelift_offset = - cranelift_codegen::ir::immediates::Offset32::new(byte_offset.try_into().unwrap()); - let cranelift_value = context.function_builder.ins().load( - cranelift_type, - cranelift_flags, - cranelift_pointer, - cranelift_offset, - ); - - def_destination(&memory_load.destination, cranelift_value, context); -} - -fn compile_memory_store( - memory_store: &PatioMemoryStore, - context: &mut CompilerFunctionContext<'_>, -) { - let cranelift_value = compile_operand(&memory_store.value, context); - let cranelift_pointer = compile_operand(&memory_store.pointer, context); - let cranelift_flags = cranelift_codegen::ir::MemFlags::new(); - let byte_offset = data_layout_byte_offset( - &memory_store.layout, - &memory_store.element_offsets, - &context.target_triple, - ); - let cranelift_offset = - cranelift_codegen::ir::immediates::Offset32::new(byte_offset.try_into().unwrap()); - - context.function_builder.ins().store( - cranelift_flags, - cranelift_value, - cranelift_pointer, - cranelift_offset, - ); -} - -fn compile_resume(resume: &PatioResume, context: &mut CompilerFunctionContext<'_>) { - let cranelift_value = compile_operand(&resume.value, context); - - context.function_builder.ins().return_(&[cranelift_value]); -} - -fn compile_return(return_: &PatioReturn, context: &mut CompilerFunctionContext<'_>) { - let cranelift_value = compile_operand(&return_.value, context); - - context.function_builder.ins().return_(&[cranelift_value]); -} - -fn compile_return_void(_return_void: &PatioReturnVoid, context: &mut CompilerFunctionContext<'_>) { - context.function_builder.ins().return_(&[]); -} - -fn compile_var_load(var_load: &PatioVarLoad, context: &mut CompilerFunctionContext<'_>) { - let stack_slot = context.stack_slot(&var_load.var.name); - let type_ = context.stack_slot_type(&var_load.var.name); - let value = context.function_builder.ins().stack_load( - type_, - stack_slot, - cranelift_codegen::ir::immediates::Offset32::new(0), - ); - def_destination(&var_load.destination, value, context); -} - -fn compile_var_store(var_store: &PatioVarStore, context: &mut CompilerFunctionContext<'_>) { - let stack_slot = context.stack_slot(&var_store.var.name); - let value = compile_operand(&var_store.value, context); - context.function_builder.ins().stack_store( - value, - stack_slot, - cranelift_codegen::ir::immediates::Offset32::new(0), - ); -} - -fn compile_operands<'a>( - operands: impl Iterator<Item = &'a PatioOperand>, - context: &mut CompilerFunctionContext<'_>, -) -> Vec<cranelift::prelude::Value> { - operands - .map(|operand| compile_operand(operand, context)) - .collect() -} - -fn compile_operand( - operand: &PatioOperand, - context: &mut CompilerFunctionContext<'_>, -) -> cranelift::prelude::Value { - match operand { - PatioOperand::BoolConst(value) => { - let integer_value = if *value { 1 } else { 0 }; - - context - .function_builder - .ins() - .iconst(CRANELIFT_BOOL_TYPE, integer_value) - } - - PatioOperand::DataPtrConst(value) => compile_data_ptr_value(&value, context), - - PatioOperand::IntConst(value) => { - let cranelift_int_type = cranelift_int_type(&context.target_triple); - compile_int_value(&value, cranelift_int_type, context) - } - - PatioOperand::Int32Const(value) => { - compile_int_value(&value, CRANELIFT_INT_32_TYPE, context) - } - - PatioOperand::LocalRef(local_ref) => { - let cranelift_variable = context.var(&local_ref.name); - context.function_builder.use_var(cranelift_variable) - } - - _ => { - todo!("Unhandled operand: {operand:?}",) - } - } -} - -fn compile_data_ptr_value( - data_ptr_value: &PatioDataPtrValue, - context: &mut CompilerFunctionContext<'_>, -) -> cranelift_codegen::ir::Value { - let data_ptr_type = context.pointer_type(); - - match data_ptr_value { - PatioDataPtrValue::DataElement { name } => { - let global_value = context.data_ptr(name); - context - .function_builder - .ins() - .global_value(data_ptr_type, global_value) - } - - PatioDataPtrValue::Null => context.function_builder.ins().iconst(data_ptr_type, 0), - } -} - -fn compile_int_value( - int_value: &PatioIntValue, - cranelift_type: cranelift::prelude::Type, - context: &mut CompilerFunctionContext<'_>, -) -> cranelift::prelude::Value { - let value = eval_int_value(int_value, &context.target_triple); - - context - .function_builder - .ins() - .iconst(cranelift_type, i64::from(value)) -} - -fn cranelift_block_args( - values: impl IntoIterator<Item = cranelift_codegen::ir::Value>, -) -> Vec<cranelift_codegen::ir::BlockArg> { - values - .into_iter() - .map(|cranelift_result| cranelift_codegen::ir::BlockArg::Value(cranelift_result)) - .collect() -} - -fn def_destination( - destination: &PatioLocalRef, - value: cranelift_codegen::ir::Value, - context: &mut CompilerFunctionContext<'_>, -) { - let cranelift_destination = context.var(&destination.name); - context - .function_builder - .def_var(cranelift_destination, value); -} - -fn cranelift_signature<'a>( - locals: impl Iterator<Item = &'a PatioLocal>, - return_type: PatioType, - target_triple: &target_lexicon::Triple, -) -> cranelift::prelude::Signature { - let mut signature = - cranelift::prelude::Signature::new(cranelift_codegen::isa::CallConv::SystemV); - - for local in locals { - if local.local_type == PatioLocalType::Param { - let param_type = patio_type_to_cranelift_type(local.type_, &target_triple); - let param = cranelift::prelude::AbiParam::new(param_type); - signature.params.push(param); - } - } - - if return_type != PatioType::Void { - let cranelift_return_type = patio_type_to_cranelift_type(return_type, &target_triple); - let returns = cranelift::prelude::AbiParam::new(cranelift_return_type); - signature.returns.push(returns); - } - - signature -} - -fn patio_visibility_to_cranelift_linkage(visibility: PatioVisibility) -> cranelift_module::Linkage { - match visibility { - PatioVisibility::Private => cranelift_module::Linkage::Local, - PatioVisibility::Public => cranelift_module::Linkage::Export, - } -} - -fn patio_type_to_cranelift_type( - patio_type: PatioType, - target_triple: &target_lexicon::Triple, -) -> cranelift::prelude::Type { - match patio_type { - PatioType::Bool => CRANELIFT_BOOL_TYPE, - - PatioType::Byte => CRANELIFT_BYTE_TYPE, - - PatioType::DataPtr => cranelift::prelude::Type::triple_pointer_type(&target_triple), - - PatioType::FunctionPtr => cranelift::prelude::Type::triple_pointer_type(&target_triple), - - PatioType::Int => cranelift_int_type(target_triple), - - PatioType::Int32 => CRANELIFT_INT_32_TYPE, - - PatioType::Void => todo!(), - } -} - -const CRANELIFT_BOOL_TYPE: cranelift_codegen::ir::Type = cranelift_codegen::ir::types::I8; -const CRANELIFT_BYTE_TYPE: cranelift_codegen::ir::Type = cranelift_codegen::ir::types::I8; - -fn cranelift_int_type(target_triple: &target_lexicon::Triple) -> cranelift_codegen::ir::Type { - // TODO: this assumes that pointers are at least as wide as i32s: - // probably safe, but we should check. - // TODO: this relies on cranelift pointer types just being integers. - cranelift::prelude::Type::triple_pointer_type(&target_triple) -} - -const CRANELIFT_INT_32_TYPE: cranelift_codegen::ir::Type = cranelift_codegen::ir::types::I32; - -fn data_layout_byte_size(layout: &PatioDataLayout, target_triple: &target_lexicon::Triple) -> u32 { - match layout { - PatioDataLayout::Array { element, size } => { - data_layout_byte_size(element, target_triple) * size - } - - PatioDataLayout::Bool => CRANELIFT_BOOL_TYPE.bytes(), - - PatioDataLayout::Byte => CRANELIFT_BYTE_TYPE.bytes(), - - PatioDataLayout::DataPtr => { - cranelift::prelude::Type::triple_pointer_type(&target_triple).bytes() - } - - PatioDataLayout::FunctionPtr => { - cranelift::prelude::Type::triple_pointer_type(&target_triple).bytes() - } - - PatioDataLayout::Int => cranelift_int_type(target_triple).bytes(), - - PatioDataLayout::Int32 => CRANELIFT_INT_32_TYPE.bytes(), - - PatioDataLayout::Struct { elements } => elements - .iter() - .map(|element| data_layout_byte_size(element, target_triple)) - .sum(), - } -} - -#[cfg(test)] -mod data_layout_byte_size_test { - use std::str::FromStr; - - use crate::patio::types::PatioDataLayout; - - fn data_layout_byte_size(layout: &PatioDataLayout) -> u32 { - super::data_layout_byte_size( - layout, - &target_lexicon::Triple::from_str("x86_64-unknown-linux-gnu").unwrap(), - ) - } - - #[test] - fn bool_is_one_byte() { - assert_eq!(data_layout_byte_size(&PatioDataLayout::Bool), 1); - } - - #[test] - fn byte_is_one_byte() { - assert_eq!(data_layout_byte_size(&PatioDataLayout::Byte), 1); - } - - #[test] - fn data_ptr_is_width_of_pointer() { - assert_eq!(data_layout_byte_size(&PatioDataLayout::DataPtr), 8); - } - - #[test] - fn function_ptr_is_width_of_pointer() { - assert_eq!(data_layout_byte_size(&PatioDataLayout::FunctionPtr), 8); - } - - #[test] - fn int_is_width_of_pointer_and_at_least_32_bits() { - assert_eq!(data_layout_byte_size(&PatioDataLayout::Int), 8); - } - - #[test] - fn int32_is_four_bytes() { - assert_eq!(data_layout_byte_size(&PatioDataLayout::Int32), 4); - } - - #[test] - fn array_size_is_size_of_each_element_multiplied_by_number_of_elements() { - assert_eq!( - data_layout_byte_size(&PatioDataLayout::array(PatioDataLayout::Int32, 7)), - 28 - ); - } - - #[test] - fn struct_size_is_sum_of_element_sizes() { - assert_eq!( - data_layout_byte_size(&PatioDataLayout::struct_(vec![ - PatioDataLayout::Int32, - PatioDataLayout::Byte - ])), - 5 - ); - } -} - -fn data_layout_byte_offset( - layout: &PatioDataLayout, - element_offsets: &[u32], - target_triple: &target_lexicon::Triple, -) -> u32 { - let mut byte_offset = 0; - let mut current_element = layout; - - for element_offset in element_offsets { - match current_element { - PatioDataLayout::Array { element, size: _ } => { - byte_offset += data_layout_byte_size(element, target_triple) * element_offset; - current_element = element; - } - - PatioDataLayout::Bool => panic!("offset not allowed for bool"), - PatioDataLayout::Byte => panic!("offset not allowed for byte"), - PatioDataLayout::DataPtr => panic!("offset not allowed for data ptr"), - PatioDataLayout::FunctionPtr => panic!("offset not allowed for function ptr"), - PatioDataLayout::Int => panic!("offset not allowed for int"), - PatioDataLayout::Int32 => panic!("offset not allowed for int32"), - - PatioDataLayout::Struct { elements } => { - for element in elements.iter().take((*element_offset).try_into().unwrap()) { - byte_offset += data_layout_byte_size(element, target_triple); - } - - current_element = &elements[usize::try_from(*element_offset).unwrap()]; - } - } - } - - byte_offset -} - -#[cfg(test)] -mod data_layout_byte_offset_test { - use std::str::FromStr; - - use crate::patio::types::PatioDataLayout; - - fn data_layout_byte_offset(layout: &PatioDataLayout, element_offsets: Vec<u32>) -> u32 { - super::data_layout_byte_offset( - layout, - &element_offsets, - &target_lexicon::Triple::from_str("x86_64-unknown-linux-gnu").unwrap(), - ) - } - - #[test] - fn byte_offset_of_first_element_of_array_is_zero() { - let layout = PatioDataLayout::array(PatioDataLayout::Int32, 2); - - let byte_offset = data_layout_byte_offset(&layout, vec![0]); - - assert_eq!(byte_offset, 0); - } - - #[test] - fn byte_offset_of_element_of_array_is_size_of_skipped_elements() { - let layout = PatioDataLayout::array(PatioDataLayout::Int32, 5); - - let byte_offset = data_layout_byte_offset(&layout, vec![2]); - - assert_eq!(byte_offset, 8); - } - - #[test] - fn byte_offset_of_element_of_element_of_array_is_size_of_skipped_elements() { - let layout = PatioDataLayout::array(PatioDataLayout::array(PatioDataLayout::Byte, 7), 5); - - let byte_offset = data_layout_byte_offset(&layout, vec![2, 3]); - - assert_eq!(byte_offset, 17); - } - - #[test] - fn byte_offset_of_first_element_of_struct_is_zero() { - let layout = PatioDataLayout::struct_(vec![ - PatioDataLayout::Int32, - PatioDataLayout::Byte, - PatioDataLayout::DataPtr, - ]); - - let byte_offset = data_layout_byte_offset(&layout, vec![0]); - - assert_eq!(byte_offset, 0); - } - - #[test] - fn byte_offset_of_element_of_struct_is_size_of_skipped_elements() { - let layout = PatioDataLayout::struct_(vec![ - PatioDataLayout::Int32, - PatioDataLayout::Byte, - PatioDataLayout::DataPtr, - ]); - - let byte_offset = data_layout_byte_offset(&layout, vec![2]); - - assert_eq!(byte_offset, 5); - } - - #[test] - fn byte_offset_of_element_of_element_of_struct_is_size_of_skipped_elements() { - let layout = PatioDataLayout::struct_(vec![ - PatioDataLayout::Int32, - PatioDataLayout::struct_(vec![ - PatioDataLayout::Byte, - PatioDataLayout::DataPtr, - PatioDataLayout::Byte, - ]), - PatioDataLayout::Int32, - ]); - - let byte_offset = data_layout_byte_offset(&layout, vec![1, 2]); - - assert_eq!(byte_offset, 13); - } -} - -fn data_layout_element_at<'a>( - layout: &'a PatioDataLayout, - element_offsets: &[u32], -) -> &'a PatioDataLayout { - let mut current_element = layout; - - for element_offset in element_offsets { - match current_element { - PatioDataLayout::Array { element, size: _ } => { - current_element = element; - } - - PatioDataLayout::Bool => panic!("offset not allowed for bool"), - PatioDataLayout::Byte => panic!("offset not allowed for byte"), - PatioDataLayout::DataPtr => panic!("offset not allowed for data ptr"), - PatioDataLayout::FunctionPtr => panic!("offset not allowed for function ptr"), - PatioDataLayout::Int => panic!("offset not allowed for int"), - PatioDataLayout::Int32 => panic!("offset not allowed for int32"), - - PatioDataLayout::Struct { elements } => { - current_element = &elements[usize::try_from(*element_offset).unwrap()]; - } - } - } - - current_element -} - -#[cfg(test)] -mod data_layout_element_at_test { - use crate::patio::types::PatioDataLayout; - - fn data_layout_element_at( - layout: &PatioDataLayout, - element_offsets: Vec<u32>, - ) -> &PatioDataLayout { - super::data_layout_element_at(layout, &element_offsets) - } - - #[test] - fn element_of_array_is_element_layout() { - let layout = PatioDataLayout::array(PatioDataLayout::Int32, 2); - - let element = data_layout_element_at(&layout, vec![0]); - - assert_eq!(element, &PatioDataLayout::Int32); - } - - #[test] - fn element_of_element_of_array() { - let layout = PatioDataLayout::array(PatioDataLayout::array(PatioDataLayout::Byte, 7), 5); - - let element = data_layout_element_at(&layout, vec![2, 3]); - - assert_eq!(element, &PatioDataLayout::Byte); - } - - #[test] - fn first_element_of_struct() { - let layout = PatioDataLayout::struct_(vec![ - PatioDataLayout::Int32, - PatioDataLayout::Byte, - PatioDataLayout::DataPtr, - ]); - - let element = data_layout_element_at(&layout, vec![0]); - - assert_eq!(element, &PatioDataLayout::Int32); - } - - #[test] - fn non_first_element_of_struct() { - let layout = PatioDataLayout::struct_(vec![ - PatioDataLayout::Int32, - PatioDataLayout::Byte, - PatioDataLayout::DataPtr, - ]); - - let element = data_layout_element_at(&layout, vec![2]); - - assert_eq!(element, &PatioDataLayout::DataPtr); - } - - #[test] - fn element_of_element_of_struct() { - let layout = PatioDataLayout::struct_(vec![ - PatioDataLayout::Int32, - PatioDataLayout::struct_(vec![ - PatioDataLayout::Byte, - PatioDataLayout::DataPtr, - PatioDataLayout::Byte, - ]), - PatioDataLayout::Int32, - ]); - - let element = data_layout_element_at(&layout, vec![1, 2]); - - assert_eq!(element, &PatioDataLayout::Byte); - } -} - -fn data_layout_to_type(layout: &PatioDataLayout) -> PatioType { - match layout { - PatioDataLayout::Array { .. } => panic!("array cannot be represented as type"), - PatioDataLayout::Bool => PatioType::Bool, - PatioDataLayout::Byte => PatioType::Byte, - PatioDataLayout::DataPtr => PatioType::DataPtr, - PatioDataLayout::FunctionPtr => PatioType::FunctionPtr, - PatioDataLayout::Int => PatioType::Int, - PatioDataLayout::Int32 => PatioType::Int32, - PatioDataLayout::Struct { .. } => panic!("struct cannot be represented as type"), - } -} - -fn data_value_to_bytes( - value: &PatioDataValue, - target_triple: &target_lexicon::Triple, -) -> Box<[u8]> { - let mut bytes = Vec::with_capacity( - data_layout_byte_size(&value.layout(), target_triple) - .try_into() - .unwrap(), - ); - - write_data_value(value, &mut bytes, target_triple); - - bytes.into_boxed_slice() -} - -fn write_data_value( - value: &PatioDataValue, - bytes: &mut Vec<u8>, - target_triple: &target_lexicon::Triple, -) { - match value { - PatioDataValue::Array { - element_layout: _, - elements: _, - } => todo!(), - PatioDataValue::Bool(_) => todo!(), - PatioDataValue::Byte(_) => todo!(), - PatioDataValue::DataPtr(_) => todo!(), - PatioDataValue::Int(int_value) => { - // TODO: handle 32-bit int - let value: i64 = eval_int_value(int_value, target_triple).into(); - bytes.write_all(&value.to_le_bytes()).unwrap(); - } - PatioDataValue::Int32(_) => todo!(), - PatioDataValue::Struct { elements } => { - for element in elements { - write_data_value(element, bytes, target_triple); - } - } - } -} - -fn eval_int_value(value: &PatioIntValue, target_triple: &target_lexicon::Triple) -> i32 { - match value { - PatioIntValue::ByteOffsetOf { - layout, - element_offsets, - } => data_layout_byte_offset(layout, element_offsets, target_triple) - .try_into() - .unwrap(), - - PatioIntValue::ByteSizeOf { layout } => data_layout_byte_size(layout, target_triple) - .try_into() - .unwrap(), - - PatioIntValue::Literal { value } => *value, - } -} |
