core: irrt error context mechanism

This commit is contained in:
lyken 2024-07-14 22:05:36 +08:00
parent 1f2bb80812
commit 51a099b602
9 changed files with 315 additions and 35 deletions

View File

@ -0,0 +1,84 @@
#pragma once
#include <irrt/int_defs.hpp>
#include <irrt/utils.hpp>
namespace {
// nac3core's "str" struct type definition
template <typename SizeT>
struct Str {
const char* content;
SizeT length;
};
// A limited set of errors IRRT could use.
typedef uint32_t ErrorId;
struct ErrorIds {
ErrorId index_error;
ErrorId value_error;
ErrorId assertion_error;
ErrorId runtime_error;
};
struct ErrorContext {
// Context
ErrorIds* error_ids;
// Error thrown by IRRT
ErrorId error_id;
const char* message_template; // MUST BE `&'static`
uint64_t param1;
uint64_t param2;
uint64_t param3;
void initialize(ErrorIds* error_ids) {
this->error_ids = error_ids;
clear_error();
}
void clear_error() {
// Point the message_template to an empty str. Don't set it to nullptr as a sentinel
this->message_template = "";
}
void set_error(ErrorId error_id, const char* message, uint64_t param1 = 0, uint64_t param2 = 0, uint64_t param3 = 0) {
this->error_id = error_id;
this->message_template = message;
this->param1 = param1;
this->param2 = param2;
this->param3 = param3;
}
bool has_error() {
return !cstr_utils::is_empty(message_template);
}
template <typename SizeT>
void get_error_str(Str<SizeT> *dst_str) {
dst_str->content = message_template;
dst_str->length = (SizeT) cstr_utils::length(message_template);
}
};
}
extern "C" {
void __nac3_error_context_initialize(ErrorContext* errctx, ErrorIds* error_ids) {
errctx->initialize(error_ids);
}
bool __nac3_error_context_has_no_error(ErrorContext* errctx) {
return !errctx->has_error();
}
void __nac3_error_context_get_error_str(ErrorContext* errctx, Str<int32_t> *dst_str) {
errctx->get_error_str<int32_t>(dst_str);
}
void __nac3_error_context_get_error_str64(ErrorContext* errctx, Str<int64_t> *dst_str) {
errctx->get_error_str<int64_t>(dst_str);
}
void __nac3_error_dummy_raise(ErrorContext* errctx) {
errctx->set_error(errctx->error_ids->runtime_error, "THROWN FROM __nac3_error_dummy_raise!!!!!!");
}
}

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include <irrt/int_defs.hpp>
namespace { namespace {
template <typename T> template <typename T>
const T& max(const T& a, const T& b) { const T& max(const T& a, const T& b) {
@ -18,4 +20,59 @@ bool arrays_match(int len, T* as, T* bs) {
} }
return true; return true;
} }
namespace cstr_utils {
bool is_empty(const char* str) {
return str[0] == '\0';
}
int8_t compare(const char* a, const char* b) {
uint32_t i = 0;
while (true) {
if (a[i] < b[i]) {
return -1;
} else if (a[i] > b[i]) {
return 1;
} else { // a[i] == b[i]
if (a[i] == '\0') {
return 0;
} else {
i++;
}
}
}
}
int8_t equal(const char* a, const char* b) {
return compare(a, b) == 0;
}
uint32_t length(const char* str) {
uint32_t length = 0;
while (*str != '\0') {
length++;
str++;
}
return length;
}
bool copy(const char* src, char* dst, uint32_t dst_max_size) {
for (uint32_t i = 0; i < dst_max_size; i++) {
bool is_last = i + 1 == dst_max_size;
if (is_last && src[i] != '\0') {
dst[i] = '\0';
return false;
}
if (src[i] == '\0') {
dst[i] = '\0';
return true;
}
dst[i] = src[i];
}
__builtin_unreachable();
}
}
} }

View File

@ -3,3 +3,4 @@
#include <irrt/core.hpp> #include <irrt/core.hpp>
#include <irrt/int_defs.hpp> #include <irrt/int_defs.hpp>
#include <irrt/utils.hpp> #include <irrt/utils.hpp>
#include <irrt/error_context.hpp>

View File

