Skip to content

Commit 73ecae1

Browse files
committed
✨ Add ability to log runtime floating-point values
Problem: - The binary logging does not support floating-point values (`float` and `double`). Solution: - Separate argument encoding/packing into a type so that it can be customized, and passed through builders. - Allow `float` and `double` to encoded/packed. - Support `%f` specifiers in JSON collateral. Note: - The MIPI-SysT spec v1.1 (section 9.2 table 16) specifies that a `float` is packed into 64 bits even when using 32-bit packing. This implementation packs a `float` into 32 bits by default.
1 parent 179769c commit 73ecae1

File tree

9 files changed

+202
-84
lines changed

9 files changed

+202
-84
lines changed

include/log/catalog/arguments.hpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#pragma once
2+
3+
#include <stdx/type_traits.hpp>
4+
5+
#include <concepts>
6+
#include <cstdint>
7+
#include <type_traits>
8+
9+
template <typename> struct encode_32;
10+
template <typename> struct encode_64;
11+
template <typename> struct encode_u32;
12+
template <typename> struct encode_u64;
13+
14+
namespace logging {
15+
template <typename T>
16+
concept signed_packable = std::signed_integral<stdx::underlying_type_t<T>> and
17+
sizeof(T) <= sizeof(std::int64_t);
18+
19+
template <typename T>
20+
concept unsigned_packable =
21+
std::unsigned_integral<stdx::underlying_type_t<T>> and
22+
sizeof(T) <= sizeof(std::int64_t);
23+
24+
template <typename T>
25+
concept float_packable = std::floating_point<stdx::underlying_type_t<T>> and
26+
sizeof(T) <= sizeof(std::int64_t);
27+
28+
template <typename T>
29+
concept packable =
30+
signed_packable<T> or unsigned_packable<T> or float_packable<T>;
31+
32+
template <typename T> struct encoding;
33+
34+
template <signed_packable T> struct encoding<T> {
35+
using encode_t = stdx::conditional_t<sizeof(T) <= sizeof(std::int32_t),
36+
encode_32<T>, encode_64<T>>;
37+
using pack_t = stdx::conditional_t<sizeof(T) <= sizeof(std::int32_t),
38+
std::int32_t, std::int64_t>;
39+
};
40+
41+
template <unsigned_packable T> struct encoding<T> {
42+
using encode_t = stdx::conditional_t<sizeof(T) <= sizeof(std::uint32_t),
43+
encode_u32<T>, encode_u64<T>>;
44+
using pack_t = stdx::conditional_t<sizeof(T) <= sizeof(std::uint32_t),
45+
std::uint32_t, std::uint64_t>;
46+
};
47+
48+
template <float_packable T> struct encoding<T> {
49+
using encode_t = stdx::conditional_t<sizeof(T) <= sizeof(std::uint32_t),
50+
encode_u32<T>, encode_u64<T>>;
51+
using pack_t = stdx::conditional_t<sizeof(T) <= sizeof(std::uint32_t),
52+
std::uint32_t, std::uint64_t>;
53+
};
54+
55+
struct default_arg_packer {
56+
template <packable T> using pack_as_t = typename encoding<T>::pack_t;
57+
template <packable T> using encode_as_t = typename encoding<T>::encode_t;
58+
};
59+
} // namespace logging

include/log/catalog/builder.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace logging::binary {
1717
}
1818

1919
CONSTEVAL auto operator()(auto &&) const {
20-
return logging::mipi::default_builder{};
20+
return logging::mipi::default_builder<>{};
2121
}
2222
} get_builder;
2323
} // namespace logging::binary

include/log/catalog/catalog.hpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,3 @@ using module_id = std::uint32_t;
1515

1616
template <typename> extern auto catalog() -> string_id;
1717
template <typename> extern auto module() -> module_id;
18-
19-
template <typename> struct encode_32;
20-
template <typename> struct encode_64;
21-
template <typename> struct encode_u32;
22-
template <typename> struct encode_u64;

include/log/catalog/encoder.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <log/catalog/arguments.hpp>
34
#include <log/catalog/builder.hpp>
45
#include <log/catalog/catalog.hpp>
56
#include <log/log.hpp>
Lines changed: 36 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
#pragma once
22

3+
#include <log/catalog/arguments.hpp>
34
#include <log/catalog/catalog.hpp>
45
#include <log/catalog/mipi_messages.hpp>
56

7+
#include <stdx/bit.hpp>
68
#include <stdx/compiler.hpp>
79
#include <stdx/type_traits.hpp>
810
#include <stdx/utility.hpp>
@@ -16,46 +18,19 @@
1618

