forked from M-Labs/nac3
core/irrt: add ErrorContext
This commit is contained in:
parent
5b7588df75
commit
7502b14d55
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;
|
||||
};
|
92
nac3core/irrt/irrt/error_context.hpp
Normal file
92
nac3core/irrt/irrt/error_context.hpp
Normal file
@ -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
|
||||
|
||||
#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,71 @@ 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 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,
|
||||
generator: &mut G,
|
||||
name: &str,
|
||||
exn_id: Int<'ctx, ExceptionId>,
|
||||
msg: Struct<'ctx, CSlice>,
|
||||
params: [Option<Int<'ctx, Int64>>; 3],
|
||||
loc: Location,
|
||||
) {
|
||||
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)
|
||||
@ -602,6 +598,23 @@ 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>,
|
||||
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>(
|
||||
&mut self,
|
||||
generator: &mut G,
|
||||
|
198
nac3core/src/codegen/irrt/error_context.rs
Normal file
198
nac3core/src/codegen/irrt/error_context.rs
Normal file
@ -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;
|
||||
|
||||
pub mod error_context;
|
||||
mod test;
|
||||
mod util;
|
||||
|
||||
use super::model::*;
|
||||
use super::{
|
||||
|
103
nac3core/src/codegen/irrt/util.rs
Normal file
103
nac3core/src/codegen/irrt/util.rs
Normal file
@ -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
Block a user