Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mkretz/several fixes #128

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,33 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
set(CMAKE_VISIBILITY_INLINES_HIDDEN 1)

if (CMAKE_CXX_COMPILER_ID MATCHES "(Clang|GNU|Intel)")
# -Og is a much more reasonable default for debugging. Also enable gdb extensions.
set(CMAKE_CXX_FLAGS_DEBUG "-Og -ggdb" CACHE INTERNAL
"Flags used by the compiler during debug builds.")

# Add a build type that keeps runtime checks enabled
set(CMAKE_CXX_FLAGS_RELWITHASSERT "-O3" CACHE INTERNAL
"Flags used by the compiler during release builds containing runtime checks.")

# The default value is often an empty string, but this is usually not desirable and one of the
# other standard build types is usually more appropriate.
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "RelWithAssert" CACHE STRING
"Choose the type of build. Options are: None Debug Release RelWithAssert RelWithDebInfo MinSizeRel.\n\
- None: no compiler flags, defaults and target-specific flags apply\n\
- Debug: best/complete debugging experience; as optimized as reasonable\n\
- Release: full optimization; some runtime checks disabled\n\
- RelWithAssert: full optimization; runtime checks enabled\n\
- RelWithDebInfo: optimized; debug info; some runtime checks disabled"
FORCE)
endif(NOT CMAKE_BUILD_TYPE)

if (CMAKE_BUILD_TYPE STREQUAL "" AND NOT CMAKE_CXX_FLAGS MATCHES "-O[123gs]")
message(WARNING "It seems you are compiling without optimization. Please set CMAKE_BUILD_TYPE or CMAKE_CXX_FLAGS.")
endif ()
endif ()

# Mainly for FMT
set(CMAKE_POSITION_INDEPENDENT_CODE TRUE)

Expand Down
21 changes: 0 additions & 21 deletions bench/bm_case1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,22 +81,6 @@ class math_bulk_op : public fg::node<math_bulk_op<T, op>, fg::IN<T, 0, N_MAX, "i
static_assert(fair::meta::always_false<T>, "unknown op");
}
}

[[nodiscard]] constexpr fg::work_return_status_t
process_bulk(std::span<const T> input, std::span<T> output) const noexcept {
// classic for-loop
for (std::size_t i = 0; i < input.size(); i++) {
output[i] = process_one(input[i]);
}

// C++17 algorithms
// std::transform(input.begin(), input.end(), output.begin(), [this](const T& elem) { return process_one(elem);});

// C++20 ranges
// std::ranges::transform(input, output.begin(), [this](const T& elem) { return process_one(elem); });

return fg::work_return_status_t::OK;
}
};

template<typename T>
Expand All @@ -108,11 +92,6 @@ using add_bulk = math_bulk_op<T, '+'>;
template<typename T>
using sub_bulk = math_bulk_op<T, '-'>;

// Clang 15 and 16 crash on the following static_assert
#ifndef __clang__
static_assert(fg::traits::node::process_bulk_requires_ith_output_as_span<multiply_bulk<float>, 0>);
#endif