@ -576,6 +576,21 @@ impl<'ctx, 'a> CodeGenContext<'ctx, 'a> {
params: [Option<IntValue<'ctx>>; 3], params: [Option<IntValue<'ctx>>; 3],
loc: Location, loc: Location,
) { ) {
let error_id = self.resolver.get_string_id(name);
let error_id = self.ctx.i32_type().const_int(error_id as u64, false);
self.raise_exn_by_id(generator, error_id, msg, params, loc);
}
pub fn raise_exn_by_id<G: CodeGenerator + ?Sized>(
&mut self,
generator: &mut G,
error_id: IntValue<'ctx>,
msg: BasicValueEnum<'ctx>,
params: [Option<IntValue<'ctx>>; 3],
loc: Location,
) {
assert_eq!(error_id.get_type().get_bit_width(), 32);
let zelf = if let Some(exception_val) = self.exception_val { let zelf = if let Some(exception_val) = self.exception_val {
exception_val exception_val
} else { } else {
@ -588,8 +603,7 @@ impl<'ctx, 'a> CodeGenContext<'ctx, 'a> {
let zero = int32.const_zero(); let zero = int32.const_zero();
unsafe { unsafe {
let id_ptr = self.builder.build_in_bounds_gep(zelf, &[zero, zero], "exn.id").unwrap(); let id_ptr = self.builder.build_in_bounds_gep(zelf, &[zero, zero], "exn.id").unwrap();
let id = self.resolver.get_string_id(name); self.builder.build_store(id_ptr, error_id).unwrap();
self.builder.build_store(id_ptr, int32.const_int(id as u64, false)).unwrap();
let ptr = self let ptr = self
.builder .builder
.build_in_bounds_gep(zelf, &[zero, int32.const_int(5, false)], "exn.msg") .build_in_bounds_gep(zelf, &[zero, int32.const_int(5, false)], "exn.msg")
@ -652,6 +666,32 @@ impl<'ctx, 'a> CodeGenContext<'ctx, 'a> {
self.raise_exn(generator, err_name, err_msg, params, loc); self.raise_exn(generator, err_name, err_msg, params, loc);
self.builder.position_at_end(then_block); self.builder.position_at_end(then_block);
} }
pub fn make_assert_impl_by_id<G: CodeGenerator + ?Sized>(
&mut self,
generator: &mut G,
cond: IntValue<'ctx>,
err_id: IntValue<'ctx>,
err_msg: BasicValueEnum<'ctx>,
params: [Option<IntValue<'ctx>>; 3],
loc: Location,
) {
let i1 = self.ctx.bool_type();
let i1_true = i1.const_all_ones();
// we assume that the condition is most probably true, so the normal path is the most
// probable path
// even if this assumption is violated, it does not matter as exception unwinding is
// slow anyway...
let cond = call_expect(self, cond, i1_true, Some("expect"));
let current_bb = self.builder.get_insert_block().unwrap();
let current_fun = current_bb.get_parent().unwrap();
let then_block = self.ctx.insert_basic_block_after(current_bb, "succ");
let exn_block = self.ctx.append_basic_block(current_fun, "fail");
self.builder.build_conditional_branch(cond, then_block, exn_block).unwrap();
self.builder.position_at_end(exn_block);
self.raise_exn_by_id(generator, err_id, err_msg, params, loc);
self.builder.position_at_end(then_block);
}
} }
/// See [`CodeGenerator::gen_constructor`]. /// See [`CodeGenerator::gen_constructor`].

View File

@ -1,13 +1,15 @@
use crate::codegen::model::*; use crate::codegen::{model::*, CodeGenContext, CodeGenerator};
use super::util::{get_sized_dependent_function_name, FunctionBuilder};
pub struct StrFields<'ctx> { pub struct StrFields<'ctx> {
content: Field<PointerModel<FixedIntModel<Byte>>>, pub content: Field<PointerModel<FixedIntModel<Byte>>>,
length: Field<IntModel<'ctx>>, pub length: Field<IntModel<'ctx>>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Str<'ctx> { pub struct Str<'ctx> {
sizet: IntModel<'ctx>, pub sizet: IntModel<'ctx>,
} }
impl<'ctx> IsStruct<'ctx> for Str<'ctx> { impl<'ctx> IsStruct<'ctx> for Str<'ctx> {
@ -27,10 +29,10 @@ impl<'ctx> IsStruct<'ctx> for Str<'ctx> {
type ErrorId = Int32; type ErrorId = Int32;
pub struct ErrorIdsFields { pub struct ErrorIdsFields {
index_error: Field<FixedIntModel<ErrorId>>, pub index_error: Field<FixedIntModel<ErrorId>>,
value_error: Field<FixedIntModel<ErrorId>>, pub value_error: Field<FixedIntModel<ErrorId>>,
assertion_error: Field<FixedIntModel<ErrorId>>, pub assertion_error: Field<FixedIntModel<ErrorId>>,
runtime_error: Field<FixedIntModel<ErrorId>>, pub runtime_error: Field<FixedIntModel<ErrorId>>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ErrorIds; pub struct ErrorIds;
@ -53,11 +55,11 @@ impl<'ctx> IsStruct<'ctx> for ErrorIds {
} }
pub struct ErrorContextFields { pub struct ErrorContextFields {
error_id: Field<FixedIntModel<ErrorId>>, pub error_id: Field<FixedIntModel<ErrorId>>,
message_template: Field<PointerModel<FixedIntModel<Byte>>>, pub message_template: Field<PointerModel<FixedIntModel<Byte>>>,
param1: Field<FixedIntModel<Int64>>, pub param1: Field<FixedIntModel<Int64>>,
param2: Field<FixedIntModel<Int64>>, pub param2: Field<FixedIntModel<Int64>>,
param3: Field<FixedIntModel<Int64>>, pub param3: Field<FixedIntModel<Int64>>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ErrorContext; pub struct ErrorContext;
@ -79,3 +81,102 @@ impl<'ctx> IsStruct<'ctx> for ErrorContext {
} }
} }
} }
// Prepare ErrorIds
fn build_error_ids<'ctx>(ctx: &CodeGenContext<'ctx, '_>) -> Pointer<'ctx, StructModel<ErrorIds>> {
// ErrorIdsLens.get_fields(ctx.ctx).assertion_error.
let error_ids = StructModel(ErrorIds).alloca(ctx, "error_ids");
let i32_model = FixedIntModel(Int32);
// i32_model.make_constant()
let get_string_id =
|string_id| i32_model.constant(ctx.ctx, ctx.resolver.get_string_id(string_id) as u64);
error_ids.gep(ctx, |f| f.index_error).store(ctx, &get_string_id("0:IndexError"));
error_ids.gep(ctx, |f| f.value_error).store(ctx, &get_string_id("0:ValueError"));
error_ids.gep(ctx, |f| f.assertion_error).store(ctx, &get_string_id("0:AssertionError"));
error_ids.gep(ctx, |f| f.runtime_error).store(ctx, &get_string_id("0:RuntimeError"));
error_ids
}
pub fn call_nac3_error_context_initialize<'ctx>(
ctx: &CodeGenContext<'ctx, '_>,
perrctx: &Pointer<'ctx, StructModel<ErrorContext>>,
perror_ids: &Pointer<'ctx, StructModel<ErrorIds>>,
) {
FunctionBuilder::begin(ctx, "__nac3_error_context_initialize")
.arg("errctx", &PointerModel(StructModel(ErrorContext)), perrctx)
.arg("error_ids", &PointerModel(StructModel(ErrorIds)), perror_ids)
.returning_void();
}
pub fn call_nac3_error_context_has_no_error<'ctx>(
ctx: &CodeGenContext<'ctx, '_>,
errctx: &Pointer<'ctx, StructModel<ErrorContext>>,
) -> FixedInt<'ctx, Bool> {
FunctionBuilder::begin(ctx, "__nac3_error_context_has_no_error")
.arg("errctx", &PointerModel(StructModel(ErrorContext)), errctx)
.returning("has_error", &FixedIntModel(Bool))
}
pub fn call_nac3_error_context_get_error_str<'ctx>(
sizet: IntModel<'ctx>,
ctx: &CodeGenContext<'ctx, '_>,
errctx: &Pointer<'ctx, StructModel<ErrorContext>>,
dst_str: &Pointer<'ctx, StructModel<Str<'ctx>>>,
) {
FunctionBuilder::begin(
ctx,
&get_sized_dependent_function_name(sizet, "__nac3_error_context_get_error_str"),
)
.arg("errctx", &PointerModel(StructModel(ErrorContext)), errctx)
.arg("dst_str", &PointerModel(StructModel(Str { sizet })), dst_str)
.returning_void();
}
pub fn prepare_error_context<'ctx>(
ctx: &CodeGenContext<'ctx, '_>,
) -> Pointer<'ctx, StructModel<ErrorContext>> {
let error_ids = build_error_ids(ctx);
let errctx_ptr = StructModel(ErrorContext).alloca(ctx, "errctx");
call_nac3_error_context_initialize(ctx, &errctx_ptr, &error_ids);
errctx_ptr
}
pub fn check_error_context<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
errctx_ptr: &Pointer<'ctx, StructModel<ErrorContext>>,
) {
let sizet = IntModel(generator.get_size_type(ctx.ctx));
let has_error = call_nac3_error_context_has_no_error(ctx, errctx_ptr);
let pstr = StructModel(Str { sizet }).alloca(ctx, "error_str");
call_nac3_error_context_get_error_str(sizet, ctx, errctx_ptr, &pstr);
let error_id = errctx_ptr.gep(ctx, |f| f.error_id).load(ctx, "error_id");
let error_str = pstr.load(ctx, "error_str");
let param1 = errctx_ptr.gep(ctx, |f| f.param1).load(ctx, "param1");
let param2 = errctx_ptr.gep(ctx, |f| f.param2).load(ctx, "param2");
let param3 = errctx_ptr.gep(ctx, |f| f.param3).load(ctx, "param3");
ctx.make_assert_impl_by_id(
generator,
has_error.value,
error_id.value,
error_str.get_llvm_value(),
[Some(param1.value), Some(param2.value), Some(param3.value)],
ctx.current_loc,
);
}
pub fn call_nac3_dummy_raise<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext,
) {
let errctx = prepare_error_context(ctx);
FunctionBuilder::begin(ctx, "__nac3_error_dummy_raise")
.arg("errctx", &PointerModel(StructModel(ErrorContext)), &errctx)
.returning_void();
check_error_context(generator, ctx, &errctx);
}

