[core] codegen: Add Exception{Type,Value}

This commit is contained in:
David Mak 2025-02-04 14:43:33 +08:00
parent 57552fb2f6
commit 064aa0411f
6 changed files with 488 additions and 66 deletions

View File

@ -32,7 +32,9 @@ use super::{
gen_for_callback_incrementing, gen_if_callback, gen_if_else_expr_callback, gen_raise,
gen_var,
},
types::{ndarray::NDArrayType, ListType, OptionType, RangeType, StringType, TupleType},
types::{
ndarray::NDArrayType, ExceptionType, ListType, OptionType, RangeType, StringType, TupleType,
},
values::{
ndarray::{NDArrayOut, RustNDIndex, ScalarOrNDArray},
ArrayLikeIndexer, ArrayLikeValue, ListValue, ProxyValue, RangeValue,
@ -576,42 +578,35 @@ impl<'ctx> CodeGenContext<'ctx, '_> {
params: [Option<IntValue<'ctx>>; 3],
loc: Location,
) {
let llvm_i32 = self.ctx.i32_type();
let llvm_i64 = self.ctx.i64_type();
let llvm_exn = ExceptionType::get_instance(generator, self);
let zelf = if let Some(exception_val) = self.exception_val {
exception_val
llvm_exn.map_pointer_value(exception_val, Some("exn"))
} else {
let ty = self.get_llvm_type(generator, self.primitives.exception).into_pointer_type();
let zelf_ty: BasicTypeEnum = ty.get_element_type().into_struct_type().into();
let zelf = generator.gen_var_alloc(self, zelf_ty, Some("exn")).unwrap();
*self.exception_val.insert(zelf)
let zelf = llvm_exn.alloca_var(generator, self, Some("exn"));
self.exception_val = Some(zelf.as_abi_value(self));
zelf
};
let int32 = self.ctx.i32_type();
let zero = int32.const_zero();
unsafe {
let id_ptr = self.builder.build_in_bounds_gep(zelf, &[zero, zero], "exn.id").unwrap();
let id = self.resolver.get_string_id(name);
self.builder.build_store(id_ptr, int32.const_int(id as u64, false)).unwrap();
let ptr = self
.builder
.build_in_bounds_gep(zelf, &[zero, int32.const_int(5, false)], "exn.msg")
.unwrap();
self.builder.build_store(ptr, msg).unwrap();
let i64_zero = self.ctx.i64_type().const_zero();
for (i, attr_ind) in [6, 7, 8].iter().enumerate() {
let ptr = self
.builder
.build_in_bounds_gep(
zelf,
&[zero, int32.const_int(*attr_ind, false)],
"exn.param",
)
.unwrap();
let val = params[i].map_or(i64_zero, |v| {
self.builder.build_int_s_extend(v, self.ctx.i64_type(), "sext").unwrap()
});
self.builder.build_store(ptr, val).unwrap();
}
}
gen_raise(generator, self, Some(&zelf.into()), loc);
let id = self.resolver.get_string_id(name);
zelf.store_name(self, llvm_i32.const_int(id as u64, false));
zelf.store_message(self, msg.into_struct_value());
zelf.store_params(
self,
params
.iter()
.map(|p| {
p.map_or(llvm_i64.const_zero(), |v| {
self.builder.build_int_s_extend(v, self.ctx.i64_type(), "sext").unwrap()
})
})
.collect_array()
.as_ref()
.unwrap(),
);
gen_raise(generator, self, Some(&zelf), loc);
}
pub fn make_assert<G: CodeGenerator + ?Sized>(

View File

@ -17,10 +17,10 @@ use super::{
gen_in_range_check,
irrt::{handle_slice_indices, list_slice_assignment},
macros::codegen_unreachable,
types::{ndarray::NDArrayType, RangeType},
types::{ndarray::NDArrayType, ExceptionType, RangeType},
values::{
ndarray::{RustNDIndex, ScalarOrNDArray},
ArrayLikeIndexer, ArraySliceValue, ListValue, ProxyValue,
ArrayLikeIndexer, ArraySliceValue, ExceptionValue, ListValue, ProxyValue,
},
CodeGenContext, CodeGenerator,
};
@ -1337,43 +1337,19 @@ pub fn exn_constructor<'ctx>(
pub fn gen_raise<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
exception: Option<&BasicValueEnum<'ctx>>,
exception: Option<&ExceptionValue<'ctx>>,
loc: Location,
) {
if let Some(exception) = exception {
unsafe {
let int32 = ctx.ctx.i32_type();
let zero = int32.const_zero();
let exception = exception.into_pointer_value();
let file_ptr = ctx
.builder
.build_in_bounds_gep(exception, &[zero, int32.const_int(1, false)], "file_ptr")
.unwrap();
let filename = ctx.gen_string(generator, loc.file.0);
ctx.builder.build_store(file_ptr, filename).unwrap();
let row_ptr = ctx
.builder
.build_in_bounds_gep(exception, &[zero, int32.const_int(2, false)], "row_ptr")
.unwrap();
ctx.builder.build_store(row_ptr, int32.const_int(loc.row as u64, false)).unwrap();
let col_ptr = ctx
.builder
.build_in_bounds_gep(exception, &[zero, int32.const_int(3, false)], "col_ptr")
.unwrap();
ctx.builder.build_store(col_ptr, int32.const_int(loc.column as u64, false)).unwrap();
exception.store_location(generator, ctx, loc);
let current_fun = ctx.builder.get_insert_block().unwrap().get_parent().unwrap();
let fun_name = ctx.gen_string(generator, current_fun.get_name().to_str().unwrap());
let name_ptr = ctx
.builder
.build_in_bounds_gep(exception, &[zero, int32.const_int(4, false)], "name_ptr")
.unwrap();
ctx.builder.build_store(name_ptr, fun_name).unwrap();
}
let current_fun = ctx.builder.get_insert_block().and_then(BasicBlock::get_parent).unwrap();
let fun_name = ctx.gen_string(generator, current_fun.get_name().to_str().unwrap());
exception.store_func(ctx, fun_name);
let raise = get_builtins(generator, ctx, "__nac3_raise");
let exception = *exception;
ctx.build_call_or_invoke(raise, &[exception], "raise");
ctx.build_call_or_invoke(raise, &[exception.as_abi_value(ctx).into()], "raise");
} else {
let resume = get_builtins(generator, ctx, "__nac3_resume");
ctx.build_call_or_invoke(resume, &[], "resume");
@ -1860,6 +1836,8 @@ pub fn gen_stmt<G: CodeGenerator>(
} else {
return Ok(());
};
let exc = ExceptionType::get_instance(generator, ctx)
.map_pointer_value(exc.into_pointer_value(), None);
gen_raise(generator, ctx, Some(&exc), stmt.location);
} else {
gen_raise(generator, ctx, None, stmt.location);

View File

@ -0,0 +1,257 @@
use inkwell::{
context::{AsContextRef, Context},
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType, StructType},
values::{IntValue, PointerValue, StructValue},
AddressSpace,
};
use itertools::Itertools;
use nac3core_derive::StructFields;
use super::{
structure::{check_struct_type_matches_fields, StructField, StructFields, StructProxyType},
ProxyType,
};
use crate::{
codegen::{values::ExceptionValue, CodeGenContext, CodeGenerator},
typecheck::typedef::{Type, TypeEnum},
};
/// Proxy type for an `Exception` in LLVM.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct ExceptionType<'ctx> {
ty: PointerType<'ctx>,
llvm_usize: IntType<'ctx>,
}
#[derive(PartialEq, Eq, Clone, Copy, StructFields)]
pub struct ExceptionStructFields<'ctx> {
/// The ID of the exception name.
#[value_type(i32_type())]
pub name: StructField<'ctx, IntValue<'ctx>>,
/// The file where the exception originated from.
#[value_type(get_struct_type("str").unwrap())]
pub file: StructField<'ctx, StructValue<'ctx>>,
/// The line number where the exception originated from.
#[value_type(i32_type())]
pub line: StructField<'ctx, IntValue<'ctx>>,
/// The column number where the exception originated from.
#[value_type(i32_type())]
pub col: StructField<'ctx, IntValue<'ctx>>,
/// The function name where the exception originated from.
#[value_type(get_struct_type("str").unwrap())]
pub func: StructField<'ctx, StructValue<'ctx>>,
/// The exception message.
#[value_type(get_struct_type("str").unwrap())]
pub message: StructField<'ctx, StructValue<'ctx>>,
#[value_type(i64_type())]
pub param0: StructField<'ctx, IntValue<'ctx>>,
#[value_type(i64_type())]
pub param1: StructField<'ctx, IntValue<'ctx>>,
#[value_type(i64_type())]
pub param2: StructField<'ctx, IntValue<'ctx>>,
}
impl<'ctx> ExceptionType<'ctx> {
/// Returns an instance of [`StructFields`] containing all field accessors for this type.
#[must_use]
fn fields(
ctx: impl AsContextRef<'ctx>,
llvm_usize: IntType<'ctx>,
) -> ExceptionStructFields<'ctx> {
ExceptionStructFields::new(ctx, llvm_usize)
}
/// Creates an LLVM type corresponding to the expected structure of an `Exception`.
#[must_use]
fn llvm_type(ctx: &'ctx Context, llvm_usize: IntType<'ctx>) -> PointerType<'ctx> {
assert!(ctx.get_struct_type("str").is_some());
let field_tys =
Self::fields(ctx, llvm_usize).into_iter().map(|field| field.1).collect_vec();
ctx.struct_type(&field_tys, false).ptr_type(AddressSpace::default())
}
fn new_impl(ctx: &'ctx Context, llvm_usize: IntType<'ctx>) -> Self {
let llvm_str = Self::llvm_type(ctx, llvm_usize);
Self { ty: llvm_str, llvm_usize }
}
/// Creates an instance of [`ExceptionType`].
#[must_use]
pub fn new(ctx: &CodeGenContext<'ctx, '_>) -> Self {
Self::new_impl(ctx.ctx, ctx.get_size_type())
}
/// Creates an instance of [`ExceptionType`].
#[must_use]
pub fn new_with_generator<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
) -> Self {
Self::new_impl(ctx, generator.get_size_type(ctx))
}
/// Creates an [`ExceptionType`] from a [unifier type][Type].
#[must_use]
pub fn from_unifier_type(ctx: &mut CodeGenContext<'ctx, '_>, ty: Type) -> Self {
// Check unifier type
assert!(
matches!(&*ctx.unifier.get_ty_immutable(ty), TypeEnum::TObj { obj_id, .. } if *obj_id == ctx.primitives.exception.obj_id(&ctx.unifier).unwrap())
);
Self::new_impl(ctx.ctx, ctx.get_size_type())
}
/// Creates an [`ExceptionType`] from a [`StructType`] representing an `Exception`.
#[must_use]
pub fn from_struct_type(ty: StructType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
Self::from_pointer_type(ty.ptr_type(AddressSpace::default()), llvm_usize)
}
/// Creates an [`ExceptionType`] from a [`PointerType`] representing an `Exception`.
#[must_use]
pub fn from_pointer_type(ptr_ty: PointerType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
debug_assert!(Self::has_same_repr(ptr_ty, llvm_usize).is_ok());
Self { ty: ptr_ty, llvm_usize }
}
/// Returns an instance of [`ExceptionType`] by obtaining the LLVM representation of the builtin
/// `Exception` type.
#[must_use]
pub fn get_instance<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
) -> Self {
Self::from_pointer_type(
ctx.get_llvm_type(generator, ctx.primitives.exception).into_pointer_type(),
ctx.get_size_type(),
)
}
/// Allocates an instance of [`ExceptionValue`] as if by calling `alloca` on the base type.
///
/// See [`ProxyType::raw_alloca`].
#[must_use]
pub fn alloca(
&self,
ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_pointer_value(
self.raw_alloca(ctx, name),
self.llvm_usize,
name,
)
}
/// Allocates an instance of [`ExceptionValue`] as if by calling `alloca` on the base type.
///
/// See [`ProxyType::raw_alloca_var`].
#[must_use]
pub fn alloca_var<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_pointer_value(
self.raw_alloca_var(generator, ctx, name),
self.llvm_usize,
name,
)
}
/// Converts an existing value into a [`ExceptionValue`].
#[must_use]
pub fn map_struct_value<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
value: StructValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_struct_value(
generator,
ctx,
value,
self.llvm_usize,
name,
)
}
/// Converts an existing value into a [`ExceptionValue`].
#[must_use]
pub fn map_pointer_value(
&self,
value: PointerValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_pointer_value(value, self.llvm_usize, name)
}
}
impl<'ctx> ProxyType<'ctx> for ExceptionType<'ctx> {
type ABI = PointerType<'ctx>;
type Base = PointerType<'ctx>;
type Value = ExceptionValue<'ctx>;
fn is_representable(
llvm_ty: impl BasicType<'ctx>,
llvm_usize: IntType<'ctx>,
) -> Result<(), String> {
if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() {
Self::has_same_repr(ty, llvm_usize)
} else {
Err(format!("Expected pointer type, got {llvm_ty:?}"))
}
}
fn has_same_repr(ty: Self::Base, llvm_usize: IntType<'ctx>) -> Result<(), String> {
let ctx = ty.get_context();
let llvm_ty = ty.get_element_type();
let AnyTypeEnum::StructType(llvm_ty) = llvm_ty else {
return Err(format!("Expected struct type for `list` type, got {llvm_ty}"));
};
check_struct_type_matches_fields(Self::fields(ctx, llvm_usize), llvm_ty, "exception", &[])
}
fn alloca_type(&self) -> impl BasicType<'ctx> {
self.as_abi_type().get_element_type().into_struct_type()
}
fn as_base_type(&self) -> Self::Base {
self.ty
}
fn as_abi_type(&self) -> Self::ABI {
self.as_base_type()
}
}
impl<'ctx> StructProxyType<'ctx> for ExceptionType<'ctx> {
type StructFields = ExceptionStructFields<'ctx>;
fn get_fields(&self) -> Self::StructFields {
Self::fields(self.ty.get_context(), self.llvm_usize)
}
}
impl<'ctx> From<ExceptionType<'ctx>> for PointerType<'ctx> {
fn from(value: ExceptionType<'ctx>) -> Self {
value.as_base_type()
}
}

