From 9848795dcc5f5f8281cb18f5f5079b2a6158aa87 Mon Sep 17 00:00:00 2001 From: lyken Date: Tue, 27 Aug 2024 10:36:51 +0800 Subject: [PATCH] core/irrt: add exceptions and debug utils --- nac3artiq/src/lib.rs | 9 ++-- nac3core/build.rs | 20 +++++--- nac3core/irrt/irrt.cpp | 1 + nac3core/irrt/irrt/cslice.hpp | 9 ++++ nac3core/irrt/irrt/debug.hpp | 25 ++++++++++ nac3core/irrt/irrt/exception.hpp | 82 ++++++++++++++++++++++++++++++++ nac3core/src/codegen/irrt/mod.rs | 25 ++++++++-- nac3standalone/src/main.rs | 18 +++++-- 8 files changed, 172 insertions(+), 17 deletions(-) create mode 100644 nac3core/irrt/irrt/cslice.hpp create mode 100644 nac3core/irrt/irrt/debug.hpp create mode 100644 nac3core/irrt/irrt/exception.hpp diff --git a/nac3artiq/src/lib.rs b/nac3artiq/src/lib.rs index be2853c72..4ed40aeef 100644 --- a/nac3artiq/src/lib.rs +++ b/nac3artiq/src/lib.rs @@ -557,6 +557,10 @@ 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, resolver.as_ref()); + let fun_signature = FunSignature { args: vec![], ret: self.primitive.none, vars: VarMap::new() }; let mut store = ConcreteTypeStore::new(); @@ -727,7 +731,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")) @@ -756,8 +760,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/build.rs b/nac3core/build.rs index d70f94249..5447a696b 100644 --- a/nac3core/build.rs +++ b/nac3core/build.rs @@ -18,7 +18,7 @@ fn main() { * HACK: Sadly, clang doesn't let us emit generic LLVM bitcode. * Compiling for WASM32 and filtering the output with regex is the closest we can get. */ - let flags: &[&str] = &[ + let mut flags: Vec<&str> = vec![ "--target=wasm32", "-x", "c++", @@ -26,20 +26,28 @@ fn main() { "-fno-discard-value-names", "-fno-exceptions", "-fno-rtti", - match env::var("PROFILE").as_deref() { - Ok("debug") => "-O0", - Ok("release") => "-O3", - flavor => panic!("Unknown or missing build flavor {flavor:?}"), - }, "-emit-llvm", "-S", "-Wall", "-Wextra", "-o", "-", + "-I", + irrt_dir.to_str().unwrap(), irrt_cpp_path.to_str().unwrap(), ]; + match env::var("PROFILE").as_deref() { + Ok("debug") => { + flags.push("-O0"); + flags.push("-DIRRT_DEBUG_ASSERT"); + } + Ok("release") => { + flags.push("-O3"); + } + flavor => panic!("Unknown or missing build flavor {flavor:?}"), + } + // Tell Cargo to rerun if any file under `irrt_dir` (recursive) changes println!("cargo:rerun-if-changed={}", irrt_dir.to_str().unwrap()); diff --git a/nac3core/irrt/irrt.cpp b/nac3core/irrt/irrt.cpp index 1bedd84f4..f717bf3c6 100644 --- a/nac3core/irrt/irrt.cpp +++ b/nac3core/irrt/irrt.cpp @@ -1,3 +1,4 @@ +#include #include #include #include diff --git a/nac3core/irrt/irrt/cslice.hpp b/nac3core/irrt/irrt/cslice.hpp new file mode 100644 index 000000000..587123965 --- /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/debug.hpp b/nac3core/irrt/irrt/debug.hpp new file mode 100644 index 000000000..77833ef1e --- /dev/null +++ b/nac3core/irrt/irrt/debug.hpp @@ -0,0 +1,25 @@ +#pragma once + +// Set in nac3core/build.rs +#ifdef IRRT_DEBUG_ASSERT +#define IRRT_DEBUG_ASSERT_BOOL true +#else +#define IRRT_DEBUG_ASSERT_BOOL false +#endif + +#define raise_debug_assert(SizeT, msg, param1, param2, param3) \ + raise_exception(SizeT, EXN_ASSERTION_ERROR, "IRRT debug assert failed: " msg, param1, param2, param3) + +#define debug_assert_eq(SizeT, lhs, rhs) \ + if constexpr (IRRT_DEBUG_ASSERT_BOOL) { \ + if ((lhs) != (rhs)) { \ + raise_debug_assert(SizeT, "LHS = {0}. RHS = {1}", lhs, rhs, NO_PARAM); \ + } \ + } + +#define debug_assert(SizeT, expr) \ + if constexpr (IRRT_DEBUG_ASSERT_BOOL) { \ + if (!(expr)) { \ + raise_debug_assert(SizeT, "Got false.", NO_PARAM, NO_PARAM, NO_PARAM); \ + } \ + } \ 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 000000000..4c1f0cb79 --- /dev/null +++ b/nac3core/irrt/irrt/exception.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include +#include + +/** + * @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` 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); + +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]; +}; + +constexpr int64_t NO_PARAM = 0; + +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 = reinterpret_cast(filename), .len = __builtin_strlen(filename)}, + .line = line, + .column = 0, + .function = {.base = reinterpret_cast(function), .len = __builtin_strlen(function)}, + .msg = {.base = reinterpret_cast(msg), .len = __builtin_strlen(msg)}, + }; + e.params[0] = param0; + e.params[1] = param1; + e.params[2] = param2; + __nac3_raise(reinterpret_cast(&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` to `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) +} // namespace \ No newline at end of file diff --git a/nac3core/src/codegen/irrt/mod.rs b/nac3core/src/codegen/irrt/mod.rs index 0a809e189..d8d2a56a8 100644 --- a/nac3core/src/codegen/irrt/mod.rs +++ b/nac3core/src/codegen/irrt/mod.rs @@ -1,4 +1,4 @@ -use crate::typecheck::typedef::Type; +use crate::{symbol_resolver::SymbolResolver, typecheck::typedef::Type}; use super::{ classes::{ @@ -16,14 +16,14 @@ use inkwell::{ memory_buffer::MemoryBuffer, module::Module, types::{BasicTypeEnum, IntType}, - values::{BasicValueEnum, CallSiteValue, FloatValue, IntValue}, + values::{BasicValue, BasicValueEnum, CallSiteValue, FloatValue, IntValue}, AddressSpace, IntPredicate, }; use itertools::Either; use nac3parser::ast::Expr; #[must_use] -pub fn load_irrt(ctx: &Context) -> Module { +pub fn load_irrt<'ctx>(ctx: &'ctx Context, symbol_resolver: &dyn SymbolResolver) -> Module<'ctx> { let bitcode_buf = MemoryBuffer::create_from_memory_range( include_bytes!(concat!(env!("OUT_DIR"), "/irrt.bc")), "irrt_bitcode_buffer", @@ -39,6 +39,25 @@ pub fn load_irrt(ctx: &Context) -> Module { let function = irrt_mod.get_function(symbol).unwrap(); function.add_attribute(AttributeLoc::Function, ctx.create_enum_attribute(inline_attr, 0)); } + + // Initialize all global `EXN_*` exception IDs in IRRT with the [`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 = irrt_mod.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); + } + irrt_mod } diff --git a/nac3standalone/src/main.rs b/nac3standalone/src/main.rs index cc4811c19..17a5d15e3 100644 --- a/nac3standalone/src/main.rs +++ b/nac3standalone/src/main.rs @@ -314,6 +314,15 @@ 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, 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 +427,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 +448,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 +459,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 +473,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");