Skip to content

Commit 558f9d9

Browse files
committed
lib: os: cbprintf: Add static packaging
Extend cbprintf with static packaging. Signed-off-by: Krzysztof Chruscinski <[email protected]>
1 parent 4e1926d commit 558f9d9

File tree

4 files changed

+375
-34
lines changed

4 files changed

+375
-34
lines changed

include/sys/cbprintf.h

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <stddef.h>
1212
#include <stdint.h>
1313
#include <toolchain.h>
14+
#include <sys/cbprintf_internal.h>
1415

1516
#ifdef CONFIG_CBPRINTF_LIBC_SUBSTS
1617
#include <stdio.h>
@@ -20,12 +21,32 @@
2021
extern "C" {
2122
#endif
2223

24+
/* Determine if _Generic is supported.
25+
* In general it's a C11 feature but it was added also in:
26+
* - GCC 4.9.0 https://gcc.gnu.org/gcc-4.9/changes.html
27+
* - Clang 3.0 https://releases.llvm.org/3.0/docs/ClangReleaseNotes.html
28+
*/
29+
#ifndef Z_C_GENERIC
30+
#if ((__STDC_VERSION__ >= 201112L) || \
31+
((__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) >= 40900) || \
32+
((__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) >= 30000))
33+
#define Z_C_GENERIC 1
34+
#else
35+
#define Z_C_GENERIC 0
36+
#endif
37+
#endif
38+
2339
/**
2440
* @defgroup cbprintf_apis Formatted Output APIs
2541
* @ingroup support_apis
2642
* @{
2743
*/
2844

45+
/** @brief Required alignment of the buffer used for packaging. */
46+
#define CBPRINTF_PACKAGE_ALIGNMENT \
47+
(IS_ENABLED(CONFIG_CBPRINTF_PACKAGE_LONGDOUBLE) ? \
48+
sizeof(long double) : MAX(sizeof(double), sizeof(long long)))
49+
2950
/** @brief Signature for a cbprintf callback function.
3051
*
3152
* This function expects two parameters:
@@ -45,6 +66,50 @@ extern "C" {
4566
*/
4667
typedef int (*cbprintf_cb)(/* int c, void *ctx */);
4768

