I’m sometimes asked if compilers are compatible, in the sense that you could build a library with the first compiler and link it into a project being built with a second compiler (that is, binary compatibility). Historically most of these questions involved compatibility of Arm Compiler for Embedded (AC6) with its predecessor AC5, or with the Arm GNU Toolchain GCC. With the approaching launch of the next-generation Arm embedded compiler, named Arm Toolchain for Embedded (ATfE), focus for these questions is now shifting to compatibility between AC6 and ATfE. So, it’s a good time to refresh the subject and provide a bit of guidance.
Before I get started, there’s a few things to note. First in this blog post, I’m going to use the word “toolchain” rather than the word “compiler”. This is because I want to stress that AC6, GCC, ATfE and others are far more than just a compiler. They are complete compilation toolchains consisting of an assembler, a linker, binary utilities and libraries in addition to the compiler. As we will see in a moment, that’s really important for binary compatibility. Where I use the word "compiler", I'm referring specifically to the compiler component rather than the toolchain as a whole.
Second, I’m talking here about C code rather than C++. C++ brings its own challenges for binary compatibility, making compatibility more difficult to achieve. Also, I’m talking about “embedded bare metal” code, by which I mean there’s no operating system involved.
To be confident of binary compatibility between a user library and the project final link, there a few things to take care of even if you are using the same version of the same toolchain to both create the library and do the final link.
The Application Binary Interface (ABI) for the Arm Architecture provides a common base standard for many aspects of binary interoperability. However, the ABI isn’t a monolithic “one size fits all” standard. The ABI provides several variants to suit different needs, particularly with reference to the Procedure Call Standard (PCS).
The PCS variants controls things like the size of the C standard types, whether code can make use of a hardware floating-point unit, and whether floating -point arguments are passed in integer registers or floating-point registers. The same PCS options must apply to the entire project, so the PCS options used to build the user library must match the PCS options of the final link.
In practice, this means checking that options match between the library and the final project, including:
Something to keep in mind, is that (at time of writing) ATfE is at beta stage and has a restricted set of library variants, which are all built with -fno-short-wchar -fno-short-enums. Linking against libraries built with -fshort-wchar -fshort-enums will succeed, but (for the current beta release of ATfE) may give data corruption and possible crashes if data is passed across the library interface.
This is where it becomes important that a toolchain is more than a compiler. All elements of a toolchain are developed together, and they all work together as a single unit to deliver benefits to the user. In particular, the toolchain libraries are likely to be optimized to deliver advantages in areas like performance and code size. As part of this optimization the toolchain libraries may contain non-standard function implementations, or additional “helper” functions. The compiler and linker understand the functionality available in the toolchain libraries, and work together with the libraries to create an optimized image. We can say that there is collusion between the compiler, linker, and library components of the toolchain.
This collusion breaks down if the toolchain library is different between the user library build and the final link. This is because the toolchain library is linked during the final link, not when the user library is created. The expectations that the compiler had when building the user library may not be realized, the final link may include incompatible function implementations, expected helper function implementations may not be available. This creates an additional binary compatibility concern: the same toolchain library must be used throughout the build.
To restore binary compatibility between toolchains, it is necessary to prevent collusion between the compiler and C library. This can be done by preventing the compiler from relying on library functionality that is outside the standard ABI. Example options are:
The output of different toolchains should then be binary compatible, provided that both the toolchains claim support for the ABI. However, there is a clear disadvantage: the purpose of compiler-library collusion is to better optimize code, resulting in faster or smaller code. If collusion is prevented, that optimization effect is lost.
Binary compatibility between toolchains is complex and can be difficult. It should be possible to gain binary compatibility for C code by invoking strict conformance to the ABI, but this is likely to have trade-offs in areas like performance and code size. Often, the best option is to build all code in a project using the same toolchain.
Binary compatibility, binary interfaces, and procedure call standards form a large and complex subject area that this blog barely scratches the surface of. There’s a lot more information on the Arm Developer ABI landing page.
I know I said I would restrict this blog to C, but I do have a few words about C++. C++ creates additional challenges for binary compatibility, making C++ interoperability between toolchains extremely difficult. If you must provide interoperability for C++ code, it is recommended to provide an extern “C” public interface to the interoperable components.
Use of the C++ Standard Template Library (STL) remains difficult and would require inclusion and final linking with the same STL across the entire build, in practice making it unsuitable for use in many cases. It’s possible that simple C++ code might interoperate between toolchains successfully, but even more so than with C code the safest option is to build all code in a project using the same toolchain.