//
// This defines a new node type that has only type template parameters.
//
Expand Down
14 changes: 14 additions & 0 deletions include/node.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,16 @@ class node : protected std::tuple<Arguments...> {
*/
work_return_t
work_internal(std::size_t requested_work) noexcept {
if constexpr (not HasRequiredProcessFunction<Derived>) {
if constexpr (HasProcessBulkFunction<Derived> and HasProcessOneFunction<Derived>) {
static_assert(HasRequiredProcessFunction<Derived>, "Ambiguous node interface. The node type implements both `process_one` and `process_bulk`. Remove one of them.");
} else if constexpr (traits::node::can_process_bulk_by_value<Derived>) {
static_assert(not traits::node::can_process_bulk_by_value<Derived>, "Deduced function parameters of `process_bulk` must be passed *by reference not by value*.");
} else {
static_assert(HasRequiredProcessFunction<Derived>,
"Missing or incorrect node interface. The node type must implement either `process_one` or `process_bulk` with arguments matching the port types.");
}
}
using fair::graph::work_return_status_t;
using input_types = traits::node::input_port_types<Derived>;
using output_types = traits::node::output_port_types<Derived>;
Expand All @@ -527,6 +537,10 @@ class node : protected std::tuple<Arguments...> {
std::size_t samples_to_process = 0;
std::size_t n_samples_until_next_tag = std::numeric_limits<std::size_t>::max(); // default: no tags in sight
if constexpr (is_source_node) {
if constexpr (requires { &Derived::available_samples; }) {
static_assert(
requires(const Derived &d) { d.available_samples(d); }, "Incorrect signature for available_samples. Should be `(signed) size_t available_samples(const NodeType&) const`");
}
if constexpr (requires(const Derived &d) {
{ self().available_samples(d) } -> std::same_as<std::make_signed_t<std::size_t>>;
}) {
Expand Down
18 changes: 18 additions & 0 deletions include/node_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,13 +228,23 @@ struct dummy_input_span : public std::span<const T> { // NOSONAR
constexpr void consume(std::size_t) noexcept;
};

template<typename T>
struct dummy_copyable_input_span : public std::span<const T> {
constexpr void consume(std::size_t) noexcept;
};

template<typename T>
struct dummy_output_span : public std::span<T> { // NOSONAR
dummy_output_span(const dummy_output_span &) = delete; // NOSONAR
dummy_output_span(dummy_output_span &&) noexcept; // NOSONAR
constexpr void publish(std::size_t) noexcept;
};

template<typename T>
struct dummy_copyable_output_span : public std::span<T> {
constexpr void publish(std::size_t) noexcept;
};

template<typename>
struct nothing_you_ever_wanted {};

Expand All @@ -256,6 +266,14 @@ concept can_process_bulk = requires(Node &n, typename meta::transform_types<deta
} -> std::same_as<work_return_status_t>;
};

template<typename Node>
concept can_process_bulk_by_value = requires(Node &n, typename meta::transform_types<detail::dummy_copyable_input_span, traits::node::input_port_types<Node>>::tuple_type inputs,
typename meta::transform_types<detail::dummy_copyable_output_span, traits::node::output_port_types<Node>>::tuple_type outputs) {
{
detail::can_process_bulk_invoke_test(n, inputs, outputs, std::make_index_sequence<input_port_types<Node>::size>(), std::make_index_sequence<output_port_types<Node>::size>())
} -> std::same_as<work_return_status_t>;
};

/*
* Satisfied if `Derived` has a member function `process_bulk` which can be invoked with a number of arguments matching the number of input and output ports. Input arguments must accept either a
* std::span<const T> or any type satisfying ConsumableSpan<T>. Output arguments must accept either a std::span<T> or any type satisfying PublishableSpan<T>, except for the I-th output argument, which
Expand Down
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ endfunction()

add_ut_test(qa_buffer)
add_ut_test(qa_data_sink)
add_ut_test(qa_decimator)
add_ut_test(qa_dynamic_node)
add_ut_test(qa_dynamic_port)
add_ut_test(qa_fft)
Expand Down
7 changes: 6 additions & 1 deletion test/blocklib/core/unit-test/tag_monitors.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ struct TagSource : public node<TagSource<T, UseProcessOne>> {
std::int64_t n_samples_produced{ 0 };

constexpr std::make_signed_t<std::size_t>
available_samples(const TagSource &) noexcept {
available_samples(const TagSource &) const noexcept {
if constexpr (UseProcessOne == ProcessFunction::USE_PROCESS_ONE) {
// '-1' -> DONE, produced enough samples
return n_samples_max == n_samples_produced ? -1 : n_samples_max - n_samples_produced;
Expand Down Expand Up @@ -186,6 +186,11 @@ static_assert(not HasProcessOneFunction<TagSource<int, ProcessFunction::USE_PROC
static_assert(HasProcessBulkFunction<TagSource<int, ProcessFunction::USE_PROCESS_BULK>>);
static_assert(HasRequiredProcessFunction<TagSource<int, ProcessFunction::USE_PROCESS_BULK>>);

// Clang 15 and 16 crash on the following static_assert
#ifndef __clang__
static_assert(traits::node::process_bulk_requires_ith_output_as_span<TagSource<int, ProcessFunction::USE_PROCESS_BULK>, 0>);
#endif

static_assert(HasProcessOneFunction<TagMonitor<int, ProcessFunction::USE_PROCESS_ONE>>);
static_assert(not HasProcessOneFunction<TagMonitor<int, ProcessFunction::USE_PROCESS_BULK>>);
static_assert(not HasProcessBulkFunction<TagMonitor<int, ProcessFunction::USE_PROCESS_ONE>>);
Expand Down
2 changes: 1 addition & 1 deletion test/qa_data_sink.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ struct Source : public node<Source<T>> {
}

constexpr std::make_signed_t<std::size_t>
available_samples(const Source &) noexcept {
available_samples(const Source &) const noexcept {
// TODO unify with other test sources
// split into chunks so that we have a single tag at index 0 (or none)
auto ret = static_cast<std::make_signed_t<std::size_t>>(n_samples_max - n_samples_produced);
Expand Down
96 changes: 96 additions & 0 deletions test/qa_decimator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#include <boost/ut.hpp>

#include <buffer.hpp>
#include <graph.hpp>
#include <node.hpp>
#include <reflection.hpp>
#include <scheduler.hpp>

#include <fmt/format.h>

namespace fg = fair::graph;

using std::size_t;
using ssize_t = std::make_signed_t<size_t>;

template<typename T>
struct source : public fg::node<source<T>, fg::OUT<T, 0, 1024, "out">> {
ssize_t produced = 0;
T value = 1;

constexpr ssize_t
available_samples(const source&) const noexcept {
return 64 - produced;
}

constexpr fg::work_return_status_t
process_bulk(std::span<T> out) noexcept {
if (available_samples(*this) <= 0) {
return fg::work_return_status_t::ERROR;
}
for (T &x : out) {
x = value++;
}
produced += ssize_t(out.size());
const auto ret = (available_samples(*this) <= 0) ? fg::work_return_status_t::DONE : fg::work_return_status_t::OK;
fmt::println("source::process_bulk; produced = {}, returning {}", produced, int(ret));
return ret;
}
};

template<typename T>
struct drop_odd_samples : public fg::node<drop_odd_samples<T>, fg::IN<T, 0, 15, "in">, fg::OUT<T, 0, 15, "out">> {
bool even = true;

constexpr fg::work_return_status_t
process_bulk(std::span<const T> in, fg::PublishableSpan auto& out) noexcept {
boost::ut::expect(in.size() <= 15);
boost::ut::expect(out.size() <= 15);
const size_t to_publish = (in.size() + even) / 2;
boost::ut::expect(to_publish <= out.size());
for (size_t i = 0; i < in.size(); ++i) {
if (even) {
out[i / 2] = in[i];
}
even = !even;
}
out.publish(to_publish);
fmt::println("drop_odd_samples::process_bulk received {} samples and published {} samples", in.size(), to_publish);
return fg::work_return_status_t::OK;
}
};

template<typename T, T increment>
struct sink : public fg::node<sink<T, increment>, fg::IN<T, 0, 1024, "in">> {
T expect_next = 1;

constexpr void
process_one(T x) noexcept {
using namespace boost::ut;
expect(eq(x, expect_next));
expect_next += increment;
}
};

const boost::ut::suite simple_decimator = [] {
using namespace boost::ut;

"drop odd"_test = [] {
fg::graph flow_graph;

auto &n0 = flow_graph.make_node<source<int>>();
auto &n1 = flow_graph.make_node<drop_odd_samples<int>>();
auto &n2 = flow_graph.make_node<sink<int, 2>>();

expect(eq(flow_graph.connect<"out">(n0).to<"in">(n1), fg::connection_result_t::SUCCESS));
expect(eq(flow_graph.connect<"out">(n1).to<"in">(n2), fg::connection_result_t::SUCCESS));

fg::scheduler::simple sched{ std::move(flow_graph) };
fmt::println("drop odd starts");
sched.start();
fmt::println("drop odd done");
};
};

int
main() {}
7 changes: 1 addition & 6 deletions test/qa_scheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class count_source : public fg::node<count_source<T, N>, fg::OUT<T, 0, std::nume
count_source(tracer &trace, std::string_view name_) : _tracer{ trace } { this->name = name_; }

constexpr std::make_signed_t<std::size_t>
available_samples(const count_source & /*d*/) noexcept {
available_samples(const count_source & /*d*/) const noexcept {
const auto ret = static_cast<std::make_signed_t<std::size_t>>(N - _count);
return ret > 0 ? ret : -1; // '-1' -> DONE, produced enough samples
}
Expand Down Expand Up @@ -76,11 +76,6 @@ class expect_sink : public fg::node<expect_sink<T, N>, fg::IN<T, 0, std::numeric
}
return fg::work_return_status_t::OK;
}

constexpr void
process_one(T /*a*/) noexcept {
_tracer.trace(this->name());
}
};

template<typename T, T Scale, typename R = decltype(std::declval<T>() * std::declval<T>())>
Expand Down
2 changes: 1 addition & 1 deletion test/qa_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ struct Source : public node<Source<T>> {
}

constexpr std::make_signed_t<std::size_t>
available_samples(const Source & /*self*/) noexcept {
available_samples(const Source & /*self*/) const noexcept {
const auto ret = static_cast<std::make_signed_t<std::size_t>>(n_samples_max - n_samples_produced);
return ret > 0 ? ret : -1; // '-1' -> DONE, produced enough samples
}
Expand Down
Loading