Conversation
DrXiao
reviewed
Sep 8, 2026
| actual sysroot. | ||
| ```shell | ||
| $ out/shecc --dynlink -o fib tests/fib.c | ||
| $ chmod +x fib |
Collaborator
There was a problem hiding this comment.
I think chmod +x fib should be preserved because the generated ELF file is not executable by default. Therefore, chmod is still necessary.
Otherwise, the following error may occur:
$ qemu-arm -L /usr/arm-linux-gnueabihf/ fib
Error while loading /home/drxiao/workspace/sysprog/shecc/fib: Exec format error
DrXiao
reviewed
Sep 8, 2026
|
|
||
| # An Arm64 runner executes the Arm output natively, so this covers the paths | ||
| # that the emulator would otherwise stand in for. | ||
| host-arm: |
Collaborator
There was a problem hiding this comment.
I think the host-arm job could also be extended to validate the AArch64 backend (through native execution).
A dynamically linked build resolves fflush through the PLT to the host libc, but lib/c.h defines stdout as the plain file descriptor 1, which is not a FILE pointer. Handing that to glibc dereferences address 1. That build is also the only one whose stdio buffers, so it is the one that most needs the flush: without it, a diagnostic written to a pipe is lost when the compiler dies. NULL means every stream to the host libc and is ignored by the unbuffered embedded one, so it suits both. Only fatal, usage_error and error_at change here. The eleven other sites in this file share the hazard and deserve their own look.
Generate ELF64 images for AArch64 Linux under AAPCS64, static and dynamically linked, with the stage-1 and stage-2 compilers byte identical. The backend maps the allocator's registers onto x0-x7 and x20-x22, keeps the synthetic global frame in x19, and reaches libc through an eager-bound PLT of ADRP/ADD/LDR/BR entries. Two shared assumptions did not survive a 64-bit pointer. The register allocator now records which operands of an instruction are addresses, because pointer arithmetic has to widen the int index beside the address and a comparison has to read the address whole; an array counts, since what reaches the instruction is its decayed base. The global-frame slot reserves a full pointer rather than the historic 32-bit word. Both are inert on the 32-bit targets. The output mode is set explicitly once the file is closed, since it depends on which libc opened it rather than on how the output links: the embedded lib/c.c passes 0775 to openat, while glibc's fopen yields 0666. Setting it also needs fchmodat on the targets whose asm-generic table has no chmod at all, where 90 is capget. Images separate their load segments by 64 KiB so they stay loadable under any of the granules AArch64 Linux may use. QEMU-user on an x86-64 host always presents 4 KiB pages and cannot observe this.
Neither had a case in the shared suite. A char or short must stay negative through promotion and through a store and reload, which an LP64 backend holding it in a 64-bit register can get wrong in either direction. A pointer must be tested for truth over its whole value, and that starts to matter once a target holds one in a register wider than an int. The case which separates a full-width test from a low-word one cannot be forced, since pinning a pointer whose low word is zero would need a 64-bit literal, so the test covers agreement between the paths instead: the backend picks a different width for a branch, for a logical negation and for a comparison, and each is reached with a null and a non-null pointer. The array form of the first belongs here too, but it fails on the Arm backend, whose char elements load zero-extended, so it stays in the AArch64 gate until that is fixed.
Name the new target where the other three are listed, and say what its images require of a loader: they separate the load segments by 64 KiB so they run under any AArch64 page granule. Both examples also lose their chmod, since the compiler now marks its own output executable, and the dynamic one gains the Arm invocation it had lost alongside the AArch64 one.
Run both link modes for the new target, and install the AArch64 sysroot the dynamic runs need under QEMU. This cannot observe everything the target needs: QEMU-user on an x86-64 host always presents 4 KiB pages, so a load-segment separation too small for a larger granule still passes here.
An Arm64 Linux host runs this target's output itself, so asking QEMU to stand in for it hides the one thing the emulator cannot show: QEMU-user maps the image on its own and, on an x86-64 host, always presents 4 KiB pages, leaving the 64 KiB separation between the load segments unexercised. The host answers this alone, without the fastfetch probe the Arm target needs to tell apart boards that can run its 32-bit output. It has to be the right kernel as well as the right architecture, since what comes out is an AArch64 Linux ELF. The runner job grows an architecture dimension, and the assertion that the output really did run natively now covers both targets rather than letting an emulated job pass as a native one. With the emulator gone the dynamic build resolves its interpreter and libc from the running system, so no cross toolchain is wanted on that host.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds AArch64 Linux as a fourth target, generating ELF64 images under AAPCS64 in both static and dynamically linked form, with the stage-1 and stage-2 compilers byte identical. The backend maps the allocator's registers onto x0-x7 and x20-x22, keeps the synthetic global frame in x19, and reaches libc through an eager-bound PLT of ADRP/ADD/LDR/BR entries. Images separate their load segments by 64 KiB so they stay loadable under any of the granules AArch64 Linux may use.
A 64-bit pointer did not survive two assumptions the 32-bit targets had shared. The register allocator now records which operand of an arithmetic instruction is an address, because pointer arithmetic has to widen the int index beside it, and the global-frame slot reserves a full pointer rather than the historic 32-bit word. Both are inert on arm, riscv and x64. Separately, the diagnostic paths now flush every stream before the compiler exits: a dynamically linked build resolves fflush through the PLT to the host libc, but lib/c.h defines stdout as the plain descriptor 1, so the old code handed glibc address 1 to dereference.
The shared suite gains cases for narrow signed values and for pointer truthiness, neither of which it covered before; a pointer has to be tested over its whole value, which only starts to matter once a target holds one in a register wider than an int. README names the new target alongside the other three, and the CI matrix runs it in both link modes.
Verified locally on x86-64:
make checkpasses for arm, riscv, x64 and arm64 statically linked, and for arm64 withDYNLINK=1, each including the stage-1/stage-2 byte-identity check. The AAPCS64 ABI suite reports 12/12 at stage 0 and stage 2 in both link modes.Two things are deliberately out. QEMU-user on an x86-64 host always presents 4 KiB pages, so neither CI nor the local runs can observe the 64 KiB segment separation actually mattering; that needs real hardware with a larger granule. And writing the array form of the narrow-signed test exposed a defect in the Arm backend, where char elements load zero-extended, so that case stays in the AArch64 gate rather than breaking the shared suite. Fixing the Arm backend belongs in its own change.
Summary by cubic
Adds a self-hosting AArch64 Linux backend that emits static and eager-bound dynamic ELF64 binaries under AAPCS64. This expands support beyond ARMv7, RV32, and x86-64 while keeping stage-1 and stage-2 output byte-identical.
New Features
gcc-aarch64-linux-gnu,qemu-aarch64, and a matching sysroot.Bug Fixes
charloads.Written for commit 83672e1. Summary will update on new commits.