Jump to content

Do not begin to migrate content here, it may be wiped without notice. More info.

ArchitectureSpecificsMemo

From Debian Wiki
Revision as of 21:58, 18 July 2025 by Zeha (talk | contribs)

This page is a draft. Please help enhancing it by adding lines and filling tables. Comments at the bottom of the text are also welcome.

This is just a quick array to recall some of the specifics of the architectures found in the Debian project.

DEB ARCH sizeof(short) sizeof(int) sizeof(long) sizeof(long long) sizeof(float) sizeof(double)
any 2 4 sizeof(void *) 8 4 8
DEB ARCH sizeof(void*) sizeof(long double) max_align_t alignment char endian stack grows page sizes

User-visible page size returned by getauxval(AT_PAGESZ) or getconf PAGESIZE, the granularity of the memory map

float

H=hard, S=soft, P=partial, +=excess precision, ~=not usual standard

double

Note that the columns refer to C types. H=hard, S=soft, P=partial, +=excess precision, ~=not usual standard)

long double

Note that the columns refer to C types. H=hard, S=soft, P=partial, +=excess precision,-=long double precision < __float128, ~=not usual standard)

sizeof(time_t)

With _TIME_BITS=64 the size is 8 on all archs

GCC pre-defined macro(s)
alpha 8 16

long double changed size from 8 to 16 in Lenny

16 signed LE down 8K ? ? ? 8 __alpha__
any-amd64, amd64, kfreebsd-amd64, hurd-amd64 8 16 16 signed LE down 4K H H H-

Use 80bits extended precision see extended precision

8 __x86_64__ && __LP64__ it's a common gotcha for x32, you need to use __LP64__/__ILP32__ to tell them apart
arc 4 16 ? unsigned LE down 8K H H H 8 __arc__
arm 4 ? 8 unsigned LE down ? S S S 4 __arm__
arm64 8 16 16 unsigned LE down 4K, 16K, 64K H H S 8 __aarch64__ && __LP64__
arm64ilp32 4 16 ? unsigned LE down 4K, 16K, 64K H H S 4 __aarch64__ && __ILP32__
armel 4 8 8 unsigned LE down 4K S S S 4 __arm__ && __ARM_EABI__
armhf 4 8 8 unsigned LE down 4K H H H 4 __arm__ && __ARM_EABI__ && __ARM_PCS_VFP
avr32 4 8 ? unsigned BE down ? ? ? ? 4 ?
hppa 4 8 8 signed BE up ? ? ? ? 4 __hppa__
any-i386i386, kfreebsd-i386, hurd-i386 4 12 168 prior to GCC 7 signed LE down 4K H+ H+ H-

Use 80bits extended precision see extended precision

4 __i386__
ia64 8 16 16 signed LE both

The conventional stack grows down from the top of the stack mapping, but there is also the Register Backing Store which grows up from the bottom of the stack mapping at the same time

? H H H 8 __ia64__
loong64 8 16 16 signed LE down 4K-64K H H S 8 __loongarch__ && __loongarch_lp64 && __loongarch_double_float
m68k 4 12 2 signed BE down 4K S? S? S? 4 __m68k__
mips 4 8 8 signed BE down 4K-64K H H H 4 __mips__ && _MIPSEB && _MIPS_SIM==_ABIO32
mipsel 4 8 8 signed LE down 4K-64K H H H 4 __mips__ && _MIPSEL && _MIPS_SIM==_ABIO32
mips64 8 16 16 signed BE down 4K-64K H H H 8 __mips__ && _MIPSEB && _MIPS_SIM==_ABI64
mips64el 8 16 16 signed LE down 4K-64K H H H 8 __mips__ && _MIPSEL && _MIPS_SIM==_ABI64
mipsn32 4 8 ? signed BE down 4K-64K H H H 4 __mips__ && _MIPSEB && _MIPS_SIM==_ABIN32
mipsn32el 4 8 ? signed LE down 4K-64K H H H 4 __mips__ && _MIPSEL && _MIPS_SIM==_ABIN32
powerpc 4 16

long double changed size from 8 to 16 in Lenny

