ndstrides: [1] Introduce models #509

Closed
lyken wants to merge 1 commits from ndstrides-1-model into ndstrides
10 changed files with 1622 additions and 0 deletions

View File

@ -48,6 +48,7 @@ pub mod extern_fns;
mod generator;
pub mod irrt;
pub mod llvm_intrinsics;
pub mod model;
pub mod numpy;
pub mod stmt;

View File

@ -0,0 +1,41 @@
use inkwell::{
context::Context,
types::{BasicType, BasicTypeEnum},
values::BasicValueEnum,
};
use super::*;
use crate::codegen::CodeGenerator;
/// A [`Model`] of any [`BasicTypeEnum`].
///
/// Use this when it is infeasible to use model abstractions.
#[derive(Debug, Clone, Copy)]
pub struct Any<'ctx>(pub BasicTypeEnum<'ctx>);
impl<'ctx> Model<'ctx> for Any<'ctx> {
type Value = BasicValueEnum<'ctx>;
type Type = BasicTypeEnum<'ctx>;
fn llvm_type<G: CodeGenerator + ?Sized>(
&self,
_generator: &G,
_ctx: &'ctx Context,
) -> Self::Type {
self.0
}
fn check_type<T: BasicType<'ctx>, G: CodeGenerator + ?Sized>(
&self,
_generator: &mut G,
_ctx: &'ctx Context,
ty: T,
) -> Result<(), ModelError> {
let ty = ty.as_basic_type_enum();
if ty == self.0 {
Ok(())
} else {
Err(ModelError(format!("Expecting {}, but got {}", self.0, ty)))
}
}
}

View File

@ -0,0 +1,146 @@
use std::fmt;
use inkwell::{
context::Context,
types::{ArrayType, BasicType, BasicTypeEnum},
values::{ArrayValue, IntValue},
};
use super::*;
use crate::codegen::{CodeGenContext, CodeGenerator};
/// Trait for Rust structs identifying length values for [`Array`].
pub trait ArrayLen: fmt::Debug + Clone + Copy {
fn length(&self) -> u32;
derppening marked this conversation as resolved Outdated

If this is only specific to Array (and not other kinds of containers), it might be better if it's named as ArrayLen.

If this is only specific to `Array` (and not other kinds of containers), it might be better if it's named as `ArrayLen`.
}
derppening marked this conversation as resolved Outdated

I think you can just name it as length().

I think you can just name it as `length()`.
/// A statically known length.
#[derive(Debug, Clone, Copy, Default)]
pub struct Len<const N: u32>;
/// A dynamically known length.
#[derive(Debug, Clone, Copy)]
pub struct AnyLen(pub u32);
impl<const N: u32> ArrayLen for Len<N> {
fn length(&self) -> u32 {
N
}
}
impl ArrayLen for AnyLen {
fn length(&self) -> u32 {
self.0
}
}
/// A Model for an [`ArrayType`].
///
/// `Len` should be of a [`LenKind`] and `Item` should be a of [`Model`].
#[derive(Debug, Clone, Copy, Default)]
pub struct Array<Len, Item> {
/// Length of this array.

Why are the type variables not directly bounded by LenKind and Model respectively then, rather than only bounding the impls?

Why are the type variables not directly bounded by `LenKind` and `Model` respectively then, rather than only bounding the `impl`s?
Outdated
Review

Bounding say Item: Model<'ctx> requires Array itself to also have 'ctx, then a _PhantomData.

Bounding say `Item: Model<'ctx>` requires `Array` itself to also have `'ctx`, then a `_PhantomData`.

PhantomData might be worth it in this case, because the entire Array class is bounded by the aforementioned type bounds, not just the specific impls.

`PhantomData` might be worth it in this case, because the entire `Array` class is bounded by the aforementioned type bounds, not just the specific `impl`s.
Outdated
Review

I am not sure if this is a good idea. I intuitively think it is less risky by keeping the generics as lax as possible even if it seems very appealing to constraint the struct, though I only first touched Rust 3 months ago.

I was trying to foresee if there will be any super wacky unfixable because-of-the-Rust-type-system-and-its-limitations issue that will crop up in the future because we are over-traiting, but honestly I can't tell.

Also a minor drawback is that 'ctx is now everywhere when you write Array<'ctx, X, Y>, and this will include Int, Float, Struct and Ptr.

Though it can prevent confusions such as "why my Array<Len<3>, Int32> is not a model?? Oh, I have to put Int<Int32>" which is quite nice.

Leaving everything as it is also does not seem to have any drawbacks in my naive opinion.

I am not sure if this is a good idea. I intuitively think it is less risky by keeping the generics as lax as possible even if it seems very appealing to constraint the struct, though I only first touched Rust 3 months ago. I was trying to foresee if there will be any super wacky unfixable because-of-the-Rust-type-system-and-its-limitations issue that will crop up in the future because we are over-traiting, but honestly I can't tell. Also a minor drawback is that `'ctx` is now everywhere when you write `Array<'ctx, X, Y>`, and this will include `Int`, `Float`, `Struct` and `Ptr`. Though it can prevent confusions such as "why my `Array<Len<3>, Int32>` is not a model?? Oh, I have to put `Int<Int32>`" which is quite nice. Leaving everything as it is also does not seem to have any drawbacks in my naive opinion.

I wouldn't be too concerned with using PhantomData here. I'd rather the type bounds of the struct be explicit so that users of this class do not misuse the fields.

In fact, we should also introduce a new function here and make len and item non-pub imo.

I wouldn't be too concerned with using `PhantomData` here. I'd rather the type bounds of the struct be explicit so that users of this class do not misuse the fields. In fact, we should also introduce a `new` function here and make `len` and `item` non-`pub` imo.
pub len: Len,
/// [`Model`] of the array items.
pub item: Item,
}
impl<'ctx, Len: ArrayLen, Item: Model<'ctx>> Model<'ctx> for Array<Len, Item> {
type Value = ArrayValue<'ctx>;
type Type = ArrayType<'ctx>;
fn llvm_type<G: CodeGenerator + ?Sized>(
&self,
generator: &G,
ctx: &'ctx Context,
) -> Self::Type {
self.item.llvm_type(generator, ctx).array_type(self.len.length())
}
fn check_type<T: BasicType<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
ty: T,
) -> Result<(), ModelError> {
let ty = ty.as_basic_type_enum();
let BasicTypeEnum::ArrayType(ty) = ty else {
return Err(ModelError(format!("Expecting ArrayType, but got {ty:?}")));
};
if ty.len() != self.len.length() {
return Err(ModelError(format!(
"Expecting ArrayType with size {}, but got an ArrayType with size {}",
ty.len(),
self.len.length()
)));
}
self.item
.check_type(generator, ctx, ty.get_element_type())
.map_err(|err| err.under_context("an ArrayType"))?;
Ok(())
}
}
impl<'ctx, Len: ArrayLen, Item: Model<'ctx>> Instance<'ctx, Ptr<Array<Len, Item>>> {
/// Get the pointer to the `i`-th (0-based) array element.
pub fn gep(
&self,
ctx: &CodeGenContext<'ctx, '_>,

Pretty sure this can be i64/usize - LLVM docs:

When indexing into an array, pointer or vector, integers of any width are allowed, and they are not required to be constant. These integers are treated as signed values where relevant.

Change this for the other array stuff as well.

Pretty sure this can be `i64`/`usize` - [LLVM docs](https://releases.llvm.org/14.0.0/docs/LangRef.html#getelementptr-instruction): > When indexing into an array, pointer or vector, integers of any width are allowed, and they are not required to be constant. These integers are treated as signed values where relevant. Change this for the other array stuff as well.
Outdated
Review

I am not sure what should be changed for line 88-94.

You mean let zero = <something else?>

I am not sure what should be changed for line 88-94. You mean `let zero = <something else?>`
let zero = ctx.ctx.i64_type().const_zero();
```rs let zero = ctx.ctx.i64_type().const_zero(); ```
i: IntValue<'ctx>,
) -> Instance<'ctx, Ptr<Item>> {
let zero = ctx.ctx.i32_type().const_zero();
let ptr = unsafe { ctx.builder.build_in_bounds_gep(self.value, &[zero, i], "").unwrap() };
unsafe { Ptr(self.model.0.item).believe_value(ptr) }
}

