Skip to content

Commit

Permalink
Fix bugs revealed by compiling alloc (#133)
Browse files Browse the repository at this point in the history
This commit is an assortment of fixes for issues that were discovered by
compiling the Rust `alloc` core library using the Hieratika compiler. It
includes:

- Fixes to constant building to account for the construction of
  explicitly-poisoned values. Note that these are _distinct_ from the
  FLO-level poisons which indicate static invalidity, as LLVM's poisons
  indicate dynamic invalidity. Poisons are safe to replace by valid
  values of the poisoned type, so this is what we do.
- Handles additional cases in the building of constants to allow the
  compiler to support previously-unexpected cases. Doing this has
  required refactoring the auxiliary parsing infrastructure to aid in
  processing portions of the IR that Inkwell does not support well.
  • Loading branch information
iamrecursion authored Jan 27, 2025
1 parent c8f21e8 commit cf95ccb
Show file tree
Hide file tree
Showing 18 changed files with 1,808 additions and 469 deletions.
30 changes: 24 additions & 6 deletions crates/compiler/input/compilation/constants.ll
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ 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_reference_const
@function_pointer_const_in_struct = constant { i1, ptr } { i1 0, ptr @hieratika_test_reference_const }

define i64 @hieratika_test_call_function_ptr() unnamed_addr {
start:
Expand All @@ -22,40 +22,58 @@ start:

define i64 @hieratika_test_const_integer() unnamed_addr {
start:
%1 = add i64 poison, 0
%2 = add i64 undef, 0
%3 = add i64 zeroinitializer, 0
%4 = add i128 -4176471573560389552232087451844504212, 1
ret i64 0
}

define double @hieratika_test_const_float() unnamed_addr {
start:
%1 = fadd double poison, 0.0
%2 = fadd double undef, 0.0
%3 = fadd double zeroinitializer, 0.0
ret double 0.0
}

define void @hieratika_test_const_pointer() unnamed_addr {
start:
%addr = alloca ptr
store ptr blockaddress(@hieratika_test_const_pointer, %bb1), ptr %addr
store ptr poison, ptr %addr
store ptr undef, ptr %addr
store ptr zeroinitializer, ptr %addr
store i64 ptrtoint (ptr @constant_pointer_const to i64), ptr %addr
store ptr inttoptr (i64 1 to ptr), ptr %addr
ret void
bb1:
unreachable
}

define void @hieratika_test_const_array() unnamed_addr {
define void @hieratika_test_const_string() unnamed_addr {
start:
%ptr = alloca ptr
store [2 x i8] [i8 0, i8 1], ptr %ptr
store [9 x i8] c"hieratika", ptr %ptr
ret void
}

define void @hieratika_test_const_string() unnamed_addr {
define void @hieratika_test_const_array() unnamed_addr {
start:
%ptr = alloca ptr
store [9 x i8] c"hieratika", ptr %ptr
store [2 x i8] [i8 0, i8 1], ptr %ptr
store [2 x i8] poison, ptr %ptr
store [2 x i8] undef, ptr %ptr
store [2 x i8] zeroinitializer, ptr %ptr
ret void
}

define void @hieratika_test_const_struct() unnamed_addr {
start:
%ptr = alloca ptr
store { i8, i1 } { i8 0, i1 1 }, ptr %ptr
store { i8, i1 } poison, ptr %ptr
store { i8, i1 } undef, ptr %ptr
store { i8, i1 } zeroinitializer, ptr %ptr
ret void
}
1 change: 1 addition & 0 deletions crates/compiler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ pub mod context;
pub mod llvm;
pub mod messages;
pub mod obj_gen;
pub mod parser;
pub mod pass;
pub mod polyfill;

Expand Down
107 changes: 51 additions & 56 deletions crates/compiler/src/llvm/data_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,23 @@ use hieratika_errors::{
compile::llvm::{Error, Result},
};

use crate::constant::{
BYTE_SIZE_BITS,
DEFAULT_FLOAT_16_LAYOUT,
DEFAULT_FLOAT_32_LAYOUT,
DEFAULT_FLOAT_64_LAYOUT,
DEFAULT_FLOAT_128_LAYOUT,
DEFAULT_INTEGER_1_LAYOUT,
DEFAULT_INTEGER_8_LAYOUT,
DEFAULT_INTEGER_16_LAYOUT,
DEFAULT_INTEGER_32_LAYOUT,
DEFAULT_INTEGER_64_LAYOUT,
DEFAULT_POINTER_0_LAYOUT,
DEFAULT_VECTOR_64_LAYOUT,
DEFAULT_VECTOR_128_LAYOUT,
use crate::{
constant::{
BYTE_SIZE_BITS,
DEFAULT_FLOAT_16_LAYOUT,
DEFAULT_FLOAT_32_LAYOUT,
DEFAULT_FLOAT_64_LAYOUT,
DEFAULT_FLOAT_128_LAYOUT,
DEFAULT_INTEGER_1_LAYOUT,
DEFAULT_INTEGER_8_LAYOUT,
DEFAULT_INTEGER_16_LAYOUT,
DEFAULT_INTEGER_32_LAYOUT,
DEFAULT_INTEGER_64_LAYOUT,
DEFAULT_POINTER_0_LAYOUT,
DEFAULT_VECTOR_64_LAYOUT,
DEFAULT_VECTOR_128_LAYOUT,
},
parser::number::positive_integer,
};

/// Information about the expected data-layout for this module.
Expand Down Expand Up @@ -450,11 +453,11 @@ impl PointerLayout {
#[must_use]
pub fn parser() -> impl parsing::DLParser<PointerLayout> {
just("p")
.ignore_then(parsing::pos_int(10).delimited_by(just("["), just("]")).or_not())
.then(parsing::field(parsing::pos_int(10)))
.then(parsing::field(parsing::pos_int(10)))
.then(parsing::field(parsing::pos_int(10)).or_not())
.then(parsing::field(parsing::pos_int(10)).or_not())
.ignore_then(positive_integer(10).delimited_by(just("["), just("]")).or_not())
.then(parsing::field(positive_integer(10)))
.then(parsing::field(positive_integer(10)))
.then(parsing::field(positive_integer(10)).or_not())
.then(parsing::field(positive_integer(10)).or_not())
.try_map(
|((((address_space, size), abi_alignment), preferred_alignment), index_size),
span| {
Expand Down Expand Up @@ -502,9 +505,9 @@ impl IntegerLayout {
#[must_use]
pub fn parser() -> impl parsing::DLParser<IntegerLayout> {
just("i")
.ignore_then(parsing::pos_int(10))
.then(parsing::field(parsing::pos_int(10)))
.then(parsing::field(parsing::pos_int(10)).or_not())
.ignore_then(positive_integer(10))
.then(parsing::field(positive_integer(10)))
.then(parsing::field(positive_integer(10)).or_not())
.try_map(|((size, abi_alignment), preferred_alignment), span| {
let preferred_alignment = preferred_alignment.unwrap_or(abi_alignment);
if size == BYTE_SIZE_BITS && abi_alignment != size {
Expand Down Expand Up @@ -542,9 +545,9 @@ impl VectorLayout {
#[must_use]
pub fn parser() -> impl parsing::DLParser<VectorLayout> {
just("v")
.ignore_then(parsing::pos_int(10))
.then(parsing::field(parsing::pos_int(10)))
.then(parsing::field(parsing::pos_int(10)).or_not())
.ignore_then(positive_integer(10))
.then(parsing::field(positive_integer(10)))
.then(parsing::field(positive_integer(10)).or_not())
.map(|((size, abi_alignment), preferred_alignment)| {
let preferred_alignment = preferred_alignment.unwrap_or(abi_alignment);

Expand Down Expand Up @@ -576,9 +579,9 @@ impl FloatLayout {
#[must_use]
pub fn parser() -> impl parsing::DLParser<FloatLayout> {
just("f")
.ignore_then(parsing::pos_int(10))
.then(parsing::field(parsing::pos_int(10)))
.then(parsing::field(parsing::pos_int(10)).or_not())
.ignore_then(positive_integer(10))
.then(parsing::field(positive_integer(10)))
.then(parsing::field(positive_integer(10)).or_not())
.try_map(|((size, abi_alignment), preferred_alignment), span| {
let preferred_alignment = preferred_alignment.unwrap_or(abi_alignment);
if !&[16, 32, 64, 80, 128].contains(&size) {
Expand Down Expand Up @@ -613,8 +616,8 @@ impl AggregateLayout {
#[must_use]
pub fn parser() -> impl parsing::DLParser<AggregateLayout> {
just("a")
.ignore_then(parsing::pos_int(10))
.then(parsing::field(parsing::pos_int(10)).or_not())
.ignore_then(positive_integer(10))
.then(parsing::field(positive_integer(10)).or_not())
.map(|(abi_alignment, preferred_alignment)| {
let preferred_alignment = preferred_alignment.unwrap_or(abi_alignment);

Expand Down Expand Up @@ -668,7 +671,7 @@ impl FunctionPointerLayout {
pub fn parser() -> impl parsing::DLParser<FunctionPointerLayout> {
just("F")
.ignore_then(FunctionPointerType::parser())
.then(parsing::pos_int(10))
.then(positive_integer(10))
.map(|(ptr_type, abi_alignment)| Self {
ptr_type,
abi_alignment,
Expand All @@ -690,8 +693,8 @@ impl NativeIntegerWidths {
#[must_use]
pub fn parser() -> impl parsing::DLParser<NativeIntegerWidths> {
just("n")
.ignore_then(parsing::pos_int(10))
.then(parsing::field(parsing::pos_int(10)).repeated())
.ignore_then(positive_integer(10))
.then(parsing::field(positive_integer(10)).repeated())
.map(|(first, mut rest)| {
rest.insert(0, first);
Self { widths: rest }
Expand All @@ -713,7 +716,7 @@ impl NonIntegralPointerAddressSpaces {
#[must_use]
pub fn parser() -> impl parsing::DLParser<NonIntegralPointerAddressSpaces> {
just("ni")
.ignore_then(parsing::field(parsing::pos_int(10)).repeated().at_least(1))
.ignore_then(parsing::field(positive_integer(10)).repeated().at_least(1))
.try_map(|address_spaces, span| {
if address_spaces.contains(&0) {
Err(Simple::custom(
Expand All @@ -730,9 +733,9 @@ impl NonIntegralPointerAddressSpaces {
/// Utility parsing functions to aid in the parsing of data-layouts but that are
/// not associated directly with any type.
pub mod parsing {
use chumsky::{Parser, error::Simple, prelude::just, text::int};
use chumsky::{Parser, error::Simple, prelude::just};

use crate::{constant::BYTE_SIZE_BITS, llvm::data_layout::parsing};
use crate::{constant::BYTE_SIZE_BITS, parser::number::positive_integer};

/// Simply to avoid typing out the whole parser type parameter specification
/// every single time given it only varies in one parameter.
Expand All @@ -759,33 +762,25 @@ pub mod parsing {
field_sep().ignore_then(then)
}

/// Parses a positive integer in the specified `radix`.
#[must_use]
pub fn pos_int(radix: u32) -> impl DLParser<usize> {
int(radix).try_map(|num: String, span| {
num.parse::<usize>().map_err(|_| {
Simple::custom(span, format!("Could not parse {num} as a positive integer"))
})
})
}

/// Parses the stack alignment specification part of the data-layout.
#[must_use]
pub fn stack_alignment() -> impl DLParser<usize> {
just("S").ignore_then(pos_int(10)).validate(|alignment, span, emit| {
if alignment % BYTE_SIZE_BITS != 0 {
emit(Simple::custom(
span,
format!("{alignment} must be aligned to a byte offset"),
));
}
alignment
})
just("S")
.ignore_then(positive_integer(10))
.validate(|alignment, span, emit| {
if alignment % BYTE_SIZE_BITS != 0 {
emit(Simple::custom(
span,
format!("{alignment} must be aligned to a byte offset"),
));
}
alignment
})
}

/// Parses the address space specification part of the data-layout.
fn address_space(space: &str) -> impl DLParser<usize> + '_ {
just(space).ignore_then(parsing::pos_int(10))
just(space).ignore_then(positive_integer(10))
}

#[must_use]
Expand Down
54 changes: 50 additions & 4 deletions crates/compiler/src/obj_gen/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,16 @@ use bimap::BiHashMap;
use hieratika_errors::compile::llvm::{Error, Result};
use hieratika_flo::{
FlatLoweredObject,
types::{ArrayType, BlockId, PoisonType, StructType, Type, VariableId, VariableLinkage},
types::{
ArrayType,
BlockExit,
BlockId,
PoisonType,
StructType,
Type,
VariableId,
VariableLinkage,
},
};
use hieratika_mangler::{NameInfo, constants::INTERNAL_NAME_PREFIX, mangle};
use inkwell::module::Linkage;
Expand Down Expand Up @@ -86,18 +95,45 @@ pub struct ObjectContext {
/// Behavior may be inconsistent if the identifiers within are not allocated
/// in the `flo` object contained in `Self`.
pub map: ObjectMap,

/// A block that can be used to represent a null pointer.
pub null_block: BlockId,
}

/// Functionality that requires access to the FLO context directly.
impl ObjectContext {
/// Constructs a new code generator data store for the module with the
/// provided name.
///
/// # Panics
///
/// - If the constant block value cannot be created for some reason.
#[must_use]
pub fn new(name: &str) -> Self {
let flo = FlatLoweredObject::new(name);
let mut flo = FlatLoweredObject::new(name);
let map = ObjectMap::new();
let null_block = flo
.add_block(|bb| -> Result<()> {
bb.set_exit(&BlockExit::Panic(
"Poison block was reachable".to_string(),
Vec::new(),
));
Ok(())
})
.expect("Constant block could not be created.");

Self {
flo,
map,
null_block,
}
}

Self { flo, map }
/// Creates a new function context for a function of type `function_type`
/// and wrapping data from `self`.
#[must_use]
pub fn new_func_ctx(&self, function_type: &LLVMFunction) -> FunctionContext {
FunctionContext::new(function_type.clone(), self.map.clone(), self.null_block)
}
}

Expand Down Expand Up @@ -319,12 +355,15 @@ pub struct FunctionContext {
/// allocated from the same `flo` object in which this function is
/// allocated.
map: ObjectMap,

/// A block that can be used to represent a null pointer.
null_block: BlockId,
}

impl FunctionContext {
/// Creates a new, empty instance of the function context.
#[must_use]
pub fn new(func_type: LLVMFunction, map: ObjectMap) -> Self {
pub fn new(func_type: LLVMFunction, map: ObjectMap, null_block: BlockId) -> Self {
let blocks = HashMap::new();
let locals = HashMap::new();
let var_types = HashMap::new();
Expand All @@ -335,6 +374,7 @@ impl FunctionContext {
locals,
var_types,
map,
null_block,
}
}

Expand Down Expand Up @@ -365,6 +405,12 @@ impl FunctionContext {
&self.blocks
}

/// Gets the identifier for the poison block for this module.
#[must_use]
pub fn poison_block(&self) -> BlockId {
self.null_block
}

/// Looks up the block with the given `name` in the function context,
/// returning its identifier if it exists or [`None`] if it does not.
#[must_use]
Expand Down
Loading

0 comments on commit cf95ccb

Please sign in to comment.