Skip to content

Commit

Permalink
Implement handling of function pointers
Browse files Browse the repository at this point in the history
Function pointers are now handled according to the memory model
documentation, ensuring that we can generate code that will allow us to
call through opaque function pointers correctly.

Note that this commit only encompasses the compiler-side part of this
work, with more work to be done in the linker to handle generation of
the meta-dispatch functions for each function type.

It also makes some minor changes to the mangler to allow it to handle
reserved names in a more sensible way.

Closes #100
  • Loading branch information
iamrecursion committed Jan 15, 2025
1 parent aeb3df3 commit bd7a5b4
Show file tree
Hide file tree
Showing 15 changed files with 570 additions and 116 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions crates/compiler/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ bimap.workspace = true
chumsky = "0.9.3"
downcast-rs = "1.2.1"
ethnum.workspace = true
inkwell.workspace = true
itertools.workspace = true
hieratika-errors.workspace = true
hieratika-flo.workspace = true
hieratika-mangler.workspace = true
inkwell.workspace = true
itertools.workspace = true
miette.workspace = true
ouroboros = "0.18.4"
rand = "0.8.5"
Expand Down
10 changes: 8 additions & 2 deletions crates/compiler/input/compilation/constants.ll
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@ target triple = "riscv64"
@constant_pointer_const = constant ptr @test_const
@constant_pointer_const_in_struct = constant { i1, ptr } { i1 0, ptr @test_const }

; @function_pointer_const = constant ptr @hieratika_test_const_integer
; @function_pointer_const_in_struct = constant { i1, ptr } { i1 0, ptr @hieratika_test_const_integer }
@function_pointer_const = constant ptr @hieratika_test_const_integer
@function_pointer_const_in_struct = constant { i1, ptr } { i1 0, ptr @hieratika_test_const_integer }

define i64 @hieratika_test_call_function_ptr() unnamed_addr {
start:
%result = call i64 @function_pointer_const()
ret i64 %result
}

define ptr @hieratika_test_reference_const() unnamed_addr {
start:
Expand Down
6 changes: 6 additions & 0 deletions crates/compiler/src/constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,9 @@ pub const DEFAULT_INTEGER_64_LAYOUT: (usize, usize, usize) = (64, 32, 64);
/// The numbers are, in order: the address space, the size, the ABI alignment,
/// the preferred alignment, and the index size.
pub const DEFAULT_POINTER_0_LAYOUT: (usize, usize, usize, usize, usize) = (0, 64, 64, 64, 64);

/// The function name stub for dispatcher functions.
pub const DISPATCH_FUNCTION_NAME: &str = "hdisp";

/// The module name for the whole-program scope.
pub const WHOLE_PROGRAM_MODULE_NAME: &str = "meta";
56 changes: 34 additions & 22 deletions crates/compiler/src/llvm/typesystem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,28 +351,9 @@ impl Display for LLVMType {
LLVMType::ptr => "ptr".to_string(),
LLVMType::void => "void".to_string(),
LLVMType::Metadata => "metadata".to_string(),
LLVMType::Array(LLVMArray { count, typ }) => {
let ty_str = typ.to_string();
format!("[{ty_str}; {count}]")
}
LLVMType::Structure(LLVMStruct { packed, elements }) => {
let elem_strs = elements.iter().map(std::string::ToString::to_string).join(", ");
if *packed {
format!("<{{ {elem_strs} }}>")
} else {
format!("{{ {elem_strs} }}")
}
}
LLVMType::Function(LLVMFunction {
return_type,
parameter_types,
}) => {
let params_string = parameter_types
.iter()
.map(std::string::ToString::to_string)
.join(", ");
format!("({params_string}) -> {return_type}")
}
LLVMType::Array(array_type) => array_type.to_string(),
LLVMType::Structure(struct_type) => struct_type.to_string(),
LLVMType::Function(function_type) => function_type.to_string(),
};

write!(f, "{result}")
Expand Down Expand Up @@ -698,6 +679,14 @@ impl LLVMArray {
}
}

impl Display for LLVMArray {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let ty_str = self.typ.to_string();
let count = self.count;
write!(f, "[{ty_str}; {count}]")
}
}

impl From<LLVMArray> for LLVMType {
fn from(value: LLVMArray) -> Self {
LLVMType::from(&value)
Expand Down Expand Up @@ -901,6 +890,17 @@ impl LLVMStruct {
}
}

impl Display for LLVMStruct {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let elem_strs = self.elements.iter().map(std::string::ToString::to_string).join(", ");
if self.packed {
write!(f, "<{{ {elem_strs} }}>")
} else {
write!(f, "{{ {elem_strs} }}")
}
}
}

impl From<LLVMStruct> for LLVMType {
fn from(value: LLVMStruct) -> Self {
LLVMType::from(&value)
Expand Down Expand Up @@ -993,6 +993,18 @@ impl LLVMFunction {
}
}

impl Display for LLVMFunction {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let params_string = self
.parameter_types
.iter()
.map(std::string::ToString::to_string)
.join(", ");
let return_type = &self.return_type;
write!(f, "({params_string}) -> {return_type}")
}
}

