Skip to content

feat(#43): x86-64, SysV and Win64 conventions, register model, selection - #356

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

octalide merged 11 commits into
devfrom
feat/43

Conversation

@octalide

@octalide octalide commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Closes #43

What

x86-64 end to end: the SysV and Win64 convention rows, the x86 Isa record (selection table, hooks, cas loops, frame code, moves, declared opcodes), the NaN default and saturating float to integer conversion. Also the shared machinery the target needs: memory operands built by rules, loops that address memory, swaps through partial readers, containment-aware clobbers, and the Win64 page probe and SEH prologue shape.

Plan, one commit or more each, shared changes kept apart from the x86 rows:

  1. Nans.propagate states its default NaN (Default). x86-64 is propagate with the negative quiet NaN, and the folder's invalid operation gives it.
  2. The selection builds memory operands: Source.address (Addressed, Factor, a fixed base for string moves) and instruction.fits_address. Tie halves take views.
  3. A copy cycle swaps through partial readers (Lower a copy cycle through partly overlapping locations with no free scratch #258): storage.lies and storage.counterpart, and swap_any in copy lowering.
  4. Clobbers read containment, so win64's Kept{xmm6,128} keeps xmm6 and clobbers ymm6. Page probes go through a helper (Code.probe, Step.probe, lay.Request.helper), and the SEH prologue shape.
  5. Loops address memory (lrow.Source.address). An explicit fixed write counts as the write an implicit read needs, so lrow.check takes the register file.
  6. The sysv64 and win64 rows (x86/abi.mach): SysV eightbyte classification with the AL count, Win64 positional slots, variadic floats in both registers, i128 by reference and returned in xmm0.
  7. The x86 Isa record and its registry entry, with the division widths moved to 32 and 64.
  8. The x86_64 linux lane, the exit stubs and the docs.

Choices the issue did not settle:

  • Integers narrower than 64 bits leave the upper bits undefined, and i1 is a clean 0 or 1. Byte and word arithmetic runs at 32 bits, so i8, i16 and i32 share one location through views.
  • i1 is not held in a flags class. Remat is not implemented, so a condition is a 0 or 1 in gpr64, and a compare is fused with the jcc, setcc or cmov that reads it in one expansion. That is "cmp then jcc in the FLAGS class" as far as the current engine allows.
  • Division widths are 32 and 64 (were 8 to 64). The narrow legalisation widens a byte or word division and keeps its traps, so no rule splits ah:al.
  • Every fence is mfence. An expansion cannot be empty, and a compiler-only barrier has no row.
  • J9's binding records need no x86 step of their own: the swap, the loops and the probe step run inside copy lowering, loop expansion and frame finishing, which carry them.
  • Address constants and func.addr are rip-relative lea. The x86 address model is none, so the hooks read the symbol directly and leave TLS unselected.
  • __chkstk is called as mov eax, n; call __chkstk; sub rsp, rax, declared by the abi legalisation when a function is defined.

Limits

  • The binary16 NaN rule is State a NaN rule for formats a target computes through lowering #359's. binary16 stays unheld on x86-64.
  • Native runs and links are gated by mink#70, mink#73 and mink#201, so the x86_64 lane and the windows link are not run here.
  • x87 results (long double) are refused as room. Vectors beyond SSE2's add, sub, mul, logic, float arithmetic, widening and moves are refused, and the shuffles, builds, inserts, extracts and reductions are declared with no owner.
  • FMA is selected under FMA3 only. A build without it has no rule for fma.
  • No thread-local access, GOT or PLT reach is selected.
  • The SysV classifier trusts the extension's register class for vectors. Fixed probes only: no dynamic alloca probing.
  • Linux enters _start with the stack 16-byte aligned, so the frames below the example stubs are 8 off. The x86 frame code uses unaligned xmm moves.

Moved to #342

  • GI3 gate under SysV and under Win64: i8, i32 and i64 views with no copy, cmp and jcc, [b+i*4+d], a call's clobbers from its row, xmm6 surviving and the ymm upper clobbered.
  • SysV and Win64 classification golden tables, the AL count, Win64 variadic floats in both registers, Win64 i128.
  • Every opcode at every native type has a rule or a declaration, the fixed operands of division and shift, and xchg breaking a copy cycle.
  • pmovsx, pmovzx and pmulld under SSE4.1 and the punpck and pmuludq fallbacks below it.
  • Kills (End a live value at a write to an overlapping register through masc's kills #277): ZEROES ends the whole value and KEEP does not, x87 st never ends an overlapping value, and mm3 ends only itself.
  • The Lower a copy cycle through partly overlapping locations with no free scratch #258 cycle through a 16-bit register and its high byte with no scratch free.
  • x86-64 NaN propagate with the negative default, folder and run agreeing.
  • Saturating float to integer at every width and sign, the boundaries and NaN.
  • The examples on x86-64 Linux, and the windows link with mink.

Ran locally: mach check . -a '*', mach build . -a cli and mach fmt --check ..

Checklist

Nans.propagate carries the nan an invalid operation gives where no
operand is one, positive canonical or negative. the folder gives it, and
x86-64's sse2 rows state the negative quiet nan, the real indefinite.
a virtual half of a tie is named through the view its position takes of
its class, so a two-address row over a narrower view of a value stands.
the address source builds a memory operand from a base, an index scaled
by a literal or derived power of two, a displacement or a symbol, a
segment and a pc-relative mode, admitted by the row's memory position.
a reader of a register lying within either end of the swap reads the
register of its class at the same place within the other, through masc's
within. the bits of the source no pending destination covers must hold
no value past the copy, and every pending pair is tried for the swap.
…h shape

a call keeps a register lying within a wholly kept one and clobbers one
holding a kept one, refusing only a straddle. a frame past a page under a
row whose prologue calls a probe routine moves through the frame code's
probe step, the routine declared by the convention lowering. windows'
unwind codes take one fixed allocation, the saves and a frame pointer set
at most 240 bytes above the stack pointer.
A loop step names the memory at the address a register operand of the
loop holds (lrow.Source.address), as x86's lock cmpxchg names its
destination. An implicit register a step reads is written for certain
by an earlier step's write through a fixed register, so lrow.check
takes the register file. A built address takes a fixed register as its
base, as the string moves address through rdi and rsi, and a
materialisation rule builds an address of its constant alone.
# Conflicts:
#	src/machine/compile.mach
The x86-64 members carry their convention rows. SysV classifies each
eightbyte as clang does, counts the vector registers of a variadic call
in al and takes the whole value to the stack when its registers run out.
Win64 passes by position, a variadic float in both registers, an i128
by reference and returns it in xmm0, keeps xmm6 to xmm15, and probes a
page through __chkstk with the seh prologue shape.

The division widths are 32 and 64: the byte and word forms divide ah:al
and dx:ax, which no rule splits, so the narrow legalisation widens a
byte or a word division and keeps its traps.
The x86-64 table holds integers, conditions and pointers in gpr64 and
floats and vectors in xmm, naming each row's view of one location, so a
value read at several widths is never copied. Compares fuse with the
jcc, setcc or cmov that reads them, division, high products and
variable shifts go through masc's fixed operands, and loads and stores
fold [b + i*s + d] and rip-relative symbols. Float to integer saturates
branch-free. sse4.1's pmovsx, pmovzx, pmulld and roundss are taken
where admitted, with punpck and pmuludq below. Atomics are lock xadd,
xchg and cmpxchg, and cas loops for the rest. The frame code moves
through lea and the page probe through __chkstk. xchg swaps two general
registers at each view. The record joins masc's x86 set in the
register family's registry.
The example lane runs x86_64 linux gnu beside the riscv lanes, its
__mirl_exit the exit system call, and windows has one through kernel32's
ExitProcess for a link with mink. The lane script takes an
architecture's own exit stub where it has one. The comments that said
masc serves x86-64 with no set are corrected.
The nodes, tests, sources and expansions of the x86 table stand in their
own groups, as RISC-V's do, each group of rules and of their expansions
under a comment saying what it selects, and the three forms of a compare
are named by their operand.
@octalide
octalide marked this pull request as ready for review October 17, 2026 01:51
@octalide
octalide enabled auto-merge October 17, 2026 01:53
@octalide
octalide added this pull request to the merge queue Oct 17, 2026
Merged via the queue into dev with commit 1cd7163 Oct 17, 2026
2 checks passed
@octalide
octalide deleted the feat/43 branch October 17, 2026 02:25
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-J1: x86-64, SysV and Win64 conventions, register model, selection, end to end

1 participant