16 unsigned BE down 4K-64K ? ? ? 4 __powerpc__
ppc64 8 16 16 unsigned BE down 4K-64K ? ? ? 8 __powerpc__ && __powerpc64__ && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
ppc64el 8 16 16 unsigned LE down 4K-64K-16M H H H~ 8 __powerpc__ && __ppc64__ && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
riscv64 8 16(8)

The RV64 ISA spec defines long double as 16 bytes, but older toolchains (pre-"riscv-gcc-6.1.0") have implemented long double as 8 bytes on RV64; cf. https://github.com/riscv/riscv-gcc/commit/54b21fc5ae83cefec44bc2caed4a8c664c274ba0

16 unsigned LE down 4K 4k is the default page size on all RISC-V systems. Depending on the chosen virtual memory mode (Sv32/Sv39/Sv48) additional page sizes are available as well: Sv32 supports 4k and 4M pages, Sv39 supports 4k, 2M and 1G pages and Sv48 supports 4k, 2M, 1G, and 512G pages H H H~ 8 __riscv && __riscv_xlen==64

Older, pre-mainline RISC-V toolchains used __riscv__ && __riscv64

s390 4 16

long double changed size from 8 to 16 in Lenny

? unsigned BE down ? ? ? ? 4 __s390__
s390x 8 16 8 unsigned BE down 4K, 1M, 2G H H H 8 __s390x__
sh4 4 8 4 signed LE down 4K ? ? ? 4 __sh__
sparc 4 16

long double changed size from 8 to 16 in Lenny

? signed BE down ? ? ? ? 4 __sparc__
sparc64 8 16 16 signed BE down 8K, 64K, 512K, 4M, 32M, 256M, 2G, 16G ? ? ? 8 __sparc__ && __arch64__
x32 4 16 16 signed LE down 4K H H H-

Use 80bits extended precision see extended precision

8 __x86_64__ && __ILP32__
DEB ARCH multiarch ELFCLASS ELF machine EM_ ld.so(8) uname -m

Used to classify CPU architectures in CMake and many ad-hoc build systems, but note that this is a kernel and/or libc specific name despite referring to the CPU

Meson

Reference table of Meson hardware architecture names

qemu

e.g. i386 results in qemu(-system)-i386(-static)

alpha alpha-linux-gnu 64 ALPHA /lib/ld-linux.so.2 alpha alpha alpha
amd64 x86_64-linux-gnu 64 X86_64 /lib64/ld-linux-x86-64.so.2 x86_64 x86_64 x86_64(-microvm)
hurd-amd64 x86_64-gnu 64 X86_64 /lib/ld-x86-64.so x86_64-AT386 x86_64 x86_64(-microvm)
kfreebsd-amd64 x86_64-kfreebsd-gnu 64 X86_64 /lib/ld-kfreebsd-x86-64.so.1 x86_64 x86_64 x86_64(-microvm)
arc arc-linux-gnu 32 ARC_COMPACT2 /lib/ld-linux-arc.so.2 arc arc -
arm arm-linux-gnu 32 ARM /lib/ld-linux.so.2 arm*

Many names starting with arm, e.g. armv5tel