69+
/** @brief Determine if string must be packaged in run time.
70+
*
71+
* Static packaging can be applied if size of the package can be determined
72+
* at compile time. In general, package size can be determined at compile time
73+
* if there are no string arguments which might be copied into package body if
74+
* they are considered transient.
75+
*
76+
* @param skip number of read only string arguments in the parameter list. It
77+
* shall be non-zero if there are known read only string arguments present
78+
* in the string (e.g. function name prefix in the log message).
79+
*
80+
* @param ... String with arguments.
81+
*
82+
* @retval 1 if string must be packaged in run time.
83+
* @retval 0 string can be statically packaged.
84+
*/
85+
#define CBPRINTF_MUST_RUNTIME_PACKAGE(skip, .../* fmt, ... */) \
86+
Z_CBPRINTF_MUST_RUNTIME_PACKAGE(skip, __VA_ARGS__)
87+
88+
/** @brief Statically package string.
89+
*
90+
* Build string package from formatted string. It assumes that formatted
91+
* string is in the read only memory.
92+
*
93+
* If _Generic is not supported then runtime packaging is performed.
94+
*
95+
* @param packaged pointer to where the packaged data can be stored. Pass a null
96+
* pointer to skip packaging but still calculate the total space required.
97+
* The data stored here is relocatable, that is it can be moved to another
98+
* contiguous block of memory. It must be aligned to the size of the longest
99+
* argument. It is recommended to use CBPRINTF_PACKAGE_ALIGNMENT for alignment.
100+
*
101+
* @param inlen set to the number of bytes available at @p packaged. If
102+
* @p packaged is NULL the value is ignored.
103+
*
104+
* @param outlen variable updated to the number of bytes required to completely
105+
* store the packed information. If input buffer was too small it is set to
106+
* -ENOSPC.
107+
*
108+
* @param ... formatted string with arguments. Format string must be constant.
109+
*/
110+
#define CBPRINTF_STATIC_PACKAGE(packaged, inlen, outlen, ... /* fmt, ... */) \
111+
Z_CBPRINTF_STATIC_PACKAGE(packaged, inlen, outlen, __VA_ARGS__)
112+
48113
/** @brief Capture state required to output formatted data later.
49114
*
50115
* Like cbprintf() but instead of processing the arguments and emitting the

include/sys/cbprintf_internal.h

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
/*
2+
* Copyright (c) 2020 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#ifndef ZEPHYR_INCLUDE_SYS_CBPRINTF_INTERNAL_H_
8+
#define ZEPHYR_INCLUDE_SYS_CBPRINTF_INTERNAL_H_
9+
10+
#include <stdarg.h>
11+
#include <stddef.h>
12+
#include <stdint.h>
13+
#include <toolchain.h>
14+
#include <sys/util.h>
15+
#include <sys/__assert.h>
16+
17+
#ifdef __cplusplus
18+
extern "C" {
19+
#endif
20+
21+
/*
22+
* Special alignment cases
23+
*/
24+
25+
#if defined(__i386__)
26+
/* there are no gaps on the stack */
27+
#define VA_STACK_ALIGN(type) 1
28+
#elif defined(__sparc__)
29+
/* there are no gaps on the stack */
30+
#define VA_STACK_ALIGN(type) 1
31+
#elif defined(__x86_64__)
32+
#define VA_STACK_MIN_ALIGN 8
33+
#elif defined(__aarch64__)
34+
#define VA_STACK_MIN_ALIGN 8
35+
#elif defined(__riscv)
36+
#define VA_STACK_MIN_ALIGN (__riscv_xlen / 8)
37+
#endif
38+
39+
/*
40+
* Default alignment values if not specified by architecture config
41+
*/
42+
43+
#ifndef VA_STACK_MIN_ALIGN
44+
#define VA_STACK_MIN_ALIGN 1
45+
#endif
46+
47+
#ifndef VA_STACK_ALIGN
48+
#define VA_STACK_ALIGN(type) MAX(VA_STACK_MIN_ALIGN, __alignof__(type))
49+
#endif
50+
51+
/** @brief Return 1 if argument is a pointer to char or wchar_t
52+
*
53+
* @param x argument.
54+
*
55+
* @return 1 if char * or wchar_t *, 0 otherwise.
56+
*/
57+
#define Z_CBPRINTF_IS_PCHAR(x) _Generic((x), \
58+
char * : 1, \
59+
const char * : 1, \
60+
volatile char * : 1, \
61+
const volatile char * : 1, \
62+
wchar_t * : 1, \
63+
const wchar_t * : 1, \
64+
volatile wchar_t * : 1, \
65+
const volatile wchar_t * : 1, \
66+
default : \
67+
0)
68+
69+
/** @brief Calculate number of char * or wchar_t * arguments in the arguments.
70+
*
71+
* @param fmt string.
72+
*
73+
* @param ... string arguments.
74+
*
75+
* @return number of arguments which are char * or wchar_t *.
76+
*/
77+
#define Z_CBPRINTF_HAS_PCHAR_ARGS(fmt, ...) \
78+
(FOR_EACH(Z_CBPRINTF_IS_PCHAR, (+), __VA_ARGS__))
79+
80+
/**
81+
* @brief Check if formatted string must be packaged in runtime.
82+
*
83+
* @param skip number of char/wchar_t pointers in the argument list which are
84+
* accepted for static packaging.
85+
*
86+
* @param ... String with arguments (fmt, ...).
87+
*
88+
* @retval 1 if string must be packaged at runtime.
89+
* @retval 0 if string can be statically packaged.
90+
*/
91+
#if Z_C_GENERIC
92+
#define Z_CBPRINTF_MUST_RUNTIME_PACKAGE(skip, ...) \
93+
COND_CODE_0(NUM_VA_ARGS_LESS_1(__VA_ARGS__), \
94+
(0), \
95+
(((Z_CBPRINTF_HAS_PCHAR_ARGS(__VA_ARGS__) - skip) > 0)))
96+
#else
97+
#define Z_CBPRINTF_MUST_RUNTIME_PACKAGE(skip, ...) 1
98+
#endif
99+
100+
/** @brief Get storage size for given argument.
101+
*
102+
* Floats are promoted to double so they use size of double, others int storage
103+
* or it's own storage size if it is bigger than int. Strings are stored in
104+
* the package with 1 byte header indicating if string is stored as pointer or
105+
* by value.
106+
*
107+
* @param x argument.
108+
*
109+
* @return Number of bytes used for storing the argument.
110+
*/
111+
#define Z_CBPRINTF_ARG_SIZE(v) \
112+
_Generic((v), \
113+
float : sizeof(double), \
114+
default : \
115+
_Generic((v), \
116+
void * : 0, \
117+
default : \
118+
sizeof((v)+0) \
119+
) \
120+
)
121+
122+
/** @brief Promote and store argument in the buffer.
123+
*
124+
* @param buf Buffer.
125+
*
126+
* @param arg Argument.
127+
*/
128+
#define Z_CBPRINTF_STORE_ARG(buf, arg) \
129+
*_Generic((arg) + 0, \
130+
char : (int *)buf, \
131+
unsigned char: (int *)buf, \
132+
short : (int *)buf, \
133+
unsigned short : (int *)buf, \
134+
int : (int *)buf, \
135+
unsigned int : (unsigned int *)buf, \
136+
long : (long *)buf, \
137+
unsigned long : (unsigned long *)buf, \
138+
long long : (long long *)buf, \
139+
unsigned long long : (unsigned long long *)buf, \
140+
float : (double *)buf, \
141+
double : (double *)buf, \
142+
long double : (long double *)buf, \
143+
default : \
144+
(const void **)buf) = arg
145+
146+
/** @brief Return alignment needed for given argument.
147+
*
148+
* @param _arg Argument
149+
*
150+
* @return Alignment in bytes.
151+
*/
152+
#define Z_CBPRINTF_ALIGNMENT(_arg) \
153+
MAX(_Generic((_arg) + 0, \
154+
float : VA_STACK_ALIGN(double), \
155+
double : VA_STACK_ALIGN(double), \
156+
long double : VA_STACK_ALIGN(long double), \
157+
long long : VA_STACK_ALIGN(long long), \
158+
unsigned long long : VA_STACK_ALIGN(long long), \
159+
default : \
160+
__alignof__((_arg) + 0)), VA_STACK_MIN_ALIGN)
161+
162+
/** @brief Detect long double variable.
163+
*
164+
* @param x Argument.
165+
*
166+
* @return 1 if @p x is a long double, 0 otherwise.
167+
*/
168+
#define Z_CBPRINTF_IS_LONGDOUBLE(x) \
169+
_Generic((x) + 0, long double : 1, default : 0)
170+
171+
/** @brief Safely package arguments to a buffer.
172+
*
173+
* Argument is put into the buffer if capable buffer is provided. Length is
174+
* incremented even if data is not packaged.
175+
*
176+
* @param _buf buffer.
177+
*
178+
* @param _idx index. Index is postincremented.
179+
*
180+
* @param _max maximum index (buffer capacity).
181+
*
182+
* @param _arg argument.
183+
*/
184+
#define Z_CBPRINTF_PACK_ARG2(_buf, _idx, _max, _arg) do { \
185+
BUILD_ASSERT(!((sizeof(double) < VA_STACK_ALIGN(long double)) && \
186+
Z_CBPRINTF_IS_LONGDOUBLE(_arg) && \
187+
!IS_ENABLED(CONFIG_CBPRINTF_PACKAGE_LONGDOUBLE)),\
188+
"Packaging of long double not enabled in Kconfig."); \
189+
while (_idx % Z_CBPRINTF_ALIGNMENT(_arg)) { \
190+
_idx += sizeof(int); \
191+
}; \
192+
uint32_t _arg_size = Z_CBPRINTF_ARG_SIZE(_arg); \
193+
if (_buf && _idx < _max) { \
194+
Z_CBPRINTF_STORE_ARG(&_buf[_idx], _arg); \
195+
} \
196+
_idx += _arg_size; \
197+
} while (0)
198+
199+
/** @brief Package single argument.
200+
*
201+
* Macro is called in a loop for each argument in the string.
202+
*
203+
* @param arg argument.
204+
*/
205+
#define Z_CBPRINTF_PACK_ARG(arg) \
206+
Z_CBPRINTF_PACK_ARG2(_pbuf, _pkg_len, _pmax, arg)
207+
208+
/** @brief Package descriptor.
209+
*
210+
* @param len Package length.
211+
*
212+
* @param str_cnt Number of strings stored in the package.
213+
*/
214+
struct z_cbprintf_desc {
215+
uint8_t len;
216+
uint8_t str_cnt;
217+
};
218+
219+
/** @brief Package header. */
220+
union z_cbprintf_hdr {
221+
struct z_cbprintf_desc desc;
222+
void *raw;
223+
};
224+
225+
/** @brief Statically package a formatted string with arguments.
226+
*
227+
* @param buf buffer. If null then only length is calculated.
228+
*
229+
* @param _inlen buffer capacity on input. Ignored when @p buf is null.
230+
*
231+
* @param _outlen number of bytes required to store the package.
232+
*
233+
* @param ... String with variable list of arguments.
234+
*/
235+
#define Z_CBPRINTF_STATIC_PACKAGE_GENERIC(buf, _inlen, _outlen, \
236+
... /* fmt, ... */) \
237+
do { \
238+
_Pragma("GCC diagnostic push") \
239+
_Pragma("GCC diagnostic ignored \"-Wpointer-arith\"") \
240+
if (IS_ENABLED(CONFIG_CBPRINTF_STATIC_PACKAGE_CHECK_ALIGNMENT)) { \
241+
__ASSERT(!((uintptr_t)buf & (CBPRINTF_PACKAGE_ALIGNMENT - 1)), \
242+
"Buffer must be aligned."); \
243+
} \
244+
uint8_t *_pbuf = buf; \
245+
size_t _pmax = (buf != NULL) ? *_inlen : SIZE_MAX; \
246+
size_t _pkg_len = 0; \
247+
union z_cbprintf_hdr *_len_loc; \
248+
/* package starts with string address and field with length */ \
249+
if (_pmax < sizeof(char *) + 2 * sizeof(uint16_t)) { \
250+
break; \
251+
} \
252+
_len_loc = (union z_cbprintf_hdr *)_pbuf; \
253+
_pkg_len += sizeof(union z_cbprintf_hdr); \
254+
if (_pbuf) { \
255+
*(char **)&_pbuf[_pkg_len] = GET_ARG_N(1, __VA_ARGS__); \
256+
} \
257+
_pkg_len += sizeof(char *); \
258+
/* Pack remaining arguments */\
259+
COND_CODE_0(NUM_VA_ARGS_LESS_1(__VA_ARGS__), (), ( \
260+
FOR_EACH(Z_CBPRINTF_PACK_ARG, (;), GET_ARGS_LESS_N(1, __VA_ARGS__));\
261+
)) \
262+
/* Store length */ \
263+
_outlen = (_pkg_len > _pmax) ? -ENOSPC : _pkg_len; \
264+
/* Store length in the header, set number of dumped strings to 0 */ \
265+
if (_pbuf) { \
266+
union z_cbprintf_hdr hdr = { .desc = {.len = _pkg_len }}; \
267+
*_len_loc = hdr; \
268+
} \
269+
_Pragma("GCC diagnostic pop") \
270+
} while (0)
271+
272+
#if Z_C_GENERIC
273+
#define Z_CBPRINTF_STATIC_PACKAGE(packaged, inlen, outlen, ... /* fmt, ... */) \
274+
Z_CBPRINTF_STATIC_PACKAGE_GENERIC(packaged, inlen, outlen, __VA_ARGS__)
275+
#else
276+
#define Z_CBPRINTF_STATIC_PACKAGE(packaged, inlen, outlen, ... /* fmt, ... */) \
277+
do { \
278+
/* Small trick needed to avoid warning on always true */ \
279+
if (((uintptr_t)packaged + 1) != 1) { \
280+
outlen = cbprintf_package(packaged, inlen, __VA_ARGS__); \
281+
} else { \
282+
outlen = cbprintf_package(NULL, 0, __VA_ARGS__); \
283+
} \
284+
} while (0)
285+
#endif /* Z_C_GENERIC */
286+
287+
#ifdef __cplusplus
288+
}
289+
#endif
290+
291+
292+
#endif /* ZEPHYR_INCLUDE_SYS_CBPRINTF_INTERNAL_H_ */

0 commit comments

Comments
 (0)