diff --git a/src/codegen.rs b/src/codegen.rs index dd98c3b7ac..7fb8638118 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -1,6 +1,8 @@ // Copyright (c) 2020 Ghaith Hachem and Mathias Rieder use std::{ cell::RefCell, + collections::HashMap, + fs, ops::Deref, path::{Path, PathBuf}, }; @@ -34,6 +36,7 @@ use inkwell::{ module::Module, passes::PassBuilderOptions, targets::{CodeModel, FileType, InitializationConfig, RelocMode}, + types::BasicTypeEnum, }; use plc_ast::ast::{CompilationUnit, LinkageType}; use plc_diagnostics::diagnostics::Diagnostic; @@ -84,6 +87,39 @@ pub struct GeneratedModule<'ink> { type MainFunction = unsafe extern "C" fn(*mut T) -> U; type MainEmptyFunction = unsafe extern "C" fn() -> U; +pub fn read_got_layout(location: &str, format: ConfigFormat) -> Result, Diagnostic> { + if !Path::new(location).is_file() { + // Assume if the file doesn't exist that there is no existing GOT layout yet. write_got_layout will handle + // creating our file when we want to. + return Ok(HashMap::new()); + } + + let s = + fs::read_to_string(location).map_err(|_| Diagnostic::new("GOT layout could not be read from file"))?; + match format { + ConfigFormat::JSON => serde_json::from_str(&s) + .map_err(|_| Diagnostic::new("Could not deserialize GOT layout from JSON")), + ConfigFormat::TOML => { + toml::de::from_str(&s).map_err(|_| Diagnostic::new("Could not deserialize GOT layout from TOML")) + } + } +} + +pub fn write_got_layout( + got_entries: HashMap, + location: &str, + format: ConfigFormat, +) -> Result<(), Diagnostic> { + let s = match format { + ConfigFormat::JSON => serde_json::to_string(&got_entries) + .map_err(|_| Diagnostic::new("Could not serialize GOT layout to JSON"))?, + ConfigFormat::TOML => toml::ser::to_string(&got_entries) + .map_err(|_| Diagnostic::new("Could not serialize GOT layout to TOML"))?, + }; + + fs::write(location, s).map_err(|_| Diagnostic::new("GOT layout could not be written to file")) +} + impl<'ink> CodeGen<'ink> { /// constructs a new code-generator that generates CompilationUnits into a module with the given module_name pub fn new( @@ -127,7 +163,6 @@ impl<'ink> CodeGen<'ink> { annotations, &index, &mut self.debug, - self.got_layout_file.clone(), ); //Generate global variables @@ -135,6 +170,72 @@ impl<'ink> CodeGen<'ink> { variable_generator.generate_global_variables(dependencies, &self.module_location)?; index.merge(llvm_gv_index); + // Build our GOT layout here. We need to find all the names for globals, programs, and + // functions and assign them indices in the GOT, taking into account prior indices. + let program_globals = global_index + .get_program_instances() + .into_iter() + .fold(Vec::new(), |mut acc, p| { + acc.push(p.get_name()); + acc.push(p.get_qualified_name()); + acc + }); + let functions = global_index.get_pous().values() + .filter_map(|p| match p { + PouIndexEntry::Function { name, linkage: LinkageType::Internal, is_generated: false, .. } + | PouIndexEntry::FunctionBlock { name, linkage: LinkageType::Internal, .. } => Some(name.as_ref()), + _ => None, + }); + let all_names = global_index + .get_globals() + .values() + .map(|g| g.get_qualified_name()) + .chain(program_globals) + .chain(functions) + .map(|n| n.to_lowercase()); + + if let Some((location, format)) = &self.got_layout_file { + let got_entries = read_got_layout(location.as_str(), *format)?; + let mut new_symbols = Vec::new(); + let mut new_got_entries = HashMap::new(); + let mut new_got = HashMap::new(); + + for name in all_names { + if let Some(idx) = got_entries.get(&name.to_string()) { + new_got_entries.insert(name.to_string(), *idx); + index.associate_got_index(&name, *idx)?; + new_got.insert(*idx, name.to_string()); + } else { + new_symbols.push(name.to_string()); + } + } + + // Put any names that weren't there last time in any free space in the GOT. + let mut idx: u64 = 0; + for name in &new_symbols { + while new_got.contains_key(&idx) { + idx += 1; + } + new_got_entries.insert(name.to_string(), idx); + index.associate_got_index(name, idx)?; + new_got.insert(idx, name.to_string()); + } + + // Now we can write new_got_entries back out to a file. + write_got_layout(new_got_entries, location.as_str(), *format)?; + + // Construct our GOT as a new global array. We initialise this array in the loader code. + let got_size = new_got.keys().max().map_or(0, |m| *m + 1); + let _got = llvm.create_global_variable( + &self.module, + "__custom_got", + BasicTypeEnum::ArrayType(Llvm::get_array_type( + BasicTypeEnum::PointerType(llvm.context.i8_type().ptr_type(0.into())), + got_size.try_into().expect("the computed custom GOT size is too large"), + )), + ); + } + //Generate opaque functions for implementations and associate them with their types let llvm = Llvm::new(context, context.create_builder()); let llvm_impl_index = pou_generator::generate_implementation_stubs( diff --git a/src/codegen/generators/expression_generator.rs b/src/codegen/generators/expression_generator.rs index 404526afd6..a6f7b7d062 100644 --- a/src/codegen/generators/expression_generator.rs +++ b/src/codegen/generators/expression_generator.rs @@ -17,10 +17,10 @@ use crate::{ }; use inkwell::{ builder::Builder, - types::{BasicType, BasicTypeEnum}, + types::{BasicType, BasicTypeEnum, FunctionType}, values::{ - ArrayValue, BasicMetadataValueEnum, BasicValue, BasicValueEnum, FloatValue, IntValue, PointerValue, - StructValue, VectorValue, + ArrayValue, BasicMetadataValueEnum, BasicValue, BasicValueEnum, CallableValue, + CallSiteValue, FloatValue, IntValue, PointerValue, StructValue, VectorValue, }, AddressSpace, FloatPredicate, IntPredicate, }; @@ -282,6 +282,77 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { self.generate_expression_value(const_expression) } + /// Generate an access to the appropriate GOT entry to achieve an access to the given base + /// lvalue. + pub fn generate_got_access( + &self, + context: &AstNode, + llvm_type: &BasicTypeEnum<'ink>, + ) -> Result>, Diagnostic> { + match self.annotations.get(context) { + Some(StatementAnnotation::Variable { qualified_name, .. }) => { + // We will generate a GEP, which has as its base address the magic constant which + // will eventually be replaced by the location of the GOT. + let base = self + .llvm + .context + .i64_type() + .const_int(0xdeadbeef00000000, false) + .const_to_pointer(llvm_type + .ptr_type(AddressSpace::default()) + .ptr_type(AddressSpace::default())); + + self.llvm_index + .find_got_index(qualified_name) + .map(|idx| { + let ptr = self.llvm.load_array_element( + base, + &[self.llvm.context.i32_type().const_int(idx, false)], + "", + )?; + Ok(self.llvm.load_pointer(&ptr, "").into_pointer_value()) + }) + .transpose() + } + _ => Ok(None), + } + } + + /// Generate an access to the appropriate GOT entry to achieve a call to the given function. + pub fn generate_got_call( + &self, + qualified_name: &str, + function_type: &FunctionType<'ink>, + args: &[BasicMetadataValueEnum<'ink>], + ) -> Result>, Diagnostic> { + // We will generate a GEP, which has as its base address the magic constant which + // will eventually be replaced by the location of the GOT. + let base = self + .llvm + .context + .i64_type() + .const_int(0xdeadbeef00000000, false) + .const_to_pointer(function_type + .ptr_type(AddressSpace::default()) + .ptr_type(AddressSpace::default())); + + self.llvm_index + .find_got_index(qualified_name) + .map(|idx| { + let mut ptr = self.llvm.load_array_element( + base, + &[self.llvm.context.i32_type().const_int(idx, false)], + "", + )?; + ptr = self.llvm.load_pointer(&ptr, "").into_pointer_value(); + let callable = CallableValue::try_from(ptr) + .map_err(|_| Diagnostic::new("Pointer was not a function pointer"))?; + + Ok(self.llvm.builder.build_call(callable, args, "call")) + }) + .transpose() + } + /// generates a binary expression (e.g. a + b, x AND y, etc.) and returns the resulting `BasicValueEnum` /// - `left` the AstStatement left of the operator /// - `right` the AstStatement right of the operator @@ -478,9 +549,17 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { None }; + // Check for the function within the GOT. If it's there, we need to generate an indirect + // call to its location within the GOT, which should contain a function pointer. + // First get the function type so our function pointer can have the correct type. + let qualified_name = self.annotations.get_qualified_name(operator) + .expect("Shouldn't have got this far without a name for the function"); + let function_type = function.get_type(); + let call = self.generate_got_call(qualified_name, &function_type, &arguments_list)? + .unwrap_or_else(|| self.llvm.builder.build_call(function, &arguments_list, "call")); + // if the target is a function, declare the struct locally // assign all parameters into the struct values - let call = &self.llvm.builder.build_call(function, &arguments_list, "call"); // so grab either: // - the out-pointer if we generated one in by_ref_func_out @@ -1218,13 +1297,17 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { } } + let ctx_type = + self.annotations.get_type_or_void(context, self.index).get_type_information(); + // no context ... so just something like 'x' match self.annotations.get(context) { Some(StatementAnnotation::Variable { qualified_name, .. }) - | Some(StatementAnnotation::Program { qualified_name, .. }) => self - .llvm_index - .find_loaded_associated_variable_value(qualified_name) - .ok_or_else(|| Diagnostic::unresolved_reference(name, offset.clone())), + | Some(StatementAnnotation::Program { qualified_name, .. }) => + self.generate_got_access(context, &self.llvm_index.get_associated_type(ctx_type.get_name())?)?.map_or(self + .llvm_index + .find_loaded_associated_variable_value(qualified_name) + .ok_or_else(|| Diagnostic::unresolved_reference(name, offset.clone())), Ok), _ => Err(Diagnostic::unresolved_reference(name, offset.clone())), } } diff --git a/src/codegen/generators/variable_generator.rs b/src/codegen/generators/variable_generator.rs index 9bd3928c7a..71b4c59d9e 100644 --- a/src/codegen/generators/variable_generator.rs +++ b/src/codegen/generators/variable_generator.rs @@ -5,15 +5,11 @@ use crate::{ codegen::{debug::Debug, llvm_index::LlvmTypedIndex, llvm_typesystem::cast_if_needed}, index::{get_initializer_name, Index, PouIndexEntry, VariableIndexEntry}, resolver::{AnnotationMap, AstAnnotations, Dependency}, - ConfigFormat, }; use indexmap::IndexSet; -use inkwell::{module::Module, types::BasicTypeEnum, values::GlobalValue}; +use inkwell::{module::Module, values::GlobalValue}; use plc_ast::ast::LinkageType; use plc_diagnostics::diagnostics::Diagnostic; -use std::collections::HashMap; -use std::fs::{read_to_string, write}; -use std::path::Path; use super::{ data_type_generator::get_default_for, @@ -24,40 +20,6 @@ use super::{ use crate::codegen::debug::DebugBuilderEnum; use crate::index::FxIndexSet; -pub fn read_got_layout(location: &str, format: ConfigFormat) -> Result, Diagnostic> { - if !Path::new(location).is_file() { - // Assume if the file doesn't exist that there is no existing GOT layout yet. write_got_layout will handle - // creating our file when we want to. - return Ok(HashMap::new()); - } - - let s = - read_to_string(location).map_err(|_| Diagnostic::new("GOT layout could not be read from file"))?; - match format { - ConfigFormat::JSON => serde_json::from_str(&s) - .map_err(|_| Diagnostic::new("Could not deserialize GOT layout from JSON")), - ConfigFormat::TOML => { - toml::de::from_str(&s).map_err(|_| Diagnostic::new("Could not deserialize GOT layout from TOML")) - } - } -} - -pub fn write_got_layout( - got_entries: HashMap, - location: &str, - format: ConfigFormat, -) -> Result<(), Diagnostic> { - let s = match format { - ConfigFormat::JSON => serde_json::to_string(&got_entries) - .map_err(|_| Diagnostic::new("Could not serialize GOT layout to JSON"))?, - ConfigFormat::TOML => toml::ser::to_string(&got_entries) - .map_err(|_| Diagnostic::new("Could not serialize GOT layout to TOML"))?, - }; - - write(location, s).map_err(|_| Diagnostic::new("GOT layout could not be written to file"))?; - Ok(()) -} - pub struct VariableGenerator<'ctx, 'b> { module: &'b Module<'ctx>, llvm: &'b Llvm<'ctx>, @@ -65,7 +27,6 @@ pub struct VariableGenerator<'ctx, 'b> { annotations: &'b AstAnnotations, types_index: &'b LlvmTypedIndex<'ctx>, debug: &'b mut DebugBuilderEnum<'ctx>, - got_layout_file: Option<(String, ConfigFormat)>, } impl<'ctx, 'b> VariableGenerator<'ctx, 'b> { @@ -76,9 +37,8 @@ impl<'ctx, 'b> VariableGenerator<'ctx, 'b> { annotations: &'b AstAnnotations, types_index: &'b LlvmTypedIndex<'ctx>, debug: &'b mut DebugBuilderEnum<'ctx>, - got_layout_file: Option<(String, ConfigFormat)>, ) -> Self { - VariableGenerator { module, llvm, global_index, annotations, types_index, debug, got_layout_file } + VariableGenerator { module, llvm, global_index, annotations, types_index, debug } } pub fn generate_global_variables( @@ -140,46 +100,6 @@ impl<'ctx, 'b> VariableGenerator<'ctx, 'b> { ); } - if let Some((location, format)) = &self.got_layout_file { - let got_entries = read_got_layout(location.as_str(), *format)?; - let mut new_globals = Vec::new(); - let mut new_got_entries = HashMap::new(); - let mut new_got = HashMap::new(); - - for (name, _) in &globals { - if let Some(idx) = got_entries.get(&name.to_string()) { - new_got_entries.insert(name.to_string(), *idx); - new_got.insert(*idx, name.to_string()); - } else { - new_globals.push(name.to_string()); - } - } - - // Put any globals that weren't there last time in any free space in the GOT. - let mut idx: u64 = 0; - for name in &new_globals { - while new_got.contains_key(&idx) { - idx += 1; - } - new_got_entries.insert(name.to_string(), idx); - new_got.insert(idx, name.to_string()); - } - - // Now we can write new_got_entries back out to a file. - write_got_layout(new_got_entries, location.as_str(), *format)?; - - // Construct our GOT as a new global array. We initialise this array in the loader code. - let got_size = new_got.keys().max().map_or(0, |m| *m + 1); - let _got = self.llvm.create_global_variable( - self.module, - "__custom_got", - BasicTypeEnum::ArrayType(Llvm::get_array_type( - BasicTypeEnum::PointerType(self.llvm.context.i8_type().ptr_type(0.into())), - got_size.try_into().expect("the computed custom GOT size is too large"), - )), - ); - } - Ok(index) } diff --git a/src/codegen/llvm_index.rs b/src/codegen/llvm_index.rs index b30d428c23..86604411a0 100644 --- a/src/codegen/llvm_index.rs +++ b/src/codegen/llvm_index.rs @@ -14,6 +14,7 @@ pub struct LlvmTypedIndex<'ink> { type_associations: FxHashMap>, pou_type_associations: FxHashMap>, global_values: FxHashMap>, + got_indices: FxHashMap, initial_value_associations: FxHashMap>, loaded_variable_associations: FxHashMap>, implementations: FxHashMap>, @@ -29,6 +30,7 @@ impl<'ink> LlvmTypedIndex<'ink> { type_associations: FxHashMap::default(), pou_type_associations: FxHashMap::default(), global_values: FxHashMap::default(), + got_indices: FxHashMap::default(), initial_value_associations: FxHashMap::default(), loaded_variable_associations: FxHashMap::default(), implementations: FxHashMap::default(), @@ -51,6 +53,9 @@ impl<'ink> LlvmTypedIndex<'ink> { for (name, value) in other.global_values.drain() { self.global_values.insert(name, value); } + for (name, index) in other.got_indices.drain() { + self.got_indices.insert(name, index); + } for (name, assocication) in other.initial_value_associations.drain() { self.initial_value_associations.insert(name, assocication); } @@ -110,6 +115,13 @@ impl<'ink> LlvmTypedIndex<'ink> { .or_else(|| self.parent_index.and_then(|it| it.find_global_value(name))) } + pub fn find_got_index(&self, name: &str) -> Option { + self.got_indices + .get(&name.to_lowercase()) + .copied() + .or_else(|| self.parent_index.and_then(|it| it.find_got_index(name))) + } + pub fn find_associated_type(&self, type_name: &str) -> Option> { self.type_associations .get(&type_name.to_lowercase()) @@ -153,6 +165,15 @@ impl<'ink> LlvmTypedIndex<'ink> { Ok(()) } + pub fn associate_got_index( + &mut self, + variable_name: &str, + index: u64, + ) -> Result<(), Diagnostic> { + self.got_indices.insert(variable_name.to_lowercase(), index); + Ok(()) + } + pub fn associate_implementation( &mut self, callable_name: &str,