Fil-C on Nix

Filnix wiki

mbrock/filnix/nanofil

c_lower.pl

Lowering from C to IR (milestone 4b), in the style of cproc: it turns the parsed translation unit into an IR module for the Fil-C backend.

Every local variable and parameter lives in an alloc'd object, and the allocs are hoisted into the entry block; with -O, promote turns them into temporaries. Pointers are class p, so the output is a Fil-C module.

Expressions are lowered from c_types' typed trees (elaborate/3). The tree says what each expression is and what its operands are converted to, and the elaborator has checked C's constraints, so lowering only translates: lower//3 for values, place//3 for objects and functions.

A narrow integer (char, short, bool) is always held sign- or zero-extended to 32 bits in a w, so conversions only extend when the source type has values the target cannot represent.

Values

ValueIs
vValue, Typean rvalue: Value is an IR value, Type a C type
lvAddr, Typean object at Addr, a p value: an lvalue, or a member of a struct rvalue
fdCallee, Typea function designator; Callee is an IR value, globalSymbol for a named function

State

The lowering nonterminals describe a one-element list holding the state, accessed through the semicontext nonterminals state//1 and state//2 and called with phrase/3 at the entry points. The state is stEnv, Cur, Insts, Blocks, N, Labels, Cases, Allocs:

FieldIs
Envthe c_types environment eOrds, Tags, Defs, Next
CurcurLabel, Phis, the open block, or none after a jump; dead code then opens an unreachable block
Insts, Blocksreversed instructions and blocks, temporarily paired with origin IDs
AllocsbuildRevAllocs, ActiveOrigins, OriginCounter, RevRecords, indexBindings, Hints, ActiveLoc; independent of executable names
Nthe counter for fresh temporaries and labels
Labelsan assoc from C goto labels to IR labels
Casesthe enclosing switch's caseValue, Label, rangeLo, Hi, Label (a GNU case range of 4096 values or more) and defaultLabel

The context argument Ctx is ctxFunction, ReturnType, Break, Continue, Loc, where Break and Continue are someLabel, or none outside a loop or switch.

The environment

Besides what c_types keeps there, the environment's Defs hold:

  • gdefSymbol: a global definition;
  • litSymbol: a string literal;
  • func_nameFunction: the symbol of that function's __func__;
  • deferredSymbol: a static inline function, lowered only if something refers to it;
  • inline_copySymbol, inline_copy_ofLocal: the local function that direct calls of Symbol use: an always_inline inline-only definition's copy, or a fortify overload (overload_wrapper/5).

Next numbers string and compound literals as well as struct ids.

Variable names

NameIs
State0, Statest/8 terms
Env0 ... Envthe environment
Ctxthe context
Loca source location
Ra v, lv or fd value term; Value0 is one before conversion
Van IR value
L-prefixed namesIR labels
Ebseightbytes, ebOffset, Bytes, Class
S0 ... SsEnv, Chunks, End in the static initializer predicates
useslists
usesassoc
usesordsets
usespairs
usesdcgs
usesirop_kind/2, narrow_type/3, width_bytes/2, variadic_func/1, asm_fixed_regs/3
usesc_parsecompound_assignment/2
usesc_lexident_start/1, ident_char/1, decimal/1
usescfginst_def/2
useslaunderlaunder_module/2
c_module+Items, -Module

Module is the IR module of the top-level Items from c_parse: module(Funcs) with the functions followed by the data of every global and string literal. Throws a semantic error for C that nanofil cannot lower.

97c_moduleItems, Module ←
c_moduleItems, Module, _
c_module+Items, -Module, -Attributes

As c_module/2; Attributes lists Sym-A for each function Sym of Module whose definition has the attribute A, always_inline or noinline. Only -O's inlining reads them (main.pl, inline.pl): the IR has no field for them, so they are not in Module.

105c_moduleItems00, moduleFuncs, Attributes ←
appendBuiltins, Items00, ItemsB
maplistweakref_itemItemsB, ItemsB, Items0
weak_aliasesItems0, Items, Aliases
findallName, external_inline_declarationItems, Name, Names0
sortNames0, ExternalNames
foldltop_itemExternalNames, Items, Funcss, Env0, Env1
foldlweak_alias_item, Aliases, AliasItems, Env1, Env
appendFuncss, Funcs0a
appendFuncs0a, AliasItems, Funcs0
with_used_inlinesFuncs0, Env, Funcs1
weak_symbolsItems0, Env, Weak
weak_linkageWeak, Funcs1, Funcs2
unprototyped_callsFuncs2, Funcs3
alias_kindsFuncs3, Funcs4
filc_directivesItems00, Directives
module_asm_symbolsDirectives, Funcs4, Funcs5, Final
launder_moduleFuncs5, Funcs
env_defsEnv, Defs
findallS−always_inline,
gen_associnline_attributeS0, Defs, always_inline
final_symbolFinal, S0, S
memberchkfuncS, _, _, _, _, Funcs
,
Always
findallS−noinline,
noinline_nameItems0, N
lookup_ordinaryEnv, N, fn_, S0, _
final_symbolFinal, S0, S
memberchkfuncS, _, _, _, _, Funcs
,
Never0
sortNever0, Never
appendNever, Always, Attributes

unprototyped_calls(+Items0, -Items): a direct call made without a prototype in scope (callee unproto(S), from lower_node//3's call) whose arguments do not match the parameters of the function the module defines, which only such a call allows (C11 6.5.2.2p6: gmp's configure calls void g { } with six arguments), calls through the function's address instead, as filcc -O0 does: every argument is passed, and the callee reads the parameters it declares, trapping on one that was not passed. Any other such call, which ir.pl's validator accepts (matching, or past a variadic definition's parameters), is a direct call. A call made with a prototype is never rewritten: a mismatch there is the validator's error.

145unprototyped_callsItems0, Items ←
findallS−Ks−V,
memberF, Items0
F = funcS, _, _, Ps, _
findallK, memberparamK, _, Ps, Ks
→
V = variadic
else
V = fixed
,
Pairs
list_to_assocPairs, Defs
maplistunprototyped_itemDefs, Items0, Items
152unprototyped_itemDefs, funcS, L, R, Ps, Bs0, funcS, L, R, Ps, Bs ←
!
foldlunprototyped_blockDefs, Bs0, Bs, 0, _
154unprototyped_item_, Item, Item
156unprototyped_blockDefs, blockL, Phis, Insts0, J, blockL, Phis, Insts, J, N0, N ←
foldlunprototyped_instDefs, Insts0, Insts1, N0, N
appendInsts1, Insts
160unprototyped_instDefs, callRes, unprotoS, Args, Insts, N0, N ←
get_assocS, Defs, Ks−V
findallK, memberargK, _, Args, Ks1
Ks1 ≢ Ks
¬
V ≡ variadic
appendKs, _, Ks1
!
N is N0 + 1
formatatomT, 'unproto~w', N0
Insts = lettmpT, p, copy, globalS, callRes, tmpT, Args
168unprototyped_inst_, callRes, unprotoS, Args, callRes, globalS, Args, N, N ←
!
169unprototyped_inst_, Inst, Inst, N, N
c_toplevel_asm+Items, -Lines

Lines are the assembly lines for the top-level asm among Items, as clang's Fil-C compiles module asm (FilPizlonator.cpp, compileModuleAsm): blank lines vanish, and .symver L, V becomes .symver pizlonated_L, pizlonated_V, versioning the symbol of L's getter, as filcc's objects do (docs/abi-findings.md, *Top-level asm*), and .filc_globl N becomes .globl pizlonated_N. The other Fil-C directives (.filc_rename, .filc_alias, .filc_weak_alias and .filc_weak) change the module's symbols instead and leave no line (module_asm_symbols/4). filcc crashes on any other text; nanofil refuses it, and .filc_unsafe_export and an alias at an offset other than 0 too.

184c_toplevel_asmItems, Lines ←
module_asm_linesItems, Pairs
maplistmodule_asm_line, Pairs, Liness
appendLiness, Lines
189module_asm_linesItems, Pairs ←
findallLoc−L,
membertoplevel_asmLoc, Template, Items
split_atTemplate, 0'\n, Ls
memberL, Ls
,
Pairs
196module_asm_lineLoc−L, Lines ←
D = symverLA, VA
→
formatatomLine, '.symver pizlonated_~w, pizlonated_~w', LA, VA
Lines = Line
D = globlN
→
formatatomLine, '.globl pizlonated_~w', N
Lines = Line
else
Lines = []
module_asm_directive+Loc−Line, -D

D is the directive of one line of top-level asm, as FilPizlonator's MATokenizer reads it: none for a blank line, symver(L, V), globl(N), weak(N), rename(Old, New), or alias(Old, New, export) and alias(Old, New, weak) for .filc_alias and .filc_weak_alias.

212module_asm_directiveLoc−L, D ←
atom_codesA, L
atom_charsA, Cs
phrase, Cs
→
D = none
phrasema_directiveD0, Cs
→
D = D0
→
sema_errorLoc, 'top-level asm "~w": an alias at an offset is not supported', A
else
sema_errorLoc, 'top-level asm "~w" is not supported; only .symver and .filc_rename, .filc_alias, .filc_weak_alias, .filc_weak and .filc_globl are', A
221ma_directiveD ⟶
"."
ma_idWord
","
atom_charsW, Word
atom_charsAA, A
atom_charsBA, B
ma_pairW, AA, BA, D
or
atom_charsW, Word
atom_charsAA, A
ma_singleW, AA, D
ma_pair
229symverLVsymverL, V
230filc_symverLVsymverL, V
231filc_renameOldNewrenameOld, New
232filc_aliasOldNewaliasOld, New, export
233filc_weak_aliasOldNewaliasOld, New, weak
235ma_singlefilc_weak, N, weakN
236ma_singlefilc_globl, N, globlN

An offset of zero, + 0, which glibc's vtables.c gives its declare_object_symbol_alias aliases, is the same symbol as none. The IR's aliases have no offset, so any other is refused.

241ma_zero_offset ⟶
242ma_zero_offset ⟶
[]
244ma_zeros ⟶
245ma_zeros_rest ⟶
246ma_zeros_rest ⟶
[]
248ma_rest[], []
249ma_rest_|Cs, Rest ←
ma_restCs, Rest
filc_directives+Items, -Ds

Ds are the Loc-D symbol directives of the unit's top-level asm, in source order, as FilPizlonator runs them after the module is built (compileModuleAsm): rename, alias, weak and globl.

255filc_directivesItems, Ds ←
module_asm_linesItems, Pairs
findallLoc−D,
memberLoc−L, Pairs
D ≠ none
D ≠ symver_, _
,
Ds

Fil-C's symbol directives, as FilPizlonator's compileModuleAsm runs them on the finished module, one after another (glibc's libc-symbols.h: hidden_proto is .filc_rename N, __GI_N; hidden_def .filc_alias __GI_N, N; hidden_weak .filc_weak_alias):

  • rename(Old, New): the symbol Old, defined or only referenced, is named New from then on, everywhere; nothing if the unit has no Old (LLVM's tree shake), an error if it already has a New.
  • alias(Old, New, L): New becomes a second symbol of linkage L for what Old names, which the unit must define. A New the unit only referenced (or declared weak) is that object from then on, as LLVM replaces the declaration; one it defines is an error, unless it is already the same alias.
  • weak(N): the definition N gets weak linkage.
  • globl(N): a definition N with local linkage gets export linkage (c_toplevel_asm/2 emits its .globl line).

Each symbol is an identity whose name the directives change, so a later rename of an alias's target moves the alias with it, as an LLVM object would. Final is the assoc from each symbol of Items0 to its name in Items; a symbol not in it keeps its name.

281module_asm_symbols[], Items, Items, Final ←
!
empty_assocFinal
282module_asm_symbolsDs, Items0, Items, Final ←
findallS,
memberI, Items0
,
Named
symbol_refsItems0, Refs0, []
appendNamed, Refs0, Names0
sortNames0, Names
findallN−N, memberN, Names, NPs
list_to_assocNPs, Cur0
foldlasm_symbol_directiveItems0, Ds, stCur0, [], [], [], stCur, Merged, Aliases, Links
final_namesCur, Merged, Final
foldlasm_itemFinal, Merged, Links, Items0, Items1, []
maplistnew_aliasFinal, Aliases, AliasItems
appendItems1, AliasItems, Items
item_symbol
293funcS, _, _, _, _S
294dataS, _, _, _S
295aliasS, _, _S

The identity a name stands for now, or none.

298cur_idCur, N, Id ←
get_assocN, Cur, Id

Id is defined: a function or data item of Items0 that is not a weak declaration, or an alias a directive made. Kind is func or data, and Target the identity an alias resolves to.

303defined_idItems0, Aliases, Id, Target ←
memberchkaId, _, T, Aliases
→
Target = T
memberchkfuncId, _, _, _, _, Items0
→
Target = Id
memberdataId, L, _, _, Items0
L ≢ externweak
→
Target = Id
memberchkaliasId, _, T, Items0
→
Target = T
310asm_symbol_directive_, Loc−renameOld, New, stCur0, M, As, Ls, stCur, M, As, Ls ←
cur_idCur0, Old, Id
→
cur_idCur0, New, _
→
sema_errorLoc, 'cannot rename ~w to ~w, which is taken', Old, New
else
del_assocOld, Cur0, Id, Cur1
put_assocNew, Cur1, Id, Cur
else
Cur = Cur0
318asm_symbol_directiveItems0, Loc−aliasOld, New, L, stCur0, M0, As0, Ls, stCur, M, As, Ls ←
cur_idCur0, Old, OldId
defined_idItems0, As0, OldId, Target
→
true
else
sema_errorLoc, 'alias ~w must point to a defined variable or function, not ~w', New, Old
cur_idCur0, New, NewId
→
memberchkaNewId, L, Target, As0
or
memberchkaliasNewId, L1, OldId, Items0as FilPizlonator
L1 ≡ weakcoalesces them
→
L ≡ weak
else
L ≢ weak
→
Cur = Cur0
M = M0
As = As0
defined_idItems0, As0, NewId, _
→
sema_errorLoc, 'alias name ~w is already taken by a definition', New
else
alias_idNew, As0, AId
put_assocNew, Cur0, AId, Cur
M = NewId−Target|M0
As = aAId, L, Target|As0
else
alias_idNew, As0, AId
put_assocNew, Cur0, AId, Cur
M = M0
As = aAId, L, Target|As0
336asm_symbol_directiveItems0, Loc−weakN, stCur, M, As0, Ls, stCur, M, As, Id−weak|Ls ←
cur_idCur, N, Id
defined_idItems0, As0, Id, _
→
true
else
sema_errorLoc, 'cannot do .filc_weak to ~w because it does not exist', N
selectaId, _, T, As0, As1
→
As = aId, weak, T|As1
else
As = As0
341asm_symbol_directiveItems0, _−globlN, stCur, M, As, Ls0, stCur, M, As, Ls ←
cur_idCur, N, Id
defined_idItems0, As, Id, _
→
Ls = Id−export|Ls0
else
Ls = Ls0

A fresh identity for an alias a directive names New: one no symbol of the unit is, whatever names come later.

348alias_idNew, As, alias_symbolNew, N ←
lengthAs, N
final_names+Cur, +Merged, -Final

the final name of every identity, and of every merged declaration the name of what it now is.

352final_namesCur, Merged, Final ←
assoc_to_listCur, NIs
findallId−N,
memberN−Id, NIs
N ≢ Id
,
Moved
list_to_assocMoved, F0
foldlmerged_nameCur, Merged, F0, Final
358merged_nameCur, Id−Target, F0, F ←
assoc_to_listCur, NIs
memberN−Target, NIs
→
true
else
N = Target
put_assocId, F0, N, F
final_symbol+Final, +S0, -S

S is the name module_asm_symbols/4 gave the symbol S0.

366final_symbolFinal, S0, S ←
get_assocS0, Final, S1
→
S = S1
else
S = S0

An item with its symbols renamed; a weak declaration of a symbol an alias replaced goes.

370asm_item_, Merged, _, dataS, externweak, _, [], Is, Is ←
memberchkS−_, Merged
!
372asm_itemFinal, _, Links, I0, I|Is, Is ←
rename_termFinal, I0, I1
memberchkS−L, Links
→
item_linkageL, I1, I
else
I = I1
378item_linkageL, funcS, L0, R, Ps, Bs, funcS, L1, R, Ps, Bs ←
!
func_linkageL, L0, L1
379item_linkageL, dataS, L0, A, Is, dataS, L1, A, Is ←
!
data_linkageL, L0, L1
380item_linkageL, aliasS, _, T, aliasS, L, T
382func_linkageweak, _, weak
383func_linkageexport, local, export ←
!
384func_linkageexport, L, L
386data_linkageweak, L0, L ←
!
memberchkL0, export, local, weak
→
L = weak
memberchkL0, roexport, rolocal, roweak
→
L = roweak
else
thrownanofil_errorformat'.filc_weak of data of linkage ~w is not supported', L0
391data_linkageexport, local, export ←
!
392data_linkageexport, rolocal, roexport ←
!
393data_linkageexport, tlslocal, tlsexport ←
!
394data_linkageexport, L, L
396new_aliasFinal, aId, L, T, aliasN, L, TN ←
final_symbolFinal, Id, N
final_symbolFinal, T, TN
symbol_refs+Term//

the symbols Term references, global(S) and sym(S, Off).

401symbol_refsglobalS ⟶
atomS
!
S
402symbol_refssymS, _ ⟶
atomS
!
S
403symbol_refsT ⟶
compoundT
!
T =.. _|As
404symbol_refsT ⟶
405symbol_refs_ ⟶
[]
407symbol_refs_list[] ⟶
[]
408symbol_refs_listT|Ts ⟶
rename_term+Final, +T0, -T

T0 with each symbol S, an item's own name or a reference global(S) or sym(S, Off), renamed as Final says.

412rename_termFinal, funcS0, L, R, Ps, Bs0, funcS, L, R, Ps, Bs ←
!
final_symbolFinal, S0, S
rename_refsFinal, Bs0, Bs
414rename_termFinal, dataS0, L, A, Is0, dataS, L, A, Is ←
!
final_symbolFinal, S0, S
rename_refsFinal, Is0, Is
416rename_termFinal, aliasS0, L, T0, aliasS, L, T ←
!
final_symbolFinal, S0, S
final_symbolFinal, T0, T
418rename_termFinal, contextS0, R, contextS, R ←
!
final_symbolFinal, S0, S
419rename_term_, I, I
421rename_refsFinal, globalS0, globalS ←
atomS0
!
final_symbolFinal, S0, S
422rename_refsFinal, symS0, O, symS, O ←
atomS0
!
final_symbolFinal, S0, S
423rename_refsFinal, T0|Ts0, T|Ts ←
!
rename_refsFinal, T0, T
rename_refsFinal, Ts0, Ts
424rename_refsFinal, T0, T ←
compoundT0
!
T0 =.. F|As0
rename_refsFinal, As0, As
T =.. F|As
426rename_refs_, T, T

MATokenizer's whitespace (but newline) and identifiers: a letter or _ and then letters, digits, _, $, @ and dots.

430ma_blank ⟶
431ma_blank ⟶
[]
432ma_white ⟶
C
memberchkC, ' ', '\t', '\v', '\f', '\r'
433ma_idC|Cs ⟶
434ma_id_restC|Cs ⟶
C
or
memberchkC, @, '.'
!
435ma_id_rest[] ⟶
[]
436ma_end[], []

Weak symbols (GCC's weak attribute and #pragma weak; C11 J.5.11 lists such extensions as common, not standard). A name declared weak in any declaration of the translation unit, before or after its definition, is a weak symbol: its definition has linkage weak (ir.pl), which a strong definition elsewhere replaces, and if the unit only declares it, its uses refer to it through data of linkage extern(weak), so that it is null when no object of the program defines it, as clang's are (docs/abi-findings.md, *Weak symbols*). A weak alias, ⟪weak, alias"t"⟫ or #pragma weak n = t, is the IR's alias(n, weak, t) (weak_alias_item/4). Weak static and thread-local names are refused.

449weak_attrSpecs ←
memberattrTokens, Specs
attr_wordTokens, W
memberchkW, weak, '__weak__'
!
weak_aliases+Items0, -Items, -Aliases

Items is Items0 with each weak alias declaration an ordinary declaration of its name, and Aliases its alias(Loc, Specs, Dcl, Target) terms, defined after the last item.

456weak_aliasesItems0, Items, Aliases ←
weak_aliasesItems0, Items0, Items, Aliases
457weak_aliases_, [], [], [] ←
!
458weak_aliasesAll, declLoc, Specs, Inits|Is0, declLoc, Specs1, Inits|Is, aliasLoc, Specs1, Inits, Target|As ←
alias_targetSpecs, Target
!
excludealias_attr, Specs, Specs0
extern_specsSpecs0, Specs1
weak_aliasesAll, Is0, Is, As
464weak_aliasesAll, pragma_weakLoc, Name, Target|Is0, Is, Alias|As ←
Target ≢ none
!
memberdecl_, Specs0, Inits, All
Inits = initdclsomeName, _, _, none
→
excludealias_attr, Specs0, Specs1
extern_specsSpecs1, Specs
Alias = aliasLoc, Specs, Inits, Target
else
sema_errorLoc, '#pragma weak ~w = ~w needs a declaration of ~w', Name, Target, Name
weak_aliasesAll, Is0, Is, As
473weak_aliasesAll, I|Is0, I|Is, As ←
weak_aliasesAll, Is0, Is, As
474alias_attrattrTokens ←
attr_wordTokens, Word
memberchkWord, alias, '__alias__'
!
extern_specs+Specs0, -Specs

an alias declares its name, never defines it, so an object alias is no tentative definition.

479extern_specsSpecs0, Specs ←
memberstorageS, Specs0
memberchkS, extern, static
→
Specs = Specs0
else
Specs = storageextern|Specs0
weak_alias_item+Alias, -Item, +Env0, -Env

Item is the IR alias of a weak alias: the unit's uses of its name stay references to its own symbol (weak_linkage/3), which a strong definition elsewhere replaces, as clang's do.

488weak_alias_itemaliasLoc, Specs, Inits, Target, aliasName, weak, Target, Kind−Loc, Env0, Env ←
Inits = initdclsomeName, _, none, none
→
true
else
sema_errorLoc, 'alias is supported only for one declarator', []
decl_typesSpecs, Inits, d_, Type, _, _, _, Env0, Env
alias_kindType, Kind
495alias_kindfunc_, _, _, func ←
!
496alias_kind_, data
alias_kinds+Items0, -Items

Items is Items0 with each alias(Name, Linkage, Target, Kind-Loc) the IR's alias(Name, Linkage, Target), once its Target is a definition of the unit of kind Kind, a function for a function and an object for an object, as GCC and clang require. A Target that is itself an alias of the unit stands for the definition at the end of the chain, as the assembler resolves a .set to a symbol the object defines (glibc's strong_alias (chdir, __GI_chdir) after weak_alias (__chdir, chdir)); a cycle is refused.

506alias_kindsItems0, Items ←
maplistalias_kind_itemItems0, Items0, Items
508alias_kind_itemItems, aliasName, Linkage, Target0, Kind−Loc, aliasName, Linkage, Target ←
!
alias_chain_endItems, Loc, Name, Target0, Target
memberchkfuncTarget, _, _, _, _, Items
→
Found = func
memberdataTarget, L, _, _, Items
L ≢ externweak
→
Found = data
else
sema_errorLoc, 'alias ~w must point to a defined variable or function, not ~w', Name, Target
Found ≡ Kind
→
true
else
sema_errorLoc, 'alias ~w of a ~w names the ~w ~w', Name, Kind, Found, Target
517alias_kind_item_, Item, Item
519alias_chain_endItems, Loc, Seen, T0, T ←
memberchkaliasT0, _, T1, _, Items
→
memberchkT0, Seen
→
sema_errorLoc, 'alias ~w is part of a cycle of aliases', T0
else
alias_chain_endItems, Loc, T0|Seen, T1, T
else
T = T0
weak_symbols+Items, +Env, -Weak

Weak is the ordset of the symbols of the names Items declare weak, each Sym-Kind, Kind fn or data.

530weak_symbolsItems, Env, Weak ←
findallLoc−Name, weak_nameItems, Loc, Name, LNs0
findallLoc−Name,
common_nameItems, Loc, Name
lookup_ordinaryEnv, Name, gvar_, _, _, _
,
LNs1
appendLNs0, LNs1, LNs
foldlweak_symbolEnv, LNs, Weak0, []
sortWeak0, Weak
537weak_nameItems, Loc, Name ←
memberItem, Items
Item = pragma_weakLoc, Name, _
or
Item = declLoc, Specs, Inits
memberinitdclsomeName, _, _, _, Inits
or
Item = fundefLoc, Specs, dclsomeName, _, _, _
common_name+Items, -Loc, -Name

Name, declared common at Loc, has a common symbol, which filcc emits as a weak definition: GCC's common attribute reaches a later declaration of Name, and applies when the unit defines Name only tentatively (C11 6.9.2p2), last by a declaration the attribute reaches. Thread-local and static names are never common (as clang); a function's common attribute is ignored.

551common_nameItems, Loc, Name ←
nth1I, Items, declLoc, Specs, Inits
memberinitdclsomeName, _, _, none, Inits
¬
memberdecl_, S, Is, Items
memberinitdclsomeName, _, _, V, Is
V ≢ none
or
memberstorageC, S
memberchkC, static, (thread_local), typedef
once
nth1J, Items, decl_, S, Is
J ≥ I
¬memberchkstorageextern, S
memberchkinitdclsomeName, _, _, none, Is
558common_attrSpecs ←
memberattrTokens, Specs
attr_wordTokens, W
memberchkW, common, '__common__'
!
561weak_symbolEnv, Loc−Name ⟶
lookup_ordinaryEnv, Name, fn_, Sym, Linkage
→
weak_publicLoc, Name, Linkage
Sym−fn
lookup_ordinaryEnv, Name, gvar_, Sym, Storage, _
→
Storage = thread_local_
→
sema_errorLoc, 'weak thread-local variable ~w is not supported', Name
Storage ≡ static
→
weak_publicLoc, Name, local
else
true
Sym−data
else
[]#pragma weak of an undeclared name: unused
573weak_publicLoc, Name, local ←
!
sema_errorLoc, 'weak declaration of ~w must have external linkage', Name
575weak_public_, _, _
weak_linkage+Weak, +Items0, -Items

Items is Items0 with the weak symbols' definitions weak, and an extern(weak) declaration of each one Items0 refers to without defining it.

580weak_linkage[], Items, Items ←
!
581weak_linkageWeak, Items0, Items ←
maplistweak_itemWeak, Items0, Items1
findalldataSym, externweak, 1, [],
memberSym−_, Weak
¬memberchkfuncSym, _, _, _, _, Items0
¬memberchkdataSym, _, _, _, Items0
¬memberchkaliasSym, _, _, _, Items0
onceitem_refItems0, Sym
,
Decls
appendItems1, Decls, Items
591weak_itemWeak, funcS, export, R, Ps, Bs, funcS, weak, R, Ps, Bs ←
memberchkS−_, Weak
!
593weak_itemWeak, dataS, export, A, Is, dataS, weak, A, Is ←
memberchkS−_, Weak
!
595weak_itemWeak, dataS, roexport, A, Is, dataS, roweak, A, Is ←
memberchkS−_, Weak
!
597weak_itemWeak, aliasS, export, T, K, aliasS, weak, T, K ←
memberchkS−_, Weak
!
599weak_item_, Item, Item

C99 6.7.4: an inline body is an external definition unless ALL file-scope declarations are inline without extern. Inspect the whole translation unit: even a declaration after the body changes emission.

604external_inline_declarationItems, Name ←
memberItem, Items
Item = decl_, Specs, Inits
memberinitdclsomeName, _, _, _, Inits
or
Item = fundef_, Specs, dclsomeName, _, _, _
¬memberchkstoragetypedef, Specs
memberchkstorageextern, Specs
or
¬memberchkfspecinline, Specs

GNU inline reverses the C99 rule: an extern inline body is never the external definition, while a plain inline body is. The parser retains both spellings in the balanced tokens of specifier and suffix attrs. GNU89 mode (NANOFIL_STD=gnu89: -std=c90, gnu90 or -fgnu89-inline, where clang predefines __GNUC_GNU_INLINE__) gives every inline body the GNU rule, as glibc's extern __inline atoi relies on.

619gnu_inline_ruleSpecs ←
getenv'NANOFIL_STD', gnu89
→
true
else
memberattrAttrs, Specs
attr_wordAttrs, Attr
memberchkAttr, gnu_inline, '__gnu_inline__'
!
626always_inlineSpecs ←
memberattrAttrs, Specs
attr_wordAttrs, A
memberchkA, always_inline, '__always_inline__'
!
inline_attribute+Specs, +Sym, +Env0, -Env

Env records a definition's always_inline attribute for c_module/3, which -O's wide inlining honours (inline.pl). Without it, Env is Env0.

633inline_attributeSpecs, Sym, Env0, Env ←
→
put_definline_attributeSym, always_inline, Env0, Env
else
Env = Env0
noinline_name+Items, -Name

GNU's noinline attribute is on some declaration or the definition of the function Name, as weak_name/3 finds weak. (A C23 [[gnu::noinline]] is not seen: c_parse keeps no C23 attribute.)

642noinline_nameItems, Name ←
memberItem, Items
Item = decl_, Specs, Inits
memberinitdclsomeName, _, _, _, Inits
or
Item = fundef_, Specs, dclsomeName, _, _, _
memberattrAttrs, Specs
attr_wordAttrs, A
memberchkA, noinline, '__noinline__'
650external_inline_bodySpecs, Storage, Name, ExternalNames ←
→
Storage ≢ extern
else
memberchkName, ExternalNames

with_used_inlines(+Funcs0, +Env0, -Items): Items is Funcs0, then every deferred static inline function they refer to, directly or through one another, then the global data. A static inline function is lowered only if something refers to it, as clang emits it only then; headers are full of ones nanofil cannot lower. Lowering one can refer to more, so this iterates until no new symbol turns up.

662with_used_inlinesFuncs0, Env0, Items ←
findallSym, func_symbolFuncs0, Sym, Defined0
sortDefined0, Defined
used_inlinesFuncs0, Defined, Env0, New, Env
global_dataEnv, Data0
appendFuncs0, New, Funcs
A constructor or destructor attribute on a function this unit only
declares registers nothing, as in clang, which registers one when
it emits the definition (libgpg-error's header declares
gpg_err_init so in every unit).
excludeundefined_initFuncs, Data0, Data
appendFuncs, Data, Items
675undefined_initFuncs, data_, init_, _, _, itemp, symSym, _ ←
¬memberchkfuncSym, _, _, _, _, Funcs
used_inlines+Scan, +Defined, +Env0, -New, -Env

New are the deferred inline functions that the functions Scan or the global data refer to and that are not in the ordset Defined, then those the new ones (or data they add) refer to, and so on. Each round scans only the functions the previous one lowered, and the sorted references are looked up once: rescanning every function per round, with a scan of the functions per reference, was quadratic for SQLite.

685used_inlinesScan, Defined, Env0, New, Env ←
env_defsEnv0, Defs
global_dataEnv0, Data
findallSym,
item_refScan, Sym
or
item_refData, Sym
,
Refs0
sortRefs0, Refs1
ord_subtractRefs1, Defined, Refs
includedeferred_defDefs, Refs, Syms
Syms ≡ []
→
New = []
Env = Env0
else
foldllower_inline, Syms, Newss, Env0, Env1
appendNewss, New0
findallSym, func_symbolNew0, Sym, Lowered0
sortLowered0, Lowered
ord_unionDefined, Lowered, Defined1
used_inlinesNew0, Defined1, Env1, New1, Env
appendNew0, New1, New
703func_symbolItems, Sym ←
memberfuncSym, _, _, _, _, Items
704deferred_defDefs, Sym ←
get_assocdeferredSym, Defs, _
item_ref+Items, -Sym

some function or data item of Items refers to symbol Sym (nondet, with repeats).

708item_refItems, Sym ←
memberItem, Items
Item = func_, _, _, _, Blocks
memberblock_, _, Insts, _, Blocks
memberInst, Insts
Inst = let_, _, _, Args
memberglobalSym, Args
or
Inst = call_, globalSym, _
or
Inst = call_, unprotoSym, _
or
Item = data_, _, _, DataItems
memberitemp, symSym, _, DataItems
or
Item = alias_, _, Sym, _
lower_inline+Sym, -Func, +Env0, -Env

Func is the deferred inline function Sym, lowered in the scope it was defined in.

724lower_inlineSym, Func, Env0, Env ←
env_defsEnv0, Defs
get_assocdeferredSym, Defs, dfLoc, Name, Type, DclType, Body, Scope
env_restoreScope, Env0, Env1a
get_associnline_copy_ofSym, Defs, _
→
bind_ordinaryName, fnType, Sym, local, Env1a, Env1
else
Env1 = Env1a
functionLoc, Name, Type, DclType, Body, Env1, Env2, Func
env_scopeEnv0, Scope0, _
env_restoreScope0, Env2, Env

Environment

738env_defse_, _, Defs, _, Defs
put_def+Key, +Value, +Env0, -Env

Env is Env0 with Key defined as Value.

742put_defKey, Value, eOrds, Tags, Defs0, Next, eOrds, Tags, Defs, Next ←
put_assocKey, Defs0, Value, Defs
next_id-Id, +Env0, -Env

Id is Env0's next number, which Env has moved past.

747next_idId, eOrds, Tags, Defs, Id, eOrds, Tags, Defs, Next ←
Next is Id + 1

Top level

top_item+ExternalNames, +Item, -Funcs, +Env0, -Env

Funcs are the IR functions a top-level item defines now (a static inline is deferred).

754top_item_, pragma_weak_, _, _, [], Env, Env ←
!
755top_item_, toplevel_asm_, _, [], Env, Env ←
!
756top_item_, static_assertLoc, Expr, _, [], Env, Env ←
!
static_assertLoc, Expr, Env
758top_item_, decl_, Specs, initDcl, none, [], Env, Env ←
overloadSpecs, Dcl, Env
!
760top_itemExternalNames, fundefLoc, Specs, dclsomeName, funcRet, knrNames, Decls, Asm, atBL, blockItems, Funcs, Env0, Env ←
!
A K&R definition is lowered as the prototyped one knr_definition/6
rewrites it to.
lookup_ordinaryEnv0, Name, fnfunc_, Ps, _, _, _
Ps ≢ unspecified
→
Prototyped = true
else
Prototyped = false
knr_definitionLoc, knrNames, Decls, Prototyped, Env0, Params, Locals
appendLocals, Items, Items1
top_itemExternalNames, fundefLoc, Specs, dclsomeName, funcRet, Params, Asm, atBL, blockItems1, Funcs, Env0, Env1
An identifier list does not give the function a prototype
(C11 6.9.1p7): calls after it convert their arguments by the
default argument promotions, as clang compiles them.
Prototyped ≡ false
lookup_ordinaryEnv1, Name, fnfuncFRet, _, fixed, Sym, Linkage
→
bind_ordinaryName, fnfuncFRet, unspecified, fixed, Sym, Linkage, Env1, Env2
lookup_ordinaryEnv2, qtypeName, funcQRet, _, fixed
→
bind_ordinaryqtypeName, funcQRet, unspecified, fixed, Env2, Env
else
Env = Env2
else
Env = Env1
784top_item_, fundef_, Specs, Dcl, _, [], Env, Env ←
overloadSpecs, Dcl, Env
!
786top_item_, fundefLoc, Specs, Dcl, Body, [], Env0, Env ←
overload_wrapperSpecs, Dcl, Env0, Sym, Type
!
Dcl = dclsomeName, DclType, _
formatatomLocal, '~w.overload', Sym
env_scopeEnv0, Scope, _
put_defdeferredLocal, dfLoc, Name, Type, DclType, Body, Scope, Env0, Env1
put_definline_copySym, Local, Env1, Env2
put_definline_copy_ofLocal, Sym, Env2, Env
794top_item_, declLoc, Specs, Inits, Items, Env0, Env ←
alias_targetSpecs, Target
!
alias_declLoc, Specs, Inits, Target, Items, Env0, Env
797top_item_, declLoc, Specs, Inits, [], Env0, Env ←
!
catchdecl_typesSpecs, Inits, Decls, Env0, Env1, not_constant, sema_errorLoc, 'array size is not a constant', []
foldlglobal_declLoc, Decls, Env1, Env2
foldlinit_declLoc, Specs, Decls, Env2, Env
802top_itemExternalNames, fundefLoc, Specs, dclsomeName, DclType, Asm, Body, Funcs, Env0, Env ←
decl_typesSpecs, initdclsomeName, DclType, Asm, none, d_, Type, Storage0, _, _, Env0, Env1
prior_storageName, Storage0, Env1, Storage
global_declLoc, dName, Type, Storage, Asm, none, Env1, Env1a
init_declLoc, Specs, dName, Type, Storage, Asm, none, Env1a, Env1b
lookup_ordinaryEnv1b, Name, fn_, DefSym, _
inline_attributeSpecs, DefSym, Env1b, Env2
memberchkfspecinline, Specs
Storage ≡ static
or
¬external_inline_bodySpecs, Storage, Name, ExternalNames
→
Funcs = []
Storage ≡ static
→
lookup_ordinaryEnv2, Name, fn_, Sym, _
env_scopeEnv2, Scope, _
put_defdeferredSym, dfLoc, Name, Type, DclType, Body, Scope, Env2, Env
→
clang inlines every call to an always_inline C99 inline-only
definition once it optimizes, so no unit need define the
symbol (ell's DEFINE_CLEANUP_FUNC): calls go to a local copy,
lowered if called; the function's address is still the
external one.
local_inline_copyalways_inline, Loc, Name, Type, DclType, Body, Env2, Env
lookup_ordinaryEnv2, Name, fn_, Sym, _
¬
names_symbolX, Sym, Env2
→
A GNU extern inline body is the function's meaning, as an
external definition must be (C11 6.7.4p7), and clang, which
sees it as available_externally, inlines its calls at -O2:
glibc defines __libc_use_alloca and __find_specmb nowhere
else. Calls go to a local copy too. One that names its own
symbol, as glibc's fortify wrappers do through an asm label
or a __builtin_ spelling, keeps calling the external one,
or the copy would call itself.
local_inline_copygnu_inline, Loc, Name, Type, DclType, Body, Env2, Env
else
Env = Env2a C99 inline-only definition
Storage ≡ static
→
One nanofil cannot lower is deferred as a static inline is:
clang emits an unreferenced static function not at all, and
headers (amxcomplexintrin.h) define ones nanofil refuses. A
reference lowers it again, which raises the error then.
catchfunctionLoc, Name, Type, DclType, Body, Env2, Env3, Funcs0, Error,
→
true
else
throwError
varFuncs0
→
Funcs = []
lookup_ordinaryEnv2, Name, fn_, Sym, _
env_scopeEnv2, Scope, _
put_defdeferredSym, dfLoc, Name, Type, DclType, Body, Scope, Env2, Env
else
Funcs = Funcs0
Env = Env3
else
vector_abiType, Env2, ctxName, none, none, none, Loc
functionLoc, Name, Type, DclType, Body, Env2, Env, Funcs
local_inline_copy+Kind, +Loc, +Name, +Type, +DclType, +Body, +Env0, -Env

calls of the function Name go to Name's Sym.Kind, a local copy of its inline Body, deferred and so lowered only if called. An always_inline copy keeps the attribute for -O's inlining.

865local_inline_copyKind, Loc, Name, Type, DclType, Body, Env0, Env ←
lookup_ordinaryEnv0, Name, fn_, Sym, _
formatatomLocal, '~w.~w', Sym, Kind
env_scopeEnv0, Scope, _
put_defdeferredLocal, dfLoc, Name, Type, DclType, Body, Scope, Env0, Env1
put_definline_copySym, Local, Env1, Env2
put_definline_copy_ofLocal, Sym, Env2, Env3
Kind ≡ always_inline
→
put_definline_attributeLocal, always_inline, Env3, Env
else
Env = Env3
body_identifier+Body, -X

X is an identifier the function body names.

877body_identifieridX, X ←
atomX
878body_identifierT, X ←
compoundT
T ≠ id_
arg_, T, A
names_symbol+X, +Sym, +Env

the identifier X calls or names a function of symbol Sym: itself, through an asm label, or as a __builtin_X spelling of it.

885names_symbolX, Sym, Env ←
lookup_ordinaryEnv, X, fn_, Sym, _
→
true
else
atom_concat'__builtin_', B, X
B ≡ Sym
→
true
else
lookup_ordinaryEnv, B, fn_, Sym, _

GNU's constructor and destructor attributes, with an optional priority: the function runs before main (after exit), as clang emits it: an .init_array (.fini_array) entry, with the priority in the section's name, whose native forwarder hands the function object to the Fil-C runtime. nanofil records init(Kind, Sym) with the priority (none without one), and global_data/2 makes a data item of linkage init(Kind, Priority) holding the function (ir.pl). The item refers to the function, so a static one is lowered even if deferred.

901init_declLoc, Specs, dName, func_, _, _, _, _, _, Env0, Env ←
findallKind−Prio, init_attrSpecs, Kind, Prio, Inits
Inits ≢ []
!
lookup_ordinaryEnv0, Name, fn_, Sym, _
foldlinit_defLoc, Sym, Inits, Env0, Env
905init_decl_, _, _, Env, Env
907init_defLoc, Sym, Kind−Prio, Env0, Env ←
Prio = bad
→
sema_errorLoc, 'the ~w priority of ~w is not an integer from 0 to 65535', Kind, Sym
else
next_idN, Env0, Env1
put_definitKind, Sym, N−Prio, Env1, Env
init_attr+Specs, -Kind, -Prioritynondet

Specs carry GNU's constructor (Kind ctor) or destructor (dtor) attribute, with Priority an integer, none without one, or bad.

917init_attrSpecs, Kind, Prio ←
memberattrTokens, Specs
attr_entryTokens, Word, Rest
init_wordWord, Kind
Rest = groupArg|_
→
Arg = intPrio, _
integerPrio
between0, 65535, Prio
→
true
else
Prio = bad
else
Prio = none
init_word
926constructorctor
927'__constructor__'ctor
928destructordtor
929'__destructor__'dtor
rvals_discarded+Args, +Ctx//

evaluate Args for their effects.

932rvals_discarded[], _ ⟶
[]
933rvals_discardedA|As, Ctx ⟶
rvalA, Ctx, _
deferrable+Error

Error is a diagnostic of C that nanofil does not lower, not an internal failure, so a static function raising it can wait for a reference.

938deferrablenanofil_error_

GCC's alias attribute: int ⟪alias"fg"⟫ bgint, char ** (dash's bgcmd). The name is bound to the target's symbol, as if declared with asm("fg"), so the translation unit's calls and addresses are the target's, as clang's are. An alias with external linkage also needs its own symbol for other translation units: the IR's alias(Name, export, Target), whose getter is the target's, as Fil-C's is (filc_amd64), so its address there is the target's too. A function alias of any type and an object alias are alike; the target must be a definition of the unit of the same kind (alias_kinds/2). Several declarators are refused.

GNU's weakref(T), or weakref with alias(T), on a static declaration of N: N refers to the symbol T (GCC, Common Function Attributes, weakref), so the declaration is an extern one of N with asm label T. clang's reference is weak, null if no object defines T, unless the unit uses T otherwise; nanofil makes it weak when the unit does not declare T, so where it differs a missing T fails to link instead of reading null. Ruby's anyargs.h declares typed weakrefs of rb_define_method this way.

958weakref_itemAll, declLoc, Specs0, initdclsomeN, Syn, none, none, declLoc, Specs, initdclsomeN, Syn, asmT, none ←
!
memberchkstoragestatic, Specs0
→
true
else
sema_errorLoc, 'weakref declaration of ~w must have internal linkage', N
excludeweakref_spec, Specs0, Specs1
memberdecl_, _, Is, All
memberinitdclsomeT, _, _, _, Is
→
Weak = []
memberfundef_, _, dclsomeT, _, _, _, All
→
Weak = []
else
Weak = attrgroupidweak
appendstorageextern, Weak, Specs1, Specs
970weakref_item_, Item, Item
972weakref_targetSpecs, T ←
memberattrTokens, Specs
attr_entryTokens, W, Rest
memberchkW, weakref, '__weakref__'
!
Rest = groupstrBytes|_
→
atom_codesT, Bytes
else
alias_targetSpecs, T
979weakref_specstoragestatic
980weakref_specattrTokens ←
attr_wordTokens, W
memberchkW, weakref, '__weakref__', alias, '__alias__'
!
983alias_targetSpecs, Target ←
memberattrTokens, Specs
attr_entryTokens, Word, groupArg|_
memberchkWord, alias, '__alias__'
!
Arg = strBytes
atom_codesTarget, Bytes
989alias_declLoc, Specs, Inits, Target, Items, Env0, Env ←
Inits = initdclsomeName, DclType, none, none
→
true
else
sema_errorLoc, 'alias is supported only for one declarator', []
extern_specsSpecs, Specs1
decl_typesSpecs1, initdclsomeName, DclType, asmTarget, none, d_, Type, Storage, Asm, Init, Env0, Env1
global_declLoc, dName, Type, Storage, Asm, Init, Env1, Env
Storage ≡ static
→
Items = []
else
alias_kindType, Kind
Items = aliasName, export, Target, Kind−Loc

clang's overloadable attribute: glibc's _FORTIFY_SOURCE headers add overloads of open, open64 and openat chosen by arity, which C cannot express. nanofil has no overloading: a declaration or definition marked overloadable whose type is not compatible with the visible declaration of its name is skipped, and calls reach that function (glibc's open). glibc's overloads only check the flags before calling it, so a valid program behaves alike. A compatible overloadable declaration is an ordinary redeclaration, except a static definition of a function with external linkage (overload_wrapper/5).

1010overloadSpecs, dclsomeName, DclType, Asm, Env ←
memberattrTokens, Specs
attr_wordTokens, Word
memberchkWord, overloadable, '__overloadable__'
!
lookup_ordinaryEnv, Name, fnVisible, _, _
decl_typesSpecs, initdclsomeName, DclType, Asm, none, d_, Type, _, _, _, Env, _
¬compatible_typeVisible, Type

overload_wrapper(+Specs, +Dcl, +Env, -Sym, -Type): Dcl, with Specs, is a static overloadable definition of function Sym, which has external linkage: glibc's printf family under clang, whose pass_object_size parameter makes clang prefer it at every call while it mangles it as a function of its own. top_item/5 lowers it under its own symbol, as always_inline's local copies are, and direct calls of the name go there even after the program defines the function itself (cyrus-sasl's dprintf, which filcc's calls never reach); the name's address stays the external function's, as in clang.

1027overload_wrapperSpecs, dclsomeName, DclType, Asm, Env, Sym, Type ←
memberattrTokens, Specs
attr_wordTokens, Word
memberchkWord, overloadable, '__overloadable__'
!
lookup_ordinaryEnv, Name, fnVisible, Sym, Linkage
Linkage ≢ local
decl_typesSpecs, initdclsomeName, DclType, Asm, none, d_, Type, static, _, _, Env, _
compatible_typeVisible, Type
static_assert+Loc, +Expr, +Env

throw a semantic error unless Expr is a nonzero constant.

1038static_assertLoc, Expr, Env ←
catchconst_valueExpr, Env, Value, _, not_constant, fail
→
true
else
sema_errorLoc, 'static assertion is not a constant', []
Value ≠ 0
→
true
else
sema_errorLoc, 'static assertion failed', []
readonly_global+Thread, +Name, +Sym, +Env0, -Env

Env records that static object Sym, declared as Name, is const (c_types readonly_object_type/1), so global_item/4 gives it linkage ro(L), as filcc gives it a read-only capability. A thread-local one is not, as in filcc.

1053readonly_globalnone, Name, Sym, Env0, Env ←
lookup_ordinaryEnv0, qtypeName, RT
!
put_defreadonlySym, true, Env0, Env
1056readonly_global_, _, _, Env, Env
global_decl+Loc, +Decl, +Env0, -Env

Env binds the d/5 declaration Decl of a function or of an object with static or thread storage, and defines the object unless it is an extern without an initializer. A thread-local definition is data of linkage tls(L) (ir.pl).

1062global_declLoc, dName, Type1, Storage0, Asm, Init, Env0, Env ←
declared_symbolName, Asm, Env0, Sym
redeclaredLoc, Name, Sym, Type1, Env0, Type0
qualified_redeclaredLoc, Name, Sym, Type0, Env0, Env00
Storage0 = thread_localStorage
→
Thread = tls
else
Storage = Storage0
Thread = none
Type0 = func_, _, _
→
Thread ≡ tls6.7.1p4
→
sema_errorLoc, 'function ~w cannot be _Thread_local', Name
else
true
prior_storageName, Storage, Env00, Storage1
Storage1 ≡ static
→
Linkage = local
else
Linkage = export
Init ≢ none
→
sema_errorLoc, 'function ~w has an initializer', Name
else
true
bind_ordinaryName, fnType0, Sym, Linkage, Env00, Env
else
static_typeName, Type0, Loc
complete_arrayType0, Init, Type, Loc, Env00, none
bind_ordinaryName, gvarType, Sym, Storage0, Init, Env00, Env1
Storage ≡ extern
Init ≡ none
→
Env = Env1
else
Storage ≡ static
→
Linkage0 = local
else
Linkage0 = export
thread_linkageThread, Linkage0, Linkage
define_globalSym, Type, Linkage, Init, Loc, Env1, Env2a
readonly_globalThread, Name, Sym, Env2a, Env2
data_alignmentName, Sym, Env2, Env
declared_symbol+Name, +Asm, +Env, -Sym

a declaration of Name with Asm (asm(S) or none) names symbol Sym: S, else the symbol of Name's visible declaration with linkage, else Name. GCC applies an asm label to every later declaration and the definition (void foo(void) asm("bar"); void foo(void) {...} defines bar, filc/tests asmname). A block-scope static has no linkage, so a later extern does not take its function-qualified symbol. filc_test_support.h's memmoves, which filcc lowers as its builtin memmove and no library defines, are the runtime's zmemmove.

1101declared_symbol_, asmSym, _, Sym ←
!
1102declared_symbolName, none, Env, Sym ←
memberchkName, zmemmove_union, zmemmove_builtin, zmemmove_builtin_volatile
→
Sym = zmemmove
lookup_ordinaryEnv, Name, Prev
Prev = fn_, Sym0, _
or
Prev = gvar_, Sym0, Storage, _
Storage ≢ static
→
Sym = Sym0
else
Sym = Name
prior_storage+Name, +Storage0, +Env, -Storage

a function declared with Storage0 has Storage: static when Storage0 is not, and a visible declaration gave it internal linkage. A function declared without a storage class, or extern, takes the prior declaration's linkage (C11 6.2.2p4-5): libevent declares min_heap_ctor_ static inline and defines it without static.

1117prior_storageName, Storage0, Env, Storage ←
Storage0 ≢ static
lookup_ordinaryEnv, Name, fn_, _, local
→
Storage = static
else
Storage = Storage0
redeclared+Loc, +Name, +Sym, +Type0, +Env, -Type

Type is what a declaration of Name (symbol Sym) as Type0 declares, given Name's visible declaration of the same symbol: their composite (C11 6.2.7p3), or a semantic error, as clang's, if they are of different kinds or incompatible types (6.7p4, 6.2.7p2). A declaration without one, or of another symbol (a block-scope static, an asm label), is Type0.

1129redeclaredLoc, Name, Sym, Type0, Env, Type ←
lookup_ordinaryEnv, Name, Prev
Prev = fnPType, PSym, _
or
Prev = gvarPType, PSym, _, _
PSym ≡ Sym
→
PType = func_, _, _
→
K0 = function
else
K0 = object
Type0 = func_, _, _
→
K = function
else
K = object
K0 ≡ K
→
true
else
sema_errorLoc, 'redefinition of \'~w\' as different kind of symbol', Name
composite_typePType, Type0, Type, Enva transparent union
→
truemeets its members
same_enum_redeclaredName, Sym, Env
→
Type = Type0
Type0 = func_, _, _
→
sema_errorLoc, 'conflicting types for \'~w\'', Name
else
sema_errorLoc, 'redeclaration of \'~w\' with a different type', Name
else
Type = Type0
same_enum_redeclared+Name, +Sym, +Env

the plain types of Name's declaration and an earlier one of Sym differ only where both name one enum, before and after its definition (c_types' enum_completed/4): their reflected types, which keep each enum's identity, agree. The later declaration's type, which sees the definition, is the composite.

1155same_enum_redeclaredName, Sym, Env ←
lookup_ordinaryEnv, qtypeName, QT
env_defsEnv, Defs
get_assocqsymName, Sym, Defs, PQT
composite_typePQT, QT, _, Env
qualified_redeclared+Loc, +Name, +Sym, +Type, +Env0, -Env

Name's declaration, of qualified type qtype(Name) (c_types binds it first), agrees with every earlier declaration of Name as symbol Sym, also one whose scope has ended (a block-scope extern): compatible types have the same qualifiers (C11 6.7.3p10, 6.2.7p2; extern const int x; int x; is clang's error). redeclared/6 compares the unqualified types. Env records the composite under qsym(Name, Sym) for the next declaration; an alias, whose Sym is its target's, is not a redeclaration of it.

1168qualified_redeclaredLoc, Name, Sym, Type, Env0, Env ←
lookup_ordinaryEnv0, qtypeName, QT
→
env_defsEnv0, Defs
get_assocqsymName, Sym, Defs, PQT
→
composite_typePQT, QT, C, Env0
→
true
Type = func_, _, _
→
sema_errorLoc, 'conflicting types for \'~w\'', Name
else
sema_errorLoc, 'redeclaration of \'~w\' with a different type', Name
else
C = QT
put_defqsymName, Sym, C, Env0, Env
else
Env = Env0
thread_linkage+Thread, +Linkage0, -Linkage

the data linkage of a definition of linkage Linkage0, thread-local if Thread is tls.

1185thread_linkagenone, Linkage, Linkage
1186thread_linkagetls, Linkage, tlsLinkage
static_type+Name, +Type, +Loc

an object Name at file scope or with linkage may have Type: nanofil supports it, and it is not variably modified (C11 6.7.6.2p2).

1191static_typeName, Type, Loc ←
supportedType, Loc
→
sema_errorLoc, '~w has a variably modified type at file scope or with linkage', Name
else
true

Definitions of globals live in the environment's Defs under gdef(Symbol), string literals under lit(Symbol); both survive block scopes. A tentative definition (no initializer) does not replace one with an initializer.

data_alignment+Name, +Sym, +Env0, -Env

the data Sym of object Name is aligned to Name's alignment request (c_types' object_alignment/5), if it has one, the strictest of its declarations'.

1205data_alignmentName, Sym, Env0, Env ←
lookup_ordinaryEnv0, qalignName, A
integerA
→
env_defsEnv0, Defs
get_assocgalignSym, Defs, A0
→
A1 is maxA, A0
else
A1 = A
put_defgalignSym, A1, Env0, Env
else
Env = Env0
1213define_globalSym, Type, Linkage, Init, Loc, Env0, Env ←
env_defsEnv0, Defs
get_assocgdefSym, Defs, g_, _, Init0, _
Init0 ≢ none
→
Init ≡ none
→
Env = Env0
else
sema_errorLoc, '~w is initialized twice', Sym
else
put_defgdefSym, gType, Linkage, Init, Loc, Env0, Env

new_literal(+Lit, -Sym, +Env0, -Env): Sym is a fresh symbol that Env defines as literal Lit, a list of bytes (a char array) or w(ElemType, Values). func_literal(+Func, -Sym, +Env0, -Env): Sym is function Func's __func__, one static array however often it is named (6.4.2.2p1).

1228func_literalFunc, Sym, Env0, Env ←
env_defsEnv0, Defs
get_assocfunc_nameFunc, Defs, Sym
→
Env = Env0
else
atom_codesFunc, Bytes
new_literalBytes, Sym, Env0, Env1
put_deffunc_nameFunc, Sym, Env1, Env

Equal literals of a unit are one object, as clang merges them (6.4.5p7 leaves it unspecified): p == "hello" holds when p was set from another "hello" (filc/tests struct).

1239new_literalLit, Sym, Env0, Env ←
env_defsEnv0, Defs
get_assoclit_ofLit, Defs, Sym0
→
Sym = Sym0
Env = Env0
else
next_idN, Env0, Env1
formatatomSym, 'str.~w', N
put_deflitSym, Lit, Env1, Env2
put_deflit_ofLit, Sym, Env2, Env
str_elems+Str, +ElemType, -Elems

string literal term Str can initialize an array of ElemType, and Elems are its elements. Str is str(Bytes) for a char array, or wstr(WideType, Values) for an integer array of its element's size.

1253str_elemsstrBytes, ElemType, Bytes ←
is_char_typeElemType
1255str_elemswstrWideType, Values, ElemType, Values ←
int_typeElemType
int_infoElemType, Size, _
int_infoWideType, Size, _
1259str_elemslistentry[], Str, ElemType, Values ←
Str ≠ list_
str_elemsStr, ElemType, Values
new_complit+TypeName, +Type0, +Init, +Loc, -Sym, +Env0, -Env

Sym is a fresh symbol that Env defines as a file-scope compound literal, an unnamed static object, read-only if TypeName is const.

1265new_complitTypeName, Type0, Init, Loc, Sym, Env0, Env ←
next_idN, Env0, Env1
formatatomSym, 'complit.~w', N
complete_arrayType0, Init, Type, Loc, Env0, none
define_globalSym, Type, local, Init, Loc, Env1, Env2
reflect_typeTypeName, Type0, Env0, RT
→
put_defreadonlySym, true, Env2, Env
else
Env = Env2
global_data+Env0, -Data

Data are the data items of every global definition and string literal.

1277global_dataEnv0, Data ←
global_itemsEnv0, [], GlobalData, Env
env_defsEnv, Defs
assoc_to_listDefs, Pairs
findalldataSym, rolocal, Align, Items,
memberlitSym−Lit, Pairs
Lit = wWideType, Values0
→
int_infoWideType, Align, _
memberchkAlign−Width, 2−h, 4−w
else
Values0 = Lit
Align = 1
Width = b
appendValues0, 0, Values
findallitemWidth, constV, memberV, Values, Items
,
LiteralData
In declaration order (N), as clang lists them: the runtime runs
.init_array forward and .fini_array backward (filc/tests exittest).
findallN−dataName, initKind, Prio, 8, itemp, symSym, 0,
memberinitKind, Sym−N−Prio, Pairs
formatatomName, '__nanofil_~w_~w', Kind, Sym
,
Inits
keysortInits, SortedInits
pairs_valuesSortedInits, InitData
appendGlobalData, LiteralData, InitData, Data
global_items+Env0, +Done, -Data, -Env

Data are the data items of the global definitions whose symbols are not in Done. Initializers can define more globals (file-scope compound literals), so this repeats until there are no new ones; Done lists the symbols already emitted.

1307global_itemsEnv0, Done, Data, Env ←
env_defsEnv0, Defs
assoc_to_listDefs, Pairs
include/3, not findall/3, which would copy each initializer (gtk3's
1.5 MB resource blob, 150 MB of syntax).
includenew_gdefDone, Pairs, NewPairs
maplistgdef_pair, NewPairs, New
New ≡ []
→
Data = []
Env = Env0
else
foldlglobal_item, New, Data0, Env0, Env1
findallNewSym, memberNewSym−_, New, NewSyms
appendDone, NewSyms, Done1
global_itemsEnv1, Done1, Data1, Env
appendData0, Data1, Data
1323new_gdefDone, gdefSym−_ ←
¬memberchkSym, Done
1324gdef_pairgdefSym−G, Sym−G
global_item+Sym−G, -Data, +Env0, -Env

Data is the data item of global definition G.

1328global_itemSym−gType, Linkage00, Init, Loc, dataSym, Linkage, Align, Items, Env0, Env ←
common_linkageLinkage00, Init, Linkage0
env_defsEnv0, Defs0
get_assocreadonlySym, Defs0, true
memberchkLinkage0, export, local, weak
→
Linkage = roLinkage0
else
Linkage = Linkage0
catchtype_size_alignType, Env0, _, Align0, badWhy, sema_errorLoc, '~w has an unsupported type (~w)', Sym, Why
env_defsEnv0, Defs
get_assocgalignSym, Defs, Requested
→
true
else
Requested = 1
Align is maxAlign0, Requested
get_associnitializedSym, Defs, Items
→
Env = Env0
else
static_itemsType, Init, Loc, Env0, Env, Items
common_linkage+Linkage0, +Init, -Linkage

under -fcommon (nanofil-cc sets NANOFIL_COMMON=1), an external object that the unit only defines tentatively (C11 6.9.2p2; Init none: no declaration initializes it) is a common symbol, which clang gives Fil-C as a weak definition: the link keeps one, and every unit's uses go through its getter. C11 J.5.11 lists this as a common extension; GCC 10 and clang 11 made -fno-common the default, which older packages' duplicate tentative definitions need undone. A const one is common too; a thread-local one (tls) is not, as in clang. clang also keeps one with a section attribute strong, which nanofil ignores.

1356common_linkageexport, none, weak ←
getenv'NANOFIL_COMMON', '1'
!
1357common_linkageLinkage, _, Linkage

Functions

function+Loc, +Name, +Type, +DclType, +Body, +Env0, -Env, -Funcdet

Func is the IR for the definition of function Name, of type Type, with declarator DclType and body Body: the list [func(Sym, Linkage, RetClass, Params, Blocks), context(Sym, Records)], the function and its provenance records (provenance.pl), which function_/8 builds. Env is Env0 with what the body defined outside its own scope. Lowering is deterministic; once/1 keeps a function's choicepoints from holding its states alive while the rest of the file is lowered. A type nanofil refuses (bad/1, from c_types) is reported at the function, unless a caller defers it (deferrable/1).

1372functionLoc, Name, Type, DclType, Body, Env0, Env, Func ←
filcc returns a struct holding an _Atomic struct or union by its own
rules, and may call even a static function, its address escaped.
Type = funcRet, _, _
holds_vecRet, Env0, K
K ≢ 'a vector'
→
sema_errorLoc, 'a function returning ~w, or an aggregate holding one, is not supported', K
else
true
catchoncefunction_Loc, Name, Type, DclType, Body, Env0, Env, Func, badWhy, sema_errorLoc, '~w uses a type nanofil does not support (~w)', Name, Why
1382function_Loc, Name, funcRetType, ParamTypes0, Variadic, DclType, Body, Env0, Env, funcSym, Linkage, RetClass, Params, Blocks, contextSym, Records ←
lookup_ordinaryEnv0, Name, fn_, Sym, Linkage
ParamTypes0 ≡ unspecified
→
ParamTypes = []
else
ParamTypes = ParamTypes0
zreturn_bodyBody, Env0
→
RetType ≡ void
→
RetClass = data
else
sema_errorLoc, 'zreturn in a function that does not return void is not supported', []
else
ret_classLoc, RetType, Env0, RetClass
param_namesDclType, Names
param_minsDclType, Env0, Mins
bind_ordinaryenclosingfunction, Name, Env0, FuncEnv6.4.2.2p1
reflect_paramsDclType, ParamTypes, FuncEnv, ParamEnv0
label_valuesBody, ParamEnv0, ParamEnv
Ctx = ctxName, RetType, none, none, Loc
Lower = lowered_bodyLoc, RetType, Variadic, Names, Mins, ParamTypes, Body, Ctx
callLower, ParamEnv, SretParams−Params0, State
appendSretParams, Params0, Params
State = stEnvEnd, none, [], RevBlocks0, _, _, _, buildRevAllocs, _, _, RevRecords, _
env_scopeEnv0, Scope, _
env_restoreScope, EnvEnd, Env
RetClass ≡ data
→
maplistdata_return, RevBlocks0, RevBlocks
else
RevBlocks = RevBlocks0
reverseRevBlocks, blockstart, [], Insts0, Jump|Rest
reverseRevAllocs, Allocs
appendAllocs, Insts0, Insts
maplistcontext_block, blockstart, [], Insts, Jump|Rest, Blocks, Sites0
appendSites0, Sites
reverseRevRecords, RawRecords
excludeis_hint, RawRecords, Origins
findallT−H, memberhintT, H, RawRecords, HintPairs
list_to_assocHintPairs, Hints
findallS−Os, membersiteS, Os, Sites, SitePairs
list_to_assocSitePairs, SiteMap
findallvalueT, Hint, Os,
memberparam_, tmpT, Params
Os = 0
or
memberblockL, Phis, Is, _, Blocks
memberphitmpT, _, _, Phis
get_assocphiT, SiteMap, Os
or
nth0I, Is, Inst
inst_defInst, T
get_associnstL, I, SiteMap, Os
get_assocT, Hints, H
→
Hint = H
else
Hint = none
,
Values
appendOrigins, Values, Sites, Records
1428is_hinthint_, _

Fil-C's zargs() is the function's entry argument snapshot, the one va_start's cursor points into, at offset 0; zreturn(P) returns the words P points at as the call's result. A function that calls zreturn has the IR return class data (ir.pl), which needs the generic entry, and its other returns return no words, a null P (docs/zsys.md).

1436zreturn_bodyBody, Env ←
lookup_ordinaryEnv, zreturn, fn_, zreturn, _
body_identifierBody, zreturn
!
1440data_returnblockL, Ps, Is, fromOs, ret, blockL, Ps, Is, fromOs, retconst0 ←
!
1441data_returnB, B
lowered_body...

(+ParamEnv, -Params, -State): the function's body lowered from ParamEnv.

1445lowered_bodyLoc, RetType, Variadic, Names, Mins, ParamTypes, Body, Ctx, ParamEnv, SretParams−Params0, State ←
source_locationLoc, SourceLoc
empty_assocEmpty
State0 = stParamEnv, curstart, [], [], [], 0, Labels, [], build[], 0, 1, origin0, SourceLoc, statementfunction, [], indexEmpty, Empty, SourceLoc
empty_assocLabels
phrase
sret_paramRetType, SretParams
paramsNames, Mins, ParamTypes, Ctx, Params00
lengthSretParams, NSret
pad_paramsParams00, NSret, Params0
variadic_entryVariadic, RetType−ParamTypes, SretParams, Params0
vm_paramsNames, ParamTypes, Ctx
stmtBody, Ctx
fall_offRetType
,
State0, State
1466context_blockblockL, Ps0, Is0, fromOs, J, blockL, Ps, Is, J, Rs ←
maplistcontext_phi, Ps0, Ps, PRs
context_instsIs0, L, 0, Is, IRs
appendPRs, IRs, sitejumpL, Os, Rs
1470context_phifromOs, P, P, sitephiT, Os ←
P = phitmpT, _, _
1471context_insts[], _, _, [], []
1472context_instsfromOs, I|Is0, L, N, I|Is, siteinstL, N, Os|Rs ←
N1 is N + 1
context_instsIs0, L, N1, Is, Rs
variadic_entry+Variadic, +Types, +Sret, +Params//

a variadic function takes the entry snapshot, a cursor past its fixed words. So does a function with a byval parameter, one whose parameters or result clang passes outside the fast signature (generic_only_type/2), or one of more than 16 words, which has no fast signature, unbound: the snapshot makes it generic-only, as clang makes it, and the packet it promotes to the heap holds the byval words, each pointer word with its capability.

1482variadic_entryvariadic, _, Sret, Params ⟶
appendSret, Params, All
lengthAll, N
Off is 8 × N
bindbuiltinva_args, tmpva_args
emitlettmpva_args, p, va_args, constOff
1486variadic_entryfixed, RetType−Types, Sret, Params ⟶
envEnv
memberType, Types
or
memtypeRetType
float_pair_typeRetType, Env
or
x87_typeRetType, Env
or
appendSret, Params, All
lengthAll, NW
NW > 16
→
appendSret, Params, All
lengthAll, N
Off is 8 × N
emitlettmpva_args, p, va_args, constOff
else
[]
ret_class+Loc, +RetType, +Env, -Class

Class is the IR return class of a function returning RetType.

1500ret_class_, void, _, void ←
!
1501ret_class_, ldouble, _, tuplel, l ←
!
1502ret_classLoc, Type, _, _ ←
complex_typeType, ldouble
!
sema_errorLoc, 'returning _Complex long double is not supported yet', []
1505ret_class_, Type, _, Class ←
type_classType, Class
!
1507ret_classLoc, Type, Env, Class ←
!
ret_shapeType, Env, Loc, Shape
Shape = wordseb_, _, K0
→
Class = K0
Shape = wordseb_, _, K1, eb_, _, K2
→
Class = tupleK1, K2
else
Class = void
1515ret_classLoc, Type, _, _ ←
sema_errorLoc, 'returning ~w is not supported yet', Type
sret_param+RetType, -Params//

Params are the hidden parameters of a function returning RetType. A struct over 16 bytes is returned through a pointer the caller passes first, as clang does; the function itself returns nothing.

1522sret_paramRetType, Params ⟶
envEnv
memtypeRetType
ret_shapeRetType, Env, unknown, sret
→
bind'%sret', sretP
Params = paramp, P
else
Params = []
param_names+DclType, -Names

Names are the parameter names, each some(Name) or none, from the function's own declarator: the outermost func, since declarator types are built outermost first.

1534param_namesfunc_, unspecified, [] ←
!
1535param_namesfunc_, paramsParams, _, Names ←
findallName, memberparam_, dclName, _, _, Params, Names
param_mins+DclType, +Env, -Mins

each parameter's minimum alignment, its typedef's (inherited_alignment/4) or 1, as clang lays it out.

1540param_minsfunc_, unspecified, _, [] ←
!
1541param_minsfunc_, paramsParams, _, Env, Mins ←
findallMin,
memberparamSpecs, dcl_, Syntax, _, Params
Syntax ≡ base
inherited_alignmentSpecs, base, Env, alignMin
→
true
else
Min = 1
,
Mins
params+Names, +Mins, +Types, +Ctx, -Params//

Params are the IR parameters, and each named one is stored into a local. A struct or union comes in one word per eightbyte.

1551params[], _, [], _, [] ⟶
[]
1552paramsName|Names, Min|Mins, Type|Types, Ctx, Params ⟶
!
envEnv
ctx_locCtx, Loc
arg_wordsType, Env, Loc, Ebs
fresh_paramsEbs, Params0, Values
su_param_localName, Type, Min, Ebs, Values
paramsNames, Mins, Types, Ctx, Params1
pad16_markType, Env, Params0, Params2
appendParams2, Params1, Params
1561paramsName|Names, Min|Mins, Type|Types, Ctx, paramClass, P|Params ⟶
ctx_locCtx, Loc
supportedType, Loc
type_classType, Class
→
true
else
sema_errorLoc, 'parameter of type ~w is not supported yet', Type
param_localName, Type, Min, P
paramsNames, Mins, Types, Ctx, Params
su_param_local+Name, +Type, +Min, +Ebs, +Values//

and param_local(+Name, +Type, +Min, +P)//: a named parameter of Type, which came in as the words Values (eightbytes Ebs) or the temporary P, is stored into a local it is bound to; an unnamed one is dropped.

1575su_param_localnone, _, _, _, _ ⟶
[]
1576su_param_localsomeName, Type, Min, Ebs, Values ⟶
local_slotType, Min, Addr
bindName, varAddr, Type
name_valuesValues, Addr, piece
store_wordsEbs, Values, Addr
1582param_localnone, _, _, _ ⟶
[]
1583param_localsomeName, Type, Min, P ⟶
local_slotType, Min, Addr
bindName, varAddr, Type
name_valueP, Addr, value
store_typedType, P, Addr
vm_params+Names, +Types, +Ctx//

on entry, the size expressions of each variably modified parameter are evaluated, in order, after every parameter is bound (C11 6.9.1p10); the parameter's binding then has the evaluated type.

1593vm_params[], [], _ ⟶
[]
1594vm_paramsName|Names, Type0|Types, Ctx ⟶
Name = someN
vm_typeType0
→
envEnv
lookup_ordinaryEnv, N, varAddr, _
realizedType0, Type, Ctx
envEnv1
bind_ordinaryN, varAddr, Type, Env1, Env2
else
[]
vm_paramsNames, Types, Ctx
fresh_params+Ebs, -Params, -Values//

one fresh parameter per eightbyte; Values are their temporaries.

1606fresh_params[], [], [] ⟶
[]
1607fresh_paramseb_, _, Class|Ebs, paramClass, P|Params, P|Values ⟶
fresh_paramsEbs, Params, Values
fall_off+RetType//

end the body; falling off the end returns as a bare return would.

1613fall_offRetType ⟶
curCur
cur_fall_offCur, RetType
cur_fall_off+Cur, +RetType//

the end of the body when Cur is the current block: nothing after a jump, else a bare return.

1619cur_fall_offnone, _ ⟶
[]
1620cur_fall_offcur_, _, RetType ⟶
zero_retRetType
zero_ret+RetType//

return without a value (zero, if the function has a result).

1624zero_retRetType ⟶
envEnv
RetType ≡ void
→
termret
RetType ≡ ldouble
→
termretsconst0, const0
memtypeRetType
ret_shapeRetType, Env, unknown, Shape
→
Shape = words_, _
→
termretsconst0, const0
Shape = words_
→
termretconst0
else
termret
else
termretconst0

State primitives

state-S//

S is the state. state(-S0, +S)//: the state is S0 and becomes S. The semicontext of the one-element state list.

1640stateS, S ⟶
S
1642stateS0, S, S ⟶
S0

Metadata has its own counter; it never consumes a temporary or label.

1645build_stateB0, B ⟶
statestE, C, Is, Bs, N, Ls, Cs, B0, stE, C, Is, Bs, N, Ls, Cs, B
1647active_originsOs ⟶
build_statebuildA, Os, N, R, X, buildA, Os, N, R, X
1648metadataIndex ⟶
build_statebuildA, Os, N, R, Index, buildA, Os, N, R, Index
1649recordR ⟶
build_statebuildA, Os, N, Rs, X0, buildA, Os, N, R|Rs, X
1653indexed_recordbindingId, Name, T, Loc, indexB0, H, O, indexB, H, O ←
!
put_assocId, B0, bindingId, Name, T, Loc, B
1655indexed_recordhintT, Hint, indexB, H0, O, indexB, H, O ←
!
put_assocT, H0, Hint, H
1657indexed_recordorigin_, _, _, _, X, X

The index's third part is the location of the active origin, the only one a binding asks for; an assoc of every origin's (half a million in SQLite) was a sixth of lowering. enter_origin//3 gives the parent's origins and location, which restore_origin//1 makes active again.

1663enter_originLoc0, Description, savedParent, ParentLoc ⟶
build_statebuildA, Parent, N, Rs, indexB, H, ParentLoc, buildA, N, Next, Rs, indexB, H, Loc
Next is N + 1
recordoriginN, Loc, Description, Parent
1668restore_originsavedOs, Loc ⟶
build_statebuildA, _, N, Rs, indexB, H, _, buildA, Os, N, Rs, indexB, H, Loc
1670source_locationF:L, atF, L ←
!
1671source_location_, none
1673binding_contextName, vartmpB, _ ⟶
!
envEnv
metadataindex_, _, Loc
expression_typeidName, Env, T
recordbindingB, Name, T, Loc
name_valuetmpB, tmpB, address
1677binding_contextName, gvar_, Sym, _, _ ⟶
!
envEnv
metadataindexBindings, _, _
get_assocglobalSym, Bindings, _
→
[]
else
expression_typeidName, Env, T
recordbindingglobalSym, Name, T, none
1682binding_context_, _ ⟶
[]
1684name_values[], _, _ ⟶
[]
1685name_valuesV|Vs, B, Role ⟶
name_valueV, B, Role
name_valuesVs, B, Role
1686name_valuetmpT, tmpB, Role ⟶
!
metadataindexBindings, Hints, _
address_bindingB, Bindings, Hints, Id
¬get_assocT, Hints, _
→
recordhintT, namedId, Role
else
[]
1691name_value_, _, _ ⟶
[]
1693address_bindingB, Bindings, Hints, Id ←
get_assocB, Bindings, _
→
Id = B
else
get_assocB, Hints, namedId, address
1696stored_namestoreW, V, Addr ⟶
!
access_namestoreW, V, Addr
1697stored_nameletV, K, loadOp, Addr ⟶
!
access_nameloadK, Op, V, Addr
1698stored_name_ ⟶
[]

A zero-offset field or partial access is not the whole C object.

1701access_nameAccess, V, tmpA ⟶
!
metadataindexBs, Hs, _
address_bindingA, Bs, Hs, B
get_assocB, Bs, binding_, _, T, _
scalar_accessAccess, U
→
name_valueV, tmpA, value
else
[]
1707access_name_, _, _ ⟶
[]
1708scalar_accessstoreW, T ←
1709scalar_accessloadK, Op, T ←
load_opT, K, Op
1711typed_originX, Ctx, Parent ⟶
envEnv
X =.. Category, Type, Node
ctx_locCtx, Loc
shallow_nodeNode, Env, Form
groundType
→
Description = typedCategory, Type, Form
else
Description = generatedlower, Form
enter_originLoc, Description, Parent

Keep the operation and resolved binding, not recursive typed trees.

1720shallow_nodeidName, Env, bindingB ←
lookup_ordinaryEnv, Name, vartmpB, _
!
1722shallow_nodeidName, Env, bindingglobalSym ←
lookup_ordinaryEnv, Name, gvar_, Sym, _, _
!
1724shallow_nodeNode, _, Form ←
Node =.. Op|Args
includeatomic, Args, Atoms
Form =.. Op|Atoms
1727result_nameval_, loadobj_, idName, vV, _ ⟶
!
envEnv
lookup_ordinaryEnv, Name, varAddr, _
→
name_valueV, Addr, value
else
[]
1730result_name_, _ ⟶
[]

Readers (cur//1, env//1, cases//1) leave the state as it is; set_*//1 replace one field; fresh//1 and label//1 take the next number.

1735curCur ⟶
statest_, Cur, _, _, _, _, _, _
env-Env//det

Env is the lowering state's semantic environment (c_types.pl's e/4); set_env//1 replaces it. The state is st(Env, Cur, Insts, Blocks, N, Labels, Cases, Build), the header comment's table.

1742envEnv ⟶
statestEnv, _, _, _, _, _, _, _
1744set_envEnv ⟶
statest_, Cur, Insts, Blocks, N, Labels, Cases, Allocs, stEnv, Cur, Insts, Blocks, N, Labels, Cases, Allocs
1748bindName, Value ⟶
envEnv0
bind_ordinaryName, Value, Env0, Env
binding_contextName, Value
1754freshtmpTemp ⟶
statestEnv, Cur, Insts, Blocks, N0, Labels, Cases, Allocs, stEnv, Cur, Insts, Blocks, N, Labels, Cases, Allocs
N is N0 + 1
number_codesN0, Cs
atom_codesTemp, 0'v|Cs
1760labelLabel ⟶
statestEnv, Cur, Insts, Blocks, N0, Labels, Cases, Allocs, stEnv, Cur, Insts, Blocks, N, Labels, Cases, Allocs
N is N0 + 1
number_codesN0, Cs
atom_codesLabel, 0'L|Cs

ensure_live//: make sure there is a current block, opening an unreachable one after a jump.

1768ensure_live ⟶
cur_live+Cur//

the current block is Cur, or a new one if Cur is none.

1774cur_livenone ⟶
labelLabel
set_curcurLabel, []
1775cur_livecur_, _ ⟶
[]
1777set_curCur ⟶
statestEnv, _, Insts, Blocks, N, Labels, Cases, Allocs, stEnv, Cur, Insts, Blocks, N, Labels, Cases, Allocs
emit+Inst//

append Inst with its active typed origin. These private wrappers disappear before c_module/2 returns; they are never IR.

1785push_instInst ⟶
statestEnv, Cur, Insts, Blocks, N, Labels, Cases, Allocs, stEnv, Cur, Inst|Insts, Blocks, N, Labels, Cases, Allocs
term+Jump//

end the current block with Jump.

1792close_blockJump ⟶
statestEnv, curLabel, Phis, Insts, Blocks, N, Labels, Cases, Allocs, stEnv, none, [], blockLabel, Phis, BlockInsts, fromOs, Jump|Blocks, N, Labels, Cases, Allocs
reverseInsts, BlockInsts
start+Label//

begin block Label, falling through from a live current block.

1801startLabel ⟶
curCur
cur_fallthroughCur, Label
set_curcurLabel, []
cur_fallthrough+Cur, +Label//

a live current block Cur ends with a jump to Label; after a jump (none) there is nothing to end.

1808cur_fallthroughnone, _ ⟶
[]
1809cur_fallthroughcur_, _, Label ⟶
termgotoLabel
start_phi+Label, +Phis//

begin block Label, reached only by jumps.

1812start_phiLabel, Phis ⟶
maplistfrom_originsOs, Phis, Located
set_curcurLabel, Located
1816from_originsOs, I, fromOs, I
here-Label//

Label is the current block's label.

1819hereLabel ⟶
curcurLabel, _
1821add_allocInst ⟶
build_statebuildAs, Os, N, Rs, X, buildfromOs, Inst|As, Os, N, Rs, X
1824casesCases ⟶
statest_, _, _, _, _, _, Cases, _
1826set_casesCases ⟶
statestEnv, Cur, Insts, Blocks, N, Labels, _, Allocs, stEnv, Cur, Insts, Blocks, N, Labels, Cases, Allocs
goto_label+Name, -Label//

Label is the IR label of C label Name, made on first use.

1832goto_labelName, Label ⟶
statestEnv, Cur, Insts, Blocks, N0, Labels0, Cases, Allocs, stEnv, Cur, Insts, Blocks, N, Labels, Cases, Allocs
get_assocName, Labels0, Label
→
N = N0
Labels = Labels0
else
N is N0 + 1
formatatomLabel, 'L~w', N0
put_assocName, Labels0, Label, Labels
let+Class, +Op, +Args, -Dest//

emit an instruction into Dest, a fresh temporary.

1844letClass, Op, Args, Dest ⟶
freshDest
emitletDest, Class, Op, Args
vla_slot+Type, +Min, +Ctx, -Addr//

Addr points at a new object of the variable length array Type, allocated where its declaration is reached, with its length as evaluated then (C11 6.2.4p7, 6.7.6.2p5), aligned to Min at least.

1852vla_slotType, Min, Ctx, Addr ⟶
extentType, Ctx, Size
envEnv
element_typeType, Elem
catchtype_size_alignElem, Env, _, Align0, badWhy, thrownanofil_errorWhy
Align is max4, maxMin, Align0
letp, allocAlign, Size, Addr
1860qualified_withqualifiedQs, _, Q ←
memberchkQ, Qs
atomic_or_volatile+T

an object of qualified type T is loaded atomically if it is a pointer: _Atomic, or volatile as Fil-C has it.

1864atomic_or_volatileT ←
1865atomic_or_volatileT ←
qualified_withT, (volatile)
element_type+Type, -Elem

Elem is the innermost element type of array Type, or Type itself.

1869element_typearrayT, _, E ←
1870element_typeT, T
local_slot+Type, -Addr//

Addr points at a new local object of Type, an alloc in the entry block; local_slot(+Type, +Min, -Addr)// aligns it to Min at least.

1875local_slotType, Addr ⟶
local_slotType, 1, Addr
1876local_slotType, Min, Addr ⟶
envEnv
catchtype_size_alignType, Env, Size0, Align0, badWhy, thrownanofil_errorWhy
Align is max4, maxAlign0, Min
Size is maxSize0, 1
freshAddr
add_allocletAddr, p, allocAlign, constSize

Context

1887ctx_locctx_, _, _, _, Loc, Loc
1889ctx_atctxFunc, Ret, Break, Cont, _, Loc, ctxFunc, Ret, Break, Cont, Loc
loop_ctx+Ctx0, +LBreak, +LCont, -Ctx

, switch_ctx(+Ctx0, +LBreak, -Ctx): Ctx is Ctx0 inside a loop or switch whose break jumps to LBreak and continue to LCont.

1894loop_ctxctxFunc, Ret, _, _, Loc, LBreak, LCont, ctxFunc, Ret, someLBreak, someLCont, Loc
1897switch_ctxctxFunc, Ret, _, Cont, Loc, LBreak, ctxFunc, Ret, someLBreak, Cont, Loc

Statements

A lexical prepass gives each statement-expression boundary its AST path. Gotos may stay inside or leave boundaries, never enter one (including a sibling). Case dispatch must originate inside the same boundary. Keeping this separate from emission also checks forward gotos and unevaluated code. It also refuses a label defined twice in the function (6.8.1p3), as clang does, at the second definition (GNU local labels have names of their own by then, c_parse's local_labels//1).

1909statement_expression_jumpsBody, Loc ←
phrasejump_regionsBody, [], [], [], Loc, Events
findallName−LLoc, memberlabel_regionName, _, LLoc, Events, Defs
append_, Name−_|Later, Defs
memberchkName−Again, Later
→
sema_errorAgain, 'redefinition of label ~w', Name
else
true
maplistcheck_jump_regionEvents, Events
1918check_jump_regionEvents, jumpName, From, Loc ←
!
memberlabel_regionName, To, _, Events
¬append_, To, From
→
sema_errorLoc, 'goto into a statement expression is not allowed', []
else
true
1924check_jump_region_, _

Visit leaves before matching compound shapes: on Trealla an atom built by atom_chars/2 (e.g. the field name "label") can unify with label/2. Recursing into its unbound "body" would then grow regions indefinitely.

1929jump_regionsTerm, _, _, _, _ ⟶
atomicTerm
!
1930jump_regionsatLoc, Term, Path, Regions, Switch, _ ⟶
!
jump_regionsTerm, Path, Regions, Switch, Loc
1932jump_regionsstmtexprBlock, Path, Regions, Switch, Loc ⟶
!
jump_regionsBlock, 0|Path, Path|Regions, Switch, Loc
1934jump_regionslabelName, Body, Path, Regions, Switch, Loc ⟶
!
label_regionName, Regions, Loc
jump_regionsBody, 0|Path, Regions, Switch, Loc
1937jump_regionsgotoName, _, Regions, _, Loc ⟶
!
jumpName, Regions, Loc
1939jump_regionsswitchExpr, Body, Path, Regions, Switch, Loc ⟶
!
jump_regionsExpr, 0|Path, Regions, Switch, Loc
jump_regionsBody, 1|Path, Regions, Regions, Loc
1942jump_regionscaseExpr, Body, Path, Regions, Switch, Loc ⟶
!
check_case_regionRegions, Switch, Loc
jump_regionsExpr, 0|Path, Regions, Switch, Loc
jump_regionsBody, 1|Path, Regions, Switch, Loc
1946jump_regionsdefaultBody, Path, Regions, Switch, Loc ⟶
!
check_case_regionRegions, Switch, Loc
jump_regionsBody, 0|Path, Regions, Switch, Loc
1949jump_regionsTerm, Path, Regions, Switch, Loc ⟶
compoundTerm
Term =.. _|Args
→
jump_region_argsArgs, 0, Path, Regions, Switch, Loc
else
[]
1955jump_region_args[], _, _, _, _, _ ⟶
[]
1956jump_region_argsArg|Args, I, Path, Regions, Switch, Loc ⟶
jump_regionsArg, I|Path, Regions, Switch, Loc
J is I + 1
jump_region_argsArgs, J, Path, Regions, Switch, Loc
1961check_case_regionRegions, Switch, Loc ←
Regions ≡ Switch
→
true
else
sema_errorLoc, 'switch dispatch into a statement expression is not allowed', []
stmt+Stmt, +Ctx//det

Lowers statement Stmt, an at(Loc, S) term, in context Ctx; stmt_//2 lowers S itself, and an origin record ties its instructions to Loc. Lowering a statement is deterministic: committing to it keeps the choicepoints of a large function (sqlite3WhereCodeOneLoopStart) from holding every earlier state alive.

1973stmtStmt, Ctx, S0, S ←
oncestmt_atStmt, Ctx, S0, S
1975stmt_atatLoc, Stmt, Ctx0 ⟶
ctx_atCtx0, Loc, Ctx
functorStmt, Kind, _
enter_originLoc, statementKind, Parent
stmt_Stmt, Ctx

Declaration and loop helpers update the enclosing source context.

1981locatedKind, Loc ⟶
enter_originLoc, statementKind, _
loop_located+Ctx//

a loop's condition or step, in a block of its own after the body, is at the loop statement's location.

1985loop_locatedCtx ⟶
ctx_locCtx, Loc
locatedloop, Loc
1987stmt_blockItems, Ctx ⟶
envEnv
cleanup_keysEnv, Outer
itemsItems, Ctx
1992stmt_null, _ ⟶
[]

An asm whose template is blank, as filcc's handleInlineAsm counts it, emits no instruction, so it leaves every operand as it was; its operands are only evaluated (asm_outputs//2, asm_inputs//2): "+r" reads and writes back the same value, a pointer's capability included, "=r" writes an unspecified value, which leaving the object unchanged is, unless an input of its type is tied to it ("0"), whose value it then receives (ell's DO_NOT_OPTIMIZE), memory-class operands of any complete type are neither read nor written, and inputs are evaluated, an "r" pointer input too (libgcrypt's wipememory, asm volatile("\\n" :: "r"(p) : "memory"); filcc passes the pointer's address and the blank asm uses nothing). These are libgcrypt's, xxhash's and elfutils' optimization barriers. "x" (an SSE register) and "t" or "f" (the x87 stack) on any floating type are register operands like "r", as glibc's math_opt_barrier and math_force_eval use them. A "memory" or "cc" clobber needs nothing: no pass of nanofil keeps a value of escaped memory across an instruction (promote takes only locals whose address does not escape, copy and the backend's caches only temporaries), so every access stays in program order as across a call. "=r" on a pointer, another constraint or clobber, and asm goto are refused.

2013stmt_asm_emptyOuts, Ins, Clobbers, Goto, Ctx ⟶
!
ctx_locCtx, Loc
Goto ≡ goto
→
sema_errorLoc, 'asm goto is not supported', []
else
true
forallmemberC, Clobbers,
memberchkC, memory, cc
→
true
else
sema_errorLoc, 'an asm clobber other than "memory" or "cc" is not supported (~w)', C
asm_outputsOuts, Ctx, Places
asm_inputsIns, Outs−Places, Ctx, Ties

Any other asm runs instructions nanofil cannot check. filcc's handleInlineAsm compiles some of them to a run-time error and passes the rest through after validateSafeInlineAsm; nanofil passes none through. Where filcc certainly refuses, nanofil calls zsafety_error with filcc's message up to its colon (filcc follows it with the call's LLVM IR, whose attribute and metadata numbers nanofil has no counterpart for; it uses the template), so a program that never runs the asm, such as GLib's G_BREAKPOINT behind a debug flag, behaves as under filcc. A fixed-register instruction (cpuid, xgetbv, rdtsc, rdtscp, lfence, sfence, pause, mfence) is lowered to asm_fixed or fence (docs/asm-fixed.md); everywhere else, where filcc may accept the asm, nanofil refuses it at compile time (asm_runtime_reason/6).

2039stmt_asmTemplate, Outs, Ins, Clobbers, Goto, Ctx ⟶
!
envEnv0
ctx_locCtx, Loc
maplistasm_operandout, Env0, Loc, Outs, OutKs
maplistasm_operandin, Env0, Loc, Ins, InKs
atom_codesT, Template
asm_runtime_reasonGoto, OutKs, InKs, Clobbers, Template, Reason
→
formatcodesMsg, 'cannot handle inline asm (~w): ~w', Reason, T
new_literalMsg, Sym, Env0, Env
foldlasm_operand_effectsCtx, Outs, OutKs
foldlasm_operand_effectsCtx, Ins, InKs
letp, copy, globalSym, Addr
emitcallvoid, globalzsafety_error, argp, Addr
Goto ≢ goto
asm_fixed_formTemplate, Outs, Ins, OutKs, InKs, Clobbers, Env0, M, OutRegs, InRegs
→
asm_fixed_loweredM, Outs, OutRegs, Ins, InRegs, Ctx
else
sema_errorLoc, 'inline asm "~w" is not supported: nanofil cannot check its instructions', T

An asm with a memory operand passes a pointer to code that nothing checks. clang's Fil-C compiles it to a run-time error, which a program reaches only if it runs the asm (gnulib's x87 control-word accesses in fpucw.h, under long double printf); nanofil calls zsafety_error alike (docs/abi-findings.md, *Inline asm*). Any other asm is refused.

2064stmt_unsupported_asmmemoryTemplate, _ ⟶
!
atom_codesT, Template
formatcodesMsg, 'cannot handle inline asm (inline assembly with pointer argument is not supported): ~w', T
envEnv0
new_literalMsg, Sym, Env0, Env
letp, copy, globalSym, Addr
emitcallvoid, globalzsafety_error, argp, Addr
2072stmt_unsupported_asm_, Ctx ⟶
ctx_locCtx, Loc
sema_errorLoc, 'inline asm is not supported, except the empty barrier asm volatile("" ::: "memory")', []
2075stmt_exprExpr, Ctx ⟶
judged_rvalExpr, Ctx, _
2076stmt_ifCond, Then, Else, Ctx ⟶
judgedCond, Ctx
labelLThen
labelLElse
labelLJoin
cond_jumpCond, Ctx, LThen, LElse
startLThen
stmtThen, Ctx
termgotoLJoin
startLElse
Else ≡ none
→
[]
else
stmtElse, Ctx
startLJoin
2083stmt_whileCond, Body, Ctx ⟶
labelLCond
labelLBody
labelLEnd
cleanup_targetsLEnd, LCond
startLCond
judgedCond, Ctx
cond_jumpCond, Ctx, LBody, LEnd
startLBody
loop_ctxCtx, LEnd, LCond, BodyCtx
stmtBody, BodyCtx
termgotoLCond
startLEnd
2092stmt_doBody, Cond, Ctx ⟶
labelLBody
labelLCond
labelLEnd
cleanup_targetsLEnd, LCond
startLBody
loop_ctxCtx, LEnd, LCond, BodyCtx
stmtBody, BodyCtx
startLCond
judgedCond, Ctx
cond_jumpCond, Ctx, LBody, LEnd
startLEnd
2100stmt_forInit, Cond, Step, Body, Ctx ⟶
envEnv0
cleanup_keysEnv0, Outer
Init ≡ none
→
[]
Init = exprInitExpr
→
judged_rvalInitExpr, Ctx, _
else
itemInit, Ctx
labelLCond
labelLBody
labelLStep
labelLEnd
cleanup_targetsLEnd, LStep
startLCond
Cond ≡ none
→
termgotoLBody
else
judgedCond, Ctx
cond_jumpCond, Ctx, LBody, LEnd
startLBody
loop_ctxCtx, LEnd, LStep, BodyCtx
stmtBody, BodyCtx
startLStep
Step ≡ none
→
[]
else
judged_rvalStep, Ctx, _
termgotoLCond
startLEnd
2121stmt_switchExpr, Body, Ctx ⟶
judged_rvalExpr, Ctx, Value0
Value0 = v_, Type0
ctx_locCtx, Loc
→
else
sema_errorLoc, 'switch on a non-integer', []
convertValue0, Type, vValue, _
labelLDispatch
labelLEnd
termgotoLDispatch
casesOuter
switch_ctxCtx, LEnd, BodyCtx
envEnv0
vm_scopesEnv0, Scopes
lookup_ordinaryEnv0, 'vm.switch', OuterScopes
→
true
else
OuterScopes = []
bind_ordinary'vm.switch', Scopes, Env0, Env1
stmtBody, BodyCtx
envEnv2
bind_ordinary'vm.switch', OuterScopes, Env2, Env3
termgotoLEnd
casesCases0
reverseCases0, Cases
distinct_casesCases, Type, Loc
startLDispatch
dispatchCases, Value, Type, LEnd
startLEnd
2144stmt_caseExpr, Body, Ctx ⟶
envEnv
ctx_locCtx, Loc
case_valuesExpr, Env, Loc, Values
labelLabel
startLabel
casesCases0
Values = rangeL, H
→
New = rangeL, H, Label
else
findallcaseV, Label, memberV, Values, New0
reverseNew0, New
appendNew, Cases0, Cases
stmtBody, Ctx
2156stmt_defaultBody, Ctx ⟶
labelLabel
startLabel
casesCases
set_casesdefaultLabel|Cases
stmtBody, Ctx
2161stmt_break, ctx_, _, Break, _, Loc ⟶
target_jumpBreak, Loc, 'break outside a loop or switch'
2164stmt_continue, ctx_, _, _, Cont, Loc ⟶
target_jumpCont, Loc, 'continue outside a loop'
2167stmt_returnnone, Ctx ⟶
Ctx = ctx_, RetType, _, _, _
envEnv
zero_retRetType
2171stmt_returnExpr, Ctx ⟶
Expr ≢ none
Ctx = ctx_, RetType, _, _, Loc
judged_rvalExpr, Ctx, Value0
envEnv
returned_cleanup_varExpr, RetType, Ks, Loc
RetType ≡ void
→
abi_memoryRetType
→
assign_convertExpr, Value0, RetType, Loc, vV, _
abi_sourceRetType, V, Addr0
return_cleanupsRetType, Addr0, Addr
return_structRetType, Addr, Loc
else
assign_convertExpr, Value0, RetType, Loc, vValue, _
termretValue
2186stmt_gotoName, Ctx ⟶
ctx_locCtx, Loc
vm_gotoName, Loc
goto_labelName, Label
goto_cleanupsName, Label, Target
termgotoTarget

GNU goto *E: E is one of the function's label values (c_types' label_value/3), the label's number. Every goto * of the function stores it in one local and jumps to the function's single dispatch block (computed_dispatch//0), so an interpreter's many gotos share one search.

2197stmt_computed_gotoE, Ctx ⟶
rvalE, Ctx, V0
ctx_locCtx, Loc
V0 = v_, ptr_
→
[]
else
sema_errorLoc, 'goto * of a non-pointer', []
convertV0, ulong, vX, _
envEnv
Ks ≡ []
→
[]
else
sema_errorLoc, 'goto * in the scope of a variable with a cleanup attribute is not supported', []
Each address-taken label is a target of this goto, from the
scopes open here (alsa-lib's goto * among labels inside a VLA's
scope), not from the dispatch block's.
lookup_ordinaryEnv, 'labels.taken', Targets
→
true
else
Targets = []
foldlvm_goto_fromLoc, Targets
statest_, _, _, _, _, Labels0, _, _
get_assoccomputedgoto, Labels0, dispatchLDispatch, Slot, _
→
[]
else
labelLDispatch
local_slotulong, Slot
statestE1, C1, I1, B1, N1, Labels1, Cs1, A1, stE1, C1, I1, B1, N1, Labels2, Cs1, A1
put_assoccomputedgoto, Labels1, dispatchLDispatch, Slot, Loc, Labels2
store_typedulong, X, Slot
termgotoLDispatch
2222stmt_labelName, Body, Ctx ⟶
vm_labellabelName
goto_labelName, Label
label_cleanupsName, Label
startLabel
stmtBody, Ctx
2228stmt_declLoc, Specs, Inits, Ctx ⟶
itemdeclLoc, Specs, Inits, Ctx
asm_operand_effects+Ctx, +Op, +Kind//

evaluate an operand of an asm that traps, as clang does before the trap: an output's or memory operand's lvalue, an input's value.

2234asm_operand_effectsCtx, opC, E, K−C ⟶
K ≡ mem
or
sub_atomC, 0, _, _, =
or
sub_atomC, 0, _, _, +
→
envEnv
ctx_locCtx, Loc
elaborateE, Env, Loc, X
placeX, Ctx, _
else
judged_rvalE, Ctx, _
asm_outputs+Ops, +Ctx, -Places//

, asm_inputs(+Ops, +Outs-Places, +Ctx, -Ties)//: a blank asm's operands (stmt_//2's asm_empty), Places the outputs' lvalues and Ties the T-V-Addr stores of the values of inputs tied to outputs, made after every input is evaluated.

2244asm_outputs[], _, [] ⟶
[]
2245asm_outputsopC, E|Os, Ctx, P|Ps ⟶
ctx_locCtx, Loc
memberchkC, '+r', '=r'
→
Class = reg
→
true
memberchkC, '=m', '+m', '=mr', '+mr', '=rm', '+rm', '=g', '+g'
→
Class = mem
else
sema_errorLoc, 'the asm output constraint "~w" is not supported', C
envEnv
elaborateE, Env, Loc, X
placeX, Ctx, P
P = lv_, T0
asm_output_typeClass, C, T, Env
→
true
Class ≡ mem
→
sema_errorLoc, 'an asm memory operand must have a complete object type', []
else
sema_errorLoc, 'an asm operand must have integer or floating type', []
asm_outputsOs, Ctx, Ps
asm_output_type+Class, +C, +T, +Env

an operand of type T with constraint C, of Class reg or mem, means nothing in a blank asm. Its lvalue is evaluated; nothing is read or written: the asm leaves an object it may read or write unchanged, and "=r" writes an unspecified value, which leaving the object unchanged is. A memory-class operand ("=m", "+m", "=mr", "g") may have any complete object type (but "+g" and "+mr" a scalar, as clang requires), as filcc passes such an asm through; "+r" on a pointer gives the pointer back, capability and all; "=r" on a pointer, whose capability the asm would have to make up, is refused.

2272asm_output_typemem, C, T, Env ←
T ≠ func_, _, _
clang refuses a read-write operand that may be a register
("+g", "+mr") of a type no register holds.
memberchkC, '+mr', '+rm', '+g'
→
or
T = ptr_
else
true
2278asm_output_typereg, _, T, _ ←
2279asm_output_typereg, '+r', ptr_, _
2280asm_output_typefloat, _, T, _ ←
memberchkT, float, double, ldouble
asm_float_reg?C, ?Class

C is an output constraint of a floating register, "x" (SSE) or "t" and "f" (x87), whose operands c_lower takes of any floating type: glibc's barriers choose between "x" and "t" by sizeof, so the branch not taken names a long double "x" too, which clang drops before it allocates registers.

asm_float_reg
2287'=x'float
2288'+x'float
2289'=t'float
2290'+t'float
2291'=f'float
2292'+f'float
2294asm_inputs[], _, _, [] ⟶
[]
2295asm_inputsopC, E|Os, OPs, Ctx, Ties ⟶
ctx_locCtx, Loc
memberchkC, r, g, i, n, m, x, t, f
→
true
atom_numberC, N
integerN
OPs = Outs−Places
nth0N, Outs, opOC, _
memberchkOC, '=r', '=x', '=t', '=f'
nth0N, Places, lvAddr, T
→
true
else
sema_errorLoc, 'the asm input constraint "~w" is not supported', C
envEnv
nonvarAddr
→
Tied to "=r" output N: the asm leaves the input's value in the
output's register, so the output receives it.
judged_rvalE, Ctx, vV, T1
or
U ≡ ldouble
→
true
else
sema_errorLoc, 'an asm input tied to an output must have its integer or floating type', []
Ties = U−V−Addr|Ties1
else
Ties = Ties1
asm_inputC, E, Env, Loc, Ctx
asm_inputsOs, OPs, Ctx, Ties1
2317asm_ties[] ⟶
[]
2318asm_tiesT−V−Addr|Ts ⟶
2320asm_inputC, E, Env, Loc, Ctx ⟶
memberchkC, m, g
→
A memory-class input of any complete object type: evaluated
(an "m" operand as an lvalue), never read.
C ≡ m
→
elaborateE, Env, Loc, X
placeX, Ctx, P
P = lv_, T0
→
true
else
sema_errorLoc, 'an asm "m" operand must be an lvalue', []
else
judged_rvalE, Ctx, v_, T0
T ≠ func_, _, _
→
true
else
sema_errorLoc, 'an asm memory operand must have a complete object type', []
else
asm_reg_inputC, E, Env, Loc, Ctx
2336asm_reg_inputC, E, Env, Loc, Ctx ⟶
memberchkC, i, n
→
catchconst_valueE, Env, _, _, not_constant, fail
→
true
else
sema_errorLoc, 'the asm "~w" operand is not an integer constant', C
else
true
judged_rvalE, Ctx, v_, T0
memberchkC, x, t, f
→
memberchkT, float, double, ldouble
→
true
else
sema_errorLoc, 'an asm "~w" operand must have floating type', C
C ≡ rlibgcrypt's wipememory barrier
T = ptr_
→
true
else
sema_errorLoc, 'an asm operand must have integer or floating type', []
2354asm_value_typeT ←
T ≢ ldouble
asm_operand+Dir, +Env, +Loc, +Op, -Kind

Kind is K-C for an operand with constraint C: K is mem if clang passes its address (an output constraint with m or g, an input "m"), else ptr, value or other by its type after decay, or vector for a GNU vector, which clang passes as a value (fftw's "=x" output) but no nanofil asm form accepts.

2361asm_operandDir, Env, Loc, opC, E, K−C ←
elaborateE, Env, Loc, X
atom_charsC, Cs
Dir ≡ out
memberchkm, Cs
or
memberchkg, Cs
→
K = mem
Dir ≡ (in)
C ≡ m
→
K = mem
memberchkT, ptr_, array_, _, func_, _, _
→
K = ptr
→
K = value
T = vec_, _, _
→
K = vector
else
K = other

asm_fixed_form(+Template, +Outs, +Ins, +OutKs, +InKs, +Clobbers, +Env, -M, -OutRegs, -InRegs): filcc passes the asm through and nanofil runs it as asm_fixed(M, ...) (docs/asm-fixed.md), or as fence for mfence. The template is one nonblank line, split at newlines and semicolons, whose lowercased mnemonic is in ir:asm_fixed_regs/3 or is mfence, with no operands. Every operand is an integer (a pointer operand traps first, asm_runtime_reason/6) in one register: an output "=R" or "+R" ("&" allowed), an input "R" or a digit tied to an "=" output, R one of a, b, c, d, S and D, each register named once on each side. Clobbers are memory, cc or general registers other than sp and bp. filcc's coverage rules hold: each register cpuid, rdtsc or rdtscp writes is an output or a clobber, and xgetbv has its c input and its a and d outputs. An output in a register M does not write must be tied to an input, whose value it gets. OutRegs and InRegs are the outputs' and inputs' registers, in order.

2387asm_fixed_formTemplate, Outs, Ins, OutKs, InKs, Clobbers, Env, M, OutRegs, InRegs ←
split_asm_linesTemplate, Lines0
findallA,
memberL0, Lines0
atom_codesL1, L0
normalize_spaceatomA0, L1
A0 ≢ ''
downcase_atomA0, A
,
M
asm_fixed_regsM, _, Writes
→
true
else
M ≡ mfence
Writes = []
forallmemberK−_, OutKs, K ≡ value
forallmemberK−_, InKs, K ≡ value
forall
memberop_, E, Outs
or
memberop_, E, Ins
,
catchexpression_typeE, Env, T0, _, fail
maplistasm_fixed_output, Outs, OutRegs
lengthOuts, NOut
maplistasm_fixed_inputOuts, OutRegs, NOut, Ins, InRegs
findallR,
nth0I, Outs, opC, _
sub_atomC, 0, 1, _, +
nth0I, OutRegs, R
,
PlusRegs
findallR,
nth0J, InRegs, R0
nth0J, Ins, opD, _
R0 ≡ tied
→
atom_numberD, N
nth0N, OutRegs, R
else
R = R0
,
TiedRegs
appendPlusRegs, TiedRegs, AllIns
is_setOutRegs
is_setAllIns
forallmemberC, Clobbers,
F ≢ sp
F ≢ bp
maplistasm_clobber_family, Clobbers, ClobFs
findallF,
memberR, OutRegs
,
OutFs
appendOutFs, ClobFs, Covered
M ≡ xgetbv
→
memberchkc, AllIns
memberchka, OutRegs
memberchkd, OutRegs
else
forall
memberR, Writes
,
memberchkF, Covered
an output M does not write must receive a tied input's value
forall
nth0I, Outs, opC, _
nth0I, OutRegs, R
¬memberchkR, Writes
,
sub_atomC, 0, 1, _, +
or
nth0J, InRegs, tied
nth0J, Ins, opD, _
atom_numberD, I
2420asm_fixed_outputopC, _, R ←
atom_charsC, P|Cs
memberchkP, =, +
exclude==&, Cs, R
2424asm_fixed_inputOuts, _, NOut, opC, _, R ←
atom_numberC, N
integerN
N ≥ 0
N < NOut
→
nth0N, Outs, opOC, _
sub_atomOC, 0, 1, _, =
R = tied
else
atom_charsC, R
asm_fixed_lowered+M, +Outs, +OutRegs, +Ins, +InRegs, +Ctx//

as clang evaluates an asm: the outputs' lvalues and "+" outputs' values, then the inputs, one tied to a wider output zero-extended to its width; then M on the registers it reads, each the low 32 bits of its input, 0 for one no input names (filcc's is unspecified), an input in a register M does not read being dropped; then each output stored: the 32-bit register M wrote, zero-extended, truncated, or bit 0 for a _Bool (clang's i1 truncation), or, in a register M does not write, its tied input's whole value, converted likewise. An output's type is its place's, a bit-field's included.

2439asm_fixed_loweredM, Outs, OutRegs, Ins, InRegs, Ctx ⟶
asm_fixed_placesOuts, OutRegs, Ctx, Places, PlusVals
asm_fixed_inputsIns, InRegs, OutRegs, Places, Ctx, InVals
appendPlusVals, InVals, Vals0
tied_valuesIns, InRegs, InVals, Ties
M ≡ mfence
→
Reads = []
Writes = []
else
asm_fixed_regsM, Reads, Writes
asm_fixed_readsReads, Vals0, IRIns
findallR,
memberR, Writes
memberchkR, OutRegs
,
Written
asm_fixed_tempsWritten, IROuts
M ≡ mfence
→
emitfence
else
emitasm_fixedM, IRIns, IROuts
asm_fixed_storesOuts, 0, OutRegs, Places, PlusVals, Ties, IROuts, Ctx
2451asm_fixed_places[], [], _, [], [] ⟶
[]
2452asm_fixed_placesopC, E|Os, R|Rs, Ctx, P|Ps, Vals ⟶
envEnv
ctx_locCtx, Loc
elaborateE, Env, Loc, X
placeX, Ctx, P
sub_atomC, 0, 1, _, +
→
place_readP, Ctx, V
Vals = R−V|Vals1
else
Vals = Vals1
asm_fixed_placesOs, Rs, Ctx, Ps, Vals1
2461asm_fixed_inputs[], [], _, _, _, [] ⟶
[]
2462asm_fixed_inputsopC, E|Os, R0|Rs, OutRegs, Places, Ctx, R−V|Vs ⟶
judged_rvalE, Ctx, V0
R0 ≡ tied
→
atom_numberC, N
nth0N, OutRegs, R
nth0N, Places, P
V0 = v_, TI
envEnv
type_size_alignTI, Env, SI, _
type_size_alignTO, Env, SO, _
SI < SO
→
convertV0, UI, V
else
V = V0
else
R = R0
V = V0
asm_fixed_inputsOs, Rs, OutRegs, Places, Ctx, Vs
2477place_typelv_, T0, T ←
2478place_typebits_, T0, _, _, T ←
tied_values+Ins, +InRegs, +InVals, -Ties

Ties are N-V, input value V tied to output N.

2482tied_values[], [], [], []
2483tied_valuesopC, _|Os, R|Rs, _−V|Vs, Ties ←
R ≡ tied
→
atom_numberC, N
Ties = N−V|Ties1
else
Ties = Ties1
tied_valuesOs, Rs, Vs, Ties1
2487asm_fixed_reads[], _, [] ⟶
[]
2488asm_fixed_readsR|Rs, Vals, R−W|Ins ⟶
memberchkR−V, Vals
→
convertV, uint, vW0, _
w_operandV, W0, W
else
W = const0
asm_fixed_readsRs, Vals, Ins
w_operand+V, +W0, -W//

W is W0 as a w operand: an 8-byte value's low 32 bits.

2497w_operand_, constN, constN ⟶
!
2498w_operandv_, T, W0, W ⟶
→
letw, copy, W0, W
else
W = W0
2501asm_fixed_temps[], [] ⟶
[]
2502asm_fixed_tempsR|Rs, R−T|Ts ⟶
2504asm_fixed_stores[], _, [], [], _, _, _, _ ⟶
[]
2505asm_fixed_stores_|Os, I, R|Rs, P|Ps, PlusVals, Ties, IROuts, Ctx ⟶
memberchkR−Tmp, IROuts
→
V0 = vTmp, uint
else
memberchkR−V0, PlusVals
→
true
else
memberchkI−V0, Ties
T ≡ bool
→
binaryand, V0, vconst1, int, Ctx, V1
convertV1, bool, V
else
convertV0, T, V
place_writeP, V, Ctx, _
I1 is I + 1
asm_fixed_storesOs, I1, Rs, Ps, PlusVals, Ties, IROuts, Ctx
asm_runtime_reason+Goto, +OutKs, +InKs, +Clobbers, +Template, -Reason

filcc certainly compiles the asm to the run-time error "cannot handle inline asm (Reason)". handleInlineAsm refuses asm goto, then a pointer result, then a pointer argument (an address for a memory operand among them); validateSafeInlineAsm then checks constraints and clobbers, and refuses the first line, split at newlines and semicolons, whose mnemonic it does not know. The cases are probed against the pinned filcc at -O0 and -O2 (tests/c/prog/asm-refused.c); nanofil claims a mnemonic refusal only for operands and clobbers the probe saw pass validation and the first nonblank line's mnemonic (c_types filcc_refused_mnemonic/1). asm goto with a pointer operand crashes clang's Fil-C, so it is not claimed.

2531asm_runtime_reasongoto, OutKs, InKs, _, _, 'asm goto is not supported' ←
!
appendOutKs, InKs, Ks
¬
memberK−_, Ks
memberchkK, mem, ptr, other, vector
2534asm_runtime_reason_, OutKs, _, _, _, 'inline assembly with pointer return type is not supported' ←
memberchkptr−_, OutKs
!
¬memberchkmem−_, OutKs
2537asm_runtime_reason_, OutKs, InKs, _, _, 'inline assembly with pointer argument is not supported' ←
memberchkmem−_, OutKs
or
memberchkmem−_, InKs
or
memberchkptr−_, InKs
!
¬memberchkother−_, OutKs
¬memberchkother−_, InKs
2540asm_runtime_reason_, OutKs, InKs, _, _, Reason ←
forallmemberK−_, OutKs, K ≡ value
forallmemberK−_, InKs, K ≡ value
appendOutKs, InKs, Ks
forallmember_−C, Ks, asm_known_constraintC
member_−C, Ks
!
formatatomReason, 'unsupported constraint for safe inline asm: ~w', IR
2547asm_runtime_reason_, OutKs, InKs, Clobbers, Template, Reason ←
forallmemberK−C, OutKs,
K ≡ value
forallmemberK−C, InKs,
K ≡ value
findallF,
member_−C, OutKs
F ≢ none
,
OutFs
maplistasm_clobber_family, Clobbers, ClobFs0
appendOutFs, ClobFs0, Covered0
exclude==none, Covered0, Covered
split_asm_linesTemplate, Lines
asm_lines_reasonLines, Covered, Reason
asm_lines_reason+Lines, +Covered, -Reason

validateSafeInlineAsm checks the lines in order and Reason is the first refusal, given the register families Covered by output constraints and clobbers. A line must be one asm_line/3 knows: one it passes, or the one it refuses; for any other line nanofil cannot tell, and fails (the asm is then refused at compile time).

2562asm_lines_reasonLine|Lines, Covered, Reason ←
atom_codesL, Line
normalize_spaceatomA, L
A ≡ ''
→
asm_lines_reasonLines, Covered, Reason
else
asm_lineA, Covered, Verdict
Verdict = refusedReason
→
true
else
asm_lines_reasonLines, Covered, Reason
asm_line+A, +Covered, -Verdict

what validateSafeInlineAsm makes of the line A, probed against the pinned filcc (tests/c/prog/asm-refused.c):

  • a first word with clang's {AT&T|Intel} alternatives, which the IR writes $( $| $), names no mnemonic: refused with that word;
  • a mnemonic its table lacks (c_types filcc_refused_mnemonic/1);
  • cpuid without operands writes eax, ebx, ecx and edx, each of which an output constraint or a clobber must cover, checked in that order;
  • xor or mov (l, q or none) of two literal registers of one width writes the second, which must be covered ("literal register %R not covered ..."); reading the first needs nothing.
2581asm_lineA, Covered, Verdict ←
atomic_list_concatW0|Ops0, ' ', A
downcase_atomW0, W
atomic_list_concatOps0, '', Ops
sub_atomW, _, _, _, '{'
→
atom_charsW, WCs
maplistasm_alternative, WCs, Parts
atomic_list_concatParts, M
formatatomR, 'unsupported mnemonic for safe inline asm: ~w', M
Verdict = refusedR
→
formatatomR, 'unsupported mnemonic for safe inline asm: ~w', W
Verdict = refusedR
W ≡ cpuid
Ops ≡ ''
→
memberReg−F, eax−ax, ebx−bx, ecx−cx, edx−dx
¬memberchkF, Covered
→
formatatomR, 'cpuid output ~w not covered by output constraint or clobber', Reg
Verdict = refusedR
else
Verdict = passed
memberchkW, xor, xorl, xorq, mov, movl, movq
atomic_list_concat'', S, D, '%%', Ops
atom_concatS1, (','), S
asm_registerS1, _, Width
asm_registerD, FD, Width
¬memberchkFD, sp, bp
→
memberchkFD, Covered
→
Verdict = passed
else
formatatomR, 'literal register %~w not covered by output constraint or clobber', D
Verdict = refusedR
2608asm_alternative'{', '$(' ←
!
2609asm_alternative('|'), '$|' ←
!
2610asm_alternative'}', '$)' ←
!
2611asm_alternativeC, C
asm_register_output+C, -Family

output constraint C is one the validator accepts as a register, = or + and r or a fixed register; Family is the fixed register's (none for r).

2616asm_register_outputC, F ←
atom_charsC, P, L
memberchkP, =, +
L ≡ r
→
F = none
asm_letter_family
2620aax
2621bbx
2622ccx
2623ddx
2624'S'si
2625'D'di
asm_clobber_family+C, -F

clobber C is memory or cc (F none) or a general-purpose register other than sp and bp, of family F, which validateSafeInlineAsm accepts; fails for any other.

2630asm_clobber_familyC, none ←
memberchkC, memory, cc
!
2631asm_clobber_familyC, F ←
atom_concat'%', R, C
→
true
else
R = C
¬memberchkF, sp, bp
asm_register?Name, ?Family, ?Width

a general-purpose register.

2636asm_registerName, F, W ←
memberF−Names, ax−rax, eax, ax, al, bx−rbx, ebx, bx, bl, cx−rcx, ecx, cx, cl, dx−rdx, edx, dx, dl, si−rsi, esi, si, sil, di−rdi, edi, di, dil, sp−rsp, esp, sp, spl, bp−rbp, ebp, bp, bpl
nth1I, Names, Name
nth1I, 64, 32, 16, 8, W
2641asm_registerName, F, W ←
between8, 15, N
atom_concatr, N, F
memberSuffix−W, ''−64, d−32, w−16, b−8
atom_concatF, Suffix, Name
asm_ir_constraint+C, -IR

the constraint clang writes in the IR for a value operand's GCC constraint C: + becomes = (its tied input is a digit), a fixed register letter its {reg}, g is imr, the rest as written (lib/CodeGen/CGStmt.cpp, X86's convertConstraint). An operand whose constraint allows memory and is passed by address is a pointer argument, which asm_operand/5 classifies as mem.

2651asm_ir_constraintC, IR ←
atom_charsC, Cs
maplistasm_ir_prefix, Ps0, Ps
maplistasm_ir_letter, Body, BCss
appendBCss, BCs
appendPs, BCs, IRCs
atom_charsIR, IRCs
asm_ir_letter+L, -Cs

clang's SimplifyConstraint writes g as imr and X86's convertConstraint each fixed register letter as its {reg}, also within a longer constraint ("Nd" is N{dx}).

2661asm_ir_letterg, i, m, r ←
!
2662asm_ir_letterL, Cs ←
!
atom_charsReg, Cs
2663asm_ir_letterL, L
2665asm_constraint_prefixP|Cs, P|Ps, Body ←
memberchkP, =, +, &, '%'
!
2667asm_constraint_prefixBody, [], Body
2669asm_ir_prefix+, = ←
!
2670asm_ir_prefixP, P
asm_fixed_register
2672a'{ax}'
2673b'{bx}'
2674c'{cx}'
2675d'{dx}'
2676'S''{si}'
2677'D''{di}'
asm_known_constraint+C

C's letters are ones whose lowering by clang asm_ir_constraint/2 knows: register classes, fixed registers, r, m, g, i, n, e and N, digits.

2682asm_known_constraintC ←
atom_charsC, Cs
Body = _|_
forallmemberL, Body,
memberchkL, r, m, g, i, n, e, 'N', q, 'Q', x, y, v, a, b, c, d, 'S', 'D'
or
asm_validated_constraint+IR

validateSafeInlineAsm accepts an operand constraint IR in its first loop (FilPizlonator.cpp): after the prefixes =, +, & and %, a register class r, q, Q, x, y, v or Yk, a fixed register of a general-purpose family, or a matching digit string. Every other operand constraint is refused there, first in the IR's order, outputs then inputs, with "unsupported constraint for safe inline asm: IR".

2694asm_validated_constraintIR ←
atom_charsIR, Cs
atom_charsR, Rest
memberchkR, r, q, 'Q', x, y, v, 'Yk'
→
true
memberchkR, '{ax}', '{bx}', '{cx}', '{dx}', '{si}', '{di}'
→
true
else
Rest = _|_
forallmemberD, Rest, char_code_digitD
2701char_code_digitD ←
char_codeD, N
N ≥ 0'0
N ≤ 0'9
2703asm_validated_inputr
2704asm_validated_inputC ←
2705asm_validated_inputC ←
atom_lengthC, 1
2707split_asm_linesTemplate, Lines ←
split_atTemplate, 0'\n, 0';, Lines
2709split_atCs, Seps, L|Ls ←
appendL, Sep|Rest, Cs
memberchkSep, Seps
→
split_atRest, Seps, Ls
else
L = Cs
Ls = []
case_values+Expr, +Env, +Loc, -Values

the values a case label matches: its constant, or each of a GNU range Lo ... Hi, none when Lo exceeds Hi (GCC, Case Ranges). A range of fewer than 4096 values is expanded; a longer one is range(Lo, Hi), which dispatch//4 tests as an interval (cryptsetup's utf16_encode_unichar, case 0 ... 0xd7ffU).

2720case_valuesrangeLo, Hi, Env, Loc, Values ←
!
case_constantLo, Env, Loc, L
case_constantHi, Env, Loc, H
H−L ≥ 4096
→
Values = rangeL, H
H < L
→
Values = []
else
numlistL, H, Values
2726case_valuesExpr, Env, Loc, Value ←
case_constantExpr, Env, Loc, Value
2728case_constantExpr, Env, Loc, Value ←
catchconst_valueExpr, Env, Value, _, not_constant, fail
→
true
else
sema_errorLoc, 'case label is not a constant', []

computed_dispatch//: the dispatch block of the function's goto *, if any: among its address-taken labels by number, any other value reaching unreachable, a Fil-C panic. Each such label is a target of every goto *, so none may be in a variably modified scope one of them is not (stmt_//2 records them).

2738computed_dispatch ⟶
statestEnv, _, _, _, _, Labels, _, _
get_assoccomputedgoto, Labels, dispatchLDispatch, Slot, Loc
→
startLDispatch
letl, loadl, Slot, X
lookup_ordinaryEnv, 'labels.taken', Names
label_casesNames, 1, Loc, Cases
labelLTrap
dispatchCases, X, ulong, LTrap
startLTrap
termunreachable
else
[]
2751label_cases[], _, _, [] ⟶
[]
2752label_casesName|Names, N, Loc, caseN, Label|Cases ⟶
goto_labelName, Label
N1 is N + 1
label_casesNames, N1, Loc, Cases
label_values+Body, +Env0, -Env

Env is Env0 with each label whose address Body takes (&&L, label_addr(L)) bound to its number, 1, 2, ... in order of first use, and 'labels.taken' to their list.

2760label_valuesBody, Env0, Env ←
phraselabel_addrsBody, Names0
list_to_ord_setNames0, Set
first_usesNames0, Set, Names
foldllabel_number, Names, 1−Env0, _−Env1
bind_ordinary'labels.taken', Names, Env1, Env
2767label_addrslabel_addrName ⟶
!
Name
2768label_addrsT ⟶
compoundT
!
T =.. _|Args
2769label_addrs_ ⟶
[]
2770label_addrs_list[] ⟶
[]
2771label_addrs_listA|As ⟶
2773first_uses[], _, []
2774first_usesN|Ns, Left0, Firsts ←
ord_memberchkN, Left0
→
ord_del_elementLeft0, N, Left
Firsts = N|Firsts1
else
Left = Left0
Firsts = Firsts1
first_usesNs, Left, Firsts1
2781label_numberName, N−Env0, N1−Env ←
bind_ordinarylabel_addrName, N, Env0, Env
N1 is N + 1
target_jump+Target, +Loc, +Error//

a jump to the break or continue Target, some(Label); none is semantic error Error at Loc.

2786target_jumpsomeLabel, _, _ ⟶
termgotoLabel
2787target_jumpnone, Loc, Error ⟶
sema_errorLoc, Error, []

A block scope: save_scope(Scope) on entry, restore_scope(Scope) on exit.

2790save_scopeScope ⟶
envEnv
env_scopeEnv, Scope, _
2794restore_scopeScope ⟶
envEnv0
env_restoreScope, Env0, Env
items+Items, +Ctx//

, item(+Item, +Ctx)//: lower the items of a block, statements and declarations.

2801items[], _ ⟶
[]
2802itemsItem|Items, Ctx ⟶
itemItem, Ctx
itemsItems, Ctx
2804itematLoc, Stmt, Ctx ⟶
!
stmtatLoc, Stmt, Ctx
2805itemstatic_assertLoc, Expr, _, _ ⟶
!
envEnv
static_assertLoc, Expr, Env
2808itemdeclLoc, Specs, Inits, Ctx0 ⟶
envEnv0
catchdecl_typesSpecs, Inits, Decls, Env0, Env, not_constant, sema_errorLoc, 'array size is not a constant', []
ctx_atCtx0, Loc, Ctx
enter_originLoc, statementdecl, Parent
vm_typedefsSpecs, Inits, Ctx
¬memberchkstoragetypedef, Specs
cleanup_attrSpecs, Fn
→
cleanup_declsDecls, Loc, Fn, Ctx
else
local_declsDecls, Ctx
local_decls+Decls, +Ctx//

, local_decl(+Decl, +Ctx)//: lower block-scope declarations. Functions and extern objects are global, a static object is a global with a function-qualified name, anything else is a local slot.

2826local_decls[], _ ⟶
[]
2827local_declsDecl|Decls, Ctx ⟶
local_declDecl, Ctx
local_declsDecls, Ctx
2831local_decldName, Type0, Storage0, Asm, Init, Ctx ⟶
ctx_locCtx, Loc
Storage0 = thread_localStorage
→
Thread = tls
else
Storage = Storage0
Thread = none
Type0 = func_, _, _
or
Storage ≡ extern
→
envEnv0
global_declLoc, dName, Type0, Storage0, Asm, Init, Env0, Env
Thread ≡ tls6.7.1p3
Storage ≢ static
→
sema_errorLoc, '_Thread_local ~w at block scope must also be static or extern', Name
Storage ≡ static
→
supportedType0, Loc
vla_typeType06.7.6.2p2
→
sema_errorLoc, '~w is a variable length array with static storage', Name
else
true
realizedType0, Type1, Ctxa pointer to one is allowed
envEnv0
Ctx = ctxFunc, _, _, _, _
next_idK, Env0, Env1
formatatomSym, '~w.~w.~w', Func, Name, K
complete_arrayType1, Init, Type, Loc, Env0, none
bind_ordinaryName, gvarType, Sym, Storage0, Init, Env1, Env2
thread_linkageThread, local, Linkage
define_globalSym, Type, Linkage, Init, Loc, Env2, Env2b
readonly_globalThread, Name, Sym, Env2b, Env2a
data_alignmentName, Sym, Env2a, Env3
Resolve block-scope names before the scope is restored.
static_itemsType, Init, Loc, Env3, Env4, Items
put_definitializedSym, Items, Env4, Env
else
realizedType0, Type1, Ctx
envEnv
complete_arrayType1, Init, Type, Loc, Env, whole
check_local_typeType, Env, Loc, Name
lookup_ordinaryEnv, qalignName, A
integerA
→
true
else
A = 1
→
Init ≡ none6.7.9p3
→
true
else
sema_errorLoc, 'a variable length array cannot be initialized', []
vla_slotType, A, Ctx, Addr
else
local_slotType, A, Addr
bindName, varAddr, Type
Init ≡ none
→
[]
else
initAddr, Type, Init, Ctx
check_local_type+Type, +Env, +Loc, +Name

throw a semantic error unless Type is a type a local object Name can have.

2884check_local_typeType, Env, Loc, Name ←
supportedType, Loc
→
element_typeType, Elem
else
Elem = Type
catch
type_size_alignElem, Env, _, _
→
true
else
true
,
badWhy, sema_errorLoc, '~w has a type nanofil does not support (~w)', Name, Why
vm_typedefs+Specs, +Inits, +Ctx//

a block-scope typedef of a variably modified type evaluates its size expressions where it is declared, once (C11 6.7.8p3); the name then has the evaluated type.

2894vm_typedefsSpecs, Inits, Ctx ⟶
memberchkstoragetypedef, Specs
→
else
[]
2899vm_typedef_names[], _ ⟶
[]
2900vm_typedef_namesinitdclsomeName, _, _, _|Inits, Ctx ⟶
envEnv0
lookup_ordinaryEnv0, Name, typedefType0
vm_typeType0
→
realizedType0, Type, Ctx
envEnv1
bind_ordinaryName, typedefType, Env1, Env
else
[]

complete_array(+Type0, +Init, -Type, +Loc, +Env, +Whole): Type is Type0 with the size of an array of unknown size taken from its initializer Init, counting the entries each element takes with brace elision: {1, 2, 3, 4} is two elements of a struct {int a, b}. Whole is whole when an entry may be a struct expression that initializes a struct element whole (locals), none otherwise.

2916complete_arrayarrayElemType, none, none, arrayElemType, none, _, _, _ ←
!
2918complete_arrayarrayElemType, none, Init, arrayElemType, N, Loc, Env, Whole ←
!
str_elemsInit, ElemType, Values
→
lengthValues, N0
N is N0 + 1
Init = listEntries
→
phraseslotsarrayElemType, none, 0, 0, Entries, _, 0, N, cEnv, Whole, Loc, _
else
sema_errorLoc, 'array of unknown size', []
2929complete_arrayType, _, Type, _, _, _
static_complit+V, +Type, +C

V is a compound literal of Type in a static initializer, which GNU C accepts as its initializer list.

2933static_complitcomplitTN, _, Type, cEnv, none, _ ←
catchtype_name_typeTN, T, Env, _, _, fail
T ≡ Type
single_value+Type, +V, +Env, +Whole

entry value V initializes a slot of type Type by itself, so no braces were elided. The final cut makes this semidet: the alternatives can overlap.

2941single_valueType, V, Env, Whole ←
V = list_
or
static_complitV, Type, cEnv, Whole, unknown
or
¬aggregateType
or
Type = arrayCharType, _
str_elemsV, CharType, _
or
Whole ≡ whole
suType
catchexpression_typeV, Env, T0, nanofil_error_, fail
unqualifiedT1, Type1
Type1 ≡ Type
!
dispatch+Cases, +Value, +Type, +LEnd//

the switch dispatch (C11 6.8.4.2p5): a jump to the case whose value, converted to the promoted type Type, equals Value, else to the default or LEnd. Up to four cases are compared for equality in source order. More are read with Type's signedness and sorted; a binary search compares Value with them in Type's order, down to a few cases compared one by one.

A long case range is one pair Lo-to(Hi, Label), its bounds converted to Type, which must hold them unchanged; it matches when Value - Lo, unsigned, is at most Hi - Lo. Cases do not overlap (distinct_cases/3), so the search's test against a pivot's Lo still splits them.

2966dispatch[], _, _, LEnd ⟶
termgotoLEnd
2967dispatchCases, Value, Type, LEnd ⟶
findallWrapped−Target,
membercaseCaseValue, Target, Cases
convert_intCaseValue, Type, Wrapped
or
memberrangeL, H, Label, Cases
convert_intL, Type, Wrapped
convert_intH, Type, WH
Target = toWH, Label
,
Pairs0
memberchkdefaultLDefault, Cases
→
true
else
LDefault = LEnd
type_classType, Class
lengthPairs0, N
N ≤ 4
→
dispatch_casesPairs0, Value, Class, LDefault
else
keysortPairs0, Pairs
int_infoType, _, Sign
dispatch_searchPairs, Value, Sign, Class, LDefault
distinct_cases+Cases, +Type, +Loc

no two case values of a switch are equal once converted to the promoted controlling type Type (C11 6.8.4.2p3).

2988distinct_casesCases, Type, Loc ←
forallmemberrangeL, H, _, Cases,
convert_intL, Type, L
convert_intH, Type, H
→
true
else
sema_errorLoc, 'case range ~w ... ~w does not fit the switch\'s type', L, H
findallW−W,
membercaseV, _, Cases
convert_intV, Type, W
,
Is0
findallL−H, memberrangeL, H, _, Cases, Rs
appendIs0, Rs, Is
msortIs, Sorted
append_, _−H0, L1−_|_, Sorted
L1 ≤ H0
→
sema_errorLoc, 'duplicate case value ~w', L1
else
true
dispatch_search+Pairs, +Value, +Sign, +Class, +LDefault//

Pairs are sorted by value. More than four: branch on Value < the middle value, to the search of the lower or the upper half. Otherwise compare Value with each in turn, then jump to LDefault.

3006dispatch_searchPairs, Value, Sign, Class, LDefault ⟶
lengthPairs, N
N > 4
!
Half is N//2
lengthLower, Half
appendLower, Upper, Pairs
Upper = Pivot−_|_
less_thanSign, Less
letw, cmpLess, Class, Value, constPivot, Test
labelLLower
labelLUpper
termbranchTest, LLower, LUpper
startLLower
dispatch_searchLower, Value, Sign, Class, LDefault
startLUpper
dispatch_searchUpper, Value, Sign, Class, LDefault
3018dispatch_searchPairs, Value, _, Class, LDefault ⟶
dispatch_casesPairs, Value, Class, LDefault
3021less_thansigned, slt
3022less_thanunsigned, ult
dispatch_cases+Pairs, +Value, +Class, +LDefault//

compare Value with each case value of Pairs in turn, then jump to LDefault.

3026dispatch_cases[], _, _, LDefault ⟶
termgotoLDefault
3027dispatch_casesLo−toHi, Label|Pairs, Value, Class, LDefault ⟶
!
Span is Hi−Lo
letClass, sub, Value, constLo, D
letw, cmpule, Class, D, constSpan, Test
labelNext
termbranchTest, Label, Next
startNext
dispatch_casesPairs, Value, Class, LDefault
3035dispatch_casesWrapped−Label|Pairs, Value, Class, LDefault ⟶
letw, cmpeq, Class, Value, constWrapped, Test
labelNext
termbranchTest, Label, Next
startNext
dispatch_casesPairs, Value, Class, LDefault
judged+Expr, +Ctx//

full expression Expr is typed, or c_types says why not; where it cannot say, lowering reports the fault.

3044judgedExpr, Ctx ⟶
envEnv
ctx_locCtx, Loc
elaborateExpr, Env, Loc, _
→
true
else
true
expression_scope+Expr//

the scope gets the structs and unions full expression Expr's type names define (c_types expression_tags/3).

3051expression_scopeExpr ⟶
envEnv0
expression_tagsExpr, Env0, Env
judged_rval+Expr, +Ctx, -R//

judged//2, then rval//3, from one elaboration of Expr: elaborating each full expression twice was a seventh of lowering.

3057judged_rvalExpr, Ctx, R ⟶
envEnv
ctx_locCtx, Loc
elaborateExpr, Env, Loc, X
→
valueX, XV
lowerXV, Ctx, R
else
ctx_locCtx, Loc
sema_errorLoc, 'cannot type ~w', Expr
cond_jump+Expr, +Ctx, +LTrue, +LFalse//

branch on the truth of scalar Expr.

3067cond_jumpExpr, Ctx, LTrue, LFalse ⟶
rvalExpr, Ctx, Value
truthValue, Ctx, Test
termbranchTest, LTrue, LFalse
truth+Value, +Ctx, -W//

W is a w value that is nonzero exactly when scalar Value is.

3074truthvV, Type, Ctx, W ⟶
type_classType, w
→
W = V
type_classType, l
→
letw, cmpne, l, V, const0, W
→
letw, cmpne, K, V, const0, W
Type = ptr_
→
letl, ptoi, V, Int
letw, cmpne, l, Int, const0, W
else
ctx_locCtx, Loc
sema_errorLoc, 'a ~w value is not a scalar', Type

Initializers

An initializer means its leaves: the assignments it makes to parts of an object, in the order C performs them, a later one overriding what an earlier one wrote.

zero(Off, Type)        clear the object of Type at Off; a braced
                       list or a string literal first clears what
                       it initializes
val(Off, Type, N)      the integer N, an element of a string literal
vals(Off, Type, Ns)    the integers Ns, consecutive elements of
                       integer Type from Off (a string literal, or
                       a braced list of integer constants): one
                       leaf for GLib's megabyte resource blobs and
                       other large tables
expr(Off, Type, Expr)  the value of the expression Expr, converted
                       to Type: a scalar, or (locals) a whole struct

Offsets are from the start of the object. A local object stores its leaves; a static one turns them into data chunks. The walk over the entries also gives an array of unknown size its length (complete_array/6).

init_leaves+Type, +Init, +C, -Leaves

Leaves are what Init assigns to an object of Type. C = c(Env, Whole, Loc), with Whole as for complete_array/6. The walk is deterministic; once/1 drops the choicepoints its clauses leave per slot, which would keep every slot's frames alive while a large table's leaves are used.

3112init_leavesType, Init, C, Leaves ←
oncephraseobject_initType, 0, Init, C, Leaves
object_init+Type, +Off, +Init, +C//

the leaves of Init for the object of Type at Off. A scalar may be braced.

3117object_initType, Off, complitTN, Init, C ⟶
static_complitcomplitTN, Init, Type, C
!
object_initType, Off, Init, C
3120object_initbitsT, Pos, W, Off, listentry_, V|Rest, C ⟶
!
Rest ≡ []
→
true
else
init_errorC, 'excess initializers', []
object_initbitsT, Pos, W, Off, V, C
3123object_initbitsT, Pos, W, Off, Expr, _ ⟶
!
bitsOff, T, Pos, W, Expr
3124object_initarrayT, N, Off, listentry[], Str, C ⟶
str_elemsStr, T, _
!
object_initarrayT, N, Off, Str, C
3127object_initatomicS, P, Off, Init, C ⟶
Init = list_
→
init_errorC, 'an _Atomic struct or union cannot be initialized by a braced list', []
else
true
zeroOff, atomicS, P
object_initS, Off, Init, C
3131object_initvecE, N, _, Off, Init, C ⟶
GNU vectors
Init = list_
!
object_initarrayE, N, Off, Init, C
3134object_initarrayT, N, Off, listEntries, _ ⟶
integerN
int_entriesEntries, T, Vs
lengthVs, K
K ≤ N
!
A braced list of integer constants, nothing else, for an integer
array: one leaf, as a string literal is (GLib's resource blobs
for a compiler it does not know by name, gtk3's 1.5 MB of them).
zeroOff, arrayT, N, valsOff, T, Vs
3141object_initType, Off, listEntries, C ⟶
!
zeroOff, Type
→
slotsType, 0, Off, Entries, Rest, 0, _, C
Entries = entry_, V|Rest
→
object_initType, Off, V, C
else
Rest = []
Rest ≡ []
→
true
else
init_errorC, 'excess initializers', []
3150object_initarrayElemType, N, Off, Init, C ⟶
str_elemsInit, ElemType, Values
!
lengthValues, NValues
integerN
NValues > N
→
init_errorC, 'string initializer is too long', []
else
true
C = cEnv, _, _
type_size_alignElemType, Env, Size, _
zeroOff, arrayElemType, N
table_typeElemType
→
valsOff, ElemType, Values
else
elem_leavesValues, ElemType, Off, Size
3163object_initarray_, _, _, _, C ⟶
!
init_errorC, 'array initializer must be a list', []
3165object_initType, Off, Expr, _ ⟶
exprOff, Type, Expr
table_type+Type

Type is an integer type whose elements a vals leaf holds (not _Bool, whose conversion is not modular).

3169table_typeType ←
Type ≢ bool
int_infoType, _, _
int_entries+Entries, +Type, -Values

Entries are undesignated integer or character constants, whose values converted to Type are Values.

3174int_entries[], _, []
3175int_entriesentry[], X|Es, T, V|Vs ←
X = numV0, _
or
X = chrV0
integerV0
!
convert_intV0, T, V
int_entriesEs, T, Vs
elem_leaves+Values, +Type, +Off, +Size//

a val leaf for each of Values, consecutive elements of Type and Size from Off.

3182elem_leaves[], _, _, _ ⟶
[]
3183elem_leavesV|Vs, Type, Off, Size ⟶
valOff, Type, V
Off1 is Off + Size
elem_leavesVs, Type, Off1, Size

slots(+Type, +I, +Off, +Entries0, -Entries, +Max0, -Max, +C)//: the leaves of aggregate Type at Off from Entries0, the next undesignated entry going to slot I; a designator moves to its slot. Entries is what is left when the slots run out. Max is the larger of Max0 and one past the last slot initialized.

3193slots_, _, _, [], [], Max, Max, _ ⟶
!
3194slotsType, _, Off, entryrangeE1, E2|Ds, V|Entries0, Entries, Max0, Max, C ⟶
!
range_indexesE1, E2, V, C, Is
findallentryindexnumI, none−dec|Ds, V, memberI, Is, Expanded
appendExpanded, Entries0, Entries1
slotsType, 0, Off, Entries1, Entries, Max0, Max, C
3199slotsType, I, Off, entryfieldF|Ds, V|Entries0, Entries, Max0, Max, C ⟶
anonymous_memberType, F, C, KC11 6.7.2.1p13
!
slotsType, I, Off, entryanonK, fieldF|Ds, V|Entries0, Entries, Max0, Max, C
3202slotsType, _, Off, entryD|Ds, V|Entries0, Entries, Max0, Max, C ⟶
!
designated_slotD, Type, C, I, SlotOff, SlotType
Off1 is Off + SlotOff
Ds ≡ []
→
slot_initSlotType, Off1, V, Entries0, Entries1, C
else
slotsSlotType, 0, Off1, entryDs, V, _, 0, _, C
Entries1 = Entries0
I1 is I + 1
Max1 is maxMax0, I1
slotsType, I1, Off, Entries1, Entries, Max1, Max, C
3213slotsType, I, Off, Entries0, Entries, Max0, Max, C ⟶
slotType, C, I, SlotOff, SlotType
→
Entries0 = entry[], V|Entries1
Off1 is Off + SlotOff
slot_initSlotType, Off1, V, Entries1, Entries2, C
I1 is I + 1
Max1 is maxMax0, I1
slotsType, I1, Off, Entries2, Entries, Max1, Max, C
else
Entries = Entries0
Max = Max0
slot_init+Type, +Off, +V, +Entries0, -Entries, +C//

the leaves of entry value V for the slot of Type at Off. Either V initializes the slot by itself, or the slot's braces were elided and it takes the entries it needs from [V|Entries0], up to the next designator, which belongs to the enclosing list.

3230slot_initType, Off, V, Entries, Entries, C ⟶
C = cEnv, Whole, _
single_valueType, V, Env, Whole
!
object_initType, Off, V, C
3235slot_initType, Off, V, Entries0, Entries, C ⟶
undesignatedEntries0, Pre, Post
slotsType, 0, Off, entry[], V|Pre, Rest, 0, _, C
appendRest, Post, Entries
slot+Type, +C, +I, -Off, -SlotType

the undesignated entry for slot I of aggregate Type initializes the SlotType at Off. An array of unknown size has every slot; a union has only its first member.

3243slotarrayElemType, N, cEnv, _, _, I, Off, ElemType ←
N ≡ none
→
true
else
I < N
type_size_alignElemType, Env, Size, _
Off is I × Size
3247slotstructId, cEnv, _, _, I, Off, Type ←
su_membersstructId, Env, Members
nth0I, Members, M
member_slotM, _, Off, Type
3251slotunionId, cEnv, _, _, 0, Off, Type ←
su_membersunionId, Env, M|_
member_slotM, _, Off, Type
member_slot+M, -Name, -Off, -SlotType

member M, named Name, is the slot of SlotType at Off; a bit-field's is bits(Type, Pos, Width), of the Type at Off.

3258member_slotmName, Type, Off, Name, Off, Type
3259member_slotmName, Type, Off, bitsPos, W, Name, Off, bitsType, Pos, W
designated_slot+Desig, +Type, +C, -I, -Off, -SlotType

designator Desig of aggregate Type names its slot I, the SlotType at Off; any member of a union.

3264designated_slotindexExpr, arrayElemType, N, C, I, Off, ElemType ←
!
C = cEnv, _, _
catchconst_valueExpr, Env, I, _, not_constant, fail
I ≥ 0
slotarrayElemType, N, C, I, Off, _
→
true
else
init_errorC, 'bad array designator', []
3272designated_slotfieldField, SU, C, I, Off, Type ←
suSU
!
C = cEnv, _, _
su_membersSU, Env, Members
nth0I, Members, M
member_slotM, someField, Off, Type
→
true
else
init_errorC, 'no member ~w', Field
3281designated_slotanonI, SU, cEnv, _, _, I, Off, Type ←
!
su_membersSU, Env, Members
nth0I, Members, M
member_slotM, none, Off, Type
3284designated_slot_, Type, C, _, _, _ ←
init_errorC, 'bad designator for a ~w', Type
anonymous_member+SU, +F, +C, -I

F is not a member of struct or union SU itself but of its anonymous member I, so a designator .F designates that member's F.

3290anonymous_memberSU, F, cEnv, _, _, I ←
suSU
su_membersSU, Env, Members
¬
memberM, Members
member_slotM, someF, _, _
nth0I, Members, M
member_slotM, none, _, AT
catchmember_layoutAT, F, Env, _, _, _, _, fail
!
range_indexes+E1, +E2, +V, +C, -Is

the GNU range designator [E1 ... E2] designates the elements Is, each initialized by V as if it had a designator of its own. GCC evaluates V once; nanofil lowers it for each element, so V may not have side effects.

3302range_indexesE1, E2, V, C, Is ←
C = cEnv, _, _
catch
const_valueE1, Env, Lo, _
const_valueE2, Env, Hi, _
,
not_constant, fail
0 ≤ Lo
Lo ≤ Hi
→
numlistLo, Hi, Is
else
init_errorC, 'bad range designator', []
Lo < Hi
→
init_errorC, 'a range designator\'s initializer has side effects', []
else
true
effectful+E

the expression or initializer E may have side effects: it contains an assignment, increment, call or statement expression.

3317effectfulE ←
compoundE
E = assign_, _, _
or
E = call_, _
or
E = stmtexpr_
or
E = unOp, _
memberchkOp, preinc, predec, postinc, postdec
→
true
E =.. _|Args
memberA, Args
→
true
su_members+SU, +Env, -Members

Members are the m(Name, Type, Offset) of the complete struct or union SU.

3328su_membersSU, Env, Members ←
SU =.. Kind, Id
env_defsEnv, Defs
get_assocId, Defs, defKind, Members, _, _
3333init_errorc_, _, Loc, Format, Args ←
sema_errorLoc, Format, Args
3336aggregatearray_, _
3337aggregatestructK ←
¬complex_typestructK, _

a scalar's initializer (6.7.9p11)

3338aggregateunion_
undesignated+Entries, -Pre, -Post

Pre is the longest prefix of Entries without a designator, Post the rest. The final cut commits to the first split found, which ends Pre just before the first designator (or at the end).

3344undesignatedEntries, Pre, Post ←
appendPre, Post, Entries
¬memberentry_|_, _, Pre
Post = []
or
Post = entry_|_, _|_
!
3350is_char_typeType ←
memberchkType, char, schar, uchar
init+Addr, +Type, +Init, +Ctx//

initialize the local object of Type at Addr from Init, storing its leaves.

3354initAddr, Type, Init, Ctx ⟶
envEnv
ctx_locCtx, Loc
init_leavesType, Init, cEnv, whole, Loc, Leaves
store_leavesLeaves, Addr, Ctx
3360store_leaves[], _, _ ⟶
[]
3361store_leavesLeaf|Leaves, Addr, Ctx ⟶
store_leafLeaf, Addr, Ctx
store_leavesLeaves, Addr, Ctx
3365store_leafzeroOff, Type, Addr, _ ⟶
offsetAddr, Off, P
3368store_leafvalsOff, Type, Ns, Addr, Ctx ⟶
int_infoType, Size, _
phraseelem_leavesNs, Type, Off, Size, Leaves
store_leavesLeaves, Addr, Ctx
3371store_leafvalOff, Type, N, Addr, _ ⟶
offsetAddr, Off, P
store_widthType, Width
emitstoreWidth, constN, P
3375store_leafexprOff, Type, Expr, Addr, Ctx ⟶
offsetAddr, Off, P
judged_rvalExpr, Ctx, Value0
ctx_locCtx, Loc
assign_convertExpr, Value0, Type, Loc, vValue, _
Value ≡ const0
Off mod 8 ≠ 0
unqualifiedType, ptr_
→
A null pointer in a packed member: clang copies it from a
constant, so it is bytes, not a pointer store that would trap.
emitstorel, const0, P
else
store_typedType, Value, P
3386store_leafbitsOff, Type, Pos, W, Expr, Addr, Ctx ⟶
offsetAddr, Off, P
judged_rvalExpr, Ctx, Value0
ctx_locCtx, Loc
assign_convertExpr, Value0, Type, Loc, Value
place_writebitsP, Type, Pos, W, Value, Ctx, _
zero_object+Addr, +Type//

clear the whole object, since allocs are hoisted and a local inside a loop must be reinitialized on every pass.

3395zero_objectAddr, Type ⟶
envEnv
type_size_alignType, Env, Size, Align
zero_bytesAddr, 0, Size, Align
zero_bytes+Addr, +Off, +Size, +Align//

zero Off..Size with the widest stores the alignment allows; bytes below Off are done. The cut in the second clause commits to the widest store that fits.

3403zero_bytes_, Off, Size, _ ⟶
Off ≥ Size
!
3404zero_bytesAddr, Off, Size, Align ⟶
Left is Size−Off
memberWidth−N, l−8, w−4, h−2, b−1
N ≤ Left
N ≤ Align
Off mod N =:= 0
!
offsetAddr, Off, P
emitstoreWidth, const0, P
Off1 is Off + N
zero_bytesAddr, Off1, Size, Align

Expressions

GNU statement expressions load/decay their last expression before the block scope closes. A label on that statement does not hide its value.

3418statement_expression_items[], _, vconst0, void ⟶
[]
3419statement_expression_itemsItem, Ctx, R ⟶
3421statement_expression_itemsItem|Items, Ctx, R ⟶
itemItem, Ctx
3424statement_expression_lastatLoc, exprExpr, Ctx0, R ⟶
!
ctx_atCtx0, Loc, Ctx
judged_rvalExpr, Ctx, R
3426statement_expression_lastat_, labelName, Body, Ctx, R ⟶
!
vm_labellabelName
goto_labelName, Label
label_cleanupsName, Label
startLabel
3432statement_expression_lastItem, Ctx, vconst0, void ⟶
itemItem, Ctx
binding_obj+Binding, -R//

R is the object or function an identifier bound to Binding designates. A global's address is copied into a temporary.

3437binding_objvarAddr, Type, lvAddr, Type ⟶
[]
3438binding_objfnType, Sym, _, fdglobalSym, Type ⟶
[]
3439binding_objgvarType, Sym, _, _, lvAddr, Type ⟶
letp, copy, globalSym, Addr
callee_temp+V, -Callee//

Callee is V in a temporary, as a callee must be.

3444callee_temptmpT, tmpT ⟶
!
3445callee_tempV, Callee ⟶
letp, copy, V, Callee
member_lv+Addr, +SUType, +Field, +Ctx, -P, -Type//

P points at member Field, of Type, of the struct or union at Addr.

3449member_lvAddr, SUType, Field, Ctx, P, Type ⟶
envEnv
ctx_locCtx, Loc
SUType = struct_
or
SUType = union_
member_type_offsetSUType, Field, Env, Type, Off
→
offsetAddr, Off, P
SUType =.. _, Id
env_defsEnv, Defs
get_assocId, Defs, Def
Def = badWhy
or
Def = partialWhy, _
→
sema_errorLoc, 'member ~w of a type nanofil does not support (~w)', Field, Why
else
sema_errorLoc, 'no member named ~w', Field
rval+Expr, +Ctx, -R//

R is v(V, Type), the value of Expr, arrays decayed, lvalues loaded, lowered from its typed tree (c_types' elaborate/3). Full expressions are judged first (judged//2), which diagnoses one that cannot be typed.

3465rvalExpr, Ctx, R ⟶
envEnv
elaborateExpr, Env, X
→
valueX, XV
lowerXV, Ctx, R
else
ctx_locCtx, Loc
sema_errorLoc, 'cannot type ~w', Expr
lower+XV, +Ctx, -R//det

R is v(V, Type), the IR value of the typed tree XV (elaborate/3), with the instructions that compute it emitted into the state. Types are unqualified, as code generation has them; a type name the source writes (a cast's, va_arg's, sizeof's) is read again by type_name/4, which refuses one nanofil does not support. lower_node//3 has a clause per tree form; this wrapper records its origin for the context records.

3480lowerX, Ctx, R ⟶
typed_originX, Ctx, Parent
lower_nodeX, Ctx, R
3484lower_nodevalT, intV, _, vconstV, U ⟶
3485lower_nodeval_, floatCs, _, vconstV, Type ⟶
!
float_literalCs, V, Type
3487lower_nodevalT, imagXF, Ctx, R ⟶
!GNU 1.0i
lower_nodeXF, Ctx, V
cx_madeT, vconst0, int, V, R
3490lower_nodevalT, loadX, Ctx, R ⟶
!6.3.2.1p2
placeX, Ctx, P
P = lvAddr, ptr_
→
letp, atomic_loadp, Addr, D6.2.6.1p9
R = vD, U
P = lvAddr, atomicS, Size
→
atomic_su_loadedS, Size, Addr, Ctx, R
else
place_loadedP, U, Ctx, R
3500lower_nodevalT0, decayX, Ctx, R ⟶
!6.3.2.1p3-4
placeX, Ctx, P
P = lv_, arrayE, _
→
PT = ptrE
else
PT = T0
vm_typedT0, PT, T
decayedP, T, Ctx, R
3504lower_nodevalT0, addrX, Ctx, R ⟶
!6.5.3.2p3
placeX, Ctx, P
P = lv_, PT0
→
PT = ptrPT0
else
PT = T0
vm_typedT0, PT, T
decayedP, T, Ctx, R
3508lower_nodeval_, memberXS, F, Ctx, R ⟶
!6.5.2.3p3
lowerXS, Ctx, vAddr, SU
member_lvAddr, SU, F, Ctx, P, Type
Type = arrayE, _
→
R = vP, ptrE
else
loadedType, P, Ctx, R
3514lower_nodevalT, vlaneXV, XI, Ctx, R ⟶
!GNU vectors
lowerXV, Ctx, vAddr, _
lowerXI, Ctx, VI
ptr_offsetadd, vAddr, ptrU, U, VI, Ctx, vP, _
loadedU, P, Ctx, R
3520lower_nodeval_, commaXA, XB, Ctx, R ⟶
!6.5.17
lowerXA, Ctx, _
lowerXB, Ctx, R
3523lower_nodeval_, unOp, XV, Ctx, R ⟶
!
lowerXV, Ctx, V
unaryOp, V, Ctx, R
3526lower_nodeval_, binOp, XA, XB, Ctx, R ⟶
6.5.13-14
memberchkOp, land, lor
!
logicalOp, XA, XB, Ctx, R
3529lower_nodeval_, binOp, XA, XB, Ctx, R ⟶
!
lowerXA, Ctx, VA
lowerXB, Ctx, VB
binaryOp, VA, VB, Ctx, R
3533lower_nodevalT, condXC, XA, XB, Ctx, R ⟶
!6.5.15
labelLJoin
lowerXC, Ctx, VC
truthVC, Ctx, W
termbranchW, LA, LB
lowerXA, Ctx, VA
convertedVA, U, vVA0, _
arm_valueU, VA0, VA1
hereLA1
termgotoLJoin
lowerXB, Ctx, VB
convertedVB, U, vVB0, _
arm_valueU, VB0, VB1
hereLB1
termgotoLJoin
join_armsU, LJoin, LA1−VA1, LB1−VB1, R
3544lower_nodevalT, gcondXC, XB, Ctx, R ⟶
!GNU c ?: b
lowerXC, Ctx, VC
truthVC, Ctx, W
labelLJoin
termbranchW, LA, LB
convertedVC, U, vVA0, _
arm_valueU, VA0, VA1
hereLA1
termgotoLJoin
lowerXB, Ctx, VB
convertedVB, U, vVB0, _
arm_valueU, VB0, VB1
hereLB1
termgotoLJoin
join_armsU, LJoin, LA1−VA1, LB1−VB1, R

Fil-C accesses a volatile pointer as it does an _Atomic one, so its address and capability never tear (FilPizlonator's loadPtr and storePtr; invisicaps_by_example.md, Pointer Races): ptrchaos2 pushes onto a list through a racing foo *volatile head.

3559lower_nodeval_, assign=, X, XB, Ctx, R ⟶
!
placeX, Ctx, lvAddr, U
lowerXB, Ctx, Value0
convertedValue0, U, R
R = vV, _
atomic_storedU, V, Addr, 5
3567lower_nodeval_, assign=, X, XB, Ctx, R ⟶
6.2.6.1p9
!
placeX, Ctx, lvAddr, U
lowerXB, Ctx, Value0
U = atomicS, Size
→
convertedValue0, S, R
atomic_su_wordS, Size, R, Ctx, W
atomic_wordSize, WT
atomic_storedWT, W, Addr, 5
else
convertedValue0, U, R
R = vV, _
atomic_storedU, V, Addr, 5

A compound assignment or increment of an _Atomic object is one atomic read-modify-write (6.5.16.2p3, 6.5.2.4p2): a cas loop, the operand evaluated once before it; a pointer keeps its capability.

3582lower_nodeval_, assignOp, X, XB, Ctx, R ⟶
!
placeX, Ctx, lvAddr, U0
lowerXB, Ctx, VB
rmwU, Addr, BinOp, VB, Ctx, _, R
3589lower_nodeval_, incdecOp, X, Ctx, R ⟶
!
placeX, Ctx, lvAddr, U0
memberchkOp, preinc, postinc
→
BinOp = add
else
BinOp = sub
rmwU, Addr, BinOp, vconst1, int, Ctx, Old, New
memberchkOp, preinc, predec
→
R = New
else
R = Old
3597lower_nodeval_, assignOp, X, XB, Ctx, R ⟶
!6.5.16
placeX, Ctx, P
Op ≡ (=)
→
lowerXB, Ctx, Value0
else
place_readP, Ctx, Cur
lowerXB, Ctx, VB
binaryBinOp, Cur, VB, Ctx, Value0
place_writeP, Value0, Ctx, R
3606lower_nodeval_, incdecOp, X, Ctx, R ⟶
!6.5.2.4, 6.5.3.1
placeX, Ctx, P
place_readP, Ctx, Cur
memberchkOp, preinc, postinc
→
BinOp = add
else
BinOp = sub
binaryBinOp, Cur, vconst1, int, Ctx, Value0
place_writeP, Value0, Ctx, New
memberchkOp, preinc, predec
→
R = New
else
R = Cur

immintrin.h declares a few intrinsics without a body, which clang compiles as builtins (sse2_lfence and the like), not calls. filcc runs _mm_pause and _mm_sfence, and compiles _mm_lfence and _mm_mfence to its run-time "Unhandled intrinsic" error, which nanofil calls zsafety_error with; _mm_clflush, whose error text depends on clang's optimization, and _mm_getcsr and _mm_setcsr, which nanofil cannot express, are refused.

3620lower_nodeval_, callval_, decayfun_, idName, XArgs, Ctx, vconst0, void ⟶
memberchkName, '_mm_pause', '_mm_sfence', '_mm_lfence', '_mm_mfence', '_mm_clflush', '_mm_getcsr', '_mm_setcsr'
envEnv0
lookup_ordinaryEnv0, Name, fn_, _, export
!
ctx_locCtx, Loc
memberchkName−M, '_mm_pause'−pause, '_mm_sfence'−sfence
XArgs ≡ []
→
emitasm_fixedM, [], []
memberchkName−M, '_mm_lfence'−lfence, '_mm_mfence'−mfence
XArgs ≡ []
→
formatcodesMsg, 'Unhandled intrinsic: call void @llvm.x86.sse2.~w()', M
else
sema_errorLoc, '~w is not supported', Name
3633lower_nodevalT, callval_, decayfun_, idzargs, [], _, vV, T ⟶
envEnv
lookup_ordinaryEnv, zargs, fn_, zargs, _
!
letp, va_args, const0, V
3636lower_nodeval_, callval_, decayfun_, idzreturn, X, Ctx, vconst0, void ⟶
envEnv
lookup_ordinaryEnv, zreturn, fn_, zreturn, _
!
lowerX, Ctx, V0
convertV0, ptrvoid, vV, _
termretV
3641lower_nodeval_, callXF, XArgs, Ctx, R ⟶
!6.5.2.2
calleeXF, Ctx, Callee0, funcRetType, Ps, _
Ps ≡ unspecified
→
ParamTypes = []
Callee0 = globalS
¬op_kindsetjmpS, setjmp
→
Callee = unprotoS
else
Callee = Callee0
else
ParamTypes = Ps
Callee = Callee0
call_argsXArgs, ParamTypes, Ctx, IRArgs0
envEnv
memtypeRetType
ret_shapeRetType, Env, unknown, sret
→
NSret = 1
else
NSret = 0
pad_argsIRArgs0, NSret, IRArgs
call_resultCallee, RetType, IRArgs, Ctx, R
3655lower_nodeval_, builtinName, Args, Ctx, R ⟶
!
builtinName, Args, Ctx, R
3657lower_nodevalT, bit_castXA, Ctx, R ⟶
!
The operand's bytes stored, then loaded as T (c_types bit_castable/3).
lowerXA, Ctx, vV, A
store_typedA, V, Tmp
loadedT, Tmp, Ctx, R
3663lower_nodeval_, castTN, XV, Ctx, R ⟶
!6.5.4
evaluated_typeTN, Ctx, Type
lowerXV, Ctx, Value0
convertedValue0, Type, R
3667lower_nodeval_, loweredV, _, V ⟶
!
3668lower_nodeval_, va_argTN, XA, Ctx, vTmp, Type ⟶
7.16.1.1
evaluated_typeTN, Ctx, Type
suType
!
A struct or union is in consecutive 8-byte cells of the packet,
as the caller passed it: one va_arg per eightbyte, a pointer one
with its capability.
A long double member is one 16-aligned x cell (va_cells/5).
envEnv
ctx_locCtx, Loc
eightbytesType, Env, Loc, Ebs0
leavesType, Env, 0, Leaves, []
va_cellsEbs0, Leaves, Type, Loc, Ebs
lowerXA, Ctx, vP, _
va_wordsEbs, P, Words
local_slotType, Tmp
store_wordsEbs, Words, Tmp
3683lower_nodeval_, va_argTN, XA, Ctx, vV, Type ⟶
!7.16.1.1
evaluated_typeTN, Ctx, Type
ctx_locCtx, Loc
type_classType, K
memberchkType, int, uint, long, ulong, llong, ullong, double, ldouble, ptr_
→
true
else
sema_errorLoc, 'unsupported va_arg type ~w', Type
lowerXA, Ctx, vP, _
letK, va_arg, P, V

The operand of sizeof is evaluated only when it has a variable length array type, whose size is then known only at run time (6.5.3.4p2).

3693lower_nodeval_, sizeofTN, Ctx, vSize, ulong ⟶
6.5.3.4
TN = type_, _
!
envEnv
ctx_locCtx, Loc
type_name_typeTN, Type0, Env, _
→
evaluated_typeTN, Ctx, Type
extentType, Ctx, Size
else
type_sizeTN, Env, Loc, Size0
Size = constSize0

sizeof (T[]){...}: the compound literal's array type is completed by its initializer (6.5.2.5p3, 6.7.9p22); the operand is not evaluated.

3704lower_nodeval_, sizeofobjarray_, none, complitTN, Init, Ctx, vconstSize, ulong ⟶
!
envEnv
ctx_locCtx, Loc
type_name_typeTN, T0, Env, Env1
complete_arrayT0, Init, T, Loc, Env1, whole
type_size_alignT, Env1, Size, _
3710lower_nodeval_, sizeofX, Ctx, vSize, ulong ⟶
!
→
placeX, Ctx, lv_, Type
extentType, Ctx, Size
else
envEnv
tree_typeX, Type
ctx_locCtx, Loc
catchsizeof_sizeType, Env, Size0, badWhy, sema_errorLoc, 'sizeof: ~w', Why
Size = constSize0
3720lower_nodeval_, alignofTN, Ctx, vconstAlign, ulong ⟶
!
envEnv
ctx_locCtx, Loc
type_name_typeTN, Type0, Env, Env1
supportedType0, Loc
element_typeType0, Type
catchtype_size_alignType, Env1, _, Align0, badWhy, sema_errorLoc, 'alignof: ~w', Why
TN = typeSpecs, Syntax
inherited_alignmentSpecs, Syntax, Env, alignA
→
Align = A
else
Align = Align0
3732lower_nodeval_, alignof_exprX, Ctx, vconstA, ulong ⟶
!
envEnv
ctx_locCtx, Loc
catchexpr_alignmentX, Env, A, badWhy, sema_errorLoc, 'alignof: ~w', Why

offsetof with a variable array index (GCC and clang accept offsetof(struct seccomp_data, args[x])) is computed at run time.

3739lower_nodeval_, offsetofTN, Path, Ctx, vV, ulong ⟶
!
envEnv
catchconst_valueoffsetofTN, Path, Env, V0, _, not_constant, fail
→
V = constV0
else
ctx_locCtx, Loc
type_name_typeTN, T, Env, Env1
memberchkindex_, Path
→
true
else
sema_errorLoc, 'bad offsetof', []
runtime_offsetPath, T, Env1, Ctx, const0, V
3747lower_nodeval_, stmtexprat_, blockItems, Ctx, R ⟶
GNU ({ ... })
envEnv
cleanup_keysEnv, Outer
leavingOuter, Ks
Ks ≢ []the value, before them
R0 = vA, T
→
copy_objectT, A, A1
R = vA1, T
else
R = R0
converted+Value0, +Type, -R//

R is Value0 converted to Type, as assignment and casts convert, the elaborator having judged that they may: to void it is nothing, a struct or union stays its address.

3761converted_, void, vconst0, void ⟶
!
3762convertedValue0, Type, R ⟶
_Complex is a struct here
or
Value0 = v_, From
!
convertValue0, Type, R
3765convertedvAddr, _, Type, vAddr, Type ⟶
suType
!
3767convertedValue0, Type, R ⟶
convertValue0, Type, R
loaded+U, +Addr, +Ctx, -R//

R is the value of type U stored at Addr: a struct or union stands for its address.

3771loadedU, Addr, _, vCopy, U ⟶
U = vec_, _, _
!
local_slotU, Copy
vector_accessread, U, Addr
copy_objectU, Addr, Copy
3775loadedU, Addr, _, vAddr, U ⟶
!
3776loadedU, Addr, Ctx, vV, U ⟶
load_typedU, Addr, Ctx, V
decayed+P, +T, +Ctx, -R//

R, of pointer type T, points at the object or function P. A function's address is copied into a temp; setjmp's has none, since it is an intrinsic.

3781decayedlvAddr, _, T, _, vAddr, U ⟶
3782decayedfdglobalSym, _, _, Ctx, _ ⟶
op_kindsetjmpSym, setjmp
!
ctx_locCtx, Loc
sema_errorLoc, 'setjmp requires a direct call, not a function pointer', []
3786decayedfdglobalSym, Type, _, _, vAddr, ptrType ⟶
!
letp, copy, globalSym, Addr
3788decayedfdCallee, Type, _, _, vCallee, ptrType ⟶
[]
place+X, +Ctx, -P//det

P is lv(Addr, Type), the object that the typed tree X designates, or fd(Callee, Type) for a function: lower//3's counterpart for lvalues.

3794placeX, Ctx, P ⟶
typed_originX, Ctx, Parent
place_nodeX, Ctx, P
3797place_nodeobj_, idName, _, P ⟶
!
id_placeName, P
3799place_nodefun_, idName, _, P ⟶
!
envEnv
lookup_ordinaryEnv, Name, Binding
binding_objBinding, P
3802place_nodeobj_, predefined_, Ctx, lvAddr, arraychar, N ⟶
!6.4.2.2
envEnv0
Ctx = ctxFunc, _, _, _, _
atom_lengthFunc, N0
N is N0 + 1
func_literalFunc, Sym, Env0, Env
letp, copy, globalSym, Addr
3808place_nodeobj_, strBytes, _, lvAddr, arraychar, N ⟶
!
envEnv0
new_literalBytes, Sym, Env0, Env
lengthBytes, N0
N is N0 + 1
letp, copy, globalSym, Addr
3815place_nodeobj_, wstrWideType, Values, _, lvAddr, arrayWideType, N ⟶
!
envEnv0
new_literalwWideType, Values, Sym, Env0, Env
lengthValues, N0
N is N0 + 1
letp, copy, globalSym, Addr

A compound literal is an unnamed local, initialized where it appears.

3823place_nodeobj_, complitTN, Init, Ctx, lvAddr, Type ⟶
!6.5.2.5
envEnv
ctx_locCtx, Loc
type_name_typeTN, Type00, Env, _
vla_typeType006.5.2.5p1
→
sema_errorLoc, 'a compound literal cannot have a variable length array type', []
else
true
evaluated_typeTN, Ctx, Type0
complete_arrayType0, Init, Type, Loc, Env, whole
check_local_typeType, Env, Loc, 'compound literal'
local_slotType, Addr
initAddr, Type, Init, Ctx
3835place_nodeobjT, derefXV, Ctx, lvV, U ⟶
!6.5.3.2p4
lowerXV, Ctx, vV, PT
PT = ptrL
→
true
else
L = T
vm_typedT, L, U
3838place_nodefunT, derefXV, Ctx, fdCallee, U ⟶
!
lowerXV, Ctx, vV, _
callee_tempV, Callee
3842place_nodeobjT, memberXS, F, Ctx, lvP, U ⟶
6.5.2.3p3
placeXS, Ctx, lvAddr, SU
member_lvAddr, SU, F, Ctx, P, _
envEnv
U0 = vecE, N, A0
member_alignmentobjT, memberXS, F, Env, A1
→
A is minA0, A1its accesses' alignment
U = vecE, N, A
else
U = U0
3852place_nodeobjT, vlaneXS, XI, Ctx, lvP, U ⟶
GNU vectors
placeXS, Ctx, lvAddr, _
lowerXI, Ctx, VI
ptr_offsetadd, vAddr, ptrU, U, VI, Ctx, vP, _
3857place_nodeobj_, bitfieldmemberXS, F, W, Ctx, bitsP, T, Pos, W ⟶
6.7.2.1p10
placeXS, Ctx, lvAddr, SU
envEnv
member_layoutSU, F, Env, T, Off, bitsPos, W
offsetAddr, Off, P
3863id_placeName, P ⟶
envEnv
lookup_ordinaryEnv, Name, Binding
binding_objBinding, P
Binding = gvar_, Sym, _, _
P = lvtmpT, _
→
binding_contextName, Binding
recordhintT, namedglobalSym, address
else
[]

An object is read and written through its place: lv(Addr, Type), or bits(Addr, Type, Pos, W), a bit-field of W bits from bit Pos of the Type at Addr. A bit-field is read by shifting it to the bottom of its unit, extended by its type's sign, and written by merging it into the unit; its value has its promoted type (bitfield_promotion/3).

place_loaded+P, +U, +Ctx, -R//

R is the value, of type U, of the object at place P.

3879place_loadedlvAddr, PU, U, Ctx, vV, U ⟶
its access's alignment
PU = vec_, _, _
!
loadedPU, Addr, Ctx, vV, _
3881place_loadedlvAddr, _, U, Ctx, R ⟶
loadedU, Addr, Ctx, R
3882place_loadedbitsAddr, T, Pos, W, _, Ctx, R ⟶
place_readbitsAddr, T, Pos, W, Ctx, R
place_read+P, +Ctx, -R//

R is the scalar value at place P.

3886place_readlvP, U, Ctx, R ⟶
U = vec_, _, _
!
loadedU, P, Ctx, R
3887place_readlvP, U, _, vP, U ⟶

as a struct's

3888place_readlvP, U, Ctx, vCur, U ⟶
load_typedU, P, Ctx, Cur
3889place_readbitsAddr, T, Pos, W, Ctx, R ⟶
load_typedT, Addr, Ctx, Unit
Up is KB−Pos−W
shiftedK, shl, Unit, Up, V1
bits_extendedT, W, V1, R
place_write+P, +Value0, +Ctx, -R//

store Value0, converted, at place P; R is the value stored.

3897place_writelvP, U, Value0, _, R ⟶
convertedValue0, U, R
R = vV, _
3901place_writebitsAddr, T, Pos, W, Value0, Ctx, R ⟶
convertValue0, T, vV, _
load_typedT, Addr, Ctx, Unit
Mask is 1 << W−1 << Pos
Keep is \Mask /\ 1 << KB−1
letK, and, Unit, constKeep, Kept
shiftedK, shl, V, Pos, V1
letK, and, V1, constMask, Field
letK, or, Kept, Field, New
store_typedT, New, Addr
Up is KB−W
shiftedK, shl, V, Up, V2
bits_extendedT, W, V2, R
bits_extended+T, +W, +V, -R//

R is the W-bit field at the top of V, of T's class, moved down and extended as T's sign says, of the bit-field's promoted type.

3919bits_extendedT, W, V, R ⟶
Down is KB−W
int_infoT, _, Sign
Sign ≡ signed
→
Op = sar
else
Op = shr
shiftedK, Op, V, Down, V1
convertvV1, T, P, R
shifted+K, +Op, +V, +N, -R//

R is V shifted by N bits.

3927shifted_, _, V, 0, V ⟶
!
3928shiftedK, Op, V, N, R ⟶
letK, Op, V, constN, R
3930class_bitsw, 32
3931class_bitsl, 64
load_typed+Type, +Addr, +Ctx, -V//

V is the scalar of Type loaded from Addr.

3935load_typedType, Addr, Ctx, V ⟶
load_opType, Class, Op
→
letClass, loadOp, Addr, V
else
ctx_locCtx, Loc
sema_errorLoc, 'loading a ~w value is not supported yet', Type
load_op+Type, -Class, -Op

Class and load Op hold a scalar of Type: an integer narrower than 8 bytes is loaded extended into a w.

3943load_opType, Class, Op ←
int_infoType, Size, Sign
!
type_classType, Class
Size =:= 8
→
Op = l
else
narrow_typeOp, Size, Sign
3947load_opType, Class, Class ←
type_classType, Class
store_typed+Type, +V, +Addr//

store value V of Type at Addr; a struct or union V is the address of the value, copied.

3951store_typedType, V, Addr ⟶
Type = vec_, _, _
!
envEnv
eightbytesType, Env, unknown, Ebs
forallmembereb_, N, _, Ebs, N =:= 8
→
vector_accesswrite, Type, Addr
else
vector_accesswrite, Type, Addr
copy_objectType, V, Addr
3960store_typedType, V, Addr ⟶
!
copy_objectType, V, Addr
3961store_typedType, V, Addr ⟶
store_widthType, Width
emitstoreWidth, V, Addr
store_width+Type, -Width

Width is the store of a scalar of Type.

3966store_widthptr_, p ←
!
3967store_widthfloat, s ←
!
3968store_widthdouble, d ←
!
3969store_widthldouble, x ←
!
3970store_widthType, Width ←
int_infoType, Size, _
width_bytesWidth, Size
memberchkWidth, b, h, w, l
!
offset+Addr, +Off, -P//

P is the pointer Addr + Off (Addr itself for 0, with no instruction).

3976offsetAddr, 0, Addr ⟶
!
3977offsetAddr, Off, P ⟶
letp, add, Addr, constOff, P
builtin+Name, +Args, +Ctx, -R//det

R is the value of a call of builtin Name, which no declaration shadows, on source arguments Args. Elaboration (c_types.pl, with builtin_type/2 and the *_builtin tables) types the call and marks it val(T, builtin(Name, Args)); the clauses here say what code it lowers to. Arguments no clause accepts are an error.

3986builtin'__builtin_va_start', A, _, Ctx, vconst0, void ⟶
!
envEnv
ctx_locCtx, Loc
lookup_ordinaryEnv, builtinva_args, Cursor
→
true
else
sema_errorLoc, 'va_start outside a variadic function', []
rvalA, Ctx, vP, _
emitstorep, Cursor, P
3993builtin'__builtin_va_copy', A, B, Ctx, vconst0, void ⟶
!
rvalA, Ctx, vP, _
rvalB, Ctx, vQ, _
letp, loadp, Q, V
emitstorep, V, P
3996builtin'__builtin_va_end', A, Ctx, vconst0, void ⟶
!
rvalA, Ctx, _
3998builtinName, [], _, vconstV, Type ⟶
float_builtinName, Type, V
!
4000builtin'__builtin_constant_p', Expr, _, vconstV, int ⟶
!
envEnv
builtin_constant_pExpr, Env, V
4003builtinName, Args, _, vconstV, ulong ⟶
envEnv
object_sizeName, Args, Env, V
!
4005builtin'__builtin_unreachable', [], _, vconst0, void ⟶
!
termunreachable

__builtin_trap() stops the program, as clang's llvm.trap does; the IR's unreachable is a trap instruction.

4009builtin'__builtin_trap', [], _, vconst0, void ⟶
!
termunreachable

filcc's __builtin_frame_address(0) is the frame's address as an integer, without a capability; at a nonzero level it is a run-time error with the reason below, which nanofil calls zsafety_error with. nanofil cannot express the frame's address (no IR term), so level 0 is refused.

4016builtin'__builtin_frame_address', A, Ctx, R ⟶
!
envEnv0
ctx_locCtx, Loc
catchconst_valueA, Env0, Level, _, not_constant, fail
→
true
else
sema_errorLoc, '__builtin_frame_address needs a constant level', []
Level =:= 0
→
sema_errorLoc, '__builtin_frame_address(0) is not supported: nanofil cannot express the frame address', []
else
true
new_literal99, 97, 110, 110, 111, 116, 32, 117, 115, 101, 32, 95, 95, 98, 117, 105, 108, 116, 105, 110, 95, 102, 114, 97, 109, 101, 95, 97, 100, 100, 114, 101, 115, 115, 32, 119, 105, 116, 104, 32, 110, 111, 110, 122, 101, 114, 111, 32, 97, 114, 103, 117, 109, 101, 110, 116, 46, Sym, Env0, Env
letp, copy, globalSym, Addr
emitcallvoid, globalzsafety_error, argp, Addr
convertvconst0, int, ptrvoid, R
4031builtin'__builtin_alloca', Size, Ctx, vAddr, ptrvoid ⟶
!
rvalSize, Ctx, V0
ctx_locCtx, Loc
assign_convertSize, V0, ulong, Loc, vN, _
letp, alloc16, N, Addr

The sign builtins (c_types' sign_builtin/3) clear or copy the sign bit: of a float or double through its bits, of a long double through bit 15 of the 16-bit sign and exponent at byte 8 of its 80, in memory.

4039builtinName, Args, Ctx, vR, T ⟶
sign_builtinName, Op, T
!
ctx_locCtx, Loc
Op ≡ abs
→
lengthArgs, 1
else
lengthArgs, 2
→
true
else
sema_errorLoc, 'wrong number of arguments to ~w', Name
sign_argsArgs, T, Ctx, Xs
sign_resultT, Op, Xs, Ctx, R

A square root builtin (c_types' root_builtin/3) is the IR's sqrt under -fno-math-errno, else a call of libm's F, which sets errno.

4049builtinName, A, Ctx, vD, T ⟶
root_builtinName, F, T
!
rvalA, Ctx, V0
ctx_locCtx, Loc
assign_convertA, V0, T, Loc, vX, _
getenv'NANOFIL_MATH_ERRNO', '0'
→
letK, sqrt, X, D
T ≡ ldouble
→
sema_errorLoc, '~w under -fmath-errno is not supported', Name
else
emitcallresultD, K, globalF, argK, X

A trunc builtin (c_types' trunc_builtin/2): a magnitude below Big, the first with no fraction bits, goes through the integer it truncates to and takes back x's sign; anything else is integral, infinite or a NaN.

4065builtinName, A, Ctx, R ⟶
!
rvalA, Ctx, V0
ctx_locCtx, Loc
assign_convertA, V0, T, Loc, vX, _
trunc_formatT, K, IK, ToInt, FromInt, Big
sign_resultT, abs, X, Ctx, Ax
letw, cmplt, K, Ax, constBig, Small
labelLJoin
termbranchSmall, LA, LB
letIK, ToInt, X, I
letK, FromInt, I, F0
sign_resultT, copysign, F0, X, Ctx, RA
hereLA1
termgotoLJoin
letK, add, X, const0, RB
hereLB1
termgotoLJoin
join_armsT, LJoin, LA1−RA, LB1−RB, R
4081builtinName, A, Ctx, vD, double ⟶
!
rvalA, Ctx, V0
ctx_locCtx, Loc
assign_convertA, V0, double, Loc, vX, _
emitcallresultD, d, globalF, argd, X

__builtin_isnan(x): x is unordered with itself. A float compares as itself; anything else (double, long double, an integer) as a double, which keeps a NaN a NaN and makes no other value one.

4091builtin'__builtin_isnan', A, Ctx, vN, int ⟶
!
rvalA, Ctx, V0
V0 = vX, float
→
K = s
else
ctx_locCtx, Loc
assign_convertA, V0, double, Loc, vX, _
K = d
letw, cmpuo, K, X, X, N

The classification builtins (c_types' classify_builtin/1) read the float's bits: Abs is them without the sign; Inf and MinNormal are the format's infinity and smallest normal magnitude.

4101builtin'__builtin_fpclassify', Nan, Inf, Normal, Sub, Zero, A, Ctx, vN, int ⟶
rvalA, Ctx, vX, ldouble
!
ld_classesX, Ctx, cIsNan, IsInf, IsNormal, IsSub, IsZero, _, _
class_sumIsNan−Nan, IsInf−Inf, IsNormal−Normal, IsSub−Sub, IsZero−Zero, Ctx, const0, N
4106builtin'__builtin_fpclassify', Nan, Inf, Normal, Sub, Zero, A, Ctx, vN, int ⟶
!
float_bitsA, Ctx, fK, _, Abs, InfBits, MinNormal
letw, cmpugt, K, Abs, InfBits, IsNan
letw, cmpeq, K, Abs, InfBits, IsInf
normal_flagK, Abs, InfBits, MinNormal, IsNormal
letw, cmpeq, K, Abs, const0, IsZero
letw, cmpult, K, Abs, MinNormal, Small
letw, sub, Small, IsZero, IsSub
class_sumIsNan−Nan, IsInf−Inf, IsNormal−Normal, IsSub−Sub, IsZero−Zero, Ctx, const0, N
4116builtin'__builtin_signbitf', A, Ctx, R ⟶
!
builtin'__builtin_signbit', casttypetspecfloat, base, A, Ctx, R

A long double's sign is the top bit of its 80, stored to memory: bit 15 of the 16-bit sign and exponent at byte 8.

4120builtinName, A, Ctx, vN, int ⟶
memberchkName, '__builtin_signbit', '__builtin_signbitl'
rvalA, Ctx, V0
Name ≡ '__builtin_signbitl'
or
V0 = v_, ldouble
!
ctx_locCtx, Loc
assign_convertA, V0, ldouble, Loc, vX, _
local_slotldouble, Slot
store_typedldouble, X, Slot
letp, add, Slot, const8, Top
load_typedushort, Top, Ctx, Exp
letw, shr, Exp, const15, N
4131builtinName, A, Ctx, vN, int ⟶
rvalA, Ctx, vX, ldouble
!
ld_classesX, Ctx, C
ld_classifiedName, C, N
4136builtinName, A, Ctx, vN, int ⟶
!
float_bitsA, Ctx, fK, Bits, Abs, InfBits, MinNormal
classifiedName, K, Bits, Abs, InfBits, MinNormal, N

__builtin_flt_rounds(): libc's fegetround() reads the x87 control word, as clang's lowering does, and its FE_* value R (0, 0x400, 0x800, 0xc00) maps to FLT_ROUNDS by (0x2D >> (R >> 9)) & 3, clang's formula.

4143builtin'__builtin_flt_rounds', [], _, vN, int ⟶
!
emitcallresultR, w, globalfegetround, []
letw, shr, R, const9, S
letw, shr, const0x2D, S, M
letw, and, M, const3, N

The atomics c_types leaves as builtins (atomic_builtin/4): pointer loads and stores at any order, which Fil-C's helpers make single-copy atomic, a sequentially consistent integer store, the store and a fence, any store to an _Atomic object, and __c11_atomic_init's plain store. The order is a constant without side effects.

4154builtinName, P, _, Ctx, vD, T ⟶
memberchkName, '__atomic_load_n', '__c11_atomic_load'
!
rvalP, Ctx, vAddr, ptrT0
letp, atomic_loadp, Addr, D
4159builtinName, P, V|O, Ctx, vconst0, void ⟶
memberchkName−O, '__atomic_store_n'−_, '__c11_atomic_store'−_, '__c11_atomic_init'−[]
!
rvalP, Ctx, vAddr, ptrT0
rvalV, Ctx, V0
ctx_locCtx, Loc
assign_convertV, V0, T, Loc, vX, _
O = OE
→
envEnv
const_valueOE, Env, Order, _
atomic_storedT, X, Addr, Order
else
store_typedT, X, Addr

A seq_cst thread fence is mfence; x86-64 already orders loads and stores for the others, and a signal fence orders nothing a thread sees.

4172builtinName, OE, _, vconst0, void ⟶
memberchkName, '__c11_atomic_thread_fence', '__c11_atomic_signal_fence', '__atomic_thread_fence', '__atomic_signal_fence'
!
envEnv
const_valueOE, Env, 5, _
atom_concat_, '_thread_fence', Name
→
emitfence
else
[]

The read-modify-writes (c_types atomic_builtin/4): a cas loop, or one cas to compare and exchange. A __c11 fetch-and-op of a pointer returns the old address without a capability, as filcc's does.

4184builtinName, P, V|_, Ctx, R ⟶
rmw_builtinName, Family, Op, Which
!
rvalP, Ctx, vAddr, ptrT0
rvalV, Ctx, V0
ctx_locCtx, Loc
T = atomicS, Sizeexchange (c_types)
→
assign_convertV, V0, S, Loc, V1
atomic_su_wordS, Size, V1, Ctx, W
atomic_wordSize, WT
rmwWT, Addr, xchg, vW, WT, Ctx, vOld, _, _
local_slotT, Slot
store_typedWT, Old, Slot
R = vSlot, S
else
atomic_rmw_valueFamily, Op, Which, T, Addr, V, V0, Loc, Ctx, R
4197builtinName, P, E, D|_, Ctx, vEq, bool ⟶
memberchkName, '__c11_atomic_compare_exchange_strong', '__c11_atomic_compare_exchange_weak', '__atomic_compare_exchange_n'
!
rvalP, Ctx, vAddr, ptrT0
rvalE, Ctx, vEAddr, _
rvalD, Ctx, D0
ctx_locCtx, Loc
T = atomicS, Sizec_types atomic_su_type/3
→
assign_convertD, D0, S, Loc, D1
atomic_wordSize, WT
atomic_su_wordS, Size, vEAddr, S, Ctx, Expected
atomic_su_wordS, Size, D1, Ctx, New
local_slotT, Slot
else
assign_convertD, D0, T, Loc, vNew, _
WT = T
load_typedT, EAddr, Ctx, Expected
cas_onceWT, Addr, Expected, New, Got, Eq
*Expected gets *P only on failure (7.17.7.4p2).
termbranchEq, LJ, LF
T = atomicS, _
→
store_typedWT, Got, Slot
copy_objectS, Slot, EAddr
else
store_typedT, Got, EAddr
termgotoLJ
4225builtinName, P, O, N|_, Ctx, R ⟶
memberchkName−Which, '__sync_val_compare_and_swap'−val, '__sync_bool_compare_and_swap'−bool
!
rvalP, Ctx, vAddr, ptrT0
rvalO, Ctx, O0
rvalN, Ctx, N0
ctx_locCtx, Loc
assign_convertO, O0, T, Loc, vExpected, _
assign_convertN, N0, T, Loc, vNew, _
cas_onceT, Addr, Expected, New, Got, Eq
Which ≡ val
→
R = vGot, T
else
R = vEq, bool
4236builtin'__sync_lock_release', P|_, Ctx, vconst0, void ⟶
!
rvalP, Ctx, vAddr, ptrT0
atomic_storedT, const0, Addr, 3
4240builtin'__sync_synchronize', [], _, vconst0, void ⟶
!
emitfence
4241builtin'__atomic_test_and_set', P, _, Ctx, vSet, bool ⟶
!
rvalP, Ctx, vAddr, _
rmwuchar, Addr, xchg, vconst1, uchar, Ctx, vOld, _, _
letw, cmpne, w, Old, const0, Set
4245builtin'__atomic_clear', P, OE, Ctx, vconst0, void ⟶
!
rvalP, Ctx, vAddr, _
envEnv
const_valueOE, Env, Order, _
atomic_storeduchar, const0, Addr, Order
4250builtinzhas_union, P, Ctx, vconst0, void ⟶
!
rvalP, Ctx, _
4251builtin'__builtin_assume_aligned', P|Rest, Ctx, R ⟶
!
rvalP, Ctx, VP
ctx_locCtx, Loc
assign_convertP, VP, ptrvoid, Loc, R

x86 CPU dispatch, as clang lowers it: __builtin_cpu_init calls the runtime's __cpu_indicator_init, and __builtin_cpu_supports and __builtin_cpu_is read a 32-bit word of __cpu_model or __cpu_features2 (c_types cpu_feature/4, cpu_model_value/3). The argument is a string literal; an unknown feature string is 0, an unknown cpu_is name refused.

4261builtin'__builtin_cpu_init', [], _, vconst0, void ⟶
!
emitcallresultT, w, global'__cpu_indicator_init', []
4263builtinName, Args, Ctx, vN, int ⟶
memberchkName, '__builtin_cpu_supports', '__builtin_cpu_is'
!
ctx_locCtx, Loc
Args = strBytes
→
atom_codesF, Bytes
else
sema_errorLoc, '~w needs a string literal', Name
Name ≡ '__builtin_cpu_supports'
→
cpu_featureF, Sym, Off, Mask
→
cpu_wordSym, Off, Ctx, W
letw, and, W, constMask, M
letw, cmpeq, w, M, constMask, N
else
N = const0
else
→
true
else
sema_errorLoc, 'invalid cpu name ~w for __builtin_cpu_is', F
cpu_word'__cpu_model', Off, Ctx, W
letw, cmpeq, w, W, constV, N
4280builtin'__builtin_expect', A, B, Ctx, R ⟶
!
rvalA, Ctx, VA
rvalB, Ctx, _
ctx_locCtx, Loc
assign_convertA, VA, long, Loc, R
4285builtinName, Args, Ctx, R ⟶
overflow_builtinName, Op, Fixed
!
checked_arithmeticArgs, Op, Fixed, Ctx, R

__builtin_ia32_rdtsc, _rdtsc's builtin, is the rdtsc instruction (asm_fixed, as its inline asm is): edx:eax as one unsigned long long.

4290builtin'__builtin_ia32_rdtsc', [], Ctx, R ⟶
!
asm_fixed_tempsa, d, Outs
Outs = a−A, d−D
emitasm_fixedrdtsc, [], Outs
convertvA, uint, ullong, Lo
convertvD, uint, ullong, Hi0
binaryshl, Hi0, vconst32, int, Ctx, Hi
binaryor, Hi, Lo, Ctx, R

GNU vector builtins (c_types vector_builtin/3, generic_vector_builtin/1), computed element by element: each operand is converted to its parameter's type, its elements loaded from the copy it is, and the result's elements stored to a new local (vector_op//6).

4300builtinName, Args, Ctx, R ⟶
vector_builtinName, Ps, Ret
!
ctx_locCtx, Loc
lengthArgs, NA
lengthPs, NP
NA =:= NP
→
true
else
sema_errorLoc, 'wrong arguments to ~w', Name
atom_concat'__builtin_ia32_', Short, Name
vector_argsArgs, Ps, Ctx, Vs
vector_opShort, Args, Vs, Ret, Ctx, R
4307builtinName, Args, Ctx, R ⟶
!
envEnv
ctx_locCtx, Loc
vector_builtin_typeName, Args, Env, T
→
true
else
sema_errorLoc, 'wrong arguments to ~w', Name
generic_vector_opName, Args, T, Ctx, R
4315builtinName, A, Ctx, R ⟶
bit_builtinName, Op, Type
!
rvalA, Ctx, V0
ctx_locCtx, Loc
assign_convertA, V0, Type, Loc, V1
convertV1, ulong, vX, _
int_infoType, Bytes, _
Bits is 8 × Bytes
bit_countOp, Bits, X, N
convertvN, long, int, R
4324builtinName, A, Ctx, R ⟶
builtin_typeName, Type
atom_concat'__builtin_bswap', _, Name
!
rvalA, Ctx, V0
ctx_locCtx, Loc
assign_convertA, V0, Type, Loc, V1
convertV1, ulong, vX, _
int_infoType, Bytes, _
Last is Bytes−1
numlist1, Last, Is
swapped_bytes0|Is, X, Last, none, Y
convertvY, ulong, Type, R

A rotate by N of a W-bit X is (X << (N & W-1)) | (X >> (-N & W-1)), shifted the other way for a right rotate, so N = 0 shifts by nothing.

4337builtinName, A, B, Ctx, R ⟶
rotate_builtinName, Dir, Type
!
rvalA, Ctx, VA0
rvalB, Ctx, VB0
ctx_locCtx, Loc
int_infoType, Bytes, _
Mask is 8 × Bytes−1
assign_convertA, VA0, Type, Loc, VA
assign_convertB, VB0, Type, Loc, VB
convertVA, ulong, vX, _
convertVB, ulong, vN, _
letl, and, N, constMask, S
letl, neg, N, M
letl, and, M, constMask, T
Dir ≡ left
→
First = shl
Second = shr
else
First = shr
Second = shl
letl, First, X, S, Y1
letl, Second, X, T, Y2
letl, or, Y1, Y2, Y
convertvY, ulong, Type, R
4349builtinName, _, Ctx, _ ⟶
ctx_locCtx, Loc
sema_errorLoc, 'wrong arguments to ~w', Name
rmw+U, +Addr, +Op, +V, +Ctx, -Old, -New//

the atomic read-modify-write of the U at Addr, New being Old combined with the value V by Op (a binary operator, or xchg, nand, max or min): read the U, then repeat a cas of the read value for its combination until the cas finds it unchanged (ir.pl). The first read of a pointer is atomic, so the pair it combines is one; a float or double goes through its bits, compared as an integer. Old and New are those of the cas that succeeded.

4360rmwU, Addr, Op, V, Ctx, vOld, U, vNew, U ⟶
cas_reprU, T, K, Bits
T ≡ p
→
letp, atomic_loadp, Addr, Old0
else
letK, loadT, Addr, B0
from_bitsBits, B0, Old0
termgotoLL
start_phiLL, phiOld, KU, LE−Old0, LB−Got
rmw_newOp, vOld, U, V, U, Ctx, vNew, _
to_bitsBits, Old, OB
to_bitsBits, New, NB
letK, casT, OB, NB, Addr, GB
cas_equalK, GB, OB, Eq
from_bitsBits, GB, Got
termbranchEq, LD, LL

rmw_aligned(+X, +Ctx)//: X, the object tree of an atomic read-modify-write, is not an underaligned member (c_types underaligned_member/4); rmw_arg_aligned(+P, +Ctx)// is that for the object a builtin's pointer argument P takes the address &X of.

4380rmw_arg_alignedP, Ctx ⟶
envEnv
elaborateP, Env, val_, addrX
→
else
[]
4385rmw_alignedX, Ctx ⟶
envEnv
catchtype_size_alignT, Env, W, _, bad_, fail
underaligned_memberX, Env, W, Name
→
ctx_locCtx, Loc
sema_errorLoc, 'an atomic read-modify-write of ~w, a member of a packed struct or union, may be misaligned; clang calls libatomic for it, which nanofil does not have', Name
else
true
cas_once+T, +Addr, +E, +N, -Got, -Eq//

one cas of the T at Addr from E to N; Got is the old value and Eq whether it was E.

4397cas_onceT, Addr, E, N, Got, Eq ⟶
cas_reprT, CT, K, Bits
to_bitsBits, E, EB
to_bitsBits, N, NB
letK, casCT, EB, NB, Addr, GB
cas_equalK, GB, EB, Eq
from_bitsBits, GB, Got
cas_repr+U, -T, -K, -Bits

an atomic U is cas(T) of class K; a float or double is the cas of its bits (Bits s or d), else Bits is none.

4406cas_reprfloat, uw, w, s ←
!
4407cas_reprdouble, l, l, d ←
!
4408cas_reprU, T, K, none ←
load_opU, K, T
4410cas_equalp, A, B, Eq ⟶
!
letl, ptoi, A, IA
letl, ptoi, B, IB
letw, cmpeq, l, IA, IB, Eq
4413cas_equalK, A, B, Eq ⟶
letw, cmpeq, K, A, B, Eq
4415to_bitsnone, V, V ⟶
[]
4416to_bitss, V, B ⟶
letw, cast, V, B
4417to_bitsd, V, B ⟶
letl, cast, V, B
4418from_bitsnone, B, B ⟶
[]
4419from_bitss, B, V ⟶
lets, cast, B, V
4420from_bitsd, B, V ⟶
letd, cast, B, V
rmw_new+Op, +Old, +V, +U, +Ctx, -New//

New, of U, is Old combined with V by Op.

4424rmw_newxchg, _, V, U, _, New ⟶
!
convertV, U, New
4425rmw_newnand, Old, V, U, Ctx, New ⟶
!
binaryand, Old, V, Ctx, A
unarybnot, A, Ctx, B
convertB, U, New
4427rmw_newOp, Old, V, U, Ctx, New ⟶
memberchkOp−Cmp, max−gt, min−lt
!
Old where Old Cmp V, else V: a mask, so the loop stays one block.
binaryCmp, Old, V, Ctx, C
convertC, U, C1
unaryneg, C1, Ctx, M
convertM, U, M1
binaryand, Old, M1, Ctx, A
unarybnot, M1, Ctx, NM
binaryand, V, NM, Ctx, B
binaryor, A, B, Ctx, O
convertO, U, New
4433rmw_newOp, Old, V, U, Ctx, New ⟶
binaryOp, Old, V, Ctx, B
convertB, U, New
atomic_stored+T, +X, +Addr, +Order//

X, of T, stored atomically at Addr with memory order Order: a pointer through Fil-C's helper, which is seq_cst at every order, anything else by the store, then a fence when seq_cst.

4440atomic_storedptr_, X, Addr, _ ⟶
!
emitatomic_storep, X, Addr
4441atomic_storedT, X, Addr, Order ⟶
store_typedT, X, Addr
Order =:= 5
→
emitfence
else
[]
4445classified'__builtin_isinf_sign', K, Bits, Abs, InfBits, _, N ⟶
letw, cmpeq, K, Abs, InfBits, IsInf
letw, cmpslt, K, Bits, const0, Neg
letw, and, IsInf, Neg, T
letw, add, T, T, T2
letw, sub, IsInf, T2, N
4450classified'__builtin_isinf', K, _, Abs, InfBits, _, N ⟶
letw, cmpeq, K, Abs, InfBits, N
4452classified'__builtin_isfinite', K, _, Abs, InfBits, _, N ⟶
letw, cmpult, K, Abs, InfBits, N
4454classified'__builtin_isnormal', K, _, Abs, InfBits, MinNormal, N ⟶
normal_flagK, Abs, InfBits, MinNormal, N
4456classified'__builtin_signbit', K, Bits, _, _, _, N ⟶
letw, cmpslt, K, Bits, const0, N
ld_classes+X, +Ctx, -C//

C = c(Nan, Inf, Normal, Sub, Zero, IsNormal, Neg), 0 or 1 each, for the long double X, read from its stored x87 bits: E the 15-bit exponent, S the 64-bit significand with its explicit integer bit I. As clang's lowering classifies them (measured against filcc): a NaN has E = 0x7fff and S other than I alone, or E nonzero and no I (an unnormal, pseudo-infinity or pseudo-NaN); an infinity has E = 0x7fff and S = I alone; zero and subnormal have E = 0 and no I; the rest, a pseudo-denormal (E = 0 with I) included, are fpclassify's normal. __builtin_isnormal (IsNormal) wants 0 < E < 0x7fff and I.

4468ld_classesX, Ctx, cNaN, Inf, Normal, Sub, Zero, IsNormal, Neg ⟶
local_slotldouble, Slot
store_typedldouble, X, Slot
load_typedulong, Slot, Ctx, S
letp, add, Slot, const8, Top
load_typedushort, Top, Ctx, SE
letw, shr, SE, const15, Neg
letw, and, SE, const0x7fff, E
letw, cmpeq, w, E, const0, EZ
letw, xor, EZ, const1, ENZ
letw, cmpeq, w, E, const0x7fff, EMax
letw, xor, EMax, const1, ENMax
letw, cmpslt, l, S, const0, I
letw, xor, I, const1, NoI
letw, cmpeq, l, S, const0, SZ
letl, shl, S, const1, F
letw, cmpne, l, F, const0, FNZ
letw, and, ENZ, NoI, Bad
letw, and, EMax, FNZ, HiNaN
letw, or, Bad, HiNaN, NaN
letw, and, EMax, I, EI
letw, xor, FNZ, const1, FZ
letw, and, EI, FZ, Inf
letw, and, EZ, SZ, Zero
letw, xor, SZ, const1, SNZ
letw, and, EZ, NoI, EZNoI
letw, and, EZNoI, SNZ, Sub
letw, add, NaN, Inf, C1
letw, add, C1, Zero, C2
letw, add, C2, Sub, C3
letw, xor, C3, const1, Normal
letw, and, ENZ, ENMax, Mid
letw, and, Mid, I, IsNormal
4488ld_classified'__builtin_isinf', c_, Inf, _, _, _, _, _, Inf ⟶
[]
4489ld_classified'__builtin_isinf_sign', c_, Inf, _, _, _, _, Neg, N ⟶
letw, and, Inf, Neg, T
letw, add, T, T, T2
letw, sub, Inf, T2, N
4491ld_classified'__builtin_isfinite', cNaN, Inf, _, _, _, _, _, N ⟶
letw, or, NaN, Inf, T
letw, xor, T, const1, N
4493ld_classified'__builtin_isnormal', c_, _, _, _, _, IsNormal, _, IsNormal ⟶
[]

A normal magnitude is in [MinNormal, Inf): one unsigned compare.

4496normal_flagK, Abs, InfBits, MinNormal, N ⟶
letK, sub, Abs, MinNormal, D
InfBits = constI
MinNormal = constM
R is I−M
letw, cmpult, K, D, constR, N
class_sum+Flags, +Ctx, +Acc, -Sum//

Sum is Acc plus each Flag-E pair's flag (0 or 1) times E, E converted to int.

4503class_sum[], _, Sum, Sum ⟶
[]
4504class_sumFlag−E|Fs, Ctx, Acc, Sum ⟶
rvalE, Ctx, V0
ctx_locCtx, Loc
assign_convertE, V0, int, Loc, vX, _
letw, mul, Flag, X, T
letw, add, Acc, T, Acc1
class_sumFs, Ctx, Acc1, Sum
float_bits+A, +Ctx, -F//

F = f(K, Bits, Abs, Inf, MinNormal), the bits of A as an integer of class K: a float's at w, anything else's as a double at l.

4514float_bitsA, Ctx, fK, Bits, Abs, constInf, constMinNormal ⟶
rvalA, Ctx, V0
ctx_locCtx, Loc
V0 = vX, float
→
K = w
Inf = 0x7f800000
MinNormal = 0x00800000
Mask = 0x7fffffff
letw, cast, X, Bits
else
assign_convertA, V0, double, Loc, vX, _
K = l
Inf = 0x7ff0000000000000
MinNormal = 0x0010000000000000
Mask = 0x7fffffffffffffff
letl, cast, X, Bits
letK, and, Bits, constMask, Abs
sign_args+Args, +T, +Ctx, -Xs//

Xs are the values of Args converted to T, as to a prototyped parameter.

4528sign_args[], _, _, [] ⟶
[]
4529sign_argsA|As, T, Ctx, X|Xs ⟶
rvalA, Ctx, V0
ctx_locCtx, Loc
assign_convertA, V0, T, Loc, vX, _
sign_argsAs, T, Ctx, Xs
sign_result+T, +Op, +Xs, +Ctx, -R//

R is sign_builtin/3's Op of Xs at type T.

4536sign_resultT, Op, Xs, _, R ⟶
sign_bitsT, F, K, Sign, Mask
!
sign_bits_ofXs, K, Bs
Op ≡ abs
Bs = B
→
letK, and, B, constMask, B1
else
Bs = Bx, By
letK, and, Bx, constMask, Mx
letK, and, By, constSign, Sy
letK, or, Mx, Sy, B1
letF, cast, B1, R
4547sign_resultldouble, Op, Xs, Ctx, R ⟶
sign_topsXs, Ctx, Slot−Top|_, E|Es
Op ≡ abs
→
letw, and, E, const0x7fff, E1
else
Es = Ey
letw, and, E, const0x7fff, Mx
letw, and, Ey, const0x8000, Sy
letw, or, Mx, Sy, E1
store_typedushort, E1, Top
load_typedldouble, Slot, Ctx, R
trunc_formatType, K, IK, ToInt, FromInt, Big

Type's class K, the integer class IK and conversions a magnitude below Big (2^23 or 2^52, as bits) truncates through.

4562trunc_formatfloat, s, w, stosi, swtof, 0x4b000000
4563trunc_formatdouble, d, l, dtosi, sltof, 0x4330000000000000
4565sign_bitsfloat, s, w, 0x80000000, 0x7fffffff
4566sign_bitsdouble, d, l, 0x8000000000000000, 0x7fffffffffffffff
4568sign_bits_of[], _, [] ⟶
[]
4569sign_bits_ofX|Xs, K, B|Bs ⟶
letK, cast, X, B
sign_bits_ofXs, K, Bs
sign_tops+Xs, +Ctx, -SlotTops, -Es//

each long double of Xs stored in a local slot, the address of its sign and exponent, and them.

4573sign_tops[], _, [], [] ⟶
[]
4574sign_topsX|Xs, Ctx, Slot−Top|STs, E|Es ⟶
local_slotldouble, Slot
store_typedldouble, X, Slot
letp, add, Slot, const8, Top
load_typedushort, Top, Ctx, E
sign_topsXs, Ctx, STs, Es
bit_count+Op, +Bits, +X, -N//

N is the number of trailing zeros, leading zeros or ones of X, a Bits-wide unsigned value zero-extended to 64 bits; branch-free, so the operand 0 needs no block. ctz and clz of 0 are Bits, as Fil-C defines them since deluge (KillUB): ctz counts X with bit Bits set, so it stops there (filc/tests' clz/ctz probe).

4586bit_countpopcount, _, X, N ⟶
4587bit_countctz, Bits, X0, N ⟶
Bits < 64
→
Stop is 1 << Bits
letl, or, X0, constStop, X
else
X = X0
letl, neg, X, M
letl, and, X, M, Low
letl, sub, Low, const1, Below
popcount64Below, N

ffs: popcount of the bits up to the lowest set one, times 1 if X is nonzero (the top bit of X | -X), so 0 for 0.

4596bit_countffs, _, X, N ⟶
letl, neg, X, M
letl, and, X, M, Low
letl, add, Low, Low, Low2
letl, sub, Low2, const1, Upto
popcount64Upto, P
letl, or, X, M, Any
letl, shr, Any, const63, NZ
letl, mul, P, NZ, N
4602bit_countclz, Bits, X, N ⟶
smear1, 2, 4, 8, 16, 32, X, S
letl, sub, P, constBits, NegN
letl, neg, NegN, N
4606smear[], X, X ⟶
[]
4607smearSh|Shs, X, Y ⟶
letl, shr, X, constSh, T
letl, or, X, T, X1
smearShs, X1, Y
4610popcount64X, N ⟶
letl, shr, X, const1, A1
letl, and, A1, const0x5555555555555555, A2
letl, sub, X, A2, B
letl, and, B, const0x3333333333333333, C1
letl, shr, B, const2, C2
letl, and, C2, const0x3333333333333333, C3
letl, add, C1, C3, C
letl, shr, C, const4, D1
letl, add, C, D1, D2
letl, and, D2, const0x0f0f0f0f0f0f0f0f, D
letl, mul, D, const0x0101010101010101, E
letl, shr, E, const56, N
swapped_bytes+Is, +X, +Last, +Y0, -Y//

Y is Y0 (none for nothing yet) with each byte I in Is of X moved to byte Last - I.

4623swapped_bytes[], _, _, Y, Y ⟶
[]
4624swapped_bytesI|Is, X, Last, Y0, Y ⟶
From is 8 × I
To is 8 × Last−I
letl, shr, X, constFrom, B0
letl, and, B0, const255, B
letl, shl, B, constTo, Moved
Y0 ≡ none
→
Y1 = Moved
else
letl, or, Y0, Moved, Y1
swapped_bytesIs, X, Last, Y1, Y

The generic builtins do NOT use the usual arithmetic conversions. Keep the exact result in two l limbs, then compare it with the stored value extended back to two limbs. 128 bits suffice for all supported integer operands (at most 64 bits), including unsigned 64-bit products.

4634checked_arithmeticArgs, Op, Fixed, Ctx, vFlag, bool ⟶
ctx_locCtx, Loc
Args = A, B, P
→
true
else
sema_errorLoc, 'checked arithmetic requires three arguments', []
rvalA, Ctx, VA0
rvalB, Ctx, VB0
rvalP, Ctx, VP
VA0 = v_, TA
VB0 = v_, TB
VP = vAddr, ptrType
Type ≢ bool
Fixed ≡ generic
or
Fixed ≡ Type
→
true
else
sema_errorLoc, 'invalid checked arithmetic operand types', []
overflow_operandFixed, VA0, VA
overflow_operandFixed, VB0, VB
overflow_exactOp, X, XH, Y, YH, Low, High
convertvLow, ulong, Type, vStored, Type
store_typedType, Stored, Addr
overflow_widenvStored, Type, Back, BackH
letw, cmpne, l, Low, Back, BadLow
letw, cmpne, l, High, BackH, BadHigh
letw, or, BadLow, BadHigh, Flag
4655overflow_operandgeneric, V, V ⟶
!
4656overflow_operandType, V, R ⟶
convertV, Type, R

Extension to l must explicitly truncate a w value first: int_convert can reuse an l temp after a cast to int, as w consumers ignore its top.

4660overflow_widenvV, Type, Low, High ⟶
int_infoType, Size, Sign
Size < 8
→
Sign ≡ signed
→
Ext = sw
else
Ext = uw
letl, extExt, V, Low
else
Low = V
Sign ≡ signed
→
letl, sar, Low, const63, High
else
High = const0
4672overflow_exactadd, X, XH, Y, YH, Low, High ⟶
letl, add, X, Y, Low
letw, cmpult, l, Low, X, Carry
letl, extuw, Carry, C
letl, add, XH, YH, H
letl, add, H, C, High
4677overflow_exactsub, X, XH, Y, YH, Low, High ⟶
letl, sub, X, Y, Low
letw, cmpult, l, X, Y, Borrow
letl, extuw, Borrow, C
letl, sub, XH, YH, H
letl, sub, H, C, High
4682overflow_exactmul, X, XH, Y, YH, Low, High ⟶
letl, mul, X, Y, Low
letl, and, X, const4294967295, XL
letl, shr, X, const32, XU
letl, and, Y, const4294967295, YL
letl, shr, Y, const32, YU
letl, mul, XL, YL, LL
letl, mul, XU, YL, UL
letl, mul, XL, YU, LU
letl, mul, XU, YU, UU
letl, shr, LL, const32, LLH
letl, and, UL, const4294967295, ULL
letl, and, LU, const4294967295, LUL
letl, add, LLH, ULL, Mid0
letl, add, Mid0, LUL, Mid
letl, shr, Mid, const32, Carry
letl, shr, UL, const32, ULH
letl, shr, LU, const32, LUH
letl, add, UU, ULH, H0
letl, add, H0, LUH, H1
letl, add, H1, Carry, H2
Signed inputs contribute -Y or -X to the unsigned product's high limb.
letl, and, XH, Y, CX
letl, and, YH, X, CY
letl, sub, H2, CX, H3
letl, sub, H3, CY, High
type_size+TypeName, +Env, +Loc, -Size

Size is sizeof(TypeName), a type of constant size.

4704type_sizeTypeName, Env0, Loc, Size ←
A type name can define a tag (sizeof(union {...}), dash's
SHELL_SIZE), which its own size needs.
type_name_typeTypeName, Type, Env0, Env
supportedType, Loc
catchsizeof_sizeType, Env, Size, badWhy, sema_errorLoc, 'sizeof: ~w', Why
type_name+TypeName, -Type, +Env, +Loc

Type is the type TypeName names in a constant initializer, where nothing is evaluated: a variably modified one is refused.

4715type_nameTypeName, Type, Env, Loc ←
type_name_typeTypeName, Type, Env, _
supportedType, Loc
→
sema_errorLoc, 'a variably modified type in a constant initializer', []
else
true
evaluated_type+TypeName, +Ctx, -Type//

Type is the type TypeName names where it is evaluated (a cast, va_arg, sizeof of a variable length array type, a compound literal), its size expressions evaluated there (6.8p4).

4727evaluated_typeTypeName, Ctx, Type ⟶
envEnv
ctx_locCtx, Loc
type_name_typeTypeName, Type0, Env, _
supportedType0, Loc
realizedType0, Type, Ctx
supported+Type, +Loc

throw a semantic error if nanofil does not support Type (c_types' unsupported_type/3).

4735supportedType, Loc ←
unsupported_typeType, F, As
→
sema_errorLoc, F, As
else
true
vm_typed+T, +L, -U

U is the type code generation gives a tree of type T, when lowering knows it as L. A variably modified T may still hold the size expressions of a type name that lowering has evaluated since, so L, which has the lengths, is used; otherwise T unqualified.

4742vm_typedT, L, U ←
→
U = L
else

Variable length arrays (C11 6.7.6.2). The type of each has an unevaluated length vla(E), until lowering reaches the declaration or type name: realized//3 evaluates E there, once, and the type holds the value as dyn(V). extent//3 is the size of a type then: a product of those lengths and a constant. A goto or switch may not jump into the scope of an identifier of variably modified type (6.8.6.1p1, 6.8.4.2p2); vm_scope//1 enters one, and labels, cases and gotos remember the scopes they are in.

realized+Type0, -Type, +Ctx//

Type is Type0 with the size expressions of its variable length arrays evaluated, outermost first, each converted to a length of type long. Pointers and a function's return type are evaluated through; its parameters are not, since only a function's definition evaluates them, on entry.

4758realizedT, T, _ ⟶
4759realizedptrT0, ptrT, Ctx ⟶
!
realizedT0, T, Ctx
4760realizedarray_, vlastar, _, Ctx ⟶
!6.7.6.2p4
ctx_locCtx, Loc
sema_errorLoc, '[*] is allowed only in a function prototype', []
4763realizedarrayT0, vlaE, arrayT, dynV, Ctx ⟶
!
judged_rvalE, Ctx, V0
V0 = v_, LT
ctx_locCtx, Loc
→
true
else
sema_errorLoc, 'array size is not an integer', []
convertV0, long, vV, _
realizedT0, T, Ctx
4770realizedarrayT0, N, arrayT, N, Ctx ⟶
!
realizedT0, T, Ctx
4771realizedfuncR0, Ps, A, funcR, Ps, A, Ctx ⟶
realizedR0, R, Ctx
extent+Type, +Ctx, -Size//

Size is the size in bytes of Type, an l value: a constant unless Type is a variable length array.

4775extentType, Ctx, Size ⟶
!
Type = arrayT, N
extentT, Ctx, ElemSize
array_lengthN, Ctx, Length
productLength, ElemSize, Size
4781extentType, Ctx, constSize ⟶
envEnv
ctx_locCtx, Loc
catchtype_size_alignType, Env, Size, _, badWhy, sema_errorLoc, 'size of ~w (~w)', Type, Why
4787array_lengthdynV, _, V
4788array_lengthN, _, constN ←
integerN
4789array_lengthvla_, Ctx, _ ←
ctx_locCtx, Loc
sema_errorLoc, 'the length of a variable length array is not known here', []
4793productconstA, constB, constC ⟶
!
C is A × B
4794productA, const1, A ⟶
!
4795productA, B, C ⟶
letl, mul, A, B, C
vm_scope+Type//

a new identifier of Type is in scope; if Type is variably modified, this is a scope no jump may enter.

4799vm_scopeType ⟶
→
envEnv0
vm_scopesEnv0, Scopes
next_idK, Env0, Env1
bind_ordinary'vm.scopes', K|Scopes, Env1, Env
else
[]
vm_scopes+Env, -Scopes

Scopes are the variably modified scopes that are open, innermost first.

4810vm_scopesEnv, Scopes ←
lookup_ordinaryEnv, 'vm.scopes', Scopes0
→
Scopes = Scopes0
else
Scopes = []
vm_label+Key//

, vm_goto(+Name, +Loc)//: remember the open scopes of a label or case, and of a goto, in the function's label assoc.

4815vm_labelKey ⟶
envEnv
vm_scopesEnv, Scopes
statestE, C, I, B, N, Labels0, Cs, A, stE, C, I, B, N, Labels, Cs, A
put_assocvmKey, Labels0, Scopes, Labels
4820vm_gotoName, Loc ⟶
envEnv
vm_scopesEnv, Scopes
statestE, C, I, B, N, Labels0, Cs, A, stE, C, I, B, N, Labels, Cs, A
get_assocvm_gotos, Labels0, Gs
→
true
else
Gs = []
put_assocvm_gotos, Labels0, gotoName, Scopes, Loc|Gs, Labels
4826vm_goto_fromLoc, Name ⟶
vm_gotoName, Loc

vm_jumps//: no goto of the function enters a scope it is not in.

4829vm_jumps ⟶
statest_, _, _, _, _, Labels, _, _
get_assocvm_gotos, Labels, Gs
→
true
else
Gs = []
maplistvm_goto_okLabels, Gs
4834vm_goto_okLabels, gotoName, From, Loc ←
get_assocvmlabelName, Labels, To
¬append_, To, From
→
→
sema_errorLoc, 'cannot jump from this goto statement to its label: it bypasses the initialization of a variable with __attribute__((cleanup))', []
else
sema_errorLoc, 'goto into the scope of a variably modified identifier', []
else
true
entered_cleanup+To, +From

a jump from scopes From to scopes To enters the scope of a cleanup variable (cleanup_var//4).

4845entered_cleanupTo, From ←
membercleanupK, To
¬memberchkcleanupK, From
!
vm_case+Ctx//

a case or default label is in no variably modified scope its switch is not in.

4849vm_caseCtx ⟶
envEnv
vm_scopesEnv, Scopes
lookup_ordinaryEnv, 'vm.switch', Outer
→
true
else
Outer = []
Scopes ≡ Outer
→
true
ctx_locCtx, Loc
entered_cleanupScopes, Outer
→
sema_errorLoc, 'cannot jump from switch statement to this case label: it bypasses the initialization of a variable with __attribute__((cleanup))', []
else
ctx_locCtx, Loc
sema_errorLoc, 'switch jumps into the scope of a variably modified identifier', []

GNU's cleanup attribute (GCC, Common Variable Attributes): an automatic variable declared with cleanup(F) has F(&v) run when its scope ends, by falling off the end of its block, return (after the returned value is taken), break, continue or goto, in reverse order of declaration; longjmp runs none. Clang ignores it, with a warning, on a variable that is not automatic. Each such variable is a scope cleanup(K) in 'vm.scopes', so jumping into it is refused as clang refuses it, and K's call, F(&v) judged where v is declared, is kept in the label assoc with bindings of its own names, so a scope that shadows F or v cannot change it. A goto out of the scope runs the cleanups it leaves: a backward one before jumping, a forward one through a block emitted at its label. A function returning a struct copies the value before the cleanups run; clang, returning a named local in place (NRVO), lets the cleanup see the returned object.

cleanup_decls+Decls, +Loc, +Fn, +Ctx//

a cleanup attribute among a declaration's specifiers applies to each automatic variable it declares, as GCC's attributes in a specifier list apply to every declarator. Each variable's scope, and so its cleanup's, begins after its own declarator, so the cleanups of T a = .., b = ..; run b's first (C11 6.2.1p7; GLib's g_autofree char *a = NULL, *b = NULL;).

4879cleanup_decls[], _, _, _ ⟶
[]
4880cleanup_declsD|Ds, Loc, Fn, Ctx ⟶
D = dName, _, _, _, _
→
cleanup_varLoc, Name, Fn, Ctx
else
[]
cleanup_declsDs, Loc, Fn, Ctx
4887cleanup_attrSpecs, Fn ←
memberattrTokens, Specs
attr_entryTokens, Word, groupArg|_
memberchkWord, cleanup, '__cleanup__'
!
Arg = idFn
→
true
else
thrownanofil_error'the cleanup attribute takes one function name'
4895automaticd_, Type, Storage, _, _ ←
Type ≠ func_, _, _
¬memberchkStorage, static, extern
Storage ≠ thread_local_
4899cleanup_varLoc, Name, Fn, Ctx ⟶
envEnv0
lookup_ordinaryEnv0, Name, VarB
lookup_ordinaryEnv0, Fn, FnB
FnB = fn_, _, _
→
true
else
sema_errorLoc, '\'cleanup\' argument ~w is not a function', Fn
cleanup_parameterLoc, Fn, FnB, VarB
next_idK, Env0, Env1
formatatomV, 'cleanup.~w.var', K
formatatomF, 'cleanup.~w.fn', K
Binds = V−VarB, F−FnB
foldlbind_pair, Binds, Env1, Env2
elaboratecallidF, unaddr, idV, Env2, Loc, X
valueX, XV
vm_scopesEnv2, Scopes
bind_ordinary'vm.scopes', cleanupK|Scopes, Env2, Env
ctx_atCtx, Loc, Ctx1
statestE, C, I, B, N, Labels0, Cs, A, stE, C, I, B, N, Labels, Cs, A
put_assoccleanupK, Labels0, cXV, Binds, Ctx1, Labels
cleanup_parameter+Loc, +Fn, +FnBinding, +VarBinding

Fn takes one parameter, a pointer that &v converts to without a diagnostic: to void or to a type compatible with v's (clang's 'cleanup' checks; it calls Fn as if by assignment).

4923cleanup_parameterLoc, Fn, fnfunc_, PTs, _, _, _, VarB ←
VarB = var_, VT0
→
else
sema_errorLoc, 'a cleanup attribute on this variable is not supported', []
PTs = PT
unqualifiedPT, ptrQ0
Q ≡ void
or
→
true
PTs = PT
→
sema_errorLoc, '\'cleanup\' function ~w parameter has type ~w which is incompatible with type ~w', Fn, PT, ptrVT0
else
sema_errorLoc, '\'cleanup\' function ~w must take 1 parameter', Fn
4936bind_pairName−Binding, Env0, Env ←
bind_ordinaryName, Binding, Env0, Env
cleanup_keys+Env, -Ks

the cleanup variables in scope, innermost first.

4940cleanup_keysEnv, Ks ←
vm_scopesEnv, Scopes
findallK, membercleanupK, Scopes, Ks
run_cleanups+Ks//

call the cleanups of Ks, in order; none where control cannot reach.

4945run_cleanupsKs ⟶
curCur
4947run_cleanupsnone, _ ⟶
!
4948run_cleanups_, [] ⟶
!
4949run_cleanups_, K|Ks ⟶
statest_, _, _, _, _, Labels, _, _
get_assoccleanupK, Labels, cXV, Binds, Ctx
envEnv0
foldlbind_pair, Binds, Env0, Env
lowerXV, Ctx, _
leaving+Outer, -Ks//

Ks are the cleanups in scope that Outer, the cleanup keys of an enclosing point, does not have. Outer is a suffix of those in scope: a jump into a cleanup's scope is refused before (entered_cleanup/2).

4960leavingOuter, Ks ⟶
envEnv
cleanup_keysEnv, Ks0
appendKs, Outer, Ks0
→
true
else
formatatomMsg, 'cleanup scopes ~w are not enclosed by ~w', Ks0, Outer
thrownanofil_errorMsg
scope_cleanups+Outer//

at the end of a scope entered with cleanup keys Outer, run the scope's own.

4971scope_cleanupsOuter ⟶
cleanup_targets+Labels//

break or continue to each of Labels runs the cleanups declared since.

4975cleanup_targetsLabels ⟶
envEnv0
cleanup_keysEnv0, Ks
foldlcleanup_targetKs, Labels, Env0, Env
4981cleanup_targetKs, Label, Env0, Env ←
bind_ordinarycleanup_atLabel, Ks, Env0, Env
jump_cleanups+Target//

before break or continue to Target.

4984jump_cleanupsnone ⟶
[]
4985jump_cleanupssomeLabel ⟶
envEnv
lookup_ordinaryEnv, cleanup_atLabel, Outer
→
true
else
Outer = []
leavingOuter, Ks
return_cleanups+RetType, +Addr0, -Addr//

every cleanup in scope, before a return of the value at Addr0, copied first to Addr.

4992return_cleanupsRetType, Addr0, Addr ⟶
envEnv
Ks ≡ []
→
Addr = Addr0
else
local_slotRetType, Addr
copy_objectRetType, Addr0, Addr
returned_cleanup_var+Expr, +RetType, +Ks, +Loc//

refuse return v of an aggregate v with a cleanup attribute. clang treats v as a named-return-value candidate when every return names it, so the cleanup runs on the returned object itself; otherwise it copies v first. nanofil always copies first, so it refuses instead of returning a different value.

5005returned_cleanup_varExpr, RetType, Ks, Loc ⟶
Ks ≢ []
unqualifiedRetType, RT
Expr = idName
→
envEnv
statest_, _, _, _, _, Labels, _, _
lookup_ordinaryEnv, Name, VarB
memberK, Ks
get_assoccleanupK, Labels, c_, _−B|_, _
B ≡ VarB
→
sema_errorLoc, 'returning the aggregate cleanup variable ~w is not supported', Name
else
[]
else
[]
goto_cleanups+Name, +Label, -Target//

a goto to Name, whose block is Label, jumps to Target, having run the cleanups it leaves (a backward goto) or through Target, a block emitted at the label.

5021goto_cleanupsName, Label, Target ⟶
envEnv
cleanup_keysEnv, From
statest_, _, _, _, _, Labels0, _, _
From ≡ []
→
Target = Label
get_assocvmlabelName, Labels0, ToScopes
→
Target = Label
findallK, membercleanupK, ToScopes, To
leavingTo, Ks
else
labelTarget
statestE, C, I, B, N, Labels1, Cs, A, stE, C, I, B, N, Labels, Cs, A
get_assoccleanup_gotosName, Labels1, Ps
→
true
else
Ps = []
put_assoccleanup_gotosName, Labels1, pTarget, From|Ps, Labels
label_cleanups+Name, +Label//

the blocks of the forward gotos to Name that leave cleanup scopes, each running those it leaves, before Label begins.

5037label_cleanupsName, Label ⟶
statest_, _, _, _, _, Labels, _, _
get_assoccleanup_gotosName, Labels, Ps
→
termgotoLabel
envEnv
goto_blocksPs, To, Label
else
[]
5046goto_blocks[], _, _ ⟶
[]
5047goto_blockspT, From|Ps, To, Label ⟶
appendKs, To, Fromelse vm_jumps refuses
→
true
else
Ks = []
termgotoLabel
goto_blocksPs, To, Label

Conversions

convert+Value0, +To, -R//

R is v(V1, To), scalar Value0 = v(V, From) converted to scalar type To (or void). Callers check that the types are scalar first.

5058convertvV, Type, Type, vV, Type ⟶
!
5059convertvV, From, To, R ⟶
6.3.1.7
!
complex_convertvV, From, To, R
5062convertvV, vec_, _, _, To, vV, To ⟶
To = vec_, _, _
!

bitcast

5063convertvV, From, To, vAddr, To ⟶
a scalar repeated
To = vecE, N, _
!
convertvV, From, E, vX, _
local_slotTo, Addr
lengthXs, N
maplist=X, Xs
lanes_storedTo, Addr, 0, Xs
5069convertvV, _, void, vV, void ⟶
!
5070convertvV, From, bool, vB, bool ⟶
!
From ≢ void
truthvV, From, ctx_, _, _, _, unknown, W
type_classFrom, w
¬From ≡ bool
→
letw, cmpne, w, W, const0, B
else
B = W
5077convertvconstN, From, To, vconstW, To ⟶
5081convertvV, float, double, vR, double ⟶
!
letd, exts, V, R
5083convertvV, double, float, vR, float ⟶
!
lets, truncd, V, R
5085convertvV, float, ldouble, vR, ldouble ⟶
!
letx, exts, V, R
5087convertvV, double, ldouble, vR, ldouble ⟶
!
letx, extd, V, R
5089convertvV, ldouble, To, vR, To ⟶
memberchkTo, float, double
!
letK, truncx, V, R
5093convertvV, From, To, vR, To ⟶
type_classFrom, IK
int_infoFrom, _, Sign
int_float_opIK, Sign, Op
!
letK, Op, V, R
5098convertvV, From, To, vR, To ⟶
type_classFrom, K
int_infoTo, _, Sign
float_int_opK, Sign, Op
conversion_int_typeIK, Sign, Base
!
letIK, Op, V, I
int_convertBase, To, I, R
5105convertvconstN, From, To, vconstW, To ⟶
5109convertvV, From, To, vV1, To ⟶
!
int_convertFrom, To, V, V1
5113convertvconstN, From, ptrP, vconstW, ptrP ⟶
!
convert_intN, ulong, W
5117convertvV, From, ptrP, vV1, ptrP ⟶
!
int_convertFrom, ulong, V, V2
letp, itop, V2, V1
5122convertvV, ptr_, To, R ⟶
!
letl, ptoi, V, Int
convertvInt, ulong, To, R
5127convertvV, ptr_, ptrP, vV, ptrP ⟶
[]
int_float_op
5129wsignedswtof
5130wunsigneduwtof
5131lsignedsltof
5132lunsignedultof
float_int_op
5133ssignedstosi
5134sunsignedstoui
5135dsigneddtosi
5136dunsigneddtoui
5137xsignedxtosi
5138xunsignedxtoui

Float-to-integer instructions produce a full w/l; normalize narrow C destinations afterward, without ever routing an unsigned result via signed.

5142conversion_int_typeClass, Sign, Base ←
integer_typeBase, _, Sign, Rank
Rank ≥ 3
type_classBase, Class
!
int_convert+From, +To, +V, -V1//

V1 is integer V of type From converted to type To, extended where the representation changes.

5147int_convertFrom, To, V, V1 ⟶
int_infoFrom, SizeFrom, SignFrom
int_infoTo, SizeTo, SignTo
SizeTo =:= 8
SizeFrom < 8
→
narrow_typeOp, 4, SignFrom
letl, extOp, V, V1
SizeTo < 4
¬representableSizeFrom, SignFrom, SizeTo, SignTo
→
narrow_typeOp, SizeTo, SignTo
letw, extOp, V, V1
else
V1 = V
representable+SizeFrom, +SignFrom, +SizeTo, +SignTo

every value of the source type is a value of the target type.

5160representableSizeFrom, SignFrom, SizeTo, SignTo ←
SizeTo > SizeFrom
SignTo ≡ signed
or
SignFrom ≡ unsigned
!
5164representableSize, Sign, Size, Sign
assign_convert+Expr, +Value0, +To, +Loc, -R//

R is Value0, the value of Expr, converted to To as by assignment: a return value, an initializer or a builtin's argument. c_types' assigned/4 judges that it may be.

5170assign_convertExpr, Value0, To, Loc, R ⟶
envEnv
assignedExpr, Env, To, Loc
convertedValue0, To, R

_Complex

A complex value, like a struct's, is the address of its object: its real part, then its imaginary part (c_types complex_type/2). Its operators take the parts apart and build a new object, as GNU vectors take lanes; a real operand stays real (Annex G.5.1), so z + 2 leaves z's imaginary part as it is, -0 included.

complex_convert+V, +To, -R//

V converted to To, one of them complex. A real becomes the real part, with imaginary part +0; a complex keeps its real part as a real, or tests both for _Bool.

5186complex_convertV, To, R ⟶
V = v_, From
!
cx_partsV, unknown_ctx, Re, Im
→
cx_madeTo, Re, Im, R
To ≡ void
→
R = vconst0, void
To ≡ bool
→
cx_ctxunknown_ctx, Ctx
binaryne, Re, vconst0, int, Ctx, A
binaryne, Im, vconst0, int, Ctx, B
binaryor, A, B, Ctx, C
convertC, bool, R
else
convertRe, To, R
5198complex_convertV, To, R ⟶
cx_madeTo, V, vconst0, int, R
cx_parts+V, +Ctx, -Re, -Im//

Re and Im, v(X, F), are the parts of the complex value V of real type F.

5202cx_partsvAddr, CT, Ctx0, vX, F, vY, F ⟶
type_size_alignF, _, S, _
cx_ctxCtx0, Ctx
load_typedF, Addr, Ctx, X
offsetAddr, S, P
load_typedF, P, Ctx, Y
cx_made+CT, +Re, +Im, -R//

R is a new complex value of type CT, its parts Re and Im converted to its real type.

5208cx_madeCT0, Re, Im, vAddr, CT ⟶
convertRe, F, vX, _
convertIm, F, vY, _
local_slotCT, Addr
store_typedF, X, Addr
offsetAddr, S, P
5214cx_ctxunknown_ctx, ctx_, _, _, _, unknown ←
!
5215cx_ctxCtx, Ctx
cx_split+V, +Ctx, -Re, -Im//

complex V's parts, or a real V and none.

5218cx_splitV, Ctx, Re, Im ⟶
V = v_, T
!
cx_partsV, Ctx, Re, Im
5219cx_splitV, _, V, none ⟶
[]
complex_binary+Op, +A, +B, +T, +Ctx, -R//

R, of type T, is Op on A and B, converted already (c_types binary_operands/6), one complex. clang multiplies and divides two complex values with compiler-rt's __muldc3 and __divdc3, native code in Fil-C that IR cannot call, so those are refused.

5226complex_binaryOp, A, B, T, Ctx, R ⟶
cx_splitA, Ctx, Ar, Ai
cx_splitB, Ctx, Br, Bi
memberchkOp, mul, div
Bi ≢ none
Op ≡ div
or
Ai ≢ none
→
ctx_locCtx, Loc
memberchkF−L, float−s, double−d, ldouble−x
Op ≡ mul
→
W = 'multiplying a _Complex value by'
else
W = 'dividing by'
sema_errorLoc, '~w a _Complex value is not supported: clang calls compiler-rt\'s native __~w~wc3', W, Op, L
memberchkOp, eq, ne
→
binaryOp, Ar, Br, Ctx, E1
cx_im_compareOp, Ai, Bi, Ctx, E2
Op ≡ eq
→
J = and
else
J = or
binaryJ, E1, E2, Ctx, R
else
binaryOp, Ar, Br, Ctx, Re
cx_imOp, Ai, Bi, Ar, Br, Ctx, Im
cx_madeT, Re, Im, R
cx_im+Op, +Ai, +Bi, +Ar, +Br, +Ctx, -Im//

the imaginary part of A Op B for add, sub, mul and div, one of Ai and Bi none for a real.

5244cx_imOp, Ai, Bi, _, _, Ctx, Im ⟶
Ai ≢ none
Bi ≢ none
!
binaryOp, Ai, Bi, Ctx, Im
5246cx_imadd, Ai, none, _, _, _, Ai ⟶
!
5247cx_imsub, Ai, none, _, _, _, Ai ⟶
!
5248cx_imadd, none, Bi, _, _, _, Bi ⟶
!
5249cx_imsub, none, Bi, _, _, Ctx, Im ⟶
!
unaryneg, Bi, Ctx, Im
5250cx_imOp, Ai, none, _, Br, Ctx, Im ⟶
!
binaryOp, Ai, Br, Ctx, Im

mul, div

5251cx_immul, none, Bi, Ar, _, Ctx, Im ⟶
binarymul, Ar, Bi, Ctx, Im
5253cx_im_compareOp, Ai, Bi, Ctx, E ⟶
Ai ≢ none
Bi ≢ none
!
binaryOp, Ai, Bi, Ctx, E
5255cx_im_compareOp, Ai, none, Ctx, E ⟶
!
binaryOp, Ai, vconst0, int, Ctx, E
5256cx_im_compareOp, none, Bi, Ctx, E ⟶
binaryOp, Bi, vconst0, int, Ctx, E

Operators

unary+Op, +Value0, +Ctx, -R//

R is unary operator Op applied to Value0, converted as unary_operand/3 says.

5262unaryOp, Value0, Ctx, R ⟶
Value0 = v_, From
ctx_locCtx, Loc
unary_operandOp, From, Type
→
true
else
sema_errorLoc, 'invalid operand to ~w: ~w', Op, From
unary_valueOp, Value0, Type, Ctx, R
5269unary_valueOp, V, T, Ctx, R ⟶
~z is conj(z)
!
cx_partsV, Ctx, Re, Im
Op ≡ plus
→
R = V
Op ≡ neg
→
unaryneg, Re, Ctx, Re1
unaryneg, Im, Ctx, Im1
cx_madeT, Re1, Im1, R
else
unaryneg, Im, Ctx, Im1
cx_madeT, Re, Im1, R
5276unary_valueOp, vA, T, T, Ctx, vAddr, T ⟶
T = vecE, _, _GNU vectors
!
lanes_loadedT, A, Ctx, Xs
lane_unaryXs, Op, E, Ctx, Zs
local_slotT, Addr
lanes_storedT, Addr, 0, Zs
5282unary_valuenot, Value0, _, Ctx, vV, int ⟶
!
truthValue0, Ctx, W
letw, cmpeq, w, W, const0, V
5285unary_valueOp, Value0, Type, _, vV, Type ⟶
convertValue0, Type, vV1, _
type_classType, K
unary_insOp, K, V1, V
5290unary_insplus, _, V, V ⟶
[]
5291unary_insneg, K, V1, V ⟶
letK, neg, V1, V
5292unary_insbnot, K, V1, V ⟶
letK, xor, V1, const-1, V
binary+Op, +VA, +VB, +Ctx, -R//

R is binary operator Op applied to the values VA and VB, converted as binary_operands/6 says.

5296binaryOp, VA, VB, Ctx, R ⟶
VA = v_, TA
VB = v_, TB
ctx_locCtx, Loc
binary_operandsOp, TA, TB, CA, CB, T
→
true
else
sema_errorLoc, 'invalid operands to ~w: ~w and ~w', Op, TA, TB
binary_valueOp, CA, CB, T, VA, VB, Ctx, R
binary_value+Op, +CA, +CB, +T, +VA, +VB, +Ctx, -R//

R, of type T, is Op applied to VA converted to CA and VB converted to CB. Pointer arithmetic scales by the element size. Direct ordered comparisons are essential: !(a < b) is not a >= b when either operand is NaN.

5307binary_valueOp, CA, CB, T, VA, VB, Ctx, R ⟶
!
convertVA, CA, A
convertVB, CB, B
complex_binaryOp, A, B, T, Ctx, R
5311binary_valueOp, C, _, T, VA, VB, Ctx, vAddr, T ⟶
C = vecE, _, _GNU vectors
!
convertVA, C, vAA, _
convertVB, C, vBA, _
lanes_loadedC, AA, Ctx, Xs
lanes_loadedC, BA, Ctx, Ys
T = vecRE, _, _
lane_resultsXs, Ys, Op, E, RE, Ctx, Zs
local_slotT, Addr
lanes_storedT, Addr, 0, Zs
5319binary_valueOp, ptrE, long, _, VA, VB, Ctx, R ⟶
!
ptr_offsetOp, VA, E, VB, Ctx, R
5321binary_valueadd, long, ptrE, _, VA, VB, Ctx, R ⟶
!
ptr_offsetadd, VB, E, VA, Ctx, R
5323binary_valuesub, ptrE, ptr_, _, VA, VB, Ctx, R ⟶
!
ptr_diffVA, VB, E, Ctx, R
5325binary_valueOp, CA, CB, T, VA, VB, _, vV, T ⟶
convertVA, CA, vA, _
convertVB, CB, vB, _
→
Sign = signed
else
int_infoCA, _, Sign
cmp_opOp, SignedCmp, UnsignedCmp
→
Class = w
→
Cmp = Op
Sign ≡ signed
→
Cmp = SignedCmp
else
Cmp = UnsignedCmp
IOp = cmpCmp, K
else
Class = K
arith_opOp, Sign, IOp
letClass, IOp, A, B, V
cmp_op+Op, -SignedCmp, -UnsignedCmp

C comparison Op is IR comparison SignedCmp on signed and UnsignedCmp on unsigned operands.

cmp_op
5343eqeqeq
5344nenene
5345ltsltult
5346gtsgtugt
5347lesleule
5348gesgeuge
arith_op+Op, +Sign, -IROp

C arithmetic Op on operands of Sign is IR operation IROp.

arith_op
5352add_add
5353sub_sub
5354mul_mul
5355divsigneddiv
5356divunsignedudiv
5357modsignedrem
5358modunsignedurem
5359and_and
5360or_or
5361xor_xor
5362shl_shl
5363shrsignedsar
5364shrunsignedshr
ptr_offset+Op, +VP, +ElemType, +VI, +Ctx, -R//

R is P + I or P - I, I scaled by the element size.

5368ptr_offsetOp, vP, PtrType, ElemType, VI, Ctx, vR, PtrType ⟶
PtrType = ptrElemType
vla_typeElemType
!
extentElemType, Ctx, Size
convertVI, long, vI, _
productI, Size, OffV
letp, Op, P, OffV, R
5375ptr_offsetOp, vP, PtrType, ElemType, VI, Ctx, vR, PtrType ⟶
PtrType = ptrElemType
envEnv
ctx_locCtx, Loc
catcharith_sizeElemType, Env, Size, badWhy, sema_errorLoc, 'pointer arithmetic on ~w (~w)', ElemType, Why
convertVI, long, vI, _
I = constN
→
Off is N × Size
OffV = constOff
Size =:= 1
→
OffV = I
else
letl, mul, I, constSize, OffV
letp, Op, P, OffV, R
ptr_diff+VA, +VB, +ElemType, +Ctx, -R//

R is A - B in elements.

5390ptr_diffvA, _, vB, _, ElemType, Ctx, vV, long ⟶
vla_typeElemType
!
extentElemType, Ctx, Size
letl, ptoi, A, IA
letl, ptoi, B, IB
letl, sub, IA, IB, Diff
letl, div, Diff, Size, V
5397ptr_diffvA, _, vB, _, ElemType, Ctx, vV, long ⟶
envEnv
ctx_locCtx, Loc
catcharith_sizeElemType, Env, Size, badWhy, sema_errorLoc, 'pointer subtraction on ~w (~w)', ElemType, Why
letl, ptoi, A, IA
letl, ptoi, B, IB
letl, sub, IA, IB, Diff
Size =:= 1
→
V = Diff
else
letl, div, Diff, constSize, V
logical+Op, +XA, +XB, +Ctx, -R//

R is XA && XB (land) or XA || XB (lor), a phi of 0/1 over the short-circuit edges.

5412logicalOp, XA, XB, Ctx, vV, int ⟶
lowerXA, Ctx, VA
truthVA, Ctx, WA
labelLRight
labelLJoin
Op ≡ land
→
Jump = branchWA, LRight, LJoin
Short = 0
else
Jump = branchWA, LJoin, LRight
Short = 1
termJump
startLRight
lowerXB, Ctx, VB
truthVB, Ctx, WB
letw, cmpne, w, WB, const0, NB
termgotoLJoin
start_phiLJoin, phiV, w, LA−constShort, LB−NB
join_arms+Type, +LJoin, +LA−VA, +LB−VB, -R//

start block LJoin; R is the ?: result there, a phi of VA from LA and VB from LB.

5427join_armsType, LJoin, LA−VA, LB−VB, R ⟶
Type ≡ void
→
startLJoin
R = vconst0, void
Type ≡ ldouble
→
start_phiLJoin, phiA, p, LA−VA, LB−VB
letx, loadx, A, V
R = vV, Type
else
→
Class = p
else
type_classType, Class
start_phiLJoin, phiV, Class, LA−VA, LB−VB
R = vV, Type

Calls

A call (6.5.2.2) lowers from its tree, whose callee and arguments the elaborator has checked: the callee is a function, the arguments fit its arity and each converts to its parameter as by assignment.

callee+XF, +Ctx, -Callee, -FuncType//

Callee is the IR callee of function pointer XF: the function itself when XF is a function designator decayed, else a temp holding the pointer.

5449calleeval_, decayX, Ctx, Callee, FuncType ⟶
X = fun_, _
!
placeX, Ctx, fdCallee0, FuncType
envEnv
X = fun_, idName
lookup_ordinaryEnv, Name, fn_, _, Linkage
→
true
else
Linkage = export
Linkage ≡ local
→
true
else
vector_abiFuncType, Env, Ctx
env_defsEnv, Defs
Callee0 = globalSym
get_associnline_copySym, Defs, Local
→
Callee = globalLocaltop_item's local copy
else
Callee = Callee0
5460calleeXF, Ctx, Callee, FuncType ⟶
lowerXF, Ctx, vV, ptrFuncType
envEnv
vector_abiFuncType, Env, Ctx
callee_tempV, Callee
vector_abi+FuncType, +Env, +Ctx

a call of FuncType with external linkage or through a pointer, or a definition with external linkage, passes no GNU vector, nor a struct, union or array holding one: filcc passes a vector in vector registers and lays a vector-holding struct out at its vector alignment, where nanofil passes them as a struct of words, so only nanofil's own static functions (immintrin.h's intrinsics) take or return one. The same holds for an _Atomic struct or union (c_types atomic_su_type/3), which filcc passes by its own rules (a struct holding one is returned through memory).

5475vector_abifuncRet, Ps, _, Env, Ctx ←
holds_vecRet, Env, K
or
is_listPs
memberP, Ps
holds_vecP, Env, K
→
ctx_locCtx, Loc
sema_errorLoc, 'a function with external linkage or called through a pointer cannot take or return ~w', K
else
true
5481vector_abi_, _, _
holds_vec+Type, +Env, -What

Type is, or is an array, struct or union with among its elements or members, a GNU vector (What is 'a vector') or an _Atomic struct or union.

5486holds_vecvec_, _, _, _, 'a vector' ←
!
5487holds_vecatomic_, _, _, 'an _Atomic struct or union' ←
!
5488holds_vecarrayT, _, Env, K ←
!
holds_vecT, Env, K
5489holds_vecSU, Env, K ←
suSU
SU =.. _, Id
env_defsEnv, Defs
get_assocId, Defs, def_, Members, _, _
memberM, Members
arg2, M, T
holds_vecT, Env, K
!
call_result+Callee, +RetType, +IRArgs, +Ctx, -R//

R is the result of calling Callee with IRArgs; setjmp and its kin are intrinsics.

5496call_resultglobalKind, RetType, IRArgs, Ctx, R ⟶
op_kindsetjmpKind, setjmp
!
RetType ≡ int
setjmp_argsKind, IRArgs, Operands
→
true
else
ctx_locCtx, Loc
sema_errorLoc, 'invalid ~w signature', Kind
letw, setjmpKind, Operands, V
R = vV, int
5502call_resultCallee, RetType, IRArgs, Ctx, R ⟶
ctx_locCtx, Loc
complex_typeRetType, ldouble
→
sema_errorLoc, 'calls returning _Complex long double are not supported yet', []
memtypeRetType
→
struct_callRetType, Callee, IRArgs, Loc, R
RetType ≡ ldouble
→
struct_callRetType, Callee, IRArgs, Loc, vTmp, _
letx, loadx, Tmp, V
R = vV, RetType
RetType ≡ void
→
emitcallvoid, Callee, IRArgs
R = vconst0, void
type_classRetType, Class
→
emitcallresultD, Class, Callee, IRArgs
normalizeRetType, D, V
R = vV, RetType
else
sema_errorLoc, 'calls returning ~w are not supported yet', RetType
setjmp_args
5523sigsetjmpargp, Buf, argw, MaskBuf, Mask
5524setjmpargp, BufBuf, const0
5525'_setjmp'argp, BufBuf, const0
call_args+XArgs, +ParamTypes, +Ctx, -IRArgs//

IRArgs pass the arguments whose trees are XArgs. A struct argument goes as one word per eightbyte; arguments past the prototype get the default promotions.

5531call_args[], _, _, [] ⟶
[]
5532call_args_|_, atomicS, P|_, Ctx, _ ⟶
ctx_locCtx, Loc
sema_errorLoc, 'parameter of type ~w is not supported yet', atomicS, P
5534call_argsXA|XAs, ParamType|ParamTypes, Ctx, IRArgs ⟶
abi_memoryParamType
!
lowerXA, Ctx, Value0
envEnv
An argument for a transparent union initializes the member of its
own type in a temporary union, which is passed.
transparent_argumentParamType, XA, Env, Member
→
local_slotParamType, V
convertedValue0, Member, vMV, _
store_typedMember, MV, V
else
convertedValue0, ParamType, vV, _
abi_sourceParamType, V, Src
ctx_locCtx, Loc
arg_wordsParamType, Env, Loc, Ebs
load_wordsEbs, Src, Words
findallargClass, W,
nth0I, Ebs, eb_, _, Class
nth0I, Words, W
,
IRArgs00
pad16_markParamType, Env, IRArgs00, IRArgs0
call_argsXAs, ParamTypes, Ctx, IRArgs1
appendIRArgs0, IRArgs1, IRArgs
5555call_argsXA|XAs, ParamTypes0, Ctx, IRArgs ⟶
lowerXA, Ctx, Value0
ParamTypes0 ≡ []
Value0 = vV0, Type0
→
envEnv6.5.2.2p7: no conversion
abi_sourceType0, V0, Src
ctx_locCtx, Loc
holds_vecType0, Env, Kfilcc aligns it to its vector
→
sema_errorLoc, 'passing ~w to a variadic or unprototyped function is not supported', K
else
true
variadic_wordsType0, Env, Loc, Ebs
load_wordsEbs, Src, Words
findallargClass, W,
nth0I, Ebs, eb_, _, Class
nth0I, Words, W
,
IRArgs00
pad16_markType0, Env, IRArgs00, IRArgs0
ParamTypes = []
else
ParamTypes0 = ParamType|ParamTypes
→
convertedValue0, ParamType, vV, Type
else
ParamTypes = []
Value0 = v_, Type0
→
convertValue0, Type, vV, _
Type0 = ptr_
→
Value0 = vV, Type
else
ctx_locCtx, Loc
sema_errorLoc, 'passing ~w to a variadic or unprototyped function is not supported', Type0
type_classType, Class
IRArgs0 = argClass, V
call_argsXAs, ParamTypes, Ctx, IRArgs1
appendIRArgs0, IRArgs1, IRArgs
va_cells+Ebs0, +Leaves, +Type, +Loc, -Ebs

Ebs are the cells in which a variadic caller passed a struct or union of Type with eightbytes Ebs0 and scalars Leaves: a long double is one x cell, 16-aligned, covering its two eightbytes; a pointer eightbyte is p, any other l. The x cell aligns the cursor, so a 16-aligned Type must start with one.

5596va_cellsEbs0, Leaves, Type, Loc, Ebs ←
memberchk_−x87, Leaves
Ebs0 = ebO, _, _|_
¬memberchkO−x87, Leaves
→
sema_errorLoc, 'va_arg of ~w, which does not start with its long double, is not supported', Type
else
va_cellsEbs0, Leaves, Ebs
5601va_cells[], _, []
5602va_cellsebO, _, _, _|Ebs0, Leaves, ebO, 10, x|Ebs ←
memberchkO−x87, Leaves
!
va_cellsEbs0, Leaves, Ebs
5605va_cellsebO, N, K0|Ebs0, Leaves, ebO, N, K|Ebs ←
K0 ≡ p
→
K = p
else
K = l
va_cellsEbs0, Leaves, Ebs
va_words+Ebs, +P, -Words//

Words are read through the va_list at P, one per eightbyte at its class.

5611va_words[], _, [] ⟶
[]
5612va_wordseb_, _, K|Ebs, P, W|Ws ⟶
letK, va_arg, P, W
va_wordsEbs, P, Ws
struct_call+RetType, +Callee, +Args, +Loc, -R//

R is the result of a call returning a struct: its words land in a fresh local, or the callee writes the local through the sret pointer.

5618struct_callRetType, Callee, Args, Loc, vTmp, RetType ⟶
envEnv
ret_shapeRetType, Env, Loc, Shape
local_slotRetType, Tmp
Shape ≡ sret
→
emitcallvoid, Callee, argp, Tmp|Args
Shape = wordseb_, _, Class
→
emitcallresultD, Class, Callee, Args
Shape = wordsEbs
store_wordsEbs, D, Tmp
else
Shape = wordsEbs
Ebs = eb_, _, Class1, eb_, _, Class2
emitcalltupleresultD1, Class1, resultD2, Class2, Callee, Args
store_wordsEbs, D1, D2, Tmp
return_struct+RetType, +Addr, +Loc//

return the struct at Addr, in words or through the sret pointer.

5638return_structRetType, Addr, Loc ⟶
envEnv
ret_shapeRetType, Env, Loc, Shape
Shape ≡ sret
→
envEnv1
lookup_ordinaryEnv1, '%sret', sretP
copy_objectRetType, Addr, P
termret
else
Shape = wordsEbs
load_wordsEbs, Addr, Words
Words = W
→
termretW
else
termretsWords
normalize+Type, +V0, -V//

V is call result V0 of Type, a narrow one re-extended: nothing guarantees the callee did.

5655normalizeType, V0, V ⟶
int_infoType, Size, Sign
Size < 4
→
narrow_typeOp, Size, Sign
letw, extOp, V0, V
Type ≡ bool
→
letw, extub, V0, V
else
V = V0

Struct values

A struct or union rvalue is v(Addr, T): Addr points at storage holding the value, a variable's or a temporary's. Copies, arguments and returns go an eightbyte at a time (eb(Offset, Bytes, Class)): class p if a pointer starts there, so its capability is copied with load(p) and store(p, ...), else l, with a short last eightbyte moved in 4-, 2- and 1-byte pieces. A value of at most 16 bytes is passed as one word per eightbyte, as clang does; a larger return goes through an sret pointer.

5673sustruct_
5674suunion_
memtype+Type

a value of Type is the address of a copy of it, as a struct's or union's is, and is passed and returned as one. A GNU vector's is a copy that nothing else writes (vector_value//4).

5679memtypeType ←
suType
5680memtypevec_, _, _

GNU vectors are lowered element by element: a vector value is the address of a local slot holding a copy of it, made where an lvalue is loaded (loaded//4) or an operator computes it, and never written again, so a cast to another vector type of the same size keeps the address. An operator loads each element of its operands, applies the scalar operator to them, and stores each result.

vector_args+Args, +Params, +Ctx, -Vs//

Vs are Args' values, each converted to its parameter's type as for a call.

5691vector_args[], [], _, [] ⟶
[]
5692vector_argsA|As, P|Ps, Ctx, V|Vs ⟶
rvalA, Ctx, V0
ctx_locCtx, Loc
assign_convertA, V0, P, Loc, V
vector_argsAs, Ps, Ctx, Vs
lanes+V, +VT, +Ctx, -Ls//

Ls are the elements, v(X, E), of vector value V read as a vector of type VT (the same bytes).

5700lanesvAddr, _, VT, Ctx, Ls ⟶
lanes_loadedVT, Addr, Ctx, Xs
VT = vecE, _, _
findallvX, E, memberX, Xs, Ls0
Ls = Ls0
made+VT, +Ls, -R//

R is the vector of type VT whose elements are Ls, each converted to VT's element type.

5706madeVT, Ls, vAddr, VT ⟶
VT = vecE, N, _
lengthLs, N
→
[]
else
thrownanofil_error'vector arity'
local_slotVT, Addr
lanes_storedVT, Addr, 0, Xs
5713lane_converted[], _, [] ⟶
[]
5714lane_convertedL|Ls, E, X|Xs ⟶
convertL, E, vX, _
imm+A, +Ctx, -I

argument expression A is an integer constant I, as an instruction's immediate operand must be.

5719immA, Ctx, I ⟶
envEnv
ctx_locCtx, Loc
catchconst_valueA, Env, I, _, not_constant, fail
→
true
else
sema_errorLoc, 'this vector builtin needs a constant operand', []

Scalar steps on v(V, Type) values, with C's conversions.

5727bopOp, A, B, Ctx, R ⟶
binaryOp, A, B, Ctx, R
5728kN, vconstN, int
clmul_bits+I, +A, +B, +Ctx, +Lo0, +Hi0, -Lo, -Hi//

Lo:Hi is Lo0:Hi0 XOR the carry-less product of A by B's bits I to 63, each a mask that keeps or clears A's shifted copies, without a branch.

5733clmul_bits64, _, _, _, Lo, Hi, Lo, Hi ⟶
!
5734clmul_bitsI, A, B, Ctx, Lo0, Hi0, Lo, Hi ⟶
bopshr, B, vconstI, int, Ctx, B1
bopand, B1, vconst1, ullong, Ctx, Bit
bopsub, vconst0, ullong, Bit, Ctx, M
bopshl, A, vconstI, int, Ctx, AL
bopand, AL, M, Ctx, TL
bopxor, Lo0, TL, Ctx, Lo1
I =:= 0
→
Hi1 = Hi0
else
S is 64−I
bopshr, A, vconstS, int, Ctx, AH
bopand, AH, M, Ctx, TH
bopxor, Hi0, TH, Ctx, Hi1
I1 is I + 1
clmul_bitsI1, A, B, Ctx, Lo1, Hi1, Lo, Hi
pcmpstr?Name, ?Len, ?Out

Name is a SSE4.2 string compare with lengths Len (e or i) whose result is Out (m, i or a flag ia ... iz).

5752pcmpstrName, Len, Out ←
memberLen, i, e
memberOut, m, i, ia, ic, io, is, iz
atomic_list_concatpcmp, Len, str, Out, '128', Name
5756pcmp_formatI, VT ←
F is I /\ 3
nth0F, vecuchar, 16, 16, vecushort, 8, 16, vecschar, 16, 16, vecshort, 8, 16, VT
pcmp_valid+Len, +Ls, +LV, +Ctx, -Vs//

Vs are 1 for each element of Ls within its length, else 0.

5762pcmp_validi, Ls, _, Ctx, Vs ⟶
pcmp_nonzeroLs, vconst1, int, Ctx, Vs
5763pcmp_valide, Ls, LV, Ctx, Vs ⟶
convertLV, long, L
boplt, L, vconst0, long, Ctx, Neg
bopsub, vconst0, long, L, Ctx, NegL
iselNeg, NegL, L, long, Ctx, Abs
numberedLs, Js
pcmp_belowJs, Abs, Ctx, Vs
5768pcmp_nonzero[], _, _, [] ⟶
[]
5769pcmp_nonzeroL|Ls, P, Ctx, V|Vs ⟶
bopne, L, vconst0, int, Ctx, NZ
bopand, P, NZ, Ctx, V
pcmp_nonzeroLs, V, Ctx, Vs
5772pcmp_below[], _, _, [] ⟶
[]
5773pcmp_belowJ|Js, A, Ctx, V|Vs ⟶
boplt, vconstJ, long, A, Ctx, V
pcmp_belowJs, A, Ctx, Vs
pcmp_aggregated+Agg, +As, +Bs, +VAs, +VBs, +Ctx, -R1//

IntRes1, bit j for element j of B, with the SDM's overrides for invalid elements. Where an override makes a pair false, the invalid element is first replaced by 65536, which equals no element and lies in no range of them, so the pair needs no test of its own.

5781pcmp_aggregated0, As0, Bs, VAs, VBs, Ctx, R1 ⟶
!
pcmp_maskedAs0, VAs, Ctx, As
numberedBs, Js
pcmp_bitsJs, 0, As, Bs, VAs, VBs, Ctx, R1
5784pcmp_aggregated1, As0, Bs, VAs, VBs, Ctx, R1 ⟶
!
pcmp_masked_rangesAs0, VAs, Ctx, As
numberedBs, Js
pcmp_bitsJs, 1, As, Bs, VAs, VBs, Ctx, R1
5787pcmp_aggregated3, As, Bs0, VAs, VBs, Ctx, R1 ⟶
!
pcmp_maskedBs0, VBs, Ctx, Bs
pcmp_notsVAs, Ctx, NAs
numberedBs, Js
pcmp_bitsJs, 3, As, Bs, NAs, VBs, Ctx, R1
5790pcmp_aggregatedAgg, As, Bs, VAs, VBs, Ctx, R1 ⟶
numberedBs, Js
pcmp_bitsJs, Agg, As, Bs, VAs, VBs, Ctx, R1
5793pcmp_masked[], [], _, [] ⟶
[]
5794pcmp_maskedX|Xs, V|Vs, Ctx, Y|Ys ⟶
iselV, X, vconst65536, int, int, Ctx, Y
pcmp_maskedXs, Vs, Ctx, Ys

a range whose bounds are not both valid gets the empty [65536, Hi]

5797pcmp_masked_ranges[], [], _, [] ⟶
[]
5798pcmp_masked_rangesLo, Hi|Xs, VLo, VHi|Vs, Ctx, Lo1, Hi1|Ys ⟶
bopand, VLo, VHi, Ctx, V
iselV, Lo, vconst65536, int, int, Ctx, Lo1
convertHi, int, Hi1
pcmp_masked_rangesXs, Vs, Ctx, Ys
5801pcmp_nots[], _, [] ⟶
[]
5802pcmp_notsV|Vs, Ctx, N|Ns ⟶
bopxor, V, vconst1, int, Ctx, N
pcmp_notsVs, Ctx, Ns
5803pcmp_bits[], _, _, _, _, _, _, [] ⟶
[]
5804pcmp_bitsJ|Js, Agg, As, Bs, VAs, VBs, Ctx, R|Rs ⟶
pcmp_bitAgg, J, As, Bs, VAs, VBs, Ctx, R
pcmp_bitsJs, Agg, As, Bs, VAs, VBs, Ctx, Rs
5808pcmp_bit0, J, As, Bs, _, VBs, Ctx, R ⟶
equal any; As masked
nth0J, Bs, B
nth0J, VBs, VB
pcmp_anyAs, B, Ctx, vconst0, int, Any
bopand, Any, VB, Ctx, R
5812pcmp_bit1, J, As, Bs, _, VBs, Ctx, R ⟶
ranges; As masked
nth0J, Bs, B
nth0J, VBs, VB
pcmp_rangesAs, B, Ctx, vconst0, int, Any
bopand, Any, VB, Ctx, R
5816pcmp_bit2, J, As, Bs, VAs, VBs, Ctx, R ⟶
equal each
nth0J, As, A
nth0J, Bs, B
nth0J, VAs, VA
nth0J, VBs, VB
bopeq, A, B, Ctx, E
bopand, VA, VB, Ctx, Both
bopand, Both, E, Ctx, T
bopor, VA, VB, Ctx, Either
bopxor, Either, vconst1, int, Ctx, Neither
bopor, T, Neither, Ctx, R
5821pcmp_bit3, J, As, Bs, NAs, _, Ctx, R ⟶
equal ordered; Bs masked
lengthAs, N
K1 is N−1−J
numlist0, K1, Ks
pcmp_orderedKs, J, As, Bs, NAs, Ctx, vconst1, int, R
5825pcmp_any[], _, _, Acc, Acc ⟶
[]
5826pcmp_anyA|As, B, Ctx, Acc0, Acc ⟶
bopeq, A, B, Ctx, E
bopor, Acc0, E, Ctx, Acc1
pcmp_anyAs, B, Ctx, Acc1, Acc
5830pcmp_ranges[], _, _, Acc, Acc ⟶
[]
5831pcmp_rangesLo, Hi|As, B, Ctx, Acc0, Acc ⟶
bople, Lo, B, Ctx, C1
bople, B, Hi, Ctx, C2
bopand, C1, C2, Ctx, C
bopor, Acc0, C, Ctx, Acc1
pcmp_rangesAs, B, Ctx, Acc1, Acc

an invalid element of A matches anything (NA, its negated validity, is 1); an invalid element of B, masked, matches no valid one of A

5838pcmp_ordered[], _, _, _, _, _, Acc, Acc ⟶
[]
5839pcmp_orderedK|Ks, J, As, Bs, NAs, Ctx, Acc0, Acc ⟶
X is J + K
nth0K, As, A
nth0K, NAs, NA
nth0X, Bs, B
bopeq, A, B, Ctx, E
bopor, NA, E, Ctx, T
bopand, Acc0, T, Ctx, Acc1
pcmp_orderedKs, J, As, Bs, NAs, Ctx, Acc1, Acc
pcmp_polarity+Pol, +R1, +VBs, +Ctx, -R2//

IntRes2.

5846pcmp_polarityPol, R1, _, _, R1 ⟶
Pol /\ 1 =:= 0
!
5847pcmp_polarity1, R1, _, Ctx, R2 ⟶
pcmp_xorsR1, _, Ctx, R2
5848pcmp_polarity3, R1, VBs, Ctx, R2 ⟶
pcmp_xorsR1, VBs, Ctx, R2
5849pcmp_xors[], _, _, [] ⟶
[]
5850pcmp_xorsR|Rs, Ms, Ctx, X|Xs ⟶
varMs
→
M = vconst1, int
Ms1 = Ms
else
Ms = M|Ms1
bopxor, R, M, Ctx, X
pcmp_xorsRs, Ms1, Ctx, Xs
pcmp_result+Out, +Msb, +VT, +N, +R2, +Js, +R20, +VaN, +VbN, +Ret, +Ctx, -R//

the index of IntRes2's lowest set bit, N if none: ctz of it with bit N set; or of its highest, the bits up to it counted less 1, N if none

5857pcmp_resulti, 0, _, N, R2, Js, _, _, _, _, Ctx, vI, int ⟶
!
pcmp_maskR2, Js, Ctx, vconst0, int, M0
Stop is 1 << N
bopor, M0, vconstStop, int, Ctx, M1
convertM1, ullong, vX, _
bit_countctz, 32, X, C
letw, copy, C, I
5861pcmp_resulti, 1, _, N, R2, Js, _, _, _, _, Ctx, R ⟶
!
pcmp_maskR2, Js, Ctx, vconst0, int, M
convertM, ullong, vX, _
smear1, 2, 4, 8, 16, X, S
letl, sub, P, const1, Hi
letw, copy, Hi, HiW
bopeq, M, vconst0, int, Ctx, None
iselNone, vconstN, int, vHiW, int, int, Ctx, R
5867pcmp_resultm, 0, _, _, R2, Js, _, _, _, Ret, Ctx, R ⟶
!
pcmp_maskR2, Js, Ctx, vconst0, int, M
Z = vconst0, int
madevecushort, 8, 16, M, Z, Z, Z, Z, Z, Z, Z, vAddr, _
R = vAddr, Ret
5871pcmp_resultm, 1, VT, _, R2, _, _, _, _, Ret, Ctx, R ⟶
!
pcmp_unitsR2, Ctx, Us
madeVT, Us, vAddr, _
R = vAddr, Ret
5874pcmp_resultic, _, _, _, R2, _, _, _, _, _, Ctx, R ⟶
!
pcmp_orR2, Ctx, R
5875pcmp_resultia, _, _, _, R2, _, _, _, VbN, _, Ctx, R ⟶
!
pcmp_orR2, Ctx, C
bopxor, C, vconst1, int, Ctx, NC
bopand, NC, VbN, Ctx, R
5877pcmp_resultio, _, _, _, _, _, R20, _, _, _, _, R20 ⟶
!
5878pcmp_resultis, _, _, _, _, _, _, VaN, _, _, Ctx, R ⟶
!
bopxor, VaN, vconst1, int, Ctx, R
5879pcmp_resultiz, _, _, _, _, _, _, _, VbN, _, Ctx, R ⟶
bopxor, VbN, vconst1, int, Ctx, R
5881pcmp_mask[], [], _, M, M ⟶
[]
5882pcmp_maskB|Bs, J|Js, Ctx, M0, M ⟶
bopshl, B, vconstJ, int, Ctx, S
bopor, M0, S, Ctx, M1
pcmp_maskBs, Js, Ctx, M1, M
5884pcmp_units[], _, [] ⟶
[]
5885pcmp_unitsB|Bs, Ctx, U|Us ⟶
bopsub, vconst0, int, B, Ctx, U
pcmp_unitsBs, Ctx, Us
5887pcmp_orB|Bs, Ctx, R ⟶
pcmp_or_Bs, Ctx, B, R
5888pcmp_or_[], _, R, R ⟶
[]
5889pcmp_or_B|Bs, Ctx, R0, R ⟶
bopor, R0, B, Ctx, R1
pcmp_or_Bs, Ctx, R1, R
5891addsub_lanes[], [], _, _, [] ⟶
[]
5892addsub_lanesA|As, B|Bs, J, Ctx, O|Os ⟶
J /\ 1 =:= 0
→
Op = sub
else
Op = add
bopOp, A, B, Ctx, O
J1 is J + 1
addsub_lanesAs, Bs, J1, Ctx, Os
5897fma_lanes[], [], [], _, _, _, [] ⟶
[]
5898fma_lanesA|As, B|Bs, C0|Cs, Sub, J, Ctx, vD, E|Os ⟶
Sub ≡ even
J /\ 1 =:= 0
→
unaryneg, C0, Ctx, C
else
C = C0
A = vX, E
B = vY, _
C = vZ, _
E ≡ float
→
K = s
F = fmaf
else
K = d
F = fma
emitcallresultD, K, globalF, argK, X, argK, Y, argK, Z
J1 is J + 1
fma_lanesAs, Bs, Cs, Sub, J1, Ctx, Os
half_type+VT, -HT

HT is the 16-byte half of the 32-byte vector VT.

5908half_typevecE, N, _, vecE, H, 16 ←
H is N//2
halves+Vs, +Off, -Hs

Hs are Vs with each 32-byte vector replaced by its half at Off; a 16-byte count vector or a scalar is kept.

5912halves[], _, [] ⟶
[]
5913halvesV|Vs, Off, H|Hs ⟶
envEnv
V = vA, VT
VT = vec_, _, _
type_size_alignVT, Env, 32, _
→
half_typeVT, HT
offsetA, Off, B
H = vB, HT
else
H = V
halvesVs, Off, Hs
5921perm2_halfJ, I, VA, VB, C ⟶
S is I >> 4 × J /\ 15
Off is 16 × J
HT = vecullong, 2, 16
offsetC, Off, D
S /\ 8 ≠ 0
→
lanes_storedHT, D, 0, const0, const0
else
S /\ 2 =:= 0
→
Src0 = VA
else
Src0 = VB
SOff is 16 × S /\ 1
offsetSrc0, SOff, Src
copy_objectHT, Src, D
5931permuted[], _, _, [] ⟶
[]
5932permutedI|Is, VA, Ctx, L|Ls ⟶
k7, K7
k2, K2
bopand, I, K7, Ctx, J
bopshl, J, K2, Ctx, Off0
convertOff0, long, vOff, _
letp, add, VA, Off, P
load_typeduint, P, Ctx, X
L = vX, uint
permutedIs, VA, Ctx, Ls
safety_error+Msg//

call zsafety_error with Msg, a Fil-C panic.

5939safety_errorMsg ⟶
envEnv0
new_literalMsg, Sym, Env0, Env
letp, copy, globalSym, Addr
emitcallvoid, globalzsafety_error, argp, Addr

An _Atomic struct or union of size P (c_types atomic_su_type/3) is accessed as one P-byte unsigned integer, its padding stored as zero.

5946atomic_wordP, T ←
memberchkP−T, 1−uchar, 2−ushort, 4−uint, 8−ulong
atomic_rmw_value...//

the result R of an atomic read-modify-write builtin on a scalar T at Addr (builtin//4's rmw_builtin clause).

5950atomic_rmw_valueFamily, Op, Which, T, Addr, V, V0, Loc, Ctx, R ⟶
T = ptr_
Op ≢ xchg
→
To = long
else
To = T
assign_convertV, V0, To, Loc, V1
rmwT, Addr, Op, V1, Ctx, Old, New
Which ≡ old
→
R0 = Old
else
R0 = New
Family ≡ c11
T = ptr_
Op ≢ xchg
R0 = vA, _
→
Built on a null base: no capability, as filcc's result has,
and not one launder.pl could trace back to A.
letl, ptoi, A, I
letp, add, const0, I, D
R = vD, T
else
R = R0
atomic_su_loaded+S, +P, +Addr, +Ctx, -R//

R, an S, is read from the atomic object at Addr by one P-byte load into a P-byte copy.

5964atomic_su_loadedS, P, Addr, Ctx, vSlot, S ⟶
local_slotatomicS, P, Slot
load_typedT, Addr, Ctx, X
store_typedT, X, Slot
atomic_su_word+S, +P, +V, +Ctx, -W//

W is the P-byte integer that the S value V is in an atomic object: V's bytes, then zeros.

5971atomic_su_wordS, P, vSrc, _, Ctx, W ⟶
local_slotatomicS, P, Slot
store_typedT, const0, Slot
copy_objectS, Src, Slot
load_typedT, Slot, Ctx, W
5977cpu_wordSym, Off, Ctx, W ⟶
letp, copy, globalSym, G
offsetG, Off, A
load_typeduint, A, Ctx, W
isel+C, +A, +B, +T, +Ctx, -R//

R, of integer type T, is A where the int C is 1 and B where it is 0: B ^ ((A ^ B) & -C).

5982iselC, A, B, T, Ctx, R ⟶
convertA, T, A1
convertB, T, B1
k0, Z
bopsub, Z, C, Ctx, M0
convertM0, T, M
bopxor, A1, B1, Ctx, D
bopand, D, M, Ctx, D1
bopxor, B1, D1, Ctx, R0
convertR0, T, R
clamped+X, +Lo, +Hi, +T, +Ctx, -R//

R is int X clamped to [Lo, Hi].

5989clampedX, Lo, Hi, T, Ctx, R ⟶
kLo, VLo
kHi, VHi
boplt, X, VLo, Ctx, CLo
iselCLo, VLo, X, long, Ctx, X1
bopgt, X1, VHi, Ctx, CHi
iselCHi, VHi, X1, long, Ctx, X2
convertX2, T, R
5995numberedLs, Is ←
lengthLs, N
N1 is N−1
numlist0, N1, Is
5997v16quvecuchar, 16, 16
5998v8huvecushort, 8, 16
5999v4suvecuint, 4, 16
6000v2duvecullong, 2, 16
6001v16qivecchar, 16, 16
6002v8hivecshort, 8, 16
6003v4sivecint, 4, 16
6004v2divecllong, 2, 16
vector_op+Short, +Args, +Vs, +Ret, +Ctx, -R//

R is the result of __builtin_ia32_Short on argument expressions Args, whose values are Vs. AVX2's per-lane builtins (c_types lane_builtin/2): the SSE builtin on each 16-byte half of the 32-byte operands, with the same immediate and the same 16-byte shift count, the halves joined.

6011vector_opName, Args, Vs, Ret, Ctx, R ⟶
lane_builtinName, Name128
!
half_typeRet, RetH
halvesVs, 0, Vs0
halvesVs, 16, Vs1
vector_opName128, Args, Vs0, RetH, Ctx, vA0, _
vector_opName128, Args, Vs1, RetH, Ctx, vA1, _
copy_objectRetH, A0, C
offsetC, 16, C1
copy_objectRetH, A1, C1
R = vC, Ret

VEXTRACTI128 and VEXTRACTF128: imm8 bit 0 picks the half, which a vector value, a copy nothing writes, may share.

6022vector_opName, _, A, vW, _, _, Ret, Ctx, vH, Ret ⟶
memberchkName, extract128i256, vextractf128_si256, vextractf128_ps256, vextractf128_pd256
!
immA, Ctx, I
Off is 16 × I /\ 1
offsetW, Off, H

VINSERTI128 and VINSERTF128: W with the half imm8 bit 0 picks replaced.

6027vector_opName, _, _, A, vW, _, vH, HT, _, Ret, Ctx, vC, Ret ⟶
memberchkName, insert128i256, vinsertf128_si256, vinsertf128_ps256, vinsertf128_pd256
!
immA, Ctx, I
Off is 16 × I /\ 1
copy_objectRet, W, C
offsetC, Off, D
copy_objectHT, H, D

VPERM2I128 and VPERM2F128: each half of the result is the half of A or B that imm8's nibble for it picks (bits 0-1: A's low, A's high, B's low, B's high), or zero where the nibble's bit 3 is set.

6036vector_opName, _, _, A, vVA, _, vVB, _, _, Ret, Ctx, vC, Ret ⟶
memberchkName, permti256, vperm2f128_si256, vperm2f128_ps256, vperm2f128_pd256
!
immA, Ctx, I
perm2_half0, I, VA, VB, C
perm2_half1, I, VA, VB, C

VPERMD and VPERMPS: element i of the result is A's element at B's element i mod 8, read at that offset in A's copy.

6043vector_opName, _, vVA, _, VB, Ret, Ctx, R ⟶
memberchkName, permvarsi256, permvarsf256
!
Q = vecuint, 8, 32
lanesVB, Q, Ctx, Is
permutedIs, VA, Ctx, Out
madeQ, Out, vAddr, _
R = vAddr, Ret
6048vector_opName, _, A, V, _, E, Ctx, L ⟶
memberchkName, vec_ext_v32qi, vec_ext_v16hi, vec_ext_v8si, vec_ext_v4di
!
V = v_, VT
VT = vec_, N, _
immA, Ctx, I
J is I mod N
lanesV, VT, Ctx, Ls
nth0J, Ls, vX, _
L = vX, E

filcc compiles VZEROUPPER and VZEROALL to its run-time "Unhandled intrinsic" error, as _mm_lfence's.

6055vector_opName, _, [], _, _, vconst0, void ⟶
memberchkName, vzeroupper, vzeroall
!
formatcodesMsg, 'Unhandled intrinsic: call void @llvm.x86.avx.~w()', Name

ADDSUBPS and ADDSUBPD subtract in the even elements and add in the odd; the FMA builtins round a * b + c once (libm's fma), and the fmaddsub ones subtract c in the even elements.

6062vector_opName, _, VA, VB, Ret, Ctx, R ⟶
memberchkName, addsubps, addsubpd
!
lanesVA, Ret, Ctx, La
lanesVB, Ret, Ctx, Lb
addsub_lanesLa, Lb, 0, Ctx, Out
madeRet, Out, R
6067vector_opName, _, VA, VB, VC, Ret, Ctx, R ⟶
memberchkName−Sub, vfmaddps−none, vfmaddpd−none, vfmaddsubps−even, vfmaddsubpd−even
!
lanesVA, Ret, Ctx, La
lanesVB, Ret, Ctx, Lb
lanesVC, Ret, Ctx, Lc
fma_lanesLa, Lb, Lc, Sub, 0, Ctx, Out
madeRet, Out, R

VPERMILPS picks within each 128-bit lane by imm8's 2-bit fields; VPERMILPD's element i picks by imm8 bit i within its lane.

6074vector_opvpermilps, _, A, V, _, Ret, Ctx, R ⟶
!
immA, Ctx, I
Q = vecuint, 4, 16
lanesV, Q, Ctx, Ls
findallL,
between0, 3, J
S is I >> 2 × J /\ 3
nth0S, Ls, L
,
Out
madeQ, Out, vAddr, _
R = vAddr, Ret
6078vector_opName, _, A, V, _, Ret, Ctx, R ⟶
memberchkName, vpermilpd, vpermilpd256
!
immA, Ctx, I
Ret = vec_, N, Al
Q = vecullong, N, Al
lanesV, Q, Ctx, Ls
N1 is N−1
findallL,
between0, N1, J
S is J /\ -2 + I >> J /\ 1
nth0S, Ls, L
,
Out
madeQ, Out, vAddr, _
R = vAddr, Ret

VSHUFPD on 32 bytes: element j is A's (even j) or B's (odd j) element in j's lane that imm8 bit j picks.

6086vector_opshufpd256, _, _, C, VA, VB, _, Ret, Ctx, R ⟶
!
immC, Ctx, I
Q = vecullong, 4, 32
lanesVA, Q, Ctx, La
lanesVB, Q, Ctx, Lb
findallL,
between0, 3, J
S is J /\ -2 + I >> J /\ 1
J /\ 1 =:= 0
→
nth0S, La, L
else
nth0S, Lb, L
,
Out
madeQ, Out, vAddr, _
R = vAddr, Ret

lddqu is an unaligned 16-byte load, checked as _mm_loadu_si128's.

6093vector_oplddqu, _, vP, _, Ret, _, vCopy, Ret ⟶
!
local_slotRet, Copy
vector_accessread, vecchar, 16, 1, P
copy_objectRet, P, Copy
6097vector_opName, _, V, _, Ctx, vM, int ⟶
memberchkName−VT−Bit, pmovmskb128−vecuchar, 16, 16−7, movmskps−vecuint, 4, 16−31, movmskpd−vecullong, 2, 16−63, pmovmskb256−vecuchar, 32, 32−7, movmskps256−vecuint, 8, 32−31, movmskpd256−vecullong, 4, 32−63
!
lanesV, VT, Ctx, Ls
sign_bitsLs, 0, Bit, Ctx, vconst0, int, vM, _
6106vector_opName, _, V, C, Ret, Ctx, R ⟶
shift_builtinName, Kind, imm
!
shifted_lanesKind, V, C, Ret, Ctx, R
6109vector_opName, _, V, CV, Ret, Ctx, R ⟶
shift_builtinName, Kind, vector
!
lanesCV, Q, Ctx, C|_
shifted_lanesKind, V, C, Ret, Ctx, R
6113vector_opName, _, A, V, _, Ret, Ctx, R ⟶
memberchkName−Dir, pslldqi128_byteshift−left, psrldqi128_byteshift−right
!
immA, Ctx, I0
I is I0 /\ 255
lanesV, B, Ctx, Ls
numberedLs, Is
k0, Z
findallL,
memberJ, Is
Dir ≡ left
→
S is J−I
else
S is J + I
S ≥ 0
S < 16
→
nth0S, Ls, L
else
L = Z
,
Out
madeB, Out, vAddr, _
R = vAddr, Ret
6122vector_opName, _, A, B, Ret, Ctx, R ⟶
memberchkName−Lo−Hi, packsswb128−-128−127, packuswb128−0−255, packssdw128−-32768−32767, packusdw128−0−65535
!
A, B = v_, In, _
lanesA, In, Ctx, La
lanesB, In, Ctx, Lb
appendLa, Lb, Ls
Ret = vecRE, _, _
clamp_allLs, Lo, Hi, RE, Ctx, Out
madeRet, Out, R
6130vector_opName, _, A, V, _, Ret, Ctx, R ⟶
memberchkName, pshufd, pshuflw, pshufhw
!
immA, Ctx, I
lanesV, Ret, Ctx, Ls
findallL,
nth0J, Ls, _
shuffledName, I, J, S
nth0S, Ls, L
,
Out
madeRet, Out, R
6136vector_oppalignr128, _, _, A, VA, VB, _, Ret, Ctx, R ⟶
!
immA, Ctx, I0
I is I0 /\ 255
lanesVB, B, Ctx, Lb
lanesVA, B, Ctx, La
appendLb, La, T
k0, Z
findallL,
between0, 15, J
S is J + I
S < 32
→
nth0S, T, L
else
L = Z
,
Out
madeB, Out, vAddr, _
R = vAddr, Ret
6143vector_oppblendvb128, _, VA, VB, VM, Ret, Ctx, R ⟶
!
lanesVA, Ret, Ctx, La
lanesVB, Ret, Ctx, Lb
lanesVM, Ret, Ctx, Lm
blendvLa, Lb, Lm, Ctx, Out
madeRet, Out, R
6147vector_oppblendw128, _, _, A, VA, VB, _, Ret, Ctx, R ⟶
!
immA, Ctx, I
lanesVA, Ret, Ctx, La
lanesVB, Ret, Ctx, Lb
findallL,
nth0J, La, X
nth0J, Lb, Y
I >> J /\ 1 =:= 1
→
L = Y
else
L = X
,
Out
madeRet, Out, R
6153vector_opName, _, VA, VB, Ret, Ctx, R ⟶
memberchkName−In−Wide, pmuludq128−vecuint, 4, 16−ullong, pmuldq128−vecint, 4, 16−llong
!
lanesVA, In, Ctx, A0, _, A2, _
lanesVB, In, Ctx, B0, _, B2, _
convertA0, Wide, X0
convertA2, Wide, X2
bopmul, X0, B0, Ctx, P0
bopmul, X2, B2, Ctx, P2
madeRet, P0, P2, R

PCLMULQDQ: the carry-less product of A's quadword imm8 bit 0 selects and B's quadword bit 4 selects, as the XOR over each bit i set in b of a shifted left by i (its low quadword) and right by 64 - i (its high).

6163vector_oppclmulqdq128, _, _, C, VA, VB, _, Ret, Ctx, R ⟶
!
immC, Ctx, I
SA is I /\ 1
SB is I >> 4 /\ 1
lanesVA, Q, Ctx, La
lanesVB, Q, Ctx, Lb
nth0SA, La, A
nth0SB, Lb, B
Z = vconst0, ullong
clmul_bits0, A, B, Ctx, Z, Z, Lo, Hi
madeRet, Lo, Hi, R

SHUFPD and SHUFPS: imm8 picks each half's elements, the low half's from A and the high half's from B; done on integer lanes, bit-exact.

6172vector_opName, _, _, C, VA, VB, _, Ret, Ctx, R ⟶
memberchkName−Q−Bits, shufpd−vecullong, 2, 16−1, shufps−vecuint, 4, 16−2
!
immC, Ctx, I
lanesVA, Q, Ctx, La
lanesVB, Q, Ctx, Lb
Q = vec_, N, _
Half is N//2
Mask is 1 << Bits−1
N1 is N−1
findallL,
between0, N1, J
S is I >> Bits × J /\ Mask
J < Half
→
nth0S, La, L
else
nth0S, Lb, L
,
Out
madeQ, Out, vAddr, _
R = vAddr, Ret

SSE4.2's PCMPESTR* and PCMPISTR* (Intel SDM 4.1): imm8 bits 0-1 give the elements' format, 2-3 the aggregation, 4-5 the polarity, and 6 the index's end or the mask's form; the lengths are explicit (e, the absolute value of an int, saturated) or implicit (i, up to the first zero element). IntRes1 and IntRes2 are computed element by element, branch-free, then the index, mask or one flag is the result.

6186vector_opName, Args, Vs, Ret, Ctx, R ⟶
pcmpstrName, Len, Out
!
lastArgs, IA
immIA, Ctx, I
VT = vec_, N, _
Len ≡ e
→
Vs = VA, LA, VB, LB, _
else
Vs = VA, VB, _
LA = none
LB = none
lanesVA, VT, Ctx, La
lanesVB, VT, Ctx, Lb
pcmp_validLen, La, LA, Ctx, Va
pcmp_validLen, Lb, LB, Ctx, Vb
Agg is I >> 2 /\ 3
Pol is I >> 4 /\ 3
Msb is I >> 6 /\ 1
memberchkOut, is, izthe lengths alone
→
R2 = none
else
pcmp_aggregatedAgg, La, Lb, Va, Vb, Ctx, R1
pcmp_polarityPol, R1, Vb, Ctx, R2
lastVa, VaN
lastVb, VbN
R2 = R20|_
numberedR2, Js
pcmp_resultOut, Msb, VT, N, R2, Js, R20, VaN, VbN, Ret, Ctx, R
6200vector_oppmaddwd128, _, VA, VB, Ret, Ctx, R ⟶
!
lanesVA, H, Ctx, La
lanesVB, H, Ctx, Lb
pair_productsLa, Lb, uint, Ctx, Out
madeRet, Out, R
6204vector_oppmaddubsw128, _, VA, VB, Ret, Ctx, R ⟶
!
lanesVA, U, Ctx, La
lanesVB, S, Ctx, Lb
pair_productsLa, Lb, int, Ctx, Sums
clamp_allSums, -32768, 32767, short, Ctx, Out
madeRet, Out, R
6209vector_oppsadbw128, _, VA, VB, Ret, Ctx, R ⟶
!
lanesVA, U, Ctx, La
lanesVB, U, Ctx, Lb
lengthLa1, 8
appendLa1, La2, La
lengthLb1, 8
appendLb1, Lb2, Lb
abs_diff_sumLa1, Lb1, Ctx, vconst0, int, S1
abs_diff_sumLa2, Lb2, Ctx, vconst0, int, S2
madeRet, S1, S2, R
6215vector_opName, _, VA, VB, Ret, Ctx, R ⟶
memberchkName−In−Shift, pmulhuw128−vecushort, 8, 16−16, pmulhw128−vecshort, 8, 16−16
!
lanesVA, In, Ctx, La
lanesVB, In, Ctx, Lb
mulhiLa, Lb, Shift, Ctx, Out
madeRet, Out, R
6221vector_opName, _, VA, VB, Ret, Ctx, R ⟶
memberchkName−In, pavgb128−vecuchar, 16, 16, pavgw128−vecushort, 8, 16
!
lanesVA, In, Ctx, La
lanesVB, In, Ctx, Lb
averagedLa, Lb, Ctx, Out
madeRet, Out, R
6226vector_opName, _, VA, VB, Ret, Ctx, R ⟶
memberchkName, psignb128, psignw128, psignd128
!
lanesVA, Ret, Ctx, La
lanesVB, Ret, Ctx, Lb
signed_lanesLa, Lb, Ctx, Out
madeRet, Out, R
6231vector_opName, _, VA, VB, Ret, Ctx, R ⟶
memberchkName−Op−Sat, phaddw128−add−no, phaddd128−add−no, phsubw128−sub−no, phsubd128−sub−no, phaddsw128−add−yes, phsubsw128−sub−yes
!
lanesVA, Ret, Ctx, La
lanesVB, Ret, Ctx, Lb
appendLa, Lb, Ls
Ret = vecE, _, _
horizontalLs, Op, Sat, E, Ctx, Out
madeRet, Out, R
6238vector_oppshufb128, _, VA, VB, Ret, Ctx, R ⟶
!
VA = vAAddr, _
lanesVB, U, Ctx, Lb
shuffled_bytesLb, AAddr, Ctx, Out
madeU, Out, vAddr, _
R = vAddr, Ret
6242vector_opName, _, VA, VB, int, Ctx, vT, int ⟶
memberchkName, ptestz128, ptestc128, ptestnzc128
!
lanesVA, Q, Ctx, La
lanesVB, Q, Ctx, Lb
testedLa, Lb, z, Ctx, Z
testedLa, Lb, c, Ctx, C
Name ≡ ptestz128
→
vT, _ = Z
Name ≡ ptestc128
→
vT, _ = C
else
k0, K0
bopeq, Z, K0, Ctx, NZ
bopeq, C, K0, Ctx, NC
bopand, NZ, NC, Ctx, vT, _
6251vector_opName, _, A, V, _, Ret, Ctx, R ⟶
atom_concatvec_ext_, _, Name
!
immA, Ctx, I0
V = v_, VT
VT = vec_, N, _
I is I0 mod N
lanesV, VT, Ctx, Ls
nth0I, Ls, L
convertL, Ret, R
6257vector_opName, _, _, A, V, X, _, Ret, Ctx, R ⟶
atom_concatvec_set_, _, Name
!
immA, Ctx, I0
Ret = vec_, N, _
I is I0 mod N
lanesV, Ret, Ctx, Ls
nth0I, Ls, _, Rest
nth0I, Out, X, Rest
madeRet, Out, R
6264vector_opundef128, _, [], Ret, _, R ⟶
!unspecified: zero
Ret = vec_, N, _
lengthLs, N
maplist=vconst0, int, Ls
madeRet, Ls, R
6267vector_opName, _, V, Ret, Ctx, R ⟶
memberchkName−F, cvtss2si−float, cvtss2si64−float, cvtsd2si−double, cvtsd2si64−double
!
V = v_, VT
lanesV, VT, Ctx, X|_
rounded_evenX, F, Ctx, Y
convertY, Ret, R

CVTPD2PS: the two doubles rounded to float (to nearest, as the default MXCSR and a C conversion round), the upper two elements zero.

6274vector_opcvtpd2ps, _, V, Ret, Ctx, R ⟶
!
V = v_, VT
lanesV, VT, Ctx, A, B
convertA, float, FA
convertB, float, FB
madeRet, FA, FB, vconst0, int, vconst0, int, R
6278vector_opName, _, _, _, Ctx, _ ⟶
ctx_locCtx, Loc
sema_errorLoc, '__builtin_ia32_~w is not supported', Name
shift_builtin?Name, ?Kind, ?Count

Name shifts each element, Kind shl, shr (logical) or sar, by an int count or the low quadword of a vector.

6284shift_builtinName, Kind, Count ←
memberKind−P, shl−psll, shr−psrl, sar−psra
memberW, w, d, q
memberCount−S, imm−i128, vector−'128'
atomic_list_concatP, W, S, Name
¬
Kind ≡ sar
W ≡ q
shifted_lanes+Kind, +V, +C, +Ret, +Ctx, -R//

a count at or above the element width gives 0, or the sign, for sar.

6293shifted_lanesKind, V, C, Ret, Ctx, R ⟶
Ret = vecE, N, A
int_infoE, Bytes, _
Bits is 8 × Bytes
Max is Bits−1
Kind ≡ sar
→
LT = vecE, N, A
else
LT = vecUE, N, A
convertC, ullong, CU
kBits, KB
kMax, KM
boplt, CU, KB, Ctx, InRange
Kind ≡ sar
→
iselInRange, CU, KM, int, Ctx, S
else
bopand, CU, KM, Ctx, S0
convertS0, int, S
lanesV, LT, Ctx, Ls
shift_eachLs, Kind, S, InRange, UE, Ctx, Out
madeRet, Out, R
6308shift_each[], _, _, _, _, _, [] ⟶
[]
6309shift_eachL|Ls, Kind, S, InRange, UE, Ctx, O|Os ⟶
Kind ≡ sar
→
bopshr, L, S, Ctx, O
else
Kind ≡ shl
→
Op = shl
else
Op = shr
bopOp, L, S, Ctx, O0
convertO0, UE, O1
iselInRange, O1, vconst0, int, UE, Ctx, O
shift_eachLs, Kind, S, InRange, UE, Ctx, Os
6317unsigned_typeE, U ←
int_infoE, Bytes, _
memberchkBytes−U, 1−uchar, 2−ushort, 4−uint, 8−ullong
6321sign_bits[], _, _, _, M, M ⟶
[]
6322sign_bitsL|Ls, I, Bit, Ctx, M0, M ⟶
bopshr, L, vconstBit, int, Ctx, B0
convertB0, int, B
bopshl, B, vconstI, int, Ctx, B1
bopor, M0, B1, Ctx, M1
I1 is I + 1
sign_bitsLs, I1, Bit, Ctx, M1, M
6329clamp_all[], _, _, _, _, [] ⟶
[]
6330clamp_allL|Ls, Lo, Hi, T, Ctx, O|Os ⟶
convertL, long, L1
clampedL1, Lo, Hi, T, Ctx, O
clamp_allLs, Lo, Hi, T, Ctx, Os
shuffled+Name, +Imm, +J, -S

element J of pshufd, pshuflw or pshufhw's result is element S of its operand.

6337shuffledpshufd, I, J, S ←
S is I >> 2 × J /\ 3
6338shuffledpshuflw, I, J, S ←
J < 4
→
S is I >> 2 × J /\ 3
else
S = J
6339shuffledpshufhw, I, J, S ←
J ≥ 4
→
S is 4 + I >> 2 × J−4 /\ 3
else
S = J
6341blendv[], [], [], _, [] ⟶
[]
6342blendvA|As, B|Bs, M|Ms, Ctx, O|Os ⟶
M = v_, T
boplt, M, vconst0, int, Ctx, C
iselC, B, A, T, Ctx, O
blendvAs, Bs, Ms, Ctx, Os
pair_products+As, +Bs, +T, +Ctx, -Sums//

Sums are A0*B0 + A1*B1 for each pair of elements, computed in T.

6350pair_products[], [], _, _, [] ⟶
[]
6351pair_productsA0, A1|As, B0, B1|Bs, T, Ctx, S|Ss ⟶
convertA0, int, X0
convertA1, int, X1
convertB0, int, Y0
convertB1, int, Y1
bopmul, X0, Y0, Ctx, P0
bopmul, X1, Y1, Ctx, P1
convertP0, T, Q0
convertP1, T, Q1
bopadd, Q0, Q1, Ctx, S
pair_productsAs, Bs, T, Ctx, Ss
6359abs_diff_sum[], [], _, S, S ⟶
[]
6360abs_diff_sumA|As, B|Bs, Ctx, S0, S ⟶
bopsub, A, B, Ctx, D
boplt, D, vconst0, int, Ctx, Neg
bopsub, vconst0, int, D, Ctx, ND
iselNeg, ND, D, int, Ctx, AD
bopadd, S0, AD, Ctx, S1
abs_diff_sumAs, Bs, Ctx, S1, S
6368mulhi[], [], _, _, [] ⟶
[]
6369mulhiA|As, B|Bs, Shift, Ctx, O|Os ⟶
bopmul, A, B, Ctx, P
bopshr, P, vconstShift, int, Ctx, O
mulhiAs, Bs, Shift, Ctx, Os
6374horizontal[], _, _, _, _, [] ⟶
[]
6375horizontalA, B|Ls, Op, Sat, E, Ctx, O|Os ⟶
bopOp, A, B, Ctx, O0
Sat ≡ yes
→
convertO0, long, O1
clampedO1, -32768, 32767, E, Ctx, O
else
O = O0
horizontalLs, Op, Sat, E, Ctx, Os
6382averaged[], [], _, [] ⟶
[]
6383averagedA|As, B|Bs, Ctx, O|Os ⟶
bopadd, A, B, Ctx, S
bopadd, S, vconst1, int, Ctx, S1
bopshr, S1, vconst1, int, Ctx, O
averagedAs, Bs, Ctx, Os
6388signed_lanes[], [], _, [] ⟶
[]
6389signed_lanesA|As, B|Bs, Ctx, O|Os ⟶
A = v_, T
k0, Z
boplt, B, Z, Ctx, Neg
bopeq, B, Z, Ctx, IsZ
bopsub, Z, A, Ctx, NA
iselNeg, NA, A, T, Ctx, O1
iselIsZ, Z, O1, T, Ctx, O
signed_lanesAs, Bs, Ctx, Os
shuffled_bytes+Idx, +Addr, +Ctx, -Out//

pshufb: byte J is 0 where control byte J has its top bit set, else byte (control & 15) of the copy at Addr, which is 16 bytes, so no index leaves it.

6400shuffled_bytes[], _, _, [] ⟶
[]
6401shuffled_bytesI|Is, Addr, Ctx, O|Os ⟶
bopand, I, vconst15, int, Ctx, J
convertJ, long, vJL, _
letp, add, Addr, JL, P
load_typeduchar, P, Ctx, X
bopand, I, vconst128, int, Ctx, Top
bopne, Top, vconst0, int, Ctx, Zero
iselZero, vconst0, int, vX, uchar, uchar, Ctx, O
shuffled_bytesIs, Addr, Ctx, Os
tested+As, +Bs, +Which, +Ctx, -F//

ptest's ZF (A & B all zero) or CF (~A & B all zero), an int.

6412testedAs, Bs, Which, Ctx, F ⟶
tested_orAs, Bs, Which, Ctx, vconst0, ullong, Or
bopeq, Or, vconst0, int, Ctx, F
6416tested_or[], [], _, _, Or, Or ⟶
[]
6417tested_orA|As, B|Bs, Which, Ctx, Or0, Or ⟶
Which ≡ z
→
A1 = A
else
unarybnot, A, Ctx, A1
bopand, A1, B, Ctx, X
bopor, Or0, X, Ctx, Or1
tested_orAs, Bs, Which, Ctx, Or1, Or
rounded_even+X, +F, +Ctx, -Y//

Y is float or double X rounded to an integer, ties to even, as cvtss2si and cvtsd2si round by default: adding and subtracting 2^23 (2^52) with X's sign rounds a smaller magnitude; a larger one, an infinity or a NaN is kept.

6426rounded_evenX, F, Ctx, Y ⟶
F ≡ float
→
IT = uint
Sign = 0x80000000
Magic = 0x4B000000
Abs = 0x7fffffff
else
IT = ullong
Sign = 0x8000000000000000
Magic = 0x4330000000000000
Abs = 0x7fffffffffffffff
bits_ofX, F, IT, Ctx, XB
bopand, XB, vconstSign, IT, Ctx, S
bopor, S, vconstMagic, IT, Ctx, MB
bits_ofMB, IT, F, Ctx, M
bopadd, X, M, Ctx, X1
convertX1, F, X2
bopsub, X2, M, Ctx, X3
convertX3, F, X4
bits_ofX4, F, IT, Ctx, RB
bopand, XB, vconstAbs, IT, Ctx, AB
bopge, AB, vconstMagic, IT, Ctx, Big
iselBig, XB, RB, IT, Ctx, YB
bits_ofYB, IT, F, Ctx, Y
bits_of+V, +From, +To, +Ctx, -R//

R has V's bytes, read as To.

6442bits_ofvV, _, From, To, Ctx, R ⟶
local_slotFrom, Tmp
store_typedFrom, V, Tmp
loadedTo, Tmp, Ctx, R
generic_vector_op+Name, +Args, +T, +Ctx, -R//

a generic vector builtin of result type T.

6448generic_vector_op'__builtin_nondeterministic_value', _, T, _, R ⟶
!
T = vec_, N, _
lengthZs, N
maplist=vconst0, int, Zs
madeT, Zs, R
6451generic_vector_op'__builtin_shufflevector', A, B|Is, T, Ctx, R ⟶
!
rvalA, Ctx, VA
rvalB, Ctx, VB
VA = v_, VT
lanesVA, VT, Ctx, La
lanesVB, VT, Ctx, Lb
appendLa, Lb, Ls
lengthLs, NL
shuffle_picksIs, Ls, NL, Ctx, Out
madeT, Out, R
6457generic_vector_op'__builtin_convertvector', A, _, T, Ctx, R ⟶
!
rvalA, Ctx, VA
VA = v_, VT
lanesVA, VT, Ctx, Ls
madeT, Ls, R
6461generic_vector_opName, Args, T, Ctx, R ⟶
atom_concat'__builtin_elementwise_', Op, Name
T = vecE, _, _
elementwise_argsArgs, T, Ctx, Vs
ctx_locCtx, Loc
int_infoE, Bytes, Sign
memberchkOp, add_sat, sub_sat
→
Bytes ≤ 2
else
true
→
true
else
sema_errorLoc, '~w of a vector of ~w is not supported', Name, E
elementwiseOp, Vs, T, E, Sign, Bytes, Ctx, R
6472shuffle_picks[], _, _, _, [] ⟶
[]
6473shuffle_picksI|Is, Ls, NL, Ctx, L|Out ⟶
immI, Ctx, K
ctx_locCtx, Loc
K =:= -1undefined
→
L = vconst0, int
K ≥ 0
K < NL
→
nth0K, Ls, L
else
sema_errorLoc, 'shuffle index ~w out of range', K
shuffle_picksIs, Ls, NL, Ctx, Out
6482elementwise_args[], _, _, [] ⟶
[]
6483elementwise_argsA|As, T, Ctx, V|Vs ⟶
rvalA, Ctx, V0
convertV0, T, V
elementwise_argsAs, T, Ctx, Vs
6487elementwiseOp, Vs, T, E, Sign, Bytes, Ctx, R ⟶
lanes_ofVs, T, Ctx, Lss
elementwise_rowsRows, Op, E, Sign, Bytes, Ctx, Out
madeT, Out, R
6493lanes_of[], _, _, [] ⟶
[]
6494lanes_ofV|Vs, T, Ctx, Ls|Lss ⟶
lanesV, T, Ctx, Ls
lanes_ofVs, T, Ctx, Lss
6496transpose_lanesLs, Rows ←
!
findallL, memberL, Ls, Rows
6497transpose_lanesAs, Bs, Rows ←
pairs_rowsAs, Bs, Rows
6498pairs_rows[], [], []
6499pairs_rowsA|As, B|Bs, A, B|Rs ←
pairs_rowsAs, Bs, Rs
6501elementwise_rows[], _, _, _, _, _, [] ⟶
[]
6502elementwise_rowsRow|Rows, Op, E, Sign, Bytes, Ctx, O|Os ⟶
elementwise_oneOp, Row, E, Sign, Bytes, Ctx, O
elementwise_rowsRows, Op, E, Sign, Bytes, Ctx, Os
6506elementwise_oneabs, A, E, _, _, Ctx, O ⟶
k0, Z
boplt, A, Z, Ctx, Neg
bopsub, Z, A, Ctx, NA
iselNeg, NA, A, E, Ctx, O
6509elementwise_onemin, A, B, E, _, _, Ctx, O ⟶
boplt, A, B, Ctx, C
iselC, A, B, E, Ctx, O
6511elementwise_onemax, A, B, E, _, _, Ctx, O ⟶
bopgt, A, B, Ctx, C
iselC, A, B, E, Ctx, O
6513elementwise_oneOp, A, B, E, Sign, Bytes, Ctx, O ⟶
memberchkOp−BOp, add_sat−add, sub_sat−sub
Bits is 8 × Bytes
Sign ≡ signed
→
Lo is −1 << Bits−1
Hi is 1 << Bits−1−1
else
Lo = 0
Hi is 1 << Bits−1
convertA, int, A1
convertB, int, B1
bopBOp, A1, B1, Ctx, S
convertS, long, S1
clampedS1, Lo, Hi, E, Ctx, O
vector_access+K, +VT, +Addr//

a K (read or write) access of a vector of type VT at Addr is checked as one access of its full width, with its alignment capped at 8, before any of its 8-byte chunks (docs/vector-access.md), as filcc checks it.

6527vector_accessK, VT, Addr ⟶
envEnv
type_size_alignVT, Env, Size, A
Align is minA, 8
emitaccessK, Addr, Size, Align
vector_chunks_loaded+Ebs, +Src, -Xs//

, vector_chunks_stored(+Ebs, +Xs, +Dst)//: a vector store's 8-byte chunks, loaded from the copy that is its value before the full-width check, and stored after it.

6535vector_chunks_loaded[], _, [] ⟶
[]
6536vector_chunks_loadedebOff, _, _|Ebs, Src, X|Xs ⟶
offsetSrc, Off, S
letl, loadl, S, X
6540vector_chunks_stored[], [], _ ⟶
[]
6541vector_chunks_storedebOff, _, _|Ebs, X|Xs, Dst ⟶
offsetDst, Off, D
emitstorel, X, D
lanes_loaded+VT, +Addr, +Ctx, -Xs//

Xs are the elements of the vector of type VT at Addr.

6547lanes_loadedvecE, N, _, Addr, Ctx, Xs ⟶
envEnv
type_size_alignE, Env, ES, _
N1 is N−1
numlist0, N1, Is
lanes_loaded_Is, E, ES, Addr, Ctx, Xs
6552lanes_loaded_[], _, _, _, _, [] ⟶
[]
6553lanes_loaded_I|Is, E, ES, Addr, Ctx, X|Xs ⟶
Off is I × ES
offsetAddr, Off, P
load_typedE, P, Ctx, X
lanes_loaded_Is, E, ES, Addr, Ctx, Xs
lanes_stored+VT, +Addr, +I, +Xs//

store Xs as the elements of the vector of type VT at Addr from element I.

6560lanes_stored_, _, _, [] ⟶
[]
6561lanes_storedVT, Addr, I, X|Xs ⟶
VT = vecE, _, _
envEnv
type_size_alignE, Env, ES, _
Off is I × ES
offsetAddr, Off, P
I1 is I + 1
lanes_storedVT, Addr, I1, Xs
lane_results+Xs, +Ys, +Op, +E, +RE, +Ctx, -Zs//

Zs, of element type RE, are Op applied to elements Xs and Ys of type E: a comparison gives -1 where it holds, as GNU C's does, and other operators wrap to the element type.

6573lane_results[], [], _, _, _, _, [] ⟶
[]
6574lane_resultsX|Xs, Y|Ys, Op, E, RE, Ctx, Z|Zs ⟶
binaryOp, vX, E, vY, E, Ctx, Z0
cmp_opOp, _, _
→
binarysub, vconst0, int, Z0, Ctx, Z1
else
Z1 = Z0
convertZ1, RE, vZ, _
lane_resultsXs, Ys, Op, E, RE, Ctx, Zs
6583lane_unary[], _, _, _, [] ⟶
[]
6584lane_unaryX|Xs, Op, E, Ctx, Z|Zs ⟶
unaryOp, vX, E, Ctx, Z0
convertZ0, E, vZ, _
lane_unaryXs, Op, E, Ctx, Zs
eightbytes+Type, +Env, +Loc, -Ebs

Ebs are the eightbytes of Type.

6590eightbytesType, Env, Loc, Ebs ←
catchtype_size_alignType, Env, Size, _, badWhy, sema_errorLoc, '~w has an unsupported type (~w)', Type, Why
ptr_offsetsType, Env, 0, PtrOffs0, []
sortPtrOffs0, PtrOffs
Last is Size + 7//8−1
findallebOff, N, Class,
between0, Last, I
Off is 8 × I
N is min8, Size−Off
memberchkOff, PtrOffs
→
Class = p
else
Class = l
,
Ebs
ptr_offsets+Type, +Env, +Base, -Offs0, ?Offs

Offs0-Offs is the difference list of the offsets, from Base, where Type holds pointers.

6606ptr_offsetsptr_, _, Base, Base|Offs, Offs ←
!
6607ptr_offsetsarrayElemType, N, Env, Base, Offs0, Offs ←
integerN
!
ptr_offsetsElemType, Env, 0, ElemOffs, []
ElemOffs ≡ []
→
Offs0 = Offs
else
type_size_alignElemType, Env, Size, _
Last is N−1
findallI, between0, Last, I, Indices
foldlelem_ptrsElemType, Env, Base, Size, Indices, Offs0, Offs
6617ptr_offsetsSU, Env, Base, Offs0, Offs ←
suSU
!
SU =.. _, Id
env_defsEnv, Defs
get_assocId, Defs, def_, Members, _, _
foldlmember_ptrsEnv, Base, Members, Offs0, Offs
6624ptr_offsets_, _, _, Offs, Offs
6626elem_ptrsElemType, Env, Base, Size, I, Offs0, Offs ←
Base1 is Base + I × Size
ptr_offsetsElemType, Env, Base1, Offs0, Offs
6630member_ptrsEnv, Base, m_, Type, Off, Offs0, Offs ←
Base1 is Base + Off
ptr_offsetsType, Env, Base1, Offs0, Offs
6633member_ptrs_, _, m_, _, _, bits_, _, Offs, Offs
arg_words+Type, +Env, +Loc, -Ebs

Ebs are the eightbytes in which a struct or union argument of Type is passed. A byval one (class MEMORY) goes only in the generic packet, where its words are its bytes, so a float member does not matter; its eightbytes start at an 8-aligned offset, as clang's do for alignments up to 8, the most nanofil's types have. Others are classified as SysV does (abi_eightbytes/4).

6641arg_wordsldouble, _, _, eb0, 8, l, eb8, 8, l ←
!
6642arg_wordsType, Env, Loc, Ebs ←
byval_typeType, Env
→
eightbytesType, Env, Loc, Ebs
else
abi_eightbytesType, Env, Loc, Ebs
abi_memory+Type

an argument or result of Type crosses calls as the words of its bytes (arg_words/4): a struct or union, or a long double, whose 10 bytes and 6 zero bytes of padding go as two l words, as in clang's generic packet (docs/float.md).

6651abi_memoryType ←
6652abi_memoryldouble
abi_source+Type, +V, -Addr//

Addr holds the bytes of the value V of Type: V itself for a struct or union, whose value is its address; a fresh 16-byte local for a long double. Only store(x) ever writes the local, so its last 6 bytes stay zero.

6658abi_sourceldouble, V, Addr ⟶
!
local_slotldouble, Addr
emitstorex, V, Addr
6661abi_source_, Addr, Addr ⟶
[]
arm_value+Type, +V0, -V//

V is the ?: arm value V0 as the join's phi carries it: a long double through memory, since no phi has class x.

6665arm_valueldouble, V0, A ⟶
!
abi_sourceldouble, V0, A
6666arm_value_, V, V ⟶
[]
x87_type+Type, +Env

Type is or holds a long double. clang passes none of them in the fast convention's registers (a long double is digit 3, on the native stack), and nanofil makes a function taking or returning one generic-only (docs/float.md).

6672x87_typeldouble, _ ←
!
6673x87_typeType, Env ←
suType
leavesType, Env, 0, Leaves, []
memberchk_−x87, Leaves
pad16_mark+Type, +Env, +Args0, -Args

an argument of Type with alignment 16 that holds a long double starts at a 16-aligned packet offset, as clang lays out the generic packet: Args is Args0 after a pad16 marker, which pad_args//3 resolves once the word count before it is known.

6681pad16_markType, Env, Args, pad16|Args ←
x87_typeType, Env
!
6683pad16_mark_, _, Args, Args
pad_args+Args0, +N0, -Args//

and pad_params(+Params0, +N0, -Params)//: each pad16 marker becomes a zero l word when N0 words, counting an sret pointer, precede it at an odd count, and is dropped otherwise.

6688pad_args[], _, [] ⟶
[]
6689pad_argspad16|As0, N0, As ⟶
!
N0 mod 2 =:= 1
→
As = argl, const0|As1
N = N0 + 1
else
As = As1
N = N0
N1 is N
pad_argsAs0, N1, As1
6694pad_argsA|As0, N0, A|As ⟶
N is N0 + 1
pad_argsAs0, N, As
6696pad_params[], _, [] ⟶
[]
6697pad_paramspad16|Ps0, N0, Ps ⟶
!
N0 mod 2 =:= 1
→
Ps = paraml, P|Ps1
N is N0 + 1
else
Ps = Ps1
N = N0
pad_paramsPs0, N, Ps1
6702pad_paramsP|Ps0, N0, P|Ps ⟶
N is N0 + 1
pad_paramsPs0, N, Ps
generic_only_type+Type, +Env

clang gives a function taking a struct or union of Type no fast entry: it is byval, or (as for a result, float_pair_type/2) an eightbyte of it holds two floats, which clang passes as <2 x float>, a type outside the fast signature's digits. nanofil makes such a function generic-only too, so the signatures agree. A byval result is an sret pointer, which leaves the fast entry.

6710generic_only_typeType, Env ←
x87_typeType, Env
!
6711generic_only_typeType, Env ←
byval_typeType, Env
!
6712generic_only_typeType, Env ←
6714float_pair_typeType, Env ←
¬byval_typeType, Env
eightbytesType, Env, unknown, Ebs
leavesType, Env, 0, Leaves, []
memberebO, _, _, Ebs
End is O + 8
findallT,
memberOff−T, Leaves
Off ≥ O
Off < End
,
T1, T2|Ts
forallmemberT, T1, T2|Ts, T ≡ float
!
byval_type+Type, +Env

a struct or union of Type has SysV class MEMORY, so clang passes it byval and returns it through sret: it is larger than 16 bytes, or packed with misaligned members.

6725byval_typeType, Env ←
type_size_alignType, Env, Size, _
Size > 16
→
true
else
ret_shape+Type, +Env, +Loc, -Shape

Shape is how a struct or union of Type is returned: sret, or words(Eightbytes).

6731ret_shapeldouble, _, _, wordseb0, 8, l, eb8, 8, l ←
!
6732ret_shapeType, Env, Loc, Shape ←
byval_typeType, Env
→
Shape = sret
else
abi_eightbytesType, Env, Loc, Ebs
Shape = wordsEbs
abi_eightbytes+Type, +Env, +Loc, -Ebs

the eightbytes of a struct or union of 16 bytes or less, classed as the SysV psABI (3.2.3) and clang do. An eightbyte is p where a pointer starts. One holding only floating members is SSE: d if a double is among them, s for a lone float at its start, else l, for clang's <2 x float>, whose bytes go in an integer word in nanofil and in the generic packet's cell in both (so the function is generic-only in clang, by signature). Any other eightbyte is INTEGER, l. One without a scalar in it (only padding, as after aligned(16) on a struct of one int, or only unnamed bit-fields) is NO_CLASS, and is not passed at all, as clang does: its coerced type covers only the other eightbyte.

6749abi_eightbytesType, Env, Loc, Ebs ←
classed_eightbytesType, Env, Loc, Leaves, Ebs0
includeholds_leafLeaves, Ebs0, Ebs
classed_eightbytes+Type, +Env, +Loc, -Leaves, -Ebs

Ebs are all the eightbytes of Type, classed as abi_eightbytes/4 does; Leaves are its scalars (leaves/5).

6756classed_eightbytesType, Env, Loc, Leaves, Ebs ←
eightbytesType, Env, Loc, Ebs0
leavesType, Env, 0, Leaves, []
maplisteightbyte_classLeaves, Ebs0, Ebs
variadic_words+Type, +Env, +Loc, -Ebs

the eightbytes in which a struct or union of Type is passed past a prototype. clang passes it byval there, every eightbyte in the packet, NO_CLASS ones included, which is where va_arg reads them.

6765variadic_wordsType, Env, Loc, Ebs ←
Type ≡ ldouble
or
byval_typeType, Env
→
arg_wordsType, Env, Loc, Ebs
else
classed_eightbytesType, Env, Loc, _, Ebs
6771holds_leafLeaves, ebO, _, _ ←
End is O + 8
memberOff−T, Leaves
T ≡ x87X87UP too
→
LeafEnd is Off + 16
else
LeafEnd is Off + 1
Off < End
LeafEnd > O
!
6777eightbyte_class_, ebO, N, p, ebO, N, p ←
!
6778eightbyte_classLeaves, ebO, N, _, ebO, N, K ←
End is O + 8
findallOff−T,
memberOff−T, Leaves
Off ≥ O
Off < End
,
In
In ≢ []
forallmember_−T, In, memberchkT, float, double
→
memberchk_−double, In
→
K = d
forallmemberOff−_, In, Off =:= O
→
K = s
else
K = l
else
K = l
leaves+Type, +Env, +Base, -Leaves0, ?Leaves

Leaves0-Leaves are the scalars of Type as Offset-Kind, Offset from Base and Kind one of int, ptr, float, double or x87; a bit-field is an int at its unit.

6792leavesfloat, _, B, B−float|L, L ←
!
6793leavesdouble, _, B, B−double|L, L ←
!
6794leavesldouble, _, B, B−x87|L, L ←
!
6795leavesptr_, _, B, B−ptr|L, L ←
!
6796leavesvecT, N, _, Env, B, L0, L ←
!
leavesarrayT, N, Env, B, L0, L
6797leavesarrayT, N, Env, B, L0, L ←
integerN
N > 0
!
type_size_alignT, Env, Size, _
Last is N−1
numlist0, Last, Is
foldlelem_leavesT, Env, B, Size, Is, L0, L
6802leavesSU, Env, B, L0, L ←
suSU
!
SU =.. _, Id
env_defsEnv, Defs
get_assocId, Defs, def_, Members, _, _
foldlmember_leavesEnv, B, Members, L0, L
6807leavesarray_, _, _, _, L, L ←
!
6808leaves_, _, B, B−int|L, L
6810elem_leavesT, Env, B, Size, I, L0, L ←
B1 is B + I × Size
leavesT, Env, B1, L0, L
6813member_leavesEnv, B, m_, T, Off, L0, L ←
B1 is B + Off
leavesT, Env, B1, L0, L
6815member_leaves_, B, m_, _, Off, bits_, _, B1−int|L, L ←
B1 is B + Off
copy_object+Type, +Src, +Dst//

copy the object of Type at Src to Dst, an eightbyte at a time. Two kinds of object are copied by the runtime's zmemmove instead (never a memcpy the program defines), which checks both whole ranges first and keeps each capability whose word is 8-aligned at both ends:

  • one of more than 40 bytes, as filcc copies it (FilPizlonator's InlineMemmoveDstSizeLimit): a word at a time, minizip's 64 KB *zi = ziinit was a 16k-word function;
  • one aligned below 8 that holds a pointer (libmbim's, elfutils' and parted's packed structs), which may be at any address: load(p) there would trap, and an eightbyte copied as bytes would lose the capability of a pointer that the address happens to align.
6829copy_objectType, Src, Dst ⟶
envEnv
eightbytesType, Env, unknown, Ebs
type_size_alignType, Env, Size, Align
Size > 40
→
true
else
Align < 8
ptr_offsetsType, Env, 0, _|_, []
→
emitcallvoid, globalzmemmove, argp, Dst, argp, Src, argl, constSize
else
copy_ebsEbs, Src, Dst
6841copy_ebs[], _, _ ⟶
[]
6842copy_ebsebOff, N, Class|Ebs, Src, Dst ⟶
offsetSrc, Off, S
offsetDst, Off, D
Class ≡ p
→
letp, loadp, S, V
emitstorep, V, D
else
piecesN, 0, Pieces
copy_piecesPieces, S, D
copy_ebsEbs, Src, Dst
pieces+N, +Off, -Pieces

Pieces are N bytes from offset Off as p(Offset, Bytes, Load, Store), widest first. The cut after the member commits to the widest piece that fits.

6856pieces0, _, [] ←
!
6857piecesN, Off, pOff, Bytes, Load, Store|Pieces ←
memberBytes−Load−Store, 8−l−l, 4−uw−w, 2−uh−h, 1−ub−b
Bytes ≤ N
!
N1 is N−Bytes
Off1 is Off + Bytes
piecesN1, Off1, Pieces
6865copy_pieces[], _, _ ⟶
[]
6866copy_piecespOff, _, Load, Store|Pieces, Src, Dst ⟶
offsetSrc, Off, S
offsetDst, Off, D
letl, loadLoad, S, V
emitstoreStore, V, D
copy_piecesPieces, Src, Dst
load_words+Ebs, +Addr, -Words//

Words are the words of the value at Addr, one per eightbyte.

6875load_words[], _, [] ⟶
[]
6876load_wordsebOff, N, Class|Ebs, Addr, W|Words ⟶
offsetAddr, Off, P
memberchkClass, p, s, d, x
→
letClass, loadClass, P, W
else
piecesN, 0, Pieces
load_piecesPieces, P, none, W
load_wordsEbs, Addr, Words
load_pieces+Pieces, +P, +Acc0, -W//

W is Acc0 (none at first) or'd with each of Pieces loaded from P and shifted into place.

6886load_pieces[], _, W, W ⟶
[]
6887load_piecespOff, _, Load, _|Pieces, P, Acc0, W ⟶
offsetP, Off, P1
letl, loadLoad, P1, Piece0
Off =:= 0
→
Piece = Piece0
else
Shift is 8 × Off
letl, shl, Piece0, constShift, Piece
Acc0 ≡ none
→
Acc = Piece
else
letl, or, Acc0, Piece, Acc
load_piecesPieces, P, Acc, W
store_words+Ebs, +Words, +Addr//

store Words, one per eightbyte, into the object at Addr.

6901store_words[], [], _ ⟶
[]
6902store_wordsebOff, N, Class|Ebs, W|Words, Addr ⟶
offsetAddr, Off, P
memberchkClass, p, s, d, x
→
emitstoreClass, W, P
else
piecesN, 0, Pieces
store_piecesPieces, W, P
store_wordsEbs, Words, Addr
store_pieces+Pieces, +W, +P//

store each of Pieces of word W, shifted down, at P.

6912store_pieces[], _, _ ⟶
[]
6913store_piecespOff, _, _, Store|Pieces, W, P ⟶
offsetP, Off, P1
Off =:= 0
→
Piece = W
else
Shift is 8 × Off
letl, shr, W, constShift, Piece
emitstoreStore, Piece, P1
store_piecesPieces, W, P

Static initializers

static_items(+Type, +Init, +Loc, +Env0, -Env, -Items): the data items of a global of type Type. Each leaf of Init becomes a chunk chunk(Offset, Bytes, Item), a later one replacing an earlier one at the same offset, then the gaps are zero-filled. A pointer is item(p, sym(S, Off)); string literals in initializers become data of their own. The chunks are kept by offset as they are made, so that a designator that goes back (each row of tree-sitter's parse tables starts with a high index) overrides in time logarithmic in their number rather than linear.

6933static_itemsType, Init, Loc, Env0, Env, Items ←
type_size_alignType, Env0, Size, _
Init ≡ none
→
Leaves = []
else
init_leavesType, Init, cEnv0, none, Loc, Leaves
empty_assocChunks0
foldlstatic_leaf_onceLoc, Leaves, sEnv0, Chunks0, 0−no, sEnv, Chunks, _−_
assoc_to_valuesChunks, Sorted
fill_itemsSorted, 0, Size, Loc, Items
fill_items+Chunks, +Pos, +Size, +Loc, -Items

Items are the items of Chunks, sorted by offset, with zero(N) filling the gaps from Pos to Size.

6946fill_items[], Pos, Size, _, Zeros ←
Pos < Size
→
N is Size−Pos
Zeros = zeroN
else
Zeros = []
6950fill_itemschunkOff, N, Item|Chunks, Pos, Size, Loc, Items ←
Off < Pos
→
sema_errorLoc, 'overlapping initializers', []
else
true
Off > Pos
→
Gap is Off−Pos
Items = zeroGap, Item|Items1
else
Items = Item|Items1
Pos1 is Off + N
fill_itemsChunks, Pos1, Size, Loc, Items1
int_chunk+Off, +Type, +V, -Chunk

Chunk holds integer V of Type at Off.

6962int_chunkOff, Type, V, chunkOff, N, itemWidth, constU ←
int_infoType, N, _
memberchkN−Width, 1−b, 2−h, 4−w, 8−l
convert_intV, Type, U
static_leaf_once+Loc, +Leaf, +S0−Str0, −S−Str

static_leaf/4, committed: a choicepoint left per leaf would keep every leaf's state alive. Str is yes when the chunks may hold a string's bytes as one chunk; a leaf before the end so far (a designator that goes back) first splits those into a chunk per byte, so that it overrides only the bytes it covers.

6973static_leaf_onceLoc, Leaf, sEnv, Chunks0, End−Str0, S−Str ←
arg1, Leaf, Off
Str0 ≡ yes
Off < End
→
split_strsChunks0, Chunks1
Str1 = no
else
Chunks1 = Chunks0
Str1 = Str0
oncestatic_leafLoc, Leaf, sEnv, Chunks1, End, S0
Off < End
functorLeaf, val, 3
or
functorLeaf, expr, 3
S0 = sEnv1, Chunks2, End1
get_assocOff, Chunks2, chunk_, N, _
N > 1
→
It overrides the split bytes of a wider table element.
O1 is Off + 1
Hi is Off + N
drop_chunksO1, Hi, Chunks2, Chunks3
S = sEnv1, Chunks3, End1
else
S = S0
functorLeaf, vals, 3
→
Str = yes
else
Str = Str1
split_strs+Chunks0, -Chunks

Chunks is Chunks0 with each string chunk split into a chunk per byte. Nothing after a string's bytes overrides them before they are split, so they replace only older chunks.

6994split_strsChunks0, Chunks ←
assoc_to_valuesChunks0, Cs
foldlsplit_str, Cs, Chunks0, Chunks
6998split_strchunkOff, _, itemb, strCs, Chunks0, Chunks ←
!
foldlstr_byte_chunk, Cs, Off−Chunks0, _−Chunks
7000split_str_, Chunks, Chunks
7002str_byte_chunkC, Off−Chunks0, Off1−Chunks ←
char_codeC, N
Off1 is Off + 1
put_assocOff, Chunks0, chunkOff, 1, itemb, constN, Chunks
drop_chunks+Lo, +Hi, +Chunks0, -Chunks

Chunks is Chunks0 without the chunks that start in [Lo, Hi): looked up one offset at a time in a short range, else by scanning the offsets that are there.

7009drop_chunksLo, Hi, Chunks0, Chunks ←
Hi−Lo ≤ 64
→
Top is Hi−1
numlistLo, Top, Offs
else
assoc_to_keysChunks0, Keys
includewithinLo, Hi, Keys, Offs
foldldrop_chunk, Offs, Chunks0, Chunks
7017withinLo, Hi, Off ←
Off ≥ Lo
Off < Hi
7019drop_chunkOff, Chunks0, Chunks ←
del_assocOff, Chunks0, _, Chunks1
→
Chunks = Chunks1
else
Chunks = Chunks0
put_chunk+Chunk, +Chunks0, -Chunks

Chunks maps Chunk's offset to Chunk, replacing any earlier one there.

7024put_chunkchunkOff, N, Item, Chunks0, Chunks ←
put_assocOff, Chunks0, chunkOff, N, Item, Chunks

static_leaf(+Loc, +Leaf, +S0, -S): S = s(Env, Chunks, End) adds the chunk of Leaf to the Chunks of S0, by offset; End is where the chunks end so far. A zero leaf drops the chunks it covers, which only a designator that goes back can have made.

7031static_leaf_, zeroOff, Type, sEnv, Chunks0, End, sEnv, Chunks, End ←
Off ≥ End
→
Chunks = Chunks0
else
type_size_alignType, Env, Size, _
Hi is Off + Size
drop_chunksOff, Hi, Chunks0, Chunks
7038static_leaf_, vals_, _, [], S, S ←
!
7039static_leaf_, valsOff, Type, Vs, sEnv, Chunks0, End0, sEnv, Chunks, End ←
int_infoType, Size, _
phrasevals_charsVs, Type, Size, Cs
lengthCs, N
Chunk = chunkOff, N, itemb, strCs
put_chunkChunk, Chunks0, Chunks
chunk_endChunk, End0, End
7047static_leaf_, valOff, Type, V, sEnv, Chunks0, End0, sEnv, Chunks, End ←
int_chunkOff, Type, V, Chunk
put_chunkChunk, Chunks0, Chunks
chunk_endChunk, End0, End
7052static_leafLoc, exprOff, Type, Expr, sEnv0, Chunks0, End0, sEnv, Chunks, End ←
expr_chunkType, Off, Expr, Loc, Env0, Env, Chunk
put_chunkChunk, Chunks0, Chunks
chunk_endChunk, End0, End
7058static_leafLoc, bitsOff, Type, Pos, W, Expr, sEnv, Chunks0, End0, sEnv, Chunks, End ←
catchconstant_valueExpr, Env, V0, From, not_constant, fail
const_convertV0, From, Type, V
integerV
→
true
else
sema_errorLoc, 'initializer is not a constant', []
Start is 8 × Off + Pos
First is Start//8
Last is Start + W−1//8
numlistFirst, Last, Bytes
foldlbitfield_byteStart, W, V, Bytes, Chunks0, Chunks
End is maxEnd0, Last + 1
bitfield_byte+Start, +W, +V, +Byte, +Chunks0, -Chunks

Chunks maps the byte at offset Byte to its bits of the W-bit field V, starting at bit Start, merged into the bits an earlier bit-field put there.

7074bitfield_byteStart, W, V, Byte, Chunks0, Chunks ←
get_assocByte, Chunks0, chunk_, N, Item
→
N =:= 1
Item = itemb, constOld
→
true
else
Old = 0
else
Old = 0
Shift is Start−8 × Byte
Mask is 1 << W−1 << Shift + 64 >> 64 /\ 255
Bits is V /\ 1 << W−1 << Shift + 64 >> 64 /\ 255
New is Old /\ \Mask /\ 255 \/ Bits
put_chunkchunkByte, 1, itemb, constNew, Chunks0, Chunks
vals_chars+Vs, +Type, +Size//

the bytes of the elements Vs of integer Type and Size, little-endian, as characters.

7087vals_chars[], _, _ ⟶
[]
7088vals_charsV|Vs, Type, Size ⟶
convert_intV, Type, U
le_charsSize, U
vals_charsVs, Type, Size
7093le_chars0, _ ⟶
!
7094le_charsK, U ⟶
N is U /\ 255
char_codeC, N
U1 is U >> 8
K1 is K−1
C
le_charsK1, U1
7099chunk_endchunkOff, N, _, End0, End ←
End is maxEnd0, Off + N
expr_chunk+Type, +Off, +Expr, +Loc, +Env0, -Env, -Chunk

Chunk holds the constant value of Expr, of scalar Type, at Off.

7103expr_chunkType, Off, Expr, Loc, Env, Env, Chunk ←
!
catchconstant_valueExpr, Env, V0, From, not_constant, fail
const_convertV0, From, Type, V
integerV
→
true
else
sema_errorLoc, 'initializer is not a constant', []
→
type_classType, K
Chunk = chunkOff, N, itemK, constV
else
int_chunkOff, Type, V, Chunk
7116expr_chunkptr_, Off, Expr, Loc, Env0, Env, Chunk ←
!
elaborateExpr, Env0, Loc, X
→
valueX, XV
else
sema_errorLoc, 'initializer is not an address constant', []
address_constantXV, Loc, Env0, Env, R
Off mod 8 ≠ 0a packed member; null is fine
R = sym_, _
or
R = intN0
N0 ≠ 0
→
sema_errorLoc, 'Fil-C cannot initialize this object: a pointer in its initializer is not word-aligned', []
R = symSym, SymOff
→
Chunk = chunkOff, 8, itemp, symSym, SymOff
R = intN
→
Chunk = chunkOff, 8, iteml, constN
else
Chunk = chunkOff, 8, iteml, const0
7129expr_chunkType, _, _, Loc, _, _, _ ←
sema_errorLoc, 'cannot statically initialize a ~w', Type
address_constant+XV, +Loc, +Env0, -Env, -Rdet

The value tree XV is an address constant (C11 6.6p9), R = sym(Symbol, Offset), or a null pointer, R = null, or an integer constant cast to a pointer, R = int(Address). It is the address of a static object or a function, perhaps cast, plus or minus an integer constant; string literals and compound literals become data of their own (Env). An address built from an integer constant, as &((T *)0)->m (constant_address/3), is int(Address) or null too: an address with no capability, as clang gives it. Anything else is an error. Static initializers use it.

7143address_constantXV, _, Env, Env, R ←
!
U is A mod 1 << 64
U =:= 0
→
R = null
else
R = intU
7148address_constantval_, decayX, Loc, Env0, Env, R ←
!
place_constantX, Loc, Env0, Env, R
7150address_constantval_, addrX, Loc, Env0, Env, R ←
!
place_constantX, Loc, Env0, Env, R
7152address_constantval_, castTypeName, XV, Loc, Env0, Env, R ←
!
type_nameTypeName, Type, Env0, Loc
Type = ptr_
loweredXV, T
foldedXV, Env0, N
integerN
→
6.6p9: an integer constant cast to a pointer type is an address
constant, here an address with no capability, as in clang.
Env = Env0
U is N mod 1 << 64
U =:= 0
→
R = null
else
R = intU
Type = ptr_
→
address_constantXV, Loc, Env0, Env, R
A pointer cast to a 64-bit integer, inside a cast back to a
pointer (the clause above), is the same address constant with
its capability, as filcc keeps it.
type_size_alignType, Env0, 8, _
loweredXV, ptr_
→
address_constantXV, Loc, Env0, Env, R
else
sema_errorLoc, 'initializer is not a constant', []
7167address_constantvalptr_, condXC, XA, XB, Loc, Env0, Env, R ←
c ? a : b with an integer constant c is the address constant its
chosen arm is, as clang accepts it (6.6p10; nettle's curve tables
pick a reduction function by USE_REDC ? f : g).
loweredXC, TC
foldedXC, Env0, C
integerC
!
C ≠ 0
→
address_constantXA, Loc, Env0, Env, R
else
address_constantXB, Loc, Env0, Env, R
7176address_constantvalptr_, binOp, XA, XB, Loc, Env0, Env, R ←
memberchkOp, add, sub
loweredXA, TA
loweredXB, TB
TA = ptrElem
→
XP = XA
XK = XB
else
TB = ptrElem
XP = XB
XK = XA
foldedXK, Env0, K
integerK
!
address_constantXP, Loc, Env0, Env, R0
offset_constOp, R0, Elem, K, Env, Loc, R
7186address_constantXV, Loc, Env, Env, null ←
loweredXV, T
foldedXV, Env, 0
→
true
else
sema_errorLoc, 'initializer is not an address constant', []
offset_const+Op, +R0, +Elem, +K, +Env, +Loc, -R

R is address constant R0 plus (add) or minus (sub) K elements of type Elem.

7193offset_constOp, symSym, Off0, Elem, K, Env, Loc, symSym, Off ←
!
catcharith_sizeElem, Env, Size, badWhy, sema_errorLoc, 'pointer arithmetic on ~w (~w)', Elem, Why
Op ≡ add
→
Off is Off0 + K × Size
else
Off is Off0−K × Size
7199offset_const_, _, _, _, _, Loc, _ ←
sema_errorLoc, 'initializer is not an address constant', []
place_constant+X, +Loc, +Env0, -Env, -R

typed tree X designates a static object or a function at R = sym(Symbol, Offset).

7204place_constantobj_, idName, Loc, Env, Env, symSym, 0 ←
!
lookup_ordinaryEnv, Name, gvar_, Sym, Storage, _
→
Storage = thread_local_6.6p9
→
sema_errorLoc, 'the address of thread-local ~w is not a constant', Name
else
true
else
sema_errorLoc, '~w is not a global', Name
7212place_constantfun_, idName, _, Env, Env, symSym, 0 ←
lookup_ordinaryEnv, Name, fn_, Sym, _
!
7214place_constantobj_, strBytes, _, Env0, Env, symSym, 0 ←
!
new_literalBytes, Sym, Env0, Env
7216place_constantobj_, predefined_, _, Env0, Env, symSym, 0 ←
6.4.2.2p1
__func__ is a static array, so its address is a constant in the
initializer of a block-scope static (ell's L_DEBUG_SYMBOL).
lookup_ordinaryEnv0, enclosingfunction, Func
!
func_literalFunc, Sym, Env0, Env
7221place_constantobj_, predefinedName, Loc, _, _, _ ←
!
sema_errorLoc, '~w outside a function', Name
7223place_constantobj_, wstrWideType, Values, _, Env0, Env, symSym, 0 ←
!
new_literalwWideType, Values, Sym, Env0, Env
7225place_constantobj_, complitTypeName, Init, Loc, Env0, Env, symSym, 0 ←
!
type_nameTypeName, Type0, Env0, Loc
new_complitTypeName, Type0, Init, Loc, Sym, Env0, Env
7228place_constantobj_, derefXV, Loc, Env0, Env, R ←
!
address_constantXV, Loc, Env0, Env, R0
R0 = sym_, _
→
R = R0
else
sema_errorLoc, 'initializer is not an address constant', []
7233place_constantobj_, memberobjT, F, Field, Loc, Env0, Env, symSym, Off ←
!
place_constantobjT, F, Loc, Env0, Env, symSym, Off0
unqualifiedT, SUType
member_offSUType, Field, Env, Loc, _, MemberOff
Off is Off0 + MemberOff
7238place_constant_, Loc, _, _, _ ←
sema_errorLoc, 'initializer is not an address constant', []
member_off+SUType, +Field, +Env, +Loc, -Type, -Off

member Field of struct or union SUType has Type and offset Off.

7243member_offSUType, Field, Env, Loc, Type, Off ←
SUType = struct_
or
SUType = union_
member_type_offsetSUType, Field, Env, Type, Off
→
true
else
sema_errorLoc, 'no member named ~w', Field
runtime_offset+Path, +Type, +Env, +Ctx, +V0, -V//

V is V0 plus the offset of offsetof's member Path in Type, its indexes evaluated.

7252runtime_offset[], _, _, _, V, V ⟶
[]
7253runtime_offsetfieldF|Path, T, Env, Ctx, V0, V ⟶
ctx_locCtx, Loc
member_type_offsetT, F, Env, FT, Off
→
true
else
sema_errorLoc, 'bad offsetof', []
letl, add, V0, constOff, V1
runtime_offsetPath, FT, Env, Ctx, V1, V
7260runtime_offsetindexX|Path, T, Env, Ctx, V0, V ⟶
ctx_locCtx, Loc
T = arrayET, _
catchtype_size_alignET, Env, Size, _, bad_, fail
→
true
else
sema_errorLoc, 'bad offsetof', []
rvalX, Ctx, XV
assign_convertX, XV, long, Loc, vI, _
letl, mul, I, constSize, D
letl, add, V0, D, V1
runtime_offsetPath, ET, Env, Ctx, V1, V