From 0179e78756a25fb1bce4c85a6216f8d0957af540 Mon Sep 17 00:00:00 2001 From: Vladislav Rykov Date: Mon, 22 Jun 2026 07:45:07 -0700 Subject: [PATCH 01/12] add missing `type_traits` include --- include/ros/type_traits.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/ros/type_traits.hpp b/include/ros/type_traits.hpp index 11393c2..47c8513 100644 --- a/include/ros/type_traits.hpp +++ b/include/ros/type_traits.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include From 16aa177771d607be3c3539815d131a3fc1f4d884 Mon Sep 17 00:00:00 2001 From: Vladislav Rykov Date: Mon, 22 Jun 2026 10:06:55 -0700 Subject: [PATCH 02/12] =?UTF-8?q?=F0=9F=8E=A8=20make=20operations=20monadi?= =?UTF-8?q?c?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/ros/operations.hpp | 112 ++++++++++++++++++++++++++++++++----- 1 file changed, 97 insertions(+), 15 deletions(-) diff --git a/include/ros/operations.hpp b/include/ros/operations.hpp index 5e6d975..61aefec 100644 --- a/include/ros/operations.hpp +++ b/include/ros/operations.hpp @@ -14,29 +14,57 @@ template struct field_assignment { template struct field_assignment_ct : field_assignment { - constexpr static typename Field::value_type value = val; + using value_type = typename Field::value_type; + using value_type_r = typename Field::value_type_r; + + constexpr auto operator() (const value_type_r register_value) const + -> value_type_r { + return Field::to_reg(register_value, value); + } + +// private: + constexpr static value_type value = val; }; template struct field_assignment_rt : field_assignment { using value_type = typename Field::value_type; + using value_type_r = typename Field::value_type_r; - constexpr explicit field_assignment_rt(value_type v) : value{v} {} + constexpr explicit field_assignment_rt(const value_type v) : value{v} {} + + constexpr auto operator() (const value_type_r register_value) const + -> value_type_r { + return Field::to_reg(register_value, value); + } - value_type value; +// private: + const value_type value; }; template struct field_assignment_invocable : field_assignment { + using value_type = typename FieldOp::value_type; + using value_type_r = typename FieldOp::value_type_r; using fields = std::tuple; - - explicit field_assignment_invocable(F f) : lambda_{f} {} - - constexpr auto operator()(typename Field0::value_type f0, - typename Fields::value_type... fs) -> - typename FieldOp::value_type { - return lambda_(f0, fs...); + + constexpr explicit field_assignment_invocable(F f) : lambda_{f} {} + + constexpr auto operator() (const value_type_r register_value) const + -> value_type_r { + return FieldOp::to_reg(register_value, + invoke(register_value, fields{}, + std::make_index_sequence<1 + sizeof...(Fields)>{})); } - + +private: + template + constexpr auto invoke(const value_type_r value, + const FieldsTuple tup, + const std::index_sequence) const + -> value_type { + return lambda_( + std::tuple_element_t::to_field(value)...); + } F lambda_; }; @@ -46,16 +74,14 @@ template struct unsafe_field_operations_handler { // NOLINTNEXTLINE(cppcoreguidelines-c-copy-assignment-signature) constexpr auto operator=(auto const &rhs) const -> field_assignment_rt { - static_assert(Field::access != access_type::RO, - "cannot write read-only field"); + static_assert(Field::writable(), "cannot write read-only field"); return field_assignment_rt{static_cast(rhs)}; } // NOLINTNEXTLINE(cppcoreguidelines-c-copy-assignment-signature) constexpr auto operator=(auto &&rhs) const -> field_assignment_rt { - static_assert(Field::access != access_type::RO, - "cannot write read-only field"); + static_assert(Field::writable(), "cannot write read-only field"); return field_assignment_rt{static_cast(rhs)}; } @@ -63,6 +89,30 @@ template struct unsafe_field_operations_handler { // TODO: add compile time unsafe operations }; +template +struct pipe { +public: + constexpr pipe(const L lhs, const R rhs) + : lhs_{lhs}, rhs_{rhs} + {} + + constexpr auto operator() (const auto ...vs) const { + return call_rhs(lhs_(vs...)); + } + +private: + constexpr auto call_rhs(auto && ...vs) const { + return rhs_(std::forward(vs)...); + } + + L lhs_; + R rhs_; +}; + +template +constexpr auto operator| (L l, R r) { + return pipe{l, r}; +} } // namespace detail // reg operations @@ -193,5 +243,37 @@ constexpr auto get_ro_mask(reg const &r) -> T { constexpr std::size_t tup_size = std::tuple_size_v; return get_ro_mask_helper(tup, std::make_index_sequence{}); } + +template +constexpr auto evaluate_chain_helper(const T value, const Tuple ops, + std::index_sequence) -> T { + return (std::get(ops) | ...)(value); +} + +template +constexpr auto evaluate_chain(const T value, const std::tuple ops) -> T { + return evaluate_chain_helper(value, ops, + std::make_index_sequence{}); +} + +template +constexpr auto evaluate_chain(const T value, const std::tuple<> tup) -> T { + return value; +} + +template +constexpr auto chain_helper(std::tuple const &ops, + std::index_sequence) { + return (std::get(ops) | ...); +} + +template +constexpr auto chain(std::tuple const &ops) { + return chain_helper(ops, std::make_index_sequence{}); +} + +constexpr auto chain(std::tuple<> const &tup) { + return [] (auto value) { return value; }; +} } // namespace detail } // namespace ros \ No newline at end of file From 37cf5815b628ccb47b75489011ca766df28d5737 Mon Sep 17 00:00:00 2001 From: Vladislav Rykov Date: Mon, 22 Jun 2026 10:07:37 -0700 Subject: [PATCH 03/12] =?UTF-8?q?=F0=9F=8E=A8=E2=9A=A1=EF=B8=8F=20user=20m?= =?UTF-8?q?onadic=20chain=20for=20field=20transformation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/ros/eval.hpp | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/include/ros/eval.hpp b/include/ros/eval.hpp index a01852a..c3af015 100644 --- a/include/ros/eval.hpp +++ b/include/ros/eval.hpp @@ -201,18 +201,13 @@ auto eval(Op op, Ops... ops) -> detail::return_reads_t< value = bus::template read(reg::address::value); } - // evaluate invocables at the beginning - // it doesn't make much sense to evaluate it at the end because it will - // have newly assigned values. this way just literals could be provided - // in the lambda - value = detail::get_invocable_write_value(value, write_mask_inv, - writes_inv); - - // [TODO] study efficiency of bundling together all writes - // compile time - value = detail::get_write_value(value, write_mask_ct, writes_ct); - // runtime - value = detail::get_write_value(value, write_mask_rt, writes_rt); + // compose monadic chain + const auto chain + = detail::chain(writes_inv) + | detail::chain(writes_ct) + | detail::chain(writes_rt); + + value = chain(value); // registers are not guaranteed to be idempotent, (the stored // value can hold value that may trigger clear/set/toggle. From 40506d117097998d607e09029a896e97963d46e2 Mon Sep 17 00:00:00 2001 From: Vladislav Rykov Date: Mon, 22 Jun 2026 10:15:23 -0700 Subject: [PATCH 04/12] =?UTF-8?q?=F0=9F=9A=A8=20fix=20clang-tidy=20warning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/ros/operations.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/ros/operations.hpp b/include/ros/operations.hpp index 61aefec..2d0d39b 100644 --- a/include/ros/operations.hpp +++ b/include/ros/operations.hpp @@ -38,7 +38,7 @@ template struct field_assignment_rt : field_assignment { } // private: - const value_type value; + value_type value; }; template @@ -58,7 +58,7 @@ struct field_assignment_invocable : field_assignment { private: template - constexpr auto invoke(const value_type_r value, + [[nodiscard]] constexpr auto invoke(const value_type_r value, const FieldsTuple tup, const std::index_sequence) const -> value_type { From a20de9fb01a3b132064e1c71eb975bf0459bbec5 Mon Sep 17 00:00:00 2001 From: Vladislav Rykov Date: Mon, 22 Jun 2026 10:16:44 -0700 Subject: [PATCH 05/12] use access specifiers in operations --- include/ros/operations.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/ros/operations.hpp b/include/ros/operations.hpp index 2d0d39b..625b842 100644 --- a/include/ros/operations.hpp +++ b/include/ros/operations.hpp @@ -22,7 +22,7 @@ struct field_assignment_ct : field_assignment { return Field::to_reg(register_value, value); } -// private: +private: constexpr static value_type value = val; }; @@ -37,7 +37,7 @@ template struct field_assignment_rt : field_assignment { return Field::to_reg(register_value, value); } -// private: +private: value_type value; }; @@ -64,7 +64,8 @@ struct field_assignment_invocable : field_assignment { -> value_type { return lambda_( std::tuple_element_t::to_field(value)...); - } + } + F lambda_; }; From c33a109f750caf7a2b9068bc89a613ba9db666b3 Mon Sep 17 00:00:00 2001 From: Vladislav Rykov Date: Mon, 22 Jun 2026 10:24:47 -0700 Subject: [PATCH 06/12] =?UTF-8?q?=F0=9F=94=A5=20remove=20unused=20function?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/ros/eval.hpp | 70 -------------------------------------- include/ros/operations.hpp | 17 --------- 2 files changed, 87 deletions(-) diff --git a/include/ros/eval.hpp b/include/ros/eval.hpp index c3af015..0004d14 100644 --- a/include/ros/eval.hpp +++ b/include/ros/eval.hpp @@ -23,24 +23,6 @@ template struct return_reads> { template using return_reads_t = typename return_reads::type; -template -constexpr auto get_write_value_helper(Tuple tup, std::index_sequence) -> T { - return (std::tuple_element_t::type::to_reg( - T{0}, std::get(tup).value) | - ...); -} - -template -constexpr auto get_write_value(T value, T mask, std::tuple const &tup) -> T { - return (value & ~mask) | - get_write_value_helper(tup, - std::make_index_sequence{}); -} - -template -constexpr auto get_write_value(T value, T mask, std::tuple<> const &tup) -> T { - return value; -} template constexpr auto get_write_mask_helper(Tuple const &tup, @@ -58,58 +40,6 @@ constexpr auto get_write_mask(std::tuple const &tup) -> T { std::make_index_sequence{}); } -template -constexpr auto get_invocable_write_fields_helper(T value, InvocableWrite iw, - TupleFields tup, - std::index_sequence) - -> T { - // get each field - return iw(std::tuple_element_t::to_field(value)...); -} - -template -constexpr auto get_invocable_write_value_helper(T value, TupleInvocableWrites tup, - std::index_sequence) - -> T { - return ( - std::tuple_element_t< - Idx, TupleInvocableWrites>::type::to_reg( // wrap back everything to - // reg value - T{0}, // pass in zero, final value will assigned with a compound - // mask - std::tuple_element_t< - Idx, TupleInvocableWrites>::type::runtime_check( // safety check - get_invocable_write_fields_helper( // make invocable call with - // each field value - value, // original reg value - std::get(tup), // invocable lambda wrapper - typename std::tuple_element_t< - Idx, TupleInvocableWrites>::fields{}, // tuple of fields - std::make_index_sequence< - std::tuple_size_v::fields>>{}))) | - ...); -} - -template -constexpr auto -get_invocable_write_value(T value, T mask, - std::tuple const &tup) - -> T { - return (value & ~mask) | - get_invocable_write_value_helper( - value, tup, - std::make_index_sequence{}); -} - -template -constexpr auto get_invocable_write_value(T value, T mask, - std::tuple<> const &tup) - -> T { - return value; -} - template constexpr void evaluate_invocable_assignment_helper(InvocableWrite iw, diff --git a/include/ros/operations.hpp b/include/ros/operations.hpp index 625b842..efcd1a6 100644 --- a/include/ros/operations.hpp +++ b/include/ros/operations.hpp @@ -245,23 +245,6 @@ constexpr auto get_ro_mask(reg const &r) -> T { return get_ro_mask_helper(tup, std::make_index_sequence{}); } -template -constexpr auto evaluate_chain_helper(const T value, const Tuple ops, - std::index_sequence) -> T { - return (std::get(ops) | ...)(value); -} - -template -constexpr auto evaluate_chain(const T value, const std::tuple ops) -> T { - return evaluate_chain_helper(value, ops, - std::make_index_sequence{}); -} - -template -constexpr auto evaluate_chain(const T value, const std::tuple<> tup) -> T { - return value; -} - template constexpr auto chain_helper(std::tuple const &ops, std::index_sequence) { From b55494ecb2a9915e182f9890baf719fc55c79157 Mon Sep 17 00:00:00 2001 From: Vladislav Rykov Date: Mon, 22 Jun 2026 11:04:09 -0700 Subject: [PATCH 07/12] =?UTF-8?q?=F0=9F=8E=A8=E2=9A=A1=EF=B8=8F=20use=20mo?= =?UTF-8?q?nadic=20register=20operations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/ros/eval.hpp | 45 +++++-------------------------- include/ros/operations.hpp | 55 +++++++++++++++++++++++++++++++++----- 2 files changed, 55 insertions(+), 45 deletions(-) diff --git a/include/ros/eval.hpp b/include/ros/eval.hpp index 0004d14..c7c2321 100644 --- a/include/ros/eval.hpp +++ b/include/ros/eval.hpp @@ -78,6 +78,10 @@ evaluate_invocable_assignments(std::tuple writes) { writes, std::make_index_sequence{}); } +constexpr void evaluate_invocable_assignments(std::tuple<> writes) { + // no-op +} + } // namespace detail template @@ -220,44 +224,9 @@ auto eval(Op op, Ops... ops) -> detail::return_reads_t< Rs::type::address::value)...); }(reads); - if constexpr (has_writes_inv) { - detail::evaluate_invocable_assignments(writes_inv); - } - - // third, cluster adjacent writes into separate tuples - // call write bundled for each tuple - - // writes are peformed separately. the intent is to get jem - // out of compile-time accessible writes, and then let - // the compiler to optimize the runtime writes - - if constexpr (has_writes_ct) { - if constexpr (std::tuple_size_v == 1) { - // if there's only one write, just call write without bundling - using write = std::tuple_element_t<0, decltype(writes_ct)>; - write::type::bus::write(write::value, write::type::address::value); - } else { - [](std::tuple) -> void { - (Ws::type::bus::write(Ws::value, Ws::type::address::value), - ...); - }(writes_ct); - } - } - - if constexpr (has_writes_rt) { - if constexpr (std::tuple_size_v == 1) { - // if there's only one write, just call write without bundling - using write = std::tuple_element_t<0, decltype(writes_rt)>; - write::type::bus::write(std::get(writes_rt).value, - write::type::address::value); - } else { - [](std::tuple ws) -> void { - (Ws::type::bus::write(std::get(ws).value, - Ws::type::address::value), - ...); - }(writes_rt); - } - } + detail::evaluate(writes_inv); + detail::evaluate(writes_ct); + detail::evaluate(writes_rt); // if there's only one read, just return the reg value without tuple if constexpr (std::tuple_size_v == 1) { diff --git a/include/ros/operations.hpp b/include/ros/operations.hpp index efcd1a6..5c9c9cd 100644 --- a/include/ros/operations.hpp +++ b/include/ros/operations.hpp @@ -124,30 +124,56 @@ template struct register_assignment { template struct register_assignment_ct : register_assignment { - constexpr static typename Register::value_type value = val; + using value_type = typename Register::value_type; + using bus = typename Register::bus; + + constexpr auto operator() () const -> void { + return bus::template write(val, + Register::address::value); + }; }; template struct register_assignment_rt : register_assignment { using value_type = typename Register::value_type; + using bus = typename Register::bus; - constexpr explicit register_assignment_rt(value_type v) : value{v} {} + constexpr explicit register_assignment_rt(const value_type v) : value_{v} {} - value_type value; + constexpr auto operator() () const -> void { + return bus::template write(value_, + Register::address::value); + }; + +private: + value_type value_; }; template struct register_assignment_invocable : register_assignment { + using value_type = typename RegisterOp::value_type; + using bus = typename RegisterOp::bus; using registerOp = RegisterOp; using registers = std::tuple; explicit register_assignment_invocable(F f) : lambda_{f} {} - constexpr auto operator()(typename Register0::value_type r0, - typename Registers::value_type... rs) -> - typename RegisterOp::value_type { - return lambda_(r0, rs...); + constexpr auto operator() () const -> void { + bus::template write( + invoke(registers{}, + std::make_index_sequence{}), + RegisterOp::address::value); + } + +private: + template + [[nodiscard]] constexpr auto invoke(const RegistersTuple tup, + std::index_sequence) const + -> value_type { + return lambda_( + bus::template read::value_type>( + std::tuple_element_t::address::value)...); } F lambda_; @@ -259,5 +285,20 @@ constexpr auto chain(std::tuple const &ops) { constexpr auto chain(std::tuple<> const &tup) { return [] (auto value) { return value; }; } + +template +constexpr auto evaluate_helper(std::tuple const &ops, + std::index_sequence) { + return (std::get(ops)(), ...); +} + +template +constexpr auto evaluate(std::tuple const &ops) { + return evaluate_helper(ops, std::make_index_sequence{}); +} + +constexpr auto evaluate(std::tuple<> const &tup) { + // no-op +} } // namespace detail } // namespace ros \ No newline at end of file From 7e454d79e00ff3d355d176c01bb76cd80eac16ad Mon Sep 17 00:00:00 2001 From: Vladislav Rykov Date: Mon, 22 Jun 2026 11:09:21 -0700 Subject: [PATCH 08/12] =?UTF-8?q?=F0=9F=9A=9A=20move=20some=20functions=20?= =?UTF-8?q?to=20operations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/ros/eval.hpp | 80 +------------------------------------- include/ros/operations.hpp | 31 +++++++++++++++ 2 files changed, 33 insertions(+), 78 deletions(-) diff --git a/include/ros/eval.hpp b/include/ros/eval.hpp index c7c2321..8f03441 100644 --- a/include/ros/eval.hpp +++ b/include/ros/eval.hpp @@ -6,83 +6,6 @@ #include namespace ros { -namespace detail { - -template struct return_reads { - using type = void; -}; - -template struct return_reads> { - using type = typename Op::type::value_type; -}; - -template struct return_reads> { - using type = std::tuple; -}; - -template -using return_reads_t = typename return_reads::type; - - -template -constexpr auto get_write_mask_helper(Tuple const &tup, - std::index_sequence) { - return (std::tuple_element_t::type::mask | ...); -}; - -template constexpr auto get_write_mask(std::tuple<> const &tup) -> T { - return 0; -} - -template -constexpr auto get_write_mask(std::tuple const &tup) -> T { - return get_write_mask_helper(tup, - std::make_index_sequence{}); -} - -template -constexpr void -evaluate_invocable_assignment_helper(InvocableWrite iw, - std::index_sequence) { - using registerOp = typename InvocableWrite::registerOp; - using busOp = registerOp::bus; - using registers = typename InvocableWrite::registers; - - busOp::write( - iw( - // call bus::read that corresponds to each register - std::tuple_element_t::bus::template read< - typename std::tuple_element_t::value_type>( - std::tuple_element_t::address::value)...), - registerOp::address::value); -} - -template -constexpr void evaluate_invocable_assignment(InvocableWrite iw) { - using registers = typename InvocableWrite::registers; - evaluate_invocable_assignment_helper( - iw, std::make_index_sequence>{}); -} - -template -constexpr void -evaluate_invocable_assignments_helper(std::tuple writes, - std::index_sequence) { - (evaluate_invocable_assignment(std::get(writes)), ...); -} - -template -constexpr void -evaluate_invocable_assignments(std::tuple writes) { - evaluate_invocable_assignments_helper( - writes, std::make_index_sequence{}); -} - -constexpr void evaluate_invocable_assignments(std::tuple<> writes) { - // no-op -} - -} // namespace detail template requires detail::field_constraints @@ -139,7 +62,8 @@ auto eval(Op op, Ops... ops) -> detail::return_reads_t< const auto chain = detail::chain(writes_inv) | detail::chain(writes_ct) - | detail::chain(writes_rt); + | detail::chain(writes_rt) + ; value = chain(value); diff --git a/include/ros/operations.hpp b/include/ros/operations.hpp index 5c9c9cd..f06c1bc 100644 --- a/include/ros/operations.hpp +++ b/include/ros/operations.hpp @@ -201,6 +201,21 @@ template struct unsafe_register_operations_handler { // TODO: Add compile time unsafe operations support }; +template struct return_reads { + using type = void; +}; + +template struct return_reads> { + using type = typename Op::type::value_type; +}; + +template struct return_reads> { + using type = std::tuple; +}; + +template +using return_reads_t = typename return_reads::type; + template constexpr auto get_rmw_mask_helper(std::tuple const &t, std::index_sequence) -> @@ -271,6 +286,22 @@ constexpr auto get_ro_mask(reg const &r) -> T { return get_ro_mask_helper(tup, std::make_index_sequence{}); } +template +constexpr auto get_write_mask_helper(Tuple const &tup, + std::index_sequence) { + return (std::tuple_element_t::type::mask | ...); +}; + +template constexpr auto get_write_mask(std::tuple<> const &tup) -> T { + return 0; +} + +template +constexpr auto get_write_mask(std::tuple const &tup) -> T { + return get_write_mask_helper(tup, + std::make_index_sequence{}); +} + template constexpr auto chain_helper(std::tuple const &ops, std::index_sequence) { From 8d4cd5ef9ded9a0d38bc0c9c8902f5a01da9593f Mon Sep 17 00:00:00 2001 From: Vladislav Rykov Date: Mon, 22 Jun 2026 11:40:32 -0700 Subject: [PATCH 09/12] add missing `utility` include --- include/ros/operations.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/ros/operations.hpp b/include/ros/operations.hpp index f06c1bc..7f240f2 100644 --- a/include/ros/operations.hpp +++ b/include/ros/operations.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include From 684c756ca5dea834b30cf9c612db330640f4edea Mon Sep 17 00:00:00 2001 From: Vladislav Rykov Date: Mon, 22 Jun 2026 11:43:24 -0700 Subject: [PATCH 10/12] add `runtime_check` for consistency --- include/ros/operations.hpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/ros/operations.hpp b/include/ros/operations.hpp index 7f240f2..c87716b 100644 --- a/include/ros/operations.hpp +++ b/include/ros/operations.hpp @@ -52,9 +52,11 @@ struct field_assignment_invocable : field_assignment { constexpr auto operator() (const value_type_r register_value) const -> value_type_r { - return FieldOp::to_reg(register_value, - invoke(register_value, fields{}, - std::make_index_sequence<1 + sizeof...(Fields)>{})); + return FieldOp::to_reg( + register_value, + FieldOp::runtime_check( + invoke(register_value, fields{}, + std::make_index_sequence<1 + sizeof...(Fields)>{}))); } private: From 7d0a8cfd27d8288644ca70099623a67f04a673bf Mon Sep 17 00:00:00 2001 From: Vladislav Rykov Date: Mon, 22 Jun 2026 11:49:21 -0700 Subject: [PATCH 11/12] =?UTF-8?q?=E2=9C=85=20more=20invocable=20chaining?= =?UTF-8?q?=20test=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/unit/test_apply_field.cpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/unit/test_apply_field.cpp b/test/unit/test_apply_field.cpp index f17a39a..19d7a7b 100644 --- a/test/unit/test_apply_field.cpp +++ b/test/unit/test_apply_field.cpp @@ -173,6 +173,36 @@ TEST_F(ApplyFieldTest, InvocableWrite_PlusCTWrite_CoversAllRMW) { EXPECT_EQ(bus_log[1].value, 0xB4u); } +TEST_F(ApplyFieldTest, InvocableWrite_MultipleInvocables_LaterSeesEarlierWrite) { + constexpr simple_reg r{}; + bus_read_value = 0x21; // low_nibble = 1, high_nibble = 2 + + // Invocables are applied as a sequential chain in argument order, NOT against + // a single pre-read snapshot. The 2nd invocable (writing low_nibble) reads + // high_nibble, which the 1st invocable has already incremented. + eval(r.high_nibble([](uint8_t h) -> uint8_t { return h + 1; }), // high: 2 -> 3 + r.low_nibble([](uint8_t h) -> uint8_t { return h; }, // low = current high + r.high_nibble)); + + ASSERT_EQ(bus_log.size(), 2u); + // low reads high AFTER it became 3 -> 0x33 (a snapshot semantics would give 0x32) + EXPECT_EQ(bus_log[1].value, 0x33u); +} + +TEST_F(ApplyFieldTest, InvocableWrite_MultipleInvocables_OrderDependent) { + constexpr simple_reg r{}; + bus_read_value = 0x21; // low_nibble = 1, high_nibble = 2 + + // Same two invocables, reversed order: low_nibble's invocable now runs first, + // so it reads high_nibble's original value (2) before it is incremented. + eval(r.low_nibble([](uint8_t h) -> uint8_t { return h; }, r.high_nibble), + r.high_nibble([](uint8_t h) -> uint8_t { return h + 1; })); + + ASSERT_EQ(bus_log.size(), 2u); + // low reads high BEFORE the increment -> low=2; high then becomes 3 -> 0x32 + EXPECT_EQ(bus_log[1].value, 0x32u); +} + TEST_F(ApplyFieldTest, InvocableWrite_FullWidthField) { constexpr full_reg r{}; bus_read_value = 0xDEADBEEF; From 4f2002cdbfdb9926ab93d503e6f0e47233020f63 Mon Sep 17 00:00:00 2001 From: Vladislav Rykov Date: Mon, 22 Jun 2026 11:52:21 -0700 Subject: [PATCH 12/12] bump up the version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a05d13..e149cae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.25) project(ros - VERSION 0.2.0 + VERSION 0.2.1 LANGUAGES CXX DESCRIPTION "A header-only C++ library that provides safe