1
0
forked from M-Labs/nac3

core: BoundedIxed Ixed & ArraySlice optics

This commit is contained in:
lyken 2024-07-14 13:12:06 +08:00
parent 867f6ccf8e
commit 259958aded

View File

@ -6,7 +6,7 @@ use inkwell::{
};
use itertools::Itertools;
use super::CodeGenContext;
use super::{CodeGenContext, CodeGenerator};
// TODO: Write a taxonomy
@ -124,6 +124,12 @@ impl<'ctx, AddresseeOptic> OpticValue<'ctx> for Address<'ctx, AddresseeOptic> {
#[derive(Debug, Clone)]
pub struct AddressLens<AddresseeOptic>(pub AddresseeOptic);
impl<AddresseeOptic> AddressLens<AddresseeOptic> {
pub fn new_opaque<'ctx>(&self, ctx: &CodeGenContext<'ctx, '_>) -> AddressLens<IntLens<'ctx>> {
AddressLens(IntLens(ctx.ctx.i8_type()))
}
}
impl<'ctx, AddresseeOptic: Optic<'ctx>> Optic<'ctx> for AddressLens<AddresseeOptic> {
type Value = Address<'ctx, AddresseeOptic>;
@ -341,3 +347,89 @@ impl<'ctx, AddresseeOptic: StructureOptic<'ctx>> Address<'ctx, AddresseeOptic> {
field.get(ctx, self.address, field.name)
}
}
// Name inspired by https://hackage.haskell.org/package/lens-5.3.2/docs/Control-Lens-At.html#t:Ixed
pub trait Ixed<'ctx, ElementOptic> {
// TODO: Interface/Method to expose the IntType of index?
// or even make index itself parameterized? (probably no)
fn ix(
&self,
ctx: &CodeGenContext<'ctx, '_>,
index: IntValue<'ctx>,
name: &str,
) -> Address<'ctx, ElementOptic>;
}
// TODO: Can do interface seggregation
pub trait BoundedIxed<'ctx, ElementOptic>: Ixed<'ctx, ElementOptic> {
fn num_elements(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx>;
// Check if 0 <= index < self.num_elements()
fn ix_bounds_checked<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
index: IntValue<'ctx>,
name: &str,
) -> Address<'ctx, ElementOptic> {
let num_elements = self.num_elements(ctx);
let int_type = num_elements.get_type(); // NOTE: Weird get_type(), see comment under `trait Ixed`
assert_eq!(int_type.get_bit_width(), index.get_type().get_bit_width()); // Might as well check bit width to catch bugs
// TODO: SGE or UGE? or make it defined by the implementee?
// Check `0 <= index`
let lower_bounded = ctx
.builder
.build_int_compare(
inkwell::IntPredicate::SLE,
int_type.const_zero(),
index,
"lower_bounded",
)
.unwrap();
// Check `index < num_elements`
let upper_bounded = ctx
.builder
.build_int_compare(inkwell::IntPredicate::SLT, index, num_elements, "upper_bounded")
.unwrap();
// Compute `0 <= index && index < num_elements`
let bounded = ctx.builder.build_and(lower_bounded, upper_bounded, "bounded").unwrap();
// Assert `bounded`
ctx.make_assert(generator, bounded, "0:IndexError", "nac3core LLVM codegen attempting to access out of bounds array index {0}. Must satisfy 0 <= index < {2}", [Some(index), Some(num_elements), None], ctx.current_loc);
// ...and finally do indexing
self.ix(ctx, index, name)
}
}
pub struct ArraySlice<'ctx, ElementOptic> {
pub num_elements: IntValue<'ctx>,
pub base: Address<'ctx, ElementOptic>,
}
impl<'ctx, ElementOptic: Optic<'ctx>> Ixed<'ctx, ElementOptic> for ArraySlice<'ctx, ElementOptic> {
fn ix(
&self,
ctx: &CodeGenContext<'ctx, '_>,
index: IntValue<'ctx>,
name: &str,
) -> Address<'ctx, ElementOptic> {
let element_addr =
unsafe { ctx.builder.build_in_bounds_gep(self.base.address, &[index], name).unwrap() };
Address { address: element_addr, addressee_optic: self.base.addressee_optic.clone() }
}
}
impl<'ctx, ElementOptic: Optic<'ctx>> BoundedIxed<'ctx, ElementOptic>
for ArraySlice<'ctx, ElementOptic>
{
fn num_elements(&self, _ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
self.num_elements
}
}