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
| Value | Is |
|---|---|
vValue, Type | an rvalue: Value is an IR value, Type a C type |
lvAddr, Type | an object at Addr, a p value: an lvalue, or a member of a struct rvalue |
fdCallee, Type | a 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:
| Field | Is |
|---|---|
| Env | the c_types environment eOrds, Tags, Defs, Next |
| Cur | curLabel, Phis, the open block, or none after a jump; dead code then opens an unreachable block |
| Insts, Blocks | reversed instructions and blocks, temporarily paired with origin IDs |
| Allocs | buildRevAllocs, ActiveOrigins, OriginCounter, RevRecords, indexBindings, Hints, ActiveLoc; independent of executable names |
| N | the counter for fresh temporaries and labels |
| Labels | an assoc from C goto labels to IR labels |
| Cases | the 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
| Name | Is |
|---|---|
| State0, State | st/8 terms |
| Env0 ... Env | the environment |
| Ctx | the context |
| Loc | a source location |
| R | a v, lv or fd value term; Value0 is one before conversion |
| V | an IR value |
| L-prefixed names | IR labels |
| Ebs | eightbytes, ebOffset, Bytes, Class |
| S0 ... S | sEnv, Chunks, End in the static initializer predicates |
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.
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.
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.
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.
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.
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/2emits 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.
The identity a name stands for now, or none.
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.
A fresh identity for an alias a directive names New: one no symbol of the unit is, whatever names come later.
the final name of every identity, and of every merged declaration the name of what it now is.
S is the name module_asm_symbols/4 gave the symbol S0.
An item with its symbols renamed; a weak declaration of a symbol an alias replaced goes.
the symbols Term references, global(S) and sym(S, Off).
T0 with each symbol S, an item's own name or a reference global(S) or sym(S, Off), renamed as Final says.
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.
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.
an alias declares its name, never defines it, so an object alias is no tentative definition.
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.
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.
Weak is the ordset of the symbols of the names Items declare weak, each Sym-Kind, Kind fn or data.
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.
Items is Items0 with the weak symbols' definitions weak, and an extern(weak) declaration of each one Items0 refers to without defining it.
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.
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.
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.
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.)
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.
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.
some function or data item of Items refers to symbol Sym (nondet, with repeats).
Func is the deferred inline function Sym, lowered in the scope it was defined in.
Environment
Env is Env0 with Key defined as Value.
Id is Env0's next number, which Env has moved past.
Top level
Funcs are the IR functions a top-level item defines now (a static inline is deferred).
knr_definition/6calls 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.
X is an identifier the function body names.
the identifier X calls or names a function of symbol Sym: itself, through an asm label, or as a __builtin_X spelling of it.
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.
Specs carry GNU's constructor (Kind ctor) or destructor (dtor) attribute, with Priority an integer, none without one, or bad.
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.
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.
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).
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.
throw a semantic error unless Expr is a nonzero constant.
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.
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).
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.
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.
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.
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.
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.
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).
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.
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'.
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).
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).
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.
Sym is a fresh symbol that Env defines as a file-scope compound literal, an unnamed static object, read-only if TypeName is const.
Data are the data items of every global definition and string literal.
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.
Data is the data item of global definition G.
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.
Functions
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).
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).
(+ParamEnv, -Params, -State): the function's body lowered from ParamEnv.
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.
Class is the IR return class of a function returning RetType.
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.
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.
each parameter's minimum alignment, its typedef's (inherited_alignment/4) or 1, as clang lays it out.
Params are the IR parameters, and each named one is stored into a local. A struct or union comes in one word per eightbyte.
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.
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.
one fresh parameter per eightbyte; Values are their temporaries.
end the body; falling off the end returns as a bare return would.
return without a value (zero, if the function has a result).
State primitives
S is the state. state(-S0, +S)//: the state is S0 and becomes S. The semicontext of the one-element state list.
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.
A zero-offset field or partial access is not the whole C object.
Keep the operation and resolved binding, not recursive typed trees.
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.
append Inst with its active typed origin. These private wrappers disappear before c_module/2 returns; they are never IR.
end the current block with Jump.
begin block Label, falling through from a live current block.
begin block Label, reached only by jumps.
Label is the current block's label.
Label is the IR label of C label Name, made on first use.
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.
an object of qualified type T is loaded atomically if it is a pointer: _Atomic, or volatile as Fil-C has it.
Elem is the innermost element type of array Type, or Type itself.
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.
Context
, switch_ctx(+Ctx0, +LBreak, -Ctx): Ctx is Ctx0 inside a loop or switch whose break jumps to LBreak and continue to LCont.
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).
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.
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.
Declaration and loop helpers update the enclosing source context.
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.
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).
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.
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.
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.
, 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.
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.
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.
| 2287 | '=x' | float |
| 2288 | '+x' | float |
| 2289 | '=t' | float |
| 2290 | '+t' | float |
| 2291 | '=f' | float |
| 2292 | '+f' | float |
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.
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.
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.
Ties are N-V, input value V tied to output N.
W is W0 as a w operand: an 8-byte value's low 32 bits.
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.
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).
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.
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).
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.
a general-purpose register.
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.
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}).
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.
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".
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).
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).
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.
a jump to the break or continue Target, some(Label); none is semantic error Error at Loc.
, 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.
throw a semantic error unless Type is a type a local object Name can have.
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.
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.
V is a compound literal of Type in a static initializer, which GNU C accepts as its initializer list.
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.
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.
no two case values of a switch are equal once converted to the promoted controlling type Type (C11 6.8.4.2p3).
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.
compare Value with each case value of Pairs in turn, then jump to LDefault.
full expression Expr is typed, or c_types says why not; where it cannot say, lowering reports the fault.
the scope gets the structs and unions full expression Expr's type names define (c_types expression_tags/3).
W is a w value that is nonzero exactly when scalar Value is.
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 structOffsets 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).
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.
the leaves of Init for the object of Type at Off. A scalar may be braced.
Entries are undesignated integer or character constants, whose values converted to Type are Values.
a val leaf for each of Values, consecutive elements of Type and Size from Off.
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.
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.
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.
designator Desig of aggregate Type names its slot I, the SlotType at Off; any member of a union.
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.
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.
the expression or initializer E may have side effects: it contains an assignment, increment, call or statement expression.
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).
initialize the local object of Type at Addr from Init, storing its leaves.
clear the whole object, since allocs are hoisted and a local inside a loop must be reinitialized on every pass.
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.
Expressions
GNU statement expressions load/decay their last expression before the block scope closes. A label on that statement does not hide its value.
R is the object or function an identifier bound to Binding designates. A global's address is copied into a temporary.
P points at member Field, of Type, of the struct or union at Addr.
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.
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.
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.
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.
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.
bit_castable/3).va_cells/5).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).
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.
offsetof with a variable array index (GCC and clang accept offsetof(struct seccomp_data, args[x])) is computed at run time.
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.
R is the value of type U stored at Addr: a struct or union stands for its address.
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.
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.
A compound literal is an unnamed local, initialized where it appears.
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).
R is the value, of type U, of the object at place P.
R is the scalar value at place P.
as a struct's
store Value0, converted, at place P; R is the value stored.
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.
V is the scalar of Type loaded from Addr.
Class and load Op hold a scalar of Type: an integer narrower than 8 bytes is loaded extended into a w.
store value V of Type at Addr; a struct or union V is the address of the value, copied.
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.
__builtin_trap() stops the program, as clang's llvm.trap does; the IR's unreachable is a trap instruction.
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.
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.
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.
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.
__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.
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.
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.
__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.
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.
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.
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.
atomic_su_type/3x86 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.
__builtin_ia32_rdtsc, _rdtsc's builtin, is the rdtsc instruction (asm_fixed, as its inline asm is): edx:eax as one unsigned long long.
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).
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.
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.
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.
New, of U, is Old combined with V by Op.
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.
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.
Sum is Acc plus each Flag-E pair's flag (0 or 1) times E, E converted to int.
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.
R is sign_builtin/3's Op of Xs at type T.
each long double of Xs stored in a local slot, the address of its sign and exponent, and them.
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).
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.
Y is Y0 (none for nothing yet) with each byte I in Is of X moved to byte Last - I.
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.
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.
Size is sizeof(TypeName), a type of constant size.
Type is the type TypeName names in a constant initializer, where nothing is evaluated: a variably modified one is refused.
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).
throw a semantic error if nanofil does not support Type (c_types' unsupported_type/3).
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.
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.
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.
Size is the size in bytes of Type, an l value: a constant unless Type is a variable length array.
Scopes are the variably modified scopes that are open, innermost first.
vm_jumps//: no goto of the function enters a scope it is not in.
a jump from scopes From to scopes To enters the scope of a cleanup variable (cleanup_var//4).
a case or default label is in no variably modified scope its switch is not in.
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.
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;).
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).
call the cleanups of Ks, in order; none where control cannot reach.
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).
at the end of a scope entered with cleanup keys Outer, run the scope's own.
break or continue to each of Labels runs the cleanups declared since.
before break or continue to Target.
every cleanup in scope, before a return of the value at Addr0, copied first to Addr.
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.
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.
the blocks of the forward gotos to Name that leave cleanup scopes, each running those it leaves, before Label begins.
Conversions
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.
bitcast
Float-to-integer instructions produce a full w/l; normalize narrow C destinations afterward, without ever routing an unsigned result via signed.
V1 is integer V of type From converted to type To, extended where the representation changes.
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.
_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.
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.
Re and Im, v(X, F), are the parts of the complex value V of real type F.
R is a new complex value of type CT, its parts Re and Im converted to its real type.
complex V's parts, or a real V and none.
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.
the imaginary part of A Op B for add, sub, mul and div, one of Ai and Bi none for a real.
mul, div
Operators
R is unary operator Op applied to Value0, converted as unary_operand/3 says.
R is binary operator Op applied to the values VA and VB, converted as binary_operands/6 says.
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.
R is P + I or P - I, I scaled by the element size.
R is A - B in elements.
R is XA && XB (land) or XA || XB (lor), a phi of 0/1 over the short-circuit edges.
start block LJoin; R is the ?: result there, a phi of VA from LA and VB from LB.
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 is the IR callee of function pointer XF: the function itself when XF is a function designator decayed, else a temp holding the pointer.
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).
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.
R is the result of calling Callee with IRArgs; setjmp and its kin are intrinsics.
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.
atomic_su_type/3Ebs 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.
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.
V is call result V0 of Type, a narrow one re-extended: nothing guarantees the callee did.
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.
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.
Vs are Args' values, each converted to its parameter's type as for a call.
Ls are the elements, v(X, E), of vector value V read as a vector of type VT (the same bytes).
R is the vector of type VT whose elements are Ls, each converted to VT's element type.
argument expression A is an integer constant I, as an instruction's immediate operand must be.
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.
Name is a SSE4.2 string compare with lengths Len (e or i) whose result is Out (m, i or a flag ia ... iz).
Vs are 1 for each element of Ls within its length, else 0.
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.
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
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
HT is the 16-byte half of the 32-byte vector VT.
Hs are Vs with each 32-byte vector replaced by its half at Off; a 16-byte count vector or a scalar is kept.
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.
the result R of an atomic read-modify-write builtin on a scalar T at Addr (builtin//4's rmw_builtin clause).
R, an S, is read from the atomic object at Addr by one P-byte load into a P-byte copy.
W is the P-byte integer that the S value V is in an atomic object: V's bytes, then zeros.
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.
VEXTRACTI128 and VEXTRACTF128: imm8 bit 0 picks the half, which a vector value, a copy nothing writes, may share.
VINSERTI128 and VINSERTF128: W with the half imm8 bit 0 picks replaced.
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.
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.
filcc compiles VZEROUPPER and VZEROALL to its run-time "Unhandled intrinsic" error, as _mm_lfence's.
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.
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.
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.
lddqu is an unaligned 16-byte load, checked as _mm_loadu_si128's.
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).
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.
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.
CVTPD2PS: the two doubles rounded to float (to nearest, as the default MXCSR and a C conversion round), the upper two elements zero.
Name shifts each element, Kind shl, shr (logical) or sar, by an int count or the low quadword of a vector.
a count at or above the element width gives 0, or the sign, for sar.
Sums are A0*B0 + A1*B1 for each pair of elements, computed in T.
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.
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.
R has V's bytes, read as To.
a generic vector builtin of result type T.
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.
, 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.
Xs are the elements of the vector of type VT at Addr.
store Xs as the elements of the vector of type VT at Addr from element I.
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.
Ebs are the eightbytes of Type.
Offs0-Offs is the difference list of the offsets, from Base, where Type holds pointers.
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).
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).
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.
V is the ?: arm value V0 as the join's phi carries it: a long double through memory, since no phi has class x.
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).
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.
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.
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.
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.
Shape is how a struct or union of Type is returned: sret, or words(Eightbytes).
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.
Ebs are all the eightbytes of Type, classed as abi_eightbytes/4 does; Leaves are its scalars (leaves/5).
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.
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.
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 = ziinitwas 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.
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.
Words are the words of the value at Addr, one per eightbyte.
W is Acc0 (none at first) or'd with each of Pieces loaded from P and shifted into place.
store Words, one per eightbyte, into the object at Addr.
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.
Items are the items of Chunks, sorted by offset, with zero(N) filling the gaps from Pos to Size.
Chunk holds integer V of Type at Off.
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.
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.
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.
Chunks maps Chunk's offset to Chunk, replacing any earlier one there.
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.
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.
the bytes of the elements Vs of integer Type and Size, little-endian, as characters.
Chunk holds the constant value of Expr, of scalar Type, at Off.
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.
R is address constant R0 plus (add) or minus (sub) K elements of type Elem.
typed tree X designates a static object or a function at R = sym(Symbol, Offset).
member Field of struct or union SUType has Type and offset Off.
V is V0 plus the offset of offsetof's member Path in Type, its indexes evaluated.