What Is Cross Compilation?

An in-depth look at the concepts and importance of cross compilation, along with its four main stages. Explore common cross-compilation toolchains and target triplets to facilitate multi-platform software development and deployment.

What is Cross Compilation?

Preface

This article primarily focuses on C/C++ cross compilation. For cross compilation in other languages, please refer to relevant literature.

Cross compilation is the process of generating executable code for a specific platform (Operating System + CPU Architecture) on a different platform. This stands in contrast to native compilation, where the compilation occurs directly on the target platform.

  • Windows x86_64 → macOS x86_64: Different OS, same architecture → Cross compilation.
  • Windows x86_64 → Linux ARM: Different OS + different architecture → Cross compilation.
  • Windows x86_64 → Windows x86_64: Same OS, same architecture → Not cross compilation.

Why is Cross Compilation Needed?

Cross compilation is crucial for several primary reasons:

  1. Limited Target Platform Resources
    Embedded devices and IoT devices often have limited resources and cannot host a full compilation environment.
  2. Improved Development Efficiency
    Compiling directly on the target platform can be time-consuming. Cross compilation allows leveraging more powerful development machines to accelerate the build process.
  3. Multi-platform Support
    Modern software often needs to support multiple hardware platforms and operating systems. Cross compilation enables building software for all target platforms from a single environment.
  4. Inaccessible Target Environment
    In some cases, the target system may not support installing a toolchain, or may be completely inaccessible (e.g., proprietary hardware systems). Cross compilation is the only option in these scenarios.

Brief Description of the Four Compilation Processes

Whether native or cross compilation, converting source code to an executable program typically follows four standard stages. Understanding these stages helps in troubleshooting cross-compilation issues (such as missing headers or mismatched library architectures):

  1. Preprocessing
    Handles directives like #include and #define, expands macros, and merges files.
  2. Compilation
    Performs lexical, syntactic, and semantic analysis, converting preprocessed code into assembly code (or intermediate code).
  3. Assembly
    Converts assembly code into machine code, generating object files (.o). This step is highly architecture-dependent.
  4. Linking
    Combines multiple object files and libraries, resolves symbol references, and generates the final executable file.

Modern Compiler Optimization: Clang/LLVM Integrated Assembler

In traditional compilation workflows (like early GCC), the compiler converts code into text-based assembly files (.s), then invokes an external assembler (as) to read that file and parse it into machine code. This “generate text -> parse text” process involves significant I/O and string processing, resulting in lower efficiency.

Clang/LLVM introduced the Integrated Assembler technology. Utilizing the MC (Machine Code) Layer architecture, it completes the conversion from intermediate code to binary machine code directly in memory, eliminating the overhead of text processing.

LLVM Backend Data Flow Analysis

In LLVM, cross compilation is not implemented via “different assemblers”, but through a unified intermediate representation (LLVM IR) and pluggable backends. The backend’s responsibility is to progressively “concretize” platform-independent IR into target architecture machine code.

