Skip to content

feat(#42): AArch64, AAPCS64 and darwin, selection, end to end - #354

Merged
octalide merged 17 commits into
devfrom
feat/42
Oct 17, 2026
Merged

octalide merged 17 commits into
devfrom
feat/42

Conversation

@octalide

@octalide octalide commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Closes #42

What

AArch64 end to end on the register family: the AAPCS64 and darwinpcs convention rows, the AArch64 Isa record with its selection table, frame code, moves and exclusive loops, the frame's paired saves and the compact unwind shape, and start stubs for Linux and darwin.

  • Conventions (src/target/register/aarch64/abi.mach): one classifier for both rows, as clang classifies. HFA and HVA of 1 to 4 members are read from abi.view, unions included. A value over 16 bytes travels by reference, x8 carries an indirect result, v8 to v15 keep their low 64 bits, and a darwin unnamed value goes to the stack. Convention.narrow is new and states how an integer below 32 bits travels: undefined upper bits under aapcs64, extended under darwinpcs. The abi legalisation accepts vector-kind pieces for floats and short vectors.
  • Axes: the NaN rule as FPProcessNaNs runs it with FPCR.DN clear (signalling first, positive default NaN), the SVE z unit scaled through the vector length, and the address models: linux pie with TLS descriptors, darwin pie with thread-local variable thunks.
  • Selection (rules.mach with hook.mach): integer, float, conversion, memory, atomic, call, address and constant operations. movz and movk, movn or a single orr for constants. cbz, cbnz, tbz and tbnz where they fit, csel and fcsel with the compare fused. LSE atomics under the extension and ldaxr and stlxr loops (loop.mach) without it. Float to integer saturates by fcvtzs and fcvtzu, with a branch-free cmp and csel clamp for a narrow destination. smull, smull2, smlal, smlal2, addv and saddlv, and their unsigned forms, are selected directly.
  • Frame: Code.pairs and the pair steps let a set move two adjacent saves in one instruction, and aarch64 states stp and ldp. keeps_shape no longer refuses pair_saves, and admits a compact unwind frame whose record is its first pair saved (own commit, J1 touches the same function).
  • Start stubs: test/example/exit-aarch64.s and exit-aarch64-darwin.s, on J1's scaffolding. No aarch64 lane target is added, since its run is in mirl-V1: verification of the v7 transition #342.
  • Docs: doc/isa/aarch64.md, and the frame doc's pairs and compact shape.

Ran locally: mach check . -a mirl clean at each commit pushed. No unit tests added, per the v7 process.

Plan

  1. Convention.narrow and vector pieces in the abi legalisation. Done.
  2. aapcs64 and darwinpcs rows, address models. Done.
  3. Selection table, hooks, loops, frame code, moves, registry record. Done.
  4. Paired saves and the compact unwind shape. Done.
  5. Start stubs for linux and darwin. Done.
  6. Docs. Done.
  7. Port J1's contracts after feat(#43): x86-64, SysV and Win64 conventions, register model, selection #356. Done: Nans.propagate carries a Propagation of the default NaN and a priority (own commit), AArch64 states FPProcessNaNs, and loads and stores take constant offsets.
  8. Replace the stand-in handles with masc's after chore(#357): take masc 24126ea for aarch64 and x86 register handles #358, and merge J9's feat(#45): debug info and unwind, locations, DWARF 5, line and frame records #355. Done: every aarch64 file reads masc.isa.aarch64.gen_register, and no new aarch64 stage needs binding records carried, since the frame's paired saves are prologue instructions finish already records.
    8a. Read the rules and loops as maintained source. Done.
  9. Merge origin/dev, check, mark ready. Done.

Choices

  • Every float and vector is held in the 128-bit v class, scalar rows naming the s or d view, so every copy is one exact move and the convention passes them in v0 to v7.
  • Convention.narrow is a new field rather than an assumption about extension.
  • darwinpcs pairs 16-byte aligned values in even registers, as clang does, and the Pairs comment now says so.
  • The exclusive loops use ldaxr and stlxr for every ordering, so one loop serves each operation and size.
  • A paired prologue pairs saves the layout already put side by side and moves the rest one at a time.
  • Nans.propagate now carries Propagation{default, priority}, with Priority either first (x86) or signalling (AArch64). The folder's choice of NaN operand reads it. Propagation.fused states fma's operand order: addend first on AArch64 (FPMulAdd calls FPProcessNaNs3 so), unordered on x86, whose 132, 213 and 231 forms take the IR's operands in whichever order the selection chose, so the fold refuses an x86 fma with two or more NaN operands as nan.
  • The offset forms are not built on Source.address: AArch64's rows in masc state the base register and the offset as separate operands, not one memory operand, so a load of ptr.add(base, c) binds the base and the constant directly, as RISC-V's do.
  • The start stubs follow J1's scaffolding: exit-aarch64.s and exit-aarch64-darwin.s beside exit.s and the x86 stubs.

Moved to #342

  • The AAPCS64 and darwinpcs classification golden table across both rows.
  • The saturating float to integer boundary test.
  • The examples under qemu-aarch64.
  • The darwin links with mink for both architectures.

Limits

  • Mach-O and Windows aarch64 fixups have no binding in masc: a darwin address, darwin's call and every thread-local sequence are stated and refused unbound when encoded. ELF TLS waits on masc#441, the others on mink's neutral aarch64 relocation kinds and masc's rebinding.
  • Link and run checks are gated by mink#70 (PE), mink#73 (Mach-O images) and mink#201 (a writer refuses any object with frames).
  • aapcs64win is not carried (mink#70).
  • masc states some row roles other than the instruction does: integer stores' Rt written, float loads' Rt read, stp and ldp operands read and written, float data processing destinations read and written, and the smlal and umlal accumulators written alone. Liveness reads those roles. The multiply-accumulate rules hold only once masc states the accumulator read and written, so multiply and add select apart for now. masc#456 corrects these roles and is taken at the next sync, past this PR's pin (masc 24126ea).
  • Loads and stores take a base and a constant offset, scaled (ldr, str) or unscaled (ldur, stur). The register offset form [base, index, lsl #n] is not selected: masc states its extend and amount as enumerants, so an index is added into the base first.
  • The v8 to v15 saves are whole q registers, and darwin's compact encoding describes x19 to x28 and d8 to d15 only where the layout places them at the encoding's slots, and the rest by call frame information.
  • Vector operations beyond the NEON subset, and every z and p value, are the vectorisation job's (mirl-J13: Vectorisation #72).

Checklist

  • Linked to an issue
  • Conventional Commit messages
  • Build and tests pass (mach check . -a '*' and mach build . -a cli locally, CI builds)

…gister carries a piece

A convention row states whether an integer narrower than 32 bits travels
extended by its signedness or with its upper bits undefined, as aapcs64 leaves
them and darwinpcs and risc-v extend them. The abi legalisation carries a float
piece in a vector register as in a float one, a whole vector there as it is,
and 16 bytes there as two 64 bit lanes, as aarch64's v registers take them.
…ss models

The two rows classify as clang does: floats, short vectors and homogeneous
aggregates of up to four members in v0 to v7, read from the type view, x8 for
a result by reference, 16 byte aligned pairs at an even register, slot packing
under aapcs64 and natural packing and stacked unnamed values under darwinpcs.
x18 is reserved on darwin, and each row states its tls descriptor clobbers.

AArch64 states its nan rule as fpcr.dn clear runs it, propagate, and the sve
unit as a scalable z unit under sve. The registers are named through a stand-in
handle module until masc publishes generated ones (masc#440).
… the frame code and the moves

The table covers the integer, float, conversion, memory, atomic, call,
address and constant operations a native type reaches, and the neon subset
with smull, smull2, smlal, smlal2, addv and saddlv selected directly. LSE
atomics are chosen where admitted and exclusive-pair loops otherwise. The
record joins it to masc's aarch64 set in the register family's registry.
…as a pair

Code gains pairs, none by default, and Step a stored and a loaded pair.
aarch64 states stp and ldp for an x doubleword and a v register's d and q
views, refusing a pair out of their reach as unpaired.
…ompact unwind takes a frame whose record is its first pair

keeps_shape no longer refuses pair_saves: the prologue and epilogues pair two
adjacent saves of one size where the frame code has a pair and move them one
at a time otherwise. Compact unwind admits a non-empty frame whose frame
pointer and link are saved first, adjacent and above every other save, the
stp fp, lr shape darwin's arm64 encoding reads.
…rl_exit

test/example/exit holds one stub per architecture family and system,
riscv-linux.s as before and aarch64-linux.s and aarch64-darwin.s, and the
example lane assembles the one of each lane target.
…g leaves, and ties an accumulator only where masc reads and writes it
# Conflicts:
#	src/target/register/aarch64/axes.mach
…d-in

masc 24126ea publishes a register.Id per row and a class index per class, the
same values the stand-in held, so every aarch64 file names gen_register and
register.mach is gone.
Every expansion is named for the pattern and form it serves, grouped by kind
with a comment over each group, and the loop operand lists are named for the
step they feed. The rules, constants, holds and loops are unchanged.
# Conflicts:
#	doc/machine/emit.md
#	src/machine/frame/code.mach
#	src/machine/frame/lay.mach
#	src/target/register/isa.mach
#	test/example.sh
…first or the first signalling

Nans.propagate carries a Propagation of the default nan and a priority. x86
gives the first nan operand, and the folder chooses the first signalling one
where a target states that priority. aarch64 keeps its rule as it was here,
stated in the next commit.
A load or store of ptr.add(base, c), an extending load among them, binds
the base and the constant: the scaled unsigned immediate where it reaches,
the unscaled signed nine bits otherwise.
…dd's operands are taken in

Propagation gains fused: the ir's operands, the addend first as aarch64's
FPMulAdd calls FPProcessNaNs3, or unordered as x86's fma forms, whose
operand order follows the form the selection chose. The folder takes an
fma's nan in the stated order and refuses one with two or more nan operands
where none is stated.
@octalide
octalide marked this pull request as ready for review October 17, 2026 03:14
@octalide
octalide enabled auto-merge October 17, 2026 03:15
@octalide
octalide added this pull request to the merge queue Oct 17, 2026
Merged via the queue into dev with commit a8e8913 Oct 17, 2026
2 checks passed
@octalide
octalide deleted the feat/42 branch October 17, 2026 03:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

mirl-J2: AArch64, AAPCS64 plus darwin and the SVE register file, selection, end to end

1 participant