arm arm
arm64 aarch64-linux-gnu 64 AARCH64 /lib/ld-linux-aarch64.so.1 aarch64 aarch64 aarch64
arm64ilp32 aarch64-linux-gnu_ilp32 32? AARCH64? aarch64? aarch64
armel arm-linux-gnueabi 32 ARM /lib/ld-linux.so.3 arm* arm arm
armhf arm-linux-gnueabihf 32 ARM /lib/ld-linux-armhf.so.3 arm* arm arm
avr32 avr avrqemu-system-avr only
hppa hppa-linux-gnu 32 PARISC /lib/ld.so.1 parisc* parisc hppa
i386 i386-linux-gnu 32 386 /lib/ld-linux.so.2 i?86i386, i486, i586, i686 x86 i386
hurd-i386 i386-gnu 32 386 /lib/ld.so i?86-AT386i386, i486, i586, i686 x86 i386
kfreebsd-i386 i386-kfreebsd-gnu 32 386 /lib/ld.so.1 i?86i386, i486, i586, i686 x86 i386
ia64 ia64-linux-gnu 64 IA_64 /lib/ld-linux-ia64.so.2 ia64 ia64 -
loong64 loongarch64-linux-gnu 64 LOONGARCH /lib64/ld-linux-loongarch-lp64d.so.1 loongarch64 loongarch64 loongarch64
m68k m68k-linux-gnu 32 68K /lib/ld.so.1 m68k m68k m68k
mips mips-linux-gnu 32 MIPS /lib/ld.so.1 mips mips mips
mipsel mipsel-linux-gnu 32 MIPS /lib/ld.so.1 mips mips mipsel
mips64 mips64-linux-gnuabi64 mips64 mips64 mips64
mips64el mips64el-linux-gnuabi64 64 MIPS /lib64/ld.so.1 mips64 mips64 mips64el
mipsn32 mips64-linux-gnuabin32 mipsn32
mipsn32el mips64el-linux-gnuabin32 mipsn32el
powerpc powerpc-linux-gnu 32 PPC /lib/ld.so.1 ppc ppc ppc
ppc64 powerpc64-linux-gnu 64 PPC64 /lib64/ld64.so.1 ppc64 ppc64 ppc64
ppc64el powerpc64le-linux-gnu 64 PPC64 /lib64/ld64.so.2 ppcle ppc64le ppc64 ppc64le
riscv64 riscv64-linux-gnu 64 RISCV /lib/ld-linux-riscv64-lp64d.so.1 riscv64 riscv64 riscv64
s390 s390-linux-gnu 32 S390 /lib/ld.so.1 s390 s390 (s390x)
s390x s390x-linux-gnu 64 S390 /lib/ld64.so.1 s390x s390x s390x
sh4 sh4-linux-gnu 32 SH /lib/ld-linux.so.2 sh sh4 sh4
sparc sparc-linux-gnu 32 SPARC32PLUS /lib/ld-linux.so.2 sparc sparc sparc(32plus)
sparc64 sparc64-linux-gnu 64 SPARCV9 /lib64/ld-linux.so.2 sparc64 sparc64 sparc64
x32 x86_64-linux-gnux32 32 X86_64 /libx32/ld-linux-x32.so.2 x86_64 x86_64


Alignment

Note that there is alignment on the machine language level and alignment in C. Proper misalignment (for example in a packed struct) is no problem in C and allowed almost everywhere (except of SPARC, where you will receive SIGBUS on a program execution, misaligned variable/memory access) and the compiler will do the right thing (like translating a read of a variable to two loads at machine language level). On the other hand most of the constructs that lead to unaligned access not properly handled by the C compiler (especially most constructs involving pointer casting) have undefined behaviour anyway, so compiling them with optimisation enabled can cause strange effects on all architectures. Read more on a Wikipedia article Data structure alignment.

On the other hand, unaligned synchronization primitives (atomics, etc) tend to be unsupported or extremely slow. Extra joy if they cross cacheline or page boundaries.

alpha

Some alpha may emulate see kernel source

arc

ARCv2 processors (ARC HS38 and ARC HS48 variants, 1-4 cores) support unaligned access in hardware, except for "atomic instructions" where special considerations apply. ARCv2 features 2 types of atomic instructions: the first for 32-bit data ("llock"/"scond" pair) and the second for 64-bit data ("llockd"/"scondd"). Those atomic instructions must work on data aligned naturally. I.e. "llock"/"scond" must be only used on 32-bit word-aligned data, "llockd"/"scondd" must be only used on 64-bit double-word aligned data. If an unaligned access is done by "atomic instruction" in user-space, the offending application gets forcefully killed with visible mention of a segmentation fault. An unaligned access done by “atomic instruction” in kernel-mode will result in an unrecoverable "Illegal instruction" exception.

armel/armhf/arm64

High-level summary for software engineers: Don't do unaligned access. It's always inefficient, sometimes extremely inefficient, and in various cases is not permitted at all.

The short answer for the ARM case is that ARMv6 and up guarantee that the processor can be configured such that some unaligned accesses are fine (or at least just inefficient). Linux by default enable this mode and trap:

  • All <=32-bit load/store operations can be unaligned.
  • NEON load-stores can be unaligned (unless an explicit alignment is specified in the encoding).

