Skip to content

Commit ca839e1

Browse files
committed
Add support for printing floats and doubles.
This uses the float-to-string algorithm from AduinoJson, which is optimised for code size rather than performance. It is exposed in the debug print and printf. This increases code size and so is off by default. Floats will be printed as <float> or <double> if this support is not present. You can enable it with the following two xmake flags: --debug-print-doubles=y --debug-print-floats=y Note that `printf` never prints floats. The C calling convention promotes all floats to doubles when passed as variadic arguments. This means that debug-print-doubles is the only relevant one for doubles.
1 parent 743260f commit ca839e1

File tree

16 files changed

+515
-13
lines changed

16 files changed

+515
-13
lines changed

.github/workflows/main.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@ jobs:
3232
include:
3333
- sonata: false
3434
- build-type: debug
35-
build-flags: --debug-loader=y --debug-scheduler=y --debug-allocator=information --allocator-rendering=y -m debug
35+
build-flags: --debug-loader=y --debug-scheduler=y --debug-allocator=information --allocator-rendering=y -m debug --print-doubles=y --print-floats=n
3636
- build-type: release
37-
build-flags: --debug-loader=n --debug-scheduler=n --debug-allocator=none -m release --stack-usage-check-allocator=y --stack-usage-check-scheduler=y
37+
build-flags: --debug-loader=n --debug-scheduler=n --debug-allocator=none -m release --stack-usage-check-allocator=y --stack-usage-check-scheduler=y --print-doubles=n --print-floats=y
3838
- board: sonata-simulator
3939
build-type: release
40-
build-flags: --debug-loader=n --debug-scheduler=n --debug-allocator=none -m release --stack-usage-check-allocator=y --stack-usage-check-scheduler=y
40+
build-flags: --debug-loader=n --debug-scheduler=n --debug-allocator=none -m release --stack-usage-check-allocator=y --stack-usage-check-scheduler=y --print-doubles=y --print-floats=y
4141
sonata: true
4242
fail-fast: false
4343
runs-on: ubuntu-latest

sdk/include/__debug.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ enum DebugFormatArgumentKind : ptraddr_t
2020
DebugFormatArgumentSignedNumber64,
2121
/// Unsigned 64-bit integer, printed as hexadecimal.
2222
DebugFormatArgumentUnsignedNumber64,
23+
/// 32-bit floating-point value, printed as decimal.
24+
DebugFormatArgumentFloat,
25+
/// 64-bit floating-point value, printed as decimal.
26+
DebugFormatArgumentDouble,
2327
/// Pointer, printed as a full capability.
2428
DebugFormatArgumentPointer,
2529
/// Special case for permission sets, printed as in the capability

sdk/include/debug.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22
#include <__debug.h>
33
#include <__macro_map.h>
4+
#include <string.h>
45

56
#ifndef __cplusplus
67