View File

@ -1,6 +1,8 @@
use crate::typecheck::typedef::Type; use crate::typecheck::typedef::Type;
pub mod error_context;
mod test; mod test;
mod util;
use super::{ use super::{
classes::{ classes::{

View File

@ -17,9 +17,9 @@ fn get_size_variant(ty: IntType) -> SizeVariant {
} }
#[must_use] #[must_use]
pub fn get_sized_dependent_function_name(ty: IntType, fn_name: &str) -> String { pub fn get_sized_dependent_function_name(ty: IntModel, fn_name: &str) -> String {
let mut fn_name = fn_name.to_owned(); let mut fn_name = fn_name.to_owned();
match get_size_variant(ty) { match get_size_variant(ty.0) {
SizeVariant::Bits32 => { SizeVariant::Bits32 => {
// Do nothing, `fn_name` already has the correct name // Do nothing, `fn_name` already has the correct name
} }
@ -51,7 +51,7 @@ impl<'ctx, 'a> FunctionBuilder<'ctx, 'a> {
self self
} }
pub fn returning<M: Model<'ctx>>(self, name: &'static str, return_model: &M) -> S::MemoryValue { pub fn returning<M: Model<'ctx>>(self, name: &'static str, return_model: &M) -> M::Value {
let (param_tys, param_vals): (Vec<_>, Vec<_>) = self.arguments.into_iter().unzip(); let (param_tys, param_vals): (Vec<_>, Vec<_>) = self.arguments.into_iter().unzip();
let function = self.ctx.module.get_function(self.fn_name).unwrap_or_else(|| { let function = self.ctx.module.get_function(self.fn_name).unwrap_or_else(|| {

View File

@ -23,7 +23,9 @@ use inkwell::{
values::{BasicValueEnum, FunctionValue, IntValue, PhiValue, PointerValue}, values::{BasicValueEnum, FunctionValue, IntValue, PhiValue, PointerValue},
AddressSpace, IntPredicate, OptimizationLevel, AddressSpace, IntPredicate, OptimizationLevel,
}; };
use irrt::error_context::Str;
use itertools::Itertools; use itertools::Itertools;
use model::{IntModel, Model, StructModel};
use nac3parser::ast::{Location, Stmt, StrRef}; use nac3parser::ast::{Location, Stmt, StrRef};
use parking_lot::{Condvar, Mutex}; use parking_lot::{Condvar, Mutex};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
@ -647,7 +649,7 @@ pub fn gen_func_impl<
..primitives ..primitives
}; };
let mut type_cache: HashMap<_, _> = [ let mut type_cache: HashMap<_, BasicTypeEnum<'_>> = [
(primitives.int32, context.i32_type().into()), (primitives.int32, context.i32_type().into()),
(primitives.int64, context.i64_type().into()), (primitives.int64, context.i64_type().into()),
(primitives.uint32, context.i32_type().into()), (primitives.uint32, context.i32_type().into()),
@ -655,19 +657,8 @@ pub fn gen_func_impl<
(primitives.float, context.f64_type().into()), (primitives.float, context.f64_type().into()),
(primitives.bool, context.i8_type().into()), (primitives.bool, context.i8_type().into()),
(primitives.str, { (primitives.str, {
let name = "str"; let sizet = IntModel(generator.get_size_type(context));
match module.get_struct_type(name) { StructModel(Str { sizet }).get_llvm_type(context)
None => {
let str_type = context.opaque_struct_type("str");
let fields = [
context.i8_type().ptr_type(AddressSpace::default()).into(),
generator.get_size_type(context).into(),
];
str_type.set_body(&fields, false);
str_type.into()
}
Some(t) => t.as_basic_type_enum(),
}
}), }),
(primitives.range, RangeType::new(context).as_base_type().into()), (primitives.range, RangeType::new(context).as_base_type().into()),
(primitives.exception, { (primitives.exception, {
@ -675,10 +666,12 @@ pub fn gen_func_impl<
if let Some(t) = module.get_struct_type(name) { if let Some(t) = module.get_struct_type(name) {
t.ptr_type(AddressSpace::default()).as_basic_type_enum() t.ptr_type(AddressSpace::default()).as_basic_type_enum()
} else { } else {
let sizet = IntModel(generator.get_size_type(context));
let str_ty = StructModel(Str { sizet }).get_llvm_type(context);
let exception = context.opaque_struct_type("Exception"); let exception = context.opaque_struct_type("Exception");
let int32 = context.i32_type().into(); let int32 = context.i32_type().into();
let int64 = context.i64_type().into(); let int64 = context.i64_type().into();
let str_ty = module.get_struct_type("str").unwrap().as_basic_type_enum();
let fields = [int32, str_ty, int32, int32, str_ty, str_ty, int64, int64, int64]; let fields = [int32, str_ty, int32, int32, str_ty, str_ty, int64, int64, int64];
exception.set_body(&fields, false); exception.set_body(&fields, false);
exception.ptr_type(AddressSpace::default()).as_basic_type_enum() exception.ptr_type(AddressSpace::default()).as_basic_type_enum()

View File

@ -9,7 +9,7 @@ use crate::{
irrt::{ irrt::{
calculate_len_for_slice_range, call_ndarray_calc_broadcast, calculate_len_for_slice_range, call_ndarray_calc_broadcast,
call_ndarray_calc_broadcast_index, call_ndarray_calc_nd_indices, call_ndarray_calc_broadcast_index, call_ndarray_calc_nd_indices,
call_ndarray_calc_size, call_ndarray_calc_size, error_context::call_nac3_dummy_raise,
}, },
llvm_intrinsics::{self, call_memcpy_generic}, llvm_intrinsics::{self, call_memcpy_generic},
stmt::{gen_for_callback_incrementing, gen_for_range_callback, gen_if_else_expr_callback}, stmt::{gen_for_callback_incrementing, gen_for_range_callback, gen_if_else_expr_callback},
@ -538,6 +538,8 @@ fn call_ndarray_zeros_impl<'ctx, G: CodeGenerator + ?Sized>(
elem_ty: Type, elem_ty: Type,
shape: BasicValueEnum<'ctx>, shape: BasicValueEnum<'ctx>,
) -> Result<NDArrayValue<'ctx>, String> { ) -> Result<NDArrayValue<'ctx>, String> {
call_nac3_dummy_raise(generator, ctx);
let supported_types = [ let supported_types = [
ctx.primitives.int32, ctx.primitives.int32,
ctx.primitives.int64, ctx.primitives.int64,