i64 - See above.

`i64` - See above.
Outdated
Review

I am confused why we should use i: i64 in gep_const.

Shouldn't it be the case that 0 <= i < self.model.0.len.length()? even if indexing with a negative index is technically possible.

I am confused why we should use `i: i64` in `gep_const`. Shouldn't it be the case that `0 <= i < self.model.0.len.length()`? even if indexing with a negative index is technically possible.
/// Like `gep` but `i` is a constant.
pub fn gep_const(&self, ctx: &CodeGenContext<'ctx, '_>, i: u64) -> Instance<'ctx, Ptr<Item>> {
assert!(
i < u64::from(self.model.0.len.length()),
"Index {i} is out of bounds. Array length = {}",
self.model.0.len.length()
https://git.m-labs.hk/M-Labs/nac3/pulls/509/files#issuecomment-11752
);
let i = ctx.ctx.i32_type().const_int(i, false);
self.gep(ctx, i)
}
/// Convenience function equivalent to `.gep(...).load(...)`.
pub fn get<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
i: IntValue<'ctx>,
) -> Instance<'ctx, Item> {
self.gep(ctx, i).load(generator, ctx)
}
/// Like `get` but `i` is a constant.
pub fn get_const<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
i: u64,
) -> Instance<'ctx, Item> {
self.gep_const(ctx, i).load(generator, ctx)
}
/// Convenience function equivalent to `.gep(...).store(...)`.
pub fn set(
&self,
ctx: &CodeGenContext<'ctx, '_>,
i: IntValue<'ctx>,
value: Instance<'ctx, Item>,
) {
self.gep(ctx, i).store(ctx, value);
}
/// Like `set` but `i` is a constant.
pub fn set_const(&self, ctx: &CodeGenContext<'ctx, '_>, i: u64, value: Instance<'ctx, Item>) {
self.gep_const(ctx, i).store(ctx, value);
}
}

View File

@ -0,0 +1,207 @@
use std::fmt;
use inkwell::{context::Context, types::*, values::*};
use itertools::Itertools;
use super::*;
use crate::codegen::{CodeGenContext, CodeGenerator};
/// A error type for reporting any [`Model`]-related error (e.g., a [`BasicType`] mismatch).
#[derive(Debug, Clone)]
pub struct ModelError(pub String);
impl ModelError {
/// Append a context message to the error.
derppening marked this conversation as resolved Outdated

nit: ///

nit: `///`
pub(super) fn under_context(mut self, context: &str) -> Self {
self.0.push_str(" ... in ");
self.0.push_str(context);
self
}
}
/// Trait for Rust structs identifying [`BasicType`]s in the context of a known [`CodeGenerator`] and [`CodeGenContext`].
///
/// For instance,
/// - [`Int<Int32>`] identifies an [`IntType`] with 32-bits.
/// - [`Int<SizeT>`] identifies an [`IntType`] with bit-width [`CodeGenerator::get_size_type`].
/// - [`Ptr<Int<SizeT>>`] identifies a [`PointerType`] that points to an [`IntType`] with bit-width [`CodeGenerator::get_size_type`].
/// - [`Int<AnyInt>`] identifies an [`IntType`] with bit-width of whatever is set in the [`AnyInt`] object.
/// - [`Any`] identifies a [`BasicType`] set in the [`Any`] object itself.
///
/// You can get the [`BasicType`] out of a model with [`Model::get_type`].
///
/// Furthermore, [`Instance<'ctx, M>`] is a simple structure that carries a [`BasicValue`] with [`BasicType`] identified by model `M`.
///
/// The main purpose of this abstraction is to have a more Rust type-safe way to use Inkwell and give type-hints for programmers.
///
/// ### Notes on `Default` trait
///
/// For some models like [`Int<Int32>`] or [`Int<SizeT>`], they have a [`Default`] trait since just by looking at their types, it is possible
/// to tell the [`BasicType`]s they are identifying.
///
/// This can be used to create strongly-typed interfaces accepting only values of a specific [`BasicType`] without having to worry about
/// writing debug assertions to check, for example, if the programmer has passed in an [`IntValue`] with the wrong bit-width.
/// ```ignore
/// fn give_me_i32_and_get_a_size_t_back<'ctx>(i32: Instance<'ctx, Int<Int32>>) -> Instance<'ctx, Int<SizeT>> {
/// // code...
/// }
/// ```
///
/// ### Notes on converting between Inkwell and model/ge.
///
/// Suppose you have an [`IntValue`], and you want to pass it into a function that takes a [`Instance<'ctx, Int<Int32>>`]. You can do use
/// [`Model::check_value`] or [`Model::believe_value`].
/// ```ignore
/// let my_value: IntValue<'ctx>;
///
/// let my_value = Int(Int32).check_value(my_value).unwrap(); // Panics if `my_value` is not 32-bit with a descriptive error message.
///
/// // or, if you are absolutely certain that `my_value` is 32-bit and doing extra checks is a waste of time:
/// let my_value = Int(Int32).believe_value(my_value);
/// ```
pub trait Model<'ctx>: fmt::Debug + Clone + Copy {
/// The [`BasicType`] *variant* this model is identifying.
type Type: BasicType<'ctx>;
/// The [`BasicValue`] type of the [`BasicType`] of this model.
type Value: BasicValue<'ctx> + TryFrom<BasicValueEnum<'ctx>>;
/// Return the [`BasicType`] of this model.
#[must_use]
fn llvm_type<G: CodeGenerator + ?Sized>(&self, generator: &G, ctx: &'ctx Context)
derppening marked this conversation as resolved Outdated

I would suggest naming it llvm_type. Use of get_ is discouraged in Rust AFAIK.

https://rust-lang.github.io/api-guidelines/naming.html#getter-names-follow-rust-convention-c-getter

I would suggest naming it `llvm_type`. Use of `get_` is discouraged in Rust AFAIK. https://rust-lang.github.io/api-guidelines/naming.html#getter-names-follow-rust-convention-c-getter
-> Self::Type;
/// Get the number of bytes of the [`BasicType`] of this model.
derppening marked this conversation as resolved Outdated

I would suggest following Inkwell conventions - size_of.

I would suggest following Inkwell conventions - `size_of`.
fn size_of<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
) -> IntValue<'ctx> {
self.llvm_type(generator, ctx).size_of().unwrap()
}
/// Check if a [`BasicType`] matches the [`BasicType`] of this model.
fn check_type<T: BasicType<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
ty: T,
) -> Result<(), ModelError>;
/// Create an instance from a value.
///
/// # Safety
///
derppening marked this conversation as resolved Outdated

I suspect this might be a candidate for unsafe, as it is a requirement for the caller to enforce this contract.,

I suspect this might be a candidate for `unsafe`, as it is a requirement for the caller to enforce this contract.,
/// Caller must make sure the type of `value` and the type of this `model` are equivalent.
#[must_use]
unsafe fn believe_value(&self, value: Self::Value) -> Instance<'ctx, Self> {
Instance { model: *self, value }
}
/// Check if a [`BasicValue`]'s type is equivalent to the type of this model.
/// Wrap the [`BasicValue`] into an [`Instance`] if it is.
fn check_value<V: BasicValue<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
value: V,
) -> Result<Instance<'ctx, Self>, ModelError> {
let value = value.as_basic_value_enum();
self.check_type(generator, ctx, value.get_type())
.map_err(|err| err.under_context(format!("the value {value:?}").as_str()))?;
let Ok(value) = Self::Value::try_from(value) else {
unreachable!("check_type() has bad implementation")
};
unsafe { Ok(self.believe_value(value)) }
}
// Allocate a value on the stack and return its pointer.
fn alloca<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
) -> Instance<'ctx, Ptr<Self>> {
let p = ctx.builder.build_alloca(self.llvm_type(generator, ctx.ctx), "").unwrap();
unsafe { Ptr(*self).believe_value(p) }
}
// Allocate an array on the stack and return its pointer.
fn array_alloca<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
len: IntValue<'ctx>,
) -> Instance<'ctx, Ptr<Self>> {
let p =
ctx.builder.build_array_alloca(self.llvm_type(generator, ctx.ctx), len, "").unwrap();
unsafe { Ptr(*self).believe_value(p) }
}
fn var_alloca<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&str>,
) -> Result<Instance<'ctx, Ptr<Self>>, String> {
let ty = self.llvm_type(generator, ctx.ctx).as_basic_type_enum();
let p = generator.gen_var_alloc(ctx, ty, name)?;
unsafe { Ok(Ptr(*self).believe_value(p)) }
}
fn array_var_alloca<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
derppening marked this conversation as resolved Outdated

