diff --git a/nac3core/src/codegen/expr.rs b/nac3core/src/codegen/expr.rs index 1b79c0b6..e6a766b0 100644 --- a/nac3core/src/codegen/expr.rs +++ b/nac3core/src/codegen/expr.rs @@ -43,6 +43,12 @@ use nac3parser::ast::{ use std::iter::{repeat, repeat_with}; use std::{collections::HashMap, convert::TryInto, iter::once, iter::zip}; +use super::structure::cslice::CSlice; +use super::{ + model::*, + structure::exception::{Exception, ExceptionId}, +}; + pub fn get_subst_key( unifier: &mut Unifier, obj: Option, @@ -284,24 +290,7 @@ impl<'ctx, 'a> CodeGenContext<'ctx, 'a> { None } } - Constant::Str(v) => { - assert!(self.unifier.unioned(ty, self.primitives.str)); - if let Some(v) = self.const_strings.get(v) { - Some(*v) - } else { - let str_ptr = self - .builder - .build_global_string_ptr(v, "const") - .map(|v| v.as_pointer_value().into()) - .unwrap(); - let size = generator.get_size_type(self.ctx).const_int(v.len() as u64, false); - let ty = self.get_llvm_type(generator, self.primitives.str); - let val = - ty.into_struct_type().const_named_struct(&[str_ptr, size.into()]).into(); - self.const_strings.insert(v.to_string(), val); - Some(val) - } - } + Constant::Str(s) => Some(self.gen_string(generator, s).value.into()), Constant::Ellipsis => { let msg = self.gen_string(generator, "NotImplementedError"); @@ -568,96 +557,114 @@ impl<'ctx, 'a> CodeGenContext<'ctx, 'a> { } /// Helper function for generating a LLVM variable storing a [String]. - pub fn gen_string(&mut self, generator: &mut G, s: S) -> BasicValueEnum<'ctx> + pub fn gen_string(&mut self, generator: &mut G, string: &str) -> Struct<'ctx, CSlice> where G: CodeGenerator + ?Sized, - S: Into, { - self.gen_const(generator, &Constant::Str(s.into()), self.primitives.str).unwrap() + self.const_strings.get(string).copied().unwrap_or_else(|| { + let type_context = generator.type_context(self.ctx); + let sizet_model = IntModel(SizeT); + let pbyte_model = PtrModel(IntModel(Byte)); + let cslice_model = StructModel(CSlice); + + let base = self.builder.build_global_string_ptr(string, "constant_string").unwrap(); + let base = pbyte_model.believe_value(base.as_pointer_value()); + + let len = sizet_model.constant(type_context, self.ctx, string.len() as u64); + + let cslice = cslice_model.create_const(type_context, self, base, len); + + self.const_strings.insert(string.to_owned(), cslice); + + cslice + }) } pub fn raise_exn( &mut self, generator: &mut G, name: &str, - msg: BasicValueEnum<'ctx>, - params: [Option>; 3], + msg: Struct<'ctx, CSlice>, + params: [Option>; 3], loc: Location, ) { - let zelf = if let Some(exception_val) = self.exception_val { - exception_val - } 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 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(); + let type_context = generator.type_context(self.ctx); + let exn_model = StructModel(Exception); + let exn_id_model = IntModel(ExceptionId::default()); + + let exn_id = + exn_id_model.constant(type_context, self.ctx, self.resolver.get_string_id(name) as u64); + let exn = self.exception_val.unwrap_or_else(|| { + let exn = exn_model.var_alloca(generator, self, Some("exn")).unwrap(); + *self.exception_val.insert(exn) + }); + + exn.gep(self, |f| f.id).store(self, exn_id); + exn.gep(self, |f| f.msg).store(self, msg); + for (i, param) in params.iter().enumerate() { + if let Some(param) = param { + exn.gep(self, |f| f.params[i]).store(self, *param); } } - gen_raise(generator, self, Some(&zelf.into()), loc); + + gen_raise(generator, self, Some(exn), loc); } pub fn make_assert( &mut self, generator: &mut G, - cond: IntValue<'ctx>, + cond: IntValue<'ctx>, // IntType can have arbitrary bit width err_name: &str, err_msg: &str, params: [Option>; 3], loc: Location, ) { + let type_context = generator.type_context(self.ctx); + let param_model = IntModel(Int64); + let err_msg = self.gen_string(generator, err_msg); + + let ctx = self.ctx; + let params = + params.map(|p| p.map(|p| param_model.check_value(type_context, ctx, p).unwrap())); + self.make_assert_impl(generator, cond, err_name, err_msg, params, loc); } pub fn make_assert_impl( &mut self, generator: &mut G, - cond: IntValue<'ctx>, + cond: IntValue<'ctx>, // IntType can have arbitrary bit width err_name: &str, - err_msg: BasicValueEnum<'ctx>, - params: [Option>; 3], + err_msg: Struct<'ctx, CSlice>, + params: [Option>; 3], loc: Location, ) { - let i1 = self.ctx.bool_type(); - let i1_true = i1.const_all_ones(); - // we assume that the condition is most probably true, so the normal path is the most - // probable path - // even if this assumption is violated, it does not matter as exception unwinding is - // slow anyway... - let cond = call_expect(self, cond, i1_true, Some("expect")); + let type_context = generator.type_context(self.ctx); + let bool_model = IntModel(Bool); + + // We assume that the condition is most probably true, so the normal path is the most + // probable path even if this assumption is violated, it does not matter as exception unwinding is. + let cond = call_expect( + self, + generator.bool_to_i1(self, cond), + bool_model.const_true(type_context, self.ctx).value, + Some("expect"), + ); + let current_bb = self.builder.get_insert_block().unwrap(); let current_fun = current_bb.get_parent().unwrap(); + let then_block = self.ctx.insert_basic_block_after(current_bb, "succ"); let exn_block = self.ctx.append_basic_block(current_fun, "fail"); + self.builder.build_conditional_branch(cond, then_block, exn_block).unwrap(); + + // Inserting into `exn_block` self.builder.position_at_end(exn_block); self.raise_exn(generator, err_name, err_msg, params, loc); + + // Continuation self.builder.position_at_end(then_block); } } diff --git a/nac3core/src/codegen/irrt/mod.rs b/nac3core/src/codegen/irrt/mod.rs index 002cdb37..2a4c8b83 100644 --- a/nac3core/src/codegen/irrt/mod.rs +++ b/nac3core/src/codegen/irrt/mod.rs @@ -2,6 +2,7 @@ use crate::typecheck::typedef::Type; mod test; +use super::model::*; use super::{ classes::{ ArrayLikeIndexer, ArrayLikeValue, ArraySliceValue, ListValue, NDArrayValue, @@ -416,14 +417,29 @@ pub fn list_slice_assignment<'ctx, G: CodeGenerator + ?Sized>( .unwrap(); let cond_1 = ctx.builder.build_and(dest_step_eq_one, src_slt_dest, "slice_cond_1").unwrap(); let cond = ctx.builder.build_or(src_eq_dest, cond_1, "slice_cond").unwrap(); - ctx.make_assert( - generator, - cond, - "0:ValueError", - "attempt to assign sequence of size {0} to slice of size {1} with step size {2}", - [Some(src_slice_len), Some(dest_slice_len), Some(dest_idx.2)], - ctx.current_loc, - ); + + // TODO: Temporary fix. Rewrite `list_slice_assignment` later + // Exception params should have been i64 + { + let type_context = generator.type_context(ctx.ctx); + let param_model = IntModel(Int64); + + let src_slice_len = + param_model.s_extend_or_bit_cast(type_context, ctx, src_slice_len, "src_slice_len"); + let dest_slice_len = + param_model.s_extend_or_bit_cast(type_context, ctx, dest_slice_len, "dest_slice_len"); + let dest_idx_2 = + param_model.s_extend_or_bit_cast(type_context, ctx, dest_idx.2, "dest_idx_2"); + + ctx.make_assert( + generator, + cond, + "0:ValueError", + "attempt to assign sequence of size {0} to slice of size {1} with step size {2}", + [Some(src_slice_len.value), Some(dest_slice_len.value), Some(dest_idx_2.value)], + ctx.current_loc, + ); + } let new_len = { let args = vec![ diff --git a/nac3core/src/codegen/mod.rs b/nac3core/src/codegen/mod.rs index b96f3bd1..51edaa42 100644 --- a/nac3core/src/codegen/mod.rs +++ b/nac3core/src/codegen/mod.rs @@ -24,6 +24,7 @@ use inkwell::{ AddressSpace, IntPredicate, OptimizationLevel, }; use itertools::Itertools; +use model::*; use nac3parser::ast::{Location, Stmt, StrRef}; use parking_lot::{Condvar, Mutex}; use std::collections::{HashMap, HashSet}; @@ -32,6 +33,7 @@ use std::sync::{ Arc, }; use std::thread; +use structure::{cslice::CSlice, exception::Exception}; pub mod builtin_fns; pub mod classes; @@ -44,6 +46,7 @@ pub mod llvm_intrinsics; pub mod model; pub mod numpy; pub mod stmt; +pub mod structure; #[cfg(test)] mod test; @@ -169,11 +172,11 @@ pub struct CodeGenContext<'ctx, 'a> { pub registry: &'a WorkerRegistry, /// Cache for constant strings. - pub const_strings: HashMap>, + pub const_strings: HashMap>, /// [`BasicBlock`] containing all `alloca` statements for the current function. pub init_bb: BasicBlock<'ctx>, - pub exception_val: Option>, + pub exception_val: Option>>, /// The header and exit basic blocks of a loop in this context. See /// for explanation of these terminology. @@ -701,43 +704,20 @@ pub fn gen_func_impl< ..primitives }; - let mut type_cache: HashMap<_, _> = [ + let type_context = generator.type_context(context); + let cslice_model = StructModel(CSlice); + let pexn_model = PtrModel(StructModel(Exception)); + + let mut type_cache: HashMap<_, BasicTypeEnum<'ctx>> = [ (primitives.int32, context.i32_type().into()), (primitives.int64, context.i64_type().into()), (primitives.uint32, context.i32_type().into()), (primitives.uint64, context.i64_type().into()), (primitives.float, context.f64_type().into()), (primitives.bool, context.i8_type().into()), - (primitives.str, { - let name = "str"; - match module.get_struct_type(name) { - None => { - let str_type = context.opaque_struct_type("str"); - let fields = [ - context.i8_type().ptr_type(AddressSpace::default()).into(), - generator.get_size_type(context).into(), - ]; - str_type.set_body(&fields, false); - str_type.into() - } - Some(t) => t.as_basic_type_enum(), - } - }), + (primitives.str, cslice_model.get_type(type_context, context).into()), (primitives.range, RangeType::new(context).as_base_type().into()), - (primitives.exception, { - let name = "Exception"; - if let Some(t) = module.get_struct_type(name) { - t.ptr_type(AddressSpace::default()).as_basic_type_enum() - } else { - let exception = context.opaque_struct_type("Exception"); - let int32 = context.i32_type().into(); - let int64 = context.i64_type().into(); - let str_ty = module.get_struct_type("str").unwrap().as_basic_type_enum(); - let fields = [int32, str_ty, int32, int32, str_ty, str_ty, int64, int64, int64]; - exception.set_body(&fields, false); - exception.ptr_type(AddressSpace::default()).as_basic_type_enum() - } - }), + (primitives.exception, pexn_model.get_type(type_context, context).into()), ] .iter() .copied() diff --git a/nac3core/src/codegen/numpy.rs b/nac3core/src/codegen/numpy.rs index 6ebffe80..a5e0d774 100644 --- a/nac3core/src/codegen/numpy.rs +++ b/nac3core/src/codegen/numpy.rs @@ -257,7 +257,7 @@ fn ndarray_zero_value<'ctx, G: CodeGenerator + ?Sized>( } else if ctx.unifier.unioned(elem_ty, ctx.primitives.bool) { ctx.ctx.bool_type().const_zero().into() } else if ctx.unifier.unioned(elem_ty, ctx.primitives.str) { - ctx.gen_string(generator, "") + ctx.gen_string(generator, "").value.into() } else { unreachable!() } @@ -285,7 +285,7 @@ fn ndarray_one_value<'ctx, G: CodeGenerator + ?Sized>( } else if ctx.unifier.unioned(elem_ty, ctx.primitives.bool) { ctx.ctx.bool_type().const_int(1, false).into() } else if ctx.unifier.unioned(elem_ty, ctx.primitives.str) { - ctx.gen_string(generator, "1") + ctx.gen_string(generator, "1").value.into() } else { unreachable!() } diff --git a/nac3core/src/codegen/stmt.rs b/nac3core/src/codegen/stmt.rs index ea5869d6..85049730 100644 --- a/nac3core/src/codegen/stmt.rs +++ b/nac3core/src/codegen/stmt.rs @@ -1,8 +1,11 @@ +use super::model::*; +use super::structure::cslice::CSlice; use super::{ super::symbol_resolver::ValueEnum, expr::destructure_range, irrt::{handle_slice_indices, list_slice_assignment}, - CodeGenContext, CodeGenerator, + structure::exception::Exception, + CodeGenContext, CodeGenerator, Int32, IntModel, Ptr, StructModel, }; use crate::{ codegen::{ @@ -1259,47 +1262,37 @@ 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>>, 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(); + if let Some(pexn) = exception { + let type_context = generator.type_context(ctx.ctx); + let i32_model = IntModel(Int32); + let cslice_model = StructModel(CSlice); - 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(); - } + // Get and store filename + let filename = loc.file.0; + let filename = ctx.gen_string(generator, &String::from(filename)).value; + let filename = cslice_model.check_value(type_context, ctx.ctx, filename).unwrap(); + pexn.gep(ctx, |f| f.filename).store(ctx, filename); + + let row = i32_model.constant(type_context, ctx.ctx, loc.row as u64); + pexn.gep(ctx, |f| f.line).store(ctx, row); + + let column = i32_model.constant(type_context, ctx.ctx, loc.column as u64); + pexn.gep(ctx, |f| f.column).store(ctx, column); + + let current_fn = ctx.builder.get_insert_block().unwrap().get_parent().unwrap(); + let fn_name = ctx.gen_string(generator, current_fn.get_name().to_str().unwrap()); + pexn.gep(ctx, |f| f.function).store(ctx, fn_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, &[pexn.value.into()], "raise"); } else { let resume = get_builtins(generator, ctx, "__nac3_resume"); ctx.build_call_or_invoke(resume, &[], "resume"); } + ctx.builder.build_unreachable().unwrap(); } @@ -1761,30 +1754,43 @@ pub fn gen_stmt( } else { return Ok(()); }; - gen_raise(generator, ctx, Some(&exc), stmt.location); + + let type_context = generator.type_context(ctx.ctx); + let pexn_model = PtrModel(StructModel(Exception)); + let exn = pexn_model.check_value(type_context, ctx.ctx, exc).unwrap(); + + gen_raise(generator, ctx, Some(exn), stmt.location); } else { gen_raise(generator, ctx, None, stmt.location); } } StmtKind::Assert { test, msg, .. } => { - let test = if let Some(v) = generator.gen_expr(ctx, test)? { - v.to_basic_value_enum(ctx, generator, test.custom.unwrap())? - } else { + let type_context = generator.type_context(ctx.ctx); + let byte_model = IntModel(Byte); + let cslice_model = StructModel(CSlice); + + let Some(test) = generator.gen_expr(ctx, test)? else { return Ok(()); }; + let test = test.to_basic_value_enum(ctx, generator, ctx.primitives.bool)?; + let test = byte_model.check_value(type_context, ctx.ctx, test).unwrap(); // Python `bool` is represented as `i8` in nac3core + + // Check `msg` let err_msg = match msg { Some(msg) => { - if let Some(v) = generator.gen_expr(ctx, msg)? { - v.to_basic_value_enum(ctx, generator, msg.custom.unwrap())? - } else { + let Some(msg) = generator.gen_expr(ctx, msg)? else { return Ok(()); - } + }; + + let msg = msg.to_basic_value_enum(ctx, generator, ctx.primitives.str)?; + cslice_model.check_value(type_context, ctx.ctx, msg).unwrap() } None => ctx.gen_string(generator, ""), }; + ctx.make_assert_impl( generator, - generator.bool_to_i1(ctx, test.into_int_value()), + test.value, "0:AssertionError", err_msg, [None, None, None], diff --git a/nac3core/src/codegen/structure/cslice.rs b/nac3core/src/codegen/structure/cslice.rs new file mode 100644 index 00000000..481d1fe8 --- /dev/null +++ b/nac3core/src/codegen/structure/cslice.rs @@ -0,0 +1,43 @@ +use crate::codegen::{model::*, CodeGenContext}; + +/// Fields of [`CSlice<'ctx>`]. +pub struct CSliceFields<'ctx, F: FieldTraversal<'ctx>> { + /// Pointer to data. + pub base: F::Out>>, + /// Number of bytes of data. + pub len: F::Out>, +} + +/// See . +/// +/// Additionally, see ) +/// for ARTIQ-specific notes. +#[derive(Debug, Clone, Copy, Default)] +pub struct CSlice; + +impl<'ctx> StructKind<'ctx> for CSlice { + type Fields> = CSliceFields<'ctx, F>; + + fn traverse_fields>(&self, traversal: &mut F) -> Self::Fields { + Self::Fields { base: traversal.add_auto("base"), len: traversal.add_auto("len") } + } +} + +impl StructModel { + /// Create a [`CSlice`]. + /// + /// `base` and `len` must be LLVM global constants. + pub fn create_const<'ctx>( + &self, + type_context: TypeContext<'ctx>, + ctx: &CodeGenContext<'ctx, '_>, + base: Ptr<'ctx, IntModel>, + len: Int<'ctx, SizeT>, + ) -> Struct<'ctx, CSlice> { + let value = self + .0 + .get_struct_type(type_context, ctx.ctx) + .const_named_struct(&[base.value.into(), len.value.into()]); + self.believe_value(value) + } +} diff --git a/nac3core/src/codegen/structure/exception.rs b/nac3core/src/codegen/structure/exception.rs new file mode 100644 index 00000000..9072527c --- /dev/null +++ b/nac3core/src/codegen/structure/exception.rs @@ -0,0 +1,61 @@ +use crate::codegen::model::*; + +use super::cslice::CSlice; + +/// The LLVM int type of an Exception ID. +pub type ExceptionId = Int32; + +/// Fields of [`Exception<'ctx>`] +/// +/// The definition came from `pub struct Exception<'a>` in +/// . +pub struct ExceptionFields<'ctx, F: FieldTraversal<'ctx>> { + /// nac3core's ID of the exception + pub id: F::Out>, + /// The name of the file this `Exception` was raised in. + pub filename: F::Out>, + /// The line number in the file this `Exception` was raised in. + pub line: F::Out>, + /// The column number in the file this `Exception` was raised in. + pub column: F::Out>, + /// The name of the Python function this `Exception` was raised in. + pub function: F::Out>, + /// The message of this Exception. + /// + /// The message can optionally contain integer parameters `{0}`, `{1}`, and `{2}` in its string, + /// where they will be substituted by `params[0]`, `params[1]`, and `params[2]` respectively (as `int64_t`s). + /// Here is an example: + /// + /// ```ignore + /// "Index {0} is out of bounds! List only has {1} element(s)." + /// ``` + /// + /// In this case, `params[0]` and `params[1]` must be specified, and `params[2]` is ***unused***. + /// Having only 3 parameters is a constraint in ARTIQ. + pub msg: F::Out>, + pub params: [F::Out>; 3], +} + +/// nac3core & ARTIQ's Exception +#[derive(Debug, Clone, Copy, Default)] +pub struct Exception; + +impl<'ctx> StructKind<'ctx> for Exception { + type Fields> = ExceptionFields<'ctx, F>; + + fn traverse_fields>(&self, traversal: &mut F) -> Self::Fields { + Self::Fields { + id: traversal.add_auto("id"), + filename: traversal.add_auto("filename"), + line: traversal.add_auto("line"), + column: traversal.add_auto("column"), + function: traversal.add_auto("function"), + msg: traversal.add_auto("msg"), + params: [ + traversal.add_auto("params[0]"), + traversal.add_auto("params[1]"), + traversal.add_auto("params[2]"), + ], + } + } +} diff --git a/nac3core/src/codegen/structure/mod.rs b/nac3core/src/codegen/structure/mod.rs new file mode 100644 index 00000000..14b596fb --- /dev/null +++ b/nac3core/src/codegen/structure/mod.rs @@ -0,0 +1,2 @@ +pub mod cslice; +pub mod exception;