Skip to content
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
Expand Down
202 changes: 10 additions & 192 deletions include/ros/eval.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,149 +6,6 @@
#include <ros/type_traits.hpp>

namespace ros {
namespace detail {

template <typename T> struct return_reads {
using type = void;
};

template <typename Op> struct return_reads<std::tuple<Op>> {
using type = typename Op::type::value_type;
};

template <typename... Ops> struct return_reads<std::tuple<Ops...>> {
using type = std::tuple<typename Ops::type::value_type...>;
};

template <typename... Ops>
using return_reads_t = typename return_reads<Ops...>::type;

template <typename T, typename Tuple, std::size_t... Idx>
constexpr auto get_write_value_helper(Tuple tup, std::index_sequence<Idx...>) -> T {
return (std::tuple_element_t<Idx, Tuple>::type::to_reg(
T{0}, std::get<Idx>(tup).value) |
...);
}

template <typename T, typename... Ts>
constexpr auto get_write_value(T value, T mask, std::tuple<Ts...> const &tup) -> T {
return (value & ~mask) |
get_write_value_helper<T>(tup,
std::make_index_sequence<sizeof...(Ts)>{});
}

template <typename T>
constexpr auto get_write_value(T value, T mask, std::tuple<> const &tup) -> T {
return value;
}

template <typename Tuple, std::size_t... Idx>
constexpr auto get_write_mask_helper(Tuple const &tup,
std::index_sequence<Idx...>) {
return (std::tuple_element_t<Idx, Tuple>::type::mask | ...);
};

template <typename T> constexpr auto get_write_mask(std::tuple<> const &tup) -> T {
return 0;
}

template <typename T, typename... Ts>
constexpr auto get_write_mask(std::tuple<Ts...> const &tup) -> T {
return get_write_mask_helper(tup,
std::make_index_sequence<sizeof...(Ts)>{});
}

template <typename T, typename InvocableWrite, typename TupleFields,
std::size_t... Idx>
constexpr auto get_invocable_write_fields_helper(T value, InvocableWrite iw,
TupleFields tup,
std::index_sequence<Idx...>)
-> T {
// get each field
return iw(std::tuple_element_t<Idx, TupleFields>::to_field(value)...);
}

template <typename T, typename TupleInvocableWrites, std::size_t... Idx>
constexpr auto get_invocable_write_value_helper(T value, TupleInvocableWrites tup,
std::index_sequence<Idx...>)
-> 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<Idx>(tup), // invocable lambda wrapper
typename std::tuple_element_t<
Idx, TupleInvocableWrites>::fields{}, // tuple of fields
std::make_index_sequence<
std::tuple_size_v<typename std::tuple_element_t<
Idx, TupleInvocableWrites>::fields>>{}))) |
...);
}

template <typename T, typename... InvocableWrites>
constexpr auto
get_invocable_write_value(T value, T mask,
std::tuple<InvocableWrites...> const &tup)
-> T {
return (value & ~mask) |
get_invocable_write_value_helper(
value, tup,
std::make_index_sequence<sizeof...(InvocableWrites)>{});
}

template <typename T>
constexpr auto get_invocable_write_value(T value, T mask,
std::tuple<> const &tup)
-> T {
return value;
}

template <typename InvocableWrite, std::size_t... Is>
constexpr void
evaluate_invocable_assignment_helper(InvocableWrite iw,
std::index_sequence<Is...>) {
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<Is, registers>::bus::template read<
typename std::tuple_element_t<Is, registers>::value_type>(
std::tuple_element_t<Is, registers>::address::value)...),
registerOp::address::value);
}

template <typename InvocableWrite>
constexpr void evaluate_invocable_assignment(InvocableWrite iw) {
using registers = typename InvocableWrite::registers;
evaluate_invocable_assignment_helper(
iw, std::make_index_sequence<std::tuple_size_v<registers>>{});
}

template <typename... InvocableWrites, std::size_t... Is>
constexpr void
evaluate_invocable_assignments_helper(std::tuple<InvocableWrites...> writes,
std::index_sequence<Is...>) {
(evaluate_invocable_assignment(std::get<Is>(writes)), ...);
}

template <typename... InvocableWrites>
constexpr void
evaluate_invocable_assignments(std::tuple<InvocableWrites...> writes) {
evaluate_invocable_assignments_helper(
writes, std::make_index_sequence<sizeof...(InvocableWrites)>{});
}

} // namespace detail

template <typename Op, typename... Ops>
requires detail::field_constraints<Op, Ops...>
Expand Down Expand Up @@ -201,18 +58,14 @@ auto eval(Op op, Ops... ops) -> detail::return_reads_t<
value = bus::template read<value_type>(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);
// compose monadic chain
const auto chain
= detail::chain(writes_inv)
| detail::chain(writes_ct)
| detail::chain(writes_rt)
;

// [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);
value = chain(value);

// registers are not guaranteed to be idempotent, (the stored
// value can hold value that may trigger clear/set/toggle.
Expand Down Expand Up @@ -295,44 +148,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<decltype(writes_ct)> == 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 {
[]<typename... Ws>(std::tuple<Ws...>) -> void {
(Ws::type::bus::write(Ws::value, Ws::type::address::value),
...);
}(writes_ct);
}
}

if constexpr (has_writes_rt) {
if constexpr (std::tuple_size_v<decltype(writes_rt)> == 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<write>(writes_rt).value,
write::type::address::value);
} else {
[]<typename... Ws>(std::tuple<Ws...> ws) -> void {
(Ws::type::bus::write(std::get<Ws>(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<decltype(reads)> == 1) {
Expand Down
Loading
Loading