View File

@ -25,12 +25,14 @@ use super::{
values::{ArraySliceValue, ProxyValue},
{CodeGenContext, CodeGenerator},
};
pub use exception::*;
pub use list::*;
pub use option::*;
pub use range::*;
pub use string::*;
pub use tuple::*;
mod exception;
mod list;
pub mod ndarray;
mod option;

View File

@ -0,0 +1,188 @@
use inkwell::{
types::IntType,
values::{IntValue, PointerValue, StructValue},
};
use itertools::Itertools;
use nac3parser::ast::Location;
use super::{structure::StructProxyValue, ProxyValue, StringValue};
use crate::codegen::{
types::{
structure::{StructField, StructProxyType},
ExceptionType,
},
CodeGenContext, CodeGenerator,
};
/// Proxy type for accessing an `Exception` value in LLVM.
#[derive(Copy, Clone)]
pub struct ExceptionValue<'ctx> {
value: PointerValue<'ctx>,
llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>,
}
impl<'ctx> ExceptionValue<'ctx> {
/// Creates an [`ExceptionValue`] from a [`StructValue`].
#[must_use]
pub fn from_struct_value<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
val: StructValue<'ctx>,
llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>,
) -> Self {
let pval = generator
.gen_var_alloc(
ctx,
val.get_type().into(),
name.map(|name| format!("{name}.addr")).as_deref(),
)
.unwrap();
ctx.builder.build_store(pval, val).unwrap();
Self::from_pointer_value(pval, llvm_usize, name)
}
/// Creates an [`ExceptionValue`] from a [`PointerValue`].
#[must_use]
pub fn from_pointer_value(
ptr: PointerValue<'ctx>,
llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>,
) -> Self {
debug_assert!(Self::is_instance(ptr, llvm_usize).is_ok());
Self { value: ptr, llvm_usize, name }
}
fn name_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
self.get_type().get_fields().name
}
/// Stores the ID of the exception name into this instance.
pub fn store_name(&self, ctx: &CodeGenContext<'ctx, '_>, name: IntValue<'ctx>) {
debug_assert_eq!(name.get_type(), ctx.ctx.i32_type());
self.name_field().store(ctx, self.value, name, self.name);
}
fn file_field(&self) -> StructField<'ctx, StructValue<'ctx>> {
self.get_type().get_fields().file
}
/// Stores the file name of the exception source into this instance.
pub fn store_file(&self, ctx: &CodeGenContext<'ctx, '_>, file: StructValue<'ctx>) {
debug_assert!(StringValue::is_instance(file, self.llvm_usize).is_ok());
self.file_field().store(ctx, self.value, file, self.name);
}
fn line_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
self.get_type().get_fields().line
}
fn col_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
self.get_type().get_fields().col
}
/// Stores the [location][Location] of the exception source into this instance.
pub fn store_location<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
location: Location,
) {
let llvm_i32 = ctx.ctx.i32_type();
let filename = ctx.gen_string(generator, location.file.0);
self.store_file(ctx, filename);
self.line_field().store(
ctx,
self.value,
llvm_i32.const_int(location.row as u64, false),
self.name,
);
self.col_field().store(
ctx,
self.value,
llvm_i32.const_int(location.column as u64, false),
self.name,
);
}
fn func_field(&self) -> StructField<'ctx, StructValue<'ctx>> {
self.get_type().get_fields().func
}
/// Stores the function name of the exception source into this instance.
pub fn store_func(&self, ctx: &CodeGenContext<'ctx, '_>, func: StructValue<'ctx>) {
debug_assert!(StringValue::is_instance(func, self.llvm_usize).is_ok());
self.func_field().store(ctx, self.value, func, self.name);
}
fn message_field(&self) -> StructField<'ctx, StructValue<'ctx>> {
self.get_type().get_fields().message
}
/// Stores the exception message into this instance.
pub fn store_message(&self, ctx: &CodeGenContext<'ctx, '_>, message: StructValue<'ctx>) {
debug_assert!(StringValue::is_instance(message, self.llvm_usize).is_ok());
self.message_field().store(ctx, self.value, message, self.name);
}
fn param0_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
self.get_type().get_fields().param0
}
fn param1_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
self.get_type().get_fields().param1
}
fn param2_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
self.get_type().get_fields().param2
}
/// Stores the parameters of the exception into this instance.
///
/// If the parameter does not exist, pass `i64 0` in the parameter slot.
pub fn store_params(&self, ctx: &CodeGenContext<'ctx, '_>, params: &[IntValue<'ctx>; 3]) {
debug_assert!(params.iter().all(|p| p.get_type() == ctx.ctx.i64_type()));
[self.param0_field(), self.param1_field(), self.param2_field()]
.into_iter()
.zip_eq(params)
.for_each(|(field, param)| {
field.store(ctx, self.value, *param, self.name);
});
}
}
impl<'ctx> ProxyValue<'ctx> for ExceptionValue<'ctx> {
type ABI = PointerValue<'ctx>;
type Base = PointerValue<'ctx>;
type Type = ExceptionType<'ctx>;
fn get_type(&self) -> Self::Type {
Self::Type::from_pointer_type(self.value.get_type(), self.llvm_usize)
}
fn as_base_value(&self) -> Self::Base {
self.value
}
fn as_abi_value(&self, _: &CodeGenContext<'ctx, '_>) -> Self::ABI {
self.as_base_value()
}
}
impl<'ctx> StructProxyValue<'ctx> for ExceptionValue<'ctx> {}
impl<'ctx> From<ExceptionValue<'ctx>> for PointerValue<'ctx> {
fn from(value: ExceptionValue<'ctx>) -> Self {
value.as_base_value()
}
}

View File

@ -2,6 +2,7 @@ use inkwell::{types::IntType, values::BasicValue};
use super::{types::ProxyType, CodeGenContext};
pub use array::*;
pub use exception::*;
pub use list::*;
pub use option::*;
pub use range::*;
@ -9,6 +10,7 @@ pub use string::*;
pub use tuple::*;
mod array;
mod exception;
mod list;
pub mod ndarray;
mod option;