But:

  • VFP load/stores must be naturally aligned.
  • Load-multiple/store-multiple and ldrd/strd do not work with <32-bit alignment, but can sometimes get fixed up by the kernel at a substantial performance penalty.

PUSH/POP usually requires 32-bit alignment, but then the ABI requires 64-bit alignment of the stack, so that is less of an issue.

Unaligned load-exclusive/store-exclusive operations are not supported.

No unaligned accesses are permitted to memory regions of Device or Strongly-ordered type. But some permutation of this will be true for most architectures, and in user-space this would only affect things prodding /dev/mem or otherwise mmaping from a device driver.

ARM64 similarly makes it possible to trap unaligned accesses, but if that is not enabled (which it isn't by Linux), everything apart from load-exclusive/store-exclusive, load-acquire/store-release and Device memory accesses will be handled by hardware.

avr32

Unaligned access is ok for 32bits word but not for 16bits or 64bits word see kernel source

i386/x32/amd64

Unaligned access is slower (but not as much as with other architectures as it is done in hardware and not in software). Moreover some MMX/SSE/vector operation need proper alignment.

Unaligned sync primitives are supported, but there's active effort to defeature them, with Ice Lakes and newer allowing the kernel to trap.

ia64

Depending of config may fix with trap handler unaligned access

riscv64

Short version: Don't use unaligned access.

Long version: Technically the RISC-V ISA specification allows unaligned access for load and store instructions that are part of the base ISA, but only with significant limitations:

  • Naturally aligned loads and stores are guaranteed to be atomic, but this is not the case for unaligned loads and stores. This can lead to hard-to-find bugs, so unaligned access should really be avoided.
  • Support for unaligned access is usually not implemented in hardware and instead emulated in a trap handler, which makes it very slow.

All synchronization primitives (LR/SC and the atomic memory read-modify-write operations (AMOs)) only allow naturally aligned access.

Floating point

First read academic paper about the pitfall of doing multi arch floating point computation and the classical "What Every Computer Scientist Should Know About Floating-Point Arithmetic" from here.

i386

i387 is a strange beast that suffer from excess precision. Moreover long double is only 80 bits

Internal computation are done with 80 bits of precision that lead to strange and not intuitive result.

mips/mipsel/mips64el

Some mips processors do not contain a hardware floating point unit. In this case the kernel will emulate all FPU instructions at a large performance cost.

C/C++ Preprocessor Symbols

Generally, GNU C tends to define the symbol

__arch__

A list of some common arch-specific symbols can be found on the Pre-defined Architecture Macros page.

Signedness

When programming in C, variables can be signed or unsigned.

Example:

  • `unsigned char c;` - explicit unsigned, 0 <= c <= 255
  • `signed char c;` - explicit signed, -128 <= c <= 127
  • `char c;` - implicit (un)signedness, depending on architecture

To force a signedness, either declare it explicitly or use the gcc command line option -f(un)signed-char. The gcc defaults differ only due to optimisation. Other compilers may not have this issue.

Architecture baselines

Each Debian architecture has a baseline indicating the oldest or least capable CPU on which the architecture can be used. The baseline can change between Debian releases. The baseline is mostly defined by the gcc-N package, which is configured to produce baseline binaries when options like -march= are not used.

If a package uses options that enable additional CPU features such as -msse in particular source files, then the maintainer of that package is responsible for making sure those CPU features are only used on CPUs that support them. ioquake3's Altivec support on PowerPC is an example of this technique.

amd64

x86_64 with no optional extensions (psABI baseline). The core specification includes MMX, SSE and SSE2 so these are OK, but SSE3 and up are not guaranteed. (The default is -march=x86-64, but not -march=x86-64-v2 or later.)

See psABI (Table 3.1: Micro-Architecture Levels) for amd64 ABI level. Wikipedia summary: https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels

arc

A fully featured ARC HS with additional support for double-precision FPU ("-mcpu=hs38_linux"). And this is a default "-mcpu" for ARC starting from GCC 11.

To be more precise it's a baseline ARC HS with HW division ("-mdiv-rem"), atomic instructions ("-matomic"), 64-bit loads/stores ("-mll64"), the most advanced multiplier (including quad half-word & vector operations, "-mmpy-option=plus_qmacw") and double-precision FPU ("-mfpu=fpud_all").

arm64

aarch64 (ARMv8-A) with no optional extensions. VFPv4 and NEON are OK, but ARMv8.1-A and up are not guaranteed.

armel

armv5te since Debian 10 'buster' (gcc-8 8-20171215-1).

Before that, armv4te.

armhf

armv7 with VFPv3-D16 floating point. NEON is not guaranteed.

i386

  • Pentium4 since Trixie. This includes most notably SSE2. This was made necessary by LLVM's lack of support for processors without SSE2.
  • i686 since Debian 12 'bookworm'. There's no MMX nor SSE.
  • Before that, "almost" i686 (no "long NOP"/NOPL) since Debian 9 'stretch' (gcc-6 6.1.1-1).
  • Before that, i586 since gcc-4.9 4.9-20140411-1 (2014).
  • Before that, i486 since gcc-4.1 4.1ds7-0exp7 (2006).
  • Before that, i386.

See SYSTEM V APPLICATION BINARY INTERFACE, Intel386 Architecture Processor Supplement for more information. psABI (Table 3.1: Micro-Architecture Levels) for amd64 ABI level may be also relevant, for newer ABI level.

mips, mipsel

MIPS32R2 since Debian 9 'stretch'.

Before that, MIPS II.

mips64el

MIPS64R2 and up.

powerpc

(TODO: exact baseline unknown). Altivec is not guaranteed.

powerpcspe

(TODO: exact baseline unknown). Altivec is impossible, even with runtime detection.

ppc64

(TODO: exact baseline unknown). Altivec is not guaranteed.

(Initially Altivec was in the port baseline, but was later removed to support some embedded systems)

ppc64el

POWER8 and up.

s390x

z196 since Debian 10 'buster'.

loong64

(TODO: exact baseline unknown).

Obtaining this information

via the dpkg build logs

Starting with dpkg 1.22.0, its build process will print some of the architecture attributes as part of its configure summary.

Search for «Arch attributes» in the dpkg build logs.

via the C pre-processor

echo | cpp -dM | sort

via a special tool

#include <alloca.h>
#include <stddef.h>
#include <stdio.h>
#include <time.h>

static void test_size_align(const char *name, size_t size, size_t align) {
  printf("sizeof(%s) = %zu\n", name, size);
  if (size != align) printf("alignment(%s) = %zu\n", name, align);
}

#define TEST_SIZE_ALIGN(type, name) \
  struct test_align_##name { char a; type b; }; \
  test_size_align(#type, sizeof(type), offsetof(struct test_align_##name, b))

static void test_endian(void) {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
  printf("byte order = little endian\n");
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
  printf("byte order = big endian\n");
#endif
}

static void test_char(void) {
  if ((int)(char)-1 == -1) printf("char signedness = signed\n");
  else if ((int)(char)-1 == 255) printf("char signedness = unsigned\n");
}

static void test_stack(void) {
  void *a = alloca(8);
  void *b = alloca(8);
  if (a > b) printf("stack = grows down\n");
  else printf("stack = grows up\n");
}

int main(void) {
  TEST_SIZE_ALIGN(short, short);
  TEST_SIZE_ALIGN(int, int);
  TEST_SIZE_ALIGN(long, long);
  TEST_SIZE_ALIGN(long long, long_long);
  TEST_SIZE_ALIGN(float, float);
  TEST_SIZE_ALIGN(double, double);
  TEST_SIZE_ALIGN(long double, long_double);
  TEST_SIZE_ALIGN(void *, pointer);
  TEST_SIZE_ALIGN(time_t, time_t);
  test_endian();
  test_char();
  test_stack();
  return 0;
}

via autoconf

AC_INIT(archtest, 0.1)
AC_CHECK_SIZEOF(short)
AC_CHECK_SIZEOF(int)
AC_CHECK_SIZEOF(long)
AC_CHECK_SIZEOF(long long)
AC_CHECK_SIZEOF(float)
AC_CHECK_SIZEOF(double)
AC_CHECK_SIZEOF(long double)
AC_CHECK_SIZEOF(void*)
AC_C_BIGENDIAN()
AC_C_CHAR_UNSIGNED()

See also