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