Are there any types in Model that distinguishes between an array alloca and alloca? I think it would be preferable to have something like an ArrayPtr to replace the type of an ArraySliceValue.

Are there any types in `Model` that distinguishes between an array alloca and alloca? I think it would be preferable to have something like an `ArrayPtr` to replace the type of an `ArraySliceValue`.
Outdated
Review

No. An array alloc's length is never coupled with the pointer.

I was doing something like ArraySliceValue before, but the length value didn't have much use and often ends up being redundant.

No. An array alloc's length is never coupled with the pointer. I was doing something like `ArraySliceValue` before, but the length value didn't have much use and often ends up being redundant.
len: IntValue<'ctx>,
name: Option<&'ctx str>,
) -> Result<Instance<'ctx, Ptr<Self>>, String> {
// TODO: Remove ArraySliceValue
let ty = self.llvm_type(generator, ctx.ctx).as_basic_type_enum();
let p = generator.gen_array_var_alloc(ctx, ty, len, name)?;
unsafe { Ok(Ptr(*self).believe_value(PointerValue::from(p))) }
}
/// Allocate a constant array.
fn const_array<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
values: &[Instance<'ctx, Self>],
) -> Instance<'ctx, Array<AnyLen, Self>> {
macro_rules! make {
($t:expr, $into_value:expr) => {
$t.const_array(
&values
.iter()
.map(|x| $into_value(x.value.as_basic_value_enum()))
.collect_vec(),
)
};
}
let value = match self.llvm_type(generator, ctx).as_basic_type_enum() {
BasicTypeEnum::ArrayType(t) => make!(t, BasicValueEnum::into_array_value),
BasicTypeEnum::IntType(t) => make!(t, BasicValueEnum::into_int_value),
BasicTypeEnum::FloatType(t) => make!(t, BasicValueEnum::into_float_value),
BasicTypeEnum::PointerType(t) => make!(t, BasicValueEnum::into_pointer_value),
BasicTypeEnum::StructType(t) => make!(t, BasicValueEnum::into_struct_value),
BasicTypeEnum::VectorType(t) => make!(t, BasicValueEnum::into_vector_value),
};
Array { len: AnyLen(values.len() as u32), item: *self }
.check_value(generator, ctx, value)
.unwrap()
}
}
#[derive(Debug, Clone, Copy)]
derppening marked this conversation as resolved Outdated

nit: newline between fields

nit: newline between fields
pub struct Instance<'ctx, M: Model<'ctx>> {
/// The model of this instance.
pub model: M,
/// The value of this instance.
///
/// It is guaranteed the [`BasicType`] of `value` is consistent with that of `model`.
pub value: M::Value,
}

View File

@ -0,0 +1,93 @@
use std::fmt;
use inkwell::{
context::Context,
types::{BasicType, FloatType},
values::FloatValue,
};
use super::*;
use crate::codegen::CodeGenerator;
pub trait FloatKind<'ctx>: fmt::Debug + Clone + Copy {
fn get_float_type<G: CodeGenerator + ?Sized>(
&self,
generator: &G,
ctx: &'ctx Context,
) -> FloatType<'ctx>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Float32;
#[derive(Debug, Clone, Copy, Default)]
pub struct Float64;
impl<'ctx> FloatKind<'ctx> for Float32 {
fn get_float_type<G: CodeGenerator + ?Sized>(
&self,
_generator: &G,
ctx: &'ctx Context,
) -> FloatType<'ctx> {
ctx.f32_type()
}
}
impl<'ctx> FloatKind<'ctx> for Float64 {
fn get_float_type<G: CodeGenerator + ?Sized>(
&self,
_generator: &G,
ctx: &'ctx Context,
) -> FloatType<'ctx> {
ctx.f64_type()
}
}
#[derive(Debug, Clone, Copy)]
pub struct AnyFloat<'ctx>(FloatType<'ctx>);
impl<'ctx> FloatKind<'ctx> for AnyFloat<'ctx> {
fn get_float_type<G: CodeGenerator + ?Sized>(
&self,
_generator: &G,
_ctx: &'ctx Context,
) -> FloatType<'ctx> {
self.0
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Float<N>(pub N);

Same as Array, bound N to FloatKind here as well.

Same as `Array`, bound `N` to `FloatKind` here as well.
impl<'ctx, N: FloatKind<'ctx>> Model<'ctx> for Float<N> {
type Value = FloatValue<'ctx>;
type Type = FloatType<'ctx>;
fn llvm_type<G: CodeGenerator + ?Sized>(
&self,
generator: &G,
ctx: &'ctx Context,
) -> Self::Type {
self.0.get_float_type(generator, ctx)
}
fn check_type<T: BasicType<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
ty: T,
) -> Result<(), ModelError> {
let ty = ty.as_basic_type_enum();
let Ok(ty) = FloatType::try_from(ty) else {
return Err(ModelError(format!("Expecting FloatType, but got {ty:?}")));
};
derppening marked this conversation as resolved Outdated

No it does not, it has a limited set of supporting fp types.

No it does not, it has a limited set of supporting fp types.
let exp_ty = self.0.get_float_type(generator, ctx);
// TODO: Inkwell does not have get_bit_width for FloatType?
if ty != exp_ty {
return Err(ModelError(format!("Expecting {exp_ty:?}, but got {ty:?}")));
}
Ok(())
}
}

View File

@ -0,0 +1,121 @@
use inkwell::{
attributes::{Attribute, AttributeLoc},
types::{BasicMetadataTypeEnum, BasicType, FunctionType},
values::{AnyValue, BasicMetadataValueEnum, BasicValue, BasicValueEnum, CallSiteValue},
};
use itertools::Itertools;
use super::*;
use crate::codegen::{CodeGenContext, CodeGenerator};
#[derive(Debug, Clone, Copy)]
struct Arg<'ctx> {
ty: BasicMetadataTypeEnum<'ctx>,
val: BasicMetadataValueEnum<'ctx>,
}
/// A convenience structure to construct & call an LLVM function.
///
/// ### Usage
///
/// The syntax is like this:
/// ```ignore
/// let result = CallFunction::begin("my_function_name")
/// .attrs(...)
/// .arg(arg1)
/// .arg(arg2)
/// .arg(arg3)
/// .returning("my_function_result", Int32);
/// ```
///
/// The function `my_function_name` is called when `.returning()` (or its variants) is called, returning
/// the result as an `Instance<'ctx, Int<Int32>>`.
///
/// If `my_function_name` has not been declared in `ctx.module`, once `.returning()` is called, a function
/// declaration of `my_function_name` is added to `ctx.module`, where the [`FunctionType`] is deduced from
/// the argument types and returning type.
pub struct FnCall<'ctx, 'a, 'b, 'c, 'd, G: CodeGenerator + ?Sized> {
generator: &'d mut G,
derppening marked this conversation as resolved Outdated

I think FunctionCall or FuncCall might be a better name.

I think `FunctionCall` or `FuncCall` might be a better name.
Outdated
Review

Sure

