amd64.pl
The naive x86-64 SysV backend for the phase-one IR in plain (non-Fil-C) mode (milestone 2). It is the reference the later instruction selection and register allocation milestones are checked against, so it favours being obviously right over being fast.
The output is a list of asm.pl lines, instruction and directive terms, which asm:print_asm/1 prints as GNU as text. Functions that call setjmp are refused.
The frame
Every temporary lives in its own 8-byte slot below %rbp, and each phi destination also has a shadow slot. alloc moves %rsp down by its size rounded up to 16, which keeps calls aligned.
┌──────────────────────────┐ ← %rbp │ tmp(N) slots, 8 bytes │ -8, -16, … │ shadow(N) slots, 8 bytes │ │ padding to 16 bytes │ ├──────────────────────────┤ ← %rsp after the prologue │ alloc'd blocks, each a │ │ multiple of 16 bytes │ └──────────────────────────┘ ← %rsp
Instructions
Every instruction loads its operands into fixed scratch registers (%rax, %rcx, %rdx, %xmm0, %xmm1), computes, and stores %rax back to its destination's slot. A float computation moves the bits into the SSE2 scratch registers and its result back to %rax: slots hold bits, not floats. A w result is always zero-extended into %rax, so every slot is 8 bytes and a w read is the low 4 bytes of its slot.
Phis are lowered on each incoming edge as a parallel copy: every incoming value goes into its phi's shadow slot, then every shadow into its phi. A branchV, IfTrue, IfFalse gets a labelled edge for its false side, so each edge carries its own copies.
Variable conventions
| Variable | Means |
|---|---|
| K | a class, w, l, s or d, or where stated a store width, b, h, w, l, s or d |
| S | an AT&T suffix |
| R | a register: a, c, d, si, di, r8 or r9 |
| RN | the name of R at a width |
| Ctx | the function context, fName, Ret, Slots, Blocks |
Lines are the asm.pl lines of the plain (non-Fil-C) Module. Throws a nanofil_error naming the first item the backend cannot emit.
Functions
The context of a function's code is f(Name, Ret, Slots, Blocks). filc_amd64 builds the same term to call the exported nonterminals, so its shape is shared.
the lines of a function or data definition.
frame(+Params, +Blocks, -Slots, -Size): one slot per temporary name and one shadow slot per phi destination. Slots maps a key, tmp(N) or shadow(N), to its offset below %rbp.
The next 8-byte slot below %rbp, counting slots in Count.
Float arguments use an independent bank, with source-ordered stack overflow. Keep the integer-only emission unchanged.
spill the incoming argument registers, from the I-th on, to the slots of Params.
Name is the name of register Reg at Width, a store width b, h or a class w, l. Only a, c and d have byte and halfword names.
Blocks and jumps
AsmLabel is the local assembly label of block Label.
AsmLabel labels the edge block for the false side of the jnz ending block Label.
the code for the jump ending block From.
the copies for the phis of To on the edge From -> To, then the jump. All sources are read before any destination is written.
copy each phi's shadow slot into its destination's slot.
Instructions
the code for one instruction.
the store of a call's result Res from %rax; nothing for void, and the plain backend has no tuple results.
Ins stores the Width-sized part of register Reg to (%rax).
Local Fil-C selection can encode an integer RHS directly. The reference selector below stays unchanged. Only RCX's dead scratch result differs; RAX and the operation's defined flags agree, even for a zero shift.
GNU as accepts the signed spelling of every imm32 bit pattern. Qword operations sign-extend that encoding: e.g. +0xffffffff is NOT -1.
The code that computes Op at Class on Args into %rax, zero-extended for class w. Ctx is the f/4 context; filc_amd64 also calls this for its integer instructions.
x86 masks the count in %cl to the operand width, as the IR specifies.
the x86 shift Insn of the first of Args by the second, a w count in %cl.
the division of that Signedness of the first of Args by the second, leaving in %rax its Result, the quotient (quot) or remainder (rem).
%rdx:%rax (or %edx:%eax) divided by register C, extended from %rax by Signedness.
Operands
the move of the constant N, reduced modulo Class, into Reg.
The move that reads Key's frame slot at Class into RegName. Fails if Key has no slot in Ctx.
The move that writes all 8 bytes of register RegName to Key's frame slot. Fails if Key has no slot in Ctx.
Data
the directive for a data item of width W and value Value.
SSE2. Private slots and compute_op's result hold bits, not host floats. Only ABI boundaries use XMM registers as the persistent value carrier.
Shared SysV bank allocation for a flattened scalar/flight-word list. Each exhausted bank spills independently; stack positions retain order.
SSE2 has no unsigned qword conversion. Subtract 2^63 in the high half, convert the now signed-representable value, then restore bit 63. Numeric labels are local to each sequence, including repeated calls.
Preserve a sticky low bit before halving; converting then doubling is correctly rounded even above 2^63, unlike subtract/convert/add.