diff --git a/nac3core/irrt/irrt/error_context.hpp b/nac3core/irrt/irrt/error_context.hpp index 509988ba..e30f4e11 100644 --- a/nac3core/irrt/irrt/error_context.hpp +++ b/nac3core/irrt/irrt/error_context.hpp @@ -87,6 +87,6 @@ void __nac3_error_context_get_exception_str64(ErrorContext *errctx, // Used for testing void __nac3_error_dummy_raise(ErrorContext *errctx) { errctx->set_exception(errctx->exceptions->runtime_error, - "Error thrown from __nac3_error_dummy_raise"); + "Error thrown from __nac3_error_dummy_raise"); } } \ No newline at end of file diff --git a/nac3core/irrt/irrt/ndarray/basic.hpp b/nac3core/irrt/irrt/ndarray/basic.hpp new file mode 100644 index 00000000..3e91ea71 --- /dev/null +++ b/nac3core/irrt/irrt/ndarray/basic.hpp @@ -0,0 +1,306 @@ +#pragma once + +#include +#include +#include + +namespace { +namespace ndarray { +namespace basic { +namespace util { +/** + * @brief Asserts that `shape` does not contain negative dimensions. + * + * @param ndims Number of dimensions in `shape` + * @param shape The shape to check on + */ +template +void assert_shape_no_negative(ErrorContext* errctx, SizeT ndims, + const SizeT* shape) { + for (SizeT axis = 0; axis < ndims; axis++) { + if (shape[axis] < 0) { + errctx->set_exception( + errctx->exceptions->value_error, + "negative dimensions are not allowed; axis {0} " + "has dimension {1}", + axis, shape[axis]); + return; + } + } +} + +/** + * @brief Returns the number of elements of an ndarray given its shape. + * + * @param ndims Number of dimensions in `shape` + * @param shape The shape of the ndarray + */ +template +SizeT calc_size_from_shape(SizeT ndims, const SizeT* shape) { + SizeT size = 1; + for (SizeT axis = 0; axis < ndims; axis++) size *= shape[axis]; + return size; +} + +/** + * @brief Compute the array indices of the `nth` (0-based) element of an ndarray given only its shape. + * + * @param ndims Number of elements in `shape` and `indices` + * @param shape The shape of the ndarray + * @param indices The returned indices indexing the ndarray with shape `shape`. + * @param nth The index of the element of interest. + */ +template +void set_indices_by_nth(SizeT ndims, const SizeT* shape, SizeT* indices, + SizeT nth) { + for (int32_t i = 0; i < ndims; i++) { + int32_t axis = ndims - i - 1; + int32_t dim = shape[axis]; + + indices[axis] = nth % dim; + nth /= dim; + } +} +} // namespace util + +/** + * @brief Return the number of elements of an `ndarray` + * + * This function corresponds to `.size` + */ +template +SizeT size(const NDArray* ndarray) { + return util::calc_size_from_shape(ndarray->ndims, ndarray->shape); +} + +/** + * @brief Return of the number of its content of an `ndarray`. + * + * This function corresponds to `.nbytes`. + */ +template +SizeT nbytes(const NDArray* ndarray) { + return size(ndarray) * ndarray->itemsize; +} + +/** + * @brief Update the strides of an ndarray given an ndarray `shape` + * and assuming that the ndarray is fully c-contagious. + * + * You might want to read https://ajcr.net/stride-guide-part-1/. + */ +template +void set_strides_by_shape(NDArray* ndarray) { + SizeT stride_product = 1; + for (SizeT i = 0; i < ndarray->ndims; i++) { + int axis = ndarray->ndims - i - 1; + ndarray->strides[axis] = stride_product * ndarray->itemsize; + stride_product *= ndarray->shape[axis]; + } +} + +/** + * @brief Return the pointer to the element indexed by `indices`. + */ +template +uint8_t* get_pelement_by_indices(const NDArray* ndarray, + const SizeT* indices) { + uint8_t* element = ndarray->data; + for (SizeT dim_i = 0; dim_i < ndarray->ndims; dim_i++) + element += indices[dim_i] * ndarray->strides[dim_i]; + return element; +} + +/** + * @brief Return the pointer to the nth (0-based) element in a flattened view of `ndarray`. + */ +template +uint8_t* get_nth_pelement(const NDArray* ndarray, SizeT nth) { + SizeT* indices = (SizeT*)__builtin_alloca(sizeof(SizeT) * ndarray->ndims); + util::set_indices_by_nth(ndarray->ndims, ndarray->shape, indices, nth); + return get_pelement_by_indices(ndarray, indices); +} + +/** + * @brief Like `get_nth_pelement` but asserts that `nth` is in bounds. + */ +template +uint8_t* checked_get_nth_pelement(ErrorContext* errctx, + const NDArray* ndarray, SizeT nth) { + SizeT arr_size = ndarray->size(); + if (!(0 <= nth && nth < arr_size)) { + errctx->set_exception( + errctx->exceptions->index_error, + "index {0} is out of bounds, valid range is {1} <= index < {2}", + nth, 0, arr_size); + return 0; + } + return get_nth_pelement(ndarray, nth); +} + +/** + * @brief Set an element in `ndarray`. + * + * @param pelement Pointer to the element in `ndarray` to be set. + * @param pvalue Pointer to the value `pelement` will be set to. + */ +template +void set_pelement_value(NDArray* ndarray, uint8_t* pelement, + const uint8_t* pvalue) { + __builtin_memcpy(pelement, pvalue, ndarray->itemsize); +} + +/** + * @brief Get the `len()` of an ndarray, and asserts that `ndarray` is a sized object. + * + * This function corresponds to `.__len__`. + * + * @param dst_length The returned result + */ +template +void len(ErrorContext* errctx, const NDArray* ndarray, + SliceIndex* dst_length) { + // numpy prohibits `__len__` on unsized objects + if (ndarray->ndims == 0) { + errctx->set_exception(errctx->exceptions->type_error, + "len() of unsized object"); + return; + } + + *dst_length = (SliceIndex)ndarray->shape[0]; +} + +/** + * @brief Copy data from one ndarray to another of the exact same size and itemsize. + * + * Both ndarrays will be viewed in their flatten views when copying the elements. + */ +template +void copy_data(const NDArray* src_ndarray, NDArray* dst_ndarray) { + __builtin_assume(src_ndarray->itemsize == dst_ndarray->itemsize); + + for (SizeT i = 0; i < size(src_ndarray); i++) { + auto src_element = ndarray::basic::get_nth_pelement(src_ndarray, i); + auto dst_element = ndarray::basic::get_nth_pelement(dst_ndarray, i); + ndarray::basic::set_pelement_value(dst_ndarray, dst_element, + src_element); + } +} + +/** + * @brief Return a boolean indicating if `ndarray` is (C-)contiguous. + * + * You may want to see: ndarray's rules for C-contiguity: https://github.com/numpy/numpy/blob/df256d0d2f3bc6833699529824781c58f9c6e697/numpy/core/src/multiarray/flagsobject.c#L95C1-L99C45 + */ +template +bool is_c_contiguous(const NDArray* ndarray) { + // Other references: + // - tinynumpy's implementation: https://github.com/wadetb/tinynumpy/blob/0d23d22e07062ffab2afa287374c7b366eebdda1/tinynumpy/tinynumpy.py#L102 + // - ndarray's flags["C_CONTIGUOUS"]: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.flags.html#numpy.ndarray.flags + // - ndarray's rules for C-contiguity: https://github.com/numpy/numpy/blob/df256d0d2f3bc6833699529824781c58f9c6e697/numpy/core/src/multiarray/flagsobject.c#L95C1-L99C45 + + // From https://github.com/numpy/numpy/blob/df256d0d2f3bc6833699529824781c58f9c6e697/numpy/core/src/multiarray/flagsobject.c#L95C1-L99C45: + // + // The traditional rule is that for an array to be flagged as C contiguous, + // the following must hold: + // + // strides[-1] == itemsize + // strides[i] == shape[i+1] * strides[i + 1] + // [...] + // According to these rules, a 0- or 1-dimensional array is either both + // C- and F-contiguous, or neither; and an array with 2+ dimensions + // can be C- or F- contiguous, or neither, but not both. Though there + // there are exceptions for arrays with zero or one item, in the first + // case the check is relaxed up to and including the first dimension + // with shape[i] == 0. In the second case `strides == itemsize` will + // can be true for all dimensions and both flags are set. + + if (ndarray->ndims == 0) { + return true; + } + + if (ndarray->strides[ndarray->ndims - 1] != ndarray->itemsize) { + return false; + } + + for (SizeT i = 1; i < ndarray->ndims; i++) { + SizeT axis_i = ndarray->ndims - i - 1; + if (ndarray->strides[axis_i] != + ndarray->shape[axis_i + 1] + ndarray->strides[axis_i + 1]) { + return false; + } + } + + return true; +} +} // namespace basic +} // namespace ndarray +} // namespace + +extern "C" { +using namespace ndarray::basic; + +uint32_t __nac3_ndarray_size(NDArray* ndarray) { + return size(ndarray); +} + +uint64_t __nac3_ndarray_size64(NDArray* ndarray) { + return size(ndarray); +} + +uint32_t __nac3_ndarray_nbytes(NDArray* ndarray) { + return nbytes(ndarray); +} + +uint64_t __nac3_ndarray_nbytes64(NDArray* ndarray) { + return nbytes(ndarray); +} + +void __nac3_ndarray_len(ErrorContext* errctx, NDArray* ndarray, + SliceIndex* dst_len) { + return len(errctx, ndarray, dst_len); +} + +void __nac3_ndarray_len64(ErrorContext* errctx, NDArray* ndarray, + SliceIndex* dst_len) { + return len(errctx, ndarray, dst_len); +} + +void __nac3_ndarray_util_assert_shape_no_negative(ErrorContext* errctx, + int32_t ndims, + int32_t* shape) { + util::assert_shape_no_negative(errctx, ndims, shape); +} + +void __nac3_ndarray_util_assert_shape_no_negative64(ErrorContext* errctx, + int64_t ndims, + int64_t* shape) { + util::assert_shape_no_negative(errctx, ndims, shape); +} + +void __nac3_ndarray_set_strides_by_shape(NDArray* ndarray) { + set_strides_by_shape(ndarray); +} + +void __nac3_ndarray_set_strides_by_shape64(NDArray* ndarray) { + set_strides_by_shape(ndarray); +} + +bool __nac3_ndarray_is_c_contiguous(NDArray* ndarray) { + return is_c_contiguous(ndarray); +} + +bool __nac3_ndarray_is_c_contiguous64(NDArray* ndarray) { + return is_c_contiguous(ndarray); +} + +void __nac3_ndarray_copy_data(NDArray* src_ndarray, + NDArray* dst_ndarray) { + copy_data(src_ndarray, dst_ndarray); +} + +void __nac3_ndarray_copy_data64(NDArray* src_ndarray, + NDArray* dst_ndarray) { + copy_data(src_ndarray, dst_ndarray); +} +} \ No newline at end of file diff --git a/nac3core/irrt/irrt_everything.hpp b/nac3core/irrt/irrt_everything.hpp index 16ea68e7..f6558051 100644 --- a/nac3core/irrt/irrt_everything.hpp +++ b/nac3core/irrt/irrt_everything.hpp @@ -4,5 +4,6 @@ #include #include #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..c8622565 100644 --- a/nac3core/irrt/irrt_test.cpp +++ b/nac3core/irrt/irrt_test.cpp @@ -5,8 +5,10 @@ #include #include #include +#include int main() { test::core::run(); + test::ndarray_basic::run(); return 0; } \ No newline at end of file diff --git a/nac3core/irrt/test/test_ndarray_basic.hpp b/nac3core/irrt/test/test_ndarray_basic.hpp new file mode 100644 index 00000000..1bbdab26 --- /dev/null +++ b/nac3core/irrt/test/test_ndarray_basic.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include + +namespace test { +namespace ndarray_basic { +void test_calc_size_from_shape_normal() { + // Test shapes with normal values + BEGIN_TEST(); + + int32_t shape[4] = {2, 3, 5, 7}; + assert_values_match( + 210, ndarray::basic::util::calc_size_from_shape(4, shape)); +} + +void test_calc_size_from_shape_has_zero() { + // Test shapes with 0 in them + BEGIN_TEST(); + + int32_t shape[4] = {2, 0, 5, 7}; + assert_values_match( + 0, ndarray::basic::util::calc_size_from_shape(4, shape)); +} + +void run() { + test_calc_size_from_shape_normal(); + test_calc_size_from_shape_has_zero(); +} +} // namespace ndarray_basic +} // namespace test \ No newline at end of file diff --git a/nac3core/src/codegen/irrt/mod.rs b/nac3core/src/codegen/irrt/mod.rs index fe2aa640..829bef3d 100644 --- a/nac3core/src/codegen/irrt/mod.rs +++ b/nac3core/src/codegen/irrt/mod.rs @@ -1,6 +1,8 @@ use crate::typecheck::typedef::Type; pub mod error_context; +pub mod ndarray; +pub mod slice; mod test; mod util; diff --git a/nac3core/src/codegen/irrt/ndarray/allocation.rs b/nac3core/src/codegen/irrt/ndarray/allocation.rs new file mode 100644 index 00000000..ff94afeb --- /dev/null +++ b/nac3core/src/codegen/irrt/ndarray/allocation.rs @@ -0,0 +1,83 @@ +use crate::codegen::model::*; +use crate::codegen::util::array_writer::ArrayWriter; +use crate::codegen::{structure::ndarray::NpArray, CodeGenContext, CodeGenerator}; + +use super::basic::{ + call_nac3_ndarray_nbytes, call_nac3_ndarray_set_strides_by_shape, + call_nac3_ndarray_util_assert_shape_no_negative, +}; + +/** + Allocate an ndarray on the stack given its `ndims`. + + `shape` and `strides` will be automatically allocated on the stack. + + The returned ndarray's content will be: + - `data`: `nullptr` + - `itemsize`: **uninitialized** value + - `ndims`: initialized value, set to the input `ndims` + - `shape`: initialized pointer to an allocated stack with **uninitialized** values + - `strides`: initialized pointer to an allocated stack with **uninitialized** values +*/ +pub fn alloca_ndarray<'ctx, G>( + generator: &mut G, + ctx: &mut CodeGenContext<'ctx, '_>, + ndims: Int<'ctx, SizeT>, + name: &str, +) -> Result>, String> +where + G: CodeGenerator + ?Sized, +{ + let tyctx = generator.type_context(ctx.ctx); + + let sizet_model = IntModel(SizeT); + let ndarray_model = StructModel(NpArray); + let ndarray_data_model = PtrModel(IntModel(Byte)); + + // Setup ndarray + let ndarray_ptr = ndarray_model.alloca(tyctx, ctx, name); + let shape = sizet_model.array_alloca(tyctx, ctx, ndims.value, "shape"); + let strides = sizet_model.array_alloca(tyctx, ctx, ndims.value, "strides"); + + ndarray_ptr.gep(ctx, |f| f.data).store(ctx, ndarray_data_model.nullptr(tyctx, ctx.ctx)); + ndarray_ptr.gep(ctx, |f| f.ndims).store(ctx, ndims); + ndarray_ptr.gep(ctx, |f| f.shape).store(ctx, shape); + ndarray_ptr.gep(ctx, |f| f.strides).store(ctx, strides); + + Ok(ndarray_ptr) +} + +/// Initialize an ndarray's `shape` and asserts on. +/// `shape`'s values and prohibit illegal inputs like negative dimensions. +pub fn init_ndarray_shape<'ctx, G: CodeGenerator + ?Sized>( + generator: &mut G, + ctx: &mut CodeGenContext<'ctx, '_>, + pndarray: Ptr<'ctx, StructModel>, + shape_writer: &ArrayWriter<'ctx, G, SizeT, IntModel>, +) -> Result<(), String> { + let tyctx = generator.type_context(ctx.ctx); + let shape = pndarray.gep(ctx, |f| f.shape).load(tyctx, ctx, "shape"); + (shape_writer.write)(generator, ctx, shape)?; + call_nac3_ndarray_util_assert_shape_no_negative(generator, ctx, shape_writer.len, shape); + Ok(()) +} + +/// Initialize an ndarray's `data` by allocating a buffer on the stack. +/// The allocated data buffer is considered to be *owned* by the ndarray. +/// +/// `strides` of the ndarray will also be updated with `set_strides_by_shape`. +/// +/// `shape` and `itemsize` of the ndarray ***must*** be initialized first. +pub fn init_ndarray_data_by_alloca<'ctx, G: CodeGenerator + ?Sized>( + generator: &mut G, + ctx: &mut CodeGenContext<'ctx, '_>, + pndarray: Ptr<'ctx, StructModel>, +) { + let tyctx = generator.type_context(ctx.ctx); + let ndarray_data_model = IntModel(Byte); + + let nbytes = call_nac3_ndarray_nbytes(generator, ctx, pndarray); + let data = ndarray_data_model.array_alloca(tyctx, ctx, nbytes.value, "data"); + pndarray.gep(ctx, |f| f.data).store(ctx, data); + call_nac3_ndarray_set_strides_by_shape(generator, ctx, pndarray); +} diff --git a/nac3core/src/codegen/irrt/ndarray/basic.rs b/nac3core/src/codegen/irrt/ndarray/basic.rs new file mode 100644 index 00000000..a4796ef7 --- /dev/null +++ b/nac3core/src/codegen/irrt/ndarray/basic.rs @@ -0,0 +1,135 @@ +use crate::codegen::irrt::error_context::{check_error_context, setup_error_context}; +use crate::codegen::irrt::slice::SliceIndex; +use crate::codegen::irrt::util::function::CallFunction; +use crate::codegen::irrt::util::get_sizet_dependent_function_name; +use crate::codegen::model::*; +use crate::codegen::structure::ndarray::NpArray; +use crate::codegen::{CodeGenContext, CodeGenerator}; + +pub fn call_nac3_ndarray_size<'ctx, G: CodeGenerator + ?Sized>( + generator: &mut G, + ctx: &mut CodeGenContext<'ctx, '_>, + ndarray_ptr: Ptr<'ctx, StructModel>, +) -> Int<'ctx, SizeT> { + let tyctx = generator.type_context(ctx.ctx); + + CallFunction::begin( + tyctx, + ctx, + &get_sizet_dependent_function_name(tyctx, "__nac3_ndarray_size"), + ) + .arg("ndarray", ndarray_ptr) + .returning("size") +} + +pub fn call_nac3_ndarray_nbytes<'ctx, G: CodeGenerator + ?Sized>( + generator: &mut G, + ctx: &mut CodeGenContext<'ctx, '_>, + ndarray_ptr: Ptr<'ctx, StructModel>, +) -> Int<'ctx, SizeT> { + let tyctx = generator.type_context(ctx.ctx); + + CallFunction::begin( + tyctx, + ctx, + &get_sizet_dependent_function_name(tyctx, "__nac3_ndarray_nbytes"), + ) + .arg("ndarray", ndarray_ptr) + .returning("nbytes") +} + +pub fn call_nac3_ndarray_len<'ctx, G: CodeGenerator + ?Sized>( + generator: &mut G, + ctx: &mut CodeGenContext<'ctx, '_>, + ndarray_ptr: Ptr<'ctx, StructModel>, +) -> Int<'ctx, SliceIndex> { + let tyctx = generator.type_context(ctx.ctx); + let slice_index_model = IntModel(SliceIndex::default()); + + let dst_len = slice_index_model.alloca(tyctx, ctx, "dst_len"); + + let errctx = setup_error_context(tyctx, ctx); + CallFunction::begin( + tyctx, + ctx, + &get_sizet_dependent_function_name(tyctx, "__nac3_ndarray_len"), + ) + .arg("errctx", errctx) + .arg("ndarray", ndarray_ptr) + .arg("dst_len", dst_len) + .returning_void(); + check_error_context(generator, ctx, errctx); + + dst_len.load(tyctx, ctx, "len") +} + +pub fn call_nac3_ndarray_util_assert_shape_no_negative<'ctx, G: CodeGenerator + ?Sized>( + generator: &mut G, + ctx: &mut CodeGenContext<'ctx, '_>, + ndims: Int<'ctx, SizeT>, + shape: Ptr<'ctx, IntModel>, +) { + let tyctx = generator.type_context(ctx.ctx); + + let errctx = setup_error_context(tyctx, ctx); + CallFunction::begin( + tyctx, + ctx, + &get_sizet_dependent_function_name(tyctx, "__nac3_ndarray_util_assert_shape_no_negative"), + ) + .arg("errctx", errctx) + .arg("ndims", ndims) + .arg("shape", shape) + .returning_void(); + check_error_context(generator, ctx, errctx); +} + +pub fn call_nac3_ndarray_set_strides_by_shape<'ctx, G: CodeGenerator + ?Sized>( + generator: &mut G, + ctx: &mut CodeGenContext<'ctx, '_>, + ndarray_ptr: Ptr<'ctx, StructModel>, +) { + let tyctx = generator.type_context(ctx.ctx); + + CallFunction::begin( + tyctx, + ctx, + &get_sizet_dependent_function_name(tyctx, "__nac3_ndarray_set_strides_by_shape"), + ) + .arg("ndarray", ndarray_ptr) + .returning_void(); +} + +pub fn call_nac3_ndarray_is_c_contiguous<'ctx, G: CodeGenerator + ?Sized>( + generator: &mut G, + ctx: &mut CodeGenContext<'ctx, '_>, + ndarray_ptr: Ptr<'ctx, StructModel>, +) -> Int<'ctx, Bool> { + let tyctx = generator.type_context(ctx.ctx); + + CallFunction::begin( + tyctx, + ctx, + &get_sizet_dependent_function_name(tyctx, "__nac3_ndarray_is_c_contiguous"), + ) + .arg("ndarray", ndarray_ptr) + .returning("is_c_contiguous") +} + +pub fn call_nac3_ndarray_copy_data<'ctx, G: CodeGenerator + ?Sized>( + generator: &mut G, + ctx: &mut CodeGenContext<'ctx, '_>, + src_ndarray: Ptr<'ctx, StructModel>, + dst_ndarray: Ptr<'ctx, StructModel>, +) -> Int<'ctx, Bool> { + let tyctx = generator.type_context(ctx.ctx); + + CallFunction::begin( + tyctx, + ctx, + &get_sizet_dependent_function_name(tyctx, "__nac3_ndarray_copy_data"), + ) + .arg("src_ndarray", src_ndarray) + .arg("dst_ndarray", dst_ndarray) + .returning("is_c_contiguous") +} diff --git a/nac3core/src/codegen/irrt/ndarray/mod.rs b/nac3core/src/codegen/irrt/ndarray/mod.rs new file mode 100644 index 00000000..8648d99f --- /dev/null +++ b/nac3core/src/codegen/irrt/ndarray/mod.rs @@ -0,0 +1,2 @@ +pub mod allocation; +pub mod basic; diff --git a/nac3core/src/codegen/irrt/slice.rs b/nac3core/src/codegen/irrt/slice.rs new file mode 100644 index 00000000..ae768892 --- /dev/null +++ b/nac3core/src/codegen/irrt/slice.rs @@ -0,0 +1,3 @@ +use crate::codegen::model::*; + +pub type SliceIndex = Int32;