kfr

Fast, modern C++ DSP framework, FFT, Sample Rate Conversion, FIR/IIR/Biquad Filters (SSE, AVX, AVX-512, ARM NEON)
Log | Files | Refs | README

commit 96249c0adef7f4f54630398c00b90cbeb7291a78
parent 1556f65ddc63e3da13bfc340a6bf3dbbb3512a4f
Author: d.levin256@gmail.com <d.levin256@gmail.com>
Date:   Tue,  1 Nov 2022 15:26:55 +0000

Runtime checks

Diffstat:
Minclude/kfr/base/expression.hpp | 24++++--------------------
Minclude/kfr/base/shape.hpp | 45+++++++++++++++++++++++++++------------------
Minclude/kfr/cident.h | 10++++++----
Minclude/kfr/dsp/ebu.hpp | 5++++-
Ainclude/kfr/except.hpp | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minclude/kfr/kfr.h | 22++++++++++++++++++++++
Minclude/kfr/simd/impl/backend_generic.hpp | 5+----
Msources.cmake | 2++
Mtests/unit/base/basic_expressions.cpp | 8+++++++-
Mtests/unit/base/shape.cpp | 4++++
10 files changed, 167 insertions(+), 48 deletions(-)

diff --git a/include/kfr/base/expression.hpp b/include/kfr/base/expression.hpp @@ -399,13 +399,12 @@ struct expression_function : expression_with_arguments<Args...>, expression_trai constexpr static shape<dims> shapeof(const expression_function& self) { return self.fold([&](auto&&... args) CMT_INLINE_LAMBDA constexpr->auto { - return internal_generic::common_shape(expression_traits<decltype(args)>::shapeof(args)...); + return internal_generic::common_shape<true>(expression_traits<decltype(args)>::shapeof(args)...); }); } constexpr static shape<dims> shapeof() { - return expression_function<Fn, - Args...>::fold_idx([&](auto... args) CMT_INLINE_LAMBDA constexpr->auto { + return expression_function::fold_idx([&](auto... args) CMT_INLINE_LAMBDA constexpr->auto { return internal_generic::common_shape( expression_traits< typename expression_function::template nth<val_of(decltype(args)())>>::shapeof()...); @@ -419,28 +418,14 @@ struct expression_function : expression_with_arguments<Args...>, expression_trai KFR_MEM_INTRINSIC expression_function(expression_with_arguments<Args...> args, Fn&& fn) : expression_with_arguments<Args...>{ std::move(args) }, fn(std::forward<Fn>(fn)) { - check_shapes(); } KFR_MEM_INTRINSIC expression_function(Fn&& fn, Args&&... args) : expression_with_arguments<Args...>{ std::forward<Args>(args)... }, fn(std::forward<Fn>(fn)) { - check_shapes(); } KFR_MEM_INTRINSIC expression_function(Args&&... args) : expression_with_arguments<Args...>{ std::forward<Args>(args)... }, fn{} { - check_shapes(); - } - KFR_MEM_INTRINSIC void check_shapes() - { - if constexpr (dims > 0) - { - shape<dims> sh = shapeof(*this); - if (sh == shape<dims>(0)) - { - // throw std::runtime_error("KFR: Invalid shapes in expression_function"); - } - } } template <typename In, enable_if_input_expression<In>* = nullptr> @@ -578,10 +563,9 @@ KFR_INTRINSIC static void tprocess_body(Out&& out, In&& in, size_t start, size_t { outidx[OutAxis] = x; inidx[InAxis] = std::min(x, insize - 1); - auto v = get_elements(in, inidx, axis_params_v<InAxis, w>); + auto v = get_elements(in, inidx, axis_params_v<InAxis, w>); // println("## i=", x, "\n", v); - set_elements(out, outidx, axis_params_v<OutAxis, w>, - v ); + set_elements(out, outidx, axis_params_v<OutAxis, w>, v); } } CMT_LOOP_NOUNROLL diff --git a/include/kfr/base/shape.hpp b/include/kfr/base/shape.hpp @@ -25,6 +25,7 @@ */ #pragma once +#include "../except.hpp" #include "impl/static_array.hpp" #include "../cometa/string.hpp" @@ -146,7 +147,7 @@ struct shape : static_array_base<index_t, csizeseq_t<dims>> { for (index_t i = 0; i < dims; ++i) { - if (this->operator[](i) == infinite_size) + if (CMT_UNLIKELY(this->operator[](i) == infinite_size)) return true; } return false; @@ -317,7 +318,7 @@ struct shape : static_array_base<index_t, csizeseq_t<dims>> } KFR_MEM_INTRINSIC constexpr void set_revindex(size_t index, index_t val) { - if (index < dims) + if (CMT_LIKELY(index < dims)) this->operator[](dims - 1 - index) = val; } }; @@ -469,7 +470,7 @@ constexpr KFR_INTRINSIC shape<outdims> compact_shape(const shape<dims>& in) size_t j = 0; for (size_t i = 0; i < dims; ++i) { - if (i >= flags.size() || flags[i]) + if (CMT_LIKELY(i >= flags.size() || flags[i])) { result[j++] = in[i]; } @@ -500,8 +501,8 @@ bool can_assign_from(const shape<dims1>& dst_shape, const shape<dims2>& src_shap { index_t dst_size = dst_shape.revindex(i); index_t src_size = src_shape.revindex(i); - if (src_size == 1 || src_size == infinite_size || src_size == dst_size || - dst_size == infinite_size) + if (CMT_LIKELY(src_size == 1 || src_size == infinite_size || src_size == dst_size || + dst_size == infinite_size)) { } else @@ -514,13 +515,13 @@ bool can_assign_from(const shape<dims1>& dst_shape, const shape<dims2>& src_shap } } -template <index_t dims> +template <bool checked = false, index_t dims> constexpr shape<dims> common_shape(const shape<dims>& shape) { return shape; } -template <index_t dims1, index_t dims2, index_t outdims = const_max(dims1, dims2)> +template <bool checked = false, index_t dims1, index_t dims2, index_t outdims = const_max(dims1, dims2)> KFR_MEM_INTRINSIC constexpr shape<outdims> common_shape(const shape<dims1>& shape1, const shape<dims2>& shape2) { @@ -529,15 +530,15 @@ KFR_MEM_INTRINSIC constexpr shape<outdims> common_shape(const shape<dims1>& shap { index_t size1 = shape1.revindex(i); index_t size2 = shape2.revindex(i); - if (!size1 || !size2) + if (CMT_UNLIKELY(!size1 || !size2)) { result[outdims - 1 - i] = 0; continue; } - if (size1 == infinite_size) + if (CMT_UNLIKELY(size1 == infinite_size)) { - if (size2 == infinite_size) + if (CMT_UNLIKELY(size2 == infinite_size)) { result[outdims - 1 - i] = infinite_size; } @@ -548,21 +549,28 @@ KFR_MEM_INTRINSIC constexpr shape<outdims> common_shape(const shape<dims1>& shap } else { - if (size2 == infinite_size) + if (CMT_UNLIKELY(size2 == infinite_size)) { result[outdims - 1 - i] = size1 == 1 ? infinite_size : size1; } else { - if (size1 == 1 || size2 == 1 || size1 == size2) + if (CMT_LIKELY(size1 == 1 || size2 == 1 || size1 == size2)) { result[outdims - 1 - i] = std::max(size1, size2); } else { // broadcast failed - result = shape<outdims>(0); - return result; + if constexpr (checked) + { + KFR_LOGIC_CHECK(false, "invalid or incompatible shapes: ", shape1, " and ", shape2); + } + else + { + result = shape<outdims>(0); + return result; + } } } } @@ -570,18 +578,19 @@ KFR_MEM_INTRINSIC constexpr shape<outdims> common_shape(const shape<dims1>& shap return result; } -template <> +template <bool checked = false> KFR_MEM_INTRINSIC constexpr shape<0> common_shape(const shape<0>& shape1, const shape<0>& shape2) { return {}; } -template <index_t dims1, index_t dims2, index_t... dims, index_t outdims = const_max(dims1, dims2, dims...)> +template <bool checked = false, index_t dims1, index_t dims2, index_t... dims, + index_t outdims = const_max(dims1, dims2, dims...)> KFR_MEM_INTRINSIC constexpr shape<outdims> common_shape(const shape<dims1>& shape1, const shape<dims2>& shape2, const shape<dims>&... shapes) { - return common_shape(shape1, common_shape(shape2, shapes...)); + return common_shape<checked>(shape1, common_shape(shape2, shapes...)); } template <index_t dims1, index_t dims2> @@ -691,7 +700,7 @@ KFR_INTRINSIC shape<dims> increment_indices_return(const shape<dims>& indices, c const shape<dims>& stop, index_t dim = dims - 1) { shape<dims> result = indices; - if (increment_indices(result, start, stop, dim)) + if (CMT_LIKELY(increment_indices(result, start, stop, dim))) { return result; } diff --git a/include/kfr/cident.h b/include/kfr/cident.h @@ -457,13 +457,15 @@ extern char* gets(char* __s); #define CMT_HAS_BUILTIN(builtin) 0 #endif -#if CMT_HAS_BUILTIN(CMT_ASSUME) -#define CMT_ASSUME(x) __builtin_assume(x) -#else -#define CMT_ASSUME(x) \ +#define CMT_NOOP \ do \ { \ } while (0) + +#if CMT_HAS_BUILTIN(CMT_ASSUME) +#define CMT_ASSUME(x) __builtin_assume(x) +#else +#define CMT_ASSUME(x) CMT_NOOP #endif #if CMT_HAS_BUILTIN(CMT_ASSUME) diff --git a/include/kfr/dsp/ebu.hpp b/include/kfr/dsp/ebu.hpp @@ -264,6 +264,10 @@ public: : m_sample_rate(sample_rate), m_running(true), m_need_reset(false), m_packet_size(sample_rate / 10 / packet_size_factor) { + KFR_LOGIC_CHECK(!channels.empty(), "channels must not be empty"); + KFR_LOGIC_CHECK(sample_rate > 0, "sample_rate must be greater than 0"); + KFR_LOGIC_CHECK(packet_size_factor >= 1 && packet_size_factor <= 3, + "packet_size_factor must be in range [1..3]"); for (Speaker sp : channels) { m_channels.emplace_back(sample_rate, sp, packet_size_factor, T(1)); @@ -311,7 +315,6 @@ public: T shortterm = 0; for (size_t ch = 0; ch < m_channels.size(); ch++) { - // println(ch, "=> ", source[ch][0], " ", source[ch][10], " ", source[ch][20] ); TESTO_ASSERT(source[ch].size() == m_packet_size); ebu_channel<T>& chan = m_channels[ch]; chan.process_packet(source[ch].data()); diff --git a/include/kfr/except.hpp b/include/kfr/except.hpp @@ -0,0 +1,90 @@ +/* + Copyright (C) 2016-2022 Fractalium Ltd (https://www.kfrlib.com) + This file is part of KFR + + KFR is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + KFR is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with KFR. + + If GPL is not suitable for your project, you must purchase a commercial license to use KFR. + Buying a commercial license is mandatory as soon as you develop commercial activities without + disclosing the source code of your own applications. + See https://www.kfrlib.com for details. + */ +#pragma once +#include "cident.h" +#include "cometa/string.hpp" +#include <exception> + +namespace kfr +{ + +class exception : public std::exception +{ +public: + using std::exception::exception; + exception(const std::string& str) : exception(str.c_str()) {} +}; +class logic_error : public exception +{ +public: + using exception::exception; +}; +class runtime_error : public exception +{ +public: + using exception::exception; +}; + +#ifndef KFR_THROW_EXCEPTION +#define KFR_THROW_EXCEPTION(kind, ...) \ + do \ + { \ + throw ::kfr::CMT_CONCAT(kind, _error)(kfr::as_string(__VA_ARGS__)); \ + } while (0) +#endif + +#define KFR_PRINT_AND_ABORT(kind, ...) \ + do \ + { \ + std::string s = kfr::as_string(__VA_ARGS__); \ + std::fprintf(stderr, "KFR " CMT_STRINGIFY(kind) " error: %s\n", s.c_str()); \ + std::fflush(stderr); \ + std::abort(); \ + } while (0) + +#if defined __cpp_exceptions || defined _HAS_EXCEPTIONS || defined _EXCEPTIONS +#define KFR_REPORT_ERROR KFR_THROW_EXCEPTION +#else +#define KFR_REPORT_ERROR KFR_PRINT_AND_ABORT +#endif + +#define KFR_CHECK_IMPL(cond, kind, ...) \ + do \ + { \ + if (CMT_UNLIKELY(!(cond))) \ + KFR_REPORT_ERROR(kind, __VA_ARGS__); \ + } while (0) + +#if !defined(KFR_DISABLE_CHECKS) + +#define KFR_RUNTIME_CHECK(cond, ...) KFR_CHECK_IMPL(cond, runtime, __VA_ARGS__) + +#define KFR_LOGIC_CHECK(cond, ...) KFR_CHECK_IMPL(cond, logic, __VA_ARGS__) + +#else +#define KFR_RUNTIME_CHECK(cond, ...) CMT_NOOP +#define KFR_LOGIC_CHECK(cond, ...) CMT_NOOP + +#endif + +} // namespace kfr diff --git a/include/kfr/kfr.h b/include/kfr/kfr.h @@ -1,3 +1,25 @@ +/* + Copyright (C) 2016-2022 Fractalium Ltd (https://www.kfrlib.com) + This file is part of KFR + + KFR is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + KFR is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with KFR. + + If GPL is not suitable for your project, you must purchase a commercial license to use KFR. + Buying a commercial license is mandatory as soon as you develop commercial activities without + disclosing the source code of your own applications. + See https://www.kfrlib.com for details. + */ /** @addtogroup utility * @{ */ diff --git a/include/kfr/simd/impl/backend_generic.hpp b/include/kfr/simd/impl/backend_generic.hpp @@ -71,10 +71,7 @@ struct shuffle_mask<2, i0, i1> #if KFR_SHOW_NOT_OPTIMIZED CMT_PUBLIC_C CMT_DLL_EXPORT void not_optimized(const char* fn) CMT_NOEXCEPT; #else -#define not_optimized(...) \ - do \ - { \ - } while (0) +#define not_optimized(...) CMT_NOOP #endif inline namespace CMT_ARCH_NAME diff --git a/sources.cmake b/sources.cmake @@ -9,6 +9,7 @@ set( ${PROJECT_SOURCE_DIR}/include/kfr/cometa.hpp ${PROJECT_SOURCE_DIR}/include/kfr/dft.hpp ${PROJECT_SOURCE_DIR}/include/kfr/dsp.hpp + ${PROJECT_SOURCE_DIR}/include/kfr/except.hpp ${PROJECT_SOURCE_DIR}/include/kfr/graphics.hpp ${PROJECT_SOURCE_DIR}/include/kfr/io.hpp ${PROJECT_SOURCE_DIR}/include/kfr/math.hpp @@ -317,6 +318,7 @@ set( ${PROJECT_SOURCE_DIR}/include/kfr/cometa.hpp ${PROJECT_SOURCE_DIR}/include/kfr/dft.hpp ${PROJECT_SOURCE_DIR}/include/kfr/dsp.hpp + ${PROJECT_SOURCE_DIR}/include/kfr/except.hpp ${PROJECT_SOURCE_DIR}/include/kfr/graphics.hpp ${PROJECT_SOURCE_DIR}/include/kfr/io.hpp ${PROJECT_SOURCE_DIR}/include/kfr/math.hpp diff --git a/tests/unit/base/basic_expressions.cpp b/tests/unit/base/basic_expressions.cpp @@ -69,6 +69,12 @@ TEST(padded) [](size_t i) { return i >= 501 ? -1 : i; }); } +TEST(concatenate) +{ + CHECK_EXPRESSION(concatenate(truncate(counter(5, 0), 5), truncate(counter(10, 0), 5)), + { 5, 5, 5, 5, 5, 10, 10, 10, 10, 10 }); +} + TEST(rebind) { auto c_minus_two = counter() - 2; @@ -119,7 +125,7 @@ TEST(assign_expression) univector<float> f = truncate(counter(0, 1), 10); f *= 10; CHECK_EXPRESSION(f, { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90 }); - + univector<float> a = truncate(counter(0, 1), 10); univector<float> b = truncate(counter(100, 1), 10); pack(a, b) *= broadcast<2>(10.f); diff --git a/tests/unit/base/shape.cpp b/tests/unit/base/shape.cpp @@ -52,6 +52,10 @@ TEST(shape_broadcast) CHECK(common_shape(shape{}, shape{ 0 }) == shape{ 0 }); CHECK(common_shape(shape{}, shape{ 0, 0 }) == shape{ 0, 0 }); CHECK(common_shape(shape{ 0 }, shape{ 0, 0 }) == shape{ 0, 0 }); + + CHECK(common_shape<true>(shape{}, shape{ 0 }) == shape{ 0 }); + CHECK(common_shape<true>(shape{}, shape{ 0, 0 }) == shape{ 0, 0 }); + CHECK(common_shape<true>(shape{ 0 }, shape{ 0, 0 }) == shape{ 0, 0 }); CHECK(can_assign_from(shape{ 1, 4 }, shape{ 1, 4 })); CHECK(!can_assign_from(shape{ 1, 4 }, shape{ 4, 1 }));