Differences between revisions 191 and 192
Revision 191 as of 2022-09-12 22:28:55
Size: 22725
Editor: GuillemJover
Comment: Update predef macro URL
Revision 192 as of 2022-10-24 01:07:16
Size: 22925
Editor: PaulWise
Comment: add some more links
Deletions are marked like this. Additions are marked like this.
Line 340: Line 340:
 * [[https://sourceforge.net/p/predef/wiki/Architectures/|Pre-defined Compiler Macros Wiki - Architectures]]
 * [[https://gcc.gnu.org/onlinedocs/cpp/Predefined-Macros.html|GCC Predefined Macros]]

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.

Summary

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)

char

endian

stack grows

page sizes1

float2

double3

long double3

GCC pre-defined macro(s)

alpha

8

164

signed

LE

down

8K

?

?

?

__alpha__

any-amd645

8

16

signed

LE

down

4K

H

H

H6

__x86_64__ && __LP64__7

arc

4

16

unsigned

LE

down

8K

H

H

H

__arc__

arm

4

?

unsigned

LE

down

?

S

S

S

__arm__

arm64

8

16

unsigned

LE

down

4K, 16K, 64K

H

H

S

__aarch64__ && __LP64__

arm64ilp32

4

16

unsigned

LE

down

4K, 16K, 64K

H

H

S

__aarch64__ && __ILP32__

armel

4

8

unsigned

LE

down

4K

S

S

S

__arm__ && __ARM_EABI__

armhf

4

8

unsigned

LE

down

4K

H

H

H

__arm__ && __ARM_EABI__ && __ARM_PCS_VFP

avr32

4

8

unsigned

BE

down

?

?

?

?

?

hppa

4

8

signed

BE

up

?

?

?

?

__hppa__

any-i3868

4

12

signed

LE

down

4K

H+

H+

H6

__i386__

ia64

8

16

signed

LE

both9

?

H

H

H

__ia64__

?loongarch64 10

8

16

signed

LE

down

4K-64K

H

H

H

__loongarch__ && __loongarch64 && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 

m68k

4

12

signed

BE

down

4K

S?

S?

S?

__m68k__

mips 11

4

8

signed

BE

down

4K-64K

H

H

H

__mips__ && _MIPSEB && _MIPS_SIM==_ABIO32

mipsel

4

8

signed

LE

down

4K-64K

H

H

H

__mips__ && _MIPSEL && _MIPS_SIM==_ABIO32

mips64

8

16

signed

BE

down

4K-64K

H

H

H

__mips__ && _MIPSEB && _MIPS_SIM==_ABI64

mips64el

8

16

signed

LE

down

4K-64K

H

H

H

__mips__ && _MIPSEL && _MIPS_SIM==_ABI64

mipsn32

4

8

signed

BE

down

4K-64K

H

H

H

__mips__ && _MIPSEB && _MIPS_SIM==_ABIN32

mipsn32el

4

8

signed

LE

down

4K-64K

H

H

H

__mips__ && _MIPSEL && _MIPS_SIM==_ABIN32

powerpc

4

164

unsigned

BE

down

4K-64K

?

?

?

__powerpc__

ppc64

8

16

unsigned

BE

down

4K-64K

?

?

?

__powerpc__ && __powerpc64__ && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__

ppc64el

8

16

unsigned

LE

down

4K-64K-16M

H

H

H~

__powerpc__ && __ppc64__ && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__

riscv64

8

16(8)12

unsigned

LE

down

4K 13

H

H

H~

__riscv && __riscv_xlen==64 14

s390

4

164

unsigned

BE

down

?

?

?

?

__s390__

s390x

8

16

unsigned

BE

down

4K, 1M, 2G

H

H

H

__s390x__

sh4

4

8

signed

LE

down

4K

?

?

?

__sh__

sparc

4

164

signed

BE

down

?

?

?

?

__sparc__

sparc64

8

16

signed

BE

down

8K, 64K, 512K, 4M, 32M, 256M, 2G, 16G

?

?

?

__sparc__ && __arch64__

x32

4

16

signed

LE

down

4K

H

H

H6

__x86_64__ && __ILP32__

DEB ARCH

multiarch

ELFCLASS

ELF machine EM_

ld.so(8)

uname -m15

Meson

qemu16

alpha

alpha-linux-gnu

alpha

alpha

amd64

x86_64-linux-gnu

64

X86_64

/lib64/ld-linux-x86-64.so.2

x86_64

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

arm*17

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

avr18

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?8619

x86

i386

hurd-i386

i386-gnu

32

386

/lib/ld.so

i?86-AT38619

x86

i386

kfreebsd-i386

i386-kfreebsd-gnu

32

386

/lib/ld.so.1

i?8619

x86

i386

ia64

ia64-linux-gnu

64

IA_64

/lib/ld-linux-ia64.so.2

ia64

ia64

?loongarch64 10

loongarch64

m68k

m68k-linux-gnu

32

68K

/lib/ld.so.1

m68k

m68k

m68k

mips

mips-linux-gnu

32

/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

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

sh

sh4

sh4

sparc

sparc-linux-gnu

sparc

sparc

sparc(32plus)

sparc64

sparc64-linux-gnu

sparc64

sparc64

sparc64

x32

x86_64-linux-gnux32

32

X86_64

/libx32/ld-linux-x32.so.2

x86_64

x86_64

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

  2. H=hard, S=soft, P=partial, +=excess precision, ~=not usual standard (2)

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

  4. long double changed size from 8 to 16 in Lenny (5 6 7 8)

  5. amd64, kfreebsd-amd64 (9)

  6. Use 80bits extended precision see extended precision (10 11 12)

  7. it's a common gotcha for x32, you need to use __LP64__/__ILP32__ to tell them apart (13)

  8. i386, kfreebsd-i386, hurd-i386 (14)

  9. 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 (15)

  10. The architecture name has not yet been proposed for integration into dpkg (16 17)

  11. There are also 6 mips*r6* ports: see MipsRev6 (18)

  12. 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 (19)

  13. 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 (20)

  14. Older, pre-mainline RISC-V toolchains used __riscv__ && __riscv64 (21)

  15. 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 (22)

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

  17. Many names starting with arm, e.g. armv5tel (24)

  18. qemu-system-avr only (25)

  19. i386, i486, i586, i686 (26 27 28)

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 done is 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. The core specification includes MMX, SSE and SSE2 so these are OK, but SSE3 and up are not guaranteed.

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

i686 since Debian 9 'stretch' (gcc-6 6.1.1-1). There's no MMX nor SSE.

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.

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'.

loongarch64

(TODO: exact baseline unknown).

Obtaining this information

via the C pre-processor

echo | cpp -dM | sort

via a special tool

#include <alloca.h>
#include <stddef.h>
#include <stdio.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_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


CategoryDeveloper CategoryPorts