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}