@@ -11,13 +12,28 @@
1112
* Should not be used directly.
1213
*/
1314
# define CHERIOT_DEBUG_MAP_ARGUMENT(x) \
14-
{(uintptr_t)(x), \
15+
{_Generic((x), \
16+
double: ({ \
17+
typeof(x) __tmp = (x); \
18+
uintptr_t __return; \
19+
memcpy(&__return, &__tmp, sizeof(double)); \
20+
__return; \
21+
}), \
22+
float: ({ \
23+
typeof(x) __tmp = (x); \
24+
uintptr_t __return; \
25+
memcpy(&__return, &__tmp, sizeof(float)); \
26+
__return; \
27+
}), \
28+
default: (uintptr_t)(x)), \
1529
_Generic((x), \
1630
_Bool: DebugFormatArgumentBool, \
1731
char: DebugFormatArgumentCharacter, \
1832
short: DebugFormatArgumentSignedNumber32, \
1933
unsigned short: DebugFormatArgumentUnsignedNumber32, \
2034
int: DebugFormatArgumentSignedNumber32, \
35+
float: DebugFormatArgumentFloat, \
36+
double: DebugFormatArgumentDouble, \
2137
unsigned int: DebugFormatArgumentUnsignedNumber32, \
2238
signed long long: DebugFormatArgumentSignedNumber64, \
2339
unsigned long long: DebugFormatArgumentUnsignedNumber64, \

sdk/include/debug.hh

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,20 @@ struct DebugWriter
7979
* Write a single byte as hex with no leading 0x.
8080
*/
8181
virtual void write_hex_byte(uint8_t) = 0;
82+
/**
83+
* Write a single-precision floating-point value.
84+
*
85+
* If floating-point support is not compiled into the debug library, this
86+
* may print a placeholder.
87+
*/
88+
virtual void write(float) = 0;
89+
/**
90+
* Write a double-precision floating-point value.
91+
*
92+
* If floating-point support is not compiled into the debug library, this
93+
* may print a placeholder.
94+
*/
95+
virtual void write(double) = 0;
8296
/**
8397
* Write an integer as hex.
8498
*/
@@ -287,6 +301,29 @@ struct DebugFormatArgumentAdaptor<int64_t>
287301
}
288302
};
289303

304+
template<>
305+
struct DebugFormatArgumentAdaptor<float>
306+
{
307+
__always_inline static DebugFormatArgument construct(float value)
308+
{
309+
uintptr_t fudgedValue = 0;
310+
memcpy(&fudgedValue, &value, sizeof(value));
311+
return {fudgedValue, DebugFormatArgumentKind::DebugFormatArgumentFloat};
312+
}
313+
};
314+
315+
template<>
316+
struct DebugFormatArgumentAdaptor<double>
317+
{
318+
__always_inline static DebugFormatArgument construct(double value)
319+
{
320+
uintptr_t fudgedValue = 0;
321+
memcpy(&fudgedValue, &value, sizeof(value));
322+
return {fudgedValue,
323+
DebugFormatArgumentKind::DebugFormatArgumentDouble};
324+
}
325+
};
326+
290327
/**
291328
* C string specialisation, prints the string as-is.
292329
*/
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
#pragma once
2+
// Copyright Benoit Blanchon and CHERIoT Contributors.
3+
// SPDX-License-Identifier: MIT
4+
//
5+
// This code is derived from the ArduinoJson project:
6+
// Copyright Benoit Blanchon 2014-2017
7+
// MIT License
8+
9+
/**
10+
* This file provides routines to help convert from floating-point values to
11+
* strings.
12+
*
13+
* This uses the algorithm described here:
14+
*
15+
* https://blog.benoitblanchon.fr/lightweight-float-to-string/
16+
*
17+
* This approach is optimised for code size, not performance or accuracy. It
18+
* will give more rounding errors than common implementations but is a fraction
19+
* of the code size.
20+
*/
21+
22+
#include <array>
23+
#include <type_traits>
24+
25+
namespace
26+
{
27+
/**
28+
* Number of powers of ten required to represent all values that fit in any
29+
* supported floating-point type. 9 is sufficient for IEEE 754 64-bit
30+
* floating-point values. Currently, CHERIoT does not provide a long
31+
* double type.
32+
*/
33+
constexpr size_t NumberOfPowersOfTen = 9;
34+
35+
/**
36+
* Helper that computes the necessary value of `NumberOfPowersOfTen` for a
37+
* given type.
38+
*/
39+
template<typename T>
40+
constexpr size_t NecessaryNumberOfPowersOfTen = []() {
41+
return 32 - __builtin_clz(std::numeric_limits<T>::max_exponent10);
42+
}();
43+
44+
/**
45+
* Set of supported floating-point types.
46+
*
47+
* If new types are added to the allowed list, the
48+
* `NecessaryNumberOfPowersOfTen` part of the expression must also be
49+
* updated.
50+
*/
51+
template<typename T>
52+
concept SupportedFloatingPointType = requires() {
53+
requires((std::is_same_v<T, float> || std::is_same_v<T, double>) &&
54+
(NecessaryNumberOfPowersOfTen<double> <= NumberOfPowersOfTen));
55+
};
56+
57+
/**
58+
* Table of powers of ten. These are expensive to compute dynamically.
59+
*/
60+
template<typename T>
61+
constexpr std::array<T, NumberOfPowersOfTen> PositiveBinaryPowersOfTen = {
62+
T(1e1),
63+
T(1e2),
64+
T(1e4),
65+
T(1e8),
66+
T(1e16),
67+
T(1e32),
68+
T(1e64),
69+
T(1e128),
70+
T(1e256)};
71+
72+
/**
73+
* Table of negative powers of ten. These are expensive to compute
74+
* dynamically.
75+
*/
76+
template<typename T>
77+
constexpr std::array<T, NumberOfPowersOfTen> NegativeBinaryPowersOfTen = {
78+
T(1e-1),
79+
T(1e-2),
80+
T(1e-4),
81+
T(1e-8),
82+
T(1e-16),
83+
T(1e-32),
84+
T(1e-64),
85+
T(1e-128),
86+
T(1e-256)};
87+
88+
/// Largest value that is printed without an exponent
89+
template<typename T>
90+
constexpr T PositiveExponentiationThreshold = 1e7;
91+
92+
/// Smallest value that is printed without an exponent
93+
template<typename T>
94+
constexpr T NegativeExponentiationThreshold = 1e-5;
95+
96+
/**
97+
* Scale the floating-point value up or down in powers of ten so that the
98+
* decimal point will end up in a sensible place.
99+
*
100+
* The return value is the number of powers of ten (positive or negative)
101+
* that the value was scaled by.
102+
*
103+
* This uses binary exponentiation, see the blog post linked at the top of
104+
* the file for more details.
105+
*/
106+
template<SupportedFloatingPointType T>
107+
inline int normalize(T &value)
108+
{
109+
int powersOf10 = 0;
110+
111+
int8_t index = NecessaryNumberOfPowersOfTen<T> - 1;
112+
int bit = 1 << index;
113+
114+
if (value >= PositiveExponentiationThreshold<T>)
115+
{
116+
for (; index >= 0; index--)
117+
{
118+
if (value >= PositiveBinaryPowersOfTen<T>[index])
119+
{
120+
value *= NegativeBinaryPowersOfTen<T>[index];
121+
powersOf10 = (powersOf10 + bit);
122+
}
123+
bit >>= 1;
124+
}
125+
}
126+
127+
if (value > 0 && value <= NegativeExponentiationThreshold<T>)
128+
{
129+
for (; index >= 0; index--)
130+
{
131+
if (value < NegativeBinaryPowersOfTen<T>[index] * 10)
132+
{
133+
value *= PositiveBinaryPowersOfTen<T>[index];
134+
powersOf10 = (powersOf10 - bit);
135+
}
136+
bit >>= 1;
137+
}
138+
}
139+
140+
return powersOf10;
141+
}
142+
143+
/**
144+
* A floating-point value decomposed into components for printing.
145+
*/
146+
struct FloatParts
147+
{
148+
/// The integral component (before the decimal point).
149+
uint32_t integral;
150+
/// The decimal component (after the decimal point).
151+
uint32_t decimal;
152+
/// The exponent (the power of ten that this is raised to).
153+
int16_t exponent;
154+
/**
155+
* The number of decimal places in the exponent. The exponent is
156+
* represented as a value that can be converted to a string with
157+
* integer-to-string conversion. This may have leading zeroes. For
158+
* example, the number 123.045 will have an `integral` value of 123, a
159+
* `decimal` value of 45, an `exponent` of 1, and a `decimalPlaces` of
160+
* 3. The value 45 must therefore be printed with a leading zero.
161+
*/
162+
int8_t decimalPlaces;
163+
};
164+
165+
constexpr uint32_t pow10(int exponent)
166+
{
167+
return (exponent == 0) ? 1 : 10 * pow10(exponent - 1);
168+
}
169+
170+
template<SupportedFloatingPointType T>
171+
FloatParts decompose_float(T value, int8_t decimalPlaces)
172+
{
173+
uint32_t maxDecimalPart = pow10(decimalPlaces);
174+
175+
int exponent = normalize(value);
176+
177+
uint32_t integral = static_cast<uint32_t>(value);
178+
// reduce number of decimal places by the number of integral places
179+
for (uint32_t tmp = integral; tmp >= 10; tmp /= 10)
180+
{
181+
maxDecimalPart /= 10;
182+
decimalPlaces--;
183+
}
184+
185+
T remainder = (value - T(integral)) * T(maxDecimalPart);
186+
187+
uint32_t decimal = uint32_t(remainder);
188+
remainder = remainder - T(decimal);
189+
190+
// rounding:
191+
// increment by 1 if remainder >= 0.5
192+
decimal += uint32_t(remainder * 2);
193+
if (decimal >= maxDecimalPart)
194+
{
195+
decimal = 0;
196+
integral++;
197+
if (exponent && integral >= 10)
198+
{
199+
exponent++;
200+
integral = 1;
201+
}
202+
}
203+
204+
// remove trailing zeros
205+
while (decimal % 10 == 0 && decimalPlaces > 0)
206+
{
207+
decimal /= 10;
208+
decimalPlaces--;
209+
}
210+
211+
return {
212+
integral, decimal, static_cast<int16_t>(exponent), decimalPlaces};
213+
}
214+
215+
} // namespace

sdk/include/function_wrapper.hh

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,10 @@ class FunctionWrapper<R(Args...)>
8080

8181
public:
8282
/**
83-
* This is a non-owning reference, delete its copy and move
84-
* constructors to avoid accidental copies.
83+
* This is a non-owning reference, its copy constructor is safe to use for
84+
* passing down the stack.
8585
*/
86-
FunctionWrapper(FunctionWrapper &) = delete;
86+
FunctionWrapper(FunctionWrapper &) = default;
8787
FunctionWrapper(FunctionWrapper &&) = delete;
8888
FunctionWrapper &operator=(FunctionWrapper &&) = delete;
8989

@@ -92,6 +92,7 @@ class FunctionWrapper<R(Args...)>
9292
*/
9393
template<typename T>
9494
__always_inline FunctionWrapper(T &&fn)
95+
requires(!std::is_same_v<T, FunctionWrapper>)
9596
{
9697
// Make sure that we got the size for the storage right!
9798
static_assert(sizeof(storage) >= sizeof(ErasedFunctionWrapper<T>));

0 commit comments

Comments
 (0)