Sure
ctx: &'b CodeGenContext<'ctx, 'a>,
/// Function name
name: &'c str,
/// Call arguments
args: Vec<Arg<'ctx>>,
/// LLVM function Attributes
attrs: Vec<&'static str>,
}
impl<'ctx, 'a, 'b, 'c, 'd, G: CodeGenerator + ?Sized> FnCall<'ctx, 'a, 'b, 'c, 'd, G> {
pub fn builder(generator: &'d mut G, ctx: &'b CodeGenContext<'ctx, 'a>, name: &'c str) -> Self {
FnCall { generator, ctx, name, args: Vec::new(), attrs: Vec::new() }

I would name this builder instead.

I would name this `builder` instead.
Outdated
Review

Unrelated: Is there any way to improve the situation about the lifetime signature for CallFunction? or is this unavoidable.

Unrelated: Is there any way to improve the situation about the lifetime signature for `CallFunction`? or is this unavoidable.

I did some testing and this might work:

pub struct CallFunction<'ctx, 'a, 'b, G: CodeGenerator + ?Sized> {
    generator: &'b mut G,
    ctx: &'b CodeGenContext<'ctx, 'a>,
    name: &'b str,  // Or alternatively String
    args: Vec<Arg<'ctx>>,
    attrs: Vec<&'static str>,
}

AFAICT this will work as long as the CallFunction instance is used in the same scope as it is declared, or name's lifetime extends until returning is invoked.

I did some testing and this might work: ```rs pub struct CallFunction<'ctx, 'a, 'b, G: CodeGenerator + ?Sized> { generator: &'b mut G, ctx: &'b CodeGenContext<'ctx, 'a>, name: &'b str, // Or alternatively String args: Vec<Arg<'ctx>>, attrs: Vec<&'static str>, } ``` AFAICT this will work as long as the `CallFunction` instance is used in the same scope as it is declared, or `name`'s lifetime extends until `returning` is invoked.
}
/// Push a list of LLVM function attributes to the function declaration.
#[must_use]
pub fn attrs(mut self, attrs: Vec<&'static str>) -> Self {
self.attrs = attrs;
self
}
/// Push a call argument to the function call.
#[allow(clippy::needless_pass_by_value)]
#[must_use]
pub fn arg<M: Model<'ctx>>(mut self, arg: Instance<'ctx, M>) -> Self {
let arg = Arg {
ty: arg.model.llvm_type(self.generator, self.ctx.ctx).as_basic_type_enum().into(),
val: arg.value.as_basic_value_enum().into(),
};
self.args.push(arg);
self
}
/// Call the function and expect the function to return a value of type of `return_model`.
#[must_use]
pub fn returning<M: Model<'ctx>>(self, name: &str, return_model: M) -> Instance<'ctx, M> {
let ret_ty = return_model.llvm_type(self.generator, self.ctx.ctx);

I don't really agree with the naming of the returning family of functions, as it doesn't indicate to the caller that by invoking returning it actually invokes the function as well.

I would suggest adding the return name and model to the begin function as an Option<(&str, Option<M>)>.

I don't really agree with the naming of the `returning` family of functions, as it doesn't indicate to the caller that by invoking `returning` it actually invokes the function as well. I would suggest adding the return name and model to the `begin` function as an `Option<(&str, Option<M>)>`.
Outdated
Review

Passing Option<(&str, Option<M>)> to begin to define the return type may not be a good idea - If the function returns nothing, there is no type for M and Rust complains.

Passing `Option<(&str, Option<M>)>` to `begin` to define the return type may not be a good idea - If the function returns nothing, there is no type for `M` and Rust complains.

What about two functions, one that creates a void func and another that creates a value-returning func?

What about two functions, one that creates a void func and another that creates a value-returning func?
Outdated
Review

If you are talking about something like begin_void and begin_with_return, sadly no.

It requires the return model M to somehow be propagated through the .arg().arg().arg() chain until, say, .finish_and_call() consuming the FnCall and returning an Instance<'ctx, M>.

This means that FnCall itself would have to hold M in its type.

So in the end M is still there even for begin_void, and there is no type for M.

If you are talking about something like `begin_void` and `begin_with_return`, sadly no. It requires the return model `M` to somehow be propagated through the `.arg().arg().arg()` chain until, say, `.finish_and_call()` consuming the `FnCall` and returning an `Instance<'ctx, M>`. This means that `FnCall` itself would have to hold `M` in its type. So in the end `M` is still there even for `begin_void`, and there is no type for `M`.
let ret = self.call(|tys| ret_ty.fn_type(tys, false), name);
let ret = BasicValueEnum::try_from(ret.as_any_value_enum()).unwrap(); // Must work
let ret = return_model.check_value(self.generator, self.ctx.ctx, ret).unwrap(); // Must work
ret
}
/// Like [`CallFunction::returning_`] but `return_model` is automatically inferred.
#[must_use]
pub fn returning_auto<M: Model<'ctx> + Default>(self, name: &str) -> Instance<'ctx, M> {
self.returning(name, M::default())
}
/// Call the function and expect the function to return a void-type.
pub fn returning_void(self) {
let ret_ty = self.ctx.ctx.void_type();
let _ = self.call(|tys| ret_ty.fn_type(tys, false), "");
}
fn call<F>(&self, make_fn_type: F, return_value_name: &str) -> CallSiteValue<'ctx>
where
F: FnOnce(&[BasicMetadataTypeEnum<'ctx>]) -> FunctionType<'ctx>,
{
// Get the LLVM function.
let func = self.ctx.module.get_function(self.name).unwrap_or_else(|| {
// Declare the function if it doesn't exist.
let tys = self.args.iter().map(|arg| arg.ty).collect_vec();
let func_type = make_fn_type(&tys);
let func = self.ctx.module.add_function(self.name, func_type, None);
for attr in &self.attrs {
func.add_attribute(
AttributeLoc::Function,
self.ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0),
);
}
func
});
let vals = self.args.iter().map(|arg| arg.val).collect_vec();
self.ctx.builder.build_call(func, &vals, return_value_name).unwrap()
}
}

View File

@ -0,0 +1,421 @@
use std::{cmp::Ordering, fmt};
use inkwell::{
context::Context,
types::{BasicType, IntType},
values::IntValue,
IntPredicate,
};
use super::*;
use crate::codegen::{CodeGenContext, CodeGenerator};
pub trait IntKind<'ctx>: fmt::Debug + Clone + Copy {
fn get_int_type<G: CodeGenerator + ?Sized>(
&self,
generator: &G,
ctx: &'ctx Context,
) -> IntType<'ctx>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Bool;
#[derive(Debug, Clone, Copy, Default)]
pub struct Byte;
#[derive(Debug, Clone, Copy, Default)]
pub struct Int32;
#[derive(Debug, Clone, Copy, Default)]
pub struct Int64;
#[derive(Debug, Clone, Copy, Default)]
pub struct SizeT;
impl<'ctx> IntKind<'ctx> for Bool {
fn get_int_type<G: CodeGenerator + ?Sized>(
&self,
_generator: &G,
ctx: &'ctx Context,
) -> IntType<'ctx> {
ctx.bool_type()
}
}
impl<'ctx> IntKind<'ctx> for Byte {
fn get_int_type<G: CodeGenerator + ?Sized>(
&self,
_generator: &G,
ctx: &'ctx Context,
) -> IntType<'ctx> {
ctx.i8_type()
}
}
impl<'ctx> IntKind<'ctx> for Int32 {
fn get_int_type<G: CodeGenerator + ?Sized>(
&self,
_generator: &G,
ctx: &'ctx Context,
) -> IntType<'ctx> {
ctx.i32_type()
}
}
impl<'ctx> IntKind<'ctx> for Int64 {
fn get_int_type<G: CodeGenerator + ?Sized>(
&self,
_generator: &G,
ctx: &'ctx Context,
) -> IntType<'ctx> {
ctx.i64_type()
}
}
impl<'ctx> IntKind<'ctx> for SizeT {
fn get_int_type<G: CodeGenerator + ?Sized>(
&self,
generator: &G,
ctx: &'ctx Context,
) -> IntType<'ctx> {
generator.get_size_type(ctx)
}
}
#[derive(Debug, Clone, Copy)]
pub struct AnyInt<'ctx>(pub IntType<'ctx>);
impl<'ctx> IntKind<'ctx> for AnyInt<'ctx> {
fn get_int_type<G: CodeGenerator + ?Sized>(
&self,
_generator: &G,
_ctx: &'ctx Context,
) -> IntType<'ctx> {
self.0
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Int<N>(pub N);

Same as with Float and Array.

