Targets#
This page describes each machine EmbCC generates code for: the target triples it accepts, its machine-specific options, its data model, the calling convention EmbCC implements for it, the object format, the predefined macros that identify it, the runtime library it needs, and what it does not support, with the diagnostic EmbCC gives. It is for people building code for a particular target and for people checking that EmbCC's objects will link with another compiler's. General options are in Invoking EmbCC; inline assembly per target is in Inline assembly; bare-metal startup, linking and interrupt handlers are in Embedded programming.
Summary#
One embcc process compiles for one target. Every target is
little-endian but the big-endian MIPS ones, mips-none-elf and
mips64-none-elf.
| Family | Canonical triples | Object format | Calling convention | Linked by |
|---|---|---|---|---|
| x86-64 | x86_64-elf, x86_64-emblink, x86_64-linux-gnu |
ELF64 | System V AMD64 | embcc (integrated embld) |
| x86-64 macOS | x86_64-apple-darwin |
Mach-O | System V AMD64 | the system linker |
| x86-64 Windows | x86_64-windows-gnu |
COFF | Microsoft x64, incomplete | an external linker |
| AArch64 | aarch64-elf, aarch64-emblink, aarch64-linux-gnu |
ELF64 | AAPCS64 | an external linker |
| Apple arm64 | aarch64-apple-darwin |
Mach-O | Apple arm64 | the system linker |
| ARM Cortex-M | thumbv6m-none-eabi, thumbv8m.base-none-eabi, thumbv7m-none-eabi, thumbv7em-none-eabi, thumbv7em-none-eabihf, thumbv8m.main-none-eabi, thumbv8m.main-none-eabihf |
ELF32 | AAPCS32, AAPCS-VFP | embld |
| ARMv7-A | armv7a-none-eabi, armv7a-none-eabihf |
ELF32 | AAPCS, AAPCS-VFP | embld |
| RISC-V | riscv32-unknown-elf, riscv64-unknown-elf |
ELF32, ELF64 | RISC-V psABI, ilp32/lp64 and the F and D conventions (--abi=) |
embld |
| AVR | avr |
ELF32 | avr-gcc | embld |
| MIPS32 | mipsel-none-elf |
ELF32 | o32, soft float | embld |
| MIPS64 | mips64el-none-elf, mips64-none-elf |
ELF64 | n64, soft float | embld |
| LoongArch64 | loongarch64-unknown-elf |
ELF64 | LoongArch psABI, LP64S (soft float) | embld |
| TriCore | tricore-none-elf |
ELF32 | TriCore EABI, soft float | embld |
| Xtensa | xtensa-none-elf |
ELF32 | windowed, soft float | embld |
| Renesas RX | rx-none-elf |
ELF32 | GCC rx-elf, 32-bit doubles, no FPU | embld |
| SPARC | sparc-none-elf |
ELF32, big-endian | SPARC V8 (register windows), soft float | embld |
| ColdFire | m68k-none-elf |
ELF32, big-endian | m68k SVR4 (GCC m68k-elf), soft float | embld |
| Target | Status | Floating point | -g |
Lock-free atomic read-modify-write | __thread |
C++ |
|---|---|---|---|---|---|---|
| x86-64 ELF, EmbLinkOS, Linux | Primary target | SSE2; x87 for long double |
DWARF | 1, 2, 4, 8, 16 bytes | Local-exec TLS | Yes, with exceptions |
| x86-64 macOS | Objects for the system linker | SSE2; x87 for long double |
Refused | 1, 2, 4, 8, 16 bytes | Refused | Yes, with exceptions |
| x86-64 Windows | Objects only; warns on every compile | SSE2 | Refused | 1, 2, 4, 8, 16 bytes | Refused | Refused |
| AArch64 ELF, EmbLinkOS, Linux | Supported | FP/SIMD; long double in software |
DWARF | 1, 2, 4, 8, 16 bytes | Local-exec TLS | Yes, with exceptions |
| Apple arm64 | Objects for the system linker | FP/SIMD | Refused | 1, 2, 4, 8, 16 bytes | Refused | Yes, with exceptions |
| Cortex-M, soft float | Bare metal | Software | DWARF | 1, 2, 4 bytes | One shared instance | Without exceptions |
| Cortex-M, FPU | Bare metal | Single-precision VFP; double in software |
DWARF | 1, 2, 4 bytes | One shared instance | Without exceptions |
| ARMv7-A (A32), soft float | Bare metal | Software | DWARF | 1, 2, 4 bytes | One shared instance | Without exceptions |
| ARMv7-A (A32), VFP | Bare metal | VFPv3/VFPv4, single and double | DWARF | 1, 2, 4 bytes | One shared instance | Without exceptions |
| RV32 | Bare metal | Software | DWARF | 1, 2, 4 bytes | One shared instance | Without exceptions |
| RV64 | Bare metal | Software | DWARF | 1, 2, 4, 8 bytes | One shared instance | Without exceptions |
| AVR (ATmega328P) | Bare metal | Software, 4-byte double |
DWARF, 4-byte addresses | 1, 2, 4, 8 bytes (interrupts masked) | One shared instance | Refused |
| MIPS32r2 (PIC32-class) | Bare metal | Software | DWARF | 4 bytes | One shared instance | Without exceptions |
| MIPS64r2 | Bare metal | Software | DWARF | 4, 8 bytes | One shared instance | Without exceptions (little-endian untested) |
| LoongArch64 | Bare metal | Software | DWARF | 1, 2, 4, 8 bytes | One shared instance | Without exceptions |
| TriCore 1.6.1 (AURIX) | Bare metal | Software | DWARF | 4 bytes | One shared instance | Without exceptions |
| Xtensa (ESP32, ESP32-S3) | Bare metal | Software | DWARF | 4 bytes | One shared instance | Without exceptions |
| Renesas RX (RXv1) | Bare metal | Software, 4-byte double |
DWARF | 1, 2, 4 bytes (interrupts masked) | One shared instance | Without exceptions |
| SPARC V8 (LEON3) | Bare metal | Software; long double binary128 |
DWARF | 4 bytes | One shared instance | Without exceptions |
| ColdFire ISA_A (MCF5208-class) | Bare metal | Software | DWARF | 1, 2, 4 bytes (interrupts masked; supervisor mode) | One shared instance | Without exceptions |
"One shared instance" means the object is placed in .tbss but
addressed as an ordinary static object: there is one copy, not one per
thread. "Refused" under C++ means a C++ unit is not compiled for that
target (--syntax-only still checks it); the diagnostic is in the
target's Limitations. "Without exceptions" means C++ is compiled with
--no-exceptions; see C++. With exceptions on, the
32-bit embedded targets and big-endian MIPS64 refuse the unit by name;
on RV64, LoongArch and little-endian MIPS64 code that needs a landing
pad (a try block, or a destructor that must run during unwinding) does
not compile.
Selecting a target#
--target=TRIPLE#
Compile for TRIPLE. The option may appear anywhere on the command line;
it is applied before every other option, so --version, --print-target
and --dump-predef describe the target that was asked for.
Each triple accepted, and the canonical name it is reported as, is listed in the section for its family. An unknown triple is an error, followed by the full list:
embcc: error: unknown target 'armv6m-none-eabi'
embcc: the targets it emits for are:
embcc: x86_64-elf
embcc: x86_64
...
With no --target=, EmbCC compiles for x86_64-elf, unless the compiler
was built with make DEFAULT_TARGET=TRIPLE or the environment sets
EMBCC_DEFAULT_TARGET.
EMBCC_DEFAULT_TARGET#
An environment variable naming the default target. It takes precedence
over a default compiled in with DEFAULT_TARGET, and --target=
overrides both. An invalid value is an error on every invocation:
embcc: error: the default target 'bogus' is not one EmbCC knows
embcc: it came from EMBCC_DEFAULT_TARGET in the environment
--print-target#
GCC: -dumpmachine.
Print the canonical triple of the selected target and exit. It is answered after all other options are applied, so the ARM float options are reflected in it:
embcc --target=thumbv7em-none-eabi --fpu=fpv4-sp-d16 --float-abi=hard --print-target
# thumbv7em-none-eabihf
--dump-predef#
Print the target's predefined macros, one #define per line, and exit.
The list reflects --target=, --cpu=, --fpu= and --float-abi=. It
does not include the macros the preprocessor defines for every target
(__EMBCC__, __STDC__, __STDC_VERSION__, __STDC_HOSTED__). EmbCC
does not define __GNUC__ on any target.
Data models#
Size and alignment in bytes, written size/alignment. "x86-64" covers
x86_64-elf, x86_64-emblink and x86_64-linux-gnu; "AArch64" covers
aarch64-elf, aarch64-emblink and aarch64-linux-gnu; "Cortex-M"
covers every thumb* triple.
| Type | x86-64 | macOS x86-64 | Windows | AArch64 | Apple arm64 | Cortex-M | RV32 | RV64 | AVR | MIPS32 | LoongArch64 | TriCore | Xtensa | PowerPC | RX | SPARC | ColdFire |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
plain char |
signed | signed | signed | unsigned | signed | unsigned | unsigned | unsigned | signed | signed | signed | signed | unsigned | unsigned | unsigned | signed | signed |
short |
2/2 | 2/2 | 2/2 | 2/2 | 2/2 | 2/2 | 2/2 | 2/2 | 2/1 | 2/2 | 2/2 | 2/2 | 2/2 | 2/2 | 2/2 | 2/2 | 2/2 |
int |
4/4 | 4/4 | 4/4 | 4/4 | 4/4 | 4/4 | 4/4 | 4/4 | 2/1 | 4/4 | 4/4 | 4/4 | 4/4 | 4/4 | 4/4 | 4/4 | 4/2 |
long |
8/8 | 8/8 | 8/8 | 8/8 | 8/8 | 4/4 | 4/4 | 8/8 | 4/1 | 4/4 | 8/8 | 4/4 | 4/4 | 4/4 | 4/4 | 4/4 | 4/2 |
long long |
8/8 | 8/8 | 8/8 | 8/8 | 8/8 | 8/8 | 8/8 | 8/8 | 8/1 | 8/8 | 8/8 | 8/4 | 8/8 | 8/8 | 8/4 | 8/8 | 8/2 |
pointer, size_t |
8/8 | 8/8 | 8/8 | 8/8 | 8/8 | 4/4 | 4/4 | 8/8 | 2/1 | 4/4 | 8/8 | 4/4 | 4/4 | 4/4 | 4/4 long |
4/4 | 4/2 |
float |
4/4 | 4/4 | 4/4 | 4/4 | 4/4 | 4/4 | 4/4 | 4/4 | 4/1 | 4/4 | 4/4 | 4/4 | 4/4 | 4/4 | 4/4 | 4/4 | 4/2 |
double |
8/8 | 8/8 | 8/8 | 8/8 | 8/8 | 8/8 | 8/8 | 8/8 | 4/1 | 8/8 | 8/8 | 8/4 | 8/8 | 8/8 | 4/4 | 8/8 | 8/2 |
long double |
16/16 x87 | 16/16 x87 | 16/16 x87 | 16/16 binary128 | 8/8 binary64 | 8/8 binary64 | 16/16 binary128 | 16/16 binary128 | 4/1 binary32 | 8/8 binary64 | 16/16 binary128 | 8/4 binary64 | 8/8 binary64 | 8/8 binary64 | 4/4 binary32 | 16/8 binary128 | 8/2 binary64 |
wchar_t |
4/4 int |
4/4 int |
4/4 int |
4/4 unsigned int |
4/4 int |
4/4 unsigned int |
4/4 int |
4/4 int |
2/1 int |
4/4 int |
4/4 int |
4/4 int |
2/2 unsigned short |
4/4 int |
4/4 long |
4/4 int |
4/2 int |
__int128 |
16/16 | 16/16 | 16/16 | 16/16 | 16/16 | — | — | 16/16 | — | — | 16/16 | — | — | — | — | — | — |
enum (all values fit int) |
4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 2 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 |
__BIGGEST_ALIGNMENT__ |
16 | 16 | 16 | 16 | 16 | 8 | 16 | 16 | 1 | 8 | 16 | 8 | 16 | 16 | 4 | 8 | 2 |
| Stack alignment at a call | 16 | 16 | 16 | 16 | 16 | 8 | 16 | 16 | 1 | 8 | 16 | 8 | 16 | 16 | 4 | 8 | 4 |
Notes on the table:
_Boolis one byte everywhere.- An enumeration without a fixed underlying type is
intwhile every value fitsint. Otherwise it takes, and its enumerators take, the first of these that holds every value:unsigned int(when no value is negative),long,unsigned long(whenlongis 8 bytes and no value is negative), andlong long, which isunsigned long longwhen no value is negative.enum { G = 0x100000005 }is 8 bytes on every target. Clang chooses only among the unsigned types when no value is negative, so a non-negative enumeration that does not fitunsigned intcan differ from Clang's: on the targets wherelongis 8 bytes it islongwhere Clang's isunsigned long, of the same size; on AVR one whose largest value lies between 2^16 and 2^31 - 1 islongwhere Clang's isunsigned long, and one whose largest value lies between 2^31 and 2^32 - 1 is an 8-byteunsigned long longwhere Clang's is a 4-byteunsigned long. A fixed underlying type (enum e : unsigned char) is used as written.--enums=shortis refused. - Plain
char's signedness can be changed with--char=signedand--char=unsigned(see Invoking EmbCC); the__CHAR_UNSIGNED__macro follows the option. - "—" means the type does not exist on that target. Using it is an
error:
__int128 does not exist on this target (it needs 64-bit registers; use long long). - On RV32 and RV64,
long doublehas the size and the IEEE binary128 format the psABI specifies, and constant expressions involving it are folded exactly, but no operation on along doublevalue compiles:the RV32 backend cannot lower a 128-bit value yet. RV64's__int128is the same: it can be declared and measured withsizeof, and every operation on one is refused with the RV64 form of that message. - On LoongArch64
long doubleand__int128are computed: binary128 arithmetic and the 128-bit divides and variable shifts call lib/rt. - On TriCore and RX nothing is aligned beyond 4 bytes. On RX
doubleandlong doubleare binary32 (GCC's-m32bit-doubles), andsize_t,ptrdiff_tandwchar_tarelongtypes. SPARC's binary128long doubleis 8-aligned. Xtensa'swchar_tis a 16-bitunsigned short. On every target,--wchar=shortmakeswchar_tanunsigned short(see Invoking). - MIPS64 has LP64's sizes in the RV64 column:
longand pointers 8/8,long double16/16 binary128,__int12816/16, a signed plaincharand anintwchar_t,__BIGGEST_ALIGNMENT__and the stack 16 (see MIPS64).long doubleand__int128are computed, binary128 and the 128-bit divides through lib/rt. - On AVR every type has alignment 1, so
struct { char c; int i; }is three bytes. - Windows uses the LP64 model here, which is not Microsoft's; see Windows.
max_align_tis declared by<stddef.h>in C++ only.
Options common to all targets#
These x86 spellings are accepted on every target so that existing build systems work. Only the first group changes anything, and only on x86-64 and AArch64.
| Option | Effect |
|---|---|
--no-sse, -mno-sse2, --general-regs-only |
On x86-64: never use an SSE register. On AArch64: never use an FP/SIMD register. On every other target: no effect. See the x86-64 and AArch64 sections. |
-mno-mmx, -mno-80387 |
Accepted; no effect. EmbCC emits no MMX code, and these do not imply --no-sse. |
--no-red-zone |
Accepted; no effect. EmbCC does not place data below the stack pointer. |
--code-model=MODEL |
Accepted with any value; no effect. Each target uses the single code model described in its section. |
Any other -m option not listed in a target's section is an error:
embcc: error: unknown argument '-mfoo'. In particular -m32 and -m64
are not accepted, and --mcu= is refused on every target but
AVR. --cpu=, --isa= and --abi= take the values each target's
section lists, and are refused on a target whose section does not list
them.
x86-64#
Triples#
| Triple | Accepted aliases | Operating system | Object format |
|---|---|---|---|
x86_64-elf |
x86_64, x86_64-none-elf |
none (bare metal, the EmbLinkOS kernel) | ELF64 |
x86_64-emblink |
EmbLinkOS user programs | ELF64 | |
x86_64-linux-gnu |
x86_64-linux |
Linux, static executables | ELF64 |
x86_64-apple-darwin |
x86_64-darwin |
macOS | Mach-O |
x86_64-windows-gnu |
x86_64-w64-mingw32 |
Windows (MinGW) | COFF |
Options#
--no-sse, --general-regs-only#
GCC: -mno-sse, -mno-sse2, -mgeneral-regs-only.
Generate no SSE instructions: no XMM register is saved by a variadic
prologue, and block copies and structure moves use general registers.
This is what a kernel needs before it enables CR4.OSFXSR. Any
floating-point operation, long double included, is then an error:
embcc: f.c:3: error: floating point needs SSE, which -mno-sse forbids
The option does not change the predefined macros: __SSE__ and
__SSE2__ remain defined.
--code-model=MODEL, --no-red-zone#
GCC: -mcmodel=MODEL, -mno-red-zone, -mno-mmx, -mno-80387.
Accepted and ignored. The generated code is position-dependent and
addresses data and calls RIP-relatively (calls carry
R_X86_64_PLT32, data references R_X86_64_PC32), which also suits a
kernel linked in the top 2 GiB (--code-model=kernel). The red zone is never
used. Position-independent code is not available: -fPIC, -fpic,
-fPIE and -fpie are refused with embcc: error: -fPIC is not supported; EmbCC would emit ordinary code and the flag's promise would not hold, and -shared and -static-pie with embcc: error: -shared needs position-independent code, which EmbCC does not emit.
Calling convention: System V AMD64#
x86_64-elf, x86_64-emblink, x86_64-linux-gnu and
x86_64-apple-darwin use the System V AMD64 psABI.
- Integer and pointer arguments go in
rdi,rsi,rdx,rcx,r8,r9;floatanddoubleinxmm0–xmm7; the rest on the stack in eight-byte slots. Results come back inrax(andrdx),xmm0(andxmm1). long doubleis class X87: it is passed in memory and returned inst(0).__int128is passed in two general registers and returned inrax:rdx.- An aggregate of up to 16 bytes is classified eightbyte by eightbyte as
INTEGER or SSE and passed in registers. An eightbyte that holds only
padding takes no register. An aggregate larger than 16 bytes, one that
contains a
long double, or a packed one with a member at an offset its type's alignment does not divide is class MEMORY and passed on the stack. If an aggregate's eightbytes do not all find a free register of their class, the whole aggregate goes on the stack and the remaining registers stay available to later arguments. - A MEMORY-class result is written through a hidden pointer passed in
rdiand returned inrax. - A call to a variadic function sets
alto the number of vector registers used.va_listis the 24-byte__va_list_tagrecord, andva_argof a structure fetches it as the ABI places it. - The stack is 16-byte aligned at every call.
rbx,rbpandr12–r15are callee-saved. - An unnamed bit-field does not affect a structure's alignment.
Operating systems and object formats#
Bare metal and EmbLinkOS#
x86_64-elf and x86_64-emblink produce ELF64 relocatable objects.
They differ only in the predefined macros. An EmbLinkOS executable is
produced at link time: embld --embx turns the linked ELF image into an
EMBX image (see embld).
Linux#
x86_64-linux-gnu produces ELF64 objects for a static executable linked
against EmbCC's own C library (lib/libc/os/linux), which issues system
calls directly; there is no glibc and no dynamic loader. embcc file.c
without -c links the program with the integrated linker, adding
crt1.o, libc.a and librt.a from the target's library directory.
If they are not built (make libc-linux-x86_64):
embcc: error: no crt1.o for x86_64-linux-gnu: its C library is not built; `make libc-linux-x86_64` in /path/to/EmbCC builds it
embcc: or --libc=system compiles and links against the system's own C library, glibc
embcc: --print-search-dirs says where it looked
With --libc=system the program is the system's instead: compiled
against glibc's headers and linked dynamically against its libc.so.6
(and libpthread for -pthread) by the system's linker, as GCC links
one, for x86_64-linux-gnu and aarch64-linux-gnu alike. See
The C library.
macOS (Mach-O)#
x86_64-apple-darwin produces Mach-O objects for the system linker. C
symbol names carry a leading underscore in the object; the predefined
__USER_LABEL_PREFIX__ is nevertheless empty. The data model and calling
convention are those of x86_64-elf. The following are refused for both
Darwin triples, each with its own diagnostic:
| Construct | Diagnostic |
|---|---|
-g |
Warning, and the object is compiled without debug information: -g: no debug information for aarch64-apple-darwin yet; d.c is compiled without it |
__thread |
__thread is not supported for a Darwin target yet: Mach-O addresses a thread-local through a __thread_vars descriptor, which this writer does not emit |
__attribute__((constructor)), destructor |
__attribute__((constructor)) is not supported for a Darwin target yet: it needs a __DATA,__mod_init_func section this Mach-O writer does not emit |
file-scope __asm__ with labels or symbols |
a file-scope asm block with labels or symbol references is not supported for a Darwin target yet: its bytes would be emitted but its symbols and relocations dropped |
__attribute__((alias)) |
alias attribute on 'g' is not supported for Mach-O output |
__attribute__((section)) on a function |
a function's section attribute is not supported for Mach-O output |
Windows (COFF)#
x86_64-windows-gnu produces AMD64 COFF objects. No program built this
way has been run on Windows, and EmbCC provides no C library for it.
Every compile prints a warning, which is on by default and can be
silenced with --no-warn=windows-abi:
embcc: f.c: warning: x86_64-windows-gnu is not yet the Microsoft x64 ABI: `long` is 8 bytes and wchar_t 4 (Windows has 4 and 2), and rsi, rdi, xmm6 and xmm7 are not preserved across a call: objects EmbCC compiles agree with each other and with nothing else [--warn=windows-abi]
What EmbCC does implement of the Microsoft x64 convention:
- The first four arguments travel by position: slot n uses the nth
of
rcx,rdx,r8,r9for an integer andxmm0–xmm3for a floating-point value. The caller reserves 32 bytes of shadow space. - A structure of exactly 1, 2, 4 or 8 bytes is passed in its slot; any other structure is passed by reference to a copy the caller makes.
- A variadic
doubleamong the first four arguments is also copied into the integer register of its slot.va_listis achar *walking eight-byte slots; a variadic function storesrcx–r9into the home area, andva_copyis an assignment.
What is not Microsoft's, beyond the points the warning names, and is refused:
| Construct | Diagnostic |
|---|---|
long double in a function's signature |
long double in the signature of 'f' is not supported for a Windows target yet: there it travels by reference and returns through a hidden pointer, and EmbCC passes it on the stack by value |
__int128 in a function's signature |
__int128 in the signature of 'f' is not supported for a Windows target yet: EmbCC lowers its arithmetic to libgcc helpers whose arguments it places in the System V registers |
va_arg of a structure |
va_arg of a struct is not supported for a Windows target yet |
va_arg of long double or __int128 |
va_arg of long double is not supported for a Windows target yet: there it travels by reference |
-g |
-g is not supported for a Windows target yet: its debug information goes in CodeView records this does not write, and emitting DWARF under a COFF name would be worse than refusing |
__thread |
__thread is not supported for a Windows target yet: Windows reaches a thread-local through a _tls_index and a TLS directory this writer does not emit |
__attribute__((constructor)), destructor |
__attribute__((constructor)) is not supported for a Windows target yet: it needs the .ctors/.dtors sections this COFF writer does not emit |
file-scope __asm__ with labels or symbols |
a file-scope asm block with labels or symbol references is not supported for a Windows target yet |
| any C++ translation unit | C++ exceptions are not supported for a Windows target yet: the unwind tables go in .pdata and .xdata and neither is written |
C++ is refused even with --no-exceptions, because C++ always gets
unwind tables.
Predefined macros#
The architecture macros are those of x86_64-elf-gcc: __x86_64__,
__x86_64, __amd64__, __amd64, __LP64__, _LP64, __SSE__,
__SSE2__, __SSE_MATH__, __SSE2_MATH__, __MMX__, __FXSR__,
__SIZEOF_INT128__ (16), __SIZEOF_LONG_DOUBLE__ (16),
__BIGGEST_ALIGNMENT__ (16), __code_model_small__,
__GCC_ASM_FLAG_OUTPUTS__, and __ELF__ on the ELF triples. The
operating system adds:
| Triple | Added | Removed |
|---|---|---|
x86_64-emblink |
__emblink__, __emblink, __EmbLinkOS__ |
|
x86_64-linux-gnu |
__linux__, __linux, __gnu_linux__, __unix__, __unix |
|
x86_64-apple-darwin |
__APPLE__, __MACH__, __unix__, __unix |
__ELF__ |
x86_64-windows-gnu |
_WIN32, _WIN64, __MINGW32__, __MINGW64__ |
__ELF__ |
__GCC_ASM_FLAG_OUTPUTS__ is defined, but flag-output constraints such
as "=@ccc" are not implemented; see Inline assembly.
Runtime#
The compiler calls helpers named as in libgcc for operations the machine
has no instruction for: __int128 multiply, divide and shifts
(__multi3, __divti3, ...), complex multiply and divide (__muldc3,
...), and conversions between __int128 and floating point.
lib/rt implements them; on Linux it is built as librt.a by make libc-linux-x86_64 and linked automatically. On x86_64-elf, link
lib/rt or a libgcc. See Libraries.
Limitations#
embccwithout-clinks the ELF x86-64 triples, and the ARM, RISC-V and AVR firmware targets given a memory map (--link-scriptor-Wl,-Ttext/-Tdata). For AArch64 ELF, macOS and Windows it stops withembcc: error: cannot link for TRIPLE; compile with-cand link separately. See Linking.__threaduses the local-exec model only (R_X86_64_TPOFF32), which is correct in a statically linked executable.- A local aligned beyond 16 bytes, scalar or aggregate, is supported: its storage is carved from the stack at function entry and rounded up, and a scalar is read and written there.
AArch64#
Triples#
| Triple | Accepted aliases | Operating system | Object format |
|---|---|---|---|
aarch64-elf |
aarch64, arm64, aarch64-none-elf |
none (bare metal, the EmbLinkOS kernel) | ELF64 |
aarch64-emblink |
EmbLinkOS user programs | ELF64 | |
aarch64-linux-gnu |
aarch64-linux |
Linux, static executables | ELF64 |
aarch64-apple-darwin |
arm64-apple-darwin, aarch64-darwin |
macOS | Mach-O |
The instruction set is ARMv8-A. embld does not link AArch64 objects;
use an AArch64 ELF linker, or the system linker on macOS.
Options#
--general-regs-only, --no-sse#
GCC: -mgeneral-regs-only, -mno-sse, -mno-sse2.
Never touch a floating-point or SIMD register, as a kernel must before
CPACR_EL1.FPEN enables them. A variadic prologue then saves no q
registers and block copies use general registers. Any floating-point
operation is an error:
embcc: f.c:3: error: floating point used under -mgeneral-regs-only (in 'f')
Calling convention: AAPCS64#
aarch64-elf, aarch64-emblink and aarch64-linux-gnu follow the
Procedure Call Standard for the Arm 64-bit Architecture.
- Integer, pointer and small composite arguments go in
x0–x7;float,doubleandlong doubleinv0–v7. A homogeneous floating-point aggregate of up to four members goes in consecutivevregisters. Once a register file is exhausted, later arguments of that kind go on the stack; there is no back-filling. - A composite larger than 16 bytes is passed by reference to a copy the
caller makes. A composite whose natural alignment is 16 bytes, and an
__int128, start at an even-numberedxregister. The natural alignment is the largest of the members' alignments: analignedattribute on a member counts, packing lowers it, and analignedattribute on the structure itself does not. - Stack arguments occupy eight-byte slots, sixteen-byte aligned for sixteen-byte-aligned types.
- An indirect result is written through the address passed in
x8. va_listis the 32-byte AAPCS64 record; a variadic prologue savesx0–x7andq0–q7.va_argof a structure follows the same rules as argument passing.- The stack is 16-byte aligned. An unnamed bit-field affects a structure's alignment, as AAPCS64 requires.
long doubleis IEEE binary128 with no hardware support: arithmetic calls__addtf3,__multf3and the rest of that family.
Apple arm64#
aarch64-apple-darwin produces Mach-O objects and follows Apple's arm64
convention, which departs from AAPCS64 in these points:
- Plain
charis signed,wchar_tisint, andlong doubleis the same 8-byte format asdouble. The predefined macros follow (__CHAR_UNSIGNED__is not defined,__WCHAR_TYPE__isint,__SIZEOF_LONG_DOUBLE__is 8). - A named argument passed on the stack takes its own size and
alignment rather than an eight-byte slot. Homogeneous aggregates keep
their element alignment; other composites take whole doublewords, and
there an
aligned(16)on the structure does count. - Nothing is rounded to an even register: an
__int128, or a structure holding one, takes the next free pair. - Every variadic argument goes on the stack, in eight-byte slots.
va_listis achar *andva_copyis an assignment.va_argof a structure reads it from whole doublewords aligned as its type is. - An unnamed bit-field does not affect a structure's alignment, as on
x86-64:
struct { char a; int :0; char b; }is 5 bytes with alignment 1, where AAPCS64 makes it 8 bytes with alignment 4. - The address of a symbol not defined in the translation unit is loaded from the GOT.
The refusals listed under macOS apply.
Predefined macros#
The architecture macros are those of aarch64-elf-gcc, among them
__aarch64__, __AARCH64EL__, __ARM_64BIT_STATE, __ARM_ARCH (8),
__ARM_ARCH_8A, __ARM_ARCH_PROFILE (65, 'A'), __ARM_PCS_AAPCS64,
__ARM_NEON, __ARM_FP (14), __ARM_FEATURE_FMA,
__ARM_FEATURE_IDIV, __ARM_FEATURE_UNALIGNED, __CHAR_UNSIGNED__
(not on Darwin), __SIZEOF_INT128__ (16) and
__AARCH64_CMODEL_SMALL__. The operating-system macros are those of the
x86-64 triple with the same OS.
Runtime#
As for x86-64, plus the binary128 long double routines (softtf.c).
make libc-linux-aarch64 builds librt.a for aarch64-linux-gnu.
Limitations#
- Inline assembly has its own vocabulary; see Inline assembly.
__threaduses the local-exec model only.- A scalar local aligned beyond 16 bytes is refused, as on x86-64; an array or structure local so aligned is supported.
ARM Cortex-M#
The ARMv6-M profile in Thumb-1, ARMv8-M Baseline in Thumb-1 with the
32-bit instructions Baseline adds, and the ARMv7-M, ARMv7E-M and ARMv8-M
Mainline profiles in Thumb-2. Both ARMv8-M profiles support the Secure
side of TrustZone-M (--cmse). Every Cortex-M
target is freestanding.
Triples#
| Triple | Accepted aliases | Architecture | Float ABI | Cores |
|---|---|---|---|---|
thumbv6m-none-eabi |
thumbv6m, armv6m-none-eabi |
ARMv6-M | soft | Cortex-M0, M0+, M1 |
thumbv8m.base-none-eabi |
thumbv8m.base, armv8m.base-none-eabi |
ARMv8-M Baseline | soft | Cortex-M23 |
thumbv7m-none-eabi |
thumbv7m, armv7m-none-eabi, arm-none-eabi |
ARMv7-M | soft | Cortex-M3 |
thumbv7em-none-eabi |
thumbv7em, armv7em-none-eabi |
ARMv7E-M | soft | Cortex-M4, M7 |
thumbv7em-none-eabihf |
ARMv7E-M | hard, FPv4-SP-D16; FPv5-D16 with --cpu=cortex-m7 |
Cortex-M4F; Cortex-M7 | |
thumbv8m.main-none-eabi |
thumbv8m.main, thumbv8m-none-eabi, armv8m.main-none-eabi |
ARMv8-M Mainline | soft | Cortex-M33 |
thumbv8m.main-none-eabihf |
ARMv8-M Mainline | hard, FPv5-SP-D16 | Cortex-M33 with FPU |
arm-none-eabi means ARMv7-M; there is no target for the A- or R-profile
or for the ARM instruction set.
The architecture changes the object's build attributes, the predefined
macros and --print-target. The instructions generated for ARMv7-M and
ARMv7E-M are the same: the code generator does not use the DSP extension.
Where the part has it -- ARMv7E-M, and ARMv8-M Mainline with
--cpu=cortex-m33 or --isa=armv8-m.main+dsp -- its instructions are
accepted in inline asm and .s files and its macros are defined, which is
what CMSIS's cmsis_gcc.h and CMSIS-DSP select their SIMD code on
(Inline assembly, <arm_acle.h>).
thumbv8m.main-none-eabi alone has no DSP extension, as with clang.
Options#
--isa=thumb#
GCC: -mthumb.
Accepted; no effect. Thumb is the only instruction set.
--isa=arm#
GCC: -marm.
Refused: -marm is not supported: a Cortex-M has no ARM instruction set, only Thumb.
--cpu=CPU#
GCC: -mcpu=CPU.
Select the architecture variant by core. cortex-m0, cortex-m0plus
and cortex-m1 select ARMv6-M on any ARM triple, as
thumbv6m-none-eabi does, and cortex-m23 selects ARMv8-M Baseline, as
thumbv8m.base-none-eabi does. cortex-m3 selects ARMv7-M; cortex-m4,
cortex-m7 and cortex-m33 select ARMv7E-M. Otherwise the option does
not change the architecture level: --target=thumbv7m-none-eabi -mcpu=cortex-m33 is thumbv7em-none-eabi, and ARMv8-M is selected only
by a thumbv8m.main triple. On a thumbv6m triple, an ARMv7-M or
ARMv8-M part raises the level. Any other value is an error:
embcc: error: -mcpu=cortex-m55 is not a part EmbCC knows: it emits ARMv6-M (cortex-m0, m0plus, m1), ARMv8-M Baseline (cortex-m23), ARMv7-M and ARMv7E-M (cortex-m3, m4, m7) and ARMv8-M Mainline (cortex-m33)
--fpu=FPU#
GCC: -mfpu=FPU.
Name the floating-point unit. EmbCC knows three: fpv4-sp-d16, the
Cortex-M4F's unit, which requires ARMv7E-M (a thumbv7em triple, or
--cpu=cortex-m4, m7 or m33); fpv5-d16, the Cortex-M7's
double-precision unit, which requires ARMv7E-M and, when --cpu= is
given, --cpu=cortex-m7; and fpv5-sp-d16, the Cortex-M33's, which
requires a thumbv8m.main triple. none, soft and auto name no
unit. The FPU only takes effect with --float-abi=softfp or hard.
--cpu=cortex-m7 with thumbv7em-none-eabihf and no --fpu= means
fpv5-d16.
| Mistake | Diagnostic |
|---|---|
| any other unit | --fpu=fpv5-sp-d16 is not supported on thumbv7em-none-eabi: EmbCC emits VFP for the Cortex-M4F's unit (--fpu=fpv4-sp-d16) and the Cortex-M7's (--fpu=fpv5-d16) and nothing else: another unit's instruction set and attributes are unchecked here |
fpv5-d16 on ARMv8-M |
--fpu=fpv5-d16 is not supported on thumbv8m.main-none-eabi: EmbCC emits VFP for the Cortex-M33's unit (--fpu=fpv5-sp-d16) and nothing else: another unit's instruction set and attributes are unchecked here |
fpv4-sp-d16 or fpv5-d16 on ARMv7-M |
--fpu=fpv4-sp-d16 is an ARMv7E-M unit, and the part is ARMv7-M (a Cortex-M3 has no FPU); add --cpu=cortex-m4 |
fpv5-d16 on another part |
--fpu=fpv5-d16 is the Cortex-M7's double-precision unit, and --cpu=cortex-m4 does not have it; the Cortex-M4F's is --fpu=fpv4-sp-d16 |
any unit, or softfp or hard, on ARMv6-M |
--fpu=fpv5-d16 on thumbv6m-none-eabi: an ARMv6-M core (Cortex-M0, M0+, M1) has no FPU, so floating point is soft and travels in the core registers |
| the same on ARMv8-M Baseline | --fpu=fpv5-sp-d16 on thumbv8m.base-none-eabi: an ARMv8-M Baseline core (Cortex-M23) has no FPU, so floating point is soft and travels in the core registers |
--float-abi=ABI#
GCC: -mfloat-abi=ABI.
soft, softfp or hard. The default is soft, or hard for an
-eabihf triple, which also implies that architecture's FPU.
--fpu= and --float-abi= are combined after all options are read, so
their order does not matter, and an explicit value overrides what an
-eabihf triple implies.
--float-abi= |
Arithmetic | Floating-point arguments and results | --print-target |
|---|---|---|---|
soft |
Library calls, even if an FPU is named | Core registers | -eabi |
softfp |
float on the FPU; double on the FPU with fpv5-d16, else by library call |
Core registers; links with soft objects |
-eabi |
hard |
float on the FPU; double on the FPU with fpv5-d16, else by library call |
VFP registers (AAPCS-VFP) | -eabihf |
fpv4-sp-d16 and fpv5-sp-d16 are single-precision, so with them
double arithmetic is a call to the soft-float helpers under every float
ABI. fpv5-d16 computes in double precision as well:
double+,-,*,/, negation,fabs,__builtin_sqrt, the comparisons, and the conversions to and fromfloat,intandunsignedarevadd.f64,vsub.f64,vmul.f64,vdiv.f64,vneg.f64,vabs.f64,vsqrt.f64,vcmp.f64/vcmpe.f64andvcvt. libc'ssqrtisvsqrt.f64too.- The conversions between
doubleorfloatandlong longorunsigned long longstay calls (__fixdfdi,__fixunsdfdi,__floatdidf,__floatundidfand the fourfloatones): VFP has no instruction for a 64-bit integer. Built for this unit,lib/rt/softfp.ccontains those eight routines only. - With the register allocator on (
--opt=basicand up), a double gets one ofd8-d15, the callee-saved half of the file, which a function saves withvpush {d8-...}.d0andd1are scratch, andd0-d7carry the hard-float arguments and results. A double the allocator cannot place stays in an eight-byte stack slot. __ARM_FPis0xcand__ARM_FPV5__is defined. clang says0xeand defines__ARM_FEATURE_FMA; EmbCC emits neither half-precision conversions nor fused multiply-add, so it promises neither.- The object's attributes are clang's for the unit:
Tag_FP_archis FPv5-D16 (ARMv8-a FP-D16), andTag_ABI_HardFP_useis left at its default, both precisions.
| Mistake | Diagnostic |
|---|---|
| an unknown value | --float-abi=foo is not an ARM float ABI: it is one of soft, softfp and hard |
softfp or hard with no FPU |
--float-abi=hard needs an FPU to use: add --fpu=fpv4-sp-d16 (Cortex-M4F), --fpu=fpv5-d16 (Cortex-M7) or --fpu=fpv5-sp-d16 (Cortex-M33) |
| any ARM option on another target | --cpu=cortex-m3 is an ARM option, and the target is riscv32-unknown-elf |
--cmse#
GCC: -mcmse.
Compile for the Secure state of an ARMv8-M part with the security extension: see TrustZone-M.
ARMv8-M Baseline#
thumbv8m.base-none-eabi (or --cpu=cortex-m23) is the ARMv6-M code
generator with what ARMv8-M Baseline adds where it replaces a call:
- a 32-bit
/issdivorudiv, and%is the quotient multiplied back and subtracted (muls,subs; Baseline has nomls), as clang does it.__aeabi_idivand its family are not called. A 64-bit division is still__divdi3and the others. - a 1-, 2- or 4-byte atomic read-modify-write or compare-and-swap is a
ldrex/strex(b,h) loop between twodmbs, as on ARMv7-M. The__GCC_ATOMIC_*_LOCK_FREEvalues are 2. Baseline has noldrexd, so an 8-byte atomic is a call to__atomic_*_8(lib/rt), as on every 32-bit target.
Everything else is ARMv6-M's: Thumb-1 data processing on r0-r7, literal pools for constants and addresses (clang keeps them for this core too), no IT block, and no unaligned access -- the Cortex-M23 faults on one, as the M0 does. A function is scanned when it is finished, and any 32-bit encoding Baseline lacks -- or an IT block -- is refused by name (an inline asm template's included):
embcc: f.c:3: error: the ARMv8-M Baseline backend cannot lower an instruction ARMv8-M Baseline does not have (a 32-bit Thumb-2 encoding, from inline asm or the backend) yet (function f)
The 32-bit instructions Baseline has are bl, b.w, mrs, msr,
dmb, dsb, isb, sdiv, udiv, movw, movt, ldrex, strex
(with an offset), ldrexb, ldrexh, strexb, strexh, clrex, the
load-acquire and store-release family (lda, ldab, ldah, ldaex,
ldaexb, ldaexh, stl, stlb, stlh, stlex, stlexb,
stlexh), tt, ttt, tta, ttat and sg; it adds cbz, cbnz,
bxns and blxns to the 16-bit set. The inline and file assemblers
accept exactly these at this level
(Inline assembly).
tests/golden/thumbv8mbase-encoding.sh checks every one against llvm-mc
and the refusals line for line, and tests/golden/thumbv8mbase-exec.sh
runs the exec corpus at --opt=none to --opt=size, with lib/libc and lib/rt
built for the triple, and scans every object it builds.
QEMU has no Cortex-M23, so that suite runs Baseline code on the
mps2-an505's Cortex-M33 (tests/harness/thumb-m23), which executes every
Baseline instruction, with CCR.UNALIGN_TRP set so that an unaligned
access faults as on the M23. That the code contains nothing else is the
scan's to show.
The object's build attributes are clang's: Tag_CPU_arch 16 (v8-M
Baseline), Tag_THUMB_ISA_use 3, Tag_CPU_unaligned_access 0.
TrustZone-M (CMSE)#
--cmse compiles for the Secure state of an ARMv8-M part with the
security extension (ACLE's CMSE), on thumbv8m.main-none-eabi and
thumbv8m.base-none-eabi. __ARM_FEATURE_CMSE is 3 with it, and 1 (the
tt instruction) on every ARMv8-M target without it.
__attribute__((cmse_nonsecure_entry)) makes a function the Non-secure
state may call:
- its object has a second global symbol at the same address,
__acle_se_NAME.embldsees the pair and makes the secure gateway veneer,sg; b.w __acle_se_NAME, in.gnu.sgstubs, andNAMEnames the veneer (embld). - it returns with
bxns lr, and before that overwritesr0-r3(those the result does not occupy) andr12withlr, and the flags withmsr apsr_nzcvq, lr(apsr_nzcvqgon Mainline, whose DSP extension has the GE bits;apsron Baseline).r4-r11hold the caller's values again after the epilogue. This is clang's soft-float sequence. - it is kept and not inlined, has no tail calls, and must have external linkage.
A call through a pointer to a function type with
__attribute__((cmse_nonsecure_call)) enters the Non-secure state. The
attribute is written among the specifiers, before the declarator:
#include <arm_cmse.h>
typedef int __attribute__((cmse_nonsecure_call)) ns_fn(int, int);
int call_ns(void *p)
{
ns_fn *f = cmse_nsfptr_create((ns_fn *)p);
return f(1, 2);
}
The call saves r4-r11, clears bit 0 of the target, overwrites every
register that holds no argument with it, and the flags, branches with
blxns, and restores r4-r11; on Mainline it also saves and clears
the floating-point context with vlstm/vlldm, as clang does. A
narrow result is extended again after the call, since the Non-secure
callee is not trusted to have done it. tests/golden/thumbv8m-cmse.sh
compares these sequences with clang's and runs a Secure and a
Non-secure image on the mps2-an505, reading the registers on the other
side of every crossing.
<arm_cmse.h> provides cmse_address_info_t, cmse_TT, cmse_TTT,
cmse_TTA, cmse_TTAT and their _fptr forms,
cmse_check_address_range, cmse_check_pointed_object,
cmse_nsfptr_create and cmse_is_nsfptr, as ACLE defines them;
tt/ttt/tta/ttat are inline assembly there.
Without -mcmse, both attributes are ignored with a warning, as clang
and GCC do: __attribute__((cmse_nonsecure_entry)) is ignored without -mcmse (the Secure side of an ARMv8-M build).
| Construct | Diagnostic |
|---|---|
--cmse below ARMv8-M |
--cmse is the Secure side of ARMv8-M's security extension, and thumbv7m-none-eabi is not ARMv8-M: use thumbv8m.main-none-eabi or thumbv8m.base-none-eabi |
--cmse with an FPU |
--cmse with an FPU (--fpu=, --float-abi=softfp or hard, or an -eabihf triple) is not supported: EmbCC does not clear the floating-point registers a cmse_nonsecure_entry function must clear; build the Secure side with --float-abi=soft |
| an entry function with stack arguments | cmse_nonsecure_entry function 'f' requires arguments on the stack, which is the Non-secure caller's (CMSE allows r0-r3 only) |
| an entry function returning through memory | cmse_nonsecure_entry function 'f' would return its value through memory the Non-secure caller owns (CMSE allows a result in r0-r3 only) |
| a static entry function | cmse_nonsecure_entry function 'f' has internal linkage: the Non-secure state enters it through a veneer the linker makes from its global symbol |
| a variadic entry function | cmse_nonsecure_entry function 'f' is variadic, and its unnamed arguments would be on the Non-secure stack |
| a Non-secure call with stack arguments or a result through memory | a call through a cmse_nonsecure_call pointer in 'g' passes arguments on the stack, which is not supported: CMSE passes r0-r3 only |
cmse_nonsecure_call with no function type to attach to |
cmse_nonsecure_call applies to a function type, written among the specifiers before its declarator: ... |
cmse_nonsecure_caller() |
an undeclared __cmse_nonsecure_caller_is_not_supported_by_EmbCC (it needs lr as the function was entered) |
The Secure image is linked with embld, normally with a linker script
that puts .gnu.sgstubs in a region the SAU (and on parts like the
mps2-an505, the IDAU) marks Non-secure Callable; --cmse-implib --out-implib=FILE writes the import library the Non-secure image links
against. tests/golden/thumbv8m-cmse/ holds a complete example: the
Secure boot that programs the SAU, NSCCFG and the memory protection
controller and enters the Non-secure reset handler, both images, and the
script.
Calling convention: AAPCS32#
The base standard applies with --float-abi=soft and softfp:
- Arguments fill
r0–r3, then the stack. An argument with 8-byte alignment (long long,double, or a composite whose natural alignment is 8) starts at an even-numbered register and at an 8-byte-aligned stack offset, so it is never split. - A composite may be split between the last core registers and the stack, but only while nothing has been placed on the stack yet. After a split, nothing back-fills a register.
- A scalar result comes back in
r0, orr0:r1for 64-bit values. A composite of 4 bytes or fewer comes back inr0; a larger one is written through a hidden pointer passed inr0, and the real arguments start atr1. - A variadic argument is passed as a named one would be.
va_listis avoid *; a variadic function storesr0–r3immediately below the caller's stack arguments, so one pointer walks both.va_copyis an assignment, andva_argof a structure reads it from that walk where the caller placed it. - The stack is 8-byte aligned at every public interface. An unnamed bit-field affects a structure's alignment.
With --float-abi=hard (AAPCS-VFP), floating-point arguments travel in
the VFP registers instead:
- A
float, adouble, or a homogeneous aggregate of one to fourfloats ordoubles (including_Complex floatand_Complex double) is a candidate. Candidates fills0–s15/d0–d7lowest first, with back-filling:f(float, double, float)passess0,d1ands1. - A candidate that does not fit goes on the stack, and every VFP register
still free becomes unusable for the rest of the call. Other arguments
use
r0–r3and the stack exactly as in the base standard. - A floating-point result comes back in
s0ord0; a homogeneous aggregate ins0–s3ord0–d3, whatever its size. - A variadic function uses the base standard for all of its arguments and its result. The runtime helpers keep the base convention too.
__attribute__((pcs("aapcs"))) and __attribute__((pcs("aapcs-vfp")))
on a function declaration select the convention for calls to that
function:
| Mistake | Diagnostic |
|---|---|
| on a non-ARM target | __attribute__((pcs)) names an ARM calling convention, and this is not an ARM target |
| another value | pcs wants "aapcs" or "aapcs-vfp" |
aapcs-vfp without an FPU |
pcs("aapcs-vfp") passes floating point in VFP registers, and this part has no FPU: add --fpu= |
| on something other than a function | pcs is only supported on a function declaration |
| two different values | two different pcs attributes on one declaration |
Object format#
ELF32, EM_ARM, EF_ARM_EABI_VER5, with RELA relocations. Calls use
R_ARM_THM_CALL, tail calls R_ARM_THM_JUMP24, and addresses are built
with movw/movt (R_ARM_THM_MOVW_ABS_NC, R_ARM_THM_MOVT_ABS), so
code is position-dependent. Each object carries an .ARM.attributes
section stating the architecture (Tag_CPU_arch), the profile, the FPU
(Tag_FP_arch) and the float ABI (Tag_ABI_HardFP_use,
Tag_ABI_VFP_args), so a linker that checks attributes refuses to mix
hard- and soft-float objects.
Predefined macros#
The tables are those of clang -target thumbv7m-none-eabi and
clang -target thumbv8m.main-none-eabi, adjusted for the FPU:
| Macro | ARMv7-M, ARMv7E-M | ARMv8-M Mainline |
|---|---|---|
__arm__, __thumb__, __thumb2__, __ARM_EABI__, __ARMEL__ |
1 | 1 |
__ARM_ARCH |
7 | 8 |
__ARM_ARCH_7M__ / __ARM_ARCH_7EM__ / __ARM_ARCH_8M_MAIN__ |
__ARM_ARCH_7M__; __ARM_ARCH_7EM__ on ARMv7E-M |
__ARM_ARCH_8M_MAIN__ |
__ARM_FEATURE_DSP, __ARM_FEATURE_SIMD32 |
ARMv7E-M only | with the DSP extension (--cpu=cortex-m33, +dsp) |
__ARM_FEATURE_SAT, __ARM_FEATURE_QBIT |
yes | yes |
__ARM_ARCH_PROFILE |
'M' |
'M' |
__ARM_FEATURE_IDIV, __ARM_FEATURE_CLZ, __ARM_FEATURE_LDREX (0x7) |
yes | yes |
__CHAR_UNSIGNED__, __WCHAR_UNSIGNED__ |
1 | 1 |
__BIGGEST_ALIGNMENT__ |
8 | 8 |
ARMv7E-M (thumbv7em-*, --cpu=cortex-m4 or cortex-m7,
--isa=armv7e-m) defines __ARM_ARCH_7EM__ in place of
__ARM_ARCH_7M__, and __ARM_FEATURE_DSP and __ARM_FEATURE_SIMD32, as
clang does; ARMv8-M Mainline adds the last two with the DSP extension.
--cpu= names the part, and the part has one architecture whatever the
triple said: --cpu=cortex-m33 on a thumbv7em triple is ARMv8-M
Mainline, --cpu=cortex-m4 on thumbv8m.main ARMv7E-M.
tests/golden/predef.sh compares every __ARM_ARCH* and
__ARM_FEATURE_* macro with clang's for each part and -march=. clang's
__ARM_FEATURE_FMA is left out (below).
ARMv8-M Baseline's is that of clang -target thumbv8m.base-none-eabi -mcpu=cortex-m23: __ARM_ARCH 8, __ARM_ARCH_8M_BASE__,
__ARM_ARCH_ISA_THUMB 1, __ARM_FEATURE_IDIV, __ARM_FEATURE_LDREX
(0x7) and __ARM_FEATURE_CMSE (1), lock-free values of 2, and no
__thumb2__. clang also defines __ARM_FEATURE_CLZ, __ARM_FEATURE_SAT,
__ARM_FEATURE_QBIT, __ARM_FEATURE_NUMERIC_MAXMIN and
__ARM_FEATURE_DIRECTED_ROUNDING there, which name instructions a
Cortex-M23 does not have (GCC defines none of them for
armv8-m.base); EmbCC leaves them out (tools/gen-predef.sh).
__ARM_FEATURE_CMSE is 1 on both ARMv8-M tables and 3 with --cmse.
ARMv6-M's table is that of clang -target thumbv6m-none-eabi:
__ARM_ARCH is 6, __ARM_ARCH_6M__ and __ARM_ARCH_ISA_THUMB 1 are
defined, and __thumb2__, __ARM_FEATURE_IDIV, __ARM_FEATURE_CLZ,
__ARM_FEATURE_LDREX, __ARM_FEATURE_UNALIGNED and
__GCC_HAVE_SYNC_COMPARE_AND_SWAP_N are not. The __GCC_ATOMIC_*_LOCK_FREE
values are 1: an atomic read-modify-write is a call (see Runtime).
The float ABI changes these:
| Macro | soft |
softfp |
hard |
|---|---|---|---|
__SOFTFP__ |
1 | — | — |
__ARM_PCS |
1 | 1 | — |
__ARM_PCS_VFP |
— | — | 1 |
__ARM_FP |
— | 0x4 | 0x4 |
__ARM_VFPV2__, __ARM_VFPV3__, __ARM_VFPV4__ |
— | 1 | 1 |
__ARM_FPV5__ (ARMv8-M only) |
— | 1 | 1 |
__ARM_FP is 0x4 (single precision only), and __ARM_FEATURE_FMA is
not defined, because EmbCC emits neither half-precision conversions nor
fused multiply-add.
Runtime#
Every floating-point operation the hardware lacks, every 64-bit
division (__divdi3, __udivdi3, __moddi3, __umoddi3) and complex
multiply and divide are calls to helpers with libgcc's names.
make rt-embedded builds librt.a for each of the seven Cortex-M
triples (one archive per float ABI, because hard- and soft-float objects
do not link together); link it after your objects. Each also has the ARM
run-time ABI's __aeabi_* routines (lib/rt/aeabi.c), which clang, GCC
and the vendors' toolchains call where EmbCC calls libgcc's names: the
block operations, the divides, the 64-bit multiply, shifts and compares,
the soft-float arithmetic, comparisons and conversions, and the
half-precision conversions __aeabi_h2f, __aeabi_f2h and
__aeabi_d2h. They take their values in core registers whatever the
float ABI, as the run-time ABI says, and are weak, so a program's own
definition wins.
ARMv6-M has no divide, no 64-bit multiply and no exclusive loads and
stores, so more operations are calls there. librt.a for
thumbv6m-none-eabi (lib/rt/armv6m.c) provides them under the names
clang and GCC use for the triple, all weak, so a program's own
definition wins:
| Operation | Routine |
|---|---|
32-bit /; % and a paired / |
__aeabi_idiv, __aeabi_uidiv; __aeabi_idivmod, __aeabi_uidivmod |
64-bit *; shift by a variable |
__aeabi_lmul; __aeabi_llsl, __aeabi_llsr, __aeabi_lasr |
| block copy or clear of more than 8 bytes | __aeabi_memcpy, __aeabi_memclr |
| atomic read-modify-write, compare-and-swap (1, 2, 4 bytes) | __atomic_exchange_N, __atomic_fetch_OP_N, __atomic_compare_exchange_N, __sync_val_compare_and_swap_N |
ARMv8-M Baseline uses the same archive for the 64-bit multiply, the
shifts and the block routines; its 32-bit divides and its atomics are
instructions, so neither the division routines nor the atomic ones are
called (lib/rt/armv6m.c builds no atomics for it).
The atomic routines mask interrupts with PRIMASK around the access. That is atomic on a single core running privileged code; CPSID is ignored in unprivileged Thread mode, so an RTOS whose tasks run unprivileged, or a part with another bus master, defines its own.
Limitations#
| Construct | Diagnostic |
|---|---|
__int128 |
__int128 does not exist on this target (it needs 64-bit registers; use long long) |
| on ARMv6-M, an inline asm template that uses a Thumb-2 instruction | the ARMv6-M backend cannot lower an instruction ARMv6-M does not have (a 32-bit Thumb-2 encoding, from inline asm or the backend) yet (function f) |
C++ with exceptions (on by default), or unwind tables (--unwind-tables, -fasynchronous-unwind-tables) |
C++ exceptions are not supported for thumbv7m-none-eabi yet: EmbCC writes no ARM EHABI unwind tables (.ARM.exidx); compile with --no-exceptions, and unwind tables are not supported for thumbv7m-none-eabi yet (...). C++ itself compiles with --no-exceptions; see C++ |
An array or structure local aligned beyond 8 bytes is supported: its storage is carved from the stack at function entry and rounded up.
ARMv7-A#
ARMv7-A in ARM state: the A32 instruction set of a Cortex-A5, A7, A8, A9, A12, A15 or A17, little-endian, with the base AAPCS (soft float) or, with a VFP unit, AAPCS-VFP. Freestanding only. It is the Cortex-M backend's instruction selection writing A32 encodings; the design notes are in the ARMv7-A plan.
Triples#
| Triple | Accepted aliases | ISA | ABI |
|---|---|---|---|
armv7a-none-eabi |
armv7a, armv7-none-eabi, armv7a-unknown-none-eabi |
ARMv7-A, ARM state | AAPCS, soft float |
armv7a-none-eabihf |
armv7a-unknown-none-eabihf |
ARMv7-A, ARM state, VFPv3-D16 | AAPCS-VFP |
Options#
| Option | Accepted values | Refused with |
|---|---|---|
--isa=arm |
(no value) | --isa=thumb is not supported on armv7a-none-eabi: EmbCC emits ARM (A32) code for a Cortex-A |
--cpu=CPU |
cortex-a5, cortex-a7, cortex-a8, cortex-a9, cortex-a12, cortex-a15, cortex-a17, generic |
--cpu=cortex-r5 is not supported on armv7a-none-eabi (a Cortex-M or Cortex-R core) |
--float-abi=ABI |
soft, softfp, hard (the last two with an --fpu=) |
--float-abi=hard needs an FPU to use: add --fpu=vfpv3-d16 (or vfpv3, vfpv4-d16, vfpv4) |
--fpu=FPU |
vfpv3-d16, vfpv3, vfpv4-d16, vfpv4, none |
--fpu=neon is not supported on armv7a-none-eabi: EmbCC emits VFPv3 or VFPv4 ... and no NEON (Advanced SIMD) instruction |
--abi=ABI |
aapcs, aapcs-linux |
as for Cortex-M |
--unaligned-access, -mthumb-interwork |
(no value) | --no-unaligned-access is not supported |
No divide instruction is used, so the code runs on every ARMv7-A core:
/ and % call __aeabi_idiv, __aeabi_uidiv, __aeabi_idivmod and
__aeabi_uidivmod, which lib/rt provides. armv7a-none-eabihf is
--fpu=vfpv3-d16 --float-abi=hard; with softfp the FPU computes and
floating point travels in the core registers, as with soft. Either way
the code is the Cortex-M7's single- and double-precision VFP on d0–d15
(s16–s31/d8–d15 preserved); with --float-abi=soft no VFP
instruction is emitted even with an -mfpu=, as GCC reads it. No NEON
instruction is ever emitted.
Calling convention: AAPCS, soft float#
The Cortex-M targets' base standard (Calling convention:
AAPCS32), unchanged: r0–r3 for
arguments and results, an 8-byte value in an even register pair or an
8-aligned stack slot, a composite larger than 4 bytes returned through a
hidden pointer in r0, r4–r11 preserved, the stack 8-byte aligned at
every call, char unsigned and enums int-sized. float and double
travel as int and long long do. tests/golden/arm-a32-abi.sh checks
it with EmbCC and clang calling each other, in ARM state and across ARM
and Thumb.
Code generation#
Every instruction is one A32 word. Constants and addresses are
movw/movt (R_ARM_MOVW_ABS_NC, R_ARM_MOVT_ABS); calls are bl
(R_ARM_CALL) and tail calls b (R_ARM_JUMP24), reaching ±32 MiB. A
dense switch is add pc, pc, rI, lsl #2 over a table of branches.
Comparisons that produce a value use conditional movs. The code assumes
unaligned ldr/str/ldrh/strh work (__ARM_FEATURE_UNALIGNED, as
clang does): with the MMU on and the memory Normal, which is how an
A-profile system runs; with the MMU off every access is Strongly-ordered
and an unaligned one faults.
Object format#
ELF32, little-endian, EM_ARM, e_flags EF_ARM_EABI_VER5. Function
symbols are even (ARM state) and each function starts with an $a
mapping symbol. .ARM.attributes says Tag_CPU_arch v7, profile
A, the ARM ISA permitted and Thumb-2 permitted (as clang writes it), and
Tag_ABI_VFP_args base standard.
embld links A32 objects -- its own and clang's, REL or RELA -- and
interworks: a bl to a Thumb function becomes blx, as does a Thumb
bl to an ARM function. A jump (b) between the two instruction sets
needs a veneer and is refused. -Tstack writes an A32 stub that sets sp
and branches to the entry, since an A-profile core comes out of reset
with no stack.
Predefined macros#
clang's for --target=armv7a-none-eabi -mfloat-abi=soft: __arm__,
__ARM_ARCH 7, __ARM_ARCH_7A__, __ARM_ARCH_PROFILE 'A',
__ARM_ARCH_ISA_ARM, __ARM_EABI__, __SOFTFP__, __ARM_FEATURE_DSP,
__ARM_FEATURE_UNALIGNED, __ARM_FEATURE_LDREX 0xf; no __thumb__, no
__ARM_FEATURE_IDIV, no __ARM_FP. __GCC_HAVE_SYNC_COMPARE_AND_SWAP_8
is left out: an 8-byte atomic is a call to __atomic_*_8 (lib/rt),
where clang inlines an ldrexd/strexd loop.
Runtime#
make rt-embedded and make libc-embedded build lib/rt and lib/libc
for armv7a-none-eabi. lib/libc has setjmp/longjmp for ARM state,
saving s16-s31 on armv7a-none-eabihf.
tests/harness/arm-a32 boots QEMU's virt board (a Cortex-A15): the
MMU on with a flat map, the exception vectors, the PL011 UART and a
semihosting exit.
Limitations#
Refused by name: NEON (--fpu=neon), the Cortex-M FPUs, Thumb state (--isa=thumb, .thumb and .thumb_func), __builtin_frame_address and
__builtin_return_address above level 0, an interrupt handler built
for an FPU (its VFP registers would not be saved), a scalar local aligned
past 8, and C++ exceptions and unwind tables (C++ compiles with
--no-exceptions).
__attribute__((interrupt("IRQ"))) -- and "FIQ", "ABORT", "SWI",
"UNDEF", as GCC spells them; no argument is IRQ -- makes an exception
handler in ARM state: it pushes r0-r3, r12 and lr with the
callee-saved registers it uses (an even count, so an 8-aligned mode stack
stays aligned, which it assumes, as GCC's handlers do), IRQ, FIQ and ABORT
first take 4 off lr, and every kind returns with ldm sp!, {..., pc}^,
which restores CPSR from the mode's SPSR. The vector table and each
mode's stack are the program's to set up. tests/golden/arm-a32-isr.sh
runs the generic timer's interrupt through the GIC on QEMU's virt board. Inline assembly and .s files take the Cortex-M
vocabulary in ARM state -- the DSP extension's instructions (which every
ARMv7-A part has) included, with <arm_acle.h> -- with a condition on any
instruction, and add mrs/msr of cpsr, mrc/mcr, ldrexd/strexd
and the A32 ranges of svc, bkpt and udf; the M-profile special
registers, cbz, tbb and tbh are refused
(Inline assembly).
RISC-V#
Triples#
| Triple | Accepted aliases | ISA | ABI |
|---|---|---|---|
riscv32-unknown-elf |
riscv32-none-elf, riscv32, riscv32-elf, rv32 |
RV32IMAC | ilp32 |
riscv64-unknown-elf |
riscv64-none-elf, riscv64, riscv64-elf, rv64 |
RV64IMAC | lp64 |
Both are freestanding. One code generator serves both widths. The ISA
and ABI columns are the defaults; --isa= and --abi=
(invoking) select the F and D extensions
and their calling conventions.
Extensions#
| Extension | Use |
|---|---|
| I | The base integer instruction set. |
| M | Multiply and divide. At RV32, 64-bit division is a call (__divdi3 and family). Required. |
| A | Atomic read-modify-write: amoadd, amoor and the other AMOs, and lr/sc loops for compare-and-swap, on 4-byte (and at RV64, 8-byte) objects; a 1- or 2-byte object is an AMO or an lr.w/sc.w loop on the aligned word around it. The memory order sets .aq and .rl as clang's does (seq_cst by default). Memory barriers are fence rw, rw. Required. |
| F | With --isa=...f...: float add, subtract, multiply, divide, square root, comparisons and conversions to and from the integers a register holds are instructions (fadd.s, feq.s, fcvt.w.s ... ); a float lives in an f register (ft3-ft11, and fs0-fs11 where the ABI preserves them). |
| D | With --isa=...d... (or g): the same for double, and the conversions between the two. At RV32 a double crosses to an integer register pair through eight bytes of frame. |
| C | Compressed instructions, emitted wherever an encoding allows (unless --isa= leaves out c). The object's e_flags has EF_RISCV_RVC set. |
Without F a floating-point operation is a call to a soft-float helper
(__addsf3, __adddf3, ...); with F alone so is every double one; and
the 64-bit integer conversions at RV32 and everything on long double
are calls with D too. No fused multiply-add is emitted: C rounds a * b + c twice, and EmbCC does not contract. The objects carry a
.riscv.attributes section (rv32i2p1_m2p0_a2p1_c2p0 or the RV64
equivalent, stack alignment 16), and e_flags the float ABI.
Calling convention: the hardware-float ABIs#
--abi=ilp32f/lp64f and ilp32d/lp64d follow the psABI's hardware
floating-point convention, with ABI_FLEN 32 or 64, checked against clang
across the call (tests/golden/riscv-hf-abi.sh):
- a
float(and with thedABIs adouble) goes in the next offa0-fa7, and once those are gone by the integer rules below; - a structure that flattens -- nested structures and arrays opened up --
to one or two floating-point fields no wider than ABI_FLEN, or one such
field and one integer no wider than XLEN in either order, goes field by
field in
faandaregisters when enough of both are left, and whole by the integer rules otherwise; a complex number counts as two fields. Unions, pointers, two integers, three fields and wider types never flatten. A zero-width bit-field is ignored beside a lone float and ends a two-field structure, as clang has it; - variadic arguments always take the integer rules;
- results come back the same way in
fa0/fa1anda0-- a structure of two doubles at RV32 too, rather than through a hidden pointer; fs0-fs11are callee-saved, as wide as the ABI (fswunder thefABIs,fsdunder thedones).
The runtime helpers (__extendsfdf2, __floatdisf, __trunctfdf2 ...)
take and return their floating-point values the same way.
Calling convention: RISC-V psABI, soft float#
- Arguments go in
a0–a7, then on the stack. A scalar of up to XLEN bits takes one register; one of 2×XLEN bits (along longor adoubleat RV32) takes two, in any two consecutive registers for a named argument and in an even-aligned pair for a variadic one. An argument may be split betweena7and the stack. - An aggregate of up to 2×XLEN bits is passed in up to two registers; a larger one is passed by reference to a copy.
floatanddoubleare passed and returned in integer registers, as theilp32andlp64soft-float ABIs require -- with an FPU too (--isa=rv32imafc --abi=ilp32), whose f registers are then all caller-saved.- At RV64, a 32-bit integer in a register is kept sign-extended to 64
bits,
unsigned intincluded, as the psABI requires: arguments and results of 32-bit type are passed that way, in registers and on the stack, and EmbCC extends such a value wherever all 64 bits are read (a compare, a branch, a call). - An argument wholly on the stack is aligned to the larger of its type's alignment and XLEN, but never more than 16 bytes.
- Results come back in
a0anda1. A composite larger than 2×XLEN is written through a hidden pointer passed ina0. va_listis avoid *;va_copyis an assignment. A variadicva_argof a structure larger than two registers reads it by reference.- The stack is 16-byte aligned. An unnamed bit-field does not affect a structure's alignment.
Code model#
medany: addresses are formed PC-relatively with auipc and an addi
or load (R_RISCV_PCREL_HI20 / R_RISCV_PCREL_LO12_I), so code and data
may sit anywhere within ±2 GiB of each other. Calls use the auipc/jalr
pair with one R_RISCV_CALL_PLT relocation.
Predefined macros#
From clang -target riscv32-unknown-elf and riscv64-unknown-elf:
__riscv, __riscv_xlen (32 or 64), __riscv_i, __riscv_m,
__riscv_a, __riscv_c, __riscv_mul, __riscv_div,
__riscv_muldiv, __riscv_atomic, __riscv_compressed,
__riscv_float_abi_soft, __riscv_cmodel_medany, __CHAR_UNSIGNED__,
_ILP32/__ILP32__ or _LP64/__LP64__. __SIZEOF_INT128__ is
defined at RV64 only. -march= and -mabi= change them as clang's do:
__riscv_f, __riscv_d, __riscv_flen, __riscv_fdiv,
__riscv_fsqrt, __riscv_zicsr, __riscv_zcf (RV32), __riscv_zcd,
__riscv_zifencei, __riscv_float_abi_single or _double in place of
_soft, and no compressed macros without C (tests/golden/predef.sh
checks sixteen combinations).
Runtime#
make rt-embedded builds librt.a for riscv32-unknown-elf, holding
the soft-float, 64-bit division and complex helpers. There is no
archive for riscv64-unknown-elf, because lib/rt/int128.c and
lib/rt/fp128.c do not compile for it; compile the other lib/rt files
individually.
Limitations#
| Construct | Diagnostic (RV32 shown; RV64 names itself) |
|---|---|
__int128 at RV32 |
__int128 does not exist on this target (it needs 64-bit registers; use long long) |
C++ with exceptions (on by default), or unwind tables (--unwind-tables, -fasynchronous-unwind-tables) |
RV32: C++ exceptions are not supported for riscv32-unknown-elf yet: EmbCC writes no RISC-V .eh_frame; compile with --no-exceptions. RV64: unwind tables are not supported for riscv64-unknown-elf yet (--unwind-tables, -fasynchronous-unwind-tables, --exceptions): EmbCC writes no RISC-V .eh_frame. C++ itself compiles with --no-exceptions; see C++ |
__attribute__((interrupt("user"))) |
__attribute__((interrupt("user"))) is not supported: user-mode interrupts (the N extension and its uret) were never ratified and are gone from the privileged spec, and GCC and clang no longer accept them |
| an interrupt handler with parameters, or with a result | interrupt handler 'h' takes parameters: ..., interrupt handler 'h' returns a value: ... |
__attribute__((interrupt)) in C++ |
__attribute__((interrupt)) is not supported in C++ yet: ... (write the handler in C) |
An array or structure local aligned beyond 16 bytes is supported: its storage is carved from the stack at function entry and rounded up.
Renesas RX#
RXv1 -- the RX600/RX610 cores, and RX100/RX200 -- little-endian, in GCC's
rx-elf default configuration without the FPU: double and long double
are binary32 (GCC's -m32bit-doubles), plain char is unsigned,
long long is 4-aligned, size_t, ptrdiff_t and wchar_t are long
types, bit-fields use the Microsoft layout, and every C symbol carries
an underscore in the object (main is _main). Freestanding only. The
calling convention is GCC's (r1-r4 by whole words, nothing split, the
hidden result pointer in r15); EmbCC and rx-elf-gcc objects call each
other (tests/golden/rx-abi.sh). The design notes, the convention in full
and what is refused are in the RX plan.
| Triple | Accepted aliases |
|---|---|
rx-none-elf |
rx-elf, rx-unknown-elf, rx |
Accepted options: --cpu=rx600|rx610|rx200|rx100, -m32bit-doubles,
-nofpu, -mlittle-endian-data, -mrx-abi, -msmall-data-limit=0,
-mno-pid, -mint-register=0, -mallow-string-insns, -mrelax.
Refused by name: -m64bit-doubles, -fpu, -mbig-endian-data,
-mgcc-abi, a nonzero -msmall-data-limit, -mpid, a nonzero
-mint-register, -mno-allow-string-insns, -mas100-syntax, unwind
tables, fast_interrupt, and C++ exceptions (C++ compiles with
--no-exceptions; see C++).
__attribute__((interrupt)) makes an interrupt handler as rx-elf-gcc
does: it pushes the registers it may change -- the caller-saved r1-r5,
r14 and r15 as well as the callee-saved ones it uses -- with pushm, and
returns with rte, which takes back the PC and PSW the CPU pushed. The
handler goes in the relocatable vector table INTB points at; GCC's
interrupt(N), which names the slot for GNU ld's RX port to fill, is
refused (EmbLD builds no table from it), and so are parameters and a
result. tests/golden/rx-isr.sh runs CMT0's interrupt on QEMU against
code that keeps a value in every register.
EmbCC assembles RX itself, in GNU syntax: inline __asm__ with GCC's
operand constraints, naked functions, file-scope blocks and .s/.S
files, through one assembler for RXv1's integer instructions -- every form
encoded as rx-elf-as encodes it (tests/golden/rx-asm.sh compares them
byte for byte, and rx-elf-objdump reads each back), branches relaxed as
GNU as relaxes them, mov.l #sym and branches to symbols elsewhere
relocated (R_RX_DIR32, R_RX_DIR24S/16S/8S_PCREL). A C name is _name
in assembly, as with GCC. The vocabulary, the constraints and what is
refused (the FPU's and RXv2's instructions among them) are in
Inline assembly and
embas.
MIPS32#
MIPS32 Release 2, little-endian, with the o32 ABI and soft float: the core of Microchip's PIC32 parts. Freestanding only. The design notes are in the MIPS32 plan.
Triples#
| Triple | Accepted aliases | ISA | ABI |
|---|---|---|---|
mipsel-none-elf |
mipsel-unknown-elf, mipsel-elf, mipsel |
MIPS32r2 | o32, soft float |
There is no big-endian target (mips-none-elf is unknown).
Options#
EmbCC emits one configuration: MIPS32 Release 2, little-endian, o32, soft float, without abicalls and without small data. The options a MIPS build passes are accepted when they ask for exactly that and refused by name otherwise.
| Option | Accepted values | Refused with |
|---|---|---|
--cpu=CPU, --isa=CPU |
mips32r2, m4k, m14k, m14kc, 24kc, 24kf, 24kec, 24kef, 34kc, 74kc |
--cpu=mips32r6 is not a MIPS32 Release 2 core: EmbCC emits MIPS32r2 (mips32r2, m4k, m14k, m14kc, 24kc, 24kf, 24kec, 24kef, 34kc, 74kc) |
--abi=ABI |
32 |
--abi=n32 is not supported: EmbCC emits the o32 ABI (--abi=32) only |
-msoft-float |
(no value) | -mhard-float is not supported: EmbCC emits soft-float o32, which passes floating point in the integer registers |
-EL |
(no value) | -EB is not supported: the MIPS target is little-endian (mipsel) only |
-mno-abicalls |
(no value) | -mabicalls is not supported: EmbCC's MIPS code takes addresses absolutely (lui/addiu) and keeps no $gp; it is -mno-abicalls code |
-G0 |
(no value) | -G8 is not supported: EmbCC puts no data in .sdata and addresses nothing through $gp (-G0) |
The code runs on any MIPS32 Release 2 or later core that keeps the
Release 2 encodings (lwl/lwr among them), which Release 6 does not.
A floating-point unit, if the core has one, is not used.
Calling convention: o32, soft float#
- The arguments are laid out as a block in memory: each at its offset
rounded up to its alignment (at least 4, at most 8), in whole words.
The first 16 bytes travel in
a0–a3and the rest on the stack at the same offset from the caller'ssp. So after oneint, along longordoubleskipsa1fora2:a3; after three it goes on the stack atsp+16. - A structure or union of any size is passed by value in that layout,
its bytes packed into the words (the low address in the low bits) and
split between
a3and the stack when it straddles them. - The caller always reserves the first 16 bytes of its outgoing area, the
home area, for the callee to store
a0–a3into. floatanddoubletravel exactly asintandlong longdo.- A scalar result comes back in
v0, orv0:v1(low word inv0) for along longordouble. Every structure and union is returned through a hidden pointer the caller passes ina0, and the callee hands it back inv0. A_Complex floatcomes back inv0(real) andv1(imaginary), a_Complex doubleinv0:v1anda0:a1, as clang returns them. - A variadic argument follows the same layout.
va_listis avoid *; a variadic function storesa0–a3into its home area, so the named and unnamed arguments are one block, andva_argof an 8-byte type rounds the pointer up to 8 first. s0–s7,fp,gpandspsurvive a call.at,k0andk1are never used by compiled code exceptatas a scratch.- The stack is 8-byte aligned. An unnamed bit-field does not affect a structure's alignment.
tests/golden/mips-abi.sh checks every rule above with EmbCC and clang
calling each other on the board.
Code generation#
Addresses are absolute: lui and addiu with R_MIPS_HI16 and
R_MIPS_LO16. Calls, inside the unit as well, are jal with
R_MIPS_26, so caller and callee must share a 256 MiB region, as they
always do in a PIC32 or a KSEG0 image. Branches reach ±128 KiB; a branch
in a function larger than that becomes an inverted branch over a j.
A delay slot holds the instruction before the branch, call or return
when that keeps the program -- the classic fill -- and otherwise a nop;
a return's holds the release of the function's frame (jr $ra then
addiu $sp, $sp, N), as clang's does. Under -g every slot but the
return's is a nop. A dense switch dispatches through a jump table of
offsets from the address a bal returns, so it needs no relocation. A load or store the compiler
cannot prove aligned (a packed structure's member) uses lwl/lwr and
swl/swr, because a misaligned word access traps.
Object format#
ELF32, little-endian, EM_MIPS, with REL relocations (.rel.text, the
addend stored in the field) as o32 requires. e_flags is 0x70001001:
EF_MIPS_ARCH_32R2, EF_MIPS_ABI_O32 and EF_MIPS_NOREORDER; not
EF_MIPS_CPIC. Each object has a .MIPS.abiflags section saying ISA
MIPS32r2, 32-bit registers and the soft-float ABI.
embld links these objects and clang's (--target=mipsel-unknown-elf -msoft-float, in its default non-PIC mode), applying R_MIPS_32,
R_MIPS_26, R_MIPS_HI16/R_MIPS_LO16 (a HI16's addend completed by the
R_MIPS_LO16 that follows it) and R_MIPS_PC16. It refuses objects
with a different floating-point ABI in their .MIPS.abiflags, and the
GOT and gp-relative relocations of PIC and small-data code by name, and
drops .MIPS.abiflags, .reginfo and .pdr from the image.
-Tstack ADDR makes it emit an entry stub that sets sp and jumps to
the entry symbol.
Assembly#
embcc -c assembles .s and .S files for MIPS32, and file-scope
asm blocks and __attribute__((naked)) functions are assembled the
same way, in GNU as's syntax: $-spelt registers, jal sym, %hi(sym)
and %lo(sym), la, .word sym, labels and numeric locals, .set reorder/noreorder/push/pop, and the .ent/.end/.frame markers.
A file starts in .set reorder, where the assembler fills each delay
slot with a nop, as GNU as does; so does an inline-asm template, as
GCC's and clang's do. PIC and small-data code (.abicalls, .cpload,
%got, %call16, %gp_rel) is refused. The vocabulary and its rules
are in Inline assembly.
Predefined macros#
From clang --target=mipsel-unknown-elf -mcpu=mips32r2 -msoft-float -mno-abicalls: __mips__, __mips (32), mips, _mips, __MIPSEL__,
_MIPSEL, MIPSEL, __mips_isa_rev (2), _MIPS_ARCH_MIPS32R2,
__mips_o32, _ABIO32, _MIPS_SIM, _MIPS_SZINT, _MIPS_SZLONG and
_MIPS_SZPTR (32), __mips_soft_float, __mips_fpr (0). Not
__mips_abicalls: the code is not abicalls code. __CHAR_UNSIGNED__ is
not defined. __GCC_HAVE_SYNC_COMPARE_AND_SWAP_4 is, and the 1- and
2-byte forms are not.
Runtime#
make rt-embedded builds librt.a and make libc-embedded builds
libc.a for mipsel-none-elf (soft float, 64-bit division, the C
library on its bare-metal backend). tests/harness/mips runs programs on
QEMU's malta board: the image is linked at 0x80100000 in KSEG0 and
loaded with -kernel, the FPGA UART at 0xbf000900 is the console, and an
exception prints its cause and address.
Atomics#
Every atomic is an ll/sc loop bracketed by sync. A one- or two-byte
atomic works on the aligned word around it, as GCC's and clang's do: the
loop rewrites only its lane, old ^ ((new ^ old) & mask), so it is atomic
against the neighbouring bytes too (a store to any of them fails the
sc). Big-endian, the lane of offset a & 3 is counted from the top of
the word. __GCC_HAVE_SYNC_COMPARE_AND_SWAP_1, _2 and _4 are defined.
Limitations#
| Construct | Diagnostic |
|---|---|
__builtin_frame_address(N) or __builtin_return_address(N) with N above 0 (level 0 is supported; see Extensions) |
__builtin_return_address(1) is not supported on mipsel-none-elf: code for this target keeps no frame-pointer chain, so only level 0 (this function's own frame) can be found |
__int128 |
__int128 does not exist on this target (it needs 64-bit registers; use long long) |
__attribute__((interrupt, use_shadow_register_set)) |
__attribute__((use_shadow_register_set)) is not supported: EmbCC does not switch register sets: ... |
__attribute__((interrupt, use_debug_exception_return)) |
__attribute__((use_debug_exception_return)) is not supported: the handler would return with eret where the debug exception needs deret, and save DEPC as EPC |
| an interrupt handler with parameters, or with a result | interrupt handler 'h' takes parameters: ..., interrupt handler 'h' returns a value: ... |
--unwind-tables, -fasynchronous-unwind-tables, --exceptions |
unwind tables are not supported for mipsel-none-elf yet (--unwind-tables, -fasynchronous-unwind-tables, --exceptions): EmbCC writes no MIPS .eh_frame |
| C++ with exceptions (on by default) | C++ exceptions are not supported for mipsel-none-elf yet: EmbCC writes no MIPS .eh_frame; compile with --no-exceptions. C++ itself compiles with --no-exceptions; see C++ |
MIPS64#
MIPS64 Release 2 with the n64 ABI and soft float, in either byte order. Freestanding only. It is the MIPS32 backend at 64 bits; the design notes are in the MIPS64 plan.
Triples#
| Triple | Accepted aliases | ISA | ABI |
|---|---|---|---|
mips64el-none-elf |
mips64el-unknown-elf, mips64el-elf, mips64el |
MIPS64r2, little-endian | n64, soft float |
mips64-none-elf |
mips64-unknown-elf, mips64-elf, mips64 |
MIPS64r2, big-endian | n64, soft float |
Options#
EmbCC emits one configuration: MIPS64 Release 2, n64, soft float, without abicalls and without small data, in the triple's byte order. The options a MIPS64 build passes are accepted when they ask for exactly that and refused by name otherwise.
| Option | Accepted values | Refused with |
|---|---|---|
--cpu=CPU, --isa=CPU |
mips64r2, 5kc, 5kf, 5kec, 5kef, octeon |
--cpu=mips64r6 is not a MIPS64 Release 2 core: EmbCC emits MIPS64r2 (mips64r2, 5kc, 5kf, 5kec, 5kef, octeon) |
--abi=ABI |
64 |
--abi=n32 is not supported: EmbCC emits the n64 ABI (--abi=64) only |
-msoft-float |
(no value) | -mhard-float is not supported: EmbCC emits soft-float n64, which passes floating point in the integer registers |
-EL (mips64el), -EB (mips64) |
(no value) | -EB contradicts --target=mips64el-none-elf, which is little-endian: big-endian MIPS64 is --target=mips64-none-elf |
-mno-abicalls |
(no value) | -mabicalls is not supported: EmbCC's MIPS64 code takes addresses absolutely (%highest..%lo) and keeps no $gp; it is -mno-abicalls code |
-G0 |
(no value) | -G8 is not supported: EmbCC puts no data in .sdata and addresses nothing through $gp (-G0) |
A floating-point unit, if the core has one, is not used.
Data model#
LP64, as RV64's column above: long and pointers 8/8, long long 8/8,
long double 16/16 IEEE binary128, __int128 16/16. Plain char is
signed and wchar_t is a signed int. __BIGGEST_ALIGNMENT__ is 16 and
the stack is 16-byte aligned.
Calling convention: n64, soft float#
- Arguments take doubleword slots: the first eight in
a0–a7($4–$11), the rest on the stack from the caller'ssp, with no home area. Along double, or a structure aligned to 16, starts at an even slot; an__int128takes the next slot whatever it is (clang's rule). - A structure or union of any size is passed by value, its bytes as the
doublewords
ldwould read -- a short one left-justified big-endian -- split betweena7and the stack when it straddles them. - A 32-bit value,
unsignedincluded, travels and is returned sign-extended to 64 bits; afloatas its bits, adoubleas alongdoes. Afloaton the stack is the exception: GCC pads it upward, so big-endian its four bytes are its slot's first, where anint's are its last. - A scalar result comes back in
v0, an__int128inv0:v1, and along doubleinv0anda0(its first doubleword in memory inv0). A structure or union of at most 16 bytes comes back inv0:v1as its doublewords, except that a structure of one or two floating-point fields returns each field in its own register (afloatfield in the register's high half big-endian), a structure of onelong doubleas along double, and a_Complex floator_Complex doubleeach part in its own register. A larger one comes back through a hidden pointer the caller passes ina0, handed back inv0. - A variadic argument takes the same slots; an unnamed
__int128orlong doublestarts at an even one, whereva_argrounds the pointer up to 16.va_listis avoid *: a variadic function storesa0–a7just below its incoming stack words. s0–s7,fp,gpandspsurvive a call.
tests/golden/mips64-abi.sh and mips64-be-abi.sh check every rule above
with EmbCC and clang calling each other on the board.
Code generation#
Addresses are absolute and 64-bit, as clang takes them for n64 without
abicalls: lui, daddiu, dsll, daddiu, dsll, daddiu with
R_MIPS_HIGHEST, R_MIPS_HIGHER, R_MIPS_HI16 and R_MIPS_LO16. Calls
are jal with R_MIPS_26, within one 256 MiB region. Branches, delay
slots, jump tables and misaligned accesses are as on MIPS32,
with ldl/ldr and sdl/sdr for a doubleword. An __int128 or
long double is computed in two doublewords in memory; 128-bit division,
remainder and every long double operation call lib/rt.
Object format#
ELF64, EM_MIPS, in the triple's byte order, with RELA relocations whose
r_info is n64's record (symbol, then three type bytes; EmbCC writes one
type each, as clang does). e_flags is 0x80000001:
EF_MIPS_ARCH_64R2 and EF_MIPS_NOREORDER. Each object has a
.MIPS.abiflags section saying ISA MIPS64r2, 64-bit registers and the
soft-float ABI. embld links these objects and clang's
(--target=mips64el-unknown-elf -mcpu=mips64r2 -msoft-float -mno-abicalls -G0), applying R_MIPS_64, R_MIPS_32, R_MIPS_26, R_MIPS_HIGHEST,
R_MIPS_HIGHER, R_MIPS_HI16, R_MIPS_LO16 and R_MIPS_PC16; it
refuses a composite relocation (PIC and gp-relative code) by name.
-Tstack ADDR emits the entry stub, both addresses sign-extended 32-bit.
Predefined macros#
From clang --target=mips64el-unknown-elf (and mips64-unknown-elf)
--cpu=mips64r2 -msoft-float -mno-abicalls: __mips__, __mips (64),
__mips64, __mips_n64, _ABI64, _MIPS_SIM, _MIPS_SZLONG and
_MIPS_SZPTR (64), __mips_isa_rev (2), __mips_soft_float, __LP64__,
__SIZEOF_INT128__, and __MIPSEL__ or __MIPSEB__ by the byte order.
__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4 and _8 are defined; the 1- and
2-byte forms are not.
Runtime#
make rt-embedded and make libc-embedded build librt.a and libc.a
for both triples (soft float, binary128, 128-bit integer arithmetic).
tests/harness/mips64 runs programs on QEMU's malta board with a 5KEc
core: the image is linked at 0xffffffff80100000 (KSEG0) and loaded with
-kernel, with tests/harness/mips's startup and UART output.
Atomics#
Every atomic is an ll/sc loop (lld/scd for eight bytes) bracketed by sync. A one- or two-byte
atomic works on the aligned word around it, as GCC's and clang's do: the
loop rewrites only its lane, old ^ ((new ^ old) & mask), so it is atomic
against the neighbouring bytes too (a store to any of them fails the
sc). Big-endian, the lane of offset a & 3 is counted from the top of
the word. __GCC_HAVE_SYNC_COMPARE_AND_SWAP_1, _2, _4 and _8 are defined.
Limitations#
| Construct | Diagnostic |
|---|---|
| a 16-byte atomic | the MIPS64 backend cannot lower a 16-byte atomic (MIPS64's lld/scd are a doubleword; there is no 128-bit ll/sc) yet (function f) [cas16 w=16 size=16] |
__builtin_frame_address(N) or __builtin_return_address(N) with N above 0 (level 0 is supported; see Extensions) |
__builtin_return_address(1) is not supported on mips64el-none-elf: code for this target keeps no frame-pointer chain, so only level 0 (this function's own frame) can be found |
a doubleword instruction in inline assembly (daddu, ld, ...) |
asm instruction "daddu $a4, $a5, $a5" is not in the MIPS vocabulary |
la in a .s file or file-scope asm |
la loads a 32-bit address, and a MIPS64 address is 64 bits (nor are %highest and %higher assembled here): load it from a .dword holding the symbol |
| reading a packed bit-field over more than 8 bytes, big-endian | a packed bit-field 'v' across 9 bytes is not supported on a big-endian target (mips64-none-elf) |
--unwind-tables, -fasynchronous-unwind-tables, --exceptions |
unwind tables are not supported for mips64el-none-elf yet (...): EmbCC writes no MIPS .eh_frame |
| C++ with exceptions (on by default) | C++ exceptions are not supported for mips64-none-elf yet: EmbCC writes no MIPS .eh_frame; compile with --no-exceptions. C++ itself compiles with --no-exceptions; see C++ |
LoongArch64#
LA64, little-endian, with the LoongArch psABI's LP64S convention (soft
float): loongarch64-unknown-elf. Freestanding only. The design notes
are in the LoongArch64 plan.
Triples#
| Triple | Accepted aliases | ISA | ABI |
|---|---|---|---|
loongarch64-unknown-elf |
loongarch64-none-elf, loongarch64-elf, loongarch64 |
LA64 base integer | LP64S |
There is no LA32 target (loongarch32-unknown-elf is unknown).
Options#
EmbCC emits one configuration: the LA64 base integer ISA, LP64S, the normal code model. The options a LoongArch build passes are accepted when they ask for that, or for something it is a valid part of, and refused by name otherwise.
| Option | Accepted values | Refused with |
|---|---|---|
--isa=ARCH |
loongarch64, la64v1.0, la64v1.1, la464, la664 |
--isa=la32v1.0 is not an LA64 architecture: EmbCC emits the LA64 base integer ISA (loongarch64, la64v1.0, la64v1.1, la464, la664) |
--tune=CPU |
any | -- |
--abi=ABI |
lp64s |
--abi=lp64d is not supported: EmbCC emits the soft-float LP64S convention (--abi=lp64s), which passes floating point in the integer registers |
--fpu=FPU |
none, 0 |
--fpu=64 is not supported: EmbCC's LoongArch code uses no FPU (--fpu=none) |
-msoft-float |
(no value) | -mdouble-float / -msingle-float is not supported: EmbCC emits soft-float LP64S code (-msoft-float) |
--code-model=MODEL |
normal, medium |
--code-model=extreme is not supported: EmbCC emits the normal code model (bl, pcalau12i + addi.d) |
-mrelax, -mno-relax |
(no value) | -- |
-mno-strict-align |
(no value) | -mstrict-align is not supported: EmbCC's LoongArch code may access a packed member unaligned, as LA64 permits |
-mno-lsx, -mno-lasx |
(no value) | -mlsx is not supported: EmbCC emits no LSX or LASX vector instructions |
The code runs on every LA64 core; a floating-point or vector unit, if the core has one, is not used.
Calling convention: LP64S#
The LoongArch psABI's integer convention, which is RISC-V's LP64 rule for rule:
- Arguments go in
a0–a7($r4–$r11), then on the stack, each stack argument in an 8-byte slot (16-aligned for a 16-aligned type). A 16-byte scalar (__int128,long double) takes two registers with no alignment when named --f(int, __int128)usesa1:a2-- and an EVEN-aligned pair when variadic; with one register left it is split betweena7and the stack. - A structure of at most 16 bytes travels packed in one or two registers
(or split across
a7and the stack); a larger one is passed by reference to a copy the caller makes. - Results come back in
a0(anda1); a structure larger than 16 bytes through a hidden pointer ina0._Complexvalues are small structures. - A 32-bit value is kept sign-extended in its 64-bit register,
unsigned intincluded. floatanddoubletravel in the integer registers as their bits, and a variadicfloatis promoted todouble. Floating-point operations call lib/rt (__adddf3, ...).va_listis avoid *.fp($r22) ands0–s8survive a call.tp($r2) and$r21are never used by compiled code.- The stack is 16-byte aligned. An unnamed bit-field does not affect a structure's alignment.
tests/golden/loongarch-abi.sh checks these rules with EmbCC and clang
calling each other on the board.
Code generation#
Calls are bl with R_LARCH_B26 (+-128 MiB); addresses are pcalau12i
and addi.d with R_LARCH_PCALA_HI20 and R_LARCH_PCALA_LO12.
Conditional branches reach +-128 KiB (beqz/bnez +-4 MiB); one that
does not reach becomes an inverted branch over a b. A dense switch
dispatches through a table of offsets from a pcaddi. One- and two-byte
atomics are ll.w/sc.w loops on the word that holds them; four- and
eight-byte ones are the am*_db instructions and ll/sc loops.
Object format#
ELF64, little-endian, EM_LOONGARCH, RELA relocations, e_flags 0x41
(soft-float ABI, object ABI v1), as clang writes for -mabi=lp64s.
embld links these objects and clang's (--target=loongarch64-unknown-elf -msoft-float, its default medium code model included): the data and
ADD/SUB relocations, B16/B21/B26, CALL36, the PCALA pair, the ABS
sequence and PCREL20_S2. It builds no GOT: clang's GOT access
(GOT_PC_HI20/GOT_PC_LO12) is rewritten to the direct address.
R_LARCH_RELAX and R_LARCH_ALIGN are ignored. It refuses objects for
the hard-float ABIs, and the extreme code model's and TLS relocations, by
name. -Tstack ADDR makes it emit an entry stub that sets sp and
jumps to the entry symbol.
Assembly#
embcc -c assembles .s and .S files for LoongArch64, and file-scope
asm blocks and __attribute__((naked)) functions are assembled the same
way, in GNU as's syntax with $-spelt registers. The vocabulary is the
base integer ISA, the AM* atomics, the barriers and the privileged
instructions (csrrd, csrwr, csrxchg, ertn, idle, cpucfg,
rdtime*, iocsr*), llvm-mc's pseudos (nop, move, li.w, li.d,
jr, ret, bgt, ble, bgtu, bleu, bltz, bgez, bgtz,
blez), and with a symbol b/bl, call36/tail36, la.pcrel,
la.local, la/la.global and the %pc_hi20/%pc_lo12,
%got_pc_hi20/%got_pc_lo12, %abs_hi20/%abs_lo12/%abs64_lo20/
%abs64_hi12 and %call36 operators. In inline asm a register operand
is written $a0, an "m" operand $a0, 0; the constant letters are
i, n, I, J and K. la.abs, the TLS operators and
floating-point and vector instructions are refused.
Predefined macros#
From clang --target=loongarch64-unknown-elf -msoft-float:
__loongarch__, __loongarch64, __loongarch_grlen (64),
__loongarch_frlen (0), __loongarch_soft_float, __loongarch_lp64,
__loongarch_arch and __loongarch_tune ("loongarch64"), _LP64 and
__LP64__. Not __loongarch_sx (no LSX). __CHAR_UNSIGNED__ is not
defined. __GCC_HAVE_SYNC_COMPARE_AND_SWAP_1, _2, _4 and _8 are.
Runtime#
make rt-embedded builds librt.a and make libc-embedded builds
libc.a for loongarch64-unknown-elf (soft float, binary128 and the
128-bit integer routines, the C library on its bare-metal backend).
tests/harness/loongarch runs programs on QEMU's virt board: the image
is linked at 0x1000000 and loaded with -kernel, the UART at 0x1fe001e0
is the console, an exception prints its code and address, and the ACPI
GED's sleep register powers the board off at the end.
Limitations#
| Construct | Diagnostic |
|---|---|
| a 16-byte atomic | the LoongArch64 backend cannot lower a sixteen-byte atomic (the LA64 base ISA has no 128-bit ll/sc or am* instruction) yet (function f) [cas16 w=16 size=16] |
__builtin_frame_address(N) or __builtin_return_address(N) with N above 0 (level 0 is supported; see Extensions) |
__builtin_return_address(1) is not supported on loongarch64-unknown-elf: code for this target keeps no frame-pointer chain, so only level 0 (this function's own frame) can be found |
__attribute__((interrupt)) |
__attribute__((interrupt)) is not supported: ... (write the exception entry in a .S file or a naked function) |
--unwind-tables, -fasynchronous-unwind-tables, --exceptions, C++ without --no-exceptions |
unwind tables are not supported for loongarch64-unknown-elf yet (...): EmbCC writes no LoongArch .eh_frame |
TriCore#
Infineon TriCore 1.6.1, the core of the AURIX TC2xx microcontrollers and
a subset of the TC3xx's TriCore 1.6.2: 32-bit, little-endian, soft float.
Freestanding only. The design notes, and where each fact below comes
from, are in the TriCore plan: there is
no TriCore compiler on the machine EmbCC is developed on, so the calling
convention, the data layout, the relocation numbers and the predefined
macros are the TriCore EABI and GCC for TriCore as remembered, and
unverified against a reference compiler. The instruction encodings are
checked against QEMU's TriCore translator, and every test runs on QEMU's
tricore_testboard.
Triples#
| Triple | Accepted aliases | ISA | ABI |
|---|---|---|---|
tricore-none-elf |
tricore-elf, tricore-unknown-elf, tricore |
TriCore 1.6.1 | TriCore EABI, soft float |
Options#
| Option | Accepted values | Refused with |
|---|---|---|
--cpu=CPU, --isa=CPU, -mCPU |
tc16, tc161, tc162, tc1.6, tc1.6.1, tc1.6.2, tc16x, tc2xx, tc22xx, tc23xx, tc26xx, tc27xx, tc29xx, tc3xx, tc33xx, tc36xx, tc37xx, tc38xx, tc39xx |
--cpu=tc1797 is not a TriCore 1.6 core: EmbCC emits TriCore 1.6.1 code, for the AURIX TC2xx and TC3xx (...) |
-msoft-float, --endian=little |
(no value) | -mhard-float is not supported: EmbCC emits soft float for TriCore (the TC3xx FPU is not used yet) |
Calling convention (TriCore EABI, unverified)#
- A pointer argument travels in the next free of
A4–A7; any other scalar of 32 bits or fewer in the lowest free ofD4–D7; along longordoublein the next free even pair,E4(D4:D5) orE6, low word in the even register -- a register skipped on the way is filled by a later 32-bit argument. - A structure or union of 8 bytes or fewer travels as an integer of its size; a larger one by reference, the caller passing the address of its own copy as a pointer argument.
- What finds no register goes on the stack in whole words from the
caller's
A10, 4-aligned whatever its size. Every unnamed argument of a variadic call goes there too, sova_listis achar *. - A pointer result comes back in
A2; another of 32 bits or fewer inD2; a 64-bit one, or a struct of 5-8 bytes, inE2; a larger struct through a hidden pointer the caller passes inA4. CALLsaves the upper context (D8–D15,A10–A15) in a context-save area andRETrestores it, so those registers and the stack pointer survive every call with no save code.A0,A1,A8andA9are the system's and never touched.- The stack is 8-byte aligned;
long longanddoubleare 4-aligned in memory.
Code generation#
Every value lives in a data register or a frame slot; address registers
are loaded just before a load or store, and for a call's pointer
arguments and results. Addresses are absolute: movh and addi with
R_TRICORE_HIADJ and R_TRICORE_LO. Calls are CALL with
R_TRICORE_24REL (±16 MiB). Conditional branches reach ±32 KiB; a branch
in a function larger than that becomes an inverted branch over a J. A
dense switch is a tree of compares (no jump tables yet). A load or store
the compiler cannot prove aligned goes a byte at a time. Only the 32-bit
encodings are emitted.
Atomics#
An exchange of a word is SWAP.W, a compare-and-swap CMPSWAP.W, and a
read-modify-write a CMPSWAP.W loop, each bracketed by DSYNC. A one- or
two-byte atomic works on the aligned word around it: the loop rewrites
only its lane, old ^ ((new ^ old) & mask), so it is atomic against the
neighbouring bytes too (a store to any of them makes the CMPSWAP.W fail
and the loop go round). A compare-and-swap hands CMPSWAP.W the word last
seen with the expected value in the lane; when it fails because a
neighbour moved, the loop goes round, and when the lane differs, it
fails. __GCC_HAVE_SYNC_COMPARE_AND_SWAP_1, _2 and _4 are defined.
Object format and linking#
ELF32, little-endian, EM_TRICORE (44), RELA relocations, e_flags
0x00200000. embld links these objects, applying R_TRICORE_32ABS,
R_TRICORE_24REL, R_TRICORE_HIADJ, R_TRICORE_LO and R_TRICORE_LO2,
and refuses the small-data relocations by name. -Tstack ADDR --csa START:END makes it emit an entry stub that sets A10, links the
context-save areas in [START, END) into the free list every CALL draws
from, turns call-depth counting off and jumps to the entry symbol; the
areas must be 64-byte aligned and within the first 4 MiB of one 256 MiB
segment.
Assembly#
Inline asm is assembled by EmbCC's TriCore vocabulary
(src/arch/tricore/asm.c): the system instructions (mtcr/mfcr with
core registers by name or number, isync, dsync, syscall,
enable, ...), the moves, the ALU forms, the loads and stores,
swap.w, cmpswap.w and the indirect jumps and calls. Constraints:
d/r a data register, a an address register, m an address register
holding the operand's address (written [%0]), i a constant; register
variables bound to d0-d7 or a2-a7.
The same vocabulary has the control transfers: j, jl and call (+-16
MiB), the conditional branches jeq, jne, jlt, jlt.u, jge,
jge.u against a register or a 4-bit constant, jz/jnz (jeq/jne
against 0), jeq.a, jne.a, jz.a, jnz.a and loop (+-32 KiB), each
to .+N/.-N or, in a template, a numeric label (1:, 1b, 1f).
embcc -c assembles .s and .S files, and file-scope asm blocks and
__attribute__((naked)) functions are assembled the same way, in GNU
syntax with optional % on registers; there j/jl/call sym carry
R_TRICORE_24REL, an address is movh/movh.a with hi:sym or
%hi(sym) (R_TRICORE_HIADJ) and addi with lo:sym or %lo(sym)
(R_TRICORE_LO) or lea, a load or a store with [aB]lo:sym
(R_TRICORE_LO2), and .word sym is R_TRICORE_32ABS. A conditional
branch or loop reaches only a label of its own section (embld does not
apply R_TRICORE_15REL), and is refused by name with a symbol defined
elsewhere. tests/golden/tricore-gas.sh runs every transfer, assembled
from its text, through QEMU's TriCore translator (tricore-encoding.sh's
referee), and a .S file with C on the board.
Predefined macros#
__tricore__, __TRICORE__, __TC161__, __TRICORE_CORE__ and
__TRICORE_NAME__ (0x161), with the ILP32 set; __CHAR_UNSIGNED__ is
not defined. __GCC_HAVE_SYNC_COMPARE_AND_SWAP_1, _2 and _4 are.
Runtime#
make rt-embedded and make libc-embedded build librt.a and libc.a
for tricore-none-elf. tests/harness/tricore runs programs on QEMU's
tricore_testboard (-cpu tc27x): the image is linked at 0x80000000 and
loaded with -kernel; the board has no UART, so a TCG plugin
(tests/harness/tricore/putc.c) prints what the harness stores to its
output word; a trap prints its class, number and address.
Interrupt and trap handlers#
__attribute__((interrupt)) makes a handler as HighTec's GCC does: the
hardware has saved the upper context on entry, as for a CALL, and the
handler saves the lower one with svlcx and leaves with rslcx and
rfe, which takes the upper context back and returns to the interrupted
instruction. The same handler serves a trap. Parameters, a result and an
argument (the priority and vector are the interrupt router's, set at run
time) are refused. tests/golden/tricore-isr.sh traps into one from a loop
that keeps a value in every lower-context register.
Limitations#
| Construct | Diagnostic |
|---|---|
__builtin_frame_address(N) or __builtin_return_address(N) with N above 0 (level 0 is supported; see Extensions) |
__builtin_return_address(1) is not supported on tricore-none-elf: code for this target keeps no frame-pointer chain, so only level 0 (this function's own frame) can be found |
__int128 |
__int128 does not exist on this target ... |
a conditional branch or loop to a symbol defined elsewhere (.s, .S, file-scope asm) |
a conditional branch or loop reaches only a label of its own section: its 15-bit displacement (R_TRICORE_15REL) is not one embld applies; branch over a j |
--unwind-tables, -fasynchronous-unwind-tables, --exceptions |
unwind tables are not supported for tricore-none-elf yet ... |
| C++ with exceptions (on by default) | C++ exceptions are not supported for tricore-none-elf yet: EmbCC writes no TriCore .eh_frame; compile with --no-exceptions. C++ itself compiles with --no-exceptions; see C++ |
ColdFire#
Motorola/NXP ColdFire, ISA_A with the hardware divide (an MCF5208 and
every later core), big-endian, soft float: GCC's m68k-elf with
--cpu=5208. Freestanding only. The design notes, and which facts are
not yet checked against a real m68k compiler, are in
the ColdFire plan.
EmbCC assembles ColdFire itself, in GNU as's Motorola syntax (% optional,
MIT's An@(d) accepted): inline __asm__ with GCC's operand constraints,
naked functions, file-scope blocks and .s/.S files, through one
assembler for the MCF5208's ISA_A+ -- tests/golden/coldfire-asm.sh reads
every form back with QEMU's m68k disassembler, assembles FreeRTOS's
ColdFire V2 port, and runs a program that uses it against a host model.
Branches relax to .s or .w (the MCF5208 has no 32-bit branch); a bra or
bsr to a symbol defined elsewhere is a jmp/jsr to its address
(R_68K_32). The vocabulary, the constraints and what is refused are in
Inline assembly and
embas.
Triples#
| Triple | Accepted aliases | ISA | ABI |
|---|---|---|---|
m68k-none-elf |
m68k-unknown-elf, m68k-elf, m68k |
ColdFire ISA_A | m68k SVR4, soft float |
The 68000 family proper (68000-68060, CPU32) is not a target.
Options#
| Option | Accepted values | Refused |
|---|---|---|
--cpu=CPU, --tune=CPU, -mCPU |
5208, 5207, 5206e, 5211-5216, 5235, 5249, 5271-5282, 5307, 5329, 5373, 5407, 54455 and their kin |
a 68000-family CPU; 5206, 5202, 5204 (no divider); 547x/548x (an FPU) |
--isa=ISA |
isaa, isaaplus, isab, isac |
anything else |
-msoft-float, -mdiv, -mno-align-int, -mno-short, -mno-rtd, -m[no-]strict-align, --endian=big |
(no value) | -mhard-float, -mno-div, -malign-int, -mshort, -mrtd, -mpcrel, -mid-shared-library, -msep-data, -mxgot, --endian=little |
Calling convention#
- Every argument is on the stack in whole 4-byte words, the first at
4(%sp)on entry: a scalar of four bytes or fewer promoted to a word (achar's byte is its word's last), along longordoubletwo words, high first, a structure or union by value, its size rounded up to a word -- right-justified when smaller than a word. The caller pops. - A variadic function's unnamed arguments are laid out as named ones;
va_listis achar *. - A scalar result comes back in
d0, ord0:d1(high word ind0); a pointer ina0andd0; a_Complex floatind0/d1, a_Complex doubleind0:d1/d2:d3. Every other structure and union is returned through the buffer whose address the caller passes ina1. d2-d7anda2-a6survive a call;a6is the frame pointer, which every function links.- Nothing is aligned beyond 2 bytes, so
struct { char c; int i; }is 6 bytes; the stack is 4-aligned at a call.
tests/golden/coldfire-abi.sh checks that caller and callee agree, EmbCC
to EmbCC at every optimization pairing.
Code generation#
Operands are effective addresses, so a frame slot is read in place. The
allocator's data class is d2-d7 and its address class a2-a5.
Addresses and calls are 32-bit absolute (move.l #sym, lea sym,
jsr sym, R_68K_32). Branches are bcc.b/bcc.w; one beyond 32 KiB
becomes a position-independent lea/adda.l/jmp. A 64-bit multiply,
divide and variable shift call lib/rt. The atomics mask interrupts
around a plain read-modify-write -- ISA_A has no compare-and-swap -- so
they need supervisor mode, where bare-metal code runs; in user mode they
trap.
Object format#
ELF32, big-endian, EM_68K, RELA, e_flags 0x2 (ISA_A). embld links
them, applying R_68K_32/16/8 and R_68K_PC32/PC16/PC8, refuses
GOT and PLT relocations, a little-endian, FPU or non-ColdFire object by
name, and with -Tstack ADDR emits a stub that sets %sp and jumps to
the entry.
Interrupt handlers#
__attribute__((interrupt)), and its m68k spelling interrupt_handler,
makes an interrupt handler as GCC's m68k port does: it saves d0, d1,
a0 and a1 with the callee-saved registers it uses in its frame's one
movem, and returns with rte, which takes back the format word, SR and
PC the exception stacked. interrupt_thread (the Fido's register banks),
an argument, parameters and a result are refused.
tests/golden/coldfire-isr.sh runs PIT0's interrupt on QEMU's mcf5208evb
against code that keeps a value in every register.
Limitations#
| Construct | Diagnostic |
|---|---|
a rotate, dbcc, exg, cas, BCD and the FPU's instructions in asm |
rol.l: ColdFire has no rotate (and so on, by name) |
ISA_B's mvs, mvz, mov3q, sats, and a 32-bit branch (bra.l) |
mvs.b is an ISA_B instruction, which the MCF5208 (ISA_A+) does not have: it traps as illegal there |
| a frame beyond 32 KiB | a stack frame larger than 32 KiB |
| unwind tables | unwind tables are not supported for m68k-none-elf yet |
| C++ with exceptions (on by default) | C++ exceptions are not supported for m68k-none-elf yet: EmbCC writes no ColdFire .eh_frame; compile with --no-exceptions. C++ itself compiles with --no-exceptions; see C++ |
Xtensa#
Little-endian Xtensa with the windowed-register ABI: the instruction set of Espressif's ESP32 (Xtensa LX6) and ESP32-S3 (LX7). Freestanding only. The design notes are in the Xtensa plan.
Triples#
| Triple | Accepted aliases | Cores | ABI |
|---|---|---|---|
xtensa-none-elf |
xtensa-esp32-elf, xtensa-esp32s3-elf, xtensa-esp-elf, xtensa-elf, xtensa |
ESP32 (LX6), ESP32-S3 (LX7) | windowed, soft float |
There is no big-endian target (xtensaeb-none-elf is unknown).
Options#
EmbCC emits one configuration: the windowed ABI, little-endian, literal
pools in .text before each function, direct call8s, memw before every
volatile access. GCC's Xtensa options that ask for that, or that only tune
GCC's own placement and costs, are accepted; the rest are refused by name.
| Option | Accepted | Refused with |
|---|---|---|
--abi= |
windowed |
--abi=call0 is not supported: EmbCC emits the windowed ABI (call8/entry/retw), which ESP-IDF uses |
-mlongcalls, -mno-longcalls |
both: calls are direct; embld refuses one beyond call8's 512 KiB |
|
-mtext-section-literals, -mauto-litpools (and -mno-) |
yes | |
-mserialize-volatile, -mno-serialize-volatile |
yes; memw is emitted either way |
|
--endian=little |
yes | --endian=big is not supported for xtensa-none-elf, which is little-endian |
-mdynconfig= |
xtensa_esp32.so, xtensa_esp32s3.so |
-mdynconfig=xtensa_esp32s2.so is not supported for xtensa-none-elf: ... |
-mtarget-align, -mforce-no-pic, -mstrict-align, -mextra-l32r-costs=, -mno-const16 |
yes | |
-mconst16, -mforce-l32, -mfix-esp32-psram-cache-issue |
refused by name |
The code uses the core instruction set with windowed registers, MUL32
(mull), DIV32 (quos, quou, rems, remu), MINMAX, SEXT, NSA, ABS,
ADDX and L32R, and S32C1I for atomics -- what the ESP32, the ESP32-S3 and
QEMU's de212 core share. It does not use the FPU, MUL32_HIGH, the loop
instructions, MAC16, the boolean registers or the density option's
16-bit instructions. The ESP32-S2 has no S32C1I, so an atomic
read-modify-write is not for it.
Atomics#
Every atomic is an s32c1i loop (the store that happens only while the
word still equals SCOMPARE1) bracketed by memw. A one- or two-byte
atomic works on the aligned word around it, as GCC's does: the loop
rewrites only its lane, old ^ ((new ^ old) & mask), so it is atomic
against the neighbouring bytes too (a store to any of them makes the
s32c1i fail and the loop go round), and a compare-and-swap compares only
its lane. __GCC_HAVE_SYNC_COMPARE_AND_SWAP_1, _2 and _4 are defined.
Calling convention: windowed, soft float#
- A
call8rotates the register window by eight: the caller puts the arguments ina10-a15and the callee finds them ina2-a7; the caller'sa0-a7survive the call anda8-a15do not.a0holds the return address,a1is the stack pointer. A function begins withentry a1, Nand returns withretw. - Arguments are counted in words, six in registers. A type aligned beyond
4 (
long long,double, a structure containing one) starts at an even word. An argument that does not fit entirely in the words left goes on the stack, and so does every argument after it. Stack arguments are at the caller'ssp, each at its own alignment (4 to 16). - A structure or union of any size is passed by value. A
_Complexis passed as its two parts, each placed as an argument of its own. floatanddoubletravel exactly asintandlong longdo: in the address registers, whether or not the core has an FPU, so EmbCC's code links with GCC's hard-float ESP32 code.- A result of up to 16 bytes comes back in
a2-a5(the caller'sa10-a13), a structure in its memory order; a larger one through a hidden pointer passed as the first argument, which the callee returns. charandshortarguments and results are extended by the side that produces them.va_listis GCC's 12-byte record,{ int *__va_stk; int *__va_reg; int __va_ndx; }, held and passed by value, so ava_listpasses between EmbCC's and GCC's code.- The stack is 16-byte aligned. Every frame is at least 32 bytes, its top 32 bytes reserved for the window spill handlers. An unnamed bit-field does not affect a structure's alignment.
tests/golden/xtensa-abi.sh checks these rules with EmbCC and Espressif's
GCC calling each other on the board.
Code generation#
Constants that movi (or movi and a shift) cannot build, and every
address, are loaded with l32r from a literal pool placed right before
the function, since l32r reaches only backwards; the function's symbol
is at its entry, after the pool. Calls are call8 with
R_XTENSA_SLOT0_OP, even within the unit. A comparison's value is a
branch over a movi. A conditional branch that does not reach (±128 bytes,
±2 KiB against zero) becomes the inverse branch over a j, and one beyond
j's ±128 KiB an l32r of the label's address and a jx; a function more
than 256 KiB from its pool reads a literal from an island in the code. A
frame larger than entry can allocate, and every alloca, moves sp with
movsp. A load or store the compiler cannot prove aligned (a packed
structure's member) is done a byte at a time, because a misaligned access
raises an exception. A dense switch stays a decision tree.
Object format#
ELF32, little-endian, EM_XTENSA (94), RELA relocations, e_flags
0x300 (EF_XTENSA_XT_INSN | EF_XTENSA_XT_LIT), as GNU as writes for the
ESP32. embld links these objects and GCC's, applying R_XTENSA_32 and
R_XTENSA_SLOT0_OP (on callN, j, the branches, the loop
instructions, beqz.n/bnez.n and l32r) and ignoring
R_XTENSA_ASM_EXPAND and the DIFF types, which only a relaxing linker
changes. It does not relax and mints no trampolines: a call8 beyond 512
KiB, or an l32r whose literal is not before it, is refused (compile GCC's
side with -mtext-section-literals). -Tstack ADDR makes it emit an entry
stub that sets sp over a valid bottom frame, sets PS (window
exceptions on, level 0) and WINDOWSTART, and calls the entry with
callx8.
Assembly#
embcc -c assembles .s and .S files for Xtensa, and file-scope asm
blocks and __attribute__((naked)) functions are assembled the same way,
in GNU as's Xtensa syntax. The vocabulary is the ESP32's: the core ALU,
shift and SAR instructions, the MUL32/DIV32 and MIN/MAX options, the
loads and stores, movi, mov, addi, addmi, every branch form (two
registers, against zero, against a b4const, bbci/bbsi and their
.l spellings), the zero-overhead loops, j, jx, call0/4/8/12,
callx0/4/8/12, ret, retw, entry, movsp, rotw, rsr/wsr/xsr
by the ESP32's special-register names (ps, epc1-epc7,
excsave1-excsave7, eps2-eps7, intenable, interrupt, intset,
intclear, ccount, ccompare0-2, vecbase, sar, windowbase,
windowstart, lbeg, lend, lcount, scompare1, atomctl,
exccause, excvaddr, depc, prid, cpenable, misc0-3, ...) with
GNU's read/write rules, or by number, or as rsr.ps; rur/wur of
threadptr, rsil, waiti, the syncs, memw, extw, break, ill,
rfe, rfde, rfi, rfwo, rfwu, syscall, simcall, s32c1i,
l32ai, s32ri, l32e and s32e. GNU's _ prefix is accepted. The
density option's .n forms are refused by name (no instruction here is
16 bits), as is anything else outside the list.
A symbol is a target of a branch, a loop, j, callN or l32r with
R_XTENSA_SLOT0_OP, and a .word with R_XTENSA_32. movi aN, sym -- or
a constant movi cannot hold -- is an l32r of a literal, and
.literal NAME, X, ... names literals of its own; the literals go where
GNU as's --text-section-literals puts them: in the latest pool placed
before the code, at the start of the section, at each
.literal_position, and before the labels of each function's entry.
.align counts bytes, as GNU as's does for Xtensa. .begin/.end blocks
that only restrict relaxation (no-transform, literal_prefix,
schedule, ...) are accepted; .begin longcalls and
absolute-literals would change the code and are refused.
tests/golden/xtensa-asm.sh checks every form against QEMU's de212
disassembler and Espressif's GNU as, and a file's layout against GNU as's.
In inline asm a register operand is written a10, an "m" operand
a10, 0; the constraint letters are r, a, g, m, i, n and
GCC's I-P. No operand is put in a0/a1 (the return address and the
stack pointer), a7 or a14/a15; a clobber list may not name a0 or
a1; a template's call4/call8/call12 (or callx) clobbers the
callee's window -- a4/a8/a12 up to a15 -- whatever the clobber list
says, and call0/callx0, which would write a0, are refused. A
template may use numeric labels (1:, 1b, 1f); it cannot name a
symbol, since its bytes carry no relocation.
Predefined macros#
From Espressif's xtensa-esp32-elf-gcc 16.1: __xtensa__, __XTENSA__,
__XTENSA_EL__, __XTENSA_WINDOWED_ABI__, the ESP32's __XCHAL_*
configuration, __CHAR_UNSIGNED__, __WCHAR_TYPE__ short unsigned int,
__INT32_TYPE__ long int. Not __XTENSA_SOFT_FLOAT__: the ESP32's GCC
does not define it, and the float ABI is the same either way.
__GCC_HAVE_SYNC_COMPARE_AND_SWAP_1, _2 and _4 are defined.
Runtime#
make rt-embedded builds librt.a and make libc-embedded builds
libc.a for xtensa-none-elf (soft float, 64-bit multiply and division,
the C library on its bare-metal backend). tests/harness/xtensa runs
programs on QEMU's sim machine with the de212 core (an LX6 with no FPU
or MMU): the generic loader starts the image at its entry, the harness
installs the window overflow and underflow handlers and the Alloca
exception's, output and exit are the sim machine's simcalls, and any other
exception prints its cause, EPC1 and EXCVADDR.
Limitations#
| Construct | Diagnostic |
|---|---|
__builtin_frame_address(N) or __builtin_return_address(N) with N above 0 (level 0 is supported; see Extensions) |
__builtin_return_address(1) is not supported on xtensa-none-elf: code for this target keeps no frame-pointer chain, so only level 0 (this function's own frame) can be found |
call0 or callx0 in inline asm |
call0 in Xtensa asm writes a0, which holds this function's return address under the windowed ABI: call a windowed function with call8/callx8 |
a density (.n) instruction |
ret.n is a 16-bit instruction of the density option, which this assembler does not emit: write ret, its 24-bit form |
.begin longcalls |
.begin longcalls is not supported: a long call is an l32r and a callx, which this assembler does not make of a call; write them |
__int128 |
__int128 does not exist on this target (it needs 64-bit registers; use long long) |
__attribute__((interrupt)) |
__attribute__((interrupt)) is not supported: ... |
--unwind-tables, -fasynchronous-unwind-tables, --exceptions |
unwind tables are not supported for xtensa-none-elf yet (...): EmbCC writes no Xtensa .eh_frame |
| C++ with exceptions (on by default) | C++ exceptions are not supported for xtensa-none-elf yet: EmbCC writes no Xtensa .eh_frame; compile with --no-exceptions. C++ itself compiles with --no-exceptions; see C++ |
SPARC#
SPARC V8, big-endian, as Gaisler's LEON3 implements it -- the processor of many spacecraft -- with soft float and LEON3's integer multiply and divide. The design and the facts behind it (read off clang 23) are in the SPARC plan.
Triples#
| Triple | Accepted aliases | Core | ABI |
|---|---|---|---|
sparc-none-elf |
sparc-unknown-elf, sparc-elf, sparc-gaisler-elf, sparc |
SPARC V8 with MUL/DIV (LEON3) | SPARC V8 ABI, soft float |
Options#
EmbCC emits one configuration: SPARC V8 with umul/smul/udiv/sdiv,
register windows, soft float, %g2-%g4 used as scratch. The flags that
say so are accepted; the rest are refused by name.
| Option | Accepted | Refused, for example |
|---|---|---|
--cpu=CPU, --isa=CPU, --tune=CPU |
leon3, leon4, v8, gr712rc, gr740, ut699 |
--cpu=v9 is not a SPARC V8 core with hardware multiply and divide: EmbCC emits LEON3 code (...) |
-msoft-float, -mno-fpu |
(no value) | -mhard-float is not supported: EmbCC emits soft-float SPARC code, which passes floating point in the integer registers |
-mno-flat, -mapp-regs, -mv8, -m32, --code-model=medlow, --endian=big |
(no value) | -mflat is not supported: EmbCC's SPARC code uses register windows (save and restore); -mno-app-regs, -m64, --code-model=medany, -mfix-* (no errata workarounds), --endian=little likewise |
Calling convention#
Every function opens a register window (save) and returns with ret; restore. Arguments are words with no padding: the first six in
%o0-%o5 (the callee's %i0-%i5), the rest at %sp+92; a long long or double is two words, high first, and may straddle %o5 and the
stack. Every structure, union, _Complex and long double is passed by
reference to a copy the caller makes. Results: %o0, or %o0:%o1 for 8
bytes; a _Complex float in %o0:%o1 and a _Complex double in
%o0-%o3; a structure, union, _Complex long double or long double
through a buffer whose address the caller stores at %sp+64, with an
unimp holding its size after the call, which the callee returns past.
va_list is a pointer over the argument words. tests/golden/sparc-abi.sh
checks this with EmbCC and clang calling each other.
clang calls the binary128 helpers (__addtf3, ...) with their operands
in registers, which no SPARC runtime implements; EmbCC calls them as it
calls any function taking a long double, by reference, and its
lib/rt defines them that way. Code compiled by clang that does
long double arithmetic therefore does not link correctly against
EmbCC's runtime. clang's own callee of a _Complex long double returns to
%i7+8, not past its caller's unimp -- a clang bug; EmbCC's follows the
ABI.
Objects and linking#
ELF32, big-endian, EM_SPARC, e_flags 0, RELA relocations: an address
is sethi/or with R_SPARC_HI22/R_SPARC_LO10, a call is call with
R_SPARC_WDISP30, data words R_SPARC_32. embld links these objects and
clang's (non-PIC), and refuses the GOT and PC-relative-address relocations
of PIC code by name. -Tstack emits a stub that sets %sp and jumps to
the entry.
Atomics#
An exchange of a word is swap; every other atomic is LEON3's casa
(ASI 10), after a stbar, and a read-modify-write is a casa loop. A
one- or two-byte atomic works on the aligned word around it, as GCC's and
clang's do: the loop rewrites only its lane, old ^ ((new ^ old) & mask),
so it is atomic against the neighbouring bytes too (a store to any of them
makes the casa fail and the loop go round with the word it saw), and a
compare-and-swap compares only its lane. The lane of offset a & 3 is
counted from the top of the word (big-endian). Test-and-set stores 1, as
__GCC_ATOMIC_TEST_AND_SET_TRUEVAL says, not ldstub's 0xff. No
__GCC_HAVE_SYNC_COMPARE_AND_SWAP_N is defined: the table is clang's for
--cpu=leon3, which defines none.
Assembly#
embcc -c assembles .s and .S files for SPARC, and inline asm,
file-scope asm blocks and __attribute__((naked)) functions are
assembled the same way, in GNU as's SPARC syntax (registers %g0-%i7,
%r0-%r31, %sp, %fp). The vocabulary is the LEON3's integer unit:
the ALU in its register and 13-bit immediate forms, with the
condition-code, tagged and mulscc forms, umul/smul/udiv/sdiv,
save/restore; every load and store (ld, ldub, lduh, ldsb,
ldsh, ldd, st, stb, sth, std, ldstub, swap) and its
alternate-space form (lda [rs1 + rs2] ASI, rd), LEON's casa; sethi
with %hi() and %lo(); every Bicc with its ,a annul bit; call,
jmpl, rett; rd/wr of %y, %psr, %wim, %tbr and
%asr1-%asr31; every Ticc; flush, stbar, unimp, nop; and
GNU's synthetic instructions -- mov (to and from the state registers
too), cmp, tst, not, neg, inc/dec(cc), clr/clrb/clrh,
btst, bset, bclr, btog, set, jmp, ret, retl, b and the
condition synonyms (bnz, bz, bgeu, blu). Delay slots are the
programmer's: nothing is reordered or filled. Floating-point and
coprocessor instructions and SPARC V9's forms (,pt, %xcc, ldx,
membar...) are refused by name.
A call or a branch to a symbol defined elsewhere carries
R_SPARC_WDISP30/WDISP22; %hi(sym), %lo(sym) and set sym, rd
carry R_SPARC_HI22/LO10 (for a label of the same file too, whose
address the linker decides), and .word sym R_SPARC_32. .align
counts bytes, ! starts a comment and # one at a line's start, as GNU as
has them for SPARC. tests/golden/sparc-asm.sh checks every form against
llvm-mc byte for byte and against llvm-objdump's mnemonic, a file's layout
against clang's assembler, and runs C and assembly together on QEMU's
leon3_generic.
In inline asm a register operand is written %o0, an "m" operand
[%o0]; the constraint letters are r, g, m, i, n, GCC's I-P
and a digit (an input tied to an output). Operands go in %o0-%o5,
%l0-%l5 and %i0-%i5 -- never a global, %sp/%fp, %o7, %i7 or
%l6/%l7 -- and a clobber list may not name %sp, %fp or %i7. A
value live across an asm keeps out of every register the asm changes: its
operands', its clobbers', the ones its text names and, when it calls
(call, or jmpl into %o7), the outs and %g1-%g4. -S writes the
instructions as .byte and their relocations as .reloc with SPARC's
names, which llvm-mc assembles back into the same object.
Predefined macros#
From clang --target=sparc-none-elf -mcpu=leon3 -msoft-float:
__sparc__, __sparc, sparc, __sparcv8, __sparcv8__,
__BIG_ENDIAN__, __BYTE_ORDER__ big-endian, SOFT_FLOAT,
__SIZEOF_LONG_DOUBLE__ 16 and __LDBL_MANT_DIG__ 113.
Runtime#
make rt-embedded builds librt.a and make libc-embedded builds
libc.a for sparc-none-elf (soft float, 64-bit division, binary128,
the C library on its bare-metal backend). tests/harness/sparc runs
programs on QEMU's leon3_generic board: the image is linked at
0x40000000 and loaded with -kernel, the APBUART at 0x80000100 is the
console, and the harness installs the window overflow and underflow trap
handlers a windowed program needs (any other trap prints its type and
address). A program for real LEON3 hardware needs the same handlers in its
startup, as BCC's provides.
Limitations#
| Construct | Diagnostic |
|---|---|
__builtin_frame_address(N) or __builtin_return_address(N) with N above 0 (level 0 is supported; see Extensions) |
__builtin_return_address(1) is not supported on sparc-none-elf: code for this target keeps no frame-pointer chain, so only level 0 (this function's own frame) can be found |
__int128 |
__int128 does not exist on this target (it needs 64-bit registers; use long long) |
__attribute__((interrupt)) |
__attribute__((interrupt)) is not supported: ... |
| a floating-point, coprocessor or V9 instruction in asm | asm instruction "faddd" is a floating-point instruction: EmbCC compiles soft float, and this assembler has no floating-point vocabulary / asm instruction "ldx" is SPARC V9's, and the LEON3 is a V8 |
%sp, %fp or %i7 in an asm's clobber list |
SPARC asm clobbers '%sp', which holds this function's stack pointer; EmbCC does not save it around an asm |
a V9 relocation operator (%hh, %lm, %gdop_*...) |
this relocation operator is SPARC V9's or position-independent code's; EmbLD applies %hi/%lo (R_SPARC_HI22/LO10), call and branch displacements and data words |
--unwind-tables, -fasynchronous-unwind-tables, --exceptions |
unwind tables are not supported for sparc-none-elf yet (...): EmbCC writes no SPARC .eh_frame |
| C++ with exceptions (on by default) | C++ exceptions are not supported for sparc-none-elf yet: EmbCC writes no SPARC .eh_frame; compile with --no-exceptions. C++ itself compiles with --no-exceptions; see C++ |
AVR#
Triples#
| Triple | Accepted aliases |
|---|---|
avr |
avr-none-elf, avr-elf, avr-unknown-none |
The code is for the ATmega328P's core (the AVR5 architecture, as on
the Arduino Uno), and without --mcu= the part is the ATmega328P.
Options#
| Option | What it does |
|---|---|
--mcu=atmega328p, atmega328, atmega168p, atmega168 |
Selects the part: its predefined macro (__AVR_ATmega168__ and so on) replaces __AVR_ATmega328P__. These four are the avr5 parts of the ATmega328P's datasheet family; they differ only in their memories. |
--mcu=atmega48, atmega88 and their a, p, pa forms |
Refused: --mcu=atmega88 is not supported: the atmega88 is an avr4 part, with no jmp or call instruction, and EmbCC's AVR code calls with call ... |
--mcu= any other part |
Refused: --mcu=atmega2560 is not supported: EmbCC's AVR backend generates code for the ATmega328P's avr5 core, and knows the memories and vectors of atmega328p, atmega328, atmega168p and atmega168 alone |
-mrelax, -mno-relax |
Accepted, and declined: EmbLD does not shorten call and jmp to rcall and rjmp, so the image is the same program, larger than avr-gcc's relaxed one. |
-mcall-prologues, -mno-call-prologues |
Accepted, and declined: every prologue and epilogue is written in its function rather than shared through __prologue_saves__. The calling convention is the same either way. |
-mdouble=32, -mlong-double=32 |
Accepted: what EmbCC does. |
-mdouble=64, -mlong-double=64 |
Refused: -mdouble=64 is not supported: double and long double are binary32 on this target, the same type as float, and a 64-bit double changes the ABI of every one |
-mint8 |
Refused: -mint8 is not supported: int is 16 bits on this target, ... |
| Part | Flash | SRAM | EEPROM | Vectors |
|---|---|---|---|---|
| ATmega328P, ATmega328 | 32 KB | 2 KB, 0x0100-0x08FF |
1 KB | 26 |
| ATmega168P, ATmega168 | 16 KB | 1 KB, 0x0100-0x04FF |
512 bytes | 26 |
--mcu= on another target is refused: -mmcu=atmega328p is an AVR option, and the target is x86_64-elf (--target=avr).
Data model#
int is 16 bits, pointers are 16 bits, and double and long double
are the same 4-byte IEEE binary32 format as float, which is avr-gcc's
default. Nothing is aligned: _Alignof of every type is 1. Plain char
is signed, matching clang --target=avr.
int and unsigned int arithmetic is carried out modulo 2^16 inside an
expression, not only when the result is stored: (0xffffu + 1) / 2 is 0,
and long f(int x) { return x + 1; } returns -32768 for 32767.
Program memory is a separate, word-addressed space. A function pointer
holds the function's word address (half its byte address), formed with
the _GS relocations (R_AVR_LO8_LDI_GS, R_AVR_HI8_LDI_GS) or, in
data, R_AVR_16_PM. String literals and const data are placed in RAM
and copied there from flash by the startup code, as with avr-gcc.
Calling convention: avr-gcc#
- Arguments are allocated from
r25downward tor8. Each argument occupies an even number of registers (its size rounded up to even), with its low byte in the lowest register of the run: the firstcharorintargument is inr24(r24:r25), a firstlonginr22–r25. An argument that does not fit abover8goes on the stack, and so does every later one. - Stack arguments are packed at their natural size, with no padding.
- A call to a variadic function passes every argument on the stack,
named ones included.
va_listis a 2-byte pointer, andva_argof a structure reads its bytes from the packed stack arguments. - A result is returned in registers with its size rounded up to a power
of two: 1–2 bytes in
r24(:r25), 3–4 bytes inr22–r25, 5–8 bytes inr18–r25. A composite larger than 8 bytes is written through a hidden pointer passed as the first argument. r1is always zero andr0is a scratch register.r2–r17andr28–r29are callee-saved;Y(r28:r29) is the frame pointer.
clang's AVR target places structure arguments differently (first field in the highest registers); EmbCC follows avr-gcc and avr-libc's documentation, not clang, for composites.
Object format#
ELF32, EM_AVR, e_flags EF_AVR_ARCH_AVR5, RELA relocations. Calls
and long jumps are call and jmp with R_AVR_CALL; addresses are
loaded a byte at a time (R_AVR_LO8_LDI, R_AVR_HI8_LDI).
Predefined macros#
From clang --target=avr -mmcu=atmega328p: __AVR__, __AVR, AVR,
__AVR_ATmega328P__, __AVR_ARCH__ (5), __AVR_HAVE_MUL__,
__AVR_HAVE_MOVW__, __AVR_HAVE_LPMX__, __AVR_HAVE_JMP_CALL__,
__AVR_2_BYTE_PC__, __SIZEOF_INT__ (2), __SIZEOF_POINTER__ (2),
__SIZEOF_DOUBLE__ (4). With --mcu= for another part, the part's
macro (__AVR_ATmega328__, __AVR_ATmega168P__, __AVR_ATmega168__)
stands in place of __AVR_ATmega328P__, as clang's table for that part
does; nothing else in the table changes.
The table also defines __flash, as
__attribute__((__address_space__(1))), which EmbCC implements: const
data kept in program memory and read with lpm (see
Data in program memory). It also
defines __BUILTIN_AVR_CLI, _SEI, _NOP, _NOPS, _SLEEP, _SWAP,
_WDR, _DELAY_CYCLES, _FMUL, _FMULS, _FMULSU and _INSERT_BITS,
one for each of avr-gcc's __builtin_avr_* that EmbCC has
(Extensions).
Headers#
<avr/io.h>, <avr/interrupt.h>, <avr/pgmspace.h>, <avr/wdt.h>,
<avr/sleep.h>, <avr/eeprom.h>, <avr/cpufunc.h>, <avr/common.h>,
<avr/sfr_defs.h> and <util/delay.h>, with avr-libc's interface, are
on the include path for this target alone; see
avr-libc-compatible headers.
Interrupt handlers#
__attribute__((signal)) and __attribute__((interrupt)) make a
function an interrupt handler (both together are interrupt, as with
avr-gcc: avr-libc's ISR_NOBLOCK writes them so); it saves SREG, r0, r1 and every
register the backend uses, clears r1, and returns with reti. See
Embedded programming.
Runtime#
Integer multiplication and division are helper calls with libgcc's names
(__mulsi3, __divsi3, __umodsi3, __muldi3, __divdi3, ...), from
lib/rt/avr.c and avr64.c; floating-point arithmetic calls the
binary32 helpers (__addsf3, ...) in lib/rt/avrfp*.c. make rt-embedded builds librt.a for avr, and for each -mmcu= part its
startup crt<part>.o and linker script <part>.ld, which the driver
links under --mcu=, and the AVR library, libc.a from lib/avr
(The AVR library) (see
Startup and the interrupt vector table).
Debugging#
-g writes DWARF 4 that llvm-dwarfdump --verify accepts and that gdb
(set architecture avr) and EmbDBG read, at --opt=none
and above:
- Addresses are 4 bytes, as avr-gcc writes them, although a pointer
is 2. A code address is a byte address in flash (the address in the
ELF symbol table, twice the word address the core counts in). A data
address is
0x800000plus the SRAM address: data memory is a separate space that also starts at 0, and GDB and QEMU's gdb stub put it at0x800000up. A global at SRAM0x13ahasDW_OP_addr 0x80013a. - The frame base is
Y,DW_OP_breg28 0: the prologue pointsr28:r29one byte below the frame, and every variable'sDW_OP_fbregoffset is the displacement the code uses for it (std Y+1, r24). A debugger reads register 28 as the pair.Yis set by the prologue, so locations are right from the second line-table row of a function on, which is where a breakpoint on the function stops. - Line table. Each function's first row is its entry, on the line the function is declared; the body's rows follow wherever the source line changes.
- Optimized code. At
--opt=basicand above, a variable the optimizer took out of memory has an empty location, which a debugger shows as<optimized out>; so does a parameter that is assigned after it was taken out of memory. A parameter that is never assigned keeps its slot, which the prologue writes. - Globals the unit defines are described with their type.
-g turns off this backend's register allocation at --opt=basic and above, so
that every variable stays in its slot; --opt=speed -g code can be about twice
the size of --opt=speed code. There is no .debug_frame: gdb finds a caller by
analyzing the prologue, and EmbDBG's backtrace stops at frame #0.
QEMU's arduino-uno has a gdb stub:
qemu-system-avr -M uno -nographic -bios fw.elf -S -gdb tcp::1234 &
embdbg fw.elf remote 1234
The stub's register layout, which EmbDBG uses: r0–r31 one byte each,
SREG one byte, SP two bytes, and PC four bytes, a byte address.
Limitations#
| Construct | Diagnostic |
|---|---|
| an interrupt handler with parameters | the AVR backend cannot lower an interrupt handler with parameters: the hardware calls it, so there is no caller to pass them and they would be read out of whatever the interrupted code left in those registers yet (function __vector_3) |
| an interrupt handler that returns a value | the AVR backend cannot lower an interrupt handler that returns a value: `reti` goes back to the interrupted instruction, and nothing is there to receive it yet (function __vector_3) |
__int128 |
__int128 does not exist on this target (it needs 64-bit registers; use long long) |
any C++ translation unit, except with --syntax-only, -E, -M or --deps-only |
C++ is not yet supported for avr: the C++ lowering assumes a 32-bit int and 4-byte pointers (integral promotions, member pointers, RTTI), and this target's are 16 bits |
Code compiled at --opt=none is large; an ordinary program may not fit the
part's 32 KB of flash unless built with --opt=basic or above.
At -Os EmbCC's AVR code is larger than clang's for the same source
(EmbLinkRTOS's conformance images: 21.8 KB to 44.8 KB, against clang's
16.3 KB to 33.9 KB), and its stack frames are several times larger
(-fstack-usage, EmbCC against clang: 77 and 6 bytes for a test function
that only sleeps, 30 and 5 for the kernel's embk_wait_commit). Stack
sizes chosen for avr-gcc can overflow under EmbCC; measure with
--stack-usage.
Running programs under QEMU#
EmbCC's test suites run every target's programs on an emulated board.
The harnesses in tests/harness/ show a complete startup, I/O and link
for each; the machines are:
| Target | Emulator and machine | How the run ends |
|---|---|---|
x86_64-elf |
qemu-system-x86_64 -cpu max, a Multiboot image, output on -debugcon stdio |
The guest prints an exit marker; isa-debug-exit stops QEMU |
aarch64-elf |
qemu-system-aarch64 -M virt -cpu cortex-a72 -semihosting |
Semihosting exit carries the status |
x86_64-linux-gnu, aarch64-linux-gnu |
A real Linux kernel booted with the program as PID 1 (-M q35 or -M virt) |
The kernel's panic message carries the exit status |
thumbv6m-none-eabi |
qemu-system-arm -M microbit (nRF51822, Cortex-M0), UART output |
The program prints ==EXIT n==; qrun.sh --until stops QEMU |
thumbv7m-none-eabi |
qemu-system-arm -M lm3s6965evb -cpu cortex-m3, UART output |
The program prints a sentinel; the run is bounded by a timeout |
thumbv7em-none-eabihf |
qemu-system-arm -M mps2-an386 -cpu cortex-m4 |
As above |
thumbv8m.main-none-eabi[hf] |
qemu-system-arm -M mps2-an505 -cpu cortex-m33 |
As above |
riscv32-unknown-elf, riscv64-unknown-elf |
qemu-system-riscv32 / qemu-system-riscv64 -M virt -bios none -m 8 |
The startup writes the SiFive test device after main returns |
avr |
qemu-system-avr -M uno, the image passed with -bios; -S -gdb tcp::PORT for a debugger |
The program prints a sentinel; qrun.sh --until stops QEMU when it appears |
tricore-none-elf |
qemu-system-tricore -M tricore_testboard -cpu tc27x, output through a TCG plugin |
The program prints ==EXIT n== and writes n to the board's test device |
sparc-none-elf |
qemu-system-sparc -M leon3_generic, the image passed with -kernel, APBUART output |
The program prints ==EXIT n== and executes ta 0 with traps disabled, which QEMU's LEON3 takes as a shutdown |
-bios none matters on RISC-V: without it QEMU runs OpenSBI first and
enters the image in supervisor mode. On AVR, QEMU refuses an image whose
ELF entry point is not 0.