core/irrt/exceptions: allow irrt to raise exceptions
Achieved through defining all the needed Exception ID constants at link time. Secondly, since `Exception` is `size_t` dependent, `__nac3_raise()` takes an opaque pointer to `Exception`, unless IRRT is compiled into 32-bit and 64-bit separately with a defined `size_t` type.
This commit is contained in:
parent
82671a4be7
commit
cd69fa07a5
|
@ -33,6 +33,7 @@ use inkwell::{
|
||||||
OptimizationLevel,
|
OptimizationLevel,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use nac3core::codegen::irrt::setup_irrt_exceptions;
|
||||||
use nac3core::codegen::{gen_func_impl, CodeGenLLVMOptions, CodeGenTargetMachineOptions};
|
use nac3core::codegen::{gen_func_impl, CodeGenLLVMOptions, CodeGenTargetMachineOptions};
|
||||||
use nac3core::toplevel::builtins::get_exn_constructor;
|
use nac3core::toplevel::builtins::get_exn_constructor;
|
||||||
use nac3core::typecheck::typedef::{into_var_map, TypeEnum, Unifier, VarMap};
|
use nac3core::typecheck::typedef::{into_var_map, TypeEnum, Unifier, VarMap};
|
||||||
|
@ -557,6 +558,11 @@ impl Nac3 {
|
||||||
.register_top_level(synthesized.pop().unwrap(), Some(resolver.clone()), "", false)
|
.register_top_level(synthesized.pop().unwrap(), Some(resolver.clone()), "", false)
|
||||||
.unwrap();
|
.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 =
|
let fun_signature =
|
||||||
FunSignature { args: vec![], ret: self.primitive.none, vars: VarMap::new() };
|
FunSignature { args: vec![], ret: self.primitive.none, vars: VarMap::new() };
|
||||||
let mut store = ConcreteTypeStore::new();
|
let mut store = ConcreteTypeStore::new();
|
||||||
|
@ -727,7 +733,7 @@ impl Nac3 {
|
||||||
membuffer.lock().push(buffer);
|
membuffer.lock().push(buffer);
|
||||||
});
|
});
|
||||||
|
|
||||||
let context = inkwell::context::Context::create();
|
// Link all modules into `main`.
|
||||||
let buffers = membuffers.lock();
|
let buffers = membuffers.lock();
|
||||||
let main = context
|
let main = context
|
||||||
.create_module_from_ir(MemoryBuffer::create_from_memory_range(&buffers[0], "main"))
|
.create_module_from_ir(MemoryBuffer::create_from_memory_range(&buffers[0], "main"))
|
||||||
|
@ -756,8 +762,7 @@ impl Nac3 {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
main.link_in_module(load_irrt(&context))
|
main.link_in_module(irrt).map_err(|err| CompileError::new_err(err.to_string()))?;
|
||||||
.map_err(|err| CompileError::new_err(err.to_string()))?;
|
|
||||||
|
|
||||||
let mut function_iter = main.get_first_function();
|
let mut function_iter = main.get_first_function();
|
||||||
while let Some(func) = function_iter {
|
while let Some(func) = function_iter {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#include <irrt/exception.hpp>
|
||||||
#include <irrt/int_types.hpp>
|
#include <irrt/int_types.hpp>
|
||||||
#include <irrt/math_util.hpp>
|
#include <irrt/math_util.hpp>
|
||||||
#include <irrt/original.hpp>
|
#include <irrt/original.hpp>
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <irrt/int_types.hpp>
|
||||||
|
|
||||||
|
template <typename SizeT> struct CSlice
|
||||||
|
{
|
||||||
|
uint8_t *base;
|
||||||
|
SizeT len;
|
||||||
|
};
|
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <irrt/int_types.hpp>
|
||||||
|
|
||||||
|
namespace cstr
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @brief Implementation of `strlen()`.
|
||||||
|
*/
|
||||||
|
uint32_t length(const char *str)
|
||||||
|
{
|
||||||
|
uint32_t length = 0;
|
||||||
|
while (*str != '\0')
|
||||||
|
{
|
||||||
|
length++;
|
||||||
|
str++;
|
||||||
|
}
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
} // namespace cstr
|
|
@ -0,0 +1,80 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <irrt/cslice.hpp>
|
||||||
|
#include <irrt/cstr_util.hpp>
|
||||||
|
#include <irrt/int_types.hpp>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The int type of ARTIQ exception IDs.
|
||||||
|
*/
|
||||||
|
typedef int32_t ExceptionId;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set of exceptions C++ IRRT can use.
|
||||||
|
* Must be synchronized with `setup_irrt_exceptions` in `nac3core/src/codegen/irrt/mod.rs`.
|
||||||
|
*/
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
ExceptionId EXN_INDEX_ERROR;
|
||||||
|
ExceptionId EXN_VALUE_ERROR;
|
||||||
|
ExceptionId EXN_ASSERTION_ERROR;
|
||||||
|
ExceptionId EXN_TYPE_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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);
|
||||||
|
|
||||||
|
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];
|
||||||
|
};
|
||||||
|
|
||||||
|
const int64_t NO_PARAM = 0;
|
||||||
|
|
||||||
|
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::length(filename)},
|
||||||
|
.line = line,
|
||||||
|
.column = 0,
|
||||||
|
.function = {.base = (uint8_t *)function, .len = (int32_t)cstr::length(function)},
|
||||||
|
.msg = {.base = (uint8_t *)msg, .len = (int32_t)cstr::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)
|
||||||
|
} // namespace
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::typecheck::typedef::Type;
|
use crate::{symbol_resolver::SymbolResolver, typecheck::typedef::Type};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
classes::{
|
classes::{
|
||||||
|
@ -15,7 +15,7 @@ use inkwell::{
|
||||||
memory_buffer::MemoryBuffer,
|
memory_buffer::MemoryBuffer,
|
||||||
module::Module,
|
module::Module,
|
||||||
types::{BasicTypeEnum, IntType},
|
types::{BasicTypeEnum, IntType},
|
||||||
values::{BasicValueEnum, CallSiteValue, FloatValue, IntValue},
|
values::{BasicValue, BasicValueEnum, CallSiteValue, FloatValue, IntValue},
|
||||||
AddressSpace, IntPredicate,
|
AddressSpace, IntPredicate,
|
||||||
};
|
};
|
||||||
use itertools::Either;
|
use itertools::Either;
|
||||||
|
@ -929,3 +929,48 @@ pub fn call_ndarray_calc_broadcast_index<
|
||||||
Box::new(|_, v| v.into()),
|
Box::new(|_, v| v.into()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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<G: CodeGenerator + ?Sized>(
|
||||||
|
generator: &mut G,
|
||||||
|
ctx: &CodeGenContext<'_, '_>,
|
||||||
|
name: &str,
|
||||||
|
) -> String {
|
||||||
|
let mut name = name.to_owned();
|
||||||
|
match generator.get_size_type(ctx.ctx).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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ use inkwell::{
|
||||||
memory_buffer::MemoryBuffer, passes::PassBuilderOptions, support::is_multithreaded, targets::*,
|
memory_buffer::MemoryBuffer, passes::PassBuilderOptions, support::is_multithreaded, targets::*,
|
||||||
OptimizationLevel,
|
OptimizationLevel,
|
||||||
};
|
};
|
||||||
|
use nac3core::codegen::irrt::setup_irrt_exceptions;
|
||||||
use nac3core::{
|
use nac3core::{
|
||||||
codegen::{
|
codegen::{
|
||||||
concrete_type::ConcreteTypeStore, irrt::load_irrt, CodeGenLLVMOptions,
|
concrete_type::ConcreteTypeStore, irrt::load_irrt, CodeGenLLVMOptions,
|
||||||
|
@ -314,6 +315,16 @@ fn main() {
|
||||||
let resolver =
|
let resolver =
|
||||||
Arc::new(Resolver(internal_resolver.clone())) as Arc<dyn SymbolResolver + Send + Sync>;
|
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();
|
let parser_result = parser::parse_program(&program, file_name.into()).unwrap();
|
||||||
|
|
||||||
for stmt in parser_result {
|
for stmt in parser_result {
|
||||||
|
@ -418,8 +429,8 @@ fn main() {
|
||||||
registry.add_task(task);
|
registry.add_task(task);
|
||||||
registry.wait_tasks_complete(handles);
|
registry.wait_tasks_complete(handles);
|
||||||
|
|
||||||
|
// Link all modules together into `main`
|
||||||
let buffers = membuffers.lock();
|
let buffers = membuffers.lock();
|
||||||
let context = inkwell::context::Context::create();
|
|
||||||
let main = context
|
let main = context
|
||||||
.create_module_from_ir(MemoryBuffer::create_from_memory_range(&buffers[0], "main"))
|
.create_module_from_ir(MemoryBuffer::create_from_memory_range(&buffers[0], "main"))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -439,12 +450,9 @@ fn main() {
|
||||||
main.link_in_module(other).unwrap();
|
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();
|
main.link_in_module(irrt).unwrap();
|
||||||
|
|
||||||
|
// Private all functions except "run"
|
||||||
let mut function_iter = main.get_first_function();
|
let mut function_iter = main.get_first_function();
|
||||||
while let Some(func) = function_iter {
|
while let Some(func) = function_iter {
|
||||||
if func.count_basic_blocks() > 0 && func.get_name().to_str().unwrap() != "run" {
|
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();
|
function_iter = func.get_next_function();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optimize `main`
|
||||||
let target_machine = llvm_options
|
let target_machine = llvm_options
|
||||||
.target
|
.target
|
||||||
.create_target_machine(llvm_options.opt_level)
|
.create_target_machine(llvm_options.opt_level)
|
||||||
|
@ -466,6 +475,7 @@ fn main() {
|
||||||
panic!("Failed to run optimization for module `main`: {}", err.to_string());
|
panic!("Failed to run optimization for module `main`: {}", err.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write output
|
||||||
target_machine
|
target_machine
|
||||||
.write_to_file(&main, FileType::Object, Path::new("module.o"))
|
.write_to_file(&main, FileType::Object, Path::new("module.o"))
|
||||||
.expect("couldn't write module to file");
|
.expect("couldn't write module to file");
|
||||||
|
|
Loading…
Reference in New Issue