From 9c3a10377f42be5216b5d85492ae04c74ffeb1c0 Mon Sep 17 00:00:00 2001 From: lyken Date: Fri, 26 Jul 2024 14:06:18 +0800 Subject: [PATCH] core/irrt: add ErrorContext ErrorContext allows IRRT to report a Python-like exception --- nac3core/irrt/irrt/artiq_defs.hpp | 39 +++++ nac3core/irrt/irrt/error_context.hpp | 94 ++++++++++ nac3core/irrt/irrt_everything.hpp | 2 + nac3core/irrt/test/util.hpp | 65 ++++++- nac3core/src/codegen/expr.rs | 34 ++-- nac3core/src/codegen/irrt/error_context.rs | 194 +++++++++++++++++++++ nac3core/src/codegen/irrt/mod.rs | 2 + nac3core/src/codegen/irrt/util.rs | 16 ++ 8 files changed, 431 insertions(+), 15 deletions(-) create mode 100644 nac3core/irrt/irrt/artiq_defs.hpp create mode 100644 nac3core/irrt/irrt/error_context.hpp create mode 100644 nac3core/src/codegen/irrt/error_context.rs create mode 100644 nac3core/src/codegen/irrt/util.rs diff --git a/nac3core/irrt/irrt/artiq_defs.hpp b/nac3core/irrt/irrt/artiq_defs.hpp new file mode 100644 index 00000000..a85b946f --- /dev/null +++ b/nac3core/irrt/irrt/artiq_defs.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include + +/* +This file defines all ARTIQ-specific structures +*/ + +/** + * @brief ARTIQ's `cslice` object + * + * See https://docs.rs/cslice/0.3.0/src/cslice/lib.rs.html#33-37 + */ +template +struct CSlice { + const char *base; + SizeT len; +}; + +/** + * @brief Int type of ARTIQ's `Exception` IDs. + */ +typedef uint32_t ExceptionId; + +/** + * @brief ARTIQ's `Exception` object + * + * See https://github.com/m-labs/artiq/blob/b0d2705c385f64b6e6711c1726cd9178f40b598e/artiq/firmware/libeh/eh_artiq.rs#L1C1-L17C1 + */ +template +struct Exception { + ExceptionId id; + CSlice file; + uint32_t line; + uint32_t column; + CSlice function; + CSlice message; + uint32_t param; +}; \ No newline at end of file diff --git a/nac3core/irrt/irrt/error_context.hpp b/nac3core/irrt/irrt/error_context.hpp new file mode 100644 index 00000000..568f680b --- /dev/null +++ b/nac3core/irrt/irrt/error_context.hpp @@ -0,0 +1,94 @@ +#pragma once + +#include +#include +#include + +namespace { +/** + * @brief A (limited) set of known Error IDs + */ +struct ErrorIds { + ExceptionId index_error; + ExceptionId value_error; + ExceptionId assertion_error; + ExceptionId runtime_error; + ExceptionId type_error; +}; + +/** + * @brief The IRRT error context object + * + * This object contains all the details needed to propagate Python-like Exceptions in + * IRRT - within IRRT itself or propagate out of extern calls from nac3core. + */ +struct ErrorContext { + /** + * @brief The set of all + */ + const ErrorIds *error_ids; + + // Error thrown by IRRT + ExceptionId error_id; + const char *message_template; + uint64_t param1; + uint64_t param2; + uint64_t param3; + + void initialize(const ErrorIds *error_ids) { + this->error_ids = error_ids; + clear_error(); + } + + void clear_error() { + // Point the message_template to an empty str. Don't set it to nullptr + // as a sentinel + this->message_template = ""; + } + + void set_error(ExceptionId error_id, const char *message, + uint64_t param1 = 0, uint64_t param2 = 0, + uint64_t param3 = 0) { + this->error_id = error_id; + this->message_template = message; + this->param1 = param1; + this->param2 = param2; + this->param3 = param3; + } + + bool has_error() { return !cstr_utils::is_empty(message_template); } + + template + void get_error_str(CSlice *dst_str) { + dst_str->base = message_template; + dst_str->len = (SizeT)cstr_utils::length(message_template); + } +}; +} // namespace + +extern "C" { +void __nac3_error_context_initialize(ErrorContext *errctx, + ErrorIds *error_ids) { + errctx->initialize(error_ids); +} + +bool __nac3_error_context_has_no_error(ErrorContext *errctx) { + return !errctx->has_error(); +} + +void __nac3_error_context_get_error_str(ErrorContext *errctx, + CSlice *dst_str) { + errctx->get_error_str(dst_str); +} + +void __nac3_error_context_get_error_str64(ErrorContext *errctx, + CSlice *dst_str) { + errctx->get_error_str(dst_str); +} + +// Used for testing +void __nac3_error_dummy_raise(ErrorContext *errctx) { + errctx->set_error(errctx->error_ids->runtime_error, + "Error thrown from __nac3_error_dummy_raise"); +} +} \ No newline at end of file diff --git a/nac3core/irrt/irrt_everything.hpp b/nac3core/irrt/irrt_everything.hpp index a1c45e1e..fa775263 100644 --- a/nac3core/irrt/irrt_everything.hpp +++ b/nac3core/irrt/irrt_everything.hpp @@ -1,5 +1,7 @@ #pragma once +#include #include +#include #include #include \ No newline at end of file diff --git a/nac3core/irrt/test/util.hpp b/nac3core/irrt/test/util.hpp index 14711b8f..d881915c 100644 --- a/nac3core/irrt/test/util.hpp +++ b/nac3core/irrt/test/util.hpp @@ -113,4 +113,67 @@ void __assert_values_match(const char* file, int line, T expected, T got) { } #define assert_values_match(expected, got) \ - __assert_values_match(__FILE__, __LINE__, expected, got) \ No newline at end of file + __assert_values_match(__FILE__, __LINE__, expected, got) + +// A fake set of ErrorIds for testing only +const ErrorIds TEST_ERROR_IDS = { + .index_error = 0, + .value_error = 1, + .assertion_error = 2, + .runtime_error = 3, + .type_error = 4, +}; + +ErrorContext create_testing_errctx() { + // Everything is global so it is fine to directly return a struct + // ErrorContext + ErrorContext errctx; + errctx.initialize(&TEST_ERROR_IDS); + return errctx; +} + +void print_errctx_content(ErrorContext* errctx) { + if (errctx->has_error()) { + printf( + "(Error ID %d): %s ... where param1 = %ld, param2 = %ld, param3 = " + "%ld\n", + errctx->error_id, errctx->message_template, errctx->param1, + errctx->param2, errctx->param3); + } else { + printf("\n"); + } +} + +void __assert_errctx_no_error(const char* file, int line, + ErrorContext* errctx) { + if (errctx->has_error()) { + print_assertion_failed(file, line); + printf("Expecting no error but caught the following:\n\n"); + print_errctx_content(errctx); + test_fail(); + } +} + +#define assert_errctx_no_error(errctx) \ + __assert_errctx_no_error(__FILE__, __LINE__, errctx) + +void __assert_errctx_has_error(const char* file, int line, ErrorContext* errctx, + ExceptionId expected_error_id) { + if (errctx->has_error()) { + if (errctx->error_id != expected_error_id) { + print_assertion_failed(file, line); + printf( + "Expecting error id %d but got error id %d. Error caught:\n\n", + expected_error_id, errctx->error_id); + print_errctx_content(errctx); + test_fail(); + } + } else { + print_assertion_failed(file, line); + printf("Expecting an error, but there is none."); + test_fail(); + } +} + +#define assert_errctx_has_error(errctx, expected_error_id) \ + __assert_errctx_has_error(__FILE__, __LINE__, errctx, expected_error_id) \ No newline at end of file diff --git a/nac3core/src/codegen/expr.rs b/nac3core/src/codegen/expr.rs index 02bac942..2648a44c 100644 --- a/nac3core/src/codegen/expr.rs +++ b/nac3core/src/codegen/expr.rs @@ -577,17 +577,15 @@ impl<'ctx, 'a> CodeGenContext<'ctx, 'a> { }) } - pub fn raise_exn( + pub fn raise_exn_impl( &mut self, generator: &mut G, - name: &str, + exn_id: NInt<'ctx, ExceptionId>, msg: Struct<'ctx, CSlice<'ctx>>, params: [Option>; 3], loc: Location, ) { - // Define all used models let sizet = generator.get_sizet(self.ctx); - let exception_id_model = NIntModel(ExceptionId::default()); let exception_model = StructModel(Exception { sizet }); // Get `exn` @@ -596,17 +594,9 @@ impl<'ctx, 'a> CodeGenContext<'ctx, 'a> { *self.exception_val.insert(exn) }); - // Now load everything into `exn` - // Store `exception_id` - exn.gep(self, |f| f.exception_id).store( - self, - exception_id_model.constant(self.ctx, self.resolver.get_string_id(name) as u64), - ); - - // Store `message` + // Only have to store `exception_id`, `message`, and the parameters. + exn.gep(self, |f| f.exception_id).store(self, exn_id); exn.gep(self, |f| f.message).store(self, msg); - - // Store `params` for (i, param) in params.iter().enumerate() { if let Some(param) = param { exn.gep(self, |f| f.params[i]).store(self, *param); @@ -616,6 +606,22 @@ impl<'ctx, 'a> CodeGenContext<'ctx, 'a> { gen_raise(generator, self, Some(exn), loc); } + pub fn raise_exn( + &mut self, + generator: &mut G, + name: &str, + msg: Struct<'ctx, CSlice<'ctx>>, + params: [Option>; 3], + loc: Location, + ) { + let exn_id_model = NIntModel(ExceptionId::default()); + + let exn_id = self.resolver.get_string_id(name); + let exn_id = exn_id_model.constant(self.ctx, exn_id as u64); + + self.raise_exn_impl(generator, exn_id, msg, params, loc) + } + pub fn make_assert( &mut self, generator: &mut G, diff --git a/nac3core/src/codegen/irrt/error_context.rs b/nac3core/src/codegen/irrt/error_context.rs new file mode 100644 index 00000000..f15ecac8 --- /dev/null +++ b/nac3core/src/codegen/irrt/error_context.rs @@ -0,0 +1,194 @@ +use crate::codegen::{model::*, structs::cslice::CSlice, CodeGenContext, CodeGenerator}; + +use super::util::get_sized_dependent_function_name; + +/// The [`IntModel`] of nac3core's error ID. +/// +/// It is always [`Int32`]. +type ErrorId = Int32; + +pub struct ErrorIdsFields { + pub index_error: Field>, + pub value_error: Field>, + pub assertion_error: Field>, + pub runtime_error: Field>, + pub type_error: Field>, +} + +/// Corresponds to IRRT's `struct ErrorIds` +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub struct ErrorIds; + +impl<'ctx> StructKind<'ctx> for ErrorIds { + type Fields = ErrorIdsFields; + + fn struct_name(&self) -> &'static str { + "ErrorIds" + } + + fn build_fields(&self, builder: &mut FieldBuilder) -> Self::Fields { + Self::Fields { + index_error: builder.add_field_auto("index_error"), + value_error: builder.add_field_auto("value_error"), + assertion_error: builder.add_field_auto("assertion_error"), + runtime_error: builder.add_field_auto("runtime_error"), + type_error: builder.add_field_auto("type_error"), + } + } +} + +pub struct ErrorContextFields { + pub error_ids: Field>>, + pub error_id: Field>, + pub message_template: Field>>, + pub param1: Field>, + pub param2: Field>, + pub param3: Field>, +} + +/// Corresponds to IRRT's `struct ErrorContext` +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub struct ErrorContext; + +impl<'ctx> StructKind<'ctx> for ErrorContext { + type Fields = ErrorContextFields; + + fn struct_name(&self) -> &'static str { + "ErrorIds" + } + + fn build_fields(&self, builder: &mut FieldBuilder) -> Self::Fields { + Self::Fields { + error_ids: builder.add_field_auto("error_ids"), + error_id: builder.add_field_auto("error_id"), + message_template: builder.add_field_auto("message_template"), + param1: builder.add_field_auto("param1"), + param2: builder.add_field_auto("param2"), + param3: builder.add_field_auto("param3"), + } + } +} + +// Prepare ErrorIds +fn build_error_ids<'ctx>(ctx: &CodeGenContext<'ctx, '_>) -> Pointer<'ctx, StructModel> { + // ErrorIdsLens.get_fields(ctx.ctx).assertion_error. + let error_ids = StructModel(ErrorIds).alloca(ctx, "error_ids"); + let i32_model = NIntModel(Int32); + // i32_model.make_constant() + + let get_string_id = + |string_id| i32_model.constant(ctx.ctx, ctx.resolver.get_string_id(string_id) as u64); + + error_ids.gep(ctx, |f| f.index_error).store(ctx, get_string_id("0:IndexError")); + error_ids.gep(ctx, |f| f.value_error).store(ctx, get_string_id("0:ValueError")); + error_ids.gep(ctx, |f| f.assertion_error).store(ctx, get_string_id("0:AssertionError")); + error_ids.gep(ctx, |f| f.runtime_error).store(ctx, get_string_id("0:RuntimeError")); + error_ids.gep(ctx, |f| f.type_error).store(ctx, get_string_id("0:TypeError")); + + error_ids +} + +pub fn call_nac3_error_context_initialize<'ctx>( + ctx: &CodeGenContext<'ctx, '_>, + perrctx: Pointer<'ctx, StructModel>, + perror_ids: Pointer<'ctx, StructModel>, +) { + FunctionBuilder::begin(ctx, "__nac3_error_context_initialize") + .arg("errctx", perrctx) + .arg("error_ids", perror_ids) + .returning_void(); +} + +pub fn call_nac3_error_context_has_no_error<'ctx>( + ctx: &CodeGenContext<'ctx, '_>, + errctx: Pointer<'ctx, StructModel>, +) -> NInt<'ctx, Bool> { + FunctionBuilder::begin(ctx, "__nac3_error_context_has_no_error") + .arg("errctx", errctx) + .returning("has_error", NIntModel(Bool)) +} + +pub fn call_nac3_error_context_get_error_str<'ctx, G: CodeGenerator + ?Sized>( + generator: &mut G, + ctx: &CodeGenContext<'ctx, '_>, + errctx: Pointer<'ctx, StructModel>, + dst_str: Pointer<'ctx, StructModel>>, +) { + let sizet = generator.get_sizet(ctx.ctx); + + FunctionBuilder::begin( + ctx, + &get_sized_dependent_function_name(sizet, "__nac3_error_context_get_error_str"), + ) + .arg("errctx", errctx) + .arg("dst_str", dst_str) + .returning_void(); +} + +/// Setup a [`ErrorContext`] that could +/// be passed to IRRT functions taking in a `ErrorContext* errctx` +/// for error reporting purposes. +/// +/// Also see: [`check_error_context`] +pub fn setup_error_context<'ctx>( + ctx: &CodeGenContext<'ctx, '_>, +) -> Pointer<'ctx, StructModel> { + let error_ids = build_error_ids(ctx); + let errctx_ptr = StructModel(ErrorContext).alloca(ctx, "errctx"); + call_nac3_error_context_initialize(ctx, errctx_ptr, error_ids); + errctx_ptr +} + +/// Check a [`ErrorContext`] to see +/// if it contains error. +/// +/// If there is an error, an LLVM exception will be raised at runtime. +pub fn check_error_context<'ctx, G: CodeGenerator + ?Sized>( + generator: &mut G, + ctx: &mut CodeGenContext<'ctx, '_>, + errctx_ptr: Pointer<'ctx, StructModel>, +) { + let sizet = generator.get_sizet(ctx.ctx); + let cslice_model = StructModel(CSlice { sizet }); + + let current_bb = ctx.builder.get_insert_block().unwrap(); + let irrt_has_error_bb = ctx.ctx.insert_basic_block_after(current_bb, "irrt_has_error"); + let end_bb = ctx.ctx.insert_basic_block_after(irrt_has_error_bb, "end"); + + // Inserting into `current_bb` + let has_error = call_nac3_error_context_has_no_error(ctx, errctx_ptr); + ctx.builder.build_conditional_branch(has_error.value, irrt_has_error_bb, end_bb).unwrap(); + + // Inserting into `irrt_has_error_bb` + ctx.builder.position_at_end(irrt_has_error_bb); + + // Load all the values for `ctx.make_assert_impl_by_id` + let pstr = cslice_model.alloca(ctx, "error_str"); + call_nac3_error_context_get_error_str(generator, ctx, errctx_ptr, pstr); + + let error_id = errctx_ptr.gep(ctx, |f| f.error_id).load(ctx, "error_id"); + let msg = pstr.load(ctx, "msg"); + let param1 = errctx_ptr.gep(ctx, |f| f.param1).load(ctx, "param1"); + let param2 = errctx_ptr.gep(ctx, |f| f.param2).load(ctx, "param2"); + let param3 = errctx_ptr.gep(ctx, |f| f.param3).load(ctx, "param3"); + + ctx.raise_exn_impl( + generator, + error_id, + msg, + [Some(param1), Some(param2), Some(param3)], + ctx.current_loc, + ); + + // Position to `end_bb` for continuation + ctx.builder.position_at_end(end_bb); +} + +pub fn call_nac3_dummy_raise( + generator: &mut G, + ctx: &mut CodeGenContext, +) { + let errctx = setup_error_context(ctx); + FunctionBuilder::begin(ctx, "__nac3_error_dummy_raise").arg("errctx", errctx).returning_void(); + check_error_context(generator, ctx, errctx); +} diff --git a/nac3core/src/codegen/irrt/mod.rs b/nac3core/src/codegen/irrt/mod.rs index add6bf8c..cffdf920 100644 --- a/nac3core/src/codegen/irrt/mod.rs +++ b/nac3core/src/codegen/irrt/mod.rs @@ -1,6 +1,8 @@ use crate::typecheck::typedef::Type; +mod error_context; mod test; +mod util; use super::{ classes::{ diff --git a/nac3core/src/codegen/irrt/util.rs b/nac3core/src/codegen/irrt/util.rs new file mode 100644 index 00000000..015c84de --- /dev/null +++ b/nac3core/src/codegen/irrt/util.rs @@ -0,0 +1,16 @@ +use crate::codegen::model::*; + +#[must_use] +pub fn get_sized_dependent_function_name(sizet: SizeTModel<'_>, fn_name: &str) -> String { + // When its 32-bits, the function name is "{fn_name}" + // When its 64-bits, the function name is "{fn_name}64" + let mut fn_name = fn_name.to_owned(); + match sizet.0.get_bit_width() { + 32 => {} + 64 => fn_name.push_str("64"), + bit_width => { + panic!("Unsupported int type bit width {bit_width}, must be either 32-bits or 64-bits") + } + } + fn_name +}