Skip to content

Commit

Permalink
Make some initial fixes get core compiling (#137)
Browse files Browse the repository at this point in the history
This commit is a grab-bag of smaller fixes required to get Rust's
`core` library compiling properly by hieratika. They include:

- Adding support for an `i48` type which seems to be required, as well
  as adding polyfill stubs for such a type.
- Adding support for an `i40` type which seems to be required, as well
  as adding polyfill stubs for such a type.
- Changing the `DataLayout` type's query functionality to comply with
  the layout promotion rules for integers.
- Registers all variables inside a function ahead of time to cope with
  usages before declaration in lexical order but not in execution order.
- Changes the handling of GetElementPtr instructions to account for
  negative indices, which were not apparently allowed from the LangRef,
  but are encountered in `core`. As GetElementPtr does not access
  memory, it does make sense that they are allowed, so we now support
  them here.
  • Loading branch information
iamrecursion authored Jan 30, 2025
1 parent 914f3fb commit 3a774f1
Show file tree
Hide file tree
Showing 13 changed files with 361 additions and 259 deletions.
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,9 @@ tracing = "0.1.40"
debug = true # Include full debug information in release builds.
overflow-checks = true # Keep overflow checks in production builds.
lto = "thin" # Thin LTO performs cheaper cross-crate LTO.

# Running the compiler tests in a reasonable time means we need to optimize,
# but we also want to retain debug assertions and debug information.
[profile.test]
opt-level = 1
debug = "full"
26 changes: 26 additions & 0 deletions crates/compiler/input/compilation/phi.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
target datalayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"
target triple = "riscv64"

; We declare the necessary functions and constants to avoid unavailable references
@alloc_e547e50f836b5d080f631e710773931f = private unnamed_addr constant <{ ptr, [16 x i8] }> <{ ptr @alloc_e951d9caa9b0332887c975fc323e279a, [16 x i8] c"j\00\00\00\00\00\00\00\9D\01\00\00-\00\00\00" }>, align 8
@alloc_e951d9caa9b0332887c975fc323e279a = private unnamed_addr constant <{ [106 x i8] }> <{ [106 x i8] c"/nix/store/hahzrgjq3ncgd241r37xm63ydm9xxfp7-rust-mixed/lib/rustlib/src/rust/library/core/src/array/iter.rs" }>, align 1
@7 = private unnamed_addr constant <{ [4 x i8] }> undef, align 4

declare i64 @_ZN4core3ops11index_range10IndexRange3end17h736da45717d9393bE(ptr align 8 %new)
declare { i64, i64 } @_ZN4core3ops11index_range10IndexRange7zero_to17h9efe20b9ab4c8a42E(i64 %0)

; <core::array::iter::IntoIter<T,_> as core::clone::Clone>::clone
; Function Attrs: noredzone nounwind
define dso_local void @"_ZN79_$LT$core..array..iter..IntoIter$LT$T$C$_$GT$$u20$as$u20$core..clone..Clone$GT$5clone17h19b6a6ce98691d0aE"(ptr sret([32 x i8]) align 8 %_0, ptr align 8 %self) unnamed_addr {
start:
%_10 = alloca [16 x i8], align 8
br label %repeat_loop_header

repeat_loop_header: ; preds = %repeat_loop_body, %start
%1 = phi i64 [ 0, %start ], [ %4, %repeat_loop_body ]
br i1 0, label %repeat_loop_body, label %repeat_loop_header

repeat_loop_body: ; preds = %repeat_loop_header
%4 = add nuw i64 %1, 1
br label %repeat_loop_header
}
34 changes: 22 additions & 12 deletions crates/compiler/src/llvm/data_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use hieratika_errors::{
backtrace::WithBacktrace,
compile::llvm::{Error, Result},
};
use itertools::Itertools;

use crate::{
constant::{
Expand Down Expand Up @@ -276,24 +277,33 @@ impl DataLayout {
specs
}

/// Gets the integer layout corresponding to integers of the provided
/// `size`, or returns [`None`] if no such layout is available.
#[must_use]
pub fn int_spec_of(&self, size: usize) -> Option<&IntegerLayout> {
self.integer_layouts.iter().find(|int_spec| int_spec.size == size)
}

/// Gets the integer layout corresponding to integers of the provided
/// `size`.
///
/// # Panics
///
/// If no layout exists in the data layout for integers of the provided
/// `size`.
/// - If the data layout does not contain any integer specifications.
#[must_use]
pub fn expect_int_spec_of(&self, size: usize) -> &IntegerLayout {
self.int_spec_of(size)
.unwrap_or_else(|| panic!("No layout found for integer of size {size}"))
pub fn int_spec_of(&self, size: usize) -> &IntegerLayout {
// LLVM's language ref states that: "If no match is found, and the type sought
// is an integer type, then the smallest integer type that is larger
// than the bitwidth of the sought type is used. If none of the
// specifications are larger than the bitwidth then the largest
// integer type is used."
//
// We implement this algorithm here.
if let Some(layout) = self.integer_layouts.iter().find(|int_spec| int_spec.size >= size) {
// In this case, we have found a layout that is the same size or larger, so we
// just return it.
layout
} else {
// In this case, we have to provide the largest defined layout.
self.integer_layouts
.iter()
.sorted_by_key(|l| l.size)
.last()
.expect("There must be at least one integer layout for the data layout to be valid")
}
}

/// Gets the floating-point layout corresponding to floating-point numbers
Expand Down
74 changes: 60 additions & 14 deletions crates/compiler/src/llvm/typesystem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ pub enum LLVMType {
/// The 32-bit wide [integer type](https://llvm.org/docs/LangRef.html#integer-type).
i32,

/// The 40-bit wide [integer type](https://llvm.org/docs/LangRef.html#integer-type).
i40,

/// The 48-bit wide [integer type](https://llvm.org/docs/LangRef.html#integer-type).
i48,

/// The 64-bit wide [integer type](https://llvm.org/docs/LangRef.html#integer-type).
i64,

Expand Down Expand Up @@ -171,6 +177,8 @@ impl LLVMType {
| Self::i16
| Self::i24
| Self::i32
| Self::i40
| Self::i48
| Self::i64
| Self::i128
| Self::f16
Expand Down Expand Up @@ -234,6 +242,8 @@ impl LLVMType {
LLVMType::i16 => 16,
LLVMType::i24 => 24,
LLVMType::i32 => 32,
LLVMType::i40 => 40,
LLVMType::i48 => 48,
LLVMType::i64 => 64,
LLVMType::i128 => 128,
LLVMType::f16 => 16,
Expand Down Expand Up @@ -278,32 +288,40 @@ impl LLVMType {
pub fn align_of(&self, align_type: AlignType, data_layout: &DataLayout) -> usize {
match self {
LLVMType::bool => match align_type {
AlignType::ABI => data_layout.expect_int_spec_of(1).abi_alignment,
AlignType::Preferred => data_layout.expect_int_spec_of(1).preferred_alignment,
AlignType::ABI => data_layout.int_spec_of(1).abi_alignment,
AlignType::Preferred => data_layout.int_spec_of(1).preferred_alignment,
},
LLVMType::i8 => match align_type {
AlignType::ABI => data_layout.expect_int_spec_of(8).abi_alignment,
AlignType::Preferred => data_layout.expect_int_spec_of(8).preferred_alignment,
AlignType::ABI => data_layout.int_spec_of(8).abi_alignment,
AlignType::Preferred => data_layout.int_spec_of(8).preferred_alignment,
},
LLVMType::i16 => match align_type {
AlignType::ABI => data_layout.expect_int_spec_of(16).abi_alignment,
AlignType::Preferred => data_layout.expect_int_spec_of(16).preferred_alignment,
AlignType::ABI => data_layout.int_spec_of(16).abi_alignment,
AlignType::Preferred => data_layout.int_spec_of(16).preferred_alignment,
},
LLVMType::i24 => match align_type {
AlignType::ABI => data_layout.expect_int_spec_of(24).abi_alignment,
AlignType::Preferred => data_layout.expect_int_spec_of(24).preferred_alignment,
AlignType::ABI => data_layout.int_spec_of(24).abi_alignment,
AlignType::Preferred => data_layout.int_spec_of(24).preferred_alignment,
},
LLVMType::i32 => match align_type {
AlignType::ABI => data_layout.expect_int_spec_of(32).abi_alignment,
AlignType::Preferred => data_layout.expect_int_spec_of(32).preferred_alignment,
AlignType::ABI => data_layout.int_spec_of(32).abi_alignment,
AlignType::Preferred => data_layout.int_spec_of(32).preferred_alignment,
},
LLVMType::i40 => match align_type {
AlignType::ABI => data_layout.int_spec_of(40).abi_alignment,
AlignType::Preferred => data_layout.int_spec_of(40).preferred_alignment,
},
LLVMType::i48 => match align_type {
AlignType::ABI => data_layout.int_spec_of(48).abi_alignment,
AlignType::Preferred => data_layout.int_spec_of(48).preferred_alignment,
},
LLVMType::i64 => match align_type {
AlignType::ABI => data_layout.expect_int_spec_of(64).abi_alignment,
AlignType::Preferred => data_layout.expect_int_spec_of(64).preferred_alignment,
AlignType::ABI => data_layout.int_spec_of(64).abi_alignment,
AlignType::Preferred => data_layout.int_spec_of(64).preferred_alignment,
},
LLVMType::i128 => match align_type {
AlignType::ABI => data_layout.expect_int_spec_of(128).abi_alignment,
AlignType::Preferred => data_layout.expect_int_spec_of(128).preferred_alignment,
AlignType::ABI => data_layout.int_spec_of(128).abi_alignment,
AlignType::Preferred => data_layout.int_spec_of(128).preferred_alignment,
},
LLVMType::f16 => match align_type {
AlignType::ABI => data_layout.expect_float_spec_of(16).abi_alignment,
Expand Down Expand Up @@ -343,6 +361,8 @@ impl Display for LLVMType {
LLVMType::i16 => "i16".to_string(),
LLVMType::i24 => "i24".to_string(),
LLVMType::i32 => "i32".to_string(),
LLVMType::i40 => "i40".to_string(),
LLVMType::i48 => "i48".to_string(),
LLVMType::i64 => "i64".to_string(),
LLVMType::i128 => "i128".to_string(),
LLVMType::f16 => "f16".to_string(),
Expand Down Expand Up @@ -479,6 +499,8 @@ impl<'ctx> TryFrom<&IntType<'ctx>> for LLVMType {
16 => Self::i16,
24 => Self::i24,
32 => Self::i32,
40 => Self::i40,
48 => Self::i48,
64 => Self::i64,
128 => Self::i128,
_ => Err(Error::UnsupportedType(value.to_string()))?,
Expand Down Expand Up @@ -1133,6 +1155,18 @@ mod test {
assert_eq!(LLVMType::i32.store_size_of(&dl()), 32);
}

#[test]
fn calculates_correct_size_for_i40() {
assert_eq!(LLVMType::i40.size_of(&dl()), 40);
assert_eq!(LLVMType::i40.store_size_of(&dl()), 40);
}

#[test]
fn calculates_correct_size_for_i48() {
assert_eq!(LLVMType::i48.size_of(&dl()), 48);
assert_eq!(LLVMType::i48.store_size_of(&dl()), 48);
}

#[test]
fn calculates_correct_size_for_i64() {
assert_eq!(LLVMType::i64.size_of(&dl()), 64);
Expand Down Expand Up @@ -1252,6 +1286,18 @@ mod test {
assert_eq!(LLVMType::i32.align_of(Preferred, &dl()), 32);
}

#[test]
fn calculates_correct_alignment_for_i40() {
assert_eq!(LLVMType::i40.align_of(ABI, &dl()), 64);
assert_eq!(LLVMType::i40.align_of(Preferred, &dl()), 64);
}

#[test]
fn calculates_correct_alignment_for_i48() {
assert_eq!(LLVMType::i48.align_of(ABI, &dl()), 64);
assert_eq!(LLVMType::i48.align_of(Preferred, &dl()), 64);
}

#[test]
fn calculates_correct_alignment_for_i64() {
assert_eq!(LLVMType::i64.align_of(ABI, &dl()), 64);
Expand Down
32 changes: 29 additions & 3 deletions crates/compiler/src/obj_gen/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ impl ObjectContext {
LLVMType::i16 => Type::Signed16,
LLVMType::i24 => Type::Signed24,
LLVMType::i32 => Type::Signed32,
LLVMType::i40 => Type::Signed40,
LLVMType::i48 => Type::Signed48,
LLVMType::i64 => Type::Signed64,
LLVMType::i128 => Type::Signed128,
LLVMType::f16 => Err(Error::invalid_type_conversion(
Expand Down Expand Up @@ -472,15 +474,39 @@ impl FunctionContext {

/// Register the variable with the provided `id`, `name`, and `typ` into the
/// function context.
///
/// # Panics
///
/// - If an attempt is made to re-register a block for a given `name`. This
/// only occurs with `debug_assertions` enabled.
pub fn register_local(&mut self, id: VariableId, name: &str, typ: Type) {
self.locals.insert(name.to_string(), id);
// In debug builds we run a consistency check here.
if cfg!(debug_assertions) {
if let Some(prior) = self.locals.insert(name.to_string(), id) {
panic!("Variable {name} was re-registered, substituting {prior} for {id}");
}
} else {
self.locals.insert(name.to_string(), id);
}
self.var_types.insert(id, typ);
}

/// Register the block with the provided `id` and `name` into the function
/// context.
///
/// # Panics
///
/// - If an attempt is made to re-register a block for a given `name`. This
/// only occurs with `debug_assertions` enabled.
pub fn register_block(&mut self, id: BlockId, name: &str) {
self.blocks.insert(name.to_string(), id);
// In debug builds we run a consistency check here.
if cfg!(debug_assertions) {
if let Some(prior) = self.blocks.insert(name.to_string(), id) {
panic!("Block {name} was re-registered, substituting {prior} for {id}")
}
} else {
self.blocks.insert(name.to_string(), id);
}
}

/// Gets a reference to a mapping between the names and identifiers of the
Expand Down Expand Up @@ -526,6 +552,6 @@ impl FreshNameSupply {
pub fn allocate(&mut self) -> String {
let name_num = self.name_counter;
self.name_counter += 1;
name_num.to_string()
format!("anon_{name_num}")
}
}
Loading

0 comments on commit 3a774f1

Please sign in to comment.