forked from M-Labs/nac3
1
0
Fork 0

core/irrt: add ErrorContext

ErrorContext allows IRRT to report a Python-like exception
This commit is contained in:
lyken 2024-07-26 14:06:18 +08:00
parent 80e56bc081
commit 9c3a10377f
8 changed files with 431 additions and 15 deletions

View File

@ -0,0 +1,39 @@
#pragma once
#include <irrt/int_defs.hpp>
/*
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 <typename SizeT>
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 <typename SizeT>
struct Exception {
ExceptionId id;
CSlice<SizeT> file;
uint32_t line;
uint32_t column;
CSlice<SizeT> function;
CSlice<SizeT> message;
uint32_t param;
};

View File

@ -0,0 +1,94 @@
#pragma once
#include <irrt/artiq_defs.hpp>
#include <irrt/int_defs.hpp>
#include <irrt/utils.hpp>
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 <typename SizeT>
void get_error_str(CSlice<SizeT> *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<int32_t> *dst_str) {
errctx->get_error_str<int32_t>(dst_str);
}
void __nac3_error_context_get_error_str64(ErrorContext *errctx,
CSlice<int64_t> *dst_str) {
errctx->get_error_str<int64_t>(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");
}
}

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include <irrt/artiq_defs.hpp>
#include <irrt/core.hpp> #include <irrt/core.hpp>
#include <irrt/error_context.hpp>
#include <irrt/int_defs.hpp> #include <irrt/int_defs.hpp>
#include <irrt/utils.hpp> #include <irrt/utils.hpp>

View File

@ -114,3 +114,66 @@ void __assert_values_match(const char* file, int line, T expected, T got) {
#define assert_values_match(expected, got) \ #define assert_values_match(expected, got) \
__assert_values_match(__FILE__, __LINE__, expected, got) __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("<no error>\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)

View File

@ -577,17 +577,15 @@ impl<'ctx, 'a> CodeGenContext<'ctx, 'a> {
}) })
} }
pub fn raise_exn<G: CodeGenerator + ?Sized>( pub fn raise_exn_impl<G: CodeGenerator + ?Sized>(
&mut self, &mut self,
generator: &mut G, generator: &mut G,
name: &str, exn_id: NInt<'ctx, ExceptionId>,
msg: Struct<'ctx, CSlice<'ctx>>, msg: Struct<'ctx, CSlice<'ctx>>,
params: [Option<NInt<'ctx, Int64>>; 3], params: [Option<NInt<'ctx, Int64>>; 3],
loc: Location, loc: Location,
) { ) {
// Define all used models
let sizet = generator.get_sizet(self.ctx); let sizet = generator.get_sizet(self.ctx);
let exception_id_model = NIntModel(ExceptionId::default());
let exception_model = StructModel(Exception { sizet }); let exception_model = StructModel(Exception { sizet });
// Get `exn` // Get `exn`
@ -596,17 +594,9 @@ impl<'ctx, 'a> CodeGenContext<'ctx, 'a> {
*self.exception_val.insert(exn) *self.exception_val.insert(exn)
}); });
// Now load everything into `exn` // Only have to store `exception_id`, `message`, and the parameters.
// Store `exception_id` exn.gep(self, |f| f.exception_id).store(self, exn_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`
exn.gep(self, |f| f.message).store(self, msg); exn.gep(self, |f| f.message).store(self, msg);
// Store `params`
for (i, param) in params.iter().enumerate() { for (i, param) in params.iter().enumerate() {
if let Some(param) = param { if let Some(param) = param {
exn.gep(self, |f| f.params[i]).store(self, *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); gen_raise(generator, self, Some(exn), loc);
} }
pub fn raise_exn<G: CodeGenerator + ?Sized>(
&mut self,
generator: &mut G,
name: &str,
msg: Struct<'ctx, CSlice<'ctx>>,
params: [Option<NInt<'ctx, Int64>>; 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<G: CodeGenerator + ?Sized>( pub fn make_assert<G: CodeGenerator + ?Sized>(
&mut self, &mut self,
generator: &mut G, generator: &mut G,

View File

@ -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<NIntModel<ErrorId>>,
pub value_error: Field<NIntModel<ErrorId>>,
pub assertion_error: Field<NIntModel<ErrorId>>,
pub runtime_error: Field<NIntModel<ErrorId>>,
pub type_error: Field<NIntModel<ErrorId>>,
}
/// 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<PointerModel<StructModel<ErrorIds>>>,
pub error_id: Field<NIntModel<ErrorId>>,
pub message_template: Field<PointerModel<NIntModel<Byte>>>,
pub param1: Field<NIntModel<Int64>>,
pub param2: Field<NIntModel<Int64>>,
pub param3: Field<NIntModel<Int64>>,
}
/// 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<ErrorIds>> {
// 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<ErrorContext>>,
perror_ids: Pointer<'ctx, StructModel<ErrorIds>>,
) {
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<ErrorContext>>,
) -> 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<ErrorContext>>,
dst_str: Pointer<'ctx, StructModel<CSlice<'ctx>>>,
) {
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<ErrorContext>> {
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<ErrorContext>>,
) {
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<G: CodeGenerator + ?Sized>(
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);
}

View File

@ -1,6 +1,8 @@
use crate::typecheck::typedef::Type; use crate::typecheck::typedef::Type;
mod error_context;
mod test; mod test;
mod util;
use super::{ use super::{
classes::{ classes::{

View File

@ -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
}