1719
namespace logging::mipi {
1820
template <typename T>
19-
concept signed_packable = std::signed_integral<stdx::underlying_type_t<T>> and
20-
sizeof(T) <= sizeof(std::int64_t);
21+
concept packer = std::integral<typename T::template pack_as_t<int>> and
22+
requires { typename T::template encode_as_t<int>; };
2123

22-
template <typename T>
23-
concept unsigned_packable =
24-
std::unsigned_integral<stdx::underlying_type_t<T>> and
25-
sizeof(T) <= sizeof(std::int64_t);
26-
27-
template <typename T>
28-
concept packable = signed_packable<T> or unsigned_packable<T>;
29-
30-
template <typename T> struct encoding;
31-
32-
template <signed_packable T> struct encoding<T> {
33-
using encode_t = stdx::conditional_t<sizeof(T) <= sizeof(std::int32_t),
34-
encode_32<T>, encode_64<T>>;
35-
using pack_t = stdx::conditional_t<sizeof(T) <= sizeof(std::int32_t),
36-
std::int32_t, std::int64_t>;
37-
};
24+
template <typename, packer> struct builder;
3825

39-
template <unsigned_packable T> struct encoding<T> {
40-
using encode_t = stdx::conditional_t<sizeof(T) <= sizeof(std::int32_t),
41-
encode_u32<T>, encode_u64<T>>;
42-
using pack_t = stdx::conditional_t<sizeof(T) <= sizeof(std::uint32_t),
43-
std::uint32_t, std::uint64_t>;
44-
};
45-
46-
template <packable T> using pack_as_t = typename encoding<T>::pack_t;
47-
template <packable T> using encode_as_t = typename encoding<T>::encode_t;
48-
49-
template <typename> struct builder;
50-
51-
template <> struct builder<defn::short32_msg_t> {
26+
template <packer P> struct builder<defn::short32_msg_t, P> {
5227
template <auto Level> static auto build(string_id id, module_id) {
5328
using namespace msg;
5429
return owning<defn::short32_msg_t>{"payload"_field = id};
5530
}
5631
};
5732

58-
template <typename Storage> struct catalog_builder {
33+
template <typename Storage, packer P> struct catalog_builder {
5934
template <auto Level, packable... Ts>
6035
static auto build(string_id id, module_id m, Ts... args) {
6136
using namespace msg;
@@ -66,8 +41,16 @@ template <typename Storage> struct catalog_builder {
6641
constexpr auto header_size = defn::catalog_msg_t::size<V>::value;
6742

6843
auto const pack_arg = []<typename T>(V *p, T arg) -> V * {
69-
auto const packed = stdx::to_le(stdx::as_unsigned(
70-
static_cast<pack_as_t<T>>(stdx::to_underlying(arg))));
44+
typename P::template pack_as_t<T> converted{};
45+
if constexpr (sizeof(stdx::to_underlying(arg)) ==
46+
sizeof(converted)) {
47+
converted = stdx::bit_cast<decltype(converted)>(
48+
stdx::to_underlying(arg));
49+
} else {
50+
converted =
51+
static_cast<decltype(converted)>(stdx::to_underlying(arg));
52+
}
53+
auto const packed = stdx::to_le(stdx::as_unsigned(converted));
7154
std::memcpy(p, &packed, sizeof(packed));
7255
return p + stdx::sized8{sizeof(packed)}.in<V>();
7356
};
@@ -80,48 +63,49 @@ template <typename Storage> struct catalog_builder {
8063
}
8164
};
8265

83-
template <> struct builder<defn::catalog_msg_t> {
66+
template <packer P> struct builder<defn::catalog_msg_t, P> {
8467
template <auto Level, typename... Ts>
8568
static auto build(string_id id, module_id m, Ts... args) {
8669
using namespace msg;
8770
if constexpr ((0 + ... + sizeof(Ts)) <= sizeof(std::uint32_t) * 2) {
8871
constexpr auto header_size =
8972
defn::catalog_msg_t::size<std::uint32_t>::value;
9073
constexpr auto payload_size =
91-
stdx::sized8{(sizeof(id) + ... + sizeof(pack_as_t<Ts>))}
74+
stdx::sized8{(sizeof(id) + ... +
75+
sizeof(typename P::template pack_as_t<Ts>))}
9276
.in<std::uint32_t>();
9377
using storage_t =
9478
std::array<std::uint32_t, header_size + payload_size>;
95-
return catalog_builder<storage_t>{}.template build<Level>(id, m,
96-
args...);
79+
return catalog_builder<storage_t, P>{}.template build<Level>(
80+
id, m, args...);
9781
} else {
9882
constexpr auto header_size =
9983
defn::catalog_msg_t::size<std::uint8_t>::value;
10084
constexpr auto payload_size =
101-
(sizeof(id) + ... + sizeof(pack_as_t<Ts>));
85+
(sizeof(id) + ... + sizeof(typename P::template pack_as_t<Ts>));
10286
using storage_t =
10387
std::array<std::uint8_t, header_size + payload_size>;
104-
return catalog_builder<storage_t>{}.template build<Level>(id, m,
105-
args...);
88+
return catalog_builder<storage_t, P>{}.template build<Level>(
89+
id, m, args...);
10690
}
10791
}
10892
};
10993

110-
template <> struct builder<defn::compact32_build_msg_t> {
94+
template <packer P> struct builder<defn::compact32_build_msg_t, P> {
11195
template <auto Version> static auto build() {
11296
using namespace msg;
11397
return owning<defn::compact32_build_msg_t>{"build_id"_field = Version};
11498
}
11599
};
116100

117-
template <> struct builder<defn::compact64_build_msg_t> {
101+
template <packer P> struct builder<defn::compact64_build_msg_t, P> {
118102
template <auto Version> static auto build() {
119103
using namespace msg;
120104
return owning<defn::compact64_build_msg_t>{"build_id"_field = Version};
121105
}
122106
};
123107

124-
template <> struct builder<defn::normal_build_msg_t> {
108+
template <packer P> struct builder<defn::normal_build_msg_t, P> {
125109
template <auto Version, stdx::ct_string S> static auto build() {
126110
using namespace msg;
127111
constexpr auto header_size =
@@ -141,32 +125,33 @@ template <> struct builder<defn::normal_build_msg_t> {
141125
}
142126
};
143127

144-
struct default_builder {
128+
template <packer P = logging::default_arg_packer> struct default_builder {
145129
template <auto Level, packable... Ts>
146130
static auto build(string_id id, module_id m, Ts... args) {
147131
if constexpr (sizeof...(Ts) == 0u) {
148-
return builder<defn::short32_msg_t>{}.template build<Level>(id, m);
132+
return builder<defn::short32_msg_t, P>{}.template build<Level>(id,
133+
m);
149134
} else {
150-
return builder<defn::catalog_msg_t>{}.template build<Level>(
135+
return builder<defn::catalog_msg_t, P>{}.template build<Level>(
151136
id, m, args...);
152137
}
153138
}
154139

155140
template <auto Version, stdx::ct_string S = ""> auto build_version() {
156141
using namespace msg;
157142
if constexpr (S.empty() and stdx::bit_width(Version) <= 22) {
158-
return builder<defn::compact32_build_msg_t>{}
143+
return builder<defn::compact32_build_msg_t, P>{}
159144
.template build<Version>();
160145
} else if constexpr (S.empty() and stdx::bit_width(Version) <= 54) {
161-
return builder<defn::compact64_build_msg_t>{}
146+
return builder<defn::compact64_build_msg_t, P>{}
162147
.template build<Version>();
163148
} else {
164-
return builder<defn::normal_build_msg_t>{}
149+
return builder<defn::normal_build_msg_t, P>{}
165150
.template build<Version, S>();
166151
}
167152
}
168153

169154
template <template <typename...> typename F, typename... Args>
170-
using convert_args = F<encode_as_t<Args>...>;
155+
using convert_args = F<typename P::template encode_as_t<Args>...>;
171156
};
172157
} // namespace logging::mipi

test/log/catalog2b_lib.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,26 @@ using log_env2b = stdx::make_env_t<logging::get_level, logging::level::TRACE>;
2222
} // namespace
2323

2424
auto log_rt_enum_arg() -> void;
25+
auto log_rt_float_arg() -> void;
26+
auto log_rt_double_arg() -> void;
2527

2628
auto log_rt_enum_arg() -> void {
2729
auto cfg = logging::binary::config{test_log_args_destination{}};
2830
using namespace ns;
2931
cfg.logger.log_msg<log_env2b>(
3032
stdx::ct_format<"E string with {} placeholder">(E::value));
3133
}
34+
35+
auto log_rt_float_arg() -> void {
36+
auto cfg = logging::binary::config{test_log_args_destination{}};
37+
using namespace ns;
38+
cfg.logger.log_msg<log_env2b>(
39+
stdx::ct_format<"Float string with {} placeholder">(3.14f));
40+
}
41+
42+
auto log_rt_double_arg() -> void {
43+
auto cfg = logging::binary::config{test_log_args_destination{}};
44+
using namespace ns;
45+
cfg.logger.log_msg<log_env2b>(
46+
stdx::ct_format<"Double string with {} placeholder">(3.14));
47+
}

test/log/catalog_app.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ extern auto log_with_non_default_module() -> void;
2121
extern auto log_with_fixed_module() -> void;
2222
extern auto log_with_fixed_string_id() -> void;
2323
extern auto log_with_fixed_module_id() -> void;
24+
extern auto log_rt_float_arg() -> void;
25+
extern auto log_rt_double_arg() -> void;
2426

2527
TEST_CASE("log zero arguments", "[catalog]") {
2628
test_critical_section::count = 0;
@@ -58,6 +60,22 @@ TEST_CASE("log one 64-bit runtime argument", "[catalog]") {
5860
CHECK(log_calls == 1);
5961
}
6062

63+
TEST_CASE("log one float runtime argument", "[catalog]") {
64+
log_calls = 0;
65+
test_critical_section::count = 0;
66+
log_rt_float_arg();
67+
CHECK(test_critical_section::count == 2);
68+
CHECK(log_calls == 1);
69+
}
70+
71+
TEST_CASE("log one double runtime argument", "[catalog]") {
72+
log_calls = 0;
73+
test_critical_section::count = 0;
74+
log_rt_double_arg();
75+
CHECK(test_critical_section::count == 2);
76+
CHECK(log_calls == 1);
77+
}
78+
6179
TEST_CASE("log one formatted runtime argument", "[catalog]") {
6280
log_calls = 0;
6381
test_critical_section::count = 0;

0 commit comments

Comments
 (0)