Fil-C on Nix

Filnix wiki

mbrock/filnix/nanofil

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. Under --registers=temps and pointers (amd64_regs.pl; pointers is the -O default), scalar w/l homes, and under pointers a pointer's address word, may live in registers: rbx and r12-r15 across calls, and r10 and r11 too otherwise. A lower never does, so every live capability stays in the frame record the GC scans. The register checker refuses any home that overlaps that record or a local object. setjmp functions and --registers=stack (plain's default) keep every home in the frame.

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 from check_function, not from inferring that an indirect call needs one. A later scalar access that reuses a heap proof (docs/m7.md) is reusedAccess: the access at the checked pointer, reloaded from its slot, plus the access's offset (a related pointer's distance), without a check.
  • Pointer loads and stores go through the object's aux array; a pointer store ensures the aux array and runs the store barrier.
  • flight supplies its address calculation and lower source separately. Emission preserves neither by inference from a p arithmetic operation; pizlonation has already chosen both.
  • alloc is a heap allocation.
  • An edge to pollL runs 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

VariableMeans
Ctxthe function context (function_ctx/8)
Fnthe amd64 context inside Ctx
Ka word class: w, l, p, s or d
Va value, tmpN or constN
Dthe number of a destination tmpD
Ta load type
Wa store width
S, Fsymbols
Offa byte offset
Forma placed return form, reg or memBytes (target_form/2)
useslists
usesdcgs
usesformat
usesassoc
usespairs
usesir
usesamd64
usesamd64_regs_checkcallee_saved/1
usesamd64_contractslow_call_stub/2
usescfgedge_label/2, inst_def/2
usescheck_shareshare_mode/1, shared_blocks/4, share_bounds//4, shared_uppers/2
useslower_rootslowers_mode/1
filc_module_asm+Module, -Linesdet

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.

130filc_module_asmModule, Lines ←
filc_module_asmModule, stack, Lines, _
filc_module_asm+Module, +Emission, -Lines, -Reportsdet

As filc_module_asm/2, with the emission choices of main.pl's options. Emission is emit(Registers, Branches), or just Registers with reference branches. Registers is stack (the reference), temps (validated whole-function register homes, amd64_regs.pl, with validated register operands and frame cells, on a frame with a home per scalar temporary: docs/perf.md items 3, 5a-loads and 3d), pointers (temps with pointer address words as homes too, 5b-2), or limited(R, N) for R on functions of at most N instructions; Branches is reference or selected (branch selection, below). emit(Registers, Branches, Copies) also gives Copies: call (the reference) or inline, the small-copy filter of docs/perf.md item 7a; emit/2 means call. Reports records each item's register result (amd64_regs:regs_asm/6), without driver-side state.

147filc_module_asmModule0, Emission, Lines, Reports ←
without_locsModule0, Module
Module = moduleItems
emissionEmission, Registers, Branches
Lower and place each item in one committed step, so a large
module (SQLite) never holds every item's ABI product at once.
module_symbolsModule, Symbols
maplistitem_placed_linesSymbols, Registers, Branches, Items, LinesPerItem, Reports
lower_thunksModule, Callees
appendLinesPerItem, ItemLines
maplistthunk_lines, Callees, LinesPerThunk
appendLinesPerThunk, ThunkLines
appendItemLines, ThunkLines, Stubs, sectionnote_gnu_stack, Lines
162emissionemitRegisters, Branches, Copies, Registers, choicesBranches, Copies ←
!
163emissionemitRegisters, Branches, Registers, Branches ←
!
164emissionRegisters, Registers, reference
166item_placed_linesSymbols, _, _, aliasName, Linkage, Target, Lines, dataName ←
!
phrasealias_getterSymbols, Name, Linkage, Target, Lines

