Big-endian targets#
Byte order is a property of the target, like the data model. Every target
EmbCC had was little-endian, so "the low byte comes first" was written into
the compiler, the linker and the libraries without anyone deciding it. The
first big-endian target is mips-none-elf (also mips-unknown-elf,
mips-elf, mips): MIPS32r2, o32, soft float, the same backend as
mipsel-none-elf, run on QEMU's big-endian malta (qemu-system-mips).
The second is powerpc-none-eabi (powerpc-plan.md), which is big-endian
only. This page lists every place that depends on the byte order, what it
does big-endian, and the test that notices when it is wrong. A new big-endian
target (PowerPC, SPARC LEON3, m68k/ColdFire, OpenRISC, s390x) should walk
the same list.
The one question#
target_big_endian() (src/arch/target.h) answers it. It is set with the
triple (target_from_triple; on MIPS the sub-architecture column's 1 means
big-endian, and every PowerPC triple is) and nothing else changes it. -EB/-mbig-endian and
-EL/-mlittle-endian are accepted when they agree with the triple and
refused by name when they do not (the triple decides more than the order:
the runtime's directory, the predefined macros). Values go into target
memory through
target_put_uint(p, n, v)/target_get_uint(p, n): ann-byte value in the target's order;target_byte_shift(off, size, whole): where thesizebytes atoffof awhole-byte value sit in it, as a right shift.
The compiler never copies a host integer into an object, an instruction or an image byte for byte. The host is assumed little-endian, and the ELF writer refuses to run on a big-endian host rather than write the opposite order.
Where it matters#
| Place | What big-endian changes | Caught by |
|---|---|---|
src/targets/, src/arch/target.c |
the triples; target_rel_put_addend stores a REL addend into the field in the target's order |
mips-be-data, mips-be-exec |
src/arch/predef.c, src/arch/mips32eb/ |
its own generated table (tools/gen-predef.sh mips32eb): __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__, __BIG_ENDIAN__, MIPSEB, _MIPSEB, __MIPSEB, __MIPSEB__ |
predef |
src/elf/write.c |
EI_DATA = ELFDATA2MSB; the header, section headers, symbols and relocation entries are swapped field by field once their 32-bit shapes are final. Section contents are not touched: the code generator and the data writers already wrote them in order. A big-endian ELF64 is refused. |
mips-be-data (llvm-readelf-equivalent parse), every board test |
src/sema/sema.c lower_static_bytes |
every scalar, pointer-sized integer, float, double, complex part and __int128 half of a static initializer through target_put_uint; long double through ldf_encode_target |
mips-be-data |
src/sema/sema.c merge_bits |
a bit-field's bits into its unit's bytes: the unit read as one big-endian integer, and only the bytes the object owns written | mips-be-data, endian.c |
src/sema/sema.c flatten_init |
a wide string initializing an array: elements read back with target_get_uint |
mips-be-data (d_u16s, d_u32s, d_wstr) |
src/lex/lex.c lit_encode |
u"", U"", L"" units in the target's order (every literal, every consumer) |
mips-be-data, endian.c |
src/sema/ldfloat.c |
ldf_encode_target: the format's bytes reversed whole. ldf_encode stays little-endian, because ldf_to_double and the C++ evaluator read it as host order |
mips-be-data (d_ld, d_cd, d_cf) |
src/driver/main.c |
a scalar global's .data/.rodata image from g->init; -EB/-EL |
mips-be-data |
src/ir/irgen.c emit_ldconst |
a long double constant's pool bytes (ldf_encode_target) | sparc-exec (long-double-quad.c: run-time constants against the static folds) |
src/ir/irgen.c fb_wide |
the binary128 sign and exponent are its FIRST two bytes and its low doubleword the second (signbit, isnan, fabsl, copysignl, ...) | sparc-exec (fp-bits-long-double) |
src/sema/type.c bit-field layout |
bit_off is the field's shift from the least significant end of its unit AS LOADED; the positions in memory order are the same in both orders (gcc's layout), and ty_bf_mempos converts between them. Big-endian the first field is at the unit's high end. Union members and packed straddling fields likewise. |
mips-be-data (six bit-field structs, a union), endian.c |
src/ir/irgen.c bit-fields |
the unit loads and stores need nothing (they shift from the loaded value's LSB); the byte-at-a-time forms of a packed field across its unit put byte k at 8(n-1-k); the 128-bit byte forms are refused big-endian |
endian.c, mips-be-exec (packed-bitfields, refereed) |
src/debug/dwarf.c, src/debug/eh.c |
every multi-byte field through target_put_uint, the backpatched unit and header lengths included; DW_AT_data_bit_offset is the memory-order position |
mips-be-data (llvm-dwarfdump --verify, clang's bit offsets) |
src/as/gas.c emit_int |
.word/.half/.quad data in the target's order |
mips-be-gas, mips-be-exc |
src/driver/asmout.c |
a relocated MIPS instruction is decoded from its bytes in order for -S |
mips-be-asm (-S reassembled by llvm-mc) |
src/opt/opt.c ro_bytes |
a load from a constant global folded from its image: the first byte is the most significant | ro-globals, endian.c |
src/opt/opt.c pass_punfwd |
the word at +4 of an 8-byte store is the LOW half (fdlibm's GET_HIGH_WORD) | fp-bits, store-forward (refereed), endian.c |
src/opt/opt.c pass_storefwd, pass_mem2reg |
a read of a local narrower than the local is its first bytes -- the value's high end -- so neither forwards across differing sizes big-endian (the IR does not produce them; this keeps it that way) | -- |
src/arch/mips/emit.c |
instruction words in order (mips_set_big_endian, set by the driver for the compiler and by EmbLD from its objects -- the linker links this file without target.c) |
mips-encoding (bytes in memory order against llvm-mc's), every board test |
src/arch/mips/codegen.c |
see below | mips-be-exec, mips-be-abi |
src/link/link.c |
see below | every board test, mips-be-abi (clang's objects) |
lib/libc/src/math/fdlibm/fdlibm.h |
__IEEE_BIG_ENDIAN from __BYTE_ORDER__: which word of a double is first |
libc-embedded (mips-none-elf against x86-64), mips-be-exec |
lib/libc/src/stdio/format.c fp_of_ldouble |
a binary128's bytes most significant first | libc-embedded (sparc-none-elf's %Lf) |
lib/rt/softtf.c |
the 64-bit halves of the soft binary128 (tf_u128, union tfbits) in memory order |
sparc-exec (long-double-quad.c) |
tests/harness/mips/run.sh |
the board is chosen by the image's EI_DATA |
-- |
src/arch/ppc/emit.c |
instruction words always big-endian (ppc_put_word), there being no other order |
ppc-encoding (bytes in memory order against llvm-mc's) |
src/arch/ppc/codegen.c |
pairs high word first (r3:r4, PHI = the first register), as memory and the SVR4 ABI have them; a narrow variable at the end of its word home (obj_slot), and one aligned beyond its word with the object -- not the word -- on the alignment; small composites returned right-justified in r3:r4 |
ppc-exec, ppc-abi, ppc-data |
src/link/link.c apply_ppc |
every field written big-endian through ppc_put_word and a big-endian halfword for @ha/@l; a little-endian PowerPC object refused |
ppc-abi (clang's objects), ppc-refuse |
src/arch/coldfire/ |
the second big-endian target (m68k-none-elf), with no little-endian twin: the encoder writes every 16-bit word high byte first, a pair holds the high word in d0 and at the lower address, a narrow parameter's byte is its word's last (11(%fp)), a composite smaller than a word is right-justified in its argument word, and va_arg reads it there |
coldfire-exec, coldfire-abi, libc-embedded |
src/link/link.c apply_m68k |
every m68k field written big-endian whole; the -Tstack stub's words |
coldfire-exec, coldfire-refuse |
The MIPS backend#
o32 makes a register pair MIRROR memory: the word at the lower address of
the argument block travels in the lower-numbered register. So big-endian a
long long or soft double argument in a0:a1 has its HIGH word in a0, a
result comes back high word in v0, and a composite shorter than a word is
LEFT-justified in its register (clang: int k(struct {char c;} s) is
sra v0, a0, 24). The backend follows the same rule internally: a pair is
named by its first register r and holds the low word in PLO(r) and the
high in PHI(r); in memory the low word is at WLO (+4 big-endian) and the
high at WHI. Every place that took "the low word is in loc, at slot+0":
rd64/wr64,src64/dst64, 64-bit loads and stores, the narrow high-word shift (nshr), the 64-bit stack argument;- calls: each argument word to its register (
sh_), helper operands (args64x2, the divide's folded constant), results (wr64(V0..)), a 32-bit result read into a 64-bit value (v0 either way),IR_RET; - a 32-bit read or write of a 64-bit vreg is its low word (
reg_of,slot32) -- copy propagation lets any operation read one at its width; - the prologue: a pair parameter's words, a composite's partial last word
(
store_tail),pack_tailfor an argument; - a narrow integer VARIABLE's object is at the end of its four-byte home
(
obj_slot): the home is also read and written as a whole word (rd, the incoming argument register), and big-endian the value's low bytes are the word's last; - unaligned accesses:
lwlatoffandlwratoff+3(the reverse of little-endian), a halfword's high byte first; - the jump table's words and the delay-slot filler's re-read of the last
instruction;
.MIPS.abiflags' flags1 word.
EmbLD#
A big-endian ELF32 object is read by turning its descriptions -- header,
section headers, symbols, relocation entries -- into host order in place,
once (be_normalise); its section contents stay big-endian. Every MIPS
relocation reads and writes its field as a word through mips_get_word
(HI16/LO16/PC16 change the word's low half), so the AHL rule and the REL
addends work unchanged. The image is written as before and its headers,
program headers, section headers and symbol table are swapped back
(be_image); a linker script's BYTE/SHORT/LONG/QUAD go out in the
target's order (FILL is big-endian in either, as GNU ld writes it). The
-Tstack stub is encoded in the objects' order. Objects of both orders in
one link are refused by name.
Refused big-endian, by name#
- a packed bit-field across more than 8 bytes, or a packed
__int128bit-field (the 128-bit byte forms are little-endian, and no big-endian target has__int128); - an x87 long double (
ldf_encode_target), a big-endian ELF64 object, and a big-endian host; --embx(EMBX images are EmbLinkOS's, little-endian);- the
.embdbgsidecar (EmbDBG reads little-endian objects; the image's own DWARF is correct) -- a note, not an error; - C++ (refused on every ILP32 target already; its constant evaluator models memory little-endian, so a 64-bit big-endian target must refuse it too until that changes).
Tests#
tests/golden/mips-be-exec.sh: the exec corpus at -O0..-Os on the board. The programs whose expected value assumes little-endian layout (a union read through another member, a double's{lsw, msw}) are judged against clang's result for the same triple, as the LP64 ones are.tests/golden/mips-be-abi.sh: EmbCC and clang objects calling each other (small structs, long long and double pairs, variadics).tests/golden/mips-be-data.sh:tests/golden/be-data.c's every object, bytes and relocations, against clang's; and the same values printed on the board.tests/exec/endian.c, on every board: finds the order at run time and checks unions, memcpy, static images, bit-fields through bytes, wide strings and network order against it.tests/golden/predef.sh: themips32ebtable against clang's.mips-be-asm,mips-be-link,mips-be-gas,mips-be-exc,mips-be-switch,mips-be-slots,mips-be-access: the little-endian MIPS goldens run for mips-none-elf (MIPS_BE=1) against llvm-mc and clang for mips-unknown-elf and on qemu-system-mips;mips-encodingchecks the encoder's big-endian bytes;mips-refusethe triples, the header and the-EB/-ELrules;libc-embeddedlib/libc on the board against x86-64.tests/exec/llong-bitfield.c: along longbit-field's unit is eight bytes on an ILP32 target (it was loaded as along, on every 32-bit target, little-endian too -- found by mips-be-data's program).- Little-endian byte identity: lib/libc, lib/rt and tests/exec built for thumbv7em, riscv32 and mipsel at -O0, -O2 and -Os (and tests/exec at -O1 -g) by the compiler before and after are identical object files, but for tests/exec/llong-bitfield.c, whose fix is the point of it; EmbLD's mipsel images of the corpus are identical too.
Each was shown to fail against a mutant of the code it guards.
Adding the next big-endian target#
PowerPC (powerpc-plan.md) walked this list; what it found is in the table above.
- A triple that sets
g_big_endian: aBEtriple in the target database (src/targets/), orbig_endian = 1in the data model of an architecture that has one byte order; and a generated predef table from the reference compiler. - The backend: its instruction encoder writes words through an order it is told; pairs and memory halves as its ABI lays them out (check what the ABI does with a sub-word composite in a register -- o32 and the PowerPC SysV ABI both left-justify); unaligned access sequences.
- The linker: its relocation patcher reads and writes fields through the
machine's own accessors, not
put32. - Its ELF class: the writer swaps ELF32 only today; ELF64 needs the same (and C++'s evaluator, before C++ is allowed).
- lib/: anything
#if-ing on the architecture for word order (fdlibm keys on__BYTE_ORDER__and needs nothing). - The board, and the three goldens above with its triple.