forked from M-Labs/nac3
core: add error raising in IRRT & codegen IRRT CallFunction util
This commit is contained in:
parent
cb2b7bec3e
commit
49ab9087d8
@ -33,6 +33,7 @@ use inkwell::{
|
||||
OptimizationLevel,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use nac3core::codegen::irrt::setup_irrt_exceptions;
|
||||
use nac3core::codegen::{gen_func_impl, CodeGenLLVMOptions, CodeGenTargetMachineOptions};
|
||||
use nac3core::toplevel::builtins::get_exn_constructor;
|
||||
use nac3core::typecheck::typedef::{TypeEnum, Unifier, VarMap};
|
||||
@ -498,6 +499,11 @@ impl Nac3 {
|
||||
.register_top_level(synthesized.pop().unwrap(), Some(resolver.clone()), "", false)
|
||||
.unwrap();
|
||||
|
||||
// Process IRRT
|
||||
let context = inkwell::context::Context::create();
|
||||
let irrt = load_irrt(&context);
|
||||
setup_irrt_exceptions(&context, &irrt, resolver.as_ref());
|
||||
|
||||
let fun_signature =
|
||||
FunSignature { args: vec![], ret: self.primitive.none, vars: VarMap::new() };
|
||||
let mut store = ConcreteTypeStore::new();
|
||||
@ -668,7 +674,7 @@ impl Nac3 {
|
||||
membuffer.lock().push(buffer);
|
||||
});
|
||||
|
||||
let context = inkwell::context::Context::create();
|
||||
// Link all modules into `main`.
|
||||
let buffers = membuffers.lock();
|
||||
let main = context
|
||||
.create_module_from_ir(MemoryBuffer::create_from_memory_range(&buffers[0], "main"))
|
||||
@ -697,8 +703,7 @@ impl Nac3 {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
main.link_in_module(load_irrt(&context))
|
||||
.map_err(|err| CompileError::new_err(err.to_string()))?;
|
||||
main.link_in_module(irrt).map_err(|err| CompileError::new_err(err.to_string()))?;
|
||||
|
||||
let mut function_iter = main.get_first_function();
|
||||
while let Some(func) = function_iter {
|
||||
|
9
nac3core/irrt/irrt/cslice.hpp
Normal file
9
nac3core/irrt/irrt/cslice.hpp
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <irrt/int_defs.hpp>
|
||||
|
||||
template <typename SizeT>
|
||||
struct CSlice {
|
||||
uint8_t* base;
|
||||
SizeT len;
|
||||
};
|
123
nac3core/irrt/irrt/exception.hpp
Normal file
123
nac3core/irrt/irrt/exception.hpp
Normal file
@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
|
||||
#include <irrt/cslice.hpp>
|
||||
#include <irrt/int_defs.hpp>
|
||||
#include <irrt/util.hpp>
|
||||
|
||||
/**
|
||||
* @brief The int type of ARTIQ exception IDs.
|
||||
*
|
||||
* It is always `int32_t`
|
||||
*/
|
||||
typedef int32_t ExceptionId;
|
||||
|
||||
/*
|
||||
* A set of exceptions IRRT can use.
|
||||
* Must be synchronized with `setup_irrt_exceptions` in `nac3core/src/codegen/irrt/mod.rs`.
|
||||
* All exception IDs are initialized by `setup_irrt_exceptions`.
|
||||
*/
|
||||
#ifdef IRRT_TESTING
|
||||
// If we are doing IRRT tests (i.e., running `cargo test -F test`), define them with a fake set of IDs.
|
||||
ExceptionId EXN_INDEX_ERROR = 0;
|
||||
ExceptionId EXN_VALUE_ERROR = 1;
|
||||
ExceptionId EXN_ASSERTION_ERROR = 2;
|
||||
ExceptionId EXN_RUNTIME_ERROR = 3;
|
||||
ExceptionId EXN_TYPE_ERROR = 4;
|
||||
#else
|
||||
extern "C" {
|
||||
ExceptionId EXN_INDEX_ERROR;
|
||||
ExceptionId EXN_VALUE_ERROR;
|
||||
ExceptionId EXN_ASSERTION_ERROR;
|
||||
ExceptionId EXN_RUNTIME_ERROR;
|
||||
ExceptionId EXN_TYPE_ERROR;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
/**
|
||||
* @brief NAC3's Exception struct
|
||||
*/
|
||||
template <typename SizeT>
|
||||
struct Exception {
|
||||
ExceptionId id;
|
||||
CSlice<SizeT> filename;
|
||||
int32_t line;
|
||||
int32_t column;
|
||||
CSlice<SizeT> function;
|
||||
CSlice<SizeT> msg;
|
||||
int64_t params[3];
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// Declare/Define `__nac3_raise`
|
||||
#ifdef IRRT_TESTING
|
||||
#include <cstdio>
|
||||
void __nac3_raise(void* err) {
|
||||
// TODO: Print the error content?
|
||||
printf("__nac3_raise called. Exiting...\n");
|
||||
exit(1);
|
||||
}
|
||||
#else
|
||||
/**
|
||||
* @brief Extern function to `__nac3_raise`
|
||||
*
|
||||
* The parameter `err` could be `Exception<int32_t>` or `Exception<int64_t>`. The caller
|
||||
* must make sure to pass `Exception`s with the correct `SizeT` depending on the `size_t` of the runtime.
|
||||
*/
|
||||
extern "C" void __nac3_raise(void* err);
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
const int64_t NO_PARAM = 0;
|
||||
|
||||
// Helper function to raise an exception with `__nac3_raise`
|
||||
// Do not use this function directly. See `raise_exception`.
|
||||
template <typename SizeT>
|
||||
void _raise_exception_helper(ExceptionId id, const char* filename, int32_t line,
|
||||
const char* function, const char* msg,
|
||||
int64_t param0, int64_t param1, int64_t param2) {
|
||||
Exception<SizeT> e = {
|
||||
.id = id,
|
||||
.filename = {.base = (uint8_t*)filename,
|
||||
.len = (int32_t)cstr_utils::length(filename)},
|
||||
.line = line,
|
||||
.column = 0,
|
||||
.function = {.base = (uint8_t*)function,
|
||||
.len = (int32_t)cstr_utils::length(function)},
|
||||
.msg = {.base = (uint8_t*)msg, .len = (int32_t)cstr_utils::length(msg)},
|
||||
};
|
||||
e.params[0] = param0;
|
||||
e.params[1] = param1;
|
||||
e.params[2] = param2;
|
||||
__nac3_raise((void*)&e);
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Raise an exception with location details (location in the IRRT source files).
|
||||
* @param SizeT The runtime `size_t` type.
|
||||
* @param id The ID of the exception to raise.
|
||||
* @param msg A global constant C-string of the error message.
|
||||
*
|
||||
* `param0` and `param2` are optional format arguments of `msg`. They should be set to
|
||||
* `NO_PARAM` to indicate they are unused.
|
||||
*/
|
||||
#define raise_exception(SizeT, id, msg, param0, param1, param2) \
|
||||
_raise_exception_helper<SizeT>(id, __FILE__, __LINE__, __FUNCTION__, msg, \
|
||||
param0, param1, param2)
|
||||
|
||||
/**
|
||||
* @brief Throw a dummy error for testing.
|
||||
*/
|
||||
template <typename SizeT>
|
||||
void throw_dummy_error() {
|
||||
raise_exception(SizeT, EXN_RUNTIME_ERROR, "dummy error", NO_PARAM, NO_PARAM,
|
||||
NO_PARAM);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
void __nac3_throw_dummy_error() { throw_dummy_error<int32_t>(); }
|
||||
|
||||
void __nac3_throw_dummy_error64() { throw_dummy_error<int64_t>(); }
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <irrt/core.hpp>
|
||||
#include <irrt/exception.hpp>
|
||||
#include <irrt/int_defs.hpp>
|
||||
#include <irrt/util.hpp>
|
@ -1,9 +1,15 @@
|
||||
// This file will be compiled like a real C++ program,
|
||||
// and we do have the luxury to use the standard libraries.
|
||||
// That is if the nix flakes do not have issues... especially on msys2...
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
// Special macro to inform `#include <irrt/*>` that we
|
||||
// are testing.
|
||||
#define IRRT_TESTING
|
||||
|
||||
#include <test/test_core.hpp>
|
||||
|
||||
int main() {
|
||||
|
@ -1,6 +1,8 @@
|
||||
use crate::symbol_resolver::SymbolResolver;
|
||||
use crate::typecheck::typedef::Type;
|
||||
|
||||
mod test;
|
||||
pub mod util;
|
||||
|
||||
use super::model::*;
|
||||
use super::{
|
||||
@ -12,6 +14,7 @@ use super::{
|
||||
};
|
||||
use crate::codegen::classes::TypedArrayLikeAccessor;
|
||||
use crate::codegen::stmt::gen_for_callback_incrementing;
|
||||
use inkwell::values::BasicValue;
|
||||
use inkwell::{
|
||||
attributes::{Attribute, AttributeLoc},
|
||||
context::Context,
|
||||
@ -23,6 +26,8 @@ use inkwell::{
|
||||
};
|
||||
use itertools::Either;
|
||||
use nac3parser::ast::Expr;
|
||||
use util::function::CallFunction;
|
||||
use util::get_sizet_dependent_function_name;
|
||||
|
||||
#[must_use]
|
||||
pub fn load_irrt(ctx: &Context) -> Module {
|
||||
@ -946,3 +951,39 @@ pub fn call_ndarray_calc_broadcast_index<
|
||||
Box::new(|_, v| v.into()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn call_nac3_throw_dummy_error<'ctx>(tyctx: TypeContext<'ctx>, ctx: &CodeGenContext<'ctx, '_>) {
|
||||
CallFunction::begin(
|
||||
tyctx,
|
||||
ctx,
|
||||
&get_sizet_dependent_function_name(tyctx, "__nac3_throw_dummy_error"),
|
||||
)
|
||||
.returning_void();
|
||||
}
|
||||
|
||||
/// Initialize all global `EXN_*` exception IDs in IRRT with the [`SymbolResolver`].
|
||||
pub fn setup_irrt_exceptions<'ctx>(
|
||||
ctx: &'ctx Context,
|
||||
module: &Module<'ctx>,
|
||||
symbol_resolver: &dyn SymbolResolver,
|
||||
) {
|
||||
let exn_id_type = ctx.i32_type();
|
||||
|
||||
let errors = &[
|
||||
("EXN_INDEX_ERROR", "0:IndexError"),
|
||||
("EXN_VALUE_ERROR", "0:ValueError"),
|
||||
("EXN_ASSERTION_ERROR", "0:AssertionError"),
|
||||
("EXN_RUNTIME_ERROR", "0:RuntimeError"),
|
||||
("EXN_TYPE_ERROR", "0:TypeError"),
|
||||
];
|
||||
|
||||
for (irrt_name, symbol_name) in errors {
|
||||
let exn_id = symbol_resolver.get_string_id(symbol_name);
|
||||
let exn_id = exn_id_type.const_int(exn_id as u64, false).as_basic_value_enum();
|
||||
|
||||
let global = module.get_global(irrt_name).unwrap_or_else(|| {
|
||||
panic!("Exception symbol name '{irrt_name}' should exist in the IRRT LLVM module")
|
||||
});
|
||||
global.set_initializer(&exn_id);
|
||||
}
|
||||
}
|
||||
|
105
nac3core/src/codegen/irrt/util.rs
Normal file
105
nac3core/src/codegen/irrt/util.rs
Normal file
@ -0,0 +1,105 @@
|
||||
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
|
||||
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)]
|
||||
#[must_use]
|
||||
pub fn arg<M: Model<'ctx>>(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
|
||||
}
|
||||
|
||||
/// Call the function and expect the function to return a value of type of `return_model`.
|
||||
#[must_use]
|
||||
pub fn returning<M: Model<'ctx>>(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
|
||||
}
|
||||
|
||||
/// Like [`CallFunction::returning_`] but `return_model` is automatically inferred.
|
||||
#[must_use]
|
||||
pub fn returning_auto<M: Model<'ctx> + Default>(self, name: &str) -> Instance<'ctx, M> {
|
||||
self.returning(name, M::default())
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ use inkwell::{
|
||||
memory_buffer::MemoryBuffer, passes::PassBuilderOptions, support::is_multithreaded, targets::*,
|
||||
OptimizationLevel,
|
||||
};
|
||||
use nac3core::codegen::irrt::setup_irrt_exceptions;
|
||||
use nac3core::{
|
||||
codegen::{
|
||||
concrete_type::ConcreteTypeStore, irrt::load_irrt, CodeGenLLVMOptions,
|
||||
@ -314,6 +315,16 @@ fn main() {
|
||||
let resolver =
|
||||
Arc::new(Resolver(internal_resolver.clone())) as Arc<dyn SymbolResolver + Send + Sync>;
|
||||
|
||||
let context = inkwell::context::Context::create();
|
||||
|
||||
// Process IRRT
|
||||
let irrt = load_irrt(&context);
|
||||
setup_irrt_exceptions(&context, &irrt, resolver.as_ref());
|
||||
if emit_llvm {
|
||||
irrt.write_bitcode_to_path(Path::new("irrt.bc"));
|
||||
}
|
||||
|
||||
// Process the Python script
|
||||
let parser_result = parser::parse_program(&program, file_name.into()).unwrap();
|
||||
|
||||
for stmt in parser_result {
|
||||
@ -418,8 +429,8 @@ fn main() {
|
||||
registry.add_task(task);
|
||||
registry.wait_tasks_complete(handles);
|
||||
|
||||
// Link all modules together into `main`
|
||||
let buffers = membuffers.lock();
|
||||
let context = inkwell::context::Context::create();
|
||||
let main = context
|
||||
.create_module_from_ir(MemoryBuffer::create_from_memory_range(&buffers[0], "main"))
|
||||
.unwrap();
|
||||
@ -439,12 +450,9 @@ fn main() {
|
||||
main.link_in_module(other).unwrap();
|
||||
}
|
||||
|
||||
let irrt = load_irrt(&context);
|
||||
if emit_llvm {
|
||||
irrt.write_bitcode_to_path(Path::new("irrt.bc"));
|
||||
}
|
||||
main.link_in_module(irrt).unwrap();
|
||||
|
||||
// Private all functions except "run"
|
||||
let mut function_iter = main.get_first_function();
|
||||
while let Some(func) = function_iter {
|
||||
if func.count_basic_blocks() > 0 && func.get_name().to_str().unwrap() != "run" {
|
||||
@ -453,6 +461,7 @@ fn main() {
|
||||
function_iter = func.get_next_function();
|
||||
}
|
||||
|
||||
// Optimize `main`
|
||||
let target_machine = llvm_options
|
||||
.target
|
||||
.create_target_machine(llvm_options.opt_level)
|
||||
@ -466,6 +475,7 @@ fn main() {
|
||||
panic!("Failed to run optimization for module `main`: {}", err.to_string());
|
||||
}
|
||||
|
||||
// Write output
|
||||
target_machine
|
||||
.write_to_file(&main, FileType::Object, Path::new("module.o"))
|
||||
.expect("couldn't write module to file");
|
||||
|
Loading…
Reference in New Issue
Block a user