pw_log_backend_printf/varargs.rs
1// Copyright 2023 The Pigweed Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy of
5// the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations under
13// the License.
14
15//! # Infrastructure for building calls to `printf`.
16//!
17//! The `varargs` modules solves a particularly tricky problem: Some arguments
18//! passed to `pw_log` result in a single argument passed to printf (a `u32`
19//! is passed directly) while some may result in multiple arguments (a `&str`
20//! is passed as a length argument and a data argument). Since function like
21//! proc macros don't know the types of the arguments we rely on chained generic
22//! types.
23//!
24//! ## VarArgs trait
25//! The [`VarArgs`] both encapsulates the structure of the arguments as well
26//! as provides a method for calling `printf`. It accomplishes this through a
27//! recursive, chained/wrapped type through it's `OneMore<T>` associated type.
28//! We then provide generic implementations for [`VarArgs`] for tuples
29//! ov values that implement [`Clone`] such that:
30//!
31//! ```
32//! # use pw_log_backend_printf::varargs::VarArgs;
33//! # use core::any::TypeId;
34//! # use core::ffi::{c_int, c_uchar};
35//! type VarArgsType =
36//! <<<<() as VarArgs>
37//! ::OneMore<u32> as VarArgs>
38//! ::OneMore<i32> as VarArgs>
39//! ::OneMore<c_int> as VarArgs>
40//! ::OneMore<*const c_uchar>;
41//! type TupleType = (u32, i32, c_int, *const c_uchar);
42//! assert_eq!(TypeId::of::<VarArgsType>(), TypeId::of::<TupleType>());
43//! ```
44//!
45//! [`VarArgs`] provides an `append()` method that allows building up these
46//! typed tuple values:
47//!
48//! ```
49//! # use pw_log_backend_printf::varargs::VarArgs;
50//! # use core::ffi::{c_int, c_uchar};
51//! let string = "test";
52//! let args = ()
53//! .append(0u32)
54//! .append(-1i32)
55//! .append(string.len() as c_int)
56//! .append(string.as_ptr().cast::<*const c_uchar>());
57//! assert_eq!(args, (
58//! 0u32,
59//! -1i32,
60//! string.len() as c_int,
61//! string.as_ptr().cast::<*const c_uchar>()));
62//! ```
63//!
64//! Lastly [`VarArgs`] exposes an unsafe `call_printf()` method that calls
65//! printf and passes the build up arguments to `printf()`:
66//!
67//! ```
68//! # use pw_log_backend_printf::varargs::VarArgs;
69//! # use core::ffi::{c_int, c_uchar};
70//! let string = "test";
71//! unsafe {
72//! // This generates a call to:
73//! // `printf("[%s] %u %d %.*s\0".as_ptr(), "INF".as_ptr(), 0u32, -1i32, 4, string.as_ptr())`.
74//! ()
75//! .append(0u32)
76//! .append(-1i32)
77//! .append(string.len() as c_int)
78//! .append(string.as_ptr().cast::<*const c_uchar>())
79//! // Note that we always pass the null terminated log level string here
80//! // as an argument to `call_printf()`.
81//! .call_printf("[%s] %u %d %.*s\0".as_ptr().cast(), "INF\0".as_ptr().cast());
82//! }
83//! ```
84//!
85//! ## Arguments Trait
86//! The [`Arguments`] trait is the final piece of the puzzle. It is used to
87//! generate one or more calls to [`VarArgs::append()`] for each argument to
88//! `pw_log`. Most simple cases that push a single argument look like:
89//!
90//! ```ignore
91//! # // Ignored as a test as the test is neither the crate that defines
92//! # // `Arguments` nor `i32`.
93//! impl Arguments<i32> for i32 {
94//! type PushArg<Head: VarArgs> = Head::OneMore<i32>;
95//! fn push_arg<Head: VarArgs>(head: Head, arg: &i32) -> Self::PushArg<Head> {
96//! head.append(*arg)
97//! }
98//! }
99//! ```
100//!
101//! A more complex case like `&str` can push multiple arguments:
102//! ```ignore
103//! # // Ignored as a test as the test is neither the crate that defines
104//! # // `Arguments` nor `&str`.
105//! impl Arguments<&str> for &str {
106//! // Arguments are a chain of two `OneMore` types. One for the string length
107//! // and one for the pointer to the string data.
108//! type PushArg<Head: VarArgs> =
109//! <Head::OneMore<c_int> as VarArgs>::OneMore<*const c_uchar>;
110//!
111//! // `push_arg` pushes both the length and pointer to the string into the args tuple.
112//! fn push_arg<Head: VarArgs>(head: Head, arg: &&str) -> Self::PushArg<Head> {
113//! let arg = *arg;
114//! head.append(arg.len() as c_int).append(arg.as_ptr().cast::<*const c_uchar>())
115//! }
116//! }
117//! ```
118//!
119//! ## Putting it all together
120//! With all of these building blocks, the backend proc macro emits the following
121//! code:
122//!
123//! ```
124//! # use pw_log_backend_printf::varargs::{Arguments, VarArgs};
125//! // Code emitted for a call to `info!("Hello {}. It is {}:00", "Pigweed" as &str, 2 as u32)
126//! let args = ();
127//! let args = <&str as Arguments<&str>>::push_arg(args, &("Pigweed" as &str));
128//! let args = <u32 as Arguments<u32>>::push_arg(args, &(2 as u32));
129//! unsafe {
130//! args.call_printf("[%s] Hello %.*s. It is %d:00".as_ptr().cast(), "INF\0".as_ptr().cast());
131//! }
132//! ```
133use core::convert::Infallible;
134use core::ffi::{c_int, c_uchar};
135
136/// Implements a list of arguments to a vararg call.
137///
138/// See [module level docs](crate::varargs) for a detailed description on how
139/// [`Arguments`] works and is used.
140pub trait Arguments<T: ?Sized> {
141 /// Type produced by calling [`Self::push_arg()`].
142 type PushArg<Head: VarArgs>: VarArgs;
143
144 /// Push an argument onto the list of varargs.
145 ///
146 /// This may actually push zero, one, or more arguments onto the list
147 /// depending on implementation.
148 fn push_arg<Head: VarArgs>(head: Head, arg: &T) -> Self::PushArg<Head>;
149}
150
151/// Represents a variable length list of arguments to printf.
152///
153/// See [module level docs](crate::varargs) for a detailed description on how
154/// how [`VarArgs`] works and is used.
155pub trait VarArgs: Clone {
156 /// The type that is produced by a call to `append()`
157 type OneMore<T: Clone>: VarArgs;
158
159 /// Used to check if there is space left in the argument list.
160 ///
161 /// If the there is no space left in the argument list an [`Arguments<T>`]'s
162 /// PushArg type will expand to a type where `CHECK` us unable to be
163 /// compiled.
164 const CHECK: () = ();
165
166 /// Append an additional argument to this argument list.
167 fn append<T: Clone>(self, val: T) -> Self::OneMore<T>;
168
169 /// Calls `printf` with the arguments in `self` and the given format and log level string.
170 ///
171 /// # Safety
172 ///
173 /// Calls into `libc` printf without any input validation. The code further
174 /// up the stack is responsible for initializing valid [`VarArgs`] that
175 /// will cause printf to execute in a sound manner.
176 unsafe fn call_printf(self, format_str: *const c_uchar, log_level_str: *const c_uchar)
177 -> c_int;
178}
179
180#[derive(Clone)]
181/// A sentinel type for trying to append too many (>12) arguments to a
182/// [`VarArgs`] tuple.
183pub struct TooMany(Infallible);
184impl TooMany {
185 const fn panic() -> ! {
186 panic!("Too many arguments to logging call")
187 }
188}
189
190#[doc(hidden)]
191/// Implementation VarArgs for TooMany. Usages of TooMany::CHECK will cause a
192/// compile-time error.
193impl VarArgs for TooMany {
194 type OneMore<T: Clone> = TooMany;
195 const CHECK: () = Self::panic();
196
197 fn append<T: Clone>(self, _: T) -> Self::OneMore<T> {
198 Self::panic()
199 }
200
201 unsafe fn call_printf(self, _: *const c_uchar, _: *const c_uchar) -> c_int {
202 Self::panic()
203 }
204}
205
206/// Used to implement [`VarArgs`] on tuples.
207///
208/// This recursive macro divides it's arguments into a set of arguments for use
209/// in the recursive case in `[]`s and arguments used for the current
210/// implementation of [`VarArgs`] following the `[]`'s.
211macro_rules! impl_args_list {
212 // Entry point into the macro that directly invokes `@impl` with its
213 // arguments.
214 //
215 // Take a list of arguments of the form `ident => position`. `ident`
216 // is used to name the generic type argument for the implementation
217 // of `[VarArgs]` (i.e. `impl<ident: Clone, ...> VarArgs for (ident, ...)`).
218 // `position` is used to index into the argument tuple when calling
219 // printf (i.e. `printf(format_str, ..., args.position, ...)`).
220 ($($arg:ident => $arg_ident:tt),* $(,)?) => {
221 impl_args_list!(@impl [$($arg => $arg_ident),*]);
222 };
223
224 // Recursive case for [`VarArgs`] implementation.
225 //
226 // Implements [`VarArgs`] for a tuple with length equal to the number
227 // of arguments listed after the `[]`. It then recurses with taking
228 // the first argument between the `[]`s and appending it to the list
229 // after the `[]`s.
230 (@impl [$next:ident => $next_num:tt
231 $(, $remaining:ident => $next_remaining:tt)*]
232 $($current:ident => $current_num:tt),*) => {
233 impl<$($current: Clone),*> VarArgs for ($($current,)*) {
234 type OneMore<$next: Clone> = ($($current,)* $next,);
235 fn append<$next>(self, val: $next) -> ($($current,)* $next,) {
236 ($(self. $current_num,)* val,)
237 }
238
239 unsafe fn call_printf(
240 self,
241 format_str: *const c_uchar,
242 log_level_str: *const c_uchar,
243 ) -> c_int {
244 extern "C" {
245 fn printf(fmt: *const c_uchar, ...) -> c_int;
246 }
247 printf(format_str, log_level_str, $(self. $current_num),*)
248 }
249 }
250
251 impl_args_list!(@impl
252 [$($remaining => $next_remaining),*]
253 $($current => $current_num,)* $next => $next_num);
254 };
255
256 // Base for [`VarArgs`] implementation.
257 //
258 // Implements [`VarArgs`] for the full list of arguments and sets
259 // its `OneMore` type to `TooMany` to cause a compilation error
260 // if code tries to instantiate an argument list longer that this.
261 (@impl [] $($current:ident => $current_num:tt),*) => {
262 impl<$($current: Clone),*> VarArgs for ($($current),*) {
263 type OneMore<T: Clone> = TooMany;
264 fn append<T: Clone>(self, _: T) -> TooMany {
265 panic!("Too many arguments to logging call")
266 }
267
268 unsafe fn call_printf(
269 self,
270 format_str: *const c_uchar,
271 log_level_str: *const c_uchar,
272 ) -> c_int {
273 extern "C" {
274 fn printf(fmt: *const c_uchar, ...) -> c_int;
275 }
276 printf(format_str, log_level_str, $(self. $current_num),*)
277 }
278 }
279 };
280 }
281
282// Expands to implementations of [`VarArgs`] for tuples of length 0-12.
283impl_args_list!(
284 ARGS0 => 0,
285 ARGS1 => 1,
286 ARGS2 => 2,
287 ARGS3 => 3,
288 ARGS4 => 4,
289 ARGS5 => 5,
290 ARGS6 => 6,
291 ARGS7 => 7,
292 ARGS8 => 8,
293 ARGS9 => 9,
294 ARGS10 => 10,
295 ARGS11 => 11,
296 ARGS12 => 12,
297);
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302
303 #[test]
304 fn appended_args_yields_correct_tuple() {
305 let string = "test";
306 let args =
307 ().append(0u32)
308 .append(-1i32)
309 .append(string.len() as c_int)
310 .append(string.as_ptr().cast::<*const c_uchar>());
311
312 assert_eq!(
313 args,
314 (
315 0u32,
316 -1i32,
317 string.len() as c_int,
318 string.as_ptr().cast::<*const c_uchar>()
319 )
320 );
321 }
322
323 #[test]
324 fn twelve_argument_long_tuples_are_supported() {
325 let args =
326 ().append(0u32)
327 .append(1u32)
328 .append(2u32)
329 .append(3u32)
330 .append(4u32)
331 .append(5u32)
332 .append(6u32)
333 .append(7u32)
334 .append(8u32)
335 .append(9u32)
336 .append(10u32)
337 .append(11u32);
338
339 assert_eq!(args, (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11));
340 }
341}