From 064aa0411f678bd40fe8acdeffd3cad71894e1b5 Mon Sep 17 00:00:00 2001 From: David Mak Date: Tue, 4 Feb 2025 14:43:33 +0800 Subject: [PATCH] [core] codegen: Add Exception{Type,Value} --- nac3core/src/codegen/expr.rs | 63 +++--- nac3core/src/codegen/stmt.rs | 42 +--- nac3core/src/codegen/types/exception.rs | 257 +++++++++++++++++++++++ nac3core/src/codegen/types/mod.rs | 2 + nac3core/src/codegen/values/exception.rs | 188 +++++++++++++++++ nac3core/src/codegen/values/mod.rs | 2 + 6 files changed, 488 insertions(+), 66 deletions(-) create mode 100644 nac3core/src/codegen/types/exception.rs create mode 100644 nac3core/src/codegen/values/exception.rs diff --git a/nac3core/src/codegen/expr.rs b/nac3core/src/codegen/expr.rs index 3f48154c..b3cb3691 100644 --- a/nac3core/src/codegen/expr.rs +++ b/nac3core/src/codegen/expr.rs @@ -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>; 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( diff --git a/nac3core/src/codegen/stmt.rs b/nac3core/src/codegen/stmt.rs index 0c1b931a..35ffeead 100644 --- a/nac3core/src/codegen/stmt.rs +++ b/nac3core/src/codegen/stmt.rs @@ -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( } 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); diff --git a/nac3core/src/codegen/types/exception.rs b/nac3core/src/codegen/types/exception.rs new file mode 100644 index 00000000..0a8ec05d --- /dev/null +++ b/nac3core/src/codegen/types/exception.rs @@ -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( + 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( + 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>, + ) -> >::Value { + >::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( + &self, + generator: &mut G, + ctx: &mut CodeGenContext<'ctx, '_>, + name: Option<&'ctx str>, + ) -> >::Value { + >::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( + &self, + generator: &mut G, + ctx: &mut CodeGenContext<'ctx, '_>, + value: StructValue<'ctx>, + name: Option<&'ctx str>, + ) -> >::Value { + >::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>, + ) -> >::Value { + >::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> for PointerType<'ctx> { + fn from(value: ExceptionType<'ctx>) -> Self { + value.as_base_type() + } +} diff --git a/nac3core/src/codegen/types/mod.rs b/nac3core/src/codegen/types/mod.rs index cbab600b..1dc776b9 100644 --- a/nac3core/src/codegen/types/mod.rs +++ b/nac3core/src/codegen/types/mod.rs @@ -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; diff --git a/nac3core/src/codegen/values/exception.rs b/nac3core/src/codegen/values/exception.rs new file mode 100644 index 00000000..0b1796b9 --- /dev/null +++ b/nac3core/src/codegen/values/exception.rs @@ -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( + 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( + &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> for PointerValue<'ctx> { + fn from(value: ExceptionValue<'ctx>) -> Self { + value.as_base_value() + } +} diff --git a/nac3core/src/codegen/values/mod.rs b/nac3core/src/codegen/values/mod.rs index 7a43ba41..50933333 100644 --- a/nac3core/src/codegen/values/mod.rs +++ b/nac3core/src/codegen/values/mod.rs @@ -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;