Same as with `Float` and `Array`.
impl<'ctx, N: IntKind<'ctx>> Model<'ctx> for Int<N> {
type Value = IntValue<'ctx>;
type Type = IntType<'ctx>;
fn llvm_type<G: CodeGenerator + ?Sized>(
&self,
generator: &G,
ctx: &'ctx Context,
) -> Self::Type {
self.0.get_int_type(generator, ctx)
}
fn check_type<T: BasicType<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
ty: T,
) -> Result<(), ModelError> {
let ty = ty.as_basic_type_enum();
let Ok(ty) = IntType::try_from(ty) else {
return Err(ModelError(format!("Expecting IntType, but got {ty:?}")));
};
let exp_ty = self.0.get_int_type(generator, ctx);
if ty.get_bit_width() != exp_ty.get_bit_width() {
return Err(ModelError(format!(
"Expecting IntType to have {} bit(s), but got {} bit(s)",
exp_ty.get_bit_width(),
ty.get_bit_width()
)));
}
Ok(())
}
}
derppening marked this conversation as resolved Outdated

What if I want a signed constant then?

What if I want a signed constant then?
Outdated
Review

Oh I forgot to add another interface for it.

Oh I forgot to add another interface for it.
impl<'ctx, N: IntKind<'ctx>> Int<N> {
pub fn const_int<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
value: u64,
sign_extend: bool,
) -> Instance<'ctx, Self> {
let value = self.llvm_type(generator, ctx).const_int(value, sign_extend);
unsafe { self.believe_value(value) }
}
pub fn const_0<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
) -> Instance<'ctx, Self> {
let value = self.llvm_type(generator, ctx).const_zero();
unsafe { self.believe_value(value) }
}
pub fn const_1<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
) -> Instance<'ctx, Self> {
self.const_int(generator, ctx, 1, false)
}
pub fn const_all_ones<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
) -> Instance<'ctx, Self> {
let value = self.llvm_type(generator, ctx).const_all_ones();
unsafe { self.believe_value(value) }
}
pub fn s_extend_or_bit_cast<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
value: IntValue<'ctx>,
) -> Instance<'ctx, Self> {
assert!(
value.get_type().get_bit_width()
<= self.0.get_int_type(generator, ctx.ctx).get_bit_width()
);
let value = ctx
.builder
.build_int_s_extend_or_bit_cast(value, self.llvm_type(generator, ctx.ctx), "")
.unwrap();
unsafe { self.believe_value(value) }
}
pub fn s_extend<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
value: IntValue<'ctx>,
) -> Instance<'ctx, Self> {
assert!(
value.get_type().get_bit_width()
< self.0.get_int_type(generator, ctx.ctx).get_bit_width()
);
let value =
ctx.builder.build_int_s_extend(value, self.llvm_type(generator, ctx.ctx), "").unwrap();
unsafe { self.believe_value(value) }
}
pub fn z_extend_or_bit_cast<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
value: IntValue<'ctx>,
) -> Instance<'ctx, Self> {
assert!(
value.get_type().get_bit_width()
<= self.0.get_int_type(generator, ctx.ctx).get_bit_width()
);
let value = ctx
.builder
.build_int_z_extend_or_bit_cast(value, self.llvm_type(generator, ctx.ctx), "")
.unwrap();
unsafe { self.believe_value(value) }
}
pub fn z_extend<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
value: IntValue<'ctx>,
) -> Instance<'ctx, Self> {
assert!(
value.get_type().get_bit_width()
< self.0.get_int_type(generator, ctx.ctx).get_bit_width()
);
let value =
ctx.builder.build_int_z_extend(value, self.llvm_type(generator, ctx.ctx), "").unwrap();
unsafe { self.believe_value(value) }
}
pub fn truncate_or_bit_cast<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
value: IntValue<'ctx>,
) -> Instance<'ctx, Self> {
assert!(
value.get_type().get_bit_width()
>= self.0.get_int_type(generator, ctx.ctx).get_bit_width()
);
let value = ctx
.builder
.build_int_truncate_or_bit_cast(value, self.llvm_type(generator, ctx.ctx), "")
.unwrap();
unsafe { self.believe_value(value) }
}
pub fn truncate<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
value: IntValue<'ctx>,
) -> Instance<'ctx, Self> {
assert!(
value.get_type().get_bit_width()
> self.0.get_int_type(generator, ctx.ctx).get_bit_width()
);
let value =
ctx.builder.build_int_truncate(value, self.llvm_type(generator, ctx.ctx), "").unwrap();
unsafe { self.believe_value(value) }
}
/// `sext` or `trunc` an int to this model's int type. Does nothing if equal bit-widths.
pub fn s_extend_or_truncate<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
value: IntValue<'ctx>,
) -> Instance<'ctx, Self> {
let their_width = value.get_type().get_bit_width();
let our_width = self.0.get_int_type(generator, ctx.ctx).get_bit_width();
match their_width.cmp(&our_width) {
Ordering::Less => self.s_extend(generator, ctx, value),
Ordering::Equal => unsafe { self.believe_value(value) },
Ordering::Greater => self.truncate(generator, ctx, value),
}
}
/// `zext` or `trunc` an int to this model's int type. Does nothing if equal bit-widths.
pub fn z_extend_or_truncate<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
value: IntValue<'ctx>,
) -> Instance<'ctx, Self> {
let their_width = value.get_type().get_bit_width();
let our_width = self.0.get_int_type(generator, ctx.ctx).get_bit_width();
match their_width.cmp(&our_width) {
Ordering::Less => self.z_extend(generator, ctx, value),
Ordering::Equal => unsafe { self.believe_value(value) },
Ordering::Greater => self.truncate(generator, ctx, value),
}
}
}
impl Int<Bool> {
#[must_use]
pub fn const_false<'ctx, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
) -> Instance<'ctx, Self> {
self.const_int(generator, ctx, 0, false)
}
#[must_use]
pub fn const_true<'ctx, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
) -> Instance<'ctx, Self> {
self.const_int(generator, ctx, 1, false)
}
}
impl<'ctx, N: IntKind<'ctx>> Instance<'ctx, Int<N>> {
pub fn s_extend_or_bit_cast<NewN: IntKind<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
to_int_kind: NewN,
) -> Instance<'ctx, Int<NewN>> {
Int(to_int_kind).s_extend_or_bit_cast(generator, ctx, self.value)
}
pub fn s_extend<NewN: IntKind<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
to_int_kind: NewN,
) -> Instance<'ctx, Int<NewN>> {
Int(to_int_kind).s_extend(generator, ctx, self.value)
}
pub fn z_extend_or_bit_cast<NewN: IntKind<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
to_int_kind: NewN,
) -> Instance<'ctx, Int<NewN>> {
Int(to_int_kind).z_extend_or_bit_cast(generator, ctx, self.value)
}
pub fn z_extend<NewN: IntKind<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
to_int_kind: NewN,
) -> Instance<'ctx, Int<NewN>> {
Int(to_int_kind).z_extend(generator, ctx, self.value)
}
pub fn truncate_or_bit_cast<NewN: IntKind<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
to_int_kind: NewN,
) -> Instance<'ctx, Int<NewN>> {
Int(to_int_kind).truncate_or_bit_cast(generator, ctx, self.value)
}
pub fn truncate<NewN: IntKind<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
to_int_kind: NewN,
) -> Instance<'ctx, Int<NewN>> {
Int(to_int_kind).truncate(generator, ctx, self.value)
}
pub fn s_extend_or_truncate<NewN: IntKind<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
to_int_kind: NewN,
) -> Instance<'ctx, Int<NewN>> {
Int(to_int_kind).s_extend_or_truncate(generator, ctx, self.value)
}
pub fn z_extend_or_truncate<NewN: IntKind<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
to_int_kind: NewN,
) -> Instance<'ctx, Int<NewN>> {
Int(to_int_kind).z_extend_or_truncate(generator, ctx, self.value)

Why are operators provided by some functions but not by others?

Why are operators provided by some functions but not by others?
Outdated
Review

I was adding the ones I needed to implement ndarrays. I figured I should add these ops on demand.

