How to make sure no floating point code is used

Float and double data types area a bad choice for embedded applications. At least in most applications, and can or should be avoided, even with hardware FPU support present.

But how can I be sure that no floating point operations are used?

wrapping float and double runtime routines

This article describes how to configure the GNU toolchain, so that no float or double operations are used, with the example of ARM Cortex-M. What I do? ‘Poisoning’ (!!!) the source code, force the gcc compiler to use software floating point operations and then catch them with the GNU linker :-).

Why no float (or double)?

Floating point operations or float and double data types are something I avoid in embedded applications: in most cases float and double data types are not necessary and only cause precision and code size and performance issues. Even with an FPU present it is problematic. Another good read is here: https://www.embedded.com/floating-point-data-in-embedded-software/

So how to be sure that the application is not using float or double? In “Binary, do you use Hard-Float or Soft-Float?” I showed how to check a ELF/Dwarf file if it is configured for hardware or software floating point routines. But this only tells what compiler or architecture options are used. The bigger question is: how to make sure that no float or double operations are actually used in the binary or software?

This is actually a question much more difficult to answer, at least for GNU based tool chains. The GNU toolchain does not have an easy option to tell the compiler something like “do not use floating point (float or double) data types”. This article shows how you can ban float and/or double with the GNU toolchain.

Poison the float and double

One easy way is to ‘poison’ any float or double usage, using a pragma:

#pragma GCC poison float double

After the ‘poison’ keyword I specify a list of identifiers which are not allowed after the occurrence of the poison pragma, resulting in a compiler error:

The pragma is effective for anything following that pragma. So I can add it in my source files at the beginning, or I can use it with the -include compiler option to make it effective for all source files, so I don’t have to add it for every file:

Poison float and double

Use global poison

Instead adding the pragma to every source file, I can add it globally using the -include compiler option. With that option, I can include a file on top of every compiled file.

Below is how I have added to the project options:

-include with poison file

This approach is very effective, as it even bans every usage of float or double data types in all header files.

Catch float and double in libraries

The two above ways with poisoning only works and covers files I’m compiling in the project. But what about if float or double usage sneaks in through linking with libraries?

The solution I use has two steps:

A) Set the project option to use software floating point runtime routines (e.g. with -msoft-float): that way the compiler is forced to use float/double runtime routines instead of hardware which I’m going catch in step B).
For this I have to set the ‘no-float’ option both for the compiler and the linker:

Compiler soft-float option
Linker soft-float option

Below is the disassembly with calls of routines we want to catch:

B) Map the runtime routines using the the linker –wrap options (I wrote about this here: GNU Linker Wizardry: Wrapping printf() with Timestamps).

The easiest way is to list all the runtime functions in a file, for example:

-Wl,--wrap=__aeabi_fadd
-Wl,--wrap=__aeabi_dadd
-Wl,--wrap=__aeabi_fmul
-Wl,--wrap=__aeabi_dmul
-Wl,--wrap=__aeabi_fsub
-Wl,--wrap=__aeabi_dsub
-Wl,--wrap=__aeabi_fdiv
-Wl,--wrap=__aeabi_ddiv
-Wl,--wrap=__aeabi_f2iz
-Wl,--wrap=__aeabi_d2iz
-Wl,--wrap=__aeabi_f2d
-Wl,--wrap=__aeabi_d2f

Then I’m using the ‘@’ syntax to pass the options file to the linker:

wrapping float and double runtime routines

With this, even hidden and otherwise undetected float and double operations raise an error by the linker:

Linker errors for wrapped float and double functions

Summary

With this, I showed different ways to catch float and double usage in applications, to be sure that here are not floating point operations used. One way is the ‘poison’ pragma which is fine for the source files. The other approach is to tell the compiler to use software floating point operations and then catch them using the linker wrap option. In any case, no float of double operations can escape that way.

Happy un-floating 🙂

LInks

15 thoughts on “How to make sure no floating point code is used

    • I have used the –wrap for other things (see my link about wrapping printf()) for a while already. The #pragma poison is something I have discovered lately with looking for ways to improve code quality.

      Like

  1. I have a problem with blanket bans of goto. They can be useful in bailing out of deeply nested things to handle errors. Clearly we don’t want to use goto like we are writing BASIC programs.

    More importantly they can be used be hind the scenes in things like the Ragel State Machine Compiler. When properly configured Ragel will produce smaller/faster code using GCC’s Computed Goto (another GCC thing few know exist; ie. goto a place held in a variable). Banning goto in cases like this really accomplish nothing. Just makes for bigger/slower code with no gain in safety.

    Like

  2. More of a historical note now. 

    The early AVR GCC compilers required that a floating point value be passed to the _delay_xx() functions. The compiler coerced the value into an unsigned int at compile time. Passing the functions a non-float resulted in wrong delays or in rare cases broken code. It was a frequent topic in the early days of Winavr, when it caught new people by surprise. To be clear there was never any ‘float’ code involved in the compiled code when things were done per the documentation.

    Again we must be careful with outright bans of things.

    Like

    • interesting case with the _delay_ms(double) function on AVR GCC. The issue is around the fact that you need to pass a ‘compile time constant’, not a variable or the like. I think this is a good example how double/float can be used. Instead passing a double (handling ms and us), one could have defined the interface for example passing an unsigned long value as the microseconds, e.g. _delay_ms_with_us(unsigned long us). Still, if this is using a macro approach and extra computation is needed, that would end up badly.
      And just to be clear: I don’t want to ban things like this, only stirring up good discussions :-).

      Like

      • Modern AVR GCC uses __builtin_avr_delay_cycles() under the hood that takes an uint32_t of ‘ticks’.

        It was normal to write _delay_ms( 100.0f ) for a 100 millisecond delay. Not that you should be doing such things in good embedded code…

        What interested me the most was the compile time

        Like

  3. Pingback: How to make sure no floating point code is used « Adafruit Industries – Makers, hackers, artists, designers and engineers!

What do you think?

This site uses Akismet to reduce spam. Learn how your comment data is processed.