The data transformation flow in the LLVM backend can be summarized as the continuous “lowering” and concretization of data:

    graph TD
	    A[LLVM IR] -->|Instruction Selection| B[MachineInstr MI]
	    B -->|Register Allocation| C[MachineInstr MI<br>Physical Registers]
	    C -->|AsmPrinter| D[MCInst]
	    D -->|MCStreamer| E{Fork Point}
	    E -->|Path A: MCObjectStreamer| F[Binary Machine Code .o]
	    E -->|Path B: MCAsmStreamer| G[Assembly Text .s]
	    
	    style F fill:#e1f5fe,stroke:#01579b
	    style G fill:#fff3e0,stroke:#ff6f00
  1. LLVM IR (Intermediate Code)
    Platform-independent intermediate representation.
  2. MachineInstr (MI)
    The product of Instruction Selection. It is an in-memory C++ object graph representing target machine instructions (like MOV, ADD), but still containing virtual registers and pseudo-instructions.
  3. MCInst (Key Transformation)
    After Register Allocation, the AsmPrinter Pass converts high-level MachineInstr into lower-level MCInst.
    • MCInst is an extremely lightweight structure containing only Opcodes and Operands.
    • It is the “binary object form” of assembly instructions in memory, not a text form.
  4. MCStreamer (Output Divergence)
    At the MCInst layer, LLVM selects different processing streams based on output requirements:
    • Generate Machine Code (.o): Takes the MCObjectStreamer path. It directly maps Opcodes/Operands to binary encoding (e.g., 0101...) via lookup tables and writes to the file. The entire process involves no string operations.
    • Generate Assembly (.s): Takes the MCAsmStreamer path. It formats MCInst into human-readable strings (e.g., mov x0, #1).

Summary: Traditional vs. Modern

FeatureTraditional Compiler (Early GCC)Modern LLVM (Integrated Assembler)
WorkflowCompiler -> .s Text -> External Assembler -> .oStructured Memory Data -> Direct Binary Output
Intermediate StepInvolves heavy text generation and parsingPure in-memory object conversion (MachineInstr -> MCInst)
EfficiencyLower (I/O intensive)Extremely High (CPU intensive, no redundant I/O)

Introduction to Cross Compilation Tools

1. Triplets

Before using cross-compilation tools, it is essential to understand the concept of triplets. A triplet (sometimes called a target triplet) is a standardized way to describe the compilation target platform.

Composition of a Triplet

The standard format is: <Architecture>-<Vendor>-<OS>-<Environment>

  • Architecture: CPU architecture, such as x86_64, arm, aarch64, mips, riscv, etc.
  • Vendor: Toolchain provider or hardware manufacturer, such as apple, nvidia. Often unknown, none, or omitted to indicate generic.
  • OS: Target operating system, such as linux, darwin (macOS), windows. Sometimes omitted to indicate bare metal.
  • Environment: Runtime environment or ABI, such as gnu, musl, android, eabi, etc.

Common Triplet Examples

  • x86_64-linux-gnu: 64-bit x86 architecture, Linux system, GNU environment.
  • arm-none-eabi: ARM architecture, no operating system (bare metal), embedded ABI.
  • aarch64-apple-darwin: 64-bit ARM architecture, Apple Inc., macOS system.
  • i686-w64-mingw32: 32-bit x86 architecture, Windows 64-bit support.

Applications of Triplets

  • Tool Naming: Cross-compilation tools are typically prefixed with the triplet, such as arm-linux-gnueabihf-gcc, as well as in toolchain file naming.
  • Build System Configuration: Used in CMake with -DCMAKE_TOOLCHAIN_FILE to specify the target platform, and in Autotools with --host and --target parameters.
  • Compilation Flags: clang/clang++ uses --target to specify the compilation target.

Checking System Triplet

# GCC default target
gcc -dumpmachine

# Clang default target
clang -dumpmachine

In practical engineering, determining if two toolchains are “compatible” often requires matching not just the architecture, but at least Architecture + OS + ABI (Environment). Otherwise, undiagnosable errors are likely to occur during the linking stage.

2. Cross Compilers

Currently, the two main categories of cross compilers used in the C/C++ domain are the GNU Compiler Collection (GCC) and Clang/LLVM.

GNU Compiler Collection (GCC)

The GNU Compiler Collection is one of the most widely used cross-compilation tools:

  • Naming Convention: Typically follows the format of Triplet + Toolchain Name.
  • Common Examples:
    • arm-linux-gnueabihf-gcc: C cross compiler for ARM architecture, Linux system, with hardware floating-point support.
    • aarch64-linux-gnu-gcc: C cross compiler for 64-bit ARM architecture, Linux system.
    • x86_64-w64-mingw32-gcc: C cross compiler targeting Windows 64-bit.

Clang/LLVM

The LLVM project provides powerful cross-platform compilation capabilities:

  • Uses the -target parameter to specify the target platform.
  • Modular design with separation of frontend and backend.
  • Example: clang --target=arm-linux-gnueabihf -o output source.c
Core Advantage of LLVM Cross Compilation

Modular Design: Separation of Frontend and Backend, Supporting Multiple Languages and Target Platforms

LLVM’s architectural design completely decouples the frontend (like Clang) from the backend (code generation). The frontend focuses on processing different programming languages (C/C++, Rust, Swift, etc.) and generating unified LLVM IR, while the backend focuses on converting IR into target machine code. This design means that as long as a backend supports a certain architecture, all frontend languages automatically gain cross-compilation capabilities for that architecture. This is due to the LLVM backend’s data-driven design philosophy—it abstracts instruction sets, registers, and scheduling models into structured data via Target Descriptions, allowing cross-architecture code generation to reuse unified generic algorithms rather than relying on scattered target-specific logic. Therefore, in LLVM, cross compilation is an inherent capability.

Tip

However, this does not mean all languages using LLVM can seamlessly cross-compile, as different languages’ runtimes and standard libraries have different dependencies and requirements. To achieve execution, one must still ensure the target platform has the corresponding runtime and library support. Even so, compared to traditional compilers, LLVM’s modular design significantly simplifies cross-language and cross-platform cross-compilation work.

3. Cross Compilation Toolchains

A complete cross-compilation environment typically includes:

  • Compiler: gcc, g++, clang, clang++, etc.
  • Binary Tools: Assembler, linker, etc.
  • C Standard Library: glibc, musl, newlib, etc.
  • Debug Tools: gdb, lldb, etc.
  • Build Systems: cmake, autotools, make, ninja, etc.

In many Linux distributions, even when using Clang as the frontend, GCC-provided glibc and binutils are often still used during the linking stage. Therefore, using LLVM is not equivalent to completely breaking away from the GNU toolchain ecosystem. In recent years, the LLVM project has significantly improved its support for cross compilation, offering its own libc++ standard library and lld linker.

4. Obtaining Common Cross Compilation Toolchains

  • Package Manager Installation:

    sudo apt install gcc-arm-linux-gnueabihf
    
  • Cross Compilation Toolchain Generators:

    • Crosstool-NG: Flexible configuration for custom toolchains.
  • Vendor-Provided Toolchains:

    • ARM provided GNU Toolchain.
    • Chip vendor (e.g., TI, NXP, Intel) provided dedicated toolchains.

Summary

Although cross compilation sounds complex, its core principle is not mysterious: it simply performs the preprocessing, compilation, assembly, and linking processes for one platform on a different platform.