I was adding the ones I needed to implement ndarrays. I figured I should add these ops on demand.
}
#[must_use]
pub fn add(&self, ctx: &CodeGenContext<'ctx, '_>, other: Self) -> Self {
let value = ctx.builder.build_int_add(self.value, other.value, "").unwrap();
unsafe { self.model.believe_value(value) }
}
#[must_use]
pub fn sub(&self, ctx: &CodeGenContext<'ctx, '_>, other: Self) -> Self {
let value = ctx.builder.build_int_sub(self.value, other.value, "").unwrap();
unsafe { self.model.believe_value(value) }
}
#[must_use]
pub fn mul(&self, ctx: &CodeGenContext<'ctx, '_>, other: Self) -> Self {
let value = ctx.builder.build_int_mul(self.value, other.value, "").unwrap();
unsafe { self.model.believe_value(value) }
}
pub fn compare(
&self,
ctx: &CodeGenContext<'ctx, '_>,
op: IntPredicate,
other: Self,
) -> Instance<'ctx, Int<Bool>> {
let value = ctx.builder.build_int_compare(op, self.value, other.value, "").unwrap();
unsafe { Int(Bool).believe_value(value) }
}
}

View File

@ -0,0 +1,16 @@
mod any;
mod array;
mod core;
mod float;
pub mod function;
mod int;
mod ptr;
mod structure;
pub use any::*;
pub use array::*;
pub use core::*;
pub use float::*;
pub use int::*;
pub use ptr::*;
pub use structure::*;

View File

@ -0,0 +1,213 @@
use inkwell::{
context::Context,
types::{BasicType, BasicTypeEnum, PointerType},
values::{IntValue, PointerValue},
AddressSpace,
};
use super::*;
use crate::codegen::{llvm_intrinsics::call_memcpy_generic, CodeGenContext, CodeGenerator};
/// A model for [`PointerType`].
///
/// `Item` is the element type this pointer is pointing to, and should be of a [`Model`].
///
// TODO: LLVM 15: `Item` is a Rust type-hint for the LLVM type of value the `.store()/.load()` family
// of functions return. If a truly opaque pointer is needed, tell the programmer to use `OpaquePtr`.
#[derive(Debug, Clone, Copy, Default)]
pub struct Ptr<Item>(pub Item);
/// An opaque pointer. Like [`Ptr`] but without any Rust type-hints about its element type.
///
/// `.load()/.store()` is not available for [`Instance`]s of opaque pointers.
pub type OpaquePtr = Ptr<()>;
// TODO: LLVM 15: `Item: Model<'ctx>` don't even need to be a model anymore. It will only be
// a type-hint for the `.load()/.store()` functions for the `pointee_ty`.
//
// See https://thedan64.github.io/inkwell/inkwell/builder/struct.Builder.html#method.build_load.
impl<'ctx, Item: Model<'ctx>> Model<'ctx> for Ptr<Item> {
type Value = PointerValue<'ctx>;
type Type = PointerType<'ctx>;
fn llvm_type<G: CodeGenerator + ?Sized>(
&self,
generator: &G,
ctx: &'ctx Context,
) -> Self::Type {
// TODO: LLVM 15: ctx.ptr_type(AddressSpace::default())
self.0.llvm_type(generator, ctx).ptr_type(AddressSpace::default())
}
fn check_type<T: BasicType<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
ty: T,
) -> Result<(), ModelError> {
let ty = ty.as_basic_type_enum();
let Ok(ty) = PointerType::try_from(ty) else {
return Err(ModelError(format!("Expecting PointerType, but got {ty:?}")));
};
let elem_ty = ty.get_element_type();
let Ok(elem_ty) = BasicTypeEnum::try_from(elem_ty) else {
return Err(ModelError(format!(
"Expecting pointer element type to be a BasicTypeEnum, but got {elem_ty:?}"
)));
};
// TODO: inkwell `get_element_type()` will be deprecated.
// Remove the check for `get_element_type()` when the time comes.
self.0
.check_type(generator, ctx, elem_ty)
.map_err(|err| err.under_context("a PointerType"))?;
Ok(())
}
}
impl<'ctx, Item: Model<'ctx>> Ptr<Item> {
/// Return a ***constant*** nullptr.
pub fn nullptr<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
) -> Instance<'ctx, Ptr<Item>> {
let ptr = self.llvm_type(generator, ctx).const_null();
unsafe { self.believe_value(ptr) }
}
/// Cast a pointer into this model with [`inkwell::builder::Builder::build_pointer_cast`]
pub fn pointer_cast<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
ptr: PointerValue<'ctx>,
) -> Instance<'ctx, Ptr<Item>> {
// TODO: LLVM 15: Write in an impl where `Item` does not have to be `Model<'ctx>`.
// TODO: LLVM 15: This function will only have to be:
// ```
// return self.believe_value(ptr);
// ```
let t = self.llvm_type(generator, ctx.ctx);
let ptr = ctx.builder.build_pointer_cast(ptr, t, "").unwrap();
unsafe { self.believe_value(ptr) }
}
}
impl<'ctx, Item: Model<'ctx>> Instance<'ctx, Ptr<Item>> {
/// Offset the pointer by [`inkwell::builder::Builder::build_in_bounds_gep`].
#[must_use]
pub fn offset(
&self,
ctx: &CodeGenContext<'ctx, '_>,
offset: IntValue<'ctx>,
) -> Instance<'ctx, Ptr<Item>> {
let p = unsafe { ctx.builder.build_in_bounds_gep(self.value, &[offset], "").unwrap() };
unsafe { self.model.believe_value(p) }
}
/// Offset the pointer by [`inkwell::builder::Builder::build_in_bounds_gep`] by a constant offset.
#[must_use]
pub fn offset_const(

Same as Array - i64.

Same as `Array` - `i64`.
&self,
ctx: &CodeGenContext<'ctx, '_>,

i64_type, is_signed: true.

`i64_type`, `is_signed: true`.
offset: i64,
) -> Instance<'ctx, Ptr<Item>> {
let offset = ctx.ctx.i32_type().const_int(offset as u64, true);
self.offset(ctx, offset)
}
pub fn set_index(
&self,
ctx: &CodeGenContext<'ctx, '_>,
index: IntValue<'ctx>,
value: Instance<'ctx, Item>,
) {
self.offset(ctx, index).store(ctx, value);
}
pub fn set_index_const(
&self,
ctx: &CodeGenContext<'ctx, '_>,
index: i64,
value: Instance<'ctx, Item>,
) {
self.offset_const(ctx, index).store(ctx, value);
}
pub fn get_index<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
index: IntValue<'ctx>,
) -> Instance<'ctx, Item> {
self.offset(ctx, index).load(generator, ctx)
}
pub fn get_index_const<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
index: i64,
) -> Instance<'ctx, Item> {
self.offset_const(ctx, index).load(generator, ctx)
}
/// Load the value with [`inkwell::builder::Builder::build_load`].
pub fn load<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
) -> Instance<'ctx, Item> {
let value = ctx.builder.build_load(self.value, "").unwrap();
self.model.0.check_value(generator, ctx.ctx, value).unwrap() // If unwrap() panics, there is a logic error.
}
/// Store a value with [`inkwell::builder::Builder::build_store`].
pub fn store(&self, ctx: &CodeGenContext<'ctx, '_>, value: Instance<'ctx, Item>) {
ctx.builder.build_store(self.value, value.value).unwrap();
}
/// Return a casted pointer of element type `NewElement` with [`inkwell::builder::Builder::build_pointer_cast`].
pub fn pointer_cast<NewItem: Model<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
new_item: NewItem,
) -> Instance<'ctx, Ptr<NewItem>> {
// TODO: LLVM 15: Write in an impl where `Item` does not have to be `Model<'ctx>`.
Ptr(new_item).pointer_cast(generator, ctx, self.value)
}
/// Check if the pointer is null with [`inkwell::builder::Builder::build_is_null`].
pub fn is_null(&self, ctx: &CodeGenContext<'ctx, '_>) -> Instance<'ctx, Int<Bool>> {
let value = ctx.builder.build_is_null(self.value, "").unwrap();
unsafe { Int(Bool).believe_value(value) }
}
/// Check if the pointer is not null with [`inkwell::builder::Builder::build_is_not_null`].
pub fn is_not_null(&self, ctx: &CodeGenContext<'ctx, '_>) -> Instance<'ctx, Int<Bool>> {
let value = ctx.builder.build_is_not_null(self.value, "").unwrap();
unsafe { Int(Bool).believe_value(value) }
}
/// `memcpy` from another pointer.
pub fn copy_from<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
source: Self,

I would suggest extending them to SizeT to match the signature of memcpy in C.

