From 9940d00244877739aa41123bef851df32f43f02c Mon Sep 17 00:00:00 2001 From: lyken Date: Sun, 4 Aug 2024 14:05:35 +0800 Subject: [PATCH] core: add error raising in IRRT & codegen IRRT CallFunction util --- nac3artiq/src/lib.rs | 11 ++- nac3core/irrt/irrt/cslice.hpp | 9 +++ nac3core/irrt/irrt/exception.hpp | 123 ++++++++++++++++++++++++++++++ nac3core/irrt/irrt_everything.hpp | 1 + nac3core/irrt/irrt_test.cpp | 6 ++ nac3core/src/codegen/irrt/mod.rs | 41 ++++++++++ nac3core/src/codegen/irrt/util.rs | 105 +++++++++++++++++++++++++ nac3standalone/src/main.rs | 20 +++-- 8 files changed, 308 insertions(+), 8 deletions(-) create mode 100644 nac3core/irrt/irrt/cslice.hpp create mode 100644 nac3core/irrt/irrt/exception.hpp create mode 100644 nac3core/src/codegen/irrt/util.rs diff --git a/nac3artiq/src/lib.rs b/nac3artiq/src/lib.rs index 32fe0218..0badf1ce 100644 --- a/nac3artiq/src/lib.rs +++ b/nac3artiq/src/lib.rs @@ -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 { diff --git a/nac3core/irrt/irrt/cslice.hpp b/nac3core/irrt/irrt/cslice.hpp new file mode 100644 index 00000000..f46b464a --- /dev/null +++ b/nac3core/irrt/irrt/cslice.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include + +template +struct CSlice { + uint8_t* base; + SizeT len; +}; \ No newline at end of file diff --git a/nac3core/irrt/irrt/exception.hpp b/nac3core/irrt/irrt/exception.hpp new file mode 100644 index 00000000..9d68f8cd --- /dev/null +++ b/nac3core/irrt/irrt/exception.hpp @@ -0,0 +1,123 @@ +#pragma once + +#include +#include +#include + +/** + * @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 +struct Exception { + ExceptionId id; + CSlice filename; + int32_t line; + int32_t column; + CSlice function; + CSlice msg; + int64_t params[3]; +}; +} // namespace + +// Declare/Define `__nac3_raise` +#ifdef IRRT_TESTING +#include +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` or `Exception`. 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 +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 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(id, __FILE__, __LINE__, __FUNCTION__, msg, \ + param0, param1, param2) + +/** + * @brief Throw a dummy error for testing. + */ +template +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(); } + +void __nac3_throw_dummy_error64() { throw_dummy_error(); } +} \ No newline at end of file diff --git a/nac3core/irrt/irrt_everything.hpp b/nac3core/irrt/irrt_everything.hpp index 78335c19..636a011c 100644 --- a/nac3core/irrt/irrt_everything.hpp +++ b/nac3core/irrt/irrt_everything.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include \ No newline at end of file diff --git a/nac3core/irrt/irrt_test.cpp b/nac3core/irrt/irrt_test.cpp index 355dc01f..7ffa2e66 100644 --- a/nac3core/irrt/irrt_test.cpp +++ b/nac3core/irrt/irrt_test.cpp @@ -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 #include #include + +// Special macro to inform `#include ` that we +// are testing. +#define IRRT_TESTING + #include int main() { diff --git a/nac3core/src/codegen/irrt/mod.rs b/nac3core/src/codegen/irrt/mod.rs index 2a4c8b83..9150afe8 100644 --- a/nac3core/src/codegen/irrt/mod.rs +++ b/nac3core/src/codegen/irrt/mod.rs @@ -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); + } +} diff --git a/nac3core/src/codegen/irrt/util.rs b/nac3core/src/codegen/irrt/util.rs new file mode 100644 index 00000000..1f2776f8 --- /dev/null +++ b/nac3core/src/codegen/irrt/util.rs @@ -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>, + } + + 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>(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>(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 + 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(&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() + } + } +} diff --git a/nac3standalone/src/main.rs b/nac3standalone/src/main.rs index 228b75c4..7d99fad5 100644 --- a/nac3standalone/src/main.rs +++ b/nac3standalone/src/main.rs @@ -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; + 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");