1# Printf and Scanf in Picolibc
2
3Embedded systems are often tightly constrained on code space, which
4makes it important to be able to only include code and data actually
5needed by the application. The format-string based interface to
6printf and scanf makes it very difficult to determine which
7conversion operations might be needed by the application.
8
9Picolibc handles this by providing multiple printf and scanf
10implementations in the library with varying levels of conversion
11support. The application developer selects among these at compile and
12link time based on knowledge of the requirements of the
13application. The selection is done using a compiler command line
14definition of a preprocessor symbol which maps the public printf and
15scanf names to internal names in the linker.
16
17The function name mapping happens in picolibc.specs file which scans
18the compiler command line looking for the preprocessor token and uses
19that to add --defsym options when linking. This means the preprocessor
20definition must be set on the command line and not in a file.
21
22Using the defsym approach, rather than renaming the functions with the
23C preprocessor has a couple of benefits:
24
25 * Printf uses within picolibc (which are all integer-only) now share
26   whichever printf the application needs, avoiding including both
27   integer-only and float versions
28
29 * Printf optimizations in the compiler which map to puts or fputs now
30   work even when using integer-only or float printf functions.
31
32Because the linker gets --defsym flags for both vfprintf and vfscanf,
33those functions will always get included in the link process. To avoid
34linking them into the application when they aren't otherwise needed,
35picolibc.specs includes the --gc-sections linker flag. This causes
36those functions to be discarded if they aren't used in the
37application.
38
39However, the defsym approach does not work with link-time
40optimization. In that case, applications can only use the default
41function. For that case, the library needs to allow a different
42default version to be selected while compiling the library.
43
44## Printf and Scanf levels in Picolibc
45
46There are five levels of printf support provided by Picolibc that can
47be selected when building applications. One of these is the default
48used when no symbol definitions are applied; that is selected using
49the picolibc built-time option, `-Dformat-default`, which defaults to
50`double`, selecting PICOLIBC_DOUBLE_PRINTF_SCANF. These are listed in
51order of decreasing functionality and size.
52
53 * PICOLIBC_DOUBLE_PRINTF_SCANF (default when
54   `-Dformat-default=double`). This offers full printf functionality,
55   including both float and double conversions along with the C99
56   extensions and POSIX positional parameters. There is optional
57   support for the upcoming %b format specifier via the `io-percent-b`
58   setting and optional support for long double values via the
59   `io-long-double` setting. The picolibc.specs stanza that matches
60   this option maps __d_vfprintf to vfprintf and __d_vfscanf to
61   vfscanf. This is equivalent to adding this when linking your
62   application:
63
64	cc -Wl,--defsym=vfprintf=__d_vfprintf -Wl,--defsym=vfscanf=__d_vfscanf
65
66   If you're using a linker that supports -alias instead of --defsym,
67   you  would use:
68
69	cc -Wl,-alias,___d_vfprintf,_vfprintf -Wl,-alias,___d_vfscanf,_vfscanf
70
71 * PICOLIBC_FLOAT_PRINTF_SCANF (default when
72   `-Dformat-default=float`). This provides support for float, but not
73   double or long double conversions. When picolibc.specs finds
74   -DPICOLIBC_FLOAT_PRINTF_SCANF on the command line during linking,
75   it maps __f_vfprintf to vfprintf and __f_vfscanf to vfscanf. This
76   is equivalent to adding this when linking your application:
77
78	cc -Wl,--defsym=vfprintf=__f_vfprintf -Wl,--defsym=vfscanf=__f_vfscanf
79
80   If you're using a linker that supports -alias instead of --defsym,
81   you  would use:
82
83	cc -Wl,-alias,___f_vfprintf,_vfprintf -Wl,-alias,___f_vfscanf,_vfscanf
84
85 * PICOLIBC_LONG_LONG_PRINTF_SCANF (default when
86   `-Dformat-default=long-long`). This removes support for all float and
87   double conversions and makes support for C99 extensions and POSIX
88   positional parameters optional via the `io-c99-formats` and
89   `io-pos-args` settings. The picolibc.specs stanza that matches this
90   option maps __l_vfprintf to vfprintf and __l_vfscanf to
91   vfscanf. This is equivalent to adding this when linking your
92   application:
93
94	cc -Wl,--defsym=vfprintf=__l_vfprintf -Wl,--defsym=vfscanf=__l_vfscanf
95
96   If you're using a linker that supports -alias instead of --defsym,
97   you  would use:
98
99	cc -Wl,-alias,___l_vfprintf,_vfprintf -Wl,-alias,___l_vfscanf,_vfscanf
100
101 * PICOLIBC_INTEGER_PRINTF_SCANF (default when
102   `-Dformat-default=integer`). This removes support for long long
103   conversions where those values are larger than long values. The
104   picolibc.specs stanza that matches this option maps __i_vfprintf to
105   vfprintf and __i_vfscanf to vfscanf. This is equivalent to adding
106   this when linking your application:
107
108	cc -Wl,--defsym=vfprintf=__i_vfprintf -Wl,--defsym=vfscanf=__i_vfscanf
109
110   If you're using a linker that supports -alias instead of --defsym,
111   you  would use:
112
113	cc -Wl,-alias,___i_vfprintf,_vfprintf -Wl,-alias,___i_vfscanf,_vfscanf
114
115 * PICOLIBC_MINIMAL_PRINTF_SCANF (default when
116   `-Dformat-default=minimal`). This removes support for width and
117   precision, alternate presentation modes and alternate sign
118   presentation. All of these are still parsed correctly, so
119   applications needn't adjust format strings in most cases, but the
120   output will not be the same. It also disables any support for
121   positional arguments and %b formats that might be selected with
122   `io-pos-args` or `io-percent-b`.  The picolibc.specs stanza that
123   matches this option maps __m_vfprintf to vfprintf and __m_vfscanf
124   to vfscanf. This is equivalent to adding this when linking your
125   application:
126
127	cc -Wl,--defsym=vfprintf=__m_vfprintf -Wl,--defsym=vfscanf=__m_vfscanf
128
129   If you're using a linker that supports -alias instead of --defsym,
130   you  would use:
131
132	cc -Wl,-alias,___m_vfprintf,_vfprintf -Wl,-alias,___m_vfscanf,_vfscanf
133
134PICOLIBC_FLOAT_PRINTF_SCANF requires a special macro for float values:
135`printf_float`. To make it easier to switch between that and the default
136level, that macro is also correctly defined for the other two levels.
137
138Here's an example program to experiment with these options:
139
140	#include <stdio.h>
141
142	void main(void) {
143		printf(" 2⁶¹ = %lld π ≃ %.17g\n", 1ll << 61, printf_float(3.141592653589793));
144	}
145
146Now we can build and run it with the double options:
147
148	$ arm-none-eabi-gcc -Os -march=armv7-m --specs=picolibc.specs --oslib=semihost --crt0=hosted -Wl,--defsym=__flash=0 -Wl,--defsym=__flash_size=0x00200000 -Wl,--defsym=__ram=0x20000000 -Wl,--defsym=__ram_size=0x200000 -DPICOLIBC_DOUBLE_PRINTF_SCANF -o printf.elf printf.c
149	$ arm-none-eabi-size printf.elf
150	   text	   data	    bss	    dec	    hex	filename
151	   8088	     80	   4104	  12272	   2ff0	printf.elf
152	$ qemu-system-arm -chardev stdio,id=stdio0 -semihosting-config enable=on,chardev=stdio0 -monitor none -serial none -machine mps2-an385,accel=tcg -kernel printf.elf -nographic
153	 2⁶¹ = 2305843009213693952 π ≃ 3.141592653589793
154
155Switching to float-only reduces the size but lets this still work,
156although the floating point value has reduced precision:
157
158	$ arm-none-eabi-gcc -DPICOLIBC_FLOAT_PRINTF_SCANF -Os -march=armv7-m --specs=picolibc.specs --oslib=semihost --crt0=hosted -Wl,--defsym=__flash=0 -Wl,--defsym=__flash_size=0x00200000 -Wl,--defsym=__ram=0x20000000 -Wl,--defsym=__ram_size=0x200000 -o printf-float.elf printf.c
159	$ arm-none-eabi-size printf-float.elf
160	   text	   data	    bss	    dec	    hex	filename
161	   6792	     80	   4104	  10976	   2ae0	printf-float.elf
162	$ qemu-system-arm -chardev stdio,id=stdio0 -semihosting-config enable=on,chardev=stdio0 -monitor none -serial none -machine mps2-an385,accel=tcg -kernel printf-float.elf -nographic
163	 2⁶¹ = 2305843009213693952 π ≃ 3.1415927
164
165Selecting the long-long variant reduces the size further, but now the
166floating point value is not displayed correctly:
167
168	$ arm-none-eabi-gcc -DPICOLIBC_LONG_LONG_PRINTF_SCANF -Os -march=armv7-m --specs=picolibc.specs --oslib=semihost --crt0=hosted -Wl,--defsym=__flash=0 -Wl,--defsym=__flash_size=0x00200000 -Wl,--defsym=__ram=0x20000000 -Wl,--defsym=__ram_size=0x200000 -o printf-long-long.elf printf.c
169	$ arm-none-eabi-size printf-long-long.elf
170	   text	   data	    bss	    dec	    hex	filename
171	   2216	     80	   4104	   6400	   1900	printf-long-long.elf
172	$ qemu-system-arm -chardev stdio,id=stdio0 -semihosting-config enable=on,chardev=stdio0 -monitor none -serial none -machine mps2-an385,accel=tcg -kernel printf-long-long.elf -nographic
173	 2⁶¹ = 2305843009213693952 π ≃ *float*
174
175Going to integer-only reduces the size even further, but now it
176doesn't output the long long value correctly:
177
178	$ arm-none-eabi-gcc -DPICOLIBC_INTEGER_PRINTF_SCANF -Os -march=armv7-m --specs=picolibc.specs --oslib=semihost --crt0=hosted -Wl,--defsym=__flash=0 -Wl,--defsym=__flash_size=0x00200000 -Wl,--defsym=__ram=0x20000000 -Wl,--defsym=__ram_size=0x200000 -o printf-int.elf printf.c
179	$ arm-none-eabi-size printf-int.elf
180	   text	   data	    bss	    dec	    hex	filename
181	   2056	     80	   4104	   6240	   1860	printf-int.elf
182	$ qemu-system-arm -chardev stdio,id=stdio0 -semihosting-config enable=on,chardev=stdio0 -monitor none -serial none -machine mps2-an385,accel=tcg -kernel printf-int.elf -nographic
183	 2⁶¹ = 0 π ≃ *float*
184
185To shrink things still further, use the 'minimal' variant. This
186doesn't even look at the %g formatting instruction, so that value
187displays as '%g'.
188
189	$ arm-none-eabi-gcc -DPICOLIBC_MINIMAL_PRINTF_SCANF -Os -march=armv7-m --specs=picolibc.specs --oslib=semihost --crt0=hosted -Wl,--defsym=__flash=0 -Wl,--defsym=__flash_size=0x00200000 -Wl,--defsym=__ram=0x20000000 -Wl,--defsym=__ram_size=0x200000 -o printf-min.elf printf.c
190	$ arm-none-eabi-size printf-min.elf
191	   text	   data	    bss	    dec	    hex	filename
192	   1520	     80	   4104	   5704	   1648	printf-min.elf
193	$ qemu-system-arm -chardev stdio,id=stdio0 -semihosting-config enable=on,chardev=stdio0 -monitor none -serial none -machine mps2-an385,accel=tcg -kernel printf-min.elf -nographic
194	 2⁶¹ = 0 π ≃ %g
195
196There's a build-time option available that enables long-long support
197in the minimal printf variant, `-Dminimal-io-long-long=true`. Building with
198that increases the size modestly while fixing the long long output:
199
200	$ arm-none-eabi-size printf-min.elf
201	   text	   data	    bss	    dec	    hex	filename
202	   1632	     80	   4104	   5816	   16b8	printf-min.elf
203	$ qemu-system-arm -chardev stdio,id=stdio0 -semihosting-config enable=on,chardev=stdio0 -monitor none -serial none -machine mps2-an385,accel=tcg -kernel printf-min.elf -nographic
204	 2⁶¹ = 2305843009213693952 π ≃ %g
205
206## Picolibc build options for printf and scanf options
207
208In addition to the application build-time options, picolibc includes a
209number of picolibc build-time options to control the feature set (and
210hence the size) of the library:
211
212 * `-Dio-c99-formats=true` This option controls whether support for
213   the C99 type-specific format modifiers 'j', 'z' and 't' and the hex
214   float format 'a' are included in the long-long, integer and minimal
215   printf and scanf variants. Support for the C99 format specifiers
216   like PRId8 is always provided.  This option is enabled by default.
217
218 * `-Dio-pos-args=true` This option add support for C99 positional
219   arguments to the long long and integer printf and scanf variant
220   (e.g. "%1$"). Positional arguments are always supported in the
221   double and float variants and never supported in the minimal
222   variant. This option is disabled by default.
223
224 * `-Dio-long-long=true` This deprecated option controls whether
225   support for long long types is included in the integer variant of
226   printf and scanf. Instead of using this option, applications should
227   select the long long printf and scanf variants. This option is
228   disabled by default.
229
230 * `-Dminimal-io-long-long=true` This option controls whether support
231   for long long types is included in the minimal variant of printf
232   and scanf. This option is disabled by default.
233
234 * `-Dio-float-exact=true` This option, which is enabled by default,
235   controls whether the tinystdio code uses exact algorithms for
236   printf and scanf. When enabled, printing at least 9 digits
237   (e.g. "%.9g") for 32-bit floats and 17 digits (e.g. "%.17g") for
238   64-bit floats ensures that passing the output back to scanf will
239   exactly re-create the original value.
240
241 * `-Dio-long-double=true` This option add support for long double
242   parameters. That is limited to systems using 80- and 128- bit long
243   doubles, or systems for which long double is the same as
244   double. This option is disabled by default
245
246 * `-Datomic-ungetc=true` This option, which is enabled by default,
247   controls whether getc/ungetc use atomic instruction sequences to
248   make them re-entrant. Without this option, multiple threads using
249   getc and ungetc may corrupt the state of the input buffer.
250
251 * `-Dprintf-small-ultoa=true` This option, which is enabled by
252   default, switches printf's binary-to-decimal conversion code to a
253   version which avoids soft division for values larger than the
254   machine registers as those functions are often quite large and
255   slow. Applications using soft division on large values elsewhere
256   will save space by disabling this option as that avoids including
257   custom divide-and-modulus-by-ten implementations.
258
259For compatibility with newlib printf and scanf functionality, picolibc
260can be compiled with the original newlib stdio code. That greatly
261increases the code and data sizes of the library, including adding a
262requirement for heap support in the run time system. Here are the
263picolibc build options for that code:
264
265 * `-Dtinystdio=false` This disables the tinystdio code and uses
266   original newlib stdio code.
267
268 * `-Dnewlib-stdio64=true` This option changes the newlib stdio code
269   to use 64 bit values for file sizes and offsets. It also adds
270   64-bit versions of stdio interfaces which are defined with types
271   which may be 32-bits (like 'long'). This option is enabled by default.
272
273### Newlib floating point printf
274
275To build the `printf` sample program using the original newlib stdio
276code, the first step is to build picolibc with the right options.  The
277'nano' printf code doesn't support long-long integer output, so we
278can't use that, and we need to enable long-long and floating point
279support in the full newlib stdio code:
280
281        $ mkdir build-arm; cd build-arm
282        $ ../scripts/do-arm-configure -Dtinystdio=false -Dio-long-long=true -Dnewlib-io-float=true
283        $ ninja install
284
285Now we can build the example with the library:
286
287        $ arm-none-eabi-gcc -Os -march=armv7-m --specs=picolibc.specs --oslib=semihost --crt0=hosted -Wl,--defsym=__flash=0 -Wl,--defsym=__flash_size=0x00200000 -Wl,--defsym=__ram=0x20000000 -Wl,--defsym=__ram_size=0x200000 -o printf.elf printf.c
288	$ arm-none-eabi-size printf.elf
289	   text	   data	    bss	    dec	    hex	filename
290	  16632	    460	   4944	  22036	   5614	printf.elf
291        $ qemu-system-arm -chardev stdio,id=stdio0 -semihosting-config enable=on,chardev=stdio0 -monitor none -serial none -machine mps2-an385,accel=tcg -kernel printf.elf -nographic
292	 2⁶¹ = 2305843009213693952 π ≃ 3.1415926535897931
293
294This also uses 1252 bytes of space from the heap at runtime. Tinystdio
295saves 8544 bytes of text space and a total of 2472 bytes of data
296space.
297