forked from M-Labs/nac3
core/irrt: add ErrorContext
ErrorContext allows IRRT to report a Python-like exception
This commit is contained in:
parent
80e56bc081
commit
9c3a10377f
39
nac3core/irrt/irrt/artiq_defs.hpp
Normal file
39
nac3core/irrt/irrt/artiq_defs.hpp
Normal 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;
|
||||
};
|
94
nac3core/irrt/irrt/error_context.hpp
Normal file
94
nac3core/irrt/irrt/error_context.hpp
Normal 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");
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <irrt/artiq_defs.hpp>
|
||||
#include <irrt/core.hpp>
|
||||
#include <irrt/error_context.hpp>
|
||||
#include <irrt/int_defs.hpp>
|
||||
#include <irrt/utils.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)
|
||||
__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)
|
@ -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,
|
||||
generator: &mut G,
|
||||
name: &str,
|
||||
exn_id: NInt<'ctx, ExceptionId>,
|
||||
msg: Struct<'ctx, CSlice<'ctx>>,
|
||||
params: [Option<NInt<'ctx, Int64>>; 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<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>(
|
||||
&mut self,
|
||||
generator: &mut G,
|
||||
|
194
nac3core/src/codegen/irrt/error_context.rs
Normal file
194
nac3core/src/codegen/irrt/error_context.rs
Normal 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);
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
use crate::typecheck::typedef::Type;
|
||||
|
||||
mod error_context;
|
||||
mod test;
|
||||
mod util;
|
||||
|
||||
use super::{
|
||||
classes::{
|
||||
|
16
nac3core/src/codegen/irrt/util.rs
Normal file
16
nac3core/src/codegen/irrt/util.rs
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user