impl From<LLVMFunction> for LLVMType {
fn from(value: LLVMFunction) -> Self {
Self::Function(value)
Expand Down
12 changes: 11 additions & 1 deletion crates/compiler/src/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use hieratika_errors::compile::llvm::Error;
use inkwell::values::{InstructionOpcode, InstructionValue};

use crate::llvm::typesystem::LLVMType;
use crate::llvm::typesystem::{LLVMFunction, LLVMType};

/// An error message for use when expecting that an instruction had a name.
pub const INSTRUCTION_NAMED: &str =
Expand Down Expand Up @@ -33,6 +33,16 @@ pub fn assert_correct_opcode(instruction: &InstructionValue, expected: Instructi
);
}

/// Panics due to being unable to correctly mangle the provided `typ`.
///
/// # Panics
///
/// - Always
#[must_use]
pub fn panic_cannot_mangle(typ: &LLVMFunction) -> String {
panic!("Function type {typ} cannot be mangled")
}

/// Generates an [`Error::MalformedLLVM`] with a message about the number of
/// operands being incorrect for the instruction.
#[must_use]
Expand Down
93 changes: 92 additions & 1 deletion crates/compiler/src/obj_gen/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ use hieratika_flo::{
FlatLoweredObject,
types::{ArrayType, BlockId, PoisonType, StructType, Type, VariableId, VariableLinkage},
};
use hieratika_mangler::{NameInfo, constants::INTERNAL_NAME_PREFIX, mangle};
use inkwell::module::Linkage;

use crate::llvm::typesystem::{LLVMArray, LLVMFunction, LLVMStruct, LLVMType};
use crate::{
constant::{DISPATCH_FUNCTION_NAME, WHOLE_PROGRAM_MODULE_NAME},
llvm::typesystem::{LLVMArray, LLVMFunction, LLVMStruct, LLVMType},
messages::panic_cannot_mangle,
};

/// A mapping from the names of locally-defined functions to their identifiers.
pub type ModuleFunctions = HashMap<String, BlockId>;
Expand Down Expand Up @@ -99,6 +104,92 @@ impl ObjectContext {
/// Useful functionality that does not require access to the FLO context
/// directly, but nevertheless deals with the generation of FLO.
impl ObjectContext {
/// Gets the name of the local dispatch function for the provided `typ`.
///
/// Note that the return value is not guaranteed to be a valid Rust
/// identifier, but is valid in both LLVM IR and FLO.
///
/// ```
/// use hieratika_compiler::{
/// llvm::typesystem::{LLVMFunction, LLVMType},
/// obj_gen::data::ObjectContext,
/// };
///
/// let function_type = LLVMFunction::new(LLVMType::f32, &[LLVMType::bool, LLVMType::i128]);
/// let module_name = "my_module";
/// let local_dispatch_name =
/// ObjectContext::local_dispatch_name_for(&function_type, module_name).unwrap();
///
/// assert_eq!(local_dispatch_name, "__f$hdisp$co$my_module");
/// ```
///
/// # Errors
///
/// - [`Error::InvalidTypeConversion`] if any of the types in `typ` cannot
/// be represented in FLO.
///
/// # Panics
///
/// - If `typ` contains a type that cannot be mangled.
pub fn local_dispatch_name_for(typ: &LLVMFunction, module_name: &str) -> Result<String> {
let func_name = format!("{INTERNAL_NAME_PREFIX}{DISPATCH_FUNCTION_NAME}");
let input_types = typ
.parameter_types
.iter()
.map(Self::flo_type_of)
.collect::<Result<Vec<_>>>()?;
let return_types = vec![Self::flo_type_of(typ.return_type.as_ref())?];
let mangle_input = NameInfo::new(&func_name, module_name, input_types, return_types);
let mangled_name = mangle(mangle_input).unwrap_or_else(|_| panic_cannot_mangle(typ));

Ok(mangled_name)
}

/// Gets the name of the global (or meta) dispatch function for the provided
/// `typ`.
///
/// Note that the return value is not guaranteed to be a valid Rust
/// identifier, but is valid in both LLVM IR and FLO.
///
/// ```
/// use hieratika_compiler::{
/// llvm::typesystem::{LLVMFunction, LLVMType},
/// obj_gen::data::ObjectContext,
/// };
///
/// let function_type = LLVMFunction::new(LLVMType::f32, &[LLVMType::bool, LLVMType::i128]);
/// let global_dispatch_name = ObjectContext::global_dispatch_name_for(&function_type).unwrap();
///
/// assert_eq!(global_dispatch_name, "__f$hdisp$co$meta");
/// ```
///
/// # Errors
///
/// - [`Error::InvalidTypeConversion`] if any of the types in `typ` cannot
/// be represented in FLO.
///
/// # Panics
///
/// - If `typ` contains a type that cannot be mangled.
pub fn global_dispatch_name_for(typ: &LLVMFunction) -> Result<String> {
let func_name = format!("{INTERNAL_NAME_PREFIX}{DISPATCH_FUNCTION_NAME}");
let input_types = typ
.parameter_types
.iter()
.map(Self::flo_type_of)
.collect::<Result<Vec<_>>>()?;
let return_types = vec![Self::flo_type_of(typ.return_type.as_ref())?];
let mangle_input = NameInfo::new(
&func_name,
WHOLE_PROGRAM_MODULE_NAME,
input_types,
return_types,
);
let mangled_name = mangle(mangle_input).unwrap_or_else(|_| panic_cannot_mangle(typ));

Ok(mangled_name)
}

/// Generates the correct FLO linkage from the provided LLVM `linkage`.
#[must_use]
pub fn linkage_of(linkage: &Linkage, name: &str) -> VariableLinkage {
Expand Down
Loading

0 comments on commit bd7a5b4

Please sign in to comment.