filc_amd64.pl
The Fil-C backend for x86-64: filc_abi.pl lowers checked functions to frame, call and entry products; this module places their words and emits asm.pl lines whose GNU as text links with code clang builds for filnix staging Fil-C 0.687. Checked pointer-memory emission remains here, as does global-data serialization and getter initialization.
Every rule is chosen to be easy to check, not fast; docs/m3.md has them in full.
The frame
Each temporary has an 8-byte slot below %rbp, except a check's proof, which stays in %rax:%rcx for the access fused with it. A p temporary's slot holds its address; its lower lives in a lowers[] entry of the function's filc_frame, which the prologue pushes on thread->top_frame and every return and exception exit pops. Values are never kept in registers across a call or safepoint, so every live capability is always in a slot the GC scans. The prologue zeroes lowers[], so the GC sees no stale lowers.
Temporaries whose closed live intervals are disjoint share a slot (frame_slots.pl). A function that calls setjmp is the exception: each of its temporaries gets a slot of its own.
┌────────────────────────────┐ ← %rbp │ 8-byte slots: temporaries, │ -8, -16, … │ phi shadows, thread, aux, │ │ retbuf, jmp_buf pointers │ ├────────────────────────────┤ │ lowers[NLowers-1] │ │ … │ │ lowers[0] │ -FrameBase+16 ├────────────────────────────┤ │ origin │ -FrameBase+8 │ parent │ -FrameBase ├────────────────────────────┤ │ padding to 16 bytes │ └────────────────────────────┘ ← %rsp = %rbp - Size
Instructions
- The IR must be checked (ir.pl, pizlonate.pl): each check comes right before the raw access or call it covers, and the two are emitted as one instruction,
fusedCheck, Access: the check's sequence, then the operation. Function identity checks come fromcheck_function, not from inferring that an indirect call needs one. - Pointer loads and stores go through the object's aux array; a pointer store ensures the aux array and runs the store barrier.
flightsupplies its address calculation and lower source separately. Emission preserves neither by inference from aparithmetic operation; pizlonation has already chosen both.allocis a heap allocation.- An edge to
pollLruns a pollcheck: a call of filc_pollcheck_slow when the thread's state asks for one. - Scalar integer and SSE2 instructions share amd64:
compute_op//5. Local mode selects integer immediates before scalar stack caching. - Float bits have no lower. At ABI boundaries they travel in XMM registers, or in generic payload words with zero aux (docs/abi-findings.md).
Calls and entries
Calls use the fast convention: the thread, the callee's lower, then the argument words (a buffered return passes its buffer first). A function that throws pops its frame and returns has_exception = 1, in %al or in the return buffer; a caller that sees it propagates it the same way.
Origins preserve the existing can_throw=0: exception flags propagate, but the runtime will not unwind a C++ exception through this frame.
Every function gets a getter, a function object and a generic entry, so its address can be taken; an exported one also gets the global alias for its signature and a global getter, so clang code can call it directly or indirectly.
Variable conventions
| Variable | Means |
|---|---|
| Ctx | the function context (function_ctx/8) |
| Fn | the amd64 context inside Ctx |
| K | a word class: w, l, p, s or d |
| V | a value, tmpN or constN |
| D | the number of a destination tmpD |
| T | a load type |
| W | a store width |
| S, F | symbols |
| Off | a byte offset |
| Form | a placed return form, reg or memBytes (target_form/2) |
Lines is the asm.pl line list of IR module Module for the Fil-C ABI: each item's code and data, a weak thunk for each function the module calls but does not define, and the GNU-stack note. Throws nanofil_error(Msg) if an item cannot be lowered.
Registers is stack (the reference) or local (validated scalar caching). Reports records each item's caching result, without driver-side state.
Complete the ABI product before any instruction selection. Fusion is still target preparation; its separate liveness view preserves indices.
ABI facts (docs/abi-findings.md), at the pin
Symbol is the name under which code finds Role of the function or global F: its implementation (the fast entry) and FI alias for signature Sig, its getter, its function object, or its data object and aux array.
Mem is the operand of Field of the Struct whose address is in register Base. A function object's fields are at its lower.
Bits are the flags Flags in an aux word, whose low 48 bits are the aux pointer (aux_pointer_mask/1) and whose high 16 the flags. The special type is a field of the flags.
the special type field, bits 7-10
Frame layout
Place the already chosen payloads, roots and aliases below %rbp.
Named object storage is not a temporary slot and is never reused. Payload is 16-aligned; aux is a count header followed by lower words.
Slots maps Key to lowers[I0] of the filc_frame at -FrameBase(%rbp).
The function context
function_ctx(+Name, +Ret, +Slots, +Blocks, +Size, +FrameBase, +Selection, -Ctx): Ctx = ctx(Fn, Size, FrameBase, Labels, Selection) is the context of function Name, where Fn = f(Name, Ret, Slots, Blocks) is the context amd64's load//4 and compute_op//4 take. Labels maps each block label to its phis, so an edge finds them without searching Blocks.
Key's slot is at -Off(%rbp).
Local labels: A is the label atom in each relation.
The frame's origin: c (calls, can_catch) or n (pollchecks and checks).
Functions
The lines of a module item, a function or a data object.
A function: its implementation (prologue, blocks, the shared exception exit and failure blocks), then its function object and origins.
fused_blocks(+Fn, +Blocks0, -Blocks, -LiveBlocks): Blocks are the checked Blocks0 with each check and the raw access after it, which reads its proven address, fused into fused(Check, Access) at one index, so labels and frame slots number instructions as they did before checks were explicit. Access reads the checked pointer P itself: a proof is a register value (%rax:%rcx after the check) and needs no slot. LiveBlocks have the bare accesses, for liveness and layout, which so keep P live through the access, as the reload after ensure-aux needs. Function proofs fuse with raw_call in the same way: the original pointer and all arguments stay live through the call, and neither proof gets a frame slot. Throws unless the IR is checked and every cycle polls.
access_check//2 checks [P, P+Size) for alignment 1 or 8, and a rawAccess0 is a raw access at tmp(Q) and Access is the same at P.
Layout sees exactly the former whole-pointer operation at this index. In particular both original p operands survive every internal helper.
The zeroing of the N entries of lowers[], so the GC sees no stale lowers.
The store of %rax into lowers[I] for each I in Is.
The store of the c or n origin (origin_label/3) into the filc_frame.
The pop of the filc_frame: thread->top_frame back to its parent.
has_exception = 1, in %al or in the return buffer.
The failure block of function_check//1, if the function calls through a pointer.
Word I of a generic call's arguments or results is at thread offset Addr, word I of the inline buffer, with its lower at Lower, word I of the inline aux buffer.
The release of Bytes of pushed stack words, if any.
Blocks and jumps
The code of the jump that ends block From.
The returned value V of class K, in %rdx, and its lower in %rcx for p.
The words of a buffered return, into the buffer at %rax.
Phi copies for the edge to Target, a label or poll(Label), then the pollcheck if Target asks for one, then the jump.
The pollcheck: a call of filc_pollcheck_slow if the thread's state asks for it.
The parallel copy of an edge's phis: first every incoming value into its phi's shadow slots (phi_reads//3), then every shadow into the phi (phi_writes//2).
The incoming value V, of class K, into the shadow slots of phi tmp(D).
The second half of the parallel copy: each phi's shadow into the phi.
For a p phi tmp(D), its shadow lower into its lower.
The lower of a p value: its lowers[] entry, or 0 for a constant.
A p value's address into %rax and lower into %rcx.
The flight pointer in %rax:%rcx into tmp(D) and its lower.
Instructions
the code of instruction Inst, the Indexth of block Block. Instructions go by their op's kind (ir.pl), stores and calls by their width and callee.
Check exact logical bounds without manufacturing a heap lower. Offset and size are the stack reporter's arguments. Address formation happens only after the full range/alignment check, even for constant accesses.
as inst//4, for tmp(D) =K Op Args with Op of Kind. Integer instructions go to amd64:compute_op//5; the pointer ones are by K, or (copy at p) by the operand.
RDX is the snapshot payload; RCX is scratch. EAX's full native int survives restoration, which contains no calls, polls or checked loads.
the code of the integer instruction tmp(D) =K Op Args.
The helper returns payload and auxiliary *addresses*, not a flight pointer. Consume both without a safepoint, then root the loaded lower.
scalar add or sub.
a copy at K.
Flight's inputs are all read before its destination is written. The second address uses %rdx, not the scalar ALU's %rcx, which holds the explicitly selected lower. No call or safepoint splits the pair.
the code of the raw Access at P, the Indexth instruction of Block, after its check left P in %rax and its lower in %rcx.
%rax is the checked payload address, %rdx its explicit aux word. Neither register is an ordinary root. Only loaded/stored *contents* have heap lowers, and no helper or safepoint splits these operations.
The effect list determines execution; only its caller publishes the result. No-aux jumps to lower_resolve's null arm. Resolving a box reads only its lower: address_read always reads the primary payload.
a scalar load of type T at K into tmp(D). Pointer loads require their explicit effect list.
a store of the pointer V at A with its check, for an instruction whose store is internal (setjmp's jmp_buf).
A pointer store may exit during ensure-aux. Reload both rooted inputs after the helpers, before writing the lower and then the address.
Scalar stores have no aux or barrier effects.
The code of a store of the integer V of width W at %rax.
A call through an already checked function, whose lower is in the aux slot. The fast entry if the signature matches the call site's, else the generic entry with every argument as an 8-byte word at thread+128+8i and its lower at thread+384+8i (docs/abi-findings.md).
The generic result cells, after the product's required-size check.
Arguments, the entry registers and the call itself, for the fast convention in form Form; Callee is direct(Symbol) or indirect (through the aux slot).
The entry registers and the call of the fast entry of Callee. Pop is the stack words' size, which a buffered return's buffer sits above.
has_exception, then the result slots from %rdx:%rcx or the buffer.
The store of each register into the matching slot.
The copy of each buffer word into its slot; Slots start at Off(%rsp).
A generic return's words, from the thread at %rsi, into the slots.
For a p word at thread offset Off, its lower into lower(T).
The check that the generic return size in %rdx is at least Need, with a call of filc_cc_rets_check_failure if it is not.
The store of each argument into the thread's generic argument area. Invariant: Args are the arguments from argument I on.
With a flight pointer in %rax:%rcx, jump to Fail unless it points at a function object. Clobbers %rdx, %rsi and %rdi.
The aux pointer of the object at lower %rcx into %rdx, setting ZF if it has none.
The fast convention as data. A signature is a list of words wf(Class, Source): one per w, l, s or d value and two (address, then lower, both l) per p. typed_locations/5 places each word: integer words in the form's registers, s and d in %xmm0-7, the rest in stack words stack(I) at 8I(%rsp) at the call. Entries, call sites, the generic entry, the variadic adapter and external thunks all read this one placement, so they agree by construction.
NS stack words take Bytes, keeping %rsp 16-aligned at the call.
An integer register word goes straight to its slot; others via %rax.
Adapter sources are operands (thread/snapshot/saved stack), not IR values. Load all stack words first, then registers; only RAX is scratch.
Physical operands for the already flattened adapter sources.
thunk(+Product)//: the lowered weak thunk resolves its symbol through the getter and calls the fast entry when the supplied signature matches, else transports its generic cells and result channels. Every incoming word, integer or XMM, is saved below rbp before the getter can exit or clobber caller-saved registers; rbx holds the thread (r12 the return buffer) and -24(%rbp) the function object. Pointer authority stays rooted by the caller.
Access checks: k(Size, Write, Align), with the pointer in %rax and its lower in %rcx, which the failure block passes on.
A jump to the kind's failure block unless the pointer may be accessed: lower not 0, lower =< ptr =< upper - Size, the object's flags permitting a write if Write is 1, and ptr 8-aligned if Align is 8.
The distinct check kinds the function's accesses need.
Inst's access needs a check of Kind; fails if Inst does not access memory.
A failure block for each check kind, calling the runtime's reporter.
Function objects: every function gets a getter, a generic entry and a function object, so its address can be taken; exported ones also get the FI alias and a global getter.
Generic-only public entry, with a private fast body. Promotion is the only exiting call before the body roots the hidden snapshot parameter.
The getter of a function object FO: its flight pointer, whose lower is its address, in %rax:%rdx.
The function object FO with the entries Fast and Generic (symbols, or 0 for none) and signature Sig. Its header has upper = lower, so it cannot be read or written, and an aux word that points at itself.
Data objects, as clang lays out globals (docs/abi-findings.md): DO_d = {upper, aux} then the payload rounded up to 8 bytes, a 16-byte cache GP_d, an aux array DA_d if the payload holds pointers, and a getter whose slow path registers the object and runs the relocations that fill in its pointers.
AuxWord is the aux word of a global data object with relocations Rs and other flags Flags: its flags, and its static aux array DA if it holds pointers.
The aux array DA of Size bytes and the relocation table Relocs, if there are relocations Rs.
The getter's call that runs the relocations Rs, if there are any, of data object DO from the table Relocs.
Line is the payload directive of Item at offset Off0, and Rs adds to Rs0 a relocation r(Offset, Symbol, Addend) if Item is a pointer, whose slot stays 0 until the relocation fills it in. Off is the offset after Item.
as payload_item/4 for item(W, Value).
Each relocation is {u64 offset, u32 kind, pad, target}: kind 0 calls a getter, kind 1 a constexpr node {u32 add-immediate = 0, u32 0, getter, u64 addend}.
The relocation table. Invariant: Rs are the relocations from the Ith.
The constexpr node of each relocation with an addend. Invariant: Rs are the relocations from the Ith.
Node labels the constexpr node of relocation I of data object Name.
the return, in form Form, of the generic entry's result words GWs, of classes RetKs and Need bytes, then a jump to Out.
The result words, if RetKs has any, in %rdx:%rcx.
The load of each generic return word, at thread offset Off, into its register.
The copy of each generic return word, at thread offset Src, into the return buffer at Off(%r12).
The generic entry: {i1, i64 ret_size} (thread, callee_lower, args_size). Argument i is 8 bytes at thread+128+8i, with its lower (for p) at thread+384+8i, and so is result word i. It calls the fast entry through callee_lower, with a buffer on the stack for a buffered return.
generic_entry_call(+Form, +Channel, +Packet, +Pop, +RetSize, +Done)//: call the fast entry in placed Form, then transport Channel into the placed Packet and return RetSize in %edx. Pop is the native stack argument size. Exceptions skip all result reads and size production.
The result words from %rdx:%rcx at their thread offsets; a void result is one zero word.
The store of each result register at its thread offset.
The copy of each return buffer word, from Off(%rsp) on, to thread offset Dest.
Static data: function origins (staging's 48-byte layout), origins, and one access-check origin per check kind.
The name string, the c and n function origins with their origins, and the check origins of function Name.
A function origin and the origin (origin_label/3) that points at it.
An access-check origin for each check kind.