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 three 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.
51
52 * PICOLIBC_DOUBLE_PRINTF_SCANF (default when
53   `-Dformat-default=double`). This offers full printf functionality,
54   including both float and double conversions. The picolibc.specs
55   stanza that matches this option maps __d_vfprintf to vfprintf and
56   __d_vfscanf to vfscanf. This is equivalent to adding this when
57   linking your application:
58
59	cc -Wl,--defsym=vfprintf=__d_vfprintf -Wl,--defsym=vfscanf=__d_vfscanf
60
61   If you're using a linker that supports -alias instead of --defsym,
62   you  would use:
63
64	cc -Wl,-alias,___d_vfprintf,_vfprintf -Wl,-alias,___d_vfscanf,_vfscanf
65
66 * PICOLIBC_INTEGER_PRINTF_SCANF (default when
67   `-Dformat-default=integer`). This removes support for all float and
68   double conversions. The picolibc.specs stanza that matches this
69   option maps __i_vfprintf to vfprintf and __i_vfscanf to
70   vfscanf. This is equivalent to adding this when linking your
71   application:
72
73	cc -Wl,--defsym=vfprintf=__i_vfprintf -Wl,--defsym=vfscanf=__i_vfscanf
74
75   If you're using a linker that supports -alias instead of --defsym,
76   you  would use:
77
78	cc -Wl,-alias,___i_vfprintf,_vfprintf -Wl,-alias,___i_vfscanf,_vfscanf
79
80 * PICOLIBC_FLOAT_PRINTF_SCANF (default when
81   `-Dformat-default=float`). This provides support for float, but not
82   double conversions. When picolibc.specs finds
83   -DPICOLIBC_FLOAT_PRINTF_SCANF on the command line during linking,
84   it maps __f_vfprintf to vfprintf and __f_vfscanf to vfscanf. This
85   is equivalent to adding this when linking your application:
86
87	cc -Wl,--defsym=vfprintf=__f_vfprintf -Wl,--defsym=vfscanf=__f_vfscanf
88
89   If you're using a linker that supports -alias instead of --defsym,
90   you  would use:
91
92	cc -Wl,-alias,___f_vfprintf,_vfprintf -Wl,-alias,___f_vfscanf,_vfscanf
93
94PICOLIBC_FLOAT_PRINTF_SCANF requires a special macro for float values:
95`printf_float`. To make it easier to switch between that and the default
96level, that macro is also correctly defined for the other two levels.
97
98Here's an example program to experiment with these options:
99
100	#include <stdio.h>
101
102	void main(void) {
103		printf(" 2⁶¹ = %lld π ≃ %.17g\n", 1ll << 61, printf_float(3.141592653589793));
104	}
105
106Now we can build and run it with the double options:
107
108	$ arm-none-eabi-gcc -DPICOLIBC_DOUBLE_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.elf printf.c
109	$ arm-none-eabi-size printf.elf
110	   text	   data	    bss	    dec	    hex	filename
111           7760	     80	   2056	   9896	   26a8	printf.elf
112	$ 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
113	 2⁶¹ = 2305843009213693952 π ≃ 3.141592653589793
114
115Switching to float-only reduces the size but lets this still work,
116although the floating point value has reduced precision:
117
118	$ 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
119	$ arm-none-eabi-size printf-float.elf
120	   text	   data	    bss	    dec	    hex	filename
121           6232	     80	   2056	   8368	   20b0	printf-float.elf
122	$ 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
123	 2⁶¹ = 2305843009213693952 π ≃ 3.1415927
124
125Going to integer-only reduces the size even further, but now it doesn't output
126the values correctly:
127
128	$ 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
129	$ arm-none-eabi-size printf-int.elf
130	   text	   data	    bss	    dec	    hex	filename
131           1856	     80	   2056	   3992	    f98	printf-int.elf
132	$ 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
133         2⁶¹ = 0 π ≃ *float*
134
135## Picolibc build options for printf and scanf options
136
137In addition to the application build-time options, picolibc includes a
138number of picolibc build-time options to control the feature set (and
139hence the size) of the library:
140
141 * `-Dio-c99-formats=true` This option controls whether support for
142   the C99 type-specific format modifiers 'j', 'z' and 't' and the hex
143   float format 'a' are included in the library. Support for the C99
144   format specifiers like PRId8 is always provided.  This option is
145   enabled by default.
146
147 * `-Dio-long-long=true` This option controls whether support for long
148   long types is included in the integer-only version of printf and
149   scanf. long long support is always included in the full and
150   float-only versions of printf and scanf. This option is disabled by
151   default.
152
153 * `-Dio-float-exact=true` This option, which is enabled by default,
154   controls whether the tinystdio code uses exact algorithms for
155   printf and scanf. When enabled, printing at least 9 digits
156   (e.g. "%.9g") for 32-bit floats and 17 digits (e.g. "%.17g") for
157   64-bit floats ensures that passing the output back to scanf will
158   exactly re-create the original value.
159
160 * `-Datomic-ungetc=true` This option, which is enabled by default,
161   controls whether getc/ungetc use atomic instruction sequences to
162   make them re-entrant. Without this option, multiple threads using
163   getc and ungetc may corrupt the state of the input buffer.
164
165For compatibility with newlib printf and scanf functionality, picolibc
166can be compiled with the original newlib stdio code. That greatly
167increases the code and data sizes of the library, including adding a
168requirement for heap support in the run time system. Here are the
169picolibc build options for that code:
170
171 * `-Dtinystdio=false` This disables the tinystdio code and uses
172   original newlib stdio code.
173
174 * `-Dnewlib-io-pos-args=true` This option add support for C99
175   positional arguments (e.g. "%1$"). This option is disabled by default.
176
177 * `-Dnewlib-io-long-double=true` This option add support long double
178   parameters. That is limited to systems using 80- and 128- bit long
179   doubles, or systems for which long double is the same as
180   double. This option is disabled by default
181
182 * `-Dnewlib-stdio64=true` This option changes the newlib stdio code
183   to use 64 bit values for file sizes and offsets. It also adds
184   64-bit versions of stdio interfaces which are defined with types
185   which may be 32-bits (like 'long'). This option is enabled by default.
186
187### Newlib floating point printf
188
189To build the `printf` sample program using the original newlib stdio
190code, the first step is to build picolibc with the right options.  The
191'nano' printf code doesn't support long-long integer output, so we
192can't use that, and we need to enable long-long and floating point
193support in the full newlib stdio code:
194
195        $ mkdir build-arm; cd build-arm
196        $ ../scripts/do-arm-configure -Dtinystdio=false -Dio-long-long=true -Dnewlib-io-float=true
197        $ ninja install
198
199Now we can build the example with the library:
200
201        $ 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
202	$ arm-none-eabi-size printf.elf
203           text	   data	    bss	    dec	    hex	filename
204          16008	    824	   2376	  19208	   4b08	printf.elf
205        $ 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
206         2⁶¹ = 2305843009213693952 π ≃ 3.1415926535897931
207
208This also uses 2332 bytes of space from the heap at runtime. Tinystdio
209saves 8088 bytes of text space and a total of 3396 bytes of data
210space.
211