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