I would suggest extending them to `SizeT` to match the signature of `memcpy` in C.
num_items: IntValue<'ctx>,
) {
// Force extend `num_items` and `itemsize` to `i64` so their types would match.
let itemsize = self.model.size_of(generator, ctx.ctx);
let itemsize = Int(SizeT).z_extend_or_truncate(generator, ctx, itemsize);
let num_items = Int(SizeT).z_extend_or_truncate(generator, ctx, num_items);
let totalsize = itemsize.mul(ctx, num_items);
let is_volatile = ctx.ctx.bool_type().const_zero(); // is_volatile = false
call_memcpy_generic(ctx, self.value, source.value, totalsize.value, is_volatile);
}
}

View File

@ -0,0 +1,363 @@
use std::fmt;
use inkwell::{
context::Context,
types::{BasicType, BasicTypeEnum, StructType},
values::{BasicValueEnum, StructValue},
};
use super::*;
use crate::codegen::{CodeGenContext, CodeGenerator};
/// A traveral that traverses a Rust `struct` that is used to declare an LLVM's struct's field types.
pub trait FieldTraversal<'ctx> {
/// Output type of [`FieldTraversal::add`].

MapStructFields? Since the only operations its implementing classes do is to add fields based on some criteria, matching what map does to an iterator. Or FieldMapper.

`MapStructFields`? Since the only operations its implementing classes do is to add fields based on some criteria, matching what `map` does to an iterator. Or `FieldMapper`.
type Output<M>;

This name is non-descriptive. Output or OutType would be better.

This name is non-descriptive. `Output` or `OutType` would be better.
/// Traverse through the type of a declared field and do something with it.
///
/// * `name` - The cosmetic name of the LLVM field. Used for debugging.
/// * `model` - The [`Model`] representing the LLVM type of this field.
fn add<M: Model<'ctx>>(&mut self, name: &'static str, model: M) -> Self::Output<M>;
/// Like [`FieldTraversal::add`] but [`Model`] is automatically inferred from its [`Default`] trait.
fn add_auto<M: Model<'ctx> + Default>(&mut self, name: &'static str) -> Self::Output<M> {
self.add(name, M::default())
}
}
/// Descriptor of an LLVM struct field.
#[derive(Debug, Clone, Copy)]
pub struct GepField<M> {
/// The GEP index of this field. This is the index to use with `build_gep`.
pub gep_index: u32,
/// The cosmetic name of this field.
pub name: &'static str,
/// The [`Model`] of this field's type.
pub model: M,
}
/// A traversal to calculate the GEP index of fields.
pub struct GepFieldTraversal {
/// The current GEP index.

FieldIndexCollector? Same for below - Name those as *Collector?

`FieldIndexCollector`? Same for below - Name those as `*Collector`?
Outdated
Review

This is a traversal that calculates the correct GEP index of each struct field.

struct NDArrayFields<F: FieldTraversal> {
  data: F::Out<Ptr<Int<Byte>>>,
  itemsize: F::Out<Int<SizeT>>,
  shape: F::Out<Ptr<Int<SizeT>>>,
  ...
}

NDArrayFields<GepFieldTraversal>::fields() yields:

NDArrayFields {
  data: GepField { gep_index: 0, ... },
  itemsize: GepField { gep_index: 1, ... },
  shape: GepField { gep_index: 2, ...},
  ...
}

And this object is used to enable the my_ndarray.gep(|f| f.data).set_index_const(0, my_value) syntax.

This is a traversal that calculates the correct GEP index of each struct field. ```rust struct NDArrayFields<F: FieldTraversal> { data: F::Out<Ptr<Int<Byte>>>, itemsize: F::Out<Int<SizeT>>, shape: F::Out<Ptr<Int<SizeT>>>, ... } ``` `NDArrayFields<GepFieldTraversal>::fields()` yields: ``` NDArrayFields { data: GepField { gep_index: 0, ... }, itemsize: GepField { gep_index: 1, ... }, shape: GepField { gep_index: 2, ...}, ... } ``` And this object is used to enable the `my_ndarray.gep(|f| f.data).set_index_const(0, my_value)` syntax.
gep_index_counter: u32,
}

u32.

When indexing into a (optionally packed) structure, only i32 integer constants are allowed

`u32`. > When indexing into a (optionally packed) structure, only i32 integer constants are allowed
impl<'ctx> FieldTraversal<'ctx> for GepFieldTraversal {
type Output<M> = GepField<M>;
fn add<M: Model<'ctx>>(&mut self, name: &'static str, model: M) -> Self::Output<M> {
let gep_index = self.gep_index_counter;
self.gep_index_counter += 1;
Self::Output { gep_index, name, model }
}
}
/// A traversal to collect the field types of a struct.
///
/// This is used to collect field types and construct the LLVM struct type with [`Context::struct_type`].
struct TypeFieldTraversal<'ctx, 'a, G: CodeGenerator + ?Sized> {
generator: &'a G,
ctx: &'ctx Context,
/// The collected field types so far in exact order.
field_types: Vec<BasicTypeEnum<'ctx>>,
}
impl<'ctx, 'a, G: CodeGenerator + ?Sized> FieldTraversal<'ctx> for TypeFieldTraversal<'ctx, 'a, G> {
type Output<M> = (); // Checking types return nothing.
fn add<M: Model<'ctx>>(&mut self, _name: &'static str, model: M) -> Self::Output<M> {
let t = model.llvm_type(self.generator, self.ctx).as_basic_type_enum();
self.field_types.push(t);
}
}
/// A traversal to check the types of fields.
struct CheckTypeFieldTraversal<'ctx, 'a, G: CodeGenerator + ?Sized> {
generator: &'a mut G,
ctx: &'ctx Context,
/// The current GEP index, so we can tell the index of the field we are checking
/// and report the GEP index.
gep_index_counter: u32,
/// The [`StructType`] to check.
scrutinee: StructType<'ctx>,
/// The list of collected errors so far.
errors: Vec<ModelError>,
}
impl<'ctx, 'a, G: CodeGenerator + ?Sized> FieldTraversal<'ctx>
for CheckTypeFieldTraversal<'ctx, 'a, G>
{
type Output<M> = (); // Checking types return nothing.
fn add<M: Model<'ctx>>(&mut self, name: &'static str, model: M) -> Self::Output<M> {
let gep_index = self.gep_index_counter;
self.gep_index_counter += 1;
if let Some(t) = self.scrutinee.get_field_type_at_index(gep_index) {
if let Err(err) = model.check_type(self.generator, self.ctx, t) {
self.errors
.push(err.under_context(format!("field #{gep_index} '{name}'").as_str()));
}
}
// Otherwise, it will be caught by Struct's `check_type`.

nit: newline

nit: newline
}
}
/// A trait for Rust structs identifying LLVM structures.
///
/// ### Example
///
/// Suppose you want to define this structure:
/// ```c
/// template <typename T>
/// struct ContiguousNDArray {
/// size_t ndims;
/// size_t* shape;
/// T* data;
/// }
/// ```
///
/// This is how it should be done:
/// ```ignore
/// pub struct ContiguousNDArrayFields<'ctx, F: FieldTraversal<'ctx>, Item: Model<'ctx>> {
/// pub ndims: F::Out<Int<SizeT>>,
/// pub shape: F::Out<Ptr<Int<SizeT>>>,
/// pub data: F::Out<Ptr<Item>>,
/// }
///
/// /// An ndarray without strides and non-opaque `data` field in NAC3.
/// #[derive(Debug, Clone, Copy)]
/// pub struct ContiguousNDArray<M> {
/// /// [`Model`] of the items.
/// pub item: M,
/// }
///
/// impl<'ctx, Item: Model<'ctx>> StructKind<'ctx> for ContiguousNDArray<Item> {
/// type Fields<F: FieldTraversal<'ctx>> = ContiguousNDArrayFields<'ctx, F, Item>;
///
/// fn traverse_fields<F: FieldTraversal<'ctx>>(&self, traversal: &mut F) -> Self::Fields<F> {
/// // The order of `traversal.add*` is important
/// Self::Fields {
/// ndims: traversal.add_auto("ndims"),
/// shape: traversal.add_auto("shape"),
/// data: traversal.add("data", Ptr(self.item)),
/// }
/// }
/// }
/// ```
///
/// The [`FieldTraversal`] here is a mechanism to allow the fields of `ContiguousNDArrayFields` to be
/// traversed to do useful work such as:
///
/// - To create the [`StructType`] of `ContiguousNDArray` by collecting [`BasicType`]s of the fields.
/// - To enable the `.gep(ctx, |f| f.ndims).store(ctx, ...)` syntax.
///
/// Suppose now that you have defined `ContiguousNDArray` and you want to allocate a `ContiguousNDArray`
/// with dtype `float64` in LLVM, this is how you do it:
/// ```ignore
/// type F64NDArray = Struct<ContiguousNDArray<Float<Float64>>>; // Type alias for leaner documentation
/// let model: F64NDArray = Struct(ContigousNDArray { item: Float(Float64) });
/// let ndarray: Instance<'ctx, Ptr<F64NDArray>> = model.alloca(generator, ctx);
/// ```
///
/// ...and here is how you may manipulate/access `ndarray`:

Move this into the documentation for their respective functions. It is very difficult to refer to these examples when you are trying to look for how to use the APIs.

Move this into the documentation for their respective functions. It is very difficult to refer to these examples when you are trying to look for how to use the APIs.
///
/// (NOTE: some arguments have been omitted)
///
/// ```ignore
/// // Get `&ndarray->data`
/// ndarray.gep(|f| f.data); // type: Instance<'ctx, Ptr<Float<Float64>>>
///
/// // Get `ndarray->ndims`
/// ndarray.get(|f| f.ndims); // type: Instance<'ctx, Int<SizeT>>
///
/// // Get `&ndarray->ndims`
/// ndarray.gep(|f| f.ndims); // type: Instance<'ctx, Ptr<Int<SizeT>>>
///
/// // Get `ndarray->shape[0]`
/// ndarray.get(|f| f.shape).get_index_const(0); // Instance<'ctx, Int<SizeT>>
///
/// // Get `&ndarray->shape[2]`
/// ndarray.get(|f| f.shape).offset_const(2); // Instance<'ctx, Ptr<Int<SizeT>>>
///
/// // Do `ndarray->ndims = 3;`
/// let num_3 = Int(SizeT).const_int(3);
/// ndarray.set(|f| f.ndims, num_3);
/// ```
pub trait StructKind<'ctx>: fmt::Debug + Clone + Copy {
/// The associated fields of this struct.
type Fields<F: FieldTraversal<'ctx>>;
/// Traverse through all fields of this [`StructKind`].
///
/// Only used internally in this module for implementing other components.
fn iter_fields<F: FieldTraversal<'ctx>>(&self, traversal: &mut F) -> Self::Fields<F>;

iter_fields

`iter_fields`
/// Get a convenience structure to get a struct field's GEP index through its corresponding Rust field.
///
/// Only used internally in this module for implementing other components.
fn fields(&self) -> Self::Fields<GepFieldTraversal> {
self.iter_fields(&mut GepFieldTraversal { gep_index_counter: 0 })

Consider adding a new() function for GepFieldTraversal.

Consider adding a `new()` function for `GepFieldTraversal`.
}
/// Get the LLVM [`StructType`] of this [`StructKind`].
fn get_struct_type<G: CodeGenerator + ?Sized>(
&self,
generator: &G,
ctx: &'ctx Context,
) -> StructType<'ctx> {
let mut traversal = TypeFieldTraversal { generator, ctx, field_types: Vec::new() };

Consider adding a new() function for this.

Consider adding a `new()` function for this.
self.iter_fields(&mut traversal);
ctx.struct_type(&traversal.field_types, false)
}
}
/// A model for LLVM struct.
///
/// `S` should be of a [`StructKind`].
#[derive(Debug, Clone, Copy, Default)]
pub struct Struct<S>(pub S);
impl<'ctx, S: StructKind<'ctx>> Struct<S> {
/// Create a constant struct value from its fields.
///
/// This function also validates `fields` and panic when there is something wrong.
pub fn const_struct<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
fields: &[BasicValueEnum<'ctx>],
) -> Instance<'ctx, Self> {
// NOTE: There *could* have been a functor `F<M> = Instance<'ctx, M>` for `S::Fields<F>`
// to create a more user-friendly interface, but Rust's type system is not sophisticated enough
// and if you try doing that Rust would force you put lifetimes everywhere.
let val = ctx.const_struct(fields, false);
self.check_value(generator, ctx, val).unwrap()
}
}
impl<'ctx, S: StructKind<'ctx>> Model<'ctx> for Struct<S> {
type Value = StructValue<'ctx>;
type Type = StructType<'ctx>;
fn llvm_type<G: CodeGenerator + ?Sized>(
&self,
generator: &G,
ctx: &'ctx Context,
) -> Self::Type {
self.0.get_struct_type(generator, ctx)
}
fn check_type<T: BasicType<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
ty: T,
) -> Result<(), ModelError> {
let ty = ty.as_basic_type_enum();
let Ok(ty) = StructType::try_from(ty) else {
return Err(ModelError(format!("Expecting StructType, but got {ty:?}")));

Consider adding a new() function for this.

Consider adding a `new()` function for this.
};
// Check each field individually.
let mut traversal = CheckTypeFieldTraversal {
generator,
ctx,
gep_index_counter: 0,
errors: Vec::new(),
scrutinee: ty,
};
self.0.iter_fields(&mut traversal);
// Check the number of fields.
let exp_num_fields = traversal.gep_index_counter;
let got_num_fields = u32::try_from(ty.get_field_types().len()).unwrap();
if exp_num_fields != got_num_fields {
return Err(ModelError(format!(
"Expecting StructType with {exp_num_fields} field(s), but got {got_num_fields}"
)));
}
if !traversal.errors.is_empty() {
// Currently, only the first error is reported.
return Err(traversal.errors[0].clone());
}
Ok(())
}
}
impl<'ctx, S: StructKind<'ctx>> Instance<'ctx, Struct<S>> {
/// Get a field with [`StructValue::get_field_at_index`].
pub fn get_field<G: CodeGenerator + ?Sized, M, GetField>(
&self,
generator: &mut G,
ctx: &'ctx Context,
get_field: GetField,
) -> Instance<'ctx, M>
where
M: Model<'ctx>,
GetField: FnOnce(S::Fields<GepFieldTraversal>) -> GepField<M>,
{
let field = get_field(self.model.0.fields());
let val = self.value.get_field_at_index(field.gep_index).unwrap();
field.model.check_value(generator, ctx, val).unwrap()
}
}
impl<'ctx, S: StructKind<'ctx>> Instance<'ctx, Ptr<Struct<S>>> {
/// Get a pointer to a field with [`Builder::build_in_bounds_gep`].
pub fn gep<M, GetField>(
&self,
ctx: &CodeGenContext<'ctx, '_>,
get_field: GetField,
) -> Instance<'ctx, Ptr<M>>
where
M: Model<'ctx>,
GetField: FnOnce(S::Fields<GepFieldTraversal>) -> GepField<M>,
{
let field = get_field(self.model.0 .0.fields());
let llvm_i32 = ctx.ctx.i32_type();
let ptr = unsafe {
ctx.builder
.build_in_bounds_gep(
self.value,
&[llvm_i32.const_zero(), llvm_i32.const_int(u64::from(field.gep_index), false)],
field.name,
)
.unwrap()
};
unsafe { Ptr(field.model).believe_value(ptr) }
}
/// Convenience function equivalent to `.gep(...).load(...)`.
pub fn get<M, GetField, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
get_field: GetField,
) -> Instance<'ctx, M>
where
M: Model<'ctx>,
GetField: FnOnce(S::Fields<GepFieldTraversal>) -> GepField<M>,
{
self.gep(ctx, get_field).load(generator, ctx)
}
/// Convenience function equivalent to `.gep(...).store(...)`.
pub fn set<M, GetField>(
&self,
ctx: &CodeGenContext<'ctx, '_>,
get_field: GetField,
value: Instance<'ctx, M>,
) where
M: Model<'ctx>,
GetField: FnOnce(S::Fields<GepFieldTraversal>) -> GepField<M>,
{
self.gep(ctx, get_field).store(ctx, value);
}
}