limited(Registers, Limit): Registers (temps or pointers) for a function of at most Limit instructions, and the reference emission for a larger one (main.pl's --registers-limit).

171item_placed_linesSymbols, limitedRegisters0, Limit, Branches, Item, Lines, Report ←
!
Item = func_, _, _, _, Blocks
foldlblock_insts, Blocks, 0, Size
Size > Limit
→
Registers = stack
else
Registers = Registers0
item_placed_linesSymbols, Registers, Branches, Item, Lines, Report
178item_placed_linesSymbols, Registers, Branches, Item, Lines, Report ←
The allocating modes keep float temporaries out of the scalar
slots (frame_slots:temp_slots/9), so integer homes stay eligible.
Temps mode also gives each w/l temporary its own home (docs/perf.md 3d).
It lowers twice: once with every w/l temporary in a home of its own,
then with the temporaries pooled by where the first allocation put
them: those left in the frame together, and those given a register
with the others given the same one, so the frame stays compact.
Both are layout choices; the checkers validate the emitted stream.
Pointers mode is temps with pointer address words as homes too
(docs/perf.md 5b-2).
memberchkRegisters, temps, pointers
→
temps_firstRegisters, Symbols, Branches, Item, First
First = placedLines, Report
→
true
else
First = againGroups, Fused, Ranges
The first lowering is garbage now; collect it before the
second grows the stacks, in large functions only (SQLite's
largest): collecting before every function costs far more.
lengthGroups, NG
NG > 1024
→
catchgarbage_collect, errorexistence_error_, _, _, true
else
true
oncelower_fusedownGroups, Ranges, Symbols, Fused, Product
onceplaced_linestempsRegisters, Branches, Item, Product, Lines, Report, _
else
once
lower_itemshared, Symbols, Item, Product
placed_linesRegisters, Branches, Item, Product, Lines, Report
207block_instsblock_, _, Is, _, N0, N ←
lengthIs, K
N is N0 + K
temps_first+Homes, +Symbols, +Branches, +Item, -First

temps mode's first lowering, with a home per w/l temporary. First is placed(Lines, Report) when it has at most own_frame_limit/1 such homes, else again(Groups, Fused, Ranges) with each temporary's group for the second lowering, from a function-mode (3a) allocation of the first, and the fused blocks and live ranges the second reuses (the groups change only which slots the ranges share); the first's lines are dropped, so the second lowering does not hold them. Homes is temps or pointers, as for placed_lines/7.

218temps_firstHomes, Symbols, Branches, Item, First ←
fused_itemItem, Fused
oncelower_fusedown[], Ranges, Symbols, Fused, Product
Product = abi_function_, _, _, _, frame_, _, Aliases, _, _, _, _, _
findallx, member_−storagescalar, _, Aliases, Xs
lengthXs, N
N > Limit
→
Only the register map matters here: 3a's allocation, unchecked
because these lines are dropped, and no operand rewrite.
onceplaced_linesmapHomes, Branches, Item, Product, _, _, Groups
First = againGroups, Fused, Ranges
else
onceplaced_linestempsHomes, Branches, Item, Product, Lines, Report, _
First = placedLines, Report
own_frame_limit-N

own scalar homes (512 bytes) worth a second lowering to share their slots (docs/perf.md 3d).

234own_frame_limit16
filc_module_emit+Module, +Emission, :Emitdet

Calls Emit(Lines) for each part of the lines filc_module_asm/4 gives, in order, so a large module (SQLite) need not hold all its lines.

240filc_module_emitModule0, Emission, Emit ←
A last call after deterministic goals: the frame holding Module0
and its provenance contexts (170 MB for SQLite) is dropped.
oncewithout_locsModule0, Module
emissionEmission, Registers, Branches
module_emitModule, Registers, Branches, Emit
247module_emitModule, Registers, Branches, Emit ←
Module = moduleItems
Validation leaves SWI's heap near its peak; collecting once here,
with only 130 MB live for SQLite, keeps emission within 1 GB.
catchgarbage_collect, errorexistence_error_, _, _, true
module_symbolsModule, Symbols
forallmemberItem, Items,
item_placed_linesSymbols, Registers, Branches, Item, Lines, _
callEmit, Lines
lower_thunksModule, Callees
forallmemberCallee, Callees,
thunk_linesCallee, Lines
callEmit, Lines
Stubs ≡ []
→
true
else
callEmit, Stubs
callEmit, sectionnote_gnu_stack
263thunk_linesCallee, Lines ←
phrasethunkCallee, Lines

The only stronger-than-SysV call rule (docs/abi-findings.md). Route calls before allocation/validation; never retarget a checked stream.

268slow_calls_modeMode ←
getenv'NANOFIL_SLOW_CALLS', M
→
Mode = M
else
Mode = reference
memberchkMode, reference, save
→
true
else
thrownanofil_error'slow-calls must be reference or save'
273slow_callHelper ⟶
Mode ≡ save
slow_call_stubHelper, Label
→
Target = symLabel
else
Target = pltHelper
inscall, none, Target
278slow_call_stubsLines ←
Mode ≡ save
→
findallStub,
slow_call_stubHelper, Label
phraseslow_call_stub_linesHelper, Label, Stub
,
Stubs
appendStubs, Lines
else
Lines = []

Entry rsp is 8 mod 16. Allocate saves and padding before calling the ordinary C helper. These are not GC roots; lower stores stay in place.

288slow_call_stub_linesHelper, Label ⟶
sectiontext, p2align4, typeLabel, function, labelLabel, inspush, q, regr10, inspush, q, regr11, inssub, q, imm8, regrsp, inscall, none, pltHelper, insadd, q, imm8, regrsp, inspop, q, regr11, inspop, q, regr10, insret, none, [], sizeLabel
filc_module_plans+Module, :Emitdet

Calls Emit(Plan) for the ABI plan (filc_abi:lower_function/4) of each function of the checked Module, in order, as the reference emission lowers it: without its source positions, its checks fused with their accesses, the frame's pools shared. Then it calls Emit(thunks(Thunks)) with the module's weak thunk plans (filc_abi:lower_thunks/2). The Gallina backend's plans are compared with these (rocq/backend).

304filc_module_plansModule0, Emit ←
oncewithout_locsModule0, Module
Module = moduleItems
module_symbolsModule, Symbols
forallmemberItem, Items,
Item = func_, _, _, _, _
→
oncelower_itemshared, Symbols, Item, Plan
callEmit, Plan
else
true
lower_thunksModule, Thunks
callEmit, thunksThunks

Complete the ABI product before any instruction selection. Fusion is still target preparation; its separate liveness view preserves indices.

319lower_itemPools, Symbols, Item, Product ←
fused_itemItem, Fused
lower_fusedPools, Symbols, Fused, Product
322fused_itemfuncN, L, R, Ps, Bs0, fusedfuncN, L, R, Ps, Bs, Live ←
fused_blocksN, Ps, Bs0, Bs, Live
324fused_itemdataN, L, A, Is, dataN, L, A, Is
326lower_fusedPools, Symbols, fusedFunc, Live, Product ←
lower_functionPools, Symbols, Func, Live, Product
328lower_fused_, _, dataN, L, A, Is, dataN, L, A, Is
330item_linesSelection, Item, Lines ←
phraseitemSelection, Item, Lines
→
true
else
arg1, Item, Name
formatatomMsg, 'Fil-C backend cannot emit ~w', Name
thrownanofil_errorMsg
placed_lines+Registers, +Branches, +Item, +Product, -Lines, -Report, -Groups

Registers is stack, temps(Homes) or map(Homes), Homes being temps (scalar homes) or pointers (pointer address words too). Groups pairs each temporary in a scalar home with the register the map gave the home, or frame, for temps mode's second lowering; [] for data and the non-allocating modes.

343placed_linesR, Br, Item, Product, Lines, Report ←
placed_linesR, Br, Item, Product, Lines, Report, _

A setjmp function keeps reference selection, branches included.

347placed_linesstack, Branches, Func, Item, Lines, stack, [] ←
→
B = reference
else
B = Branches
item_linesselstack, B, Item, Lines
350placed_linestemps_, _, dataN, L, A, Is, Item, Lines, dataN, [] ←
Item = dataN, L, A, Is
item_linesselstack, reference, Item, Lines

Temps mode reserves a save slot for each callee-saved register, so the stream the checker compares with has the same frame. Map is temps mode's first lowering when it only needs the register map.

355placed_linesMode, Branches, Func, Item0, Lines, functionName, Report, Groups ←
Mode = tempsHomeKind
→
true
else
Mode = mapHomeKind
Func = funcName, _, _, _, _
Item0 = abi_functionName, Lk, Rt, Sig, framePlain, Lowers, Aliases, E, B, X, O, Or
→
Setjmp = true
Selection = selstack, reference
Item = Item0
else
Setjmp = false
Selection = sellocal, Branches
findallcallee_saveR, callee_savedR, Extra
appendPlain, Extra, Plain1
Item = abi_functionName, Lk, Rt, Sig, framePlain1, Lowers, Aliases, E, B, X, O, Or
item_linesSelection, Item, Original
abi_symbolimplSig, Name, Impl
onceappendBody, sizeImpl|Rest, Original
Item = abi_function_, _, _, _, Frame, _, _, _, _, _
layoutFrame, Slots, FrameBase, _
scalar_homesFunc, Aliases, Slots, Homes0
Phi shadows are homes too. Phis of different blocks may share a
shadow slot (filc_abi.pl's shadow_storage/8): one home each, with
the classes of the phis sharing it. A pointer phi's shadow is its
address word, of class p, or a in pointers mode.
Func = func_, _, _, _, Bs
findallD−K,
memberblock_, Phis, _, _, Bs
memberphitmpN, K0, _, Phis
home_classHomeKind, K0, K
get_assocshadowN, Slots, Off
D is −Off
,
DKs0
sortDKs0, DKs
group_pairs_by_keyDKs, ShadowGroups
findallhomeD, Ks, memberD−Ks, ShadowGroups, Shadows
Pointers mode: each pointer pool slot's address word is a home of
class a; its lower stays in lowers[] (docs/perf.md 5b-2). So is a
derived pointer's address slot (docs/lowers.md).
findallhomeD, a,
HomeKind ≡ pointers
gen_assocstoragepointer, _, Slots, Off
or
gen_assocstorageaddress, _, Slots, Off
D is −Off
,
Pointers
The GC roots, which no home may overlap: the frame record (parent,
origin and lowers[], stack-aux and jmpbuf roots included) and each
local object's payload and aux words.
Frame = frame_, Lowers1, _
lengthLowers1, NLowers
RecordD is −FrameBase
RecordN is 16 + 8 × NLowers
findallrootD, N,
gen_assoclocalL, Slots, V
other_rangelocalL, V, D, N
,
Locals
appendHomes0, Shadows, Pointers, rootRecordD, RecordN|Locals, Homes
findallsaveR, D,
get_assoccallee_saveR, Slots, Off
D is −Off
,
Saves
regs_modeMode, Slots, Mode1
regs_asmMode1, regs_metadataSetjmp, Impl, Homes, Saves, Body, Allocated, checked_, Map, Report
appendAllocated, sizeImpl|Rest, Lines
Map ≡ []
→
empty_assocMapped
else
list_to_assocMap, Mapped
findallN−G,
membertmpN−storagescalar, I, Aliases
get_assocstoragescalar, I, Slots, Off
D is −Off
get_assocD, Mapped, R
→
G = R
else
G = frame
,
Groups0
sortGroups0, Groups
regs_mode+Mode, +Slots, -Mode1

temps mode allocates with register operands and the frame cells (docs/perf.md, 5a-loads): the 8-byte words of lowers[], the scalar and pointer pools, phi shadows, thread, aux, retbuf, and stack-aux roots, as cells(Ds, Others) with the other frame ranges (local storage, wide slots, save slots) the checker keeps them apart from. The header words (parent, origin) are never cells.

413regs_modetemps_, Slots, framecellsDs, Others ←
!
assoc_to_listSlots, Ps
findallD,
memberK−Off, Ps
integerOff
D is −Off
,
Ds0
sortDs0, Ds
findallD−N,
memberK−V, Ps
other_rangeK, V, D, N
,
Others
418regs_modemap_, _, map
home_class+Homes, +Class0, -Class

a pointer phi's shadow is a home of class a (its address word) in pointers mode, and of class p, which no allocation takes, otherwise.

423home_classpointers, p, a ←
!
424home_class_, K, K
cell_keylower_storagescalar, _storagefloat, _storagepointer, _storageaddress, _shadow_shadow_slot_, _threadauxretbufstack_aux_
438other_rangelocal_, localPayload, Aux, _, Padded, D, N ←
D is −Aux
N is Aux−Payload + Padded
440other_rangestoragewide, _, Off, D, 16 ←
D is −Off
441other_rangecallee_save_, Off, D, 8 ←
D is −Off
scalar_homes+Func, +Aliases, +Slots, -Homes

home(Disp, Classes) for each scalar-pool slot, with the classes of the temporaries sharing it.

445scalar_homesfunc_, _, _, Ps, Bs, Aliases, Slots, Homes ←
findallT−K,
memberparamK, T, Ps
or
memberblock_, Phis, Is, _, Bs
memberphiT, K, _, Phis
or
memberI, Is
,
Types0
sortTypes0, Types1
list_to_assocTypes1, Types
findallKey−K,
memberT−Key, Aliases
Key = storagescalar, _
get_assocT, Types, K0
→
K = K0
else
K = unknown
,
Uses
keysortUses, Sorted
group_pairs_by_keySorted, Groups
findallhomeD, Ks,
memberKey−Ks, Groups
get_assocKey, Slots, Off
D is −Off
,
Homes
457cache_temp_classletT, K, _, _, T, K
458cache_temp_classlocal_loadT, K, _, _, _, T, K
459cache_temp_classcallresultT, K, _, _, T, K
460cache_temp_classraw_callresultT, K, _, _, T, K
461cache_temp_classcalltupleRs, _, _, T, K ←
memberresultT, K, Rs
462cache_temp_classraw_calltupleRs, _, _, T, K ←
memberresultT, K, Rs
463cache_temp_classasm_fixed_, _, Outs, T, w ←
member_−T, Outs

ABI facts (docs/abi-findings.md), at the pin

abi_symbol+Role, +F, -Symbol

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.

471abi_symbolimplSig, F, S ←
formatatomS, 'pizlonatedFIP~w_~w', Sig, F
472abi_symbolentryneverR−A, F, S ←
!
formatatomS, 'pizlonatedFIN~w_~w_~w', R, A, F
474abi_symbolentrySig, F, S ←
formatatomS, 'pizlonatedFI~w_~w', Sig, F
475abi_symbolgetter, F, S ←
formatatomS, 'pizlonated_~w', F
476abi_symbolfunction_object, F, S ←
formatatomS, 'pizlonatedFO_~w', F
477abi_symboldata_object, F, S ←
formatatomS, 'pizlonatedDO_~w', F
478abi_symboldata_aux, F, S ←
formatatomS, 'pizlonatedDA_~w', F
field+Struct, +Field, +Base, -Mem

Mem is the operand of Field of the Struct whose address is in register Base. A function object's fields are at its lower.

483fieldStruct, Field, Base, memOff, Base ←
struct_fieldStruct, Field, Off
struct_field
486threadstack_limit0
487threadstate8
488threadtop_frame16
489threadcc_inline_buffer128
490threadcc_inline_aux_buffer384
491threadcc_outline_buffer640
492threadcc_outline_aux_buffer648
493threadcc_outline_size656
494function_objectfast_entry0
495function_objectgeneric_entry8
496function_objectsignature16
aux_bits+Flags, -Bits

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.

501aux_bitsFlags, Bits ←
foldlflag_or, Flags, 0, Sum
Bits is Sum << 48
505flag_orFlag, Sum0, Sum ←
object_flagFlag, Bit
Sum is Sum0 \/ Bit
object_flag
509global0x1
510readonly0x2
511global_aux0x10

the aux array is static data

512function0x80

special type 1, function

513special_type0x780

the special type field, bits 7-10

515aux_pointer_mask0xffffffffffff

Frame layout

Place the already chosen payloads, roots and aliases below %rbp.

520layoutframePlain, Lowers, Aliases, Slots, FrameBase, Size ←
lengthLowers, NLowers
empty_assocSlots0
foldlplain_slot, Plain, Slots0−0, Slots1−PlainBytes
FrameBase is PlainBytes + 16 + 8 × NLowers
foldllower_slotFrameBase, Lowers, Slots1−0, Slots2−_
One assoc from all the keys, not an insertion per alias (a fifth
of the frame's cost for SQLite). put_assoc would let a later alias
replace a key; list_to_assoc refuses duplicates, which no frame has.
assoc_to_listSlots2, Placed
findallT−Off,
memberT−Key, Aliases
get_assocKey, Slots2, Off
,
AliasSlots
appendPlaced, AliasSlots, All
list_to_assocAll, Slots
Size is FrameBase + 15//16 × 16

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.

537plain_slotlocalName, Size, Slots0−Off0, Slots−End ←
!
Padded is Size + 15//16 × 16
Payload is Off0 + Padded + 15//16 × 16
Aux is Payload + Padded + 8
End = Aux
put_assoclocalName, Slots0, localPayload, Aux, Size, Padded, Slots

A long double temporary's wide slot is 16 bytes, 10 of them used.

543plain_slotstoragewide, I, Slots0−Off0, Slots−Off ←
!
Off is Off0 + 16
put_assocstoragewide, I, Slots0, Off, Slots
546plain_slotKey, Slots0−Off0, Slots−Off ←
Off is Off0 + 8
put_assocKey, Slots0, Off, Slots
lower_slot+FrameBase, +Key, +Slots0−I0, −Slots−I

Slots maps Key to lowers[I0] of the filc_frame at -FrameBase(%rbp).

552lower_slotFrameBase, Key, Slots0−I0, Slots−I ←
Off is FrameBase−16−8 × I0
I is I0 + 1
put_assocKey, Slots0, Off, Slots

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.

564function_ctxName, Ret, Slots, Blocks, Size, FrameBase, selImm, B0, Ctx ←
findallLabel−Phis, memberblockLabel, Phis, _, _, Blocks, LabelBlocks
list_to_assocLabelBlocks, Labels
B0 = choicesB, Copies
→
true
else
B = B0
Copies = call
branch_selectionB, Blocks, Branches
Ctx = ctxfName, Ret, Slots, Blocks, Size, FrameBase, Labels, selImm, Branches, Copies
572ctx_fnctxFn, _, _, _, _, Fn
574ctx_namectxfName, _, _, _, _, _, _, _, Name
576ctx_sizectx_, Size, _, _, _, Size
578ctx_frame_basectx_, _, FrameBase, _, _, FrameBase
580ctx_labelsctx_, _, _, Labels, _, Labels

Immediate selection: stack (the reference) or local.

583ctx_selectionctx_, _, _, _, selSelection, _, _, Selection

Branch selection: reference, or selected(Uses) (branch selection).

586ctx_branchesctx_, _, _, _, sel_, Branches, _, Branches

Small constant copies: call (the reference) or inline (item 7a).

589ctx_copiesctx_, _, _, _, sel_, _, Copies, Copies
ctx_slot+Ctx, +Key, -Off

Key's slot is at -Off(%rbp).

592ctx_slotctxf_, _, Slots, _, _, _, _, _, Key, Off ←
get_assocKey, Slots, Off

The store of register Reg into Key's slot.

596reg_slot_storeReg, Key, Ctx ⟶
ctx_slotCtx, Key, Off
Disp is −Off
insmov, q, regReg, memDisp, rbp

The load of Key's slot into register Reg.

602slot_reg_loadKey, Reg, Ctx ⟶
ctx_slotCtx, Key, Off
Disp is −Off
insmov, q, memDisp, rbp, regReg

Local labels: A is the label atom in each relation.

A label shared by the whole function, e.g. exc for exception exits.

610special_labelCtx, Tag, A ←
ctx_nameCtx, Name
label_atom'.L', Name, .., Tag, A

The label of the block named Label.

615block_labelCtx, Label, A ←
ctx_nameCtx, Name
label_atom'.L', Name, '.', Label, A

A label inside the code of instruction Index (or a jump) in Block.

620local_labelCtx, Block, Index, Tag, A ←
ctx_nameCtx, Name
label_atom'.L', Name, '.', Block, .., Index, '.', Tag, A
label_atom+Parts, -A

A is the parts' text, as format's ~w writes them; atomic_list_concat/2 is several times faster than format/3, which was a twentieth of emission for SQLite.

627label_atomParts, A ←
maplistatomic, Parts
→
atomic_list_concatParts, A
else
lengthParts, N
lengthWs, N
maplist='~w', Ws
atomic_list_concatWs, Format
formatatomA, Format, Parts

The frame's origin: c (calls, can_catch) or n (pollchecks and checks).

634origin_labelName, Which, A ←
formatatomA, '.L~w..origin_~w', Name, Which

Functions

The lines of a module item, a function or a data object.

640itemSelection, Func ⟶
Func = abi_function_, _, _, _, _, _, _, _, _, _
functionSelection, Func
643item_, dataName, tlsLinkage, Align, Items ⟶
!
thread_local_objectName, Linkage, Align, Items
645item_, dataName, initKind, Prio, _, itemp, symF, 0 ⟶
!
init_entryName, Kind, Prio, F
647item_, dataName, externweak, _, [] ⟶
!
abi_symbolgetter, Name, Getter
weakGetter
649item_, dataName, Linkage, Align, Items ⟶
data_objectName, Linkage, Align, Items

A constructor or destructor table entry (ir.pl's init linkage), as clang's Fil-C emits one: a native forwarder in the .init_array or .fini_array section, which hands F's function object to the runtime (filc_defer_or_run_global_ctor or filc_run_global_dtor) to call.

656init_entryName, Kind, Prio, F ⟶
abi_symbolfunction_object, F, FO
formatatomForwarder, '.L~w..forwarder', Name
init_runtimeKind, Run
sectiontext, p2align4, typeForwarder, function, labelForwarder, inslea, q, ripFO + 16, regrdi, insmov, q, regrdi, regrsi, insjmp, none, pltRun, sectioninitKind, Prio, p2align3, quadForwarder
666init_runtimector, filc_defer_or_run_global_ctor
667init_runtimedtor, filc_run_global_dtor

A function: its implementation (prologue, blocks, the shared exception exit and failure blocks), then its function object and origins.

671functionSelection, abi_functionName, Linkage, Ret, Sig, Frame, Entry, Blocks, Exit, Object, Origins ⟶
layoutFrame, Slots, FrameBase, Size
function_ctxName, Ret, Slots, Blocks, Size, FrameBase, Selection, Ctx
abi_symbolimplSig, Name, Impl
access_check_kindsBlocks, CheckKinds
special_labelCtx, exc, Exc
sectiontext, p2align4
linkage_globlLinkage, Impl
typeImpl, function, labelImpl
abi_actionsEntry, entry, entry, Ctx
shared_bodyBlocks, Ctx
labelExc
abi_actionsExit, exit, exit, Ctx
check_fail_blocksCheckKinds, Ctx
sizeImpl
body_function_objectObject, Name, Linkage, Impl
originsName, Origins, CheckKinds

fused_blocks(+Fn, +Params, +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.

706fused_blocksFn, Params, Blocks0, Blocks, LiveBlocks ←
→
true
else
throw_fnFn, 'a cycle without a poll edge; run pizlonate first'
A check_ahead (docs/schedule.md) emits as the check of its whole
range: the early bytes are tested with the access's own.
maplistahead_as_check, Blocks0, Blocks1
single_proofsParams, Blocks1, Single
maplistfused_blockFn, Single, Blocks1, Blocks2
Shared null tests and uppers (check_share.pl, docs/perf.md item 17).
shared_blocksShare, Params−Blocks1, Blocks2, Blocks
maplistlive_block, Blocks, LiveBlocks
720ahead_as_checkblockL, Phis, Is0, J, blockL, Phis, Is, J ←
maplistahead_check, Is0, Is
723ahead_checkletQ, a, check_aheadLo, Hi, Rights, Align, _, Ps, letQ, a, checkLo, Hi, Rights, Align, Ps ←
!
724ahead_checkI, I
726fused_blockFn, Single, blockL, Phis, Insts0, Jump, blockL, Phis, Insts, Jump ←
fused_instsInsts0, Fn, Single, Proofs, Insts
single_proofs+Params, +Blocks, -Single

Q-P for each heap check of P defining tmp(Q) where Q has one definition in the function and P at most one, counting parameters and phis. Only these may be reused in another block: with strict SSA, reloading P there gives the value that was checked.

735single_proofsParams, Blocks, Single ←
temp_defsParams, Blocks, Defs0
pairs_keysDefs0, Names
msortNames, Sorted
multiply_definedSorted, Multi0
sortMulti0, Multi
findallN−true, memberN, Multi, MultiPairs
list_to_assocMultiPairs, MultiSet
findallQ−P,
memberblock_, _, Is, _, Blocks
memberlettmpQ, a, Op, P|_, Is
Op = check_, _, _, _
or
Op = renew_if_, _, _, _
or
Op = check_limit_, _, _, _
¬get_assocQ, MultiSet, _
P = tmpN
→
¬get_assocN, MultiSet, _
else
true
,
Checks0
A half check (docs/half.md) is its check of P, its second operand.
findallQ−P,
memberblock_, _, Is, _, Blocks
memberlettmpQ, a, half_check_, _, _, _, _, _, P, Is
¬get_assocQ, MultiSet, _
P = tmpN
→
¬get_assocN, MultiSet, _
else
true
,
Halves
appendChecks0, Halves, Checks
A renew's proof stands for its own pointer P, the replaced check's
(docs/m7.md, round 4 addendum), when Q and P have one definition.
list_to_assocChecks, CheckAssoc
findallQ−P,
memberblock_, _, Is, _, Blocks
memberlettmpQ, a, renew_, _, _, _, tmpQ1, P, Is
or
memberlettmpQ, a, renew_hull_, _, _, _, tmpQ1, tmpQH, P, Is
get_assocQH, CheckAssoc, _
¬get_assocQ, MultiSet, _
get_assocQ1, CheckAssoc, _
P = tmpN
→
¬get_assocN, MultiSet, _
else
true
,
Renews
appendChecks, Renews, Single0
Q has one definition, so each key occurs once.
list_to_assocSingle0, Single
multiply_defined+Sorted, -Names

the names that occur twice or more.

771multiply_defined[], []
772multiply_definedN, M|Ns, N|Ds ←
N ≡ M
!
773multiply_defined_|Ns, Ds ←
fused_insts+Insts0, +Fn, +Single, +Proofs, -Insts

Proofs are Q-P for the block's heap checks so far, Single those of the function that may be reused in another block. A later scalar access at one of them is reused(Access) at P, with no check, at its offset from P (nonzero for a related pointer, round 3): validation (ir.pl, docs/m7.md) has established that its evidence still holds, which is not repeated here. Its liveness view reads P, so P keeps its rooted slot.

782fused_insts[], _, _, _, []
783fused_instsInst0|Insts0, Fn, Single, Proofs0, Inst|Insts ←
Emission reloads P from its slot where the interpreter reads the
value Q captured: forget a pair once either name is redefined.
inst_defInst0, D
→
proofs_killD, Proofs0, Proofs
else
Proofs = Proofs0
Inst0 = lettmpQ, a, check_, _, _, _, P
→
proofs_addQ−P, Proofs, Proofs1
Inst0 = lettmpQ, a, renew_if_, _, _, _, P, _
→
proofs_addQ−P, Proofs, Proofs1
Inst0 = lettmpQ, a, check_limit_, _, _, _, P, _
→
proofs_addQ−P, Proofs, Proofs1
Inst0 = lettmpQ, a, half_check_, _, _, _, _, _, P
→
proofs_addQ−P, Proofs, Proofs1
Inst0 = lettmpQ, a, renew_, _, _, _, tmpQ1, P
get_assocQ1, Single, _
→
proofs_addQ−P, Proofs, Proofs1
Inst0 = lettmpQ, a, renew_hull_, _, _, _, tmpQ1, tmpQH, P
get_assocQ1, Single, _
get_assocQH, Single, _
→
proofs_addQ−P, Proofs, Proofs1
else
Proofs1 = Proofs
Inst0 = let_, _, raw_load_, Off, tmpQ1
or
Inst0 = raw_store_, Off, _, tmpQ1
A pointer load reads a reused proof too (docs/forward.md).
or
Inst0 = let_, p, raw_load_pointerOff, Es, tmpQ1
Proofs = prByQ, _
get_assocQ1, ByQ, P1
→
true
else
get_assocQ1, Single, P1
→
integerOff
Off ≥ -2147483648
Off ≤ 2147483647
proven_accessInst0, Q1, P1, Access
→
Inst = reusedAccess
Insts1 = Insts0
else
throw_fnFn, 'a reused proof at an offset beyond 32 bits'
Inst0 = lettmpQ, a, renewLo, Hi, Rights, Align, tmpQ1, P
→
renewed_instQ, renewLo, Hi, Rights, Align, Q1−P, Insts0, Fn, Single, Inst, Insts1
A renew_hull emits as a renew: the flag test reads only P's lower.
Inst0 = lettmpQ, a, renew_hullLo, Hi, Rights, Align, tmpQ1, tmpQH, P
→
get_assocQH, Single, _
→
true
else
throw_fnFn, 'a renew_hull without single-definition sources'
renewed_instQ, renewLo, Hi, Rights, Align, Q1−P, Insts0, Fn, Single, Inst, Insts1
else
fused_instInst0, Insts0, Fn, Inst, Insts1
A fused access or call may define Q's pointer too.
live_instInst, Live
findallLD, inst_defLive, LD, Ds
foldlproofs_kill, Ds, Proofs1, Proofs2
fused_instsInsts1, Fn, Single, Proofs2, Insts

The block's proofs so far are pr(ByQ, ByP): ByQ maps each proof Q to its checked pointer P, and ByP each name N to the Qs once paired with tmp(N), perhaps since redefined. Q has at most one pair, since its definition drops the old one, so a kill of D is the removal of D's pair and of each pair whose pointer is tmp(D), without scanning the block's other proofs (a struct copy has tens of thousands).

837proofs_emptyprE, E ←
empty_assocE
839proofs_addQ−P, prByQ0, ByP0, prByQ, ByP ←
put_assocQ, ByQ0, P, ByQ
P = tmpN
→
get_assocN, ByP0, Qs
→
true
else
Qs = []
put_assocN, ByP0, Q|Qs, ByP
else
ByP = ByP0
847proofs_killD, prByQ0, ByP0, prByQ, ByP ←
del_assocD, ByQ0, _, ByQ1
→
true
else
ByQ1 = ByQ0
del_assocD, ByP0, Qs, ByP
→
foldlproof_of_nameD, Qs, ByQ1, ByQ
else
ByP = ByP0
ByQ = ByQ1
854proof_of_nameD, Q, ByQ0, ByQ ←
get_assocQ, ByQ0, P
P ≡ tmpD
→
del_assocQ, ByQ0, _, ByQ
else
ByQ = ByQ0
renewed_inst+Q, +Renew, +Q1−P, +Insts0, +Fn, +Single, -Inst, -Insts

a renew of Q1 and the access after it, at P, the replaced check's pointer, which has Q1's checked pointer's address and capability (the validator's same_pointer/3, docs/m7.md round 4 addendum). P1 is not read, so it need not stay live. Fusion and offsets as for a check.

862renewed_instQ, Renew, Q1−P, Access0|Insts, Fn, Single, fusedRenew, Access, Insts ←
!
get_assocQ1, Single, _
P = tmp_
proven_accessAccess0, Q, P, Access
Access ≠ let_, _, raw_cas_, _, _
→
true
else
throw_fnFn, 'a renew without a single-definition source, or its access'
Renew = renew0, Size, _, Align
memberchkAlign, 1, 2, 4, 8
integerSize
Size > 0
Size ≤ 2147483647
access_pointerAccess, _, 0
→
true
else
throw_fnFn, 'a renew or offset access the emitter cannot emit yet'
875renewed_inst_, _, _, [], Fn, _, _, _ ←
throw_fnFn, 'a renew not followed by its access'
879fused_instlettmpQ, a, Check, [], Access0|Insts, Fn, fusedCheck, Access, Insts ←
Check = check_localName, Off, _, _, _
!
proven_local_accessAccess0, Q, localName, Off, Access
access_pointerAccess, _, 0
→
true
else
throw_fnFn, 'a local check not followed by its local access'

A guarded renewal (docs/m7.md, round 5) fuses as a check does, and keeps its guard G for emission and liveness.

888fused_instlettmpQ, a, renew_ifLo, Hi, Rights, Align, P, G, Access0|Insts, Fn, fusedguardedG, renew_ifLo, Hi, Rights, Align, Access, Insts ←
!
G = tmp_
proven_accessAccess0, Q, P, Access
Access ≠ let_, _, raw_cas_, _, _
Lo =:= 0
Size is Hi
Size ≤ 2147483647
memberchkAlign, 1, 2, 4, 8
access_pointerAccess, _, 0
→
true
else
throw_fnFn, 'a renew_if or its access the emitter cannot emit yet'

A half check (docs/half.md) fuses as a check does, at P; its source proof Q1 only justifies it and is not read.

899fused_instlettmpQ, a, half_checkBound, Lo, Hi, Rights, Align, _, P, Access0|Insts, Fn, fusedhalf_checkBound, Lo, Hi, Rights, Align, Access, Insts ←
!
proven_accessAccess0, Q, P, Access
Lo =:= 0
Hi > 0
Hi ≤ 2147483647
memberchkAlign, 1, 2, 4, 8
access_pointerAccess, _, 0
→
true
else
throw_fnFn, 'a half_check or its access the emitter cannot emit yet'

A hoisted check (docs/bounds.md) fuses as a check does, and keeps its limit U for emission and liveness.

909fused_instlettmpQ, a, check_limitLo, Hi, Rights, Align, P, U, Access0|Insts, Fn, fusedlimitedU, check_limitLo, Hi, Rights, Align, Access, Insts ←
!
U = tmp_
proven_accessAccess0, Q, P, Access
Access ≠ let_, _, raw_cas_, _, _
Lo =:= 0
Hi ≤ 2147483647
memberchkAlign, 1, 2, 4, 8
access_pointerAccess, _, 0
→
true
else
throw_fnFn, 'a check_limit or its access the emitter cannot emit yet'
918fused_instlettmpQ, a, Check, P, Access0|Insts, Fn, Inst, Insts ←
!
proven_accessAccess0, Q, P, Access
→
Inst = fusedCheck, Access
else
throw_fnFn, 'a check not followed by the access it proves'
access_check//2 checks [P, P+Size) for alignment 1, 2, 4 or 8 (a
cas is aligned to its width), and a raw
access emits at offset 0. subq's immediate is signed 32-bit: a
larger check needs a different native representation, not truncation.
Check = check0, Size, _, Align
memberchkAlign, 1, 2, 4, 8
integerSize
Size > 0
Size ≤ 2147483647
access_pointerAccess, _, 0
→
true
else
throw_fnFn, 'a check or offset access_check//2 cannot emit yet'
932fused_instlettmpQ, f, check_function, P, Insts0, Fn, fusedcheck_function, callRes, P, Args, Insts ←
!
Insts0 = raw_callRes, tmpQ, Args|Insts
→
true
else
throw_fnFn, 'a function check not followed by its raw call'
937fused_instInst, Insts, Fn, Inst, Insts ←
→
throw_fnFn, 'an unchecked access; run pizlonate first'
→
throw_fnFn, 'an implicit pointer operation; run pizlonate first'
else
true
proven_access+Access0, +Q, +P, -Access

Access0 is a raw access at tmp(Q) and Access is the same at P.

947proven_accessletD, K, raw_loadT, Off, tmpQ, Q, P, letD, K, raw_loadT, Off, P ←
T ≢ p
949proven_accessraw_storeW, Off, V, tmpQ, Q, P, raw_storeW, Off, V, P ←
W ≢ p
951proven_accessletD, p, raw_load_pointerOff, Es, tmpQ, Q, P, letD, p, raw_load_pointerOff, Es, P ←
954proven_accessraw_store_pointerOff, V, tmpQ, Es, Q, P, raw_store_pointerOff, V, P, Es ←

Atomic pointer accesses are heap-only: no proven_local_access clause.

958proven_accessletD, p, raw_load_pointerOff, Es, tmpQ, Q, P, letD, p, raw_load_pointerOff, Es, P ←
pointer_memory_effectsatomic_load, Es
961proven_accessraw_store_pointerOff, V, tmpQ, Es, Q, P, raw_store_pointerOff, V, P, Es ←
pointer_memory_effectsatomic_store, Es

A touch (docs/vector-access.md) uses the proof and accesses nothing.

966proven_accesstouchtmpQ, Q, P, touchP

A cas, heap only too (escape.pl), reads Expected and New beside P.

968proven_accessletD, K, raw_casT, Off, tmpQ, E, N, Q, P, letD, K, raw_casT, Off, P, E, N
971proven_local_accessletD, p, raw_load_pointerOff, Es, tmpQ, Q, P, letD, p, raw_load_pointerOff, Es, P ←
974proven_local_accessraw_store_pointerOff, V, tmpQ, Es, Q, P, raw_store_pointerOff, V, P, Es ←
pointer_memory_effectslocal_store, Es
977proven_local_accessletD, K, raw_loadT, Off, tmpQ, Q, P, letD, K, raw_loadT, Off, P ←
T ≢ p
979proven_local_accessraw_storeW, Off, V, tmpQ, Q, P, raw_storeW, Off, V, P ←
W ≢ p
access_pointer+Access, -P, -Off

the fused Access is at P + Off.

access_pointer
983let_, _, raw_load_, Off, PPOff
984raw_store_, Off, _, PPOff
985let_, p, raw_load_pointerOff, _, PPOff
986raw_store_pointerOff, _, P, _POff
987let_, _, raw_cas_, Off, P|_POff
988touchPP0
989access_pointerAccess, P ←
access_pointerAccess, P, _
zero_offset+Access, -Access0

Access at offset 0.

zero_offset
992letD, K, raw_loadT, _, PsletD, K, raw_loadT, 0, Ps
993raw_storeW, _, V, Praw_storeW, 0, V, P
994letD, p, raw_load_pointer_, Es, PsletD, p, raw_load_pointer0, Es, Ps
unchecked_accesslet_, _, load_, _store_, _, _let_, _, atomic_load_, _atomic_store_, _, _call_, tmp_, _raw_call_, _, _let_, _, raw_load_, _, _raw_store_, _, _, _let_, _, raw_load_pointer_, _, _raw_store_pointer_, _, _, _let_, _, raw_cas_, _, _local_load_, _, _, _, _local_store_, _, _, _access_, _, _, _touch_
1012live_blockblockL, Phis, Insts0, Jump, blockL, Phis, Insts, Jump ←
maplistlive_inst, Insts0, Insts

Layout sees exactly the former whole-pointer operation at this index. In particular both original p operands survive every internal helper. A guarded access reads its guard too, so G keeps its slot to here.

1018live_instfusedguardedG, _, Access, usesInst, G ←
!
live_instfusednone, Access, Inst
1020live_instfusedlimitedU, _, Access, usesInst, U ←
!
live_instfusednone, Access, Inst
1022live_instfused_, letD, p, raw_load_pointerOff, _, P, letD, p, raw_loadp, Off, P ←
!
1024live_instfused_, raw_store_pointerOff, V, P, _, raw_storep, Off, V, P ←
!
1026live_instreusedAccess, Access ←
!
1027live_instInst0, Inst ←
Inst0 = fused_, Access
→
Inst = Access
else
Inst = Inst0
1030throw_fnFn, Why ←
formatatomMsg, 'Fil-C backend, in ~w: ~w', Fn, Why
thrownanofil_errorMsg
1034setjmp_snapshots[], _, _ ⟶
[]
1035setjmp_snapshotsD|Ds, Bytes, Ctx ⟶
slot_reg_loadthread, rdi, Ctx
insmov, q, immBytes, regrsi, inscall, none, pltfilc_allocate, inslea, q, mem16, rax, regrdx
reg_slot_storerdx, lowersetjmp_snapshotD, Ctx
setjmp_snapshotsDs, Bytes, Ctx
1044body_function_objectgeneric_onlyAdapter, Name, Linkage, Impl ⟶
variadic_objectName, Linkage, Adapter, Impl
1046body_function_objectfast_and_genericSig, Adapter, Name, Linkage, Impl ⟶
function_objectName, Linkage, Adapter, Sig, Impl

The return protocol is already chosen; this is only its native size.

1050target_formregisters, reg
1051target_formbufferN, memBytes ←
Bytes is 8 + 8 × N + 15//16 × 16
1053abi_actions[], _, _, _ ⟶
[]
1054abi_actionsA|As, I, B, Ctx ⟶
abi_actionA, I, B, Ctx
abi_actionsAs, I, B, Ctx
1057abi_actionnative_frameReturn, _, _, Ctx ⟶
ctx_sizeCtx, Size
target_formReturn, Form
inspush, q, regrbp, insmov, q, regrsp, regrbp, inssub, q, immSize, regrsp
1063abi_actionstack_check, _, _, _ ⟶
fieldthread, stack_limit, rdi, Limit
inscmp, q, regrsp, Limit, insjae, none, pltfilc_stack_overflow_failure
1067abi_actionsave_thread, _, _, Ctx ⟶
reg_slot_storerdi, thread, Ctx
1068abi_actionsave_returnReturn, _, _, Ctx ⟶
target_formReturn, Form
retbuf_storeForm, Ctx
1070abi_actionzero_rootsN, _, _, Ctx ⟶
1072abi_actionpush_frameWhich, _, _, Ctx ⟶
NegFrameBase is −Base
fieldthread, top_frame, rdi, Top
insmov, q, Top, regrax, insmov, q, regrax, memNegFrameBase, rbp
origin_storeCtx, Which
inslea, q, memNegFrameBase, rbp, regrax, insmov, q, regrax, Top
1080abi_actionreceiveReturn, Ts, _, _, Ctx ⟶
target_formReturn, Form
typed_locationsTs, Form, Ls, _, _
typed_incomingTs, Ls, Ctx
1083abi_actionsnapshotsSites, Keys, _, _, Ctx ⟶
lengthKeys, N
Bytes is 8 × N
setjmp_snapshotsSites, Bytes, Ctx
1085abi_actioninitialize_localName, _, _, Ctx ⟶
ctx_slotCtx, localName, localPayload, Aux, _, Padded
P is −Payload
A is −Aux
Words is Padded//8
zero_wordsP, Words
Lowers is A + 8
zero_wordsLowers, Words
insmov, q, immWords, memA, rbp, inslea, q, memA, rbp, regrax
reg_slot_storerax, stack_auxName, Ctx
1093abi_actionpop_frame, _, _, Ctx ⟶
1094abi_actionexceptionReturn, _, _, Ctx ⟶
target_formReturn, Form
insleave, none, [], insret, none, []
1097abi_actionoriginWhich, _, _, Ctx ⟶
origin_storeCtx, Which
1098abi_actionreserve_resultReturn, _, _, _ ⟶
1100abi_actionrelease_resultReturn, _, _, _ ⟶
1102abi_actioninvokeTarget, Return, Words, _, _, Ctx ⟶
target_formReturn, Form
target_operandTarget, Operand
fast_callForm, Words, directOperand, Ctx
1105abi_actionresultReturn, Channels, _, _, Ctx ⟶
target_formReturn, Form
fast_resultForm, Channels, Ctx
1107abi_actiondispatchP, Sig, Return, Words, Result, Size, Cells, GenericResult, I, B, Ctx ⟶
special_labelCtx, notfn, NotFn
target_formReturn, Form
reg_slot_storercx, aux, Ctx
indirect_callSig, Form, Words, Result, Size, Cells, GenericResult, I, B, Ctx
1111abi_actiongetterweak_getterS, Which, D, I, B, Ctx ⟶
!
A weak declaration's getter is null if nothing defines it; then its
address is the null pointer (clang's filc_weak_symbol_is_null).
abi_symbolgetter, S, Getter
local_labelCtx, B, I, weak_null, Null
local_labelCtx, B, I, weak_done, Done
inscmp, q, imm0, gotpcrelGetter, insje, none, symNull
abi_actiongetterexternal_getterS, Which, D, I, B, Ctx
insjmp, none, symDone, labelNull, insxor, l, regeax, regeax, insxor, l, regecx, regecx
labelDone
1123abi_actiongetterTarget, Which, D, _, _, Ctx ⟶
ctx_nameCtx, Name
origin_labelName, Which, Origin
target_operandTarget, Getter
slot_reg_loadthread, rdi, Ctx
inslea, q, ripOrigin, regrsi, inscall, none, Getter, insmov, q, regrdx, regrcx
1130abi_actionsnapshot_cursorSnapshot, Off, D, _, _, Ctx ⟶
ctx_fnCtx, Fn
flight_loadSnapshot, Ctx
loadconstOff, l, d, Fn
insadd, q, regrdx, regrax
1134abi_actionconsume_vaK, P, Size, D, I, B, Ctx ⟶
insmov, q, regrax, regrdi, insmov, q, regrcx, regrsi, insmov, l, immSize, regedx, insmov, l, immSize, regecx, inscall, none, pltfilc_get_next_ptr_bytes_for_va_arg
va_arg_loadK, D, I, B, Ctx
1140abi_actionreturns_twiceD, Kind, Buf, Mask, Keys, I, B, Ctx ⟶
setjmpD, Kind, Buf, Mask, Keys, I, B, Ctx
1142abi_actionreturn_valueK, V, _, _, Ctx ⟶
ret_valueK, V, Ctx

zreturn: filc_prepare_to_return_with_data(thread, V, no origin) copies V's words into the thread's call buffer and returns their byte count, which %rdx carries out through pop_frame to the generic entry.

1146abi_actionreturn_dataV, _, _, Ctx ⟶
insmov, q, regrax, regrsi, insmov, q, regrcx, regrdx
slot_reg_loadthread, rdi, Ctx
insxor, l, regecx, regecx, inscall, none, pltfilc_prepare_to_return_with_data, insmov, q, regrax, regrdx
1153abi_actionreturn_typedKs, Vs, _, _, Ctx ⟶
ctx_fnCtx, Fn
foldltyped_return_word, Ks, Vs, KTs, []
typed_return_loadsKTs, Rs, Fn, Ctx
1157abi_actionreturn_registersV1, V2, _, _, Ctx ⟶
ctx_fnCtx, Fn
loadV1, l, d, Fn
loadV2, l, c, Fn
1159abi_actionreturn_bufferWords, _, _, Ctx ⟶
slot_reg_loadretbuf, rax, Ctx
buffer_wordsWords, 8, Ctx
1161abi_actionsuccessregisters, _, _, _ ⟶
insxor, l, regeax, regeax, insleave, none, [], insret, none, []
1163abi_actionsuccess_buffer, _, _, _ ⟶
insmov, b, imm0, mem0, rax
1165abi_actionnative_return, _, _, _ ⟶
insleave, none, [], insret, none, []
1166abi_actioncopy_filterD, S, N, Res, I, B, Ctx ⟶
ctx_copiesCtx, inline
→
copy_filterD, S, N, Res, I, B, Ctx
else
[]
1170abi_actioncopy_join, I, B, Ctx ⟶
ctx_copiesCtx, inline
→
local_labelCtx, B, I, copied, Done
labelDone
else
[]

The small-copy filter (docs/perf.md item 7a): a copy of N bytes, 1 =< N =< 16, from S to D, done inline when the runtime's memmove would pass its checks and have nothing to do for aux; otherwise a jump to the unchanged call after it. D is in %rax:%rcx and S in %rsi:%rdi; the checks and the copy use %rdx and %r8. Only the call's scratch registers, which nothing allocates, are named.

1182copy_filterD, S, N, Res, I, B, Ctx ⟶
ctx_fnCtx, Fn
local_labelCtx, B, I, copy, Slow
local_labelCtx, B, I, copied, Done
loadD, l, a, Fn
lower_reg_loadD, rcx, Ctx
copy_checkrax, rcx, N, 0x0786ffffffffffff, Slow
loadS, l, si, Fn
lower_reg_loadS, rdi, Ctx
copy_checkrsi, rdi, N, 0x0000ffffffffffff, Slow
copy_partsN, Parts
copy_loadsParts, rdx, r8
copy_storesParts, rdx, r8
copy_resultRes, Ctx
insjmp, none, symDone, labelSlow

A jump to Slow unless the pointer P with lower L may be accessed for N bytes (CHECK_BOUNDS_FAST: lower =< P, N =< upper - P) and the header word at -8(L) has none of Mask's bits: the aux pointer (low 48 bits) and, for the destination, READONLY | FREE | SPECIAL (CHECK_WRITE_FAST).

1201copy_checkP, L, N, Mask, Slow ⟶
instest, q, regL, regL, insjz, none, symSlow, inscmp, q, regL, regP, insjb, none, symSlow, insmov, q, mem-16, L, regrdx, inssub, q, immN, regrdx, insjb, none, symSlow, inscmp, q, regrdx, regP, insja, none, symSlow, insmovabs, q, immMask, regrdx, instest, q, regrdx, mem-8, L, insjnz, none, symSlow
copy_parts+N, -Parts

the accesses, Bytes-Offset, that cover N bytes; two may overlap.

1217copy_partsN, N−0 ←
memberchkN, 1, 2, 4, 8
!
1218copy_parts3, 2−0, 2−1 ←
!
1219copy_partsN, 4−0, 4−O ←
N < 8
!
O is N−4
1220copy_partsN, 8−0, 8−O ←
O is N−8

Every load before any store, so an overlapping memmove copies the source as it was.

1224copy_loads[], _ ⟶
[]
1225copy_loadsBytes−Off|Ps, R|Rs ⟶
copy_loadBytes, Op, S, R, Rn
insOp, S, memOff, rsi, regRn
1229copy_stores[], _ ⟶
[]
1230copy_storesBytes−Off|Ps, R|Rs ⟶
copy_regBytes, R, Rn
copy_widthBytes, S
insmov, S, regRn, memOff, rax
1235copy_load1, movzb, l, R, Rn ←
copy_reg4, R, Rn
1236copy_load2, movzw, l, R, Rn ←
copy_reg4, R, Rn
1237copy_load4, mov, l, R, Rn ←
copy_reg4, R, Rn
1238copy_load8, mov, q, R, Rn ←
copy_reg8, R, Rn
copy_width
12391b
12392w
12394l
12398q
copy_reg
12401rdxdl
12402rdxdx
12404rdxedx
12408rdxrdx
12411r8r8b
12412r8r8w
12414r8r8d
12418r8r8

The call's result is its destination, address and lower.

1244copy_resultvoid, _ ⟶
[]
1245copy_resultresultT, p, Ctx ⟶
reg_slot_storerax, T, Ctx
reg_slot_storercx, lowerT, Ctx
1248target_operandimplementationF, Sig, symS ←
abi_symbolimplSig, F, S
1249target_operandentryF, Sig, symS ←
abi_symbolentrySig, F, S
1250target_operandlocal_getterF, symS ←
abi_symbolgetter, F, S
1251target_operandexternal_getterF, pltS ←
abi_symbolgetter, F, S

A buffered return's entry has the buffer in %rdi and the thread in %rsi: the buffer goes to %rax and the thread to %rdi.

1255entry_regs_shiftreg ⟶
[]
1256entry_regs_shiftmem_ ⟶
insmov, q, regrdi, regrax, insmov, q, regrsi, regrdi

The buffer of a buffered return, from %rax into its slot.

1261retbuf_storereg, _ ⟶
[]
1262retbuf_storemem_, Ctx ⟶
reg_slot_storerax, retbuf, Ctx

The zeroing of the N entries of lowers[], so the GC sees no stale lowers.

1266lowers_zeroingN, FrameBase ⟶
N > 0
→
insxor, l, regeax, regeax
N1 is N−1
numlist0, N1, Is
lower_entry_storesIs, FrameBase
else
[]

The store of %rax into lowers[I] for each I in Is.

1276lower_entry_stores[], _ ⟶
[]
1277lower_entry_storesI|Is, FrameBase ⟶
Off is 16 + 8 × I−FrameBase
insmov, q, regrax, memOff, rbp
lower_entry_storesIs, FrameBase
1282zero_words_, 0 ⟶
!
[]
1283zero_wordsOff, N ⟶
insmov, q, imm0, memOff, rbp
Next is Off + 8
Rest is N−1
zero_wordsNext, Rest

The store of the c or n origin (origin_label/3) into the filc_frame.

1288origin_storeCtx, Which ⟶
ctx_nameCtx, Name
ctx_frame_baseCtx, FrameBase
Off is 8−FrameBase
origin_labelName, Which, Origin
inslea, q, ripOrigin, regrax, insmov, q, regrax, memOff, rbp

The pop of the filc_frame: thread->top_frame back to its parent.

1297frame_popCtx ⟶
ctx_frame_baseCtx, FrameBase
NegFrameBase is −FrameBase
fieldthread, top_frame, rsi, Top
slot_reg_loadthread, rsi, Ctx
insmov, q, memNegFrameBase, rbp, regrdi, insmov, q, regrdi, Top

has_exception = 1, in %al or in the return buffer.

1306exception_flagreg, _ ⟶
insmov, b, imm1, regal
1308exception_flagmem_, Ctx ⟶
slot_reg_loadretbuf, rax, Ctx
insmov, b, imm1, mem0, rax

The failure block of function_check//1, if the function calls through a pointer.

1314not_function_blockBlocks, Ctx ⟶
memberblock_, _, Insts, _, Blocks
memberabiActions, Insts
memberchkdispatch_, _, _, _, _, _, _, _, Actions
→
special_labelCtx, notfn, NotFn
labelNotFn, insmov, q, regrax, regrdi, insmov, q, regrcx, regrsi, inscall, none, pltfilc_check_function_call_fail, insud2, none, []
else
[]

The failure block of jump(unreachable)//3, if a block ends in one: filc_error(Message, Origin) with the frame's n origin.

1329unreachable_blockBlocks, Ctx ⟶
memberchkblock_, _, _, unreachable, Blocks
→
special_labelCtx, unreachable, Unreachable
ctx_nameCtx, Name
origin_labelName, n, Origin
labelUnreachable, inslea, q, ripMessage, regrdi, inslea, q, ripOrigin, regrsi, inscall, none, pltfilc_error, insud2, none, []
else
[]

clang's message, so a nanofil panic reads as clang's does.

1344unreachable_messageBlocks, Name ⟶
memberchkblock_, _, _, unreachable, Blocks
→
sectionrodata, labelMessage, asciz'llvm unreachable instruction'
else
[]
1352unreachable_message_labelName, Label ←
formatatomLabel, '.L~w..unreachable_msg', Name

The same registers by their amd64 short names, for load//4.

1356form_short_regsreg, d, c, r8, r9
1357form_short_regsmem_, c, r8, r9

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.

1362generic_offsetsI, Addr, Lower ←
struct_fieldthread, cc_inline_buffer, Buffer
struct_fieldthread, cc_inline_aux_buffer, AuxBuffer
Addr is Buffer + 8 × I
Lower is AuxBuffer + 8 × I
1368channel_offsetdataI, Off ←
1369channel_offsetauxI, Off ←
1370packet_offsetspacketCopies, Zeros, packetOffsets, ZeroOffsets ←
maplistchannel_offset, Copies, Offsets
maplistchannel_offset, Zeros, ZeroOffsets

Stack space for a buffered return, around a call.

1374ret_buffer_reservationreg ⟶
[]
1375ret_buffer_reservationmemBytes ⟶
inssub, q, immBytes, regrsp
1377ret_buffer_releasereg ⟶
[]
1378ret_buffer_releasememBytes ⟶
insadd, q, immBytes, regrsp

The padding, Pad = 0 or 1 words, that keeps %rsp 16-aligned at a call that pushes an odd number of words.

1382align_pad0 ⟶
[]
1383align_pad1 ⟶
inssub, q, imm8, regrsp

The release of Bytes of pushed stack words, if any.

1386stack_releaseBytes ⟶
Bytes > 0
→
insadd, q, immBytes, regrsp
else
[]

Blocks and jumps

The blocks' code, with each shared upper resolved (check_share.pl) when checks share.

1396shared_bodyBlocks, Ctx ⟶
→
blocksBlocks, Ctx
else
phraseblocksBlocks, Ctx, Lines0
shared_uppersLines0, Lines
Lines

Each block: its label, instructions and jump.

1402blocks[], _ ⟶
[]
1403blocksBlock|Blocks, Ctx ⟶
block_onceBlock, Blocks, Ctx
blocksBlocks, Ctx

A block's code is its first: committing frees the choicepoints that would keep a large function's (sqlite3VdbeExec's) garbage alive. Blocks are the ones after it, for branch selection's fallthrough.

1410block_onceBlock, Blocks, Ctx, S0, S ←
onceblock_codeBlock, Blocks, Ctx, S0, S
1412block_codeblockLabel, _, Insts, Jump, Blocks, Ctx ⟶
block_labelCtx, Label, A
labelA
ctx_branchesCtx, selectedUses
→
next_blockBlocks, Next
compare_branchInsts, Jump, Uses, Insts1, Jump1
instsInsts1, 0, Label, Ctx
selected_jumpJump1, Label, Next, Ctx
else
instsInsts, 0, Label, Ctx
jumpJump, Label, Ctx

The code of the jump that ends block From.

1425jumpgotoTo, From, Ctx ⟶
edgeFrom, To, j, Ctx
1427jumpbranchV, True, False, From, Ctx ⟶
ctx_fnCtx, Fn
local_labelCtx, From, jnz, false, FalseLabel
loadV, w, a, Fn
instest, l, regeax, regeax, insjz, none, symFalseLabel
edgeFrom, True, t, Ctx
labelFalseLabel
edgeFrom, False, f, Ctx
1436jumpabi_returnActions, B, Ctx ⟶
abi_actionsActions, ret, B, Ctx

A reached unreachable is a Fil-C safety panic, as clang's is (FilPizlonator's UnreachableInst case): one shared block per function.

1439jumpunreachable, _, Ctx ⟶
special_labelCtx, unreachable, Unreachable
insjmp, none, symUnreachable

The returned value V of class K, in %rdx, and its lower in %rcx for p.

1444ret_valuep, V, Ctx ⟶
ctx_fnCtx, Fn
loadV, l, d, Fn
lower_reg_loadV, rcx, Ctx
1448ret_valuew, V, Ctx ⟶
ctx_fnCtx, Fn
loadV, w, d, Fn
1451ret_valuel, V, Ctx ⟶
ctx_fnCtx, Fn
loadV, l, d, Fn
1454ret_valueK, V, Ctx ⟶
loadV, K, a, Fn
to_xmmK, a, xmm0

A returned tuple's words as K-Word, a p value being its address and lower; then each loaded into its result register.

1460typed_return_wordp, V, l−addrV, l−lowerV|W, W ←
!
1461typed_return_wordK, V, K−valV|W, W
1462typed_return_loads[], [], _, _ ⟶
[]
1463typed_return_loadsK−W|KTs, R|Rs, Fn, Ctx ⟶
typed_return_loadW, K, R, Fn, Ctx
typed_return_loadsKTs, Rs, Fn, Ctx
1465typed_return_loadvalV, K, xmmK, X, Fn, _ ⟶
loadV, K, a, Fn
to_xmmK, a, X
1466typed_return_loadvalV, K, gprG, Fn, _ ⟶
loadV, K, R, Fn
1467typed_return_loadaddrV, _, gprG, Fn, _ ⟶
loadV, l, R, Fn
1468typed_return_loadlowerV, _, gprG, _, Ctx ⟶
1469short_regrdx, d
1470short_regrcx, c

The words of a buffered return, into the buffer at %rax.

1473buffer_words[], _, _ ⟶
[]
1474buffer_wordswf_, Word|Words, Off, Ctx ⟶
ctx_fnCtx, Fn
reg_nameR, l, Reg
buffer_word_loadWord, R, Fn, Ctx
insmov, q, regReg, memOff, rax
Next is Off + 8
buffer_wordsWords, Next, Ctx
buffer_register
1479lower_c
1480addr_d
1481val_, _d

Tuple payloads occupy full eightbyte slots even for a w element.

1483buffer_word_loadval_, V, R, Fn, _ ⟶
loadV, l, R, Fn
1484buffer_word_loadaddrV, R, Fn, _ ⟶
loadV, l, R, Fn
1485buffer_word_loadlowerV, R, _, Ctx ⟶
reg_nameR, l, Reg
lower_reg_loadV, Reg, Ctx

Phi copies for the edge to Target, a label or poll(Label), then the pollcheck if Target asks for one, then the jump.

1490edgeFrom, Target, Tag, Ctx ⟶
edge_movesFrom, Target, Tag, Ctx
edge_labelTarget, To
block_labelCtx, To, ToLabel
insjmp, none, symToLabel
1496edge_movesFrom, Target, Tag, Ctx ⟶
edge_labelTarget, To
ctx_labelsCtx, Labels
get_assocTo, Labels, Phis
phrase
phi_readsPhis, From, Ctx
phi_writesPhis, Ctx
,
Copy
maplistcopy_line, Copy
→
true
else
thrownanofil_error'a phi copy contains a possible safepoint'
seqCopy
edge_pollTarget, From, Tag, Ctx
copy_line+Line

Line is a move that cannot reach a safepoint: no call, poll, helper, trap or jump. Phis of different blocks share shadow slots and roots (filc_abi.pl's shadow_storage/8) because no safepoint is inside an edge's parallel copy; this keeps it so.

copy_lineinsmov, _, _, _insmovabs, q, imm_, reg_inslea, q, rip_, reg_

Branch selection (docs/perf.md). A block whose last instruction is an integer comparison read only by the block's branch branches on the comparison's flags; a branch whose edge needs no phi copies or poll jumps straight to its block; an edge to the next block falls through. The reference above stores the comparison's boolean, tests it and jumps on both arms. Setjmp functions keep the reference.

branch_selection+Mode, +Blocks, -Branches

reference, or selected(Uses), where Uses counts each temporary's occurrences in Blocks, its definition included.

1525branch_selectionreference, _, reference
1526branch_selectionselected, Blocks, selectedUses ←
phrasetemp_namesBlocks, Names
msortNames, Sorted
name_countsSorted, Counts
list_to_assocCounts, Uses
1532temp_namestmpN ⟶
!
N
1533temp_namesT ⟶
compoundT
→
functorT, _, Arity
temp_name_args1, Arity, T
else
[]
1538temp_name_argsI, N, T ⟶
I ≤ N
→
argI, T, A
J is I + 1
else
[]
1544name_counts[], []
1545name_countsN|Ns, N−C|Cs ←
same_namesNs, N, 1, C, Rest
name_countsRest, Cs
1546same_namesM|Ms, N, C0, C, Rest ←
M ≡ N
!
C1 is C0 + 1
same_namesMs, N, C1, C, Rest
1547same_namesRest, _, C, C, Rest
1549next_blockblockL, _, _, _|_, someL
1550next_block[], none
compare_branch+Insts, +Jump, +Uses, -Insts1, -Jump1

a last integer comparison that only the branch reads moves into the jump.

1554compare_branchInsts, branchtmpD, T, F, Uses, Init, compareC, K, Args, T, F ←
onceappendInit, lettmpD, w, cmpC, K, Args, Insts
K ≡ w
or
K ≡ l
get_assocD, Uses, 2
!
1559compare_branchInsts, Jump, _, Insts, Jump
1561selected_jumpcompareC, K, X, Y, T, F, From, Next, Ctx ⟶
ctx_fnCtx, Fn
reg_namea, K, A
loadX, K, a, Fn
Y = constN
→
inscmp, S, immV, regA
else
reg_namec, K, RC
loadY, K, c, Fn
inscmp, S, regRC, regA
selected_branchCC, T, F, From, Next, Ctx
1571selected_jumpbranchV, T, F, From, Next, Ctx ⟶
ctx_fnCtx, Fn
loadV, w, a, Fn
instest, l, regeax, regeax
selected_branchne, T, F, From, Next, Ctx
1576selected_jumpgotoTo, From, Next, Ctx ⟶
selected_edgeFrom, To, j, Next, Ctx
1578selected_jumpabi_returnAs, From, _, Ctx ⟶
jumpabi_returnAs, From, Ctx
1579selected_jumpunreachable, From, _, Ctx ⟶
jumpunreachable, From, Ctx

The flags hold condition CC exactly when the branch goes to True.

1582selected_branchCC, True, False, From, Next, Ctx ⟶
Next ≡ someTrue
plain_edgeTrue, Ctx
plain_edgeFalse, Ctx
→
block_labelCtx, False, FalseLabel
negatedCC, NC
atom_concatj, NC, J
insJ, none, symFalseLabel
plain_edgeTrue, Ctx
→
block_labelCtx, True, TrueLabel
atom_concatj, CC, J
insJ, none, symTrueLabel
selected_edgeFrom, False, f, Next, Ctx
plain_edgeFalse, Ctx
→
block_labelCtx, False, FalseLabel
negatedCC, NC
atom_concatj, NC, J
insJ, none, symFalseLabel
selected_edgeFrom, True, t, Next, Ctx
else
local_labelCtx, From, jnz, false, FalseLabel
negatedCC, NC
atom_concatj, NC, J
insJ, none, symFalseLabel
edgeFrom, True, t, Ctx
labelFalseLabel
selected_edgeFrom, False, f, Next, Ctx

An edge to a block without phis, and without a poll.

1603plain_edgeTarget, Ctx ←
Target ≠ poll_
ctx_labelsCtx, Labels
get_assocTarget, Labels, []

The edge's code without its jump when it goes to the next block.

1609selected_edgeFrom, Target, Tag, Next, Ctx ⟶
edge_labelTarget, To
Next ≡ someTo
→
edge_movesFrom, Target, Tag, Ctx
else
edgeFrom, Target, Tag, Ctx
negated
1615ene
1615nee
1616lge
1616gel
1616leg
1616gle
1617bae
1617aeb
1617bea
1617abe
1619edge_pollTarget, From, Tag, Ctx ⟶
Target = poll_
→
pollcheckCtx, From, Tag
else
[]

The pollcheck: a call of filc_pollcheck_slow if the thread's state asks for it.

1627pollcheckCtx, Block, Tag ⟶
ctx_nameCtx, Name
local_labelCtx, Block, Tag, poll, NoPoll
origin_labelName, n, Origin
fieldthread, state, rdi, State
slot_reg_loadthread, rdi, Ctx
instest, b, imm14, State, insjz, none, symNoPoll, inslea, q, ripOrigin, regrsi
slow_callfilc_pollcheck_slow
labelNoPoll

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).

1642phi_reads[], _, _ ⟶
[]
1643phi_readsphitmpD, K, Pairs|Phis, From, Ctx ⟶
memberchkFrom−V, Pairs
phi_readK, V, D, Pairs, Ctx
phi_readsPhis, From, Ctx

The incoming value V, of class K, into the shadow slots of phi tmp(D). A shared phi's lower is already in its entry (shared_phi/3).

1650phi_readp, V, D, Pairs, Ctx ⟶
ctx_fnCtx, Fn
loadV, l, a, Fn
reg_slot_storerax, shadowD, Ctx
shared_phi_lowerD, Pairs, Ctx
→
[]
else
lower_reg_loadV, rax, Ctx
reg_slot_storerax, lowershadowD, Ctx
1658phi_readw, V, D, _, Ctx ⟶
ctx_fnCtx, Fn
loadV, w, a, Fn
reg_slot_storerax, shadowD, Ctx
1662phi_readl, V, D, _, Ctx ⟶
ctx_fnCtx, Fn
loadV, l, a, Fn
reg_slot_storerax, shadowD, Ctx
1666phi_readK, V, D, _, Ctx ⟶
loadV, K, a, Fn
reg_slot_storerax, shadowD, Ctx

The second half of the parallel copy: each phi's shadow into the phi.

1671phi_writes[], _ ⟶
[]
1672phi_writesphitmpD, K, Pairs|Phis, Ctx ⟶
slot_reg_loadshadowD, rax, Ctx
reg_slot_storerax, tmpD, Ctx
phi_lower_writeK, D, Pairs, Ctx
phi_writesPhis, Ctx

For a p phi tmp(D), its shadow lower into its lower, unless it is shared.

1679phi_lower_writep, D, Pairs, Ctx ⟶
shared_phi_lowerD, Pairs, Ctx
→
[]
else
slot_reg_loadlowershadowD, rax, Ctx
reg_slot_storerax, lowertmpD, Ctx
1684phi_lower_writew, _, _, _ ⟶
[]
1685phi_lower_writel, _, _, _ ⟶
[]
1686phi_lower_writes, _, _, _ ⟶
[]
1687phi_lower_writed, _, _, _ ⟶
[]

The lower of a p value: its lowers[] entry, or 0 for a constant.

1690lower_reg_loadtmpN, Reg, Ctx ⟶
slot_reg_loadlowertmpN, Reg, Ctx
1691lower_reg_loadconst_, Reg, _ ⟶
insmov, q, imm0, regReg

A p value's address into %rax and lower into %rcx.

1694flight_loadV, Ctx ⟶
ctx_fnCtx, Fn
loadV, l, a, Fn
lower_reg_loadV, rcx, Ctx

The flight pointer in %rax:%rcx into tmp(D) and its lower.

1700flight_storeD, Ctx ⟶
reg_slot_storerax, tmpD, Ctx
reg_slot_storercx, lowertmpD, Ctx
shared_lower+Lower, +D, +Ctx

the flight tmp(D)'s lower is its donor P's lowers[] entry (--lowers=shared, docs/lowers.md), which already holds it, so the flight neither loads nor stores a lower (lowers_check.pl validates that the entry does hold it).

1708shared_lowerlowertmpP, D, Ctx ←
P ≢ D
ctx_slotCtx, lowertmpP, Off
ctx_slotCtx, lowertmpD, Off
shared_phi_lower+D, +Pairs, +Ctx

under --lowers=shared, every input in Pairs of the p phi tmp(D) is a temporary whose lowers[] entry is the phi's, so the edge copies no lower (lowers_check.pl validates that the entry holds the input's lower at the end of each predecessor).

1715shared_phi_lowerD, Pairs, Ctx ←
ctx_slotCtx, lowertmpD, Off
forallmember_−V, Pairs,
V = tmpN
ctx_slotCtx, lowertmpN, Off

Instructions

The code of each instruction. Invariant: Insts are the instructions of Block from index Index on.

1724insts[], _, _, _ ⟶
[]
1725instsInst|Insts, Index, Block, Ctx ⟶
inst_onceInst, Index, Block, Ctx
Index1 is Index + 1
instsInsts, Index1, Block, Ctx

An instruction's code is its first, as a block's is (block_once/5): inst//4's clauses leave choicepoints (shared first-argument functors, reg_name/3), which would keep every earlier instruction of a long block's frames alive (libaom: 30k instructions overflowed 1 GB).

1734inst_onceInst, Index, Block, Ctx, S0, S ←
onceinstInst, Index, Block, Ctx, S0, S
inst+Inst, +Index, +Block, +Ctx//

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.

1739instlettmpD, K, Op, Args, Index, Block, Ctx ⟶
op_kindOp, Kind
kind_instKind, K, Op, Args, D, Index, Block, Ctx
1742instflighttmpD, Op, A|Args, Lower, _, _, Ctx ⟶
shared_lowerLower, D, Ctx
→
flight_calculationOp, Args, Ctx
reg_slot_storerax, tmpD, Ctx
1752instfusedcheck_, Size, Rights, Align, Access, Index, Block, Ctx ⟶
rights_writeRights, Write
access_checkkSize, Write, Align, Ctx
raw_accessAccess, Index, Block, Ctx

A check with shared parts (check_share.pl): with the split bounds under local selection, the null test is left out when an earlier check of the same lower passed, and the upper reused right after one; otherwise the check as above.

1762instfusedsharedNull, Upper, check_, Size, Rights, Align, Access, Index, Block, Ctx ⟶
rights_writeRights, Write
ctx_selectionCtx, Selection
Selection ≡ local
Shape ≡ split
→
check_fail_labelCtx, kSize, Write, Align, Fail
share_boundsNull, Upper, Size, Fail
write_checkWrite, Fail
align_checkAlign, Fail
else
access_checkkSize, Write, Align, Ctx
raw_accessAccess, Index, Block, Ctx

A renew (docs/m7.md, round 4): the check's bounds and alignment still hold, so only its temporal part is tested, the free flag, and for a write the read-only one, in the flags byte write_check//2 tests. A failure goes to the same reporter with the same pointer and origin.

1780instfusedrenew_, Size, Rights, Align, Access, Index, Block, Ctx ⟶
rights_writeRights, Write
renew_maskWrite, Mask
check_fail_labelCtx, kSize, Write, Align, Fail
instest, b, immMask, mem-2, rcx, insjnz, none, symFail
raw_accessAccess, Index, Block, Ctx

A half check (docs/half.md): the check with only one bound tested, the other being known from an earlier check of the object, and the same flags and alignment tests. Its failure goes to the check's label with %rax:%rcx = P, so the runtime reports what the full check would.

1793instfusedhalf_checkBound, _, Size, Rights, Align, Access, Index, Block, Ctx ⟶
rights_writeRights, Write
check_fail_labelCtx, kSize, Write, Align, Fail
ctx_selectionCtx, Selection
half_boundsBound, Selection, Size, Write, Fail
align_checkAlign, Fail
raw_accessAccess, Index, Block, Ctx

A guarded renewal (docs/m7.md, round 5): the flight is loaded once. When the span test G passed, the bounds hold, and only the flags a renew tests and the alignment are tested here; otherwise the full check runs out of line (guard_slow_blocks//2). Both fail to the check's label with %rax:%rcx = P, so the report is the check's.

1807instfusedguardedtmpG, renew_if_, Size, Rights, Align, Access, Index, Block, Ctx ⟶
rights_writeRights, Write
renew_maskWrite, Mask
check_fail_labelCtx, kSize, Write, Align, Fail
ctx_slotCtx, tmpG, GOff
GDisp is −GOff
local_labelCtx, Block, Index, full, Full
local_labelCtx, Block, Index, access, Here
inscmp, l, imm0, memGDisp, rbp, insje, none, symFull, instest, b, immMask, mem-2, rcx, insjnz, none, symFail
align_checkAlign, Fail
labelHere
raw_accessAccess, Index, Block, Ctx

A hoisted check (docs/bounds.md): the bounds against the limit U that limit/1 computed before the loop, then the free flag, and for a write the read-only one, as a renewal tests them, and the alignment. Every failure goes to the check's label with %rax:%rcx = P, so the report is the full check's.

1828instfusedlimitedU, check_limit_, Size, Rights, Align, Access, Index, Block, Ctx ⟶
rights_writeRights, Write
check_fail_labelCtx, kSize, Write, Align, Fail
ctx_fnCtx, Fn
loadU, l, d, Fn
limit_checkShape, Write, Fail
align_checkAlign, Fail
raw_accessAccess, Index, Block, Ctx

A reused pointer load needs P's lower in %rcx too, for its aux read.

1840instreusedAccess, Index, Block, Ctx ⟶
Access = let_, p, raw_load_pointer_, _, _
!
access_pointerAccess, P, Off
zero_offsetAccess, Access0
Off =:= 0
→
[]
else
insadd, q, immOff, regrax
raw_accessAccess0, Index, Block, Ctx
1846instreusedAccess, Index, Block, Ctx ⟶
access_pointerAccess, P, Off
ctx_fnCtx, Fn
zero_offsetAccess, Access0
loadP, l, a, Fn
Off =:= 0
→
[]
else
insadd, q, immOff, regrax
raw_accessAccess0, Index, Block, Ctx
1851instfusedcheck_localName, Off, Size, Rights, Align, Access, I, B, Ctx ⟶
rights_writeRights, Write
local_checkName, Off, kSize, Write, Align, I, B, Ctx
local_raw_accessAccess, I, B, Ctx
1855instabiActions, Index, Block, Ctx ⟶
abi_actionsActions, Index, Block, Ctx
1856instfence, _, _, _ ⟶
insmfence, none, []
1857instasm_fixedM, Ins, Outs, _, _, Ctx ⟶
ctx_fnCtx, Fn
asm_fixedM
1863asm_fixed_reg_stores[], _ ⟶
[]
1864asm_fixed_reg_storesR−T|Outs, Ctx ⟶

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.

1870local_checkName, Off, Kind, I, B, Ctx ⟶
Kind = kBytes, _, Align
Mask is Align−1
ctx_slotCtx, localName, localPayload, Aux, Size, _
P is −Payload
A is 8−Aux
Limit is Size−Bytes
local_labelCtx, B, I, localfail, Fail
local_labelCtx, B, I, localok, Good
ctx_nameCtx, FnName
check_origin_labelFnName, Kind, Origin
ctx_fnCtx, Fn
loadconstOff, l, a, Fn
loadconstSize, l, c, Fn
loadconstLimit, l, d, Fn
instest, q, regrdx, regrdx, insjs, none, symFail, inscmp, q, regrdx, regrax, insja, none, symFail, instest, q, immMask, regrax, insjnz, none, symFail, insjmp, none, symGood, labelFail, insmov, q, regrax, regrdi, insmov, q, regrcx, regrsi, inslea, q, ripOrigin, regrdx, inscall, none, pltfilc_optimized_stack_access_check_fail, insud2, none, [], labelGood
local_addressOff, Limit, Mask, A, P

The addresses of the access's aux word and payload byte. The check has passed, so an offset outside 0..Limit or misaligned never gets here; any other offset is a constant displacement, which the register checker sees exactly (docs/perf.md item 3c). Otherwise index by %rax.

1894local_addressOff, Limit, Mask, A, P ⟶
integerOff
Off ≥ 0
Off ≤ Limit
Off /\ Mask =:= 0
!
A1 is A + Off
P1 is P + Off
inslea, q, memA1, rbp, regrdx, inslea, q, memP1, rbp, regrax
1899local_address_, _, _, A, P ⟶
inslea, q, memA, rbp, rax, regrdx, inslea, q, memP, rbp, rax, regrax
kind_inst+Kind, +K, +Op, +Args, +D, +Index, +Block, +Ctx//

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.

1907kind_instKind, K, Op, Args, D, _, _, Ctx ⟶
x87_opKind, K, Op
!
x87_instOp, K, Args, D, Ctx
1909kind_instfloat_convert, K, Op, Args, D, _, _, Ctx ⟶
integer_instOp, K, Args, D, Ctx
1910kind_instfloat_to_int, K, Op, Args, D, _, _, Ctx ⟶
integer_instOp, K, Args, D, Ctx
1911kind_instint_to_float, K, Op, Args, D, _, _, Ctx ⟶
integer_instOp, K, Args, D, Ctx
1912kind_instcast, K, Op, Args, D, _, _, Ctx ⟶
integer_instOp, K, Args, D, Ctx
1913kind_instadditive, K, Op, Args, D, _, _, Ctx ⟶
additive_instK, Op, Args, D, Ctx
1915kind_instalu, K, Op, Args, D, _, _, Ctx ⟶
integer_instOp, K, Args, D, Ctx
1917kind_instshift, K, Op, Args, D, _, _, Ctx ⟶
integer_instOp, K, Args, D, Ctx
1919kind_instneg, K, Op, Args, D, _, _, Ctx ⟶
integer_instOp, K, Args, D, Ctx
1921kind_instcopy, K, Op, Args, D, _, _, Ctx ⟶
copy_instK, Op, Args, D, Ctx

A span test (docs/m7.md, round 5): 1 when P0's lower is not null and lower =< P0+Lo < P0+Hi =< upper, unsigned, addresses modulo 2^64; else 0. It reads only what a full check reads and never traps.

1926kind_instspan_test, w, span_testLo, Hi, P0, D, Index, Block, Ctx ⟶
local_labelCtx, Block, Index, span0, Zero
local_labelCtx, Block, Index, span1, Done
instest, q, regrcx, regrcx, insjz, none, symZero
span_endLo, rdx
inscmp, q, regrcx, regrdx, insjb, none, symZero
span_endHi, rax
inscmp, q, regrdx, regrax, insjbe, none, symZero, inscmp, q, mem-16, rcx, regrax, insja, none, symZero, insmov, l, imm1, regeax, insjmp, none, symDone, labelZero, insxor, l, regeax, regeax, labelDone
reg_slot_storerax, tmpD, Ctx

A limit (docs/bounds.md): P0's bounds for Size-byte accesses in the encoding the shape's limit_check//3 reads. It never traps.

1948kind_instlimit, l, limitSize, P0, D, Index, Block, Ctx ⟶
local_labelCtx, Block, Index, limit, Done
lower_reg_loadP0, rcx, Ctx
limit_valueShape, Size, Done
reg_slot_storerdx, tmpD, Ctx
1954kind_instptoi, l, _, P, D, _, _, Ctx ⟶
ctx_fnCtx, Fn
loadP, l, a, Fn
reg_slot_storerax, tmpD, Ctx
1958kind_instcmp, K, Op, Args, D, _, _, Ctx ⟶
integer_instOp, K, Args, D, Ctx
1960kind_instext, K, Op, Args, D, _, _, Ctx ⟶
integer_instOp, K, Args, D, Ctx
1962kind_installoc, p, allocAlign, N, D, _, _, Ctx ⟶
ctx_fnCtx, Fn
loadN, l, si, Fn
slot_reg_loadthread, rdi, Ctx
Align ≥ 16
→
insmov, l, immAlign, regedx, inscall, none, pltfilc_allocate_with_alignment
else
inscall, none, pltfilc_allocate
inslea, q, mem16, rax, regrax, insmov, q, regrax, regrcx
1975setjmpD, KindNo, Buf, Mask, Keys, Index, Block, Ctx ⟶
ctx_fnCtx, Fn
ctx_slotCtx, lowertmphiddensetjmp_bufD, Off
NegOff is −Off
local_labelCtx, Block, Index, setjmp_done, Done
slot_reg_loadlowersetjmp_snapshotD, rdx, Ctx
setjmp_payload_copysave, Keys, 0, Ctx
slot_reg_loadthread, rdi, Ctx
inslea, q, memNegOff, rbp, regrsi, insmov, l, immKindNo, regedx
loadMask, w, c, Fn
inscall, none, pltfilc_jmp_buf_recreate, insmov, q, regrax, regrcx
flight_storehiddensetjmp_bufD, Ctx
checked_pointer_storetmphiddensetjmp_bufD, Buf, Index, Block, Ctx
slot_reg_loadlowertmphiddensetjmp_bufD, rdi, Ctx
inscall, none, plt'_setjmp', instest, l, regeax, regeax, insjz, none, symDone
slot_reg_loadlowersetjmp_snapshotD, rdx, Ctx
setjmp_payload_copyrestore, Keys, 0, Ctx
labelDone
reg_slot_storerax, tmpD, Ctx

RDX is the snapshot payload; RCX is scratch. EAX's full native int survives restoration, which contains no calls, polls or checked loads.

2000setjmp_payload_copy_, [], _, _ ⟶
[]
2001setjmp_payload_copyDirection, Key|Keys, Off, Ctx ⟶
payload_word_copyDirection, Key, Off, Ctx
Next is Off + 8
setjmp_payload_copyDirection, Keys, Next, Ctx
2005payload_word_copysave, Key, Off, Ctx ⟶
slot_reg_loadKey, rcx, Ctx
insmov, q, regrcx, memOff, rdx
2008payload_word_copyrestore, Key, Off, Ctx ⟶
insmov, q, memOff, rdx, regrcx
reg_slot_storercx, Key, Ctx
integer_inst+Op, +K, +Args, +D, +Ctx//

the code of the integer instruction tmp(D) =K Op Args.

2014integer_instOp, K, Args, D, Ctx ⟶
ctx_fnCtx, Fn
ctx_selectionCtx, Selection
compute_opSelection, Op, K, Args, Fn
reg_slot_storerax, tmpD, Ctx

The helper returns payload and auxiliary *addresses*, not a flight pointer. Consume both without a safepoint, then root the loaded lower.

2021va_arg_loadw, D, _, _, Ctx ⟶
insmov, l, mem0, rax, regeax
reg_slot_storerax, tmpD, Ctx
2024va_arg_loadl, D, _, _, Ctx ⟶
insmov, q, mem0, rax, regrax
reg_slot_storerax, tmpD, Ctx
2027va_arg_loadx, D, _, _, Ctx ⟶
insfld, t, mem0, rax
x87_popD, Ctx
2029va_arg_loads, D, I, B, Ctx ⟶
va_arg_loadw, D, I, B, Ctx
2030va_arg_loadd, D, I, B, Ctx ⟶
va_arg_loadl, D, I, B, Ctx
2031va_arg_loadp, D, Index, Block, Ctx ⟶
local_labelCtx, Block, Index, va_none, None
local_labelCtx, Block, Index, va_have, Have
insxor, l, regecx, regecx, instest, q, regrdx, regrdx, insjz, none, symNone, insmov, q, mem0, rdx, regrcx, instest, b, imm1, regcl, insjz, none, symHave, insand, q, imm-2, regrcx, insmov, q, mem8, rcx, regrcx, labelHave, labelNone, insmov, q, mem0, rax, regrax

x87 long double (docs/float.md). An x temporary has a 16-byte wide slot, of which fldt and fstpt use the first 10 bytes. Each instruction pushes its operands on the x87 stack, computes and pops the stack empty again, so no value stays in an x87 register across instructions, calls or safepoints. Scalars and constants go through the red zone below %rsp: no call or signal frame can intervene within one instruction's sequence.

2053x87_op_, x, _
2054x87_opcmp, _, cmp_, x
2055x87_opfloat_convert, _, truncx
2056x87_opfloat_to_int, _, Op ←
memberchkOp, xtosi, xtoui
x87_inst+Op, +K, +Args, +D, +Ctx//

tmp(D) =K Op Args, with x87. Operands go in reverse, so %st is the first and %st(1) the second.

2060x87_instOp, x, A, B, D, Ctx ⟶
memberchkOp−I, add−fadd, sub−fsub, mul−fmul, div−fdiv
!
x87_pushB, Ctx
x87_pushA, Ctx
insI, none, reg'st(1)', regst
x87_popD, Ctx
insfstp, none, reg'st(0)'
2065x87_instneg, x, A, D, Ctx ⟶
!
x87_pushA, Ctx
insfchs, none, []
x87_popD, Ctx
2067x87_instsqrt, x, A, D, Ctx ⟶
!
x87_pushA, Ctx
insfsqrt, none, []
x87_popD, Ctx
2069x87_instcopy, x, A, D, Ctx ⟶
!
x87_pushA, Ctx
x87_popD, Ctx
2071x87_instcmpCond, x, w, A, B, D, Ctx ⟶
!
x87_pushB, Ctx
x87_pushA, Ctx
insfucomip, none, reg'st(1)', regst, insfstp, none, reg'st(0)'
insmovzb, l, regal, regeax
reg_slot_storerax, tmpD, Ctx
2078x87_instextF, x, A, D, Ctx ⟶
!
x87_popD, Ctx
2080x87_insttruncx, K, A, D, Ctx ⟶
!
x87_suffixK, S, Mov
Mov ≡ l
→
R = eax
else
R = rax
x87_pushA, Ctx
insfstp, S, mem-8, rsp, insmov, Mov, mem-8, rsp, regR
reg_slot_storerax, tmpD, Ctx
2085x87_instxtosi, K, A, D, Ctx ⟶
!
x87_pushA, Ctx
x87_fisttpK, D, Ctx
2087x87_instxtoui, w, A, D, Ctx ⟶
!
x87_pushA, Ctx
x87_fisttpw, D, Ctx

Above 2^63, subtract 2^63, convert, and restore bit 63, as SSE2's unsigned qword conversion does (amd64:float_integer//4).

2091x87_instxtoui, l, A, D, Ctx ⟶
!
x87_pushA, Ctx
insmov, l, imm1593835520, mem-12, rsp, insfld, s, mem-12, rsp, insfucomip, none, reg'st(1)', regst, insja, none, sym'1f', insfsub, s, mem-12, rsp, insfisttp, ll, mem-8, rsp, insmov, q, mem-8, rsp, regrax, insbtc, q, imm63, regrax, insjmp, none, sym'2f', label'1', insfisttp, ll, mem-8, rsp, insmov, q, mem-8, rsp, regrax, label'2'
reg_slot_storerax, tmpD, Ctx
2107x87_instOp, x, A, D, Ctx ⟶
memberchkOp, swtof, uwtof, sltof, ultof
ctx_fnCtx, Fn
insmov, q, regrax, mem-8, rsp, insfild, ll, mem-8, rsp
x87_popD, Ctx

The integer operand in %rax, extended to 64 bits as Op reads it.

2115x87_int_sourceswtof, A, Fn ⟶
loadA, w, a, Fn
insmovsl, q, regeax, regrax
2116x87_int_sourceuwtof, A, Fn ⟶
loadA, w, a, Fn
insmov, l, regeax, regeax
2117x87_int_sourcesltof, A, Fn ⟶
loadA, l, a, Fn
2118x87_int_sourceultof, A, Fn ⟶
loadA, l, a, Fn

fildll read an unsigned l with bit 63 set as negative: add 2^64, exactly, since the 64-bit significand holds every such sum.

2122x87_unsigned_fixultof ⟶
!
instest, q, regrax, regrax, insjns, none, sym'1f', insmov, l, imm1602224128, mem-12, rsp, insfadd, s, mem-12, rsp, label'1'
2126x87_unsigned_fix_ ⟶
[]

Truncate %st toward zero into tmp(D) of class K and pop it.

2129x87_fisttpK, D, Ctx ⟶
K ≡ w
→
Mov = l
R = eax
else
Mov = q
R = rax
insfisttp, ll, mem-8, rsp, insmov, Mov, mem-8, rsp, regR
reg_slot_storerax, tmpD, Ctx
2134x87_suffixs, s, l
2135x87_suffixd, l, q
x87_push+V, +Ctx//

push the x value V. A constant's 10 bytes go through the red zone, by %rdx, which no raw access holds.

2139x87_pushtmpN, Ctx ⟶
ctx_slotCtx, tmpN, Off
Disp is −Off
insfld, t, memDisp, rbp
2142x87_pushconstN, Ctx ⟶
ctx_fnCtx, Fn
Lo is N /\ 0xffffffffffffffff
Hi is N >> 64 /\ 0xffff
loadconstLo, l, d, Fn
insmov, q, regrdx, mem-16, rsp, insmov, w, immHi, mem-8, rsp, insfld, t, mem-16, rsp

Push the s or d value V, widened exactly.

2150x87_push_floatF, V, Ctx ⟶
ctx_fnCtx, Fn
x87_suffixF, S, _
loadV, F, a, Fn
insmov, q, regrax, mem-8, rsp, insfld, S, mem-8, rsp

Pop %st into tmp(D)'s wide slot.

2156x87_popD, Ctx ⟶
ctx_slotCtx, tmpD, Off
Disp is −Off
insfstp, t, memDisp, rbp
additive_inst+K, +Op, +Args, +D, +Ctx//

scalar add or sub.

2161additive_instw, Op, Args, D, Ctx ⟶
integer_instOp, w, Args, D, Ctx
2162additive_instl, Op, Args, D, Ctx ⟶
integer_instOp, l, Args, D, Ctx
2163additive_instK, Op, Args, D, Ctx ⟶
integer_instOp, K, Args, D, Ctx
copy_inst+K, +Op, +Args, +D, +Ctx//

a copy at K.

2167copy_instw, Op, Args, D, Ctx ⟶
integer_instOp, w, Args, D, Ctx
2168copy_instl, Op, Args, D, Ctx ⟶
integer_instOp, l, Args, D, Ctx
2169copy_instK, Op, Args, D, Ctx ⟶
integer_instOp, K, Args, D, Ctx

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.

2175flight_address_loadaddressP, R, Ctx ⟶
!
ctx_fnCtx, Fn
loadP, l, R, Fn
2177flight_address_loadV, R, Ctx ⟶
ctx_fnCtx, Fn
loadV, l, R, Fn
2180flight_lower_loadlowerP, Ctx ⟶
lower_reg_loadP, rcx, Ctx
2181flight_lower_loadnone, _ ⟶
insxor, l, regecx, regecx
2183flight_calculationcopy, [], _ ⟶
[]
2184flight_calculationOp, X, Ctx ⟶
memberchkOp, add, sub
insOp, q, regrdx, regrax
raw_access+Access, +Index, +Block, +Ctx//

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.

2192raw_accesstouch_, _, _, _ ⟶
[]
2193raw_accesslettmpD, K, raw_loadT, 0, _, Index, Block, Ctx ⟶
raw_loadT, K, D, Index, Block, Ctx
2195raw_accessraw_storeW, 0, V, P, Index, Block, Ctx ⟶
raw_storeW, V, P, Index, Block, Ctx
2197raw_accesslettmpD, p, raw_load_pointer0, Effects, _, I, B, Ctx ⟶
pointer_load_effectsEffects, I, B, Ctx
2200raw_accessraw_store_pointer0, V, P, Effects, I, B, Ctx ⟶
pointer_store_effectsEffects, V, P, I, B, Ctx
2202raw_accesslettmpD, K, raw_casT, 0, _, E, N, _, _, Ctx ⟶
raw_casT, K, D, E, N, Ctx
raw_cas+T, +K, +D, +E, +N, +Ctx//

the cas(T) of Expected E and New N at the checked address in %rax (lower in %rcx), its old value into tmp(D) at class K (ir.pl). An integer is lock cmpxchg on the address in %rdx, Expected in %rax and New in %rcx; %rax is then old, extended as load(T) would. A pointer is Fil-C's filc_strong_cas_ptr_with_manual_tracking(thread, filc_ptr dest, offset, filc_ptr expected, filc_ptr new), as clang calls it: dest in %rsi/%rdx, offset 0 in %rcx, Expected's pair in %r8/%r9 and New's on the stack, the old pair back in %rax/%rdx. The helper checks again, ensures aux (it may exit), barriers New and boxes the slot; P and New stay rooted in their slots, and flight_store roots the result at once.

2216raw_casp, p, D, E, N, Ctx ⟶
!
ctx_fnCtx, Fn
insmov, q, regrax, regrsi, insmov, q, regrcx, regrdx
slot_reg_loadthread, rdi, Ctx
loadE, l, r8, Fn
lower_reg_loadE, r9, Ctx
loadN, l, a, Fn
lower_reg_loadN, rcx, Ctx
inssub, q, imm16, regrsp, insmov, q, regrax, mem0, rsp, insmov, q, regrcx, mem8, rsp, insxor, l, regecx, regecx, inscall, none, pltfilc_strong_cas_ptr_with_manual_tracking, insadd, q, imm16, regrsp, insmov, q, regrdx, regrcx
2234raw_casT, K, D, E, N, Ctx ⟶
ctx_fnCtx, Fn
cas_operandT, S, New
insmov, q, regrax, regrdx
loadE, K, a, Fn
loadN, K, c, Fn
ins'lock cmpxchg', S, regNew, mem0, rdx
T ≢ l
ext_insnT, K, Ext
→
Ext
else
[]
reg_slot_storerax, tmpD, Ctx
cas_operand?T, ?S, ?New

a cas of type T is cmpxchg with suffix S, New in the part New of %rcx.

cas_operand
2245lqrcx
2246swlecx
2246uwlecx
2247shwcx
2247uhwcx
2248sbbcl
2248ubbcl

%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.

2253local_raw_accesslettmpD, p, raw_load_pointer0, Es, _, _, _, Ctx ⟶
2255local_raw_accessraw_store_pointer0, V, _, Es, _, _, Ctx ⟶
2257local_raw_accessAccess, I, B, Ctx ⟶
raw_accessAccess, I, B, Ctx
2259local_load_effects[] ⟶
[]
2260local_load_effectsE|Es ⟶
2261local_load_effectaux_read ⟶
insmov, q, mem0, rdx, regrcx
2262local_load_effectaddress_read ⟶
insmov, q, mem0, rax, regrax
2264local_store_effects[], _, _ ⟶
[]
2265local_store_effectsE|Es, V, Ctx ⟶
2267local_store_effectlower_write, V, Ctx ⟶
lower_reg_loadV, rcx, Ctx
insmov, q, regrcx, mem0, rdx
2269local_store_effectaddress_write, V, Ctx ⟶
ctx_fnCtx, Fn
loadV, l, si, Fn
insmov, q, regrsi, mem0, rax
2273rights_writeread, 0
2274rights_writewrite, 1

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.

2279pointer_load_effects[], _, _, _ ⟶
[]
2280pointer_load_effectsE|Es, I, B, Ctx ⟶
2283pointer_load_effectaux_read, Index, Block, Ctx ⟶
local_labelCtx, Block, Index, nolower, NoLower
insjz, none, symNoLower, insmov, q, regrax, regrsi, inssub, q, regrcx, regrsi, insmov, q, mem0, rdx, rsi, regrsi
2290pointer_load_effectlower_resolve, Index, Block, Ctx ⟶
local_labelCtx, Block, Index, nolower, NoLower
local_labelCtx, Block, Index, have, Have
instest, b, imm1, regsil, insjz, none, symHave, insand, q, imm-2, regrsi, insmov, q, mem8, rsi, regrsi, insjmp, none, symHave, labelNoLower, insxor, l, regesi, regesi, labelHave

Fil-C's filc_load_ptr_atomic_with_manual_tracking_outline(filc_ptr): the checked pair in %rdi/%rsi, the result pair in %rax/%rdx. The pair leaves %rax/%rcx before origin_store, which uses %rax. The helper takes no thread and cannot exit; flight_store roots the result at once, as clang's frame slot does (no tracking call).

2306pointer_load_effectatomic_read, _, _, Ctx ⟶
insmov, q, regrax, regrdi, insmov, q, regrcx, regrsi
inscall, none, pltfilc_load_ptr_atomic_with_manual_tracking_outline, insmov, q, regrdx, regrcx
2312pointer_load_effectaddress_read, _, _, _ ⟶
insmov, q, mem0, rax, regrax, insmov, q, regrsi, regrcx
raw_load+T, +K, +D, +Index, +Block, +Ctx//

a scalar load of type T at K into tmp(D). Pointer loads require their explicit effect list.

2318raw_loadl, K, D, _, _, Ctx ⟶
integer_loadl, K, D, Ctx
2319raw_loadx, x, D, _, _, Ctx ⟶
insfld, t, mem0, rax
x87_popD, Ctx
2320raw_loads, K, D, _, _, Ctx ⟶
integer_loads, K, D, Ctx
2321raw_loadd, K, D, _, _, Ctx ⟶
integer_loadd, K, D, Ctx
2322raw_loadsw, K, D, _, _, Ctx ⟶
integer_loadsw, K, D, Ctx
2323raw_loaduw, K, D, _, _, Ctx ⟶
integer_loaduw, K, D, Ctx
2324raw_loadsh, K, D, _, _, Ctx ⟶
integer_loadsh, K, D, Ctx
2325raw_loaduh, K, D, _, _, Ctx ⟶
integer_loaduh, K, D, Ctx
2326raw_loadsb, K, D, _, _, Ctx ⟶
integer_loadsb, K, D, Ctx
2327raw_loadub, K, D, _, _, Ctx ⟶
integer_loadub, K, D, Ctx
2329integer_loadT, K, D, Ctx ⟶
load_insnT, K, LoadInsn
LoadInsn
reg_slot_storerax, tmpD, Ctx
checked_pointer_store+V, +A, +Index, +Block, +Ctx//

a store of the pointer V at A with its check, for an instruction whose store is internal (setjmp's jmp_buf).

2337checked_pointer_storeV, A, Index, Block, Ctx ⟶
access_checkk8, 1, 8, Ctx
pointer_store_effectsEffects, V, A, Index, Block, Ctx

A pointer store may exit during ensure-aux. Reload both rooted inputs after the helpers, before writing the lower and then the address.

2345pointer_store_effects[], _, _, _, _, _ ⟶
[]
2346pointer_store_effectsE|Es, V, A, I, B, Ctx ⟶
pointer_store_effectE, V, A, I, B, Ctx
pointer_store_effectsEs, V, A, I, B, Ctx
2350pointer_store_effectaux_ensure, _, _, Index, Block, Ctx ⟶
local_labelCtx, Block, Index, haveaux, HaveAux
insjnz, none, symHaveAux
slot_reg_loadthread, rdi, Ctx
inslea, q, mem-16, rcx, regrsi
slow_callfilc_object_ensure_aux_ptr_outline
insmov, q, regrax, regrdx, labelHaveAux
reg_slot_storerdx, aux, Ctx
2360pointer_store_effectlower_barrier, V, _, Index, Block, Ctx ⟶
local_labelCtx, Block, Index, nobarrier, NoBarrier
lower_reg_loadV, rsi, Ctx
instest, q, regrsi, regrsi, insjz, none, symNoBarrier, insmov, q, gotpcrelfilc_current_marking_state, regrax, inscmp, l, imm0, mem0, rax, insje, none, symNoBarrier
slot_reg_loadthread, rdi, Ctx
slow_callfilc_store_barrier_for_lower_slow
labelNoBarrier
2371pointer_store_effectrooted_reload, V, A, _, _, Ctx ⟶
ctx_fnCtx, Fn
slot_reg_loadaux, rdx, Ctx
insmov, q, regrax, regr8, inssub, q, regrcx, regr8
lower_reg_loadV, rsi, Ctx
loadV, l, r9, Fn

Fil-C's filc_store_ptr_atomic_outline(thread, filc_ptr dest, filc_ptr value): the checked destination in %rsi/%rdx, the value from its rooted slots in %rcx/%r8. The helper checks again, ensures aux (it may exit), barriers the value, boxes the slot and stores the pair atomically, then the address; V stays rooted throughout.

2384pointer_store_effectatomic_write, V, _, _, _, Ctx ⟶
ctx_fnCtx, Fn
insmov, q, regrax, regrsi, insmov, q, regrcx, regrdx
slot_reg_loadthread, rdi, Ctx
loadV, l, c, Fn
lower_reg_loadV, r8, Ctx
inscall, none, pltfilc_store_ptr_atomic_outline
2393pointer_store_effectlower_write, _, _, _, _, _ ⟶
insmov, q, regrsi, mem0, rdx, r8
2395pointer_store_effectaddress_write, _, _, _, _, _ ⟶
insmov, q, regr9, mem0, rax

Scalar stores have no aux or barrier effects.

2399raw_storeb, V, _, _, _, Ctx ⟶
integer_storeb, V, Ctx
2400raw_storeh, V, _, _, _, Ctx ⟶
integer_storeh, V, Ctx
2401raw_storew, V, _, _, _, Ctx ⟶
integer_storew, V, Ctx
2402raw_storel, V, _, _, _, Ctx ⟶
integer_storel, V, Ctx
2403raw_stores, V, _, _, _, Ctx ⟶
integer_stores, V, Ctx
2404raw_stored, V, _, _, _, Ctx ⟶
integer_stored, V, Ctx
2405raw_storex, V, _, _, _, Ctx ⟶
x87_pushV, Ctx
insfstp, t, mem0, rax

The code of a store of the integer V of width W at %rax.

2408integer_storeW, V, Ctx ⟶
ctx_fnCtx, Fn
store_insnW, d, StoreInsn
loadV, ValueK, d, Fn
StoreInsn
width_value_class?W, ?K

an integer store of width W reads its value at class K.

width_value_class
2417bw
2418hw
2419ww
2420ll
2421ss
2422dd

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).

2428indirect_callSig, Form, Words, result_, Channels, ArgsSize, Cells, GenericResult, Index, Block, Ctx ⟶
ctx_fnCtx, Fn
special_labelCtx, exc, Exc
local_labelCtx, Block, Index, generic, Generic
local_labelCtx, Block, Index, done, Done
local_labelCtx, Block, Index, retok, RetOk
fieldfunction_object, signature, rcx, FoSig
fieldfunction_object, generic_entry, rsi, FoGeneric
insmovabs, q, immSig, regrsi, inscmp, q, regrsi, FoSig, insjne, none, symGeneric
fast_callForm, Words, indirect, Ctx
fast_resultForm, Channels, Ctx
insjmp, none, symDone, labelGeneric
local_labelCtx, Block, Index, outline, Outline
outline_ensureArgsSize, Outline, Ctx
generic_call_argsCells, Fn, Ctx
slot_reg_loadthread, rdi, Ctx
slot_reg_loadaux, rsi, Ctx
insmov, l, immArgsSize, regedx, inscall, q, indFoGeneric, instest, b, imm1, regal, insjnz, none, symExc
generic_resultsGenericResult, RetOk, Ctx
labelDone

The generic result cells, after the product's required-size check.

2457generic_resultsreceive_generic0, [], _, _ ⟶
[]
2458generic_resultsreceive_genericNeed, Cell|Cells, RetOk, Ctx ⟶
slot_reg_loadthread, rsi, Ctx
rets_checkNeed, RetOk
generic_slot_copiesCell|Cells, Ctx

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).

2466fast_callForm, Ts, Callee, Ctx ⟶
ctx_fnCtx, Fn
typed_locationsTs, Form, Ls, NS, _
typed_stack_argsTs, Ls, Fn, Ctx
typed_register_argsTs, Ls, Fn, Ctx
entry_callForm, Callee, Pop, Ctx

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.

2477entry_callreg, directTarget, _, Ctx ⟶
slot_reg_loadthread, rdi, Ctx
insxor, l, regesi, regesi, inscall, none, Target
2481entry_callreg, indirect, _, Ctx ⟶
slot_reg_loadthread, rdi, Ctx
slot_reg_loadaux, rsi, Ctx
fieldfunction_object, fast_entry, rsi, Fast
inscall, q, indFast
2486entry_callmem_, directTarget, Pop, Ctx ⟶
slot_reg_loadthread, rsi, Ctx
inslea, q, memPop, rsp, regrdi, insxor, l, regedx, regedx, inscall, none, Target
2491entry_callmem_, indirect, Pop, Ctx ⟶
slot_reg_loadthread, rsi, Ctx
slot_reg_loadaux, rdx, Ctx
fieldfunction_object, fast_entry, rdx, Fast
inslea, q, memPop, rsp, regrdi, inscall, q, indFast

has_exception, then the result slots from %rdx:%rcx or the buffer.

2499fast_resultreg, floatK, T, Ctx ⟶
special_labelCtx, exc, Exc
instest, b, imm1, regal, insjnz, none, symExc
from_xmmK, xmm0, a
reg_slot_storerax, T, Ctx
2503fast_resultreg, typedKTs, Ctx ⟶
special_labelCtx, exc, Exc
instest, b, imm1, regal, insjnz, none, symExc
2509fast_resultreg, slotsSlots, Ctx ⟶
special_labelCtx, exc, Exc
instest, b, imm1, regal, insjnz, none, symExc
reg_slot_storesSlots, rdx, rcx, Ctx
2514fast_resultmem_, slotsSlots, Ctx ⟶
special_labelCtx, exc, Exc
inscmp, b, imm0, mem0, rsp, insjne, none, symExc
buffer_slot_copiesSlots, 8, Ctx
result_registers+KTs, -Rs

the register of each K-Slot word of a returned tuple, in order: integer words in %rdx then %rcx, float words in %xmm0 then %xmm1, as LLVM returns {i1, ...}.

2523result_registersKTs, Rs ←
result_registersKTs, rdx, rcx, xmm0, xmm1, Rs
2524result_registers[], _, _, []
2525result_registersK−_|KTs, Gs, X|Xs, xmmK, X|Rs ←
2527result_registers_|KTs, G|Gs, Xs, gprG|Rs ←
result_registersKTs, Gs, Xs, Rs

Integer registers first: a float word goes into its slot through %rax.

2531typed_result_storesKTs, Rs, Ctx ⟶
typed_result_gprsKTs, Rs, Ctx
typed_result_xmmsKTs, Rs, Ctx
2533typed_result_gprs[], [], _ ⟶
[]
2534typed_result_gprs_−T|KTs, R|Rs, Ctx ⟶
R = gprG
→
else
[]
typed_result_gprsKTs, Rs, Ctx
2537typed_result_xmms[], [], _ ⟶
[]
2538typed_result_xmms_−T|KTs, R|Rs, Ctx ⟶
R = xmmK, X
→
from_xmmK, X, a
reg_slot_storerax, T, Ctx
else
[]
typed_result_xmmsKTs, Rs, Ctx

The store of each register into the matching slot.

2545reg_slot_stores[], _, _ ⟶
[]
2546reg_slot_storesSlot|Slots, Reg|Regs, Ctx ⟶
reg_slot_storeReg, Slot, Ctx
reg_slot_storesSlots, Regs, Ctx

The copy of each buffer word into its slot; Slots start at Off(%rsp).

2551buffer_slot_copies[], _, _ ⟶
[]
2552buffer_slot_copiesSlot|Slots, Off, Ctx ⟶
insmov, q, memOff, rsp, regrax
reg_slot_storerax, Slot, Ctx
Off1 is Off + 8
buffer_slot_copiesSlots, Off1, Ctx

A generic return's words, from the thread at %rsi, into the slots.

2559generic_slot_copies[], _ ⟶
[]
2560generic_slot_copiescellI, _, T, Lower|Cells, Ctx ⟶
generic_offsetsI, Off, LowerOff
insmov, q, memOff, rsi, regrax
reg_slot_storerax, T, Ctx
generic_lower_slot_copyLower, LowerOff, Ctx

For a p word at thread offset Off, its lower into lower(T).

2568generic_lower_slot_copylowerT, LowerOff, Ctx ⟶
insmov, q, memLowerOff, rsi, regrax
reg_slot_storerax, lowerT, Ctx
2571generic_lower_slot_copynone, _, _ ⟶
[]

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.

2575rets_checkNeed, RetOk ⟶
inscmp, q, immNeed, regrdx, insjae, none, symRetOk, insmov, q, regrdx, regrdi, insmov, l, immNeed, regesi, insxor, l, regedx, regedx, inscall, none, pltfilc_cc_rets_check_failure, insud2, none, [], labelRetOk

The store of each argument into the thread's generic argument area. Invariant: Args are the arguments from argument I on.

2587generic_call_args[], _, _ ⟶
[]
2588generic_call_argscellI, K, V, LowerSource|Cells, Fn, Ctx ⟶
loadV, K, a, Fn
flight_lower_loadLowerSource, Ctx
slot_reg_loadthread, rdx, Ctx
generic_call_argsCells, Fn, Ctx
cell_stores+I, +Thread//

argument cell I from %rax, its lower from %rcx, with the thread in register Thread: in the inline buffers, or for a cell past their 256 bytes in the outline ones (clobbering %rsi), as storeCC writes them.

2599cell_storesI, T ⟶
!
generic_offsetsI, Addr, Lower
insmov, q, regrax, memAddr, T, insmov, q, regrcx, memLower, T
2604cell_storesI, T ⟶
Off is 8 × I−N
fieldthread, cc_outline_buffer, T, Buf
fieldthread, cc_outline_aux_buffer, T, AuxBuf
insmov, q, Buf, regrsi, insmov, q, regrax, memOff, rsi, insmov, q, AuxBuf, regrsi, insmov, q, regrcx, memOff, rsi
2611inline_cells32
outline_ensure+Size, +Ctx//

for an argument packet of Size bytes past the 256 inline ones, make the thread's outline buffers hold the rest (filc_thread_ensure_cc_outline_buffer), before any cell is set.

2616outline_ensureSize, Ok, Ctx ⟶
Size > 256
→
slot_reg_loadthread, rdi, Ctx
else
[]
2621outline_ensure_rdiSize, Ok ⟶
Size > 256
→
Need is Size−256
fieldthread, cc_outline_size, rdi, Have
inscmp, q, immNeed, Have, insjae, none, symOk, insmov, q, immNeed, regrsi, inscall, none, pltfilc_thread_ensure_cc_outline_buffer_slow, labelOk
else
[]

With a flight pointer in %rax:%rcx, jump to Fail unless it points at a function object. Clobbers %rdx, %rsi and %rdi.

2634function_checkFail ⟶
aux_bitsspecial_type, TypeMask
aux_bitsfunction, Function
instest, q, regrcx, regrcx, insjz, none, symFail, insmov, q, mem-8, rcx, regrdx, insmovabs, q, immTypeMask, regrsi, insand, q, regrdx, regrsi, insmovabs, q, immFunction, regrdi, inscmp, q, regrdi, regrsi, insjne, none, symFail, insmovabs, q, immMask, regrsi, insand, q, regrsi, regrdx, inscmp, q, regrdx, regrax, insjne, none, symFail

The aux pointer of the object at lower %rcx into %rdx, setting ZF if it has none.

2653aux_of_lower ⟶
insmov, q, mem-8, rcx, regrdx, insmovabs, q, immMask, regrsi, insand, q, regrsi, regrdx

A word into register R, an amd64 register short name (a, d, c, r8...).

2661word_reg_loadaddrV, R, Fn, _ ⟶
loadV, l, R, Fn
2662word_reg_loadvalK, V, R, Fn, _ ⟶
loadV, K, R, Fn
2663word_reg_loadlowerV, R, _, Ctx ⟶
reg_nameR, l, RN
lower_reg_loadV, RN, Ctx

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.

2674typed_locationsTs, Form, Ls, NS, NX ←
findallK, memberwfK, _, Ts, Ks
bank_locationsKs, Rs, Ls, NS, NX
stack_bytes+NS, -Bytes

NS stack words take Bytes, keeping %rsp 16-aligned at the call.

2679stack_bytesNS, Bytes ←
Bytes is NS + 1//2 × 16
2681reserve_stack0 ⟶
[]
2682reserve_stackN ⟶
N > 0
inssub, q, immN, regrsp
2684typed_incoming[], [], _ ⟶
[]
2685typed_incomingwfK, T|Ts, L|Ls, Ctx ⟶
incoming_wordL, K, T, Ctx
typed_incomingTs, Ls, Ctx

An integer register word goes straight to its slot; others via %rax.

2689incoming_wordregR, _, T, Ctx ⟶
!
reg_nameR, l, RN
reg_slot_storeRN, T, Ctx
2691incoming_wordL, K, T, Ctx ⟶
2692incoming_bitsregR, _ ⟶
reg_nameR, l, RN
insmov, q, regRN, regrax
2694incoming_bitsxregX, K ⟶
from_xmmK, X, a
2695incoming_bitsstackI, K ⟶
O is 16 + 8 × I
reg_namea, K, A
insmov, S, memO, rbp, regA
2699typed_stack_args[], [], _, _ ⟶
[]
2700typed_stack_argswf_, W|Ts, L|Ls, Fn, Ctx ⟶
L = stackI
O is 8 × I
→
word_reg_loadW, a, Fn, Ctx
insmov, q, regrax, memO, rsp
else
[]
typed_stack_argsTs, Ls, Fn, Ctx
2704typed_register_args[], [], _, _ ⟶
[]
2705typed_register_argswfK, W|Ts, L|Ls, Fn, Ctx ⟶
typed_register_argL, K, W, Fn, Ctx
typed_register_argsTs, Ls, Fn, Ctx
2707typed_register_argstack_, _, _, _, _ ⟶
[]
2708typed_register_argregR, _, W, Fn, Ctx ⟶
word_reg_loadW, R, Fn, Ctx
2709typed_register_argxregX, K, W, Fn, Ctx ⟶
word_reg_loadW, a, Fn, Ctx
to_xmmK, a, X

Adapter sources are operands (thread/snapshot/saved stack), not IR values. Load all stack words first, then registers; only RAX is scratch.

2714adapter_stack[], [], _ ⟶
[]
2715adapter_stackwfK, W|Ts, L|Ls, G ⟶
L = stackI
O is 8 × I
→
insmov, q, regrax, memO, rsp
else
[]
adapter_stackTs, Ls, G
2719adapter_registers[], [], _ ⟶
[]
2720adapter_registerswfK, W|Ts, L|Ls, G ⟶
2722adapter_registerstack_, _, _, _ ⟶
[]
2723adapter_registerregR, K, memO, B, _ ⟶
K ≢ s
!
reg_nameR, l, RN
insmov, q, memO, B, regRN
2726adapter_registerregR, K, W, G ⟶
reg_nameR, l, RN
insmov, q, regrax, regRN
2729adapter_registerxregX, K, W, G ⟶
to_xmmK, a, X
2730adapter_loadK, W, G ⟶
K ≡ s
→
insmov, l, regeax, regeax
else
[]

Physical operands for the already flattened adapter sources.

2735adapter_wordArea, wfK, Source, wfK, Operand ←
adapter_operandArea, Source, Operand
2737adapter_operandthread, Source, memOff, rbx ←
channel_offsetSource, Off
2738adapter_operandsnapshot, dataI, memOff, r12 ←
Off is 8 × I
2739adapter_operandsnapshot, auxI, snapshot_lowerOff ←
Off is 8 × I
2740adapter_operandsnapshot, snapshot, regr12
2741saved_operandsavedI, memOff, rbp ←
Off is -32−8 × I

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. The saves are not roots: until the callee's entry records them, pointer authority is rooted only by the caller (abi-findings.md).

2752thunkthunkF, Ref, Sig, Ts, Cells, ArgsSize, Result, Need ⟶
abi_symbolentrySig, F, FI
abi_symbolgetter, F, Getter
atom_concat'.L', FI, G
atom_concatG, '.generic', Generic
atom_concatG, '.out', Out
atom_concatG, '.fail', Fail
atom_concatG, '.retok', RetOk
atom_concatG, '.exc', Exc
Result = adapter_resultReturn, Channel, _, Packet
target_formReturn, Form
packet_offsetsPacket, packetSrcs, _
typed_locationsTs, Form, Ls, NS, _
saved_wordsTs, 0, Saved
lengthTs, NW
Alloc is 8 + 8 × NW + 15//16 × 16
fieldfunction_object, generic_entry, rsi, GenericEntry
Sig = never_
→
else
SigV = Sig
sectiontext, p2align4
Ref ≡ local
→
[]
else
hiddenFI, weakFI
typeFI, function, labelFI, inspush, q, regrbp, insmov, q, regrsp, regrbp, inspush, q, regrbx, inspush, q, regr12, inssub, q, immAlloc, regrsp
save_mixed_wordsTs, Ls, Saved
thunk_getterRef, Getter, G
fieldfunction_object, signature, rcx, Signature
insmov, q, regrcx, mem-24, rbp, insmovabs, q, immSigV, regrsi, inscmp, q, regrsi, Signature, insjne, none, symGeneric
adapter_stackSaved, Ls, G
adapter_registersSaved, Ls, G
insjmp, none, symOut, labelGeneric
insmov, q, regrbx, regrdi, insmov, q, mem-24, rbp, regrsi, insmov, l, immArgsSize, regedx, inscall, q, indGenericEntry, instest, b, imm1, regal
thunk_generic_returnForm, Channel, Need, Srcs, Out, RetOk, Exc
labelFail, insmov, q, regrax, regrdi, insmov, q, regrcx, regrsi, inscall, none, pltfilc_check_function_call_fail, insud2, none, [], labelOut, inslea, q, mem-16, rbp, regrsp, inspop, q, regr12, inspop, q, regrbx, inspop, q, regrbp, insret, none, [], sizeFI

The callee's function object in %rax:%rcx. A weak declaration's getter may be null, and then so is the object: the function check fails, as clang's thunk does.

2794thunk_getterlocal, Getter, G ⟶
thunk_getterstrong, Getter, G
2795thunk_getterstrong, Getter, _ ⟶
insxor, l, regesi, regesi, inscall, none, pltGetter, insmov, q, regrdx, regrcx
2798thunk_getterweak, Getter, G ⟶
atom_concatG, '.null', Null
atom_concatG, '.got', Got
inscmp, q, imm0, gotpcrelGetter, insje, none, symNull
thunk_getterstrong, Getter, G
insjmp, none, symGot, labelNull, insxor, l, regeax, regeax, insxor, l, regecx, regecx, labelGot
2805saved_words[], _, []
2806saved_wordswfK, _|Ts, I, wfK, memO, rbp|Ss ←
O is -32−8 × I
I1 is I + 1
saved_wordsTs, I1, Ss
2808mixed_thunk_threadreg ⟶
insmov, q, regrdi, regrbx
2809mixed_thunk_threadmem_ ⟶
insmov, q, regrdi, regr12, insmov, q, regrsi, regrbx, insmov, q, regrsi, regrdi
2812save_mixed_words[], [], [] ⟶
[]

A thunk copies an integer stack word whole, as a register one: w and l share a signature digit, so one emitted for a 32-bit call also serves another object's 64-bit calls (the linker keeps one weak thunk).

2816save_mixed_wordswfK0, _|Ts, L|Ls, wf_, W|Ss ⟶
K0 ≡ w
→
K = l
else
K = K0
insmov, q, regrax, W
2819mixed_thunk_callreg ⟶
fieldfunction_object, fast_entry, rsi, Fast
insmov, q, regrbx, regrdi, insmov, q, mem-24, rbp, regrsi, inscall, q, indFast
2823mixed_thunk_callmem_ ⟶
fieldfunction_object, fast_entry, rdx, Fast
insmov, q, regr12, regrdi, insmov, q, regrbx, regrsi, insmov, q, mem-24, rbp, regrdx, inscall, q, indFast
2827thunk_outline_ensureArgsSize, G ⟶
ArgsSize > 256
→
atom_concatG, '.outline', Outline
insmov, q, regrbx, regrdi
outline_ensure_rdiArgsSize, Outline
else
[]

Inline cells go straight to the thread at %rbx; later ones through cell_stores//2 into the outline buffers.

2836saved_generic[] ⟶
[]
2837saved_genericcellI, _, Data, savedL|Cells ⟶
2841saved_genericcellI, K, Data, none|Cells ⟶
!
adapter_loadK, W, unused
insmov, q, regrax, memAO, rbx, insmov, q, imm0, memLO, rbx
2847saved_genericcellI, _, Data, savedL|Cells ⟶
saved_operandData, memAD, AB
saved_operandsavedL, memLD, LB
insmov, q, memAD, AB, regrax, insmov, q, memLD, LB, regrcx, insmov, q, regrbx, regrdx
2852saved_genericcellI, K, Data, none|Cells ⟶
adapter_loadK, W, unused
insxor, l, regecx, regecx, insmov, q, regrbx, regrdx

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.

2864access_checkkSize, Write, Align, Ctx ⟶
check_fail_labelCtx, kSize, Write, Align, Fail
ctx_selectionCtx, Selection
bounds_checkShape, Selection, Size, Fail
write_checkWrite, Fail
align_checkAlign, Fail

bounds_check(+Shape, +Selection, +Size, +Fail)//: a jump to Fail unless lower (%rcx) is not 0 and lower =< ptr (%rax) and ptr + Size =< upper, in the shape --bounds selects (docs/bounds.md). tests/bounds_smt.py proves each shape's lines equivalent to that condition.

2876bounds_checkShape, Selection, Size, Fail ⟶
instest, q, regrcx, regrcx, insjz, none, symFail
bounds_shapeShape, Selection, Size, Fail
2881bounds_shapesplit, Selection, Size, Fail ⟶
inscmp, q, regrcx, regrax, insjb, none, symFail, insmov, q, mem-16, rcx, regrdx, inssub, q, immSize, regrdx
borrow_checkSelection, Size, Fail
inscmp, q, regrdx, regrax, insja, none, symFail
2889bounds_shapemerged, _, Size, Fail ⟶
merged_boundsSize, Fail
2890bounds_shapesign, _, Size, Fail ⟶
insjs, none, symFail
2893bounds_shapeor, _, Size, Fail ⟶
insshr, q, imm56, regrdx, insjnz, none, symFail

or_bounds(+Size)//: %rdx = (ptr - lower) | (upper - Size - ptr), after D. J. Bernstein: both terms are small and non-negative exactly when the access is in bounds, also for a freed object (upper = lower) and one shorter than Size. --bounds=sign then branches on the sign, his proposal; it also passes pointers within Size of 2^63 above lower, whose accesses are non-canonical and fault in hardware instead of trapping in Fil-C. --bounds=or tests bits 56 and up instead, which is exact while objects stay below 2^56 bytes (five-level paging's user space): an out-of-bounds term is at least 2^56 or, negative, at most -(2^56 + Size). %rsi and %rdx are scratch.

2908or_boundsSize ⟶
insmov, q, regrax, regrsi, inssub, q, regrcx, regrsi, insmov, q, mem-16, rcx, regrdx, inssub, q, regrax, regrdx, inssub, q, immSize, regrdx, insor, q, regrsi, regrdx

merged_bounds(+Size, +Fail)//: lower =< ptr =< upper - Size as one unsigned comparison (--bounds=merged, docs/bounds.md), after D. J. Bernstein's suggestion: with T = ptr - lower and M = (upper - lower)

  • (Size - 1), the valid T are exactly 0 =< T < M. A pointer below

lower makes T at least 2^64 - 2^48, above any M. M is negative when the object is shorter than Size - 1 bytes (malloc(1) read as 8): the subtraction borrows, and cmovb then makes T = M so that the test fails, where comparing T with upper - Size - lower alone would pass. lower =< upper always, and free sets upper = lower (filc_runtime.c free_impl), so M =< 0 for a freed object. %rsi and %rdx are scratch.

2926merged_boundsSize, Fail ⟶
insmov, q, regrax, regrsi, inssub, q, regrcx, regrsi, insmov, q, mem-16, rcx, regrdx, inssub, q, regrcx, regrdx
Size > 1
→
S1 is Size−1
inssub, q, immS1, regrdx, inscmovb, q, regrdx, regrsi
else
[]
inscmp, q, regrdx, regrsi, insjae, none, symFail
bounds_shape-Shape

the shape --bounds selects (NANOFIL_BOUNDS).

2941bounds_shapeShape ←
getenv'NANOFIL_BOUNDS', Shape
→
true
else
Shape = split

limit_value(+Shape, +Size, +Done)//: %rdx = the limit of the lower in %rcx for Size-byte accesses (a hoisted check, docs/bounds.md), which limit_check//3 reads in a loop, Done a fresh local label. With L the lower and U the upper now:

ShapeL not 0L = 0
splitU - Size + 1, or 0 if that borrows0
mergedU - L - Size + 1, or 0 if that borrows0
or, signU - Size-1

The header is read only when L is not 0. tests/hoist_smt.py proves each pair of limit_value and limit_check equivalent to Fil-C's condition, given that the object is not freed in between or its free flag is set.

2959limit_valuesplit, Size, Done ⟶
S1 is Size−1
insxor, l, regedx, regedx, instest, q, regrcx, regrcx, insjz, none, symDone, insmov, q, mem-16, rcx, regrdx, inssub, q, immS1, regrdx, insjae, none, symDone, insxor, l, regedx, regedx, labelDone
2969limit_valuemerged, Size, Done ⟶
S1 is Size−1
insxor, l, regedx, regedx, instest, q, regrcx, regrcx, insjz, none, symDone, insmov, q, mem-16, rcx, regrdx, inssub, q, regrcx, regrdx, inssub, q, immS1, regrdx, insjae, none, symDone, insxor, l, regedx, regedx, labelDone
2980limit_valueor, Size, Done ⟶
or_limit_valueSize, Done
2981limit_valuesign, Size, Done ⟶
or_limit_valueSize, Done
2983or_limit_valueSize, Done ⟶
insmov, q, imm-1, regrdx, instest, q, regrcx, regrcx, insjz, none, symDone, insmov, q, mem-16, rcx, regrdx, inssub, q, immSize, regrdx, labelDone

limit_check(+Shape, +Write, +Fail)//: a jump to Fail unless the pointer in %rax, with the lower in %rcx, may be accessed by the limit in %rdx (limit_value//3), the object's free flag is clear and, for a write, its read-only flag. The flags byte is read only once the bounds passed, so never at a null lower. %rsi and %rdx are scratch.

2996limit_checksplit, Write, Fail ⟶
inscmp, q, regrcx, regrax, insjb, none, symFail, inscmp, q, regrdx, regrax, insjae, none, symFail
flags_checkWrite, Fail
3002limit_checkmerged, Write, Fail ⟶
insmov, q, regrax, regrsi, inssub, q, regrcx, regrsi, inscmp, q, regrdx, regrsi, insjae, none, symFail
flags_checkWrite, Fail
3008limit_checkor, Write, Fail ⟶
insshr, q, imm56, regrdx, insjnz, none, symFail
flags_checkWrite, Fail
3013limit_checksign, Write, Fail ⟶
insjs, none, symFail
flags_checkWrite, Fail

%rdx = (ptr - lower) | (limit - ptr), djb's two terms.

3019or_limit_terms ⟶
insmov, q, regrax, regrsi, inssub, q, regrcx, regrsi, inssub, q, regrax, regrdx, insor, q, regrsi, regrdx

half_bounds(+Bound, +Selection, +Size, +Write, +Fail)//: a half check's tests (docs/half.md), with ptr in %rax and lower in %rcx, neither null test nor the known bound. upper: split's upper test, then the read-only test for a write as access_check//2 makes it (a freed object has upper = lower, so this fails for it). lower: lower =< ptr compared signed, since an access below a low object may wrap below 0, then the free flag, and for a write the read-only one, as a renew tests them: the upper the earlier check saw holds until a free. %rdx is scratch. tests/half_smt.py proves both equivalent to Fil-C's check.

3034half_boundsupper, Selection, Size, Write, Fail ⟶
insmov, q, mem-16, rcx, regrdx, inssub, q, immSize, regrdx
borrow_checkSelection, Size, Fail
inscmp, q, regrdx, regrax, insja, none, symFail
write_checkWrite, Fail
3041half_boundslower, _, _, Write, Fail ⟶
inscmp, q, regrcx, regrax, insjl, none, symFail
flags_checkWrite, Fail

The flags a renew tests (renew_mask/2): free, and read-only for a write.

3047flags_checkWrite, Fail ⟶
renew_maskWrite, Mask
instest, b, immMask, mem-2, rcx, insjnz, none, symFail

The borrow test of upper - Size. Local selection drops it for Size =< 16: a nonnull lower has its 16-byte header below it and upper >= lower, so upper >= 16 >= Size (docs/perf.md, item 6a). Checked IR may state larger sizes (tests/filc/check-range.ir), which keep it.

3056borrow_checkSelection, Size, Fail ⟶
Selection ≡ local
Size ≤ 16
→
[]
else
insjb, none, symFail
span_end+Off, +Reg//

Reg = %rax + Off, modulo 2^64, leaving %rax; an offset beyond 32 bits goes through %rsi, a scratch register no temporary is homed in.

3064span_endOff, Reg ⟶
Off ≥ -2147483648
Off ≤ 2147483647
→
inslea, q, memOff, rax, regReg
else
U is Off mod 18446744073709551616
insmovabs, q, immU, regrsi, inslea, q, mem0, rax, rsi, regReg
guard_slow_blocks+Blocks, +Ctx//

for each guarded renewal, the full check it runs when its span test failed, out of line, then back to its access. %rax:%rcx still hold P from its flight load.

3075guard_slow_blocksBlocks, Ctx ⟶
findallslowL, I, K,
memberblockL, _, Is, _, Blocks
nth0I, Is, fusedguarded_, renew_if_, Size, Rights, Align, _
rights_writeRights, W
K = kSize, W, Align
,
Slows
guard_slowsSlows, Ctx
3083guard_slows[], _ ⟶
[]
3084guard_slowsslowL, I, K|Slows, Ctx ⟶
local_labelCtx, L, I, full, Full
local_labelCtx, L, I, access, Here
labelFull
insjmp, none, symHere
guard_slowsSlows, Ctx
renew_mask+Write, -Mask

the flags a renew tests, FREE (4) and, for a write, READONLY (2) (filc_runtime.h).

3094renew_mask0, 4
3095renew_mask1, 6

For a write (Write = 1), a check of the object's read-only flags.

3098write_check0, _ ⟶
[]
3099write_check1, Fail ⟶
instest, b, imm6, mem-2, rcx, insjnz, none, symFail

For Align = 2, 4 or 8, a check that the pointer is so aligned.

3104align_check1, _ ⟶
[]
3105align_check2, Fail ⟶
instest, b, imm1, regal, insjnz, none, symFail
3108align_check4, Fail ⟶
instest, b, imm3, regal, insjnz, none, symFail
3111align_check8, Fail ⟶
instest, b, imm7, regal, insjnz, none, symFail

The labels of a check kind's failure block and origin.

3116check_fail_labelCtx, kSize, Write, Align, Label ←
ctx_nameCtx, Name
label_atom'.L', Name, '..fail.', Size, '.', Write, '.', Align, Label
3120check_origin_labelName, kSize, Write, Align, Label ←
formatatomLabel, '.L~w..chk.~w.~w.~w', Name, Size, Write, Align

The distinct check kinds the function's accesses need.

3124access_check_kindsBlocks, Kinds ←
findallKind,
memberblock_, _, Insts, _, Blocks
memberInst, Insts
inst_check_kindInst, Kind
,
Kinds0
sortKinds0, Kinds
inst_check_kind+Inst, -Kind

Inst's access needs a check of Kind; fails if Inst does not access memory.

3135inst_check_kindfusedcheck_, Size, Rights, Align, _, kSize, Write, Align ←
rights_writeRights, Write
3137inst_check_kindfusedshared_, _, check_, Size, Rights, Align, _, kSize, Write, Align ←
rights_writeRights, Write
3139inst_check_kindfusedrenew_, Size, Rights, Align, _, kSize, Write, Align ←
rights_writeRights, Write
3141inst_check_kindfusedhalf_check_, _, Size, Rights, Align, _, kSize, Write, Align ←
rights_writeRights, Write
3143inst_check_kindfusedguarded_, renew_if_, Size, Rights, Align, _, kSize, Write, Align ←
rights_writeRights, Write
3145inst_check_kindfusedlimited_, check_limit_, Size, Rights, Align, _, kSize, Write, Align ←
rights_writeRights, Write
3147inst_check_kindfusedcheck_local_, _, Size, Rights, Align, _, kSize, Write, Align ←
rights_writeRights, Write
3149inst_check_kindabiActions, k8, 1, 8 ←
memberchkreturns_twice_, _, _, _, _, Actions

A failure block for each check kind, calling the runtime's reporter.

3153check_fail_blocks[], _ ⟶
[]
3154check_fail_blocksKind|Kinds, Ctx ⟶
check_fail_labelCtx, Kind, Fail
ctx_nameCtx, Name
check_origin_labelName, Kind, Origin
labelFail, insmov, q, regrax, regrdi, insmov, q, regrcx, regrsi, inslea, q, ripOrigin, regrdx, inscall, none, pltfilc_optimized_access_check_fail, insud2, none, []

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.

3172variadic_objectName, Linkage, Adapter, Impl ⟶
abi_symbolfunction_object, Name, FO
abi_symbolgetter, Name, Getter
abi_symbolimpl0, Name, Generic
abi_symbolentry0, Name, FI
atom_concatGeneric, '.body', BodyObject
linkage_aliasLinkage, FI, Generic, Getter
object_getterGetter, FO
variadic_adapterGeneric, BodyObject, Adapter
sectiondata_rel_ro, p2align4, labelBodyObject, quadImpl
function_object_dataFO, 0, Generic, 0
3184variadic_adapterGeneric, BodyObject, promote_incomingNeed, Words, Result ⟶
Result = adapter_resultReturn, Channel, RetSize, Packet
target_formReturn, Form
packet_offsetsPacket, PlacedPacket
maplistadapter_wordsnapshot, Words, Ts
typed_locationsTs, Form, Ls, NS, _
atom_concatGeneric, '.short', Short
atom_concatGeneric, '.done', Done
sectiontext, typeGeneric, function, labelGeneric, inspush, q, regrbx, inspush, q, regr12, inspush, q, regr13, inscmp, q, immNeed, regrdx, insjb, none, symShort, insmov, q, regrdi, regrbx, insmov, q, regrdx, regrsi, inscall, none, pltfilc_promote_args_to_heap, insmov, q, regrax, regr12, insxor, l, regr13d, regr13d, instest, q, regrdx, regrdx
atom_concatGeneric, '.aux', Aux
insjz, none, symAux, insmov, q, mem-8, rdx, regr13, insmovabs, q, immMask, regrax, insand, q, regrax, regr13, labelAux
adapter_stackTs, Ls, Generic
adapter_registersTs, Ls, Generic
insmov, q, regrbx, regrdi, inslea, q, ripBodyObject, regrsi
generic_entry_callForm, Channel, PlacedPacket, Pop, RetSize, Done
inspop, q, regr13, inspop, q, regr12, inspop, q, regrbx, insret, none, [], labelShort, insmov, q, regrdx, regrdi, insmov, l, immNeed, regesi, insxor, l, regedx, regedx, inscall, none, pltfilc_cc_args_check_failure, insud2, none, []
3224snapshot_loadregR, _ ⟶
insmov, q, regR, regrax
3225snapshot_loadmemO, R, _ ⟶
insmov, q, memO, R, regrax
3226snapshot_loadsnapshot_lowerO, G ⟶
formatatomNoAux, '~w.lower.~w', G, O
insxor, l, regeax, regeax, instest, q, regr13, regr13, insjz, none, symNoAux, insmov, q, memO, r13, regrax, labelNoAux
3233function_objectName, Linkage, Adapter, Sig, Impl ⟶
abi_symbolentrySig, Name, FI
abi_symbolfunction_object, Name, FO
abi_symbolgetter, Name, Getter
formatatomGeneric, '.L~w..generic', Name
linkage_aliasLinkage, FI, Impl, Getter
object_getterGetter, FO
generic_entryGeneric, Adapter
local_entryLinkage, Impl, Fast
sectiondata_rel_ro, p2align4
function_object_dataFO, Fast, Generic, Sig
local_entry+Linkage, +Impl, -Fast//

the symbol a function object names its fast entry by. A global Impl is preemptible, so the dynamic linker would bind the object's word to the first definition of that name it loaded: every HDF5 plugin netcdf opens with RTLD_GLOBAL defines H5PLget_plugin_info, and each one's object called the first one's. clang's names a local alias, .L<Impl>.local, bound at link time (docs/abi-findings.md, *Function objects name a local alias*).

3252local_entrylocal, Impl, Impl ⟶
!
3253local_entry_, Impl, Fast ⟶
formatatomFast, '.L~w.local', Impl
typeFast, function, setFast, Impl
alias_getter+Symbols, +Name, +Linkage, +Target//

an alias's getter returns its target's getter's result, so the alias's function or data object is the target's, as Fil-C's (filc_alias_global): it calls the target's getter, directly when the target is local, else through the PLT, with the stack kept 16-aligned.

3262alias_getterSymbols, Name, Linkage, Target ⟶
abi_symbolgetter, Name, Getter
abi_symbolgetter, Target, TG
func_namedSymbols, Target, func_, local, _, _, _
or
ir:data_itemSymbols, Target, data_, L, _, _
memberchkL, local, rolocal, tlslocal
→
Op = symTG
else
Op = pltTG
sectiontext
linkage_globlLinkage, Getter
typeGetter, function, labelGetter, inspush, q, regrax, inscall, none, Op, inspop, q, regrcx, insret, none, []

The getter of a function object FO: its flight pointer, whose lower is its address, in %rax:%rdx.

3279object_getterGetter, FO ⟶
typeGetter, function, labelGetter, inslea, q, ripFO + 16, regrax, insmov, q, regrax, regrdx, insret, none, []

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.

3288function_object_dataFO, Fast, Generic, Sig ⟶
aux_bitsglobal, readonly, function, Flags
typeFO, object, labelFO, quadFO + 16, quadFO + 16 + Flags, quadFast, quadGeneric, quadSig, sizeFO, 40

An exported function's FI alias of Impl and its global Getter.

3299linkage_aliasexport, FI, Impl, Getter ⟶
globlFI, typeFI, function, setFI, Impl, globlGetter
3301linkage_aliaslocal, _, _, _ ⟶
[]

A weak function has a weak getter and no FI alias, as clang's: callers elsewhere go through their thunks to whichever definition the linker keeps.

3305linkage_aliasweak, _, _, Getter ⟶
weakGetter

The .globl of S if its linkage is export.

3308linkage_globlexport, S ⟶
globlS
3309linkage_globllocal, _ ⟶
[]
3310linkage_globlweak, S ⟶
weakS

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. A payload aligned to more than 16 has Align - 16 zero bytes before DO_d, so that DO_d + 16 is aligned.

3319data_objectName, Linkage0, Align, Items ⟶
linkage_flagsLinkage0, Linkage, Flags
abi_symboldata_object, Name, DO
abi_symboldata_aux, Name, DA
formatatomGP, '.LpizlonatedGP_~w', Name
abi_symbolgetter, Name, Getter
formatatomRelocs, '.Lpizlonated_~w..relocs', Name
formatatomSlow, '.Lpizlonated_~w..slow', Name
formatatomInited, '.Lpizlonated_~w..inited', Name
foldlpayload_item, Items, Payload, 0−[], Size0−Rs0
reverseRs0, Rs
Size is Size0 + 7//8 × 8
Pad is Size−Size0
relocs_aux_wordRs, Flags, DA, AuxWord
Total is 16 + Size
PayloadAlign is maxAlign, 16
Front is PayloadAlign−16
Log is msbPayloadAlign
clang's section: a const object or string literal without
pointers is read-only (.data.rel.ro), where glibc's fortified
printf accepts a %n format; one with pointers stays in .data, as
the getter's relocations write its payload at run time (the
readonly flag in its aux word still refuses stores).
memberchkreadonly, Flags
Rs ≡ []
→
Section = data_rel_ro
else
Section = data
sectionSection, p2alignLog
Front > 0
→
zeroFront
else
[]
typeDO, object, labelDO, quadDO + 16 + Size, quadAuxWord
seqPayload
Pad > 0
→
zeroPad
else
[]
sizeDO, Total
sectionbss, p2align4, labelGP, zero16
aux_arrayRs, DA, Size, Relocs, Name
sectiontext, p2align4
linkage_globlLinkage, Getter
typeGetter, function, labelGetter, insmov, q, ripGP, regrax, insmov, q, ripGP + 8, regrdx, instest, q, regrax, regrax, insjz, none, symSlow, instest, q, regrdx, regrdx, insjz, none, symSlow, insret, none, [], labelSlow, inspush, q, regrbx, insmov, q, regrdi, regrbx, inslea, q, ripGP, regrdx, inslea, q, ripDO, regrcx, inscall, none, pltfilc_global_initialization_start, instest, b, imm1, regal, insjz, none, symInited
relocations_callRs, DO, Relocs
insmov, q, regrbx, regrdi, inscall, none, pltfilc_global_initialization_end, labelInited, inspop, q, regrbx, inslea, q, ripDO + 16, regrax, insmov, q, regrax, regrdx, insret, none, []

Thread-local data, as clang lays it out (docs/abi-findings.md, *Thread-local variables*): a .tbss word TP_d holding this thread's payload address, null until the thread first asks, and a getter that returns it or calls the slow path GS_d. GS_d allocates the thread's object with filc_allocate_thread_local (which roots it in the thread), copies the initializer's bytes from a template TI_d and runs its relocations, as a global's slow path does, then sets TP_d.

3394thread_local_objectName, Linkage, Align, Items ⟶
abi_symbolgetter, Name, Getter
formatatomTP, 'pizlonatedTP_~w', Name
formatatomSlow, '.LpizlonatedGS_~w', Name
formatatomTemplate, '.LpizlonatedTI_~w', Name
formatatomRelocs, '.Lpizlonated_~w..relocs', Name
formatatomEnsure, '.Lpizlonated_~w..ensure', Name
foldlpayload_item, Items, Payload, 0−[], Size−Rs0
reverseRs0, Rs
Rs ≡ []
→
Allocate = filc_allocate_thread_local
else
Allocate = filc_allocate_thread_local_with_ptrs
sectiontbss, p2align3
linkage_globlLinkage, TP
typeTP, object, labelTP, zero8, sizeTP, 8
→
sectionrodata, p2align4, labelTemplate
seqPayload
Copy = inslea, q, mem16, r12, regrdi, inslea, q, ripTemplate, regrsi, insmov, q, immSize, regrcx, ins'rep movsb', none, []
else
Copy = []
Rs ≡ []
→
RelocsCall = []
else
sectiondata_rel_ro, p2align4, labelRelocs
relocationsRs, Name
lengthRs, NRelocs
RelocsCall = insmov, q, regrbx, regrdi, insmov, q, regr12, regrsi, inslea, q, ripRelocs, regrdx, insmov, l, immNRelocs, regecx, inscall, none, pltfilc_execute_constant_relocations
sectiontext, p2align4
linkage_globlLinkage, Getter
typeGetter, function, labelGetter, insmov, q, gottpoffTP, regrax, insmov, q, fsmem0, rax, regrax, instest, q, regrax, regrax, insjz, none, symEnsure, insmov, q, regrax, regrdx, insret, none, [], labelEnsure, inssub, q, imm8, regrsp, inscall, none, symSlow, insadd, q, imm8, regrsp, insmov, q, regrax, regrdx, insret, none, [], p2align4, typeSlow, function, labelSlow, inspush, q, regrbx, inspush, q, regr12, inssub, q, imm8, regrsp, insmov, q, regrdi, regrbx, insmov, q, immSize, regrsi, insmov, q, immAlign, regrdx, inscall, none, pltAllocate, insmov, q, regrax, regr12
seqCopy
seqRelocsCall
inslea, q, mem16, r12, regrax, insmov, q, gottpoffTP, regrcx, insmov, q, regrax, fsmem0, rcx, insadd, q, imm8, regrsp, inspop, q, regr12, inspop, q, regrbx, insret, none, []
template_needed+Items

some byte of data Items is not zero, so the fresh (zeroed) object needs a copy of them.

3464template_neededItems ←
memberItem, Items
Item = item_, constN
N ≠ 0
or
Item = itemb, strCs
memberC, Cs
char_codeC, Code
Code ≠ 0
!
relocs_aux_word+Rs, +Flags, +DA, -AuxWord

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.

3474relocs_aux_word[], Flags, _, Bits ←
aux_bitsglobal|Flags, Bits
3476relocs_aux_word_|_, Flags, DA, DA + Bits ←
aux_bitsglobal, global_aux|Flags, Bits

The aux array DA of Size bytes and the relocation table Relocs, if there are relocations Rs.

3481aux_array[], _, _, _, _ ⟶
[]
3482aux_arrayR|Rs, DA, Size, Relocs, Name ⟶
p2align4, labelDA, zeroSize
sectiondata_rel_ro, p2align4, labelRelocs
relocationsR|Rs, Name

The getter's call that runs the relocations Rs, if there are any, of data object DO from the table Relocs.

3489relocations_call[], _, _ ⟶
[]
3490relocations_callR|Rs, DO, Relocs ⟶
lengthR|Rs, NRelocs
insmov, q, regrbx, regrdi, inslea, q, ripDO, regrsi, inslea, q, ripRelocs, regrdx, insmov, l, immNRelocs, regecx, inscall, none, pltfilc_execute_constant_relocations
linkage_flags+Linkage0, -Linkage, -Flags

a data object of linkage Linkage0 has linkage Linkage and, if read-only, the flag readonly.

linkage_flags
3500roLinkageLinkagereadonly
3501exportexport[]
3502locallocal[]
3503weakweak[]
payload_item+Item, -Line, +Off0−Rs0, −Off−Rs

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.

3509payload_itemzeroN, zeroN, Off0−Rs, Off−Rs ←
Off is Off0 + N
3511payload_itemitemW, Value, Line, S0, S ←
value_payloadValue, W, Line, S0, S
value_payload+Value, +W, -Line, +Off0−Rs0, −Off−Rs

as payload_item/4 for item(W, Value).

3516value_payloadstrCs, b, asciiCs, Off0−Rs, Off−Rs ←
lengthCs, N
Off is Off0 + N
3519value_payloadsymS, Addend, p, quad0, Off0−Rs, Off−rOff0, S, Addend|Rs ←
Off0 mod 8 =:= 0
→
true
else
thrownanofil_error'pointer data item is not 8-aligned'
Off is Off0 + 8
3526value_payloadconstN, x, byteBs, Off0−Rs, Off−Rs ←
!
numlist0, 9, Is
findallB,
memberI, Is
B is N >> 8 × I /\ 255
,
Bs
Off is Off0 + 10
3530value_payloadconstN, W, Directive, Off0−Rs, Off−Rs ←
width_bytesW, Bytes
Bits is 8 × Bytes
U is N mod 1 << Bits
memberchkBytes−Directive, 1−byteU, 2−shortU, 4−longU, 8−quadU
Off is Off0 + Bytes

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}.

3541relocationsRs, Name ⟶
reloc_entriesRs, 0, Name
reloc_nodesRs, 0, Name

The relocation table. Invariant: Rs are the relocations from the Ith.

3546reloc_entries[], _, _ ⟶
[]
3547reloc_entriesrOff, S, Addend|Rs, I, Name ⟶
abi_symbolgetter, S, Getter
Addend =:= 0
→
quadOff, long0, zero4, quadGetter
else
reloc_nodeName, I, Node
quadOff, long1, zero4, quadNode
I1 is I + 1
reloc_entriesRs, I1, Name

The constexpr node of each relocation with an addend. Invariant: Rs are the relocations from the Ith.

3559reloc_nodes[], _, _ ⟶
[]
3560reloc_nodesr_, S, Addend|Rs, I, Name ⟶
Addend =:= 0
→
[]
else
abi_symbolgetter, S, Getter
reloc_nodeName, I, Node
U is Addend mod 18446744073709551616
labelNode, long0, long0, quadGetter, quadU
I1 is I + 1
reloc_nodesRs, I1, Name
reloc_node+Name, +I, -Node

Node labels the constexpr node of relocation I of data object Name.

3573reloc_nodeName, I, Node ←
formatatomNode, '.Lpizlonated_~w..node~w', Name, I
thunk_generic_return+Form, +RetKs, +Need, +GWs, +Out, +RetOk, +GenericExc//

the return, in form Form, of the generic entry's result words GWs, of classes RetKs and Need bytes, then a jump to Out.

3579thunk_generic_returnreg, RetKs, Need, GWs, Out, RetOk, _ ⟶
insjnz, none, symOut
thunk_generic_reg_wordsRetKs, Need, GWs, RetOk
insxor, l, regeax, regeax, insjmp, none, symOut
3584thunk_generic_returnmem_, _, Need, Srcs, Out, RetOk, GenericExc ⟶
insjnz, none, symGenericExc
rets_checkNeed, RetOk
insmov, b, imm0, mem0, r12, insmov, q, regr12, regrax, insjmp, none, symOut, labelGenericExc, insmov, b, imm1, mem0, r12, insmov, q, regr12, regrax, insjmp, none, symOut

The result words, if RetKs has any, in %rdx:%rcx.

3597thunk_generic_reg_wordsvoid, _, _, _ ⟶
[]
3598thunk_generic_reg_wordsfloatK, Need, Off, RetOk ⟶
rets_checkNeed, RetOk
insmov, S, memOff, rbx, regR
to_xmmK, d, xmm0
3602thunk_generic_reg_wordstypedKs, Need, Srcs, RetOk ⟶
foldltyped_ret_kind, Ks, KTs, []
rets_checkNeed, RetOk
3606thunk_generic_reg_wordswords, Need, Srcs, RetOk ⟶
rets_checkNeed, RetOk
thunk_reg_loadsSrcs, rdx, rcx
3610thunk_typed_loads[], [] ⟶
[]
3611thunk_typed_loadsR|Rs, Off|Offs ⟶
R = gprG
→
insmov, q, memOff, rbx, regG
else
R = xmmK, X
insmov, q, memOff, rbx, regrax
to_xmmK, a, X

The load of each generic return word, at thread offset Off, into its register.

3620thunk_reg_loads[], _ ⟶
[]
3621thunk_reg_loadsOff|Offs, Reg|Regs ⟶
insmov, q, memOff, rbx, regReg
thunk_reg_loadsOffs, Regs

The copy of each generic return word, at thread offset Src, into the return buffer at Off(%r12).

3627thunk_buffer_copies[], _ ⟶
[]
3628thunk_buffer_copiesSrc|Srcs, Off ⟶
insmov, q, memSrc, rbx, regrax, insmov, q, regrax, memOff, r12
Off1 is Off + 8

The operand Word into the thread at offset Off, through %rax if it is in memory.

3636operand_thread_storeregR, Off ⟶
insmov, q, regR, memOff, rbx
3638operand_thread_storememD, B, Off ⟶
insmov, q, memD, B, regrax, insmov, q, regrax, memOff, rbx

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.

3647generic_entryGeneric, adaptNeed, Words, Result ⟶
Result = adapter_resultReturn, Channel, RetSize, Packet
target_formReturn, Form
packet_offsetsPacket, PlacedPacket
maplistadapter_wordthread, Words, Ts
typed_locationsTs, Form, Ls, NS, _
atom_concatGeneric, '.short', Short
atom_concatGeneric, '.done', Done
sectiontext, labelGeneric, inspush, q, regrbx, inscmp, q, immNeed, regrdx, insjb, none, symShort, insmov, q, regrdi, regrbx
adapter_stackTs, Ls, Generic
adapter_registersTs, Ls, Generic
generic_entry_callForm, Channel, PlacedPacket, Pop, RetSize, Done
inspop, q, regrbx, insret, none, [], labelShort, insmov, q, regrdx, regrdi, insmov, l, immNeed, regesi, insxor, l, regedx, regedx, inscall, none, pltfilc_cc_args_check_failure, insud2, none, []

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.

3667generic_entry_callreg, data, _, Pop, _, Done ⟶
!
The body returned its words' byte count in %rdx (return_data).
fieldfunction_object, fast_entry, rsi, Fast
inscall, q, indFast
labelDone
3673generic_entry_callreg, Channel, Packet, Pop, RetSize, Done ⟶
fieldfunction_object, fast_entry, rsi, Fast
inscall, q, indFast
instest, b, imm1, regal, insjnz, none, symDone
generic_reg_resultsChannel, Packet
insmov, l, immRetSize, regedx, labelDone
3682generic_entry_callmemBytes, _, packetDests, Zeros, Pop, RetSize, Done ⟶
fieldfunction_object, fast_entry, rdx, Fast
insmov, q, regrsi, regrdx, insmov, q, regrbx, regrsi, inslea, q, memPop, rsp, regrdi, inscall, q, indFast
insmov, b, mem0, rsp, regal, instest, b, regal, regal, insjnz, none, symDone
insmov, l, immRetSize, regedx, insxor, l, regeax, regeax, labelDone

The result words from %rdx:%rcx at their thread offsets; a void result is one zero word.

3701generic_reg_resultsvoid, packet[], Zeros ⟶
3702generic_reg_resultsfloatK, packetOff, Lower ⟶
from_xmmK, xmm0, d
insmov, q, regrdx, memOff, rbx, insmov, q, imm0, memLower, rbx
3706generic_reg_resultstypedKs, packetDests, Zeros ⟶
foldltyped_ret_kind, Ks, KTs, []
insxor, l, regeax, regeax
3711generic_reg_resultswords, packetDests, Zeros ⟶
3715typed_ret_kindp, l−x, l−x|W, W ←
!
3716typed_ret_kindK, K−x|W, W
3717generic_typed_gprs[], [] ⟶
[]
3718generic_typed_gprsR|Rs, Off|Offs ⟶
R = gprG
→
insmov, q, regG, memOff, rbx
else
[]
3721generic_typed_xmms[], [] ⟶
[]
3722generic_typed_xmmsR|Rs, Off|Offs ⟶
R = xmmK, X
→
from_xmmK, X, a
insmov, q, regrax, memOff, rbx
else
[]

The store of each result register at its thread offset.

3730generic_reg_stores[], _ ⟶
[]
3731generic_reg_storesOff|Offs, Reg|Regs ⟶
insmov, q, regReg, memOff, rbx

The copy of each return buffer word, from Off(%rsp) on, to thread offset Dest.

3737generic_buffer_copies[], _ ⟶
[]
3738generic_buffer_copiesDest|Dests, Off ⟶
insmov, q, memOff, rsp, regrax, insmov, q, regrax, memDest, rbx
Off1 is Off + 8

Explicit zero channels from the ABI product.

3745generic_zeroings[] ⟶
[]
3746generic_zeroingsOff|Offs ⟶
insmov, q, imm0, memOff, rbx

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.

3754originsName, Origins, CheckKinds ⟶
formatatomNameLabel, '.L~w..name', Name
sectionrodata, labelNameLabel, ascizName, sectiondata_rel_ro, p2align4
function_originsOrigins, Name, NameLabel
check_originsCheckKinds, Name
3761function_origins[], _, _ ⟶
[]
3762function_originsoriginWhich, Catch, NLowers, NumSetjmps, NumLocals|Origins, Name, NameLabel ⟶
catch_flagCatch, CanCatch
function_originName, NameLabel, NLowers, Which, CanCatch, NumSetjmps, NumLocals
function_originsOrigins, Name, NameLabel
3766catch_flagcatches, 1
3767catch_flagno_catch, 0

A function origin and the origin (origin_label/3) that points at it.

3770function_originName, NameLabel, NLowers, Which, CanCatch, NumSetjmps, NumLocals ⟶
formatatomFOrigin, '.L~w..forigin_~w', Name, Which
origin_labelName, Which, Origin
labelFOrigin, quadNameLabel, quad0, longNLowers, zero4, quad0, byte0, CanCatch, zero2, longNumSetjmps, longNumLocals, zero4, labelOrigin, quadFOrigin, long0, long0

An access-check origin for each check kind.

3783check_origins[], _ ⟶
[]
3784check_originsKind|Kinds, Name ⟶
Kind = kSize, Write, Align
check_origin_labelName, Kind, Label
origin_labelName, n, Origin
p2align4, labelLabel, longSize, byteAlign, 0, Write, zero1, quadOrigin, quadOrigin, quad0
check_originsKinds, Name