forked from M-Labs/nac3
core/irrt: add ErrorContext
This commit is contained in:
parent
5b7588df75
commit
7502b14d55
|
@ -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;
|
||||||
|
};
|
|
@ -0,0 +1,92 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <irrt/artiq_defs.hpp>
|
||||||
|
#include <irrt/int_defs.hpp>
|
||||||
|
#include <irrt/utils.hpp>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
/**
|
||||||
|
* @brief A (limited) set of known Exception IDs usable in IRRT
|
||||||
|
*/
|
||||||
|
struct ErrorContextExceptions {
|
||||||
|
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 {
|
||||||
|
const ErrorContextExceptions *exceptions;
|
||||||
|
|
||||||
|
// Exception thrown by IRRT
|
||||||
|
ExceptionId exception_id;
|
||||||
|
// Points to empty c-string if there is no thrown Exception
|
||||||
|
const char *msg;
|
||||||
|
uint64_t param1;
|
||||||
|
uint64_t param2;
|
||||||
|
uint64_t param3;
|
||||||
|
|
||||||
|
void initialize(const ErrorContextExceptions *exceptions) {
|
||||||
|
this->exceptions = exceptions;
|
||||||
|
clear_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_error() {
|
||||||
|
// NOTE: Point the msg to an empty str.
|
||||||
|
// Don't set it to nullptr - to implement `has_exception`
|
||||||
|
this->msg = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_exception(ExceptionId exception_id, const char *msg,
|
||||||
|
uint64_t param1 = 0, uint64_t param2 = 0,
|
||||||
|
uint64_t param3 = 0) {
|
||||||
|
this->exception_id = exception_id;
|
||||||
|
this->msg = msg;
|
||||||
|
this->param1 = param1;
|
||||||
|
this->param2 = param2;
|
||||||
|
this->param3 = param3;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has_exception() { return !cstr_utils::is_empty(msg); }
|
||||||
|
|
||||||
|
template <typename SizeT>
|
||||||
|
void get_exception_str(CSlice<SizeT> *dst_str) {
|
||||||
|
dst_str->base = msg;
|
||||||
|
dst_str->len = (SizeT)cstr_utils::length(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
void __nac3_error_context_initialize(ErrorContext *errctx,
|
||||||
|
ErrorContextExceptions *exceptions) {
|
||||||
|
errctx->initialize(exceptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool __nac3_error_context_has_exception(ErrorContext *errctx) {
|
||||||
|
return errctx->has_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
void __nac3_error_context_get_exception_str(ErrorContext *errctx,
|
||||||
|
CSlice<int32_t> *dst_str) {
|
||||||
|
errctx->get_exception_str<int32_t>(dst_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void __nac3_error_context_get_exception_str64(ErrorContext *errctx,
|
||||||
|
CSlice<int64_t> *dst_str) {
|
||||||
|
errctx->get_exception_str<int64_t>(dst_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used for testing
|
||||||
|
void __nac3_error_dummy_raise(ErrorContext *errctx) {
|
||||||
|
errctx->set_exception(errctx->exceptions->runtime_error,
|
||||||
|
"Error thrown from __nac3_error_dummy_raise");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
|
@ -113,4 +113,71 @@ 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 ExceptionIds for testing only
|
||||||
|
const ErrorContextExceptions TEST_ERROR_CONTEXT_EXCEPTIONS = {
|
||||||
|
.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_CONTEXT_EXCEPTIONS);
|
||||||
|
return errctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_errctx_content(ErrorContext* errctx) {
|
||||||
|
if (errctx->has_exception()) {
|
||||||
|
printf(
|
||||||
|
"(Exception ID %d): %s ... where param1 = %ld, param2 = %ld, "
|
||||||
|
"param3 = "
|
||||||
|
"%ld\n",
|
||||||
|
errctx->exception_id, errctx->msg, errctx->param1, errctx->param2,
|
||||||
|
errctx->param3);
|
||||||
|
} else {
|
||||||
|
printf("<no exception>\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void __assert_errctx_no_exception(const char* file, int line,
|
||||||
|
ErrorContext* errctx) {
|
||||||
|
if (errctx->has_exception()) {
|
||||||
|
print_assertion_failed(file, line);
|
||||||
|
printf("Expecting no exception but caught the following:\n\n");
|
||||||
|
print_errctx_content(errctx);
|
||||||
|
test_fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define assert_errctx_no_exception(errctx) \
|
||||||
|
__assert_errctx_no_exception(__FILE__, __LINE__, errctx)
|
||||||
|
|
||||||
|
void __assert_errctx_has_exception(const char* file, int line,
|
||||||
|
ErrorContext* errctx,
|
||||||
|
ExceptionId expected_exception_id) {
|
||||||
|
if (errctx->has_exception()) {
|
||||||
|
if (errctx->exception_id != expected_exception_id) {
|
||||||
|
print_assertion_failed(file, line);
|
||||||
|
printf(
|
||||||
|
"Expecting exception id %d but got exception id %d. Error "
|
||||||
|
"caught:\n\n",
|
||||||
|
expected_exception_id, errctx->exception_id);
|
||||||
|
print_errctx_content(errctx);
|
||||||
|
test_fail();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print_assertion_failed(file, line);
|
||||||
|
printf("Expecting an exception, but there is none.");
|
||||||
|
test_fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define assert_errctx_has_exception(errctx, expected_exception_id) \
|
||||||
|
__assert_errctx_has_exception(__FILE__, __LINE__, errctx, \
|
||||||
|
expected_exception_id)
|
|
@ -572,20 +572,16 @@ 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: Int<'ctx, ExceptionId>,
|
||||||
msg: Struct<'ctx, CSlice>,
|
msg: Struct<'ctx, CSlice>,
|
||||||
params: [Option<Int<'ctx, Int64>>; 3],
|
params: [Option<Int<'ctx, Int64>>; 3],
|
||||||
loc: Location,
|
loc: Location,
|
||||||
) {
|
) {
|
||||||
let type_context = generator.type_context(self.ctx);
|
|
||||||
let exn_model = StructModel(Exception);
|
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 = self.exception_val.unwrap_or_else(|| {
|
||||||
let exn = exn_model.var_alloca(generator, self, Some("exn")).unwrap();
|
let exn = exn_model.var_alloca(generator, self, Some("exn")).unwrap();
|
||||||
*self.exception_val.insert(exn)
|
*self.exception_val.insert(exn)
|
||||||
|
@ -602,6 +598,23 @@ 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>,
|
||||||
|
params: [Option<Int<'ctx, Int64>>; 3],
|
||||||
|
loc: Location,
|
||||||
|
) {
|
||||||
|
let tyctx = generator.type_context(self.ctx);
|
||||||
|
let exn_id_model = IntModel(ExceptionId::default());
|
||||||
|
|
||||||
|
let exn_id = self.resolver.get_string_id(name);
|
||||||
|
let exn_id = exn_id_model.constant(tyctx, 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,
|
||||||
|
|
|
@ -0,0 +1,198 @@
|
||||||
|
use super::util::{function::CallFunction, get_sizet_dependent_function_name};
|
||||||
|
use crate::codegen::{
|
||||||
|
model::*,
|
||||||
|
structure::{cslice::CSlice, exception::ExceptionId},
|
||||||
|
CodeGenContext, CodeGenerator,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[allow(clippy::struct_field_names)]
|
||||||
|
pub struct ErrorContextExceptionsFields<F: FieldVisitor> {
|
||||||
|
pub index_error: F::Field<IntModel<ExceptionId>>,
|
||||||
|
pub value_error: F::Field<IntModel<ExceptionId>>,
|
||||||
|
pub assertion_error: F::Field<IntModel<ExceptionId>>,
|
||||||
|
pub runtime_error: F::Field<IntModel<ExceptionId>>,
|
||||||
|
pub type_error: F::Field<IntModel<ExceptionId>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Corresponds to IRRT's `struct ErrorContextExceptions`
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
|
pub struct ErrorContextExceptions;
|
||||||
|
|
||||||
|
impl StructKind for ErrorContextExceptions {
|
||||||
|
type Fields<F: FieldVisitor> = ErrorContextExceptionsFields<F>;
|
||||||
|
|
||||||
|
fn visit_fields<F: FieldVisitor>(&self, visitor: &mut F) -> Self::Fields<F> {
|
||||||
|
Self::Fields {
|
||||||
|
index_error: visitor.add("index_error"),
|
||||||
|
value_error: visitor.add("value_error"),
|
||||||
|
assertion_error: visitor.add("assertion_error"),
|
||||||
|
runtime_error: visitor.add("runtime_error"),
|
||||||
|
type_error: visitor.add("type_error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ErrorContextFields<F: FieldVisitor> {
|
||||||
|
pub exceptions: F::Field<PtrModel<StructModel<ErrorContextExceptions>>>,
|
||||||
|
pub exception_id: F::Field<IntModel<ExceptionId>>,
|
||||||
|
pub msg: F::Field<PtrModel<IntModel<Byte>>>,
|
||||||
|
pub param1: F::Field<IntModel<Int64>>,
|
||||||
|
pub param2: F::Field<IntModel<Int64>>,
|
||||||
|
pub param3: F::Field<IntModel<Int64>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Corresponds to IRRT's `struct ErrorContext`
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
|
pub struct ErrorContext;
|
||||||
|
|
||||||
|
impl StructKind for ErrorContext {
|
||||||
|
type Fields<F: FieldVisitor> = ErrorContextFields<F>;
|
||||||
|
|
||||||
|
fn visit_fields<F: FieldVisitor>(&self, visitor: &mut F) -> Self::Fields<F> {
|
||||||
|
Self::Fields {
|
||||||
|
exceptions: visitor.add("exceptions"),
|
||||||
|
exception_id: visitor.add("exception_id"),
|
||||||
|
msg: visitor.add("msg"),
|
||||||
|
param1: visitor.add("param1"),
|
||||||
|
param2: visitor.add("param2"),
|
||||||
|
param3: visitor.add("param3"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build an [`ErrorContextExceptions`] loaded with resolved [`ExceptionID`]s according to the [`SymbolResolver`].
|
||||||
|
fn build_error_context_exceptions<'ctx>(
|
||||||
|
tyctx: TypeContext<'ctx>,
|
||||||
|
ctx: &CodeGenContext<'ctx, '_>,
|
||||||
|
) -> Ptr<'ctx, StructModel<ErrorContextExceptions>> {
|
||||||
|
let exceptions =
|
||||||
|
StructModel(ErrorContextExceptions).alloca(tyctx, ctx, "error_context_exceptions");
|
||||||
|
let i32_model = IntModel(Int32);
|
||||||
|
|
||||||
|
let get_string_id = |string_id| {
|
||||||
|
i32_model.constant(tyctx, ctx.ctx, ctx.resolver.get_string_id(string_id) as u64)
|
||||||
|
};
|
||||||
|
|
||||||
|
exceptions.gep(ctx, |f| f.index_error).store(ctx, get_string_id("0:IndexError"));
|
||||||
|
exceptions.gep(ctx, |f| f.value_error).store(ctx, get_string_id("0:ValueError"));
|
||||||
|
exceptions.gep(ctx, |f| f.assertion_error).store(ctx, get_string_id("0:AssertionError"));
|
||||||
|
exceptions.gep(ctx, |f| f.runtime_error).store(ctx, get_string_id("0:RuntimeError"));
|
||||||
|
exceptions.gep(ctx, |f| f.type_error).store(ctx, get_string_id("0:TypeError"));
|
||||||
|
|
||||||
|
exceptions
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn call_nac3_error_context_initialize<'ctx>(
|
||||||
|
tyctx: TypeContext<'ctx>,
|
||||||
|
ctx: &CodeGenContext<'ctx, '_>,
|
||||||
|
perrctx: Ptr<'ctx, StructModel<ErrorContext>>,
|
||||||
|
pexceptions: Ptr<'ctx, StructModel<ErrorContextExceptions>>,
|
||||||
|
) {
|
||||||
|
CallFunction::begin(tyctx, ctx, "__nac3_error_context_initialize")
|
||||||
|
.arg("errctx", perrctx)
|
||||||
|
.arg("exceptions", pexceptions)
|
||||||
|
.returning_void();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn call_nac3_error_context_has_exception<'ctx>(
|
||||||
|
tyctx: TypeContext<'ctx>,
|
||||||
|
ctx: &CodeGenContext<'ctx, '_>,
|
||||||
|
perrctx: Ptr<'ctx, StructModel<ErrorContext>>,
|
||||||
|
) -> Int<'ctx, Bool> {
|
||||||
|
CallFunction::begin(tyctx, ctx, "__nac3_error_context_has_exception")
|
||||||
|
.arg("errctx", perrctx)
|
||||||
|
.returning("has_exception")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn call_nac3_error_context_get_exception_str<'ctx>(
|
||||||
|
tyctx: TypeContext<'ctx>,
|
||||||
|
ctx: &CodeGenContext<'ctx, '_>,
|
||||||
|
perrctx: Ptr<'ctx, StructModel<ErrorContext>>,
|
||||||
|
dst_str: Ptr<'ctx, StructModel<CSlice>>,
|
||||||
|
) {
|
||||||
|
CallFunction::begin(
|
||||||
|
tyctx,
|
||||||
|
ctx,
|
||||||
|
&get_sizet_dependent_function_name(tyctx, "__nac3_error_context_get_exception_str"),
|
||||||
|
)
|
||||||
|
.arg("errctx", perrctx)
|
||||||
|
.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>(
|
||||||
|
tyctx: TypeContext<'ctx>,
|
||||||
|
ctx: &CodeGenContext<'ctx, '_>,
|
||||||
|
) -> Ptr<'ctx, StructModel<ErrorContext>> {
|
||||||
|
let errctx_model = StructModel(ErrorContext);
|
||||||
|
|
||||||
|
let exceptions = build_error_context_exceptions(tyctx, ctx);
|
||||||
|
let errctx_ptr = errctx_model.alloca(tyctx, ctx, "errctx");
|
||||||
|
|
||||||
|
call_nac3_error_context_initialize(tyctx, ctx, errctx_ptr, exceptions);
|
||||||
|
|
||||||
|
errctx_ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check a [`ErrorContext`] to see if it contains error. **If there is an error,
|
||||||
|
/// a Pythonic exception will be raised in the firmware**.
|
||||||
|
pub fn check_error_context<'ctx, G: CodeGenerator + ?Sized>(
|
||||||
|
generator: &mut G,
|
||||||
|
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||||
|
perrctx: Ptr<'ctx, StructModel<ErrorContext>>,
|
||||||
|
) {
|
||||||
|
let tyctx = generator.type_context(ctx.ctx);
|
||||||
|
let cslice_model = StructModel(CSlice);
|
||||||
|
|
||||||
|
let current_bb = ctx.builder.get_insert_block().unwrap();
|
||||||
|
let irrt_has_exception_bb = ctx.ctx.insert_basic_block_after(current_bb, "irrt_has_exception");
|
||||||
|
let end_bb = ctx.ctx.insert_basic_block_after(irrt_has_exception_bb, "end");
|
||||||
|
|
||||||
|
// Inserting into `current_bb`
|
||||||
|
let has_exception = call_nac3_error_context_has_exception(tyctx, ctx, perrctx);
|
||||||
|
ctx.builder
|
||||||
|
.build_conditional_branch(has_exception.value, irrt_has_exception_bb, end_bb)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Inserting into `irrt_has_exception_bb`
|
||||||
|
ctx.builder.position_at_end(irrt_has_exception_bb);
|
||||||
|
|
||||||
|
// Load all the values for `ctx.make_assert_impl_by_id`
|
||||||
|
let pexception_str = cslice_model.alloca(tyctx, ctx, "exception_str");
|
||||||
|
call_nac3_error_context_get_exception_str(tyctx, ctx, perrctx, pexception_str);
|
||||||
|
|
||||||
|
let exception_id = perrctx.gep(ctx, |f| f.exception_id).load(tyctx, ctx, "exception_id");
|
||||||
|
let msg = pexception_str.load(tyctx, ctx, "msg");
|
||||||
|
let param1 = perrctx.gep(ctx, |f| f.param1).load(tyctx, ctx, "param1");
|
||||||
|
let param2 = perrctx.gep(ctx, |f| f.param2).load(tyctx, ctx, "param2");
|
||||||
|
let param3 = perrctx.gep(ctx, |f| f.param3).load(tyctx, ctx, "param3");
|
||||||
|
|
||||||
|
ctx.raise_exn_impl(
|
||||||
|
generator,
|
||||||
|
exception_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 tyctx = generator.type_context(ctx.ctx);
|
||||||
|
|
||||||
|
let errctx = setup_error_context(tyctx, ctx);
|
||||||
|
CallFunction::begin(tyctx, 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;
|
use crate::typecheck::typedef::Type;
|
||||||
|
|
||||||
|
pub mod error_context;
|
||||||
mod test;
|
mod test;
|
||||||
|
mod util;
|
||||||
|
|
||||||
use super::model::*;
|
use super::model::*;
|
||||||
use super::{
|
use super::{
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
use crate::codegen::model::*;
|
||||||
|
|
||||||
|
// When [`TypeContext::size_type`] is 32-bits, the function name is "{fn_name}".
|
||||||
|
// When [`TypeContext::size_type`] is 64-bits, the function name is "{fn_name}64".
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_sizet_dependent_function_name(tyctx: TypeContext<'_>, name: &str) -> String {
|
||||||
|
let mut name = name.to_owned();
|
||||||
|
match tyctx.size_type.get_bit_width() {
|
||||||
|
32 => {}
|
||||||
|
64 => name.push_str("64"),
|
||||||
|
bit_width => {
|
||||||
|
panic!("Unsupported int type bit width {bit_width}, must be either 32-bits or 64-bits")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod function {
|
||||||
|
use crate::codegen::{model::*, CodeGenContext};
|
||||||
|
use inkwell::{
|
||||||
|
types::{BasicMetadataTypeEnum, BasicType, FunctionType},
|
||||||
|
values::{AnyValue, BasicMetadataValueEnum, BasicValue, BasicValueEnum, CallSiteValue},
|
||||||
|
};
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct Arg<'ctx> {
|
||||||
|
ty: BasicMetadataTypeEnum<'ctx>,
|
||||||
|
val: BasicMetadataValueEnum<'ctx>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper structure to reduce IRRT Inkwell function call boilerplate
|
||||||
|
/// TODO: Optimize
|
||||||
|
pub struct CallFunction<'ctx, 'a, 'b, 'c> {
|
||||||
|
tyctx: TypeContext<'ctx>,
|
||||||
|
ctx: &'b CodeGenContext<'ctx, 'a>,
|
||||||
|
/// Function name
|
||||||
|
name: &'c str,
|
||||||
|
/// Call arguments
|
||||||
|
args: Vec<Arg<'ctx>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'ctx, 'a, 'b, 'c> CallFunction<'ctx, 'a, 'b, 'c> {
|
||||||
|
pub fn begin(
|
||||||
|
tyctx: TypeContext<'ctx>,
|
||||||
|
ctx: &'b CodeGenContext<'ctx, 'a>,
|
||||||
|
name: &'c str,
|
||||||
|
) -> Self {
|
||||||
|
CallFunction { tyctx, ctx, name, args: Vec::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push a call argument to the function call.
|
||||||
|
///
|
||||||
|
/// The `_name` parameter is there for self-documentation purposes.
|
||||||
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
|
pub fn arg<M: Model>(mut self, _name: &str, arg: Instance<'ctx, M>) -> Self {
|
||||||
|
let arg = Arg {
|
||||||
|
ty: arg.model.get_type(self.tyctx, self.ctx.ctx).as_basic_type_enum().into(),
|
||||||
|
val: arg.value.as_basic_value_enum().into(),
|
||||||
|
};
|
||||||
|
self.args.push(arg);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Like [`CallFunction::returning_`] but `return_model` is automatically inferred.
|
||||||
|
pub fn returning<M: Model>(self, name: &str) -> Instance<'ctx, M> {
|
||||||
|
self.returning_(name, M::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call the function and expect the function to return a value of type of `return_model`.
|
||||||
|
pub fn returning_<M: Model>(self, name: &str, return_model: M) -> Instance<'ctx, M> {
|
||||||
|
let ret_ty = return_model.get_type(self.tyctx, self.ctx.ctx);
|
||||||
|
|
||||||
|
let ret = self.get_function(|tys| ret_ty.fn_type(tys, false), name);
|
||||||
|
let ret = BasicValueEnum::try_from(ret.as_any_value_enum()).unwrap(); // Must work
|
||||||
|
let ret = return_model.check_value(self.tyctx, self.ctx.ctx, ret).unwrap(); // Must work
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call the function and expect the function to return a void-type.
|
||||||
|
pub fn returning_void(self) {
|
||||||
|
let ret_ty = self.ctx.ctx.void_type();
|
||||||
|
|
||||||
|
let _ = self.get_function(|tys| ret_ty.fn_type(tys, false), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_function<F>(&self, make_fn_type: F, return_value_name: &str) -> CallSiteValue<'ctx>
|
||||||
|
where
|
||||||
|
F: FnOnce(&[BasicMetadataTypeEnum<'ctx>]) -> FunctionType<'ctx>,
|
||||||
|
{
|
||||||
|
// Get the LLVM function, declare the function if it doesn't exist - it will be defined by other
|
||||||
|
// components of NAC3.
|
||||||
|
let func = self.ctx.module.get_function(self.name).unwrap_or_else(|| {
|
||||||
|
let tys = self.args.iter().map(|arg| arg.ty).collect_vec();
|
||||||
|
let fn_type = make_fn_type(&tys);
|
||||||
|
self.ctx.module.add_function(self.name, fn_type, None)
|
||||||
|
});
|
||||||
|
|
||||||
|
let vals = self.args.iter().map(|arg| arg.val).collect_vec();
|
||||||
|
self.ctx.builder.build_call(func, &vals, return_value_name).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue