c_types.pl
C's types as terms: specifiers and declarators become type terms, and from those come sizes and layouts, the integer conversions and the values of constant expressions (milestone 4a).
Types
| Type | Means |
|---|---|
void bool | as in C |
char schar uchar | char, signed char, unsigned char |
short int long llong | the signed integer types |
ushort uint ulong ullong | their unsigned counterparts |
float double ldouble | the floating types |
ptrT | a pointer to T |
arrayT, N | N elements of T, or arrayT, none of unknown length |
arrayT, vlaE | a variable length array whose size expression E is not yet evaluated; E is star for [*] |
arrayT, dynV | a variable length array whose length lowering evaluated into the IR value V |
funcRet, Params, Arity | Params a list of types, or unspecified; Arity fixed or variadic |
structId unionId | the struct or union Id of the environment's Defs |
badReason | a type the frontend cannot handle yet |
Parameter types are already adjusted: arrays and functions become pointers. A type with a vla or dyn length somewhere inside is variably modified (vm_type/1); lowering evaluates each vlaE where C evaluates it, which makes it dynV. Qualifiers are dropped, and an enum type is its underlying integer type. A badReason is an error only when a declaration uses it, so headers that mention such types still parse.
The environment
The semantic environment eOrds, Tags, Defs, Next has four parts:
| Part | From | To |
|---|---|---|
| Ords | an ordinary identifier | typedefT, enumconstV, T, or what lowering stores for a variable or function |
| Tags | a tag | tagKind, Id, Kind struct or union, or enumT |
| Defs | a struct or union Id | its definition, below |
| Next | the next Id |
A definition is one of:
| Definition | Means |
|---|---|
incompleteTag | declared, not yet defined; Tag is someName, or none when anonymous |
defKind, Members, Size, Align | complete: Members in order, and the layout |
partialReason, Members | only the members whose offsets are known: no usable layout |
badReason | a definition the frontend cannot handle yet |
and a member one of:
| Member | Means |
|---|---|
msomeField, Type, Offset | Field, of Type, Offset bytes in |
mnone, Type, Offset | an anonymous struct or union member |
msomeField, Type, Offset, bitsPos, Width | a bit-field: Width bits from bit Pos of the Type at Offset |
Lowering keeps entries of its own in Defs too. A block scope saves Ords and Tags and restores them when it ends; Defs and Next only grow. A predicate that changes the environment takes Env0 and gives Env as its last two arguments, so it threads through foldl/4,6.
Documentation is a relation of the module, doc(Head, Det, Props) (docs/dterm.md); tests/unit.pl runs every example.
Environment
Env has no user identifiers or tags, and the builtin va_list record.
| T | in | a type, perhaps qualified |
| F | out | its real type: float, double or ldouble |
_Complex F (6.2.5p11) is laid out, copied, passed and returned as a struct of two F members, the real and the imaginary part, whose names no C code can spell: clang compiles _Complex float and double to the code of struct { F re, im; }. It is not an arithmetic type here, since nanofil's arithmetic values sit in registers; its operators and conversions are the cases that name it.
| ✓ | complex_typestruct'builtin.complex.double', double | ||
| ✓ | complex_typequalifiedconst, struct'builtin.complex.float', float | ||
| ✓ | complex_typedouble, _ | ⟹ | fails |
| F | in/out | float, double or ldouble |
| T | in/out | _Complex F, the struct complex_type/2 names |
| ✓ | complex_offloat, struct'builtin.complex.float' | ||
| ✓ | complex_ofint, _ | ⟹ | fails |
Value is what the ordinary identifier Name stands for in Env.
Env is Env0 with the ordinary identifier Name standing for Value, replacing any binding it had.
Scope is the scoped part of Env (its ordinary identifiers and tags), for env_restore/3. The third argument is unused.
Env is Env0 with its scoped part replaced by Scope: the identifiers and tags of an inner scope are gone, its definitions stay.
Throw nanofil_error with the message Fmt/Args, prefixed with Loc. Never succeeds.
Declarations
Decls are d(Name, Type, Storage, Asm, Init) for the declarators Inits under the specifiers Specs, Storage as decl_storage/2 gives it. A typedef declares no Decls; Env is Env0 with its names bound instead. Env also has any struct, union or enum Specs defines, and each object's alignment request (object_alignment/5). Throws not_constant when an enumerator's value is not constant.
| Specs | in | a declaration's specifiers |
| Storage | out | none, typedef, static, extern, auto or register, or thread_local(S) with S one of none, static or extern |
Storage is the declaration's storage-class specifier. _Thread_local (also spelled thread_local and GNU __thread) may appear with static or extern, and then gives the object thread storage duration instead of static: one instance per thread, created and initialized when the thread first uses it (Fil-C creates it lazily, as clang's getter does). Where it may appear is the lowering's to check.
| ✓ | decl_storagestoragestatic, tspecint, S | ⟹ | S = static |
| ✓ | decl_storagetspecint, S | ⟹ | S = none |
| ✓ | decl_storagestorage(thread_local), storagestatic, tspecint, S | ⟹ | S = thread_localstatic |
| ✓ | decl_storagestorageextern, storage(thread_local), tspecint, S | ⟹ | S = thread_localextern |
| ✓ | decl_storagestorage(thread_local), tspecint, S | ⟹ | S = thread_localnone |
Decl is the d/5 term Init declares, or none for a typedef, which binds the name. An object's alignment beyond its type's (object_alignment/5, from the alignment Requests and its typedef's) is bound to qalign(Name), none when it has none and an outer declaration did. A typedef's alignment (typedef_alignment/5) is bound to talign(Name).
expr_alignment/3, unscoped| Requests | in | the _Alignas specifiers and GNU aligned attributes of an object's declaration |
| Type0 | in | the declared type |
| Env | in | the scope |
| Type | out | Type0, or bad(Reason) for a request nanofil cannot honor |
| A | out | the alignment the object needs beyond its type's, or none |
An alignment specifier or aligned attribute on an object declaration aligns that object, not its type: the strictest request, or the type's own alignment if stricter, is the object's. _Alignas(0) and a request no stricter than the type's change nothing; aligned with no argument is 16, x86-64's largest alignment (__BIGGEST_ALIGNMENT__). Lowering honors up to 4096 bytes, the most an alloc or a data item aligns to (above 16 as Fil-C's filc_allocate_with_alignment and its padded data objects do); a stricter request, or one on a function, is refused (bad). The type, and so sizeof and _Alignof of it, is unchanged.
| ✓ | object_alignmentattrgroupidaligned, groupint8, none−dec, arraychar, 4, E, T, A | ⟹ | T = arraychar, 4, A = 8 |
| ✓ | object_alignmentalignasexprnum2, none−dec, int, E, T, A | ⟹ | T = int, A = none |
| ✓ | object_alignmentalignastypetypetspeclong, base, char, E, T, A | ⟹ | T = char, A = 8 |
| ✓ | object_alignmentattrgroupid'__aligned__', char, E, _, A | ⟹ | A = 16 |
| ✓ | object_alignmentalignasexprnum64, none−dec, int, E, T, A | ⟹ | T = int, A = 64 |
| ✓ | object_alignmentalignasexprnum8192, none−dec, int, E, T, _ | ⟹ | T = bad'an alignment above 4096' |
| ✓ | object_alignmentalignasexprnum16, none−dec, bad'vector types', E, T, _ | ⟹ | T = bad'vector types' |
keep the first reason
| Name | in | a declared object |
| Reflected | in | its reflected type, with its qualifiers |
| Type | in | its type |
| Storage | in | its storage class |
| Init | in | its initializer, or none |
| Env0 | in | the scope it is declared in |
| Env | out | Env0 with cval(Name) bound to its value c(V) if it is a constant object, else to none if an outer one was |
An object of const, not volatile, arithmetic type whose definition has an initializer that folds is a constant in constant expressions where its definition is visible, as clang and GCC fold it (6.6p10 lets an implementation accept other forms): in initializers, case labels, enumerators, static assertions and array sizes, which so declare no variable length array. Any other declaration of the name hides it, except an extern one without an initializer, which declares the same object. A parameter's reflected type has no top-level qualifiers, so a parameter is never read as one.
| ✓ | constant_object'K', qualifiedconst, int, int, none, num300, none−dec, E0, E lookup_ordinaryE, cval'K', C | ⟹ | C = c300 |
| ✓ | constant_object'K', qualifiedconst, char, char, static, num300, none−dec, E0, E lookup_ordinaryE, cval'K', C | ⟹ | C = c44 |
| ✓ | constant_object'K', qualifiedconst, (volatile), int, int, none, num7, none−dec, E0, E ¬lookup_ordinaryE, cval'K', _ | ||
| ✓ | bind_ordinarycval'K', c7, E0, E1 constant_object'K', int, int, none, none, E1, E lookup_ordinaryE, cval'K', C | ⟹ | C = none |
the strictest alignment nanofil gives an object.
| Requests | in | the aligned attributes of a typedef declaration |
| Inherited | in | [align(I)] if it names a typedef of alignment I (inherited_alignment/4), else [] |
| Type | in | the type it declares |
| Env | in | the scope |
| A | out | the typedef's alignment, none for its type's own, or bad(Reason) |
GNU aligned on a typedef sets the alignment of the typedef, not of its type, and may lower it as well as raise it; sizeof is unchanged (clang gives typedef struct { long a[3]; } B __attribute__((aligned)) size 24, alignment 16). Without one, a typedef of a typedef keeps its alignment. _Alignas may not be applied to a typedef, and an alignment above 4096 or one that is not a constant power of two is refused.
| ✓ | typedef_alignmentattrgroupid'__aligned__', [], long, E, A | ⟹ | A = 16 |
| ✓ | typedef_alignmentattrgroupidaligned, groupint1, none−dec, [], int, E, A | ⟹ | A = 1 |
| ✓ | typedef_alignment[], align64, int, E, A | ⟹ | A = 64 |
| ✓ | typedef_alignment[], [], int, E, A | ⟹ | A = none |
| ✓ | typedef_alignmentattrgroupidaligned, groupint8192, none−dec, [], int, E, A | ⟹ | A = bad'an alignment above 4096' |
| Specs | in | a declaration's specifiers |
| SyntaxType | in | its declarator's type syntax |
| Env | in | the scope |
| Inherited | out | [align(A)] if Specs name a typedef of alignment A (typedef_alignment/5) and the declarator declares an object or array of it, else [] |
| ✓ | c_types:bind_ordinarytalign'B', 64, E0, E inherited_alignmenttname'B', arraybase, none, E, I | ⟹ | I = align64 |
| ✓ | ⟹ | I = [] |
the constant alignment Request asks for, a power of two or 0 (none).
| TypeName | in | a type name, type(Specs, Syntax) |
| Type | in | the unqualified type code generation gives it |
| Env | in | the scope |
| Reflected | out | Type with its qualifiers kept, qualified(Qs, T) at each level that has any, and enum(K) for an enum |
Reflection is how the frontend knows const, volatile and _Atomic without putting qualifiers into the types code generation sees.
Keep qualification for type reflection without changing the unqualified code-generation types. These entries share ordinary identifier scope, including shadowing and typedefs. Lvalue conversion removes only the outer qualification, never the qualification of a pointer's target. reflect_declarator/5 is deterministic; a choicepoint left by each declaration would keep its terms alive to the end of the file.
an enum type's identity in reflected types, enum(K) among its qualifiers (composite_type/3): its tag, or for an anonymous one its first constant, which no other enum in scope can declare.
| 491 | someName | _ | Name |
| 492 | none | enumeratorFirst, __ | anonFirst |
| 493 | none | packedenumeratorFirst, __ | anonFirst |
when the specifiers name an _Atomic struct or union, Type = atomic(S, P) (atomic_su_type/3), in any spelling (_Atomic(T), or _Atomic on a tag, typedef or typeof name), the reflected Base keeps it under Base0's qualifiers.
| Declarator | in | a function definition's declarator |
| Types | in | its parameters' types |
| Env0 | in | the function's scope |
| Env | out | Env0 with each named parameter's reflected type (reflect_type/4) |
A declarator that is not a parameter list leaves Env0 as it is.
Specifiers
Type is the type the declaration specifiers Specs name, bad(Reason) if the frontend cannot handle it, as for an address_space attribute. Env is Env0 with the struct, union and enum definitions in Specs entered.
| Word | out | a GNU attribute's name, without GCC's optional underscores |
| Kind | out | hint, an attribute that changes nothing nanofil emits, or honored, one nanofil implements |
| Why | out | why: what it does, and why nanofil may ignore it or where it is honored |
nanofil accepts an attribute only if it is listed. One that changes layout, lifetime, linkage, calling or values must be honored (and its unsupported uses refused there), never dropped: cleanup and mode were once ignored and miscompiled. c_parse refuses any other attribute, "attribute X is not supported", where it reads it; ifunc, weakref, naked, ms_abi, scalar_storage_order and address_space are among those.
| ✓ | known_attributenonnull, hint, _ | ||
| ✓ | known_attributecleanup, honored, _ | ||
| ✓ | known_attributesection, honored, _ | ||
| ✓ | known_attributeifunc, _, _ | ⟹ | fails |
| M | out | an x86-64 mnemonic, lowercase, as an asm line's first word |
filcc's safe inline asm validator refuses M whatever its operands: parseMnemonic fails, and validateSafeInlineAsm's Reason is "unsupported mnemonic for safe inline asm: M". The list is what the pinned filcc answers for __asm__ volatile("M"), probed over the privileged, control-transfer, I/O, prefix and directive mnemonics packages use; a mnemonic not listed may pass or fail for another reason (ud2, pause and the fences pass; cpuid, rdtsc and xgetbv need their register operands declared), so nanofil refuses its asm at compile time rather than guess (c_lower asm_runtime_reason/6).
The port I/O forms with a size suffix are refused too (kbd's resizecons, glibc's sys/io.h, util-linux's lscpu-virt).
| ✓ | filcc_refused_mnemonicint | ||
| ✓ | filcc_refused_mnemonicsyscall | ||
| ✓ | filcc_refused_mnemonicoutb | ||
| ✓ | filcc_refused_mnemonicinl | ||
| ✓ | filcc_refused_mnemoniccpuid | ⟹ | fails |
| ✓ | filcc_refused_mnemonicud2 | ⟹ | fails |
the attribute is GCC's mode(Mode), its name without GCC's optional underscores.
| Type0 | in | the type the specifiers name |
| Mode | in | a machine mode, as mode_attr/2 reads it |
| Type | out | the type of that mode, bad(Reason) if nanofil has none |
GCC's mode attribute gives an integer or floating type the width of a machine mode, keeping its signedness: QImode is one byte, HImode two, SImode four and DImode eight; byte is QI, and word, unwind_word (libgcc's unwind.h declares _Unwind_Word so) and pointer are DI on x86-64; SF is float and DF double. As clang does, nanofil picks the first standard type of that width and signedness (long, not long long, for DI). gmp's longlong.h declares UQItype, USItype and UDItype so. TImode needs __int128, which nanofil refuses. Ignoring the attribute would keep int's four bytes.
| Tokens | in | the tokens of a GNU attribute |
The attribute is vector_size, which makes the declared type a vector of its specifiers' type (immintrin.h's __m128i). nanofil has no vector types: the type is refused (bad), an error only where it is used, so a header declaring vector typedefs and inline functions it never calls still compiles. Ignoring the attribute would give the vector the size of one element.
| ✓ | vector_attrgroupid'__vector_size__', groupint16, none−dec | ||
| ✓ | vector_attrgroupidaligned, groupint16, none−dec | ⟹ | fails |
| Specs | in | declaration specifiers with a vector_size attribute |
| Elem | in | the type the other specifiers name |
| Env | in | the scope |
| Type | out | vec(Elem, N, Align), or bad(Why) |
GCC's vector_size(Bytes) makes a vector of Bytes / sizeof(Elem) elements of an integer type other than _Bool, float or double. Its alignment is its size unless an aligned attribute in the same specifiers sets it, which may lower it. A typedef's aligned attribute (typedef_alignment/5) becomes its vector type's alignment, since a vector access is checked against the alignment of the type it is made through (immintrin.h's __m128i_u is aligned(1), so _mm_loadu_si128 checks alignment 1). Only 8-, 16- and 32-byte vectors are supported (the SSE through AVX2 intrinsics, and xmmintrin.h's 8-byte loads and stores); each access is checked as one access of its full width (docs/vector-access.md). They are on unless NANOFIL_VECTORS=0 (--no-vectors), which refuses every vector type.
| Tokens | in | an __attribute__((...))'s tokens, [group(Items)], or its Items |
| Word | out | the name of one of its attributes, as written |
| Rest | out | the tokens after that name in its comma-separated entry |
Only the entries' names count, never an identifier among their arguments: in __attribute__((cleanup(weak))) weak names a function, not the weak attribute. Consumers look attributes up through this.
| ✓ | attr_entrygroupidweak, p(','), idalias, groupstr116, alias, R | ⟹ | R = groupstr116 |
| ✓ | attr_entryid'__aligned__', groupint8, none−dec, W, _ | ⟹ | W = '__aligned__' |
| ✓ | attr_entrygroupidcleanup, groupidweak, weak, _ | ⟹ | fails |
| Tokens | in | the tokens of a GNU __attribute__((...)) list |
| Word | out | the name of one of its attributes (attr_entry/3) |
| ✓ | findallW, attr_wordidaligned, p(','), idweak, W, Ws | ⟹ | Ws ≡ aligned, weak |
Type from the type specifiers alone: either keywords or one named type, never both.
Clang retains the SysV va_list array/record layout; Fil-C stores its capability-bearing cursor in the first word and ignores the rest.
clang predefines __int128_t and __uint128_t as __int128 and unsigned __int128, which nanofil refuses where they are used (keyword_combo/2): glibc's bits/link.h pads its audit structs with them.
c_parse resolves a declaration's __auto_type from its initializer; one left over (a parameter, member or type name) has none.
| T | in | the unqualified type an _Atomic specifier or qualifier applies to |
_Atomic(T) and _Atomic T are the atomic version of T. nanofil takes an integer, a pointer, float or double: on x86-64 each has the size and alignment of T, a power of two of at most 8 bytes, which clang keeps for its atomic version, so the layout is T's. An array or function type is not allowed; a struct, union or long double, whose atomic version clang may pad, realign or make a library call, is refused (bad). Lvalue conversion of an atomic object is an atomic load and simple assignment an atomic store, both sequentially consistent; a compound assignment or increment is an atomic read-modify-write, a cas loop in lowering. Initialization is not atomic. An _Atomic bit-field, which C leaves to the implementation and clang rejects, is refused (bad).
| ✓ | atomic_object_typeint | ||
| ✓ | atomic_object_typeptrvoid | ||
| ✓ | atomic_object_typedouble | ||
| ✓ | atomic_object_typeldouble | ⟹ | fails |
| ✓ | atomic_object_typestruct1 | ⟹ | fails |
| ✓ | atomic_object_typearrayint, 2 | ⟹ | fails |
| S | in | a struct or union type |
| Env | in | the scope S is complete in |
| T | out | atomic(S, P), its atomic version |
_Atomic of a struct or union is a type of its own (6.2.5p27): clang rounds a size of at most 8 bytes up to a power of two P and aligns it to P, so its atomic loads and stores are one P-byte access (a 3-byte struct is 4 bytes, aligned 4; an 8-byte one aligned 4 is aligned 8); a store writes the padding as zero. nanofil takes one whose members hold no pointer or vector; a larger one, which clang lowers to libatomic calls, is refused. Lvalue conversion gives S (6.3.2.1p2); a compare-exchange needs S's size to be P already: for a smaller S, clang leaves *expected unchanged when it fails, against 7.17.7.4p2 (its padded copy of *expected is never copied back), so it is refused. a member of an atomic struct or union is not accessed (clang refuses it, -Watomic-access).
T is or holds a pointer or GNU vector.
| T | in | a reflected type (reflect_type/4) |
T is _Atomic at its top level.
| ✓ | atomic_qualifiedqualifiedatomic, int | ||
| ✓ | atomic_qualifiedint | ⟹ | fails |
| Arg | in | a typeof specifier's argument: expr(E) or type(TypeName) |
| Type | out | the unqualified type it names, as code generation has it |
| Env0 | in | the scope it is read in |
| Env | out | Env0 with any struct, union or enum TypeName defines |
The type of expression E, which is not evaluated, or of TypeName (GNU typeof, C23 typeof). An array or function keeps its type: no conversion applies. Reflection (reflect_type/4) keeps its qualifiers. A variably modified type would need E evaluated for its size, and is refused.
| ✓ | ⟹ | T = arrayint, 4 | |
| ✓ | typeof_typeexprbinadd, num1, none−dec, chr65, T, E0, _ | ⟹ | T = int |
the type a list of type keywords names.
implicit int, as in old headers' K&R style
the msorted keywords name Type.
GNU
Structs and unions
Members is none for a reference to the tag, which declares it if it is not yet visible, and otherwise the list of member declarations that define it. Tag is some(Name), or none for an anonymous struct or union; a reference always has a tag.
Type is the struct or union Members define, laid out in Env.
| E | in | a full expression |
| Env0 | in | the scope it is in |
| Env | out | Env0 with the structs and unions E's type names define |
A type name in an expression may define a struct or union, as krb5's load_16_le reads ((const struct { uint16_t i; } __attribute__((packed)) *)p)->i; the tag is in scope from there to the end of the block (C11 6.2.1p4, 6.7.2.3p6). Elaboration is pure, so the definitions are made first, in Env, and each is recorded against its syntax: elaborating the same type name again finds the same type.
TNs-Rest are the type names in E, in order, whose specifiers define a struct or union; statement expressions declare in their own blocks.
| Attrs | in | the attributes of a struct or union definition |
| Env | in | the scope it is defined in |
| Align | out | the alignment its aligned attributes request, 1 if none |
GNU's aligned(N) on a struct or union definition makes its alignment the larger of N and its members' (packed or not); bare aligned means the target's biggest alignment, 16 on x86-64 (clang's __BIGGEST_ALIGNMENT__). N must be a constant power of two. Fails for an alignment above 4096, which nanofil does not give objects (max_alignment/1), or a non-constant N.
| ✓ | definition_alignmentgroupidpacked, p(','), idaligned, groupint4, none−dec, E, A | ⟹ | A = 4 |
| ✓ | definition_alignmentgroupid'__aligned__', E, A | ⟹ | A = 16 |
| ✓ | definition_alignment[], E, A | ⟹ | A = 1 |
| ✓ | definition_alignmentgroupidaligned, groupint8192, none−dec, E, _ | ⟹ | fails |
| ✓ | definition_alignmentgroupidaligned, groupint12, none−dec, E, _ | ⟹ | fails |
Request is bare, or arg(Ts) with the argument tokens Ts, for each aligned attribute of an attribute's Tokens (attr_entry/3).
Def is the definition Def0 with its alignment at least Align and its size rounded up to that.
| From | in | the type of __builtin_bit_cast's operand, after conversion |
| To | in | the type it names |
| Env | in | the scope |
Both are complete object types of one size, and To is not an array (clang's rule; C has no __builtin_bit_cast). The result has the operand's bytes, as memcpy into a To would give it (C11 7.24.2.1): a pointer's bytes keep its address and leave its capability, and a pointer made from bytes has none, as in Fil-C's clang.
| ✓ | bit_castablefloat, uint, E | ||
| ✓ | bit_castableint, long, E | ⟹ | fails |
| ✓ | bit_castablelong, arraychar, 8, E | ⟹ | fails |
A later unsupported member (notably glibc FILE's _flags2 bitfield) does not change preceding offsets. Never infer offsets beyond it, or a size/alignment for the whole object; those uses must still reject.
Tag names the Kind with Id, which is declared but not yet defined. An anonymous one never is.
| Decl | in | a field(Specs, Dcl, Width) of c_parse, or a static_assert |
| Fields | out | the f(Name, Type) or b(Name, Type, Width) it declares, [] for a static assertion, or [bad(Why)] |
| Reflected | out | the q(Name, QType) of a named member, QType keeping the qualifiers Type drops |
| Env0 | in | the scope the struct or union is defined in |
| Env | out | Env0 with what the specifiers define and the base type kept |
Name is some(Field), or none for an anonymous member or an unnamed bit-field. The declarators of one member declaration share its specifiers' type: c_parse marks each after the first shared_base, and it takes the base type the previous field kept as enclosing(member_base), so struct { ... } a, b; declares one struct and a = b assigns.
| ✓ | S = sustruct, none, fieldtspecint, dclsomek, base, none, none, [] member_declfieldS, dclsomea, base, none, none, f_, TA, _, E0, E1 member_declfieldshared_base, S, dclsomeb, base, none, none, f_, TB, _, E1, _ | ⟹ | TA ≡ TB |
| ✓ | S = sustruct, none, fieldtspecint, dclsomek, base, none, none, [] member_declfieldS, dclsomea, base, none, none, f_, TA, _, E0, E1 member_declfieldS, dclsomeb, base, none, none, f_, TB, _, E1, _ | ⟹ | TA ≢ TB |
| Requests | in | a member's _Alignas specifiers and aligned attributes |
| Inherited | in | [align(I)] if its type is a typedef of alignment I (inherited_alignment/4), else [] |
| Env | in | the scope it is declared in |
| MA | out | none, a(Base, Explicit) or bad(Reason) |
The member's alignment is Explicit, the strictest request (1 if none), or Base if that is stricter: its typedef's alignment, or natural, its type's own. _Alignas and aligned cannot weaken a member (6.7.5p4), but a typedef's alignment can (GCC's aligned typedefs). In a packed struct only Explicit counts. Above 4096, or with a request that is not a constant power of two, the member is refused (bad).
| ✓ | member_alignment[], [], E, MA | ⟹ | MA = none |
| ✓ | member_alignmentattrgroupidaligned, groupint32, none−dec, [], E, MA | ⟹ | MA = anatural, 32 |
| ✓ | member_alignment[], align1, E, MA | ⟹ | MA = a1, 1 |
| ✓ | member_alignmentalignasexprnum8192, none−dec, [], E, MA | ⟹ | MA = bad'an alignment above 4096' |
Packed is yes when a member's attributes include packed, which GCC gives the member the least alignment, 1, unless aligned asks for more (Common Variable Attributes, packed): its alignment base is 1 (member_alignment/4). Specs are Specs0 without the packed items.
| Specs0 | in | a declaration's specifiers and attributes |
| Requests | out | its alignment requests: each _Alignas, and each aligned attribute as attr([group(Item)]) |
| Specs | out | Specs0 without them |
One __attribute__((...)) may list several attributes, separated by commas, each applying as if alone: GLib's resources declare their data with section(...), aligned(...) together. An aligned item is a request and the group's other items stay with the specifiers, so the section still places the object.
| ✓ | alignment_requestsattrgroupidsection, groupstr46, 114, p(','), idaligned, groupint8, none−dec, tspecint, R, S | ⟹ | R = attrgroupidaligned, groupint8, none−dec, S = attrgroupidsection, groupstr46, 114, tspecint |
| ✓ | alignment_requestsattrgroupid'__aligned__', alignasexprnum4, none−dec, R, S | ⟹ | R = attrgroupid'__aligned__', alignasexprnum4, none−dec, S = [] |
| ✓ | alignment_requestsattrgroupidpacked, tspecchar, R, S | ⟹ | R = [], S = attrgroupidpacked, tspecchar |
Tokens are the Items joined by commas.
E is the constant expression the attribute argument tokens Flat are, read with the typedef names of Env that they use.
attribute Tokens, groups parenthesized again, as c_lex tokens.
a bit-field Name of Type may have the width Expr, of value W: an integer constant no wider than Type, and 0 only without a name (6.7.2.1p4).
| Kind | in | struct or union |
| Packing | in | natural, packed for GNU's packed attribute, or pack(N) under #pragma pack(N) |
| Fields | in | the f(Name, Type) and bit-fields b(Name, Type, Width) in order |
| Env | in | the scope defining the members' types |
| Def | out | the definition: def(Kind, Members, Size, Align), or bad(Why) |
The SysV rules: each member at the next multiple of its alignment; size rounded up to the alignment. A trailing flexible array member has size 0. A bit-field follows the bits before it unless it would cross a boundary of its type's size, where it starts instead; a zero-width one only moves to that boundary. Unnamed bit-fields do not align the struct. Packed, every member has alignment 1, and so has the struct, and a bit-field follows the bits before it across any boundary (a zero-width one still moves to one), as GCC and clang lay it out; a packed bit-field that no access of its type within the struct covers is accessed in the narrowest integer of its sign that does, as clang's narrower access, when its type is no wider than int, else refused. A packed pointer is laid out as any member: Fil-C keeps a capability only at an 8-aligned address, and each access of a pointer checks at run time that its address is 8-aligned, as filcc's does ("alignment requirement of 8 bytes not met"), so libmbim's and elfutils' packed structs work where they are placed at an 8-aligned address and parted's GPT header, whose pointer is at 92, traps where it is accessed. Under #pragma pack(N) (c_pp marks the definition) each member's alignment is at most N, as GCC's and clang's, a pointer's too; a bit-field or an explicitly aligned member that N would underalign is refused. C11 leaves the alignment of members implementation-defined.
| ✓ | ⟹ | D = defstruct, msomea, char, 0, msomeb, int, 4, 8, 4 | |
| ✓ | ⟹ | D = defstruct, msomea, char, 0, msomeb, int, 1, 5, 1 | |
| ✓ | ⟹ | D = defunion, msomea, char, 0, msomeb, long, 0, 8, 1 | |
| ✓ | ⟹ | D = defstruct, msomec, char, 0, msomep, ptrint, 1, 9, 1 | |
| ✓ | ⟹ | D = defstruct, msomep, ptrint, 0, msomec, char, 8, 16, 8 | |
| ✓ | ⟹ | D = defstruct, msomec, char, 0, msomep, ptrint, 1, 16, 8 | |
| ✓ | ⟹ | D = defstruct, msomea, char, 0, msomex, int, 1, bits0, 30, msomeb, char, 5, 6, 1 | |
| ✓ | ⟹ | D = defstruct, msomea, char, 0, msomeb, char, 4, 5, 1 | |
| ✓ | ⟹ | D = defstruct, msomea, char, 0, msomeb, int, 2, 6, 2 | |
| ✓ | ⟹ | D = defstruct, msomea, char, 0, msomep, ptrint, 8, 16, 8 | |
| ✓ | ⟹ | D = defstruct, msomec, char, 0, msomep, ptrint, 4, 12, 4 | |
| ✓ | ⟹ | D = defstruct, msomex, uchar, 0, bits0, 6, msomef, uchar, 0, bits6, 2, 1, 1 | |
| ✓ | ⟹ | D = defstruct, msomex, schar, 0, bits0, 3, 1, 1 | |
| ✓ | ⟹ | D = bad'a packed bit-field that no access of its type covers' |
su_layout/5 with the definition aligned to at least Request (its aligned attribute).
Layout is placed(Members, End, Align) for Fields laid out in Env, or incomplete when a field has no size. Throws bad(Why) as type_size_align/4.
Members, the member Field declares or none for an unnamed bit-field, are placed after members ending at bit End0 with alignment Align0; End and Align include them. Throws bad(Why) as type_size_align/4. Any type but a flexible array (array(_, none)) has its own size, so the if-then-else keeps that default case.
A packed bit-field follows the bits before it, crossing any boundary; a zero-width one still moves to its type's boundary (clang's layout). Its unit, placed by packed_unit/4 once the size is known, is a window of its type's size within the struct.
Member is Member0, a packed bit-field's packed_bits(Name, Type, Start, W) placed in the unit of Type's size that starts at the latest byte at or before bit Start and ends within the struct's End bytes, as an access to it must. Where none does (parted's one-byte FatClusterInfo of unsigned int:6 and an enum :2), the unit is the narrowest integer of Type's sign that does, as clang accesses the field in a narrower integer: for a type no wider than int, the field's values and their promotion to int are the same. Else it is bad.
the Size bytes at Off, the latest that start at or before bit Start and end within End bytes, hold the W bits from bit Pos.
| T | in | a struct or union type |
| Env | in | the scope defining it |
T has a member, perhaps nested, at an offset that is not a multiple of its type's alignment, as only a packed struct can. The SysV ABI then classifies T as MEMORY: passed and returned in memory.
| ✓ | su_definitionfieldtspecchar, dclsomea, base, none, none, fieldtspecint, dclsomeb, base, none, none, struct, none, groupidpacked, T, E0, E unaligned_typeT, E | ||
| ✓ | su_definitionfieldtspecint, dclsomeb, base, none, none, fieldtspecchar, dclsomea, base, none, none, struct, none, groupidpacked, T, E0, E unaligned_typeT, E | ⟹ | fails |
| ✓ | unaligned_typestruct'builtin.va_list', E | ⟹ | fails |
| X | in | an object tree |
| Env | in | the scope of X |
| Width | in | the byte width of an atomic access to X |
| Name | out | the member, perhaps containing X, that may be misaligned |
X is a member, perhaps nested, of a struct or union aligned to less than Width, or at an offset that is not a multiple of it, as only a packed one can be: clang then lowers an atomic read-modify-write of X to libatomic's lock-based calls, which nanofil does not have, so lowering refuses it rather than trap on Fil-C's alignment check.
| ✓ | su_definitionfieldtspecchar, dclsomec, base, none, none, fieldtspecint, dclsomex, base, none, none, struct, none, groupidpacked, T, E0, E underaligned_memberobjint, memberobjT, idps, x, E, 4, N | ⟹ | N = x |
| ✓ | su_definitionfieldtspecchar, dclsomec, base, none, none, fieldtspecint, dclsomex, base, none, none, struct, none, [], T, E0, E underaligned_memberobjint, memberobjT, ids, x, E, 4, _ | ⟹ | fails |
| X | in | an object tree that is a member, perhaps nested |
| Env | in | the scope of X |
| A | out | the alignment an access to X is known to have |
A member's alignment is its struct or union's, or its own containing member's, at the member's offset: the largest power of two dividing the offset, at most that base alignment. A packed struct's members are known to be aligned to 1 only. clang gives an access this alignment, and filcc checks a vector access against it (capped at 8; docs/vector-access.md), so _mm_loadu_si128's member of its packed struct is checked at alignment 1.
| ✓ | su_definitionfieldtspecchar, dclsomec, base, none, none, fieldtspeclong, dclsomex, base, none, none, struct, none, groupidpacked, T, E0, E member_alignmentobjlong, memberobjT, idps, x, E, A | ⟹ | A = 1 |
| ✓ | su_definitionfieldtspecint, dclsomec, base, none, none, fieldtspecint, dclsomex, base, none, none, struct, none, [], T, E0, E member_alignmentobjint, memberobjT, ids, x, E, A | ⟹ | A = 4 |
a member of Size and Align after members ending at bit End0 is at byte Off and ends the struct or union so far at bit End.
a bit-field of W bits, in units of UnitBits, after bits ending at End0 occupies bits Start to End.
Enums
Type is the enum's underlying type. Enumerators is none for a reference to Tag, and otherwise the list that defines it; a reference to an unknown enum tag has type uint. Tag is some(Name) or none, as in su_type/7.
Type is the underlying type of the enum Enumerators define; Env binds its constants and its tag. enum_definition/6 with packed gives it the packed underlying type.
V is the constant's value, Next0 when it has no expression, and Env binds it. Each constant has type int when it fits, as C requires of constants whose values fit; the enum's own type follows clang.
Type is the type clang gives an enum whose constants have Values: the first of uint, int, ulong and long that holds them all (uint when there are none).
| Values | in | the values of an enum's constants |
| Type | out | its underlying type under GNU's packed attribute |
The smallest of uchar, ushort, uint and ulong that holds every value when none is negative, else of schar, short, int and long, as GCC and clang choose (uchar when there are none). C11 leaves the choice of an enumerated type's compatible integer type implementation- defined, as long as it can represent every member.
| ✓ | packed_enum_underlying0, 1, 2, T | ⟹ | T = uchar |
| ✓ | packed_enum_underlying0, 256, T | ⟹ | T = ushort |
| ✓ | packed_enum_underlying-1, 127, T | ⟹ | T = schar |
| ✓ | packed_enum_underlying-1, 128, T | ⟹ | T = short |
| ✓ | packed_enum_underlying[], T | ⟹ | T = uchar |
Declarators
| SyntaxType | in | a declarator's type, built by the parser outermost first around the atom base |
| Base | in | the type the declaration specifiers name |
| Type | out | the declared type |
| Env0 | in | the scope |
| Env | out | Env0 with the struct and union definitions of any parameter list |
Array sizes are evaluated and parameter types resolved. A size that is not an integer constant expression, or [*], makes a variable length array, whose size expression lowering evaluates; a negative constant size is bad. Parameter names are in scope for the size expressions of the parameters after them, and only there.
| ✓ | apply_declaratorarraybase, num3, none−dec, int, T, E, _ | ⟹ | T = arrayint, 3 |
| ✓ | apply_declaratorarraybase, idn, int, T, E, _ | ⟹ | T = arrayint, vlaidn |
| ✓ | apply_declaratorptrarraybase, star, [], int, T, E, _ | ⟹ | T = ptrarrayint, vlastar |
| ✓ | apply_declaratorarraybase, unneg, num1, none−dec, int, T, E, _ | ⟹ | T = bad'negative array size' |
| ✓ | bind_ordinaryn, enumconst4, int, E0, E apply_declaratorfuncbase, paramsparamtspecint, dclsomen, base, none, paramtspecint, dclsomea, ptrarraybase, idn, [], none, fixed, int, T, E, _ | ⟹ | T = funcint, int, ptrarrayint, vlaidn, fixed |
Types are the adjusted parameter types. Parameter declarations are scoped to the parameter list.
| Loc | in | the definition's location |
| Knr | in | knr(Names, Decls): a K&R definition's identifier list and the declarations of its parameters |
| Prototyped | in | true if a prototype of the function is in scope, else false |
| Env | in | the file scope |
| Params | out | params(Ps, fixed), the parameters of the equivalent prototyped definition |
| Locals | out | the declarations its body starts with |
Without a prototype in scope a caller passes each argument default- promoted, so a parameter whose declared type promotion changes (char, short, _Bool, float) comes in as the promoted type, under a name no source can spell, and the body starts with a local of the declared type initialized from it. clang gives the function the signature of the promoted types; with a prototype in scope it keeps the declared ones, as this does. A name with no declaration is int in GNU89 mode (NANOFIL_STD=gnu89) and refused otherwise with clang's message, as is a declaration of a name not in the list.
| ✓ | knr_definitionl, knra, p, decll, tspecchar, initdclsomea, base, none, none, decll, tspecchar, initdclsomep, ptrbase, [], none, none, false, E, Ps, Ls | ⟹ | Ps = paramsparamtspecint, dclsome'%knr_a', base, none, paramtspecchar, dclsomep, ptrbase, [], none, fixed, Ls = decll, tspecchar, initdclsomea, base, none, id'%knr_a' |
| ✓ | knr_definitionl, knrb, decll, tspecfloat, initdclsomeb, base, none, none, true, E, Ps, Ls | ⟹ | Ps = paramsparamtspecfloat, dclsomeb, base, none, fixed, Ls = [] |
| Type | in | a parameter's unqualified type |
| Env | in | the scope |
| Members | out | the types of its members, in order |
Type is a union with GNU's transparent_union attribute, on its definition or on a typedef of it, that clang keeps transparent: its first member is not floating, and every member has the first's size and alignment. clang otherwise warns and ignores the attribute, and so does nanofil. The parameter keeps the union type, which clang and filcc pass as its first member: a union of one eightbyte has the Fil-C signature of that member, pointer (p) or integer.
| Param | in | a parameter's unqualified type |
| T | in | the unqualified value type of an argument for it, not Param |
| Null | in | null if the argument is a null pointer constant, else value |
| Env | in | the scope |
| Member | out | the type of the member the argument initializes |
An argument for a transparent union parameter initializes the first member, in order, that takes it: a pointer member takes a void * or a null pointer constant, and any member a value of a compatible type, an arithmetic value for an arithmetic member, or a pointer to a compatible type or from or to void. The argument is converted to that member's type, never reinterpreted as the first member.
| Union | in | the union type a cast names |
| T | in | the unqualified value type of the cast's operand |
| Env | in | the scope |
| Name | out | the first named member of type T |
GNU C casts a value to a union one of whose members has its type: the result is the union with that member initialized, a compound literal (mailutils casts a function to its sieve_op_t this way). Without such a member the cast stays refused, as clang refuses it.
an argument of unqualified value type T (and nullness Null) may be passed for a parameter of unqualified type P: assigned to it, or to a transparent union's member.
| Param | in | a parameter's type |
| XV | in | an argument's value tree |
| Env | in | the scope |
| Member | out | the member type XV initializes, when Param is a transparent union and XV is not one |
As transparent_member/5, for the lowering of a call.
a typedef of a union with the transparent_union attribute marks the union so.
Type is the type the type name TypeName (as in a cast or sizeof) denotes; Env is Env0 with any tag it defines.
| Elem | in | the pointed-to type of a pointer in arithmetic |
| Env | in | the scope |
| Size | out | the bytes one step of the pointer moves |
The size of Elem, as type_size_align/4 gives it (and throws). GNU C also lets a pointer to void step by one byte, as clang does with a warning; dash's stackblock() + n relies on it. ISO C requires a complete object type.
| ✓ | arith_sizevoid, E, S | ⟹ | S = 1 |
| ✓ | arith_sizelong, E, S | ⟹ | S = 8 |
| T | in | a type |
T is variably modified: a variable length array type, or a type derived from one by pointers, arrays, or a function's return or parameter types.
| T | in | a type |
T is a variable length array type: an array whose size is not an integer constant expression, or whose element type has no constant size. A pointer to one is variably modified but has a constant size.
| T | in | a type an expression or declaration names |
| Format | out | the diagnostic's format |
| Args | out | its arguments |
nanofil does not support T: it has no values nanofil can hold.
| ✓ | unsupported_typebad'_Complex', '~w is not supported', '_Complex' | ||
| ✓ | unsupported_typeldouble, _, _ | ⟹ | fails |
| ✓ | unsupported_typeptrbad'_Complex', _, _ | ⟹ | fails |
| ✓ | unsupported_typearrayint, vlaidn, _, _ | ⟹ | fails |
nanofil has no values of Type, for the reason Why.
Sizes
Size and Align are the size and alignment in bytes of Type in Env. Throws bad(Why) for a bad or incomplete type.
the struct or union Id has Size and Align. Fails when it is incomplete; throws bad(Why) when it is bad.
| X | in | the elaborated operand of GNU __alignof__ applied to an expression |
| Env | in | the scope |
| A | out | its alignment in bytes |
A named object's alignment is its declaration's, with its _Alignas and aligned requests (qalign), as clang's GetAlignOfExpr takes the declaration's; any other operand's is its type's, an array's its element's. A member, whose alignment clang takes from the field's declaration (aligned, packed), is refused, as is a type type_size_align/4 refuses: both throw bad(Why). So is an operand whose type, or an array level of it, is that of some aligned typedef of the unit, unless its declaration's own request settles it: a type keeps no typedef, so *p of an A *p, A aligned(16), would read 4 where clang gives 16. The operand is not evaluated. glibc's string functions assert __alignof__ (table[0]).
| ✓ | expr_alignmentvallong, loadobjptrlong, idp, E, 8 | ||
| ✓ | expr_alignmentobjarrayshort, 4, ida, E, 2 | ||
| ✓ | |||
| ✓ | catchexpr_alignmentobjint, memberobjsustruct, s, ids, x, E, _, badW, true W ≡ '__alignof__ of a member' |
L is T or, for an array, one of its element types.
X is GNU __real__ or __imag__ (Part 'builtin.re' or 'builtin.im') of the elaborated XS: the member of a complex XS, an lvalue if XS is; and of a real XS, XS itself for __real__. (GCC's __imag__ of a real value, its zero, is refused.)
| Type | in | the type of sizeof's operand |
| Env | in | the scope |
| Size | out | the operand's size in bytes |
GNU C gives a function type and void the size 1 for sizeof, as for pointer arithmetic on void * and function pointers; clang accepts it with a -Wpointer-arith warning. curl's typecheck-gcc.h applies sizeof to a callback's function designator. The types stay incomplete everywhere else. Other types are as type_size_align/4, which throws bad(Why).
| ✓ | sizeof_sizefuncint, [], fixed, _, 1 | ||
| ✓ | sizeof_sizequalifiedconst, void, _, 1 |
Type is complete in Env: it has a size.
The struct or union type SU has a member Name of Type at Offset, looking into anonymous members. Fails if it has none, if Name is a bit-field, or if SU is not defined.
| SU | in | an unqualified struct or union type |
| Name | in | a member name |
| Env | in | the scope that defines SU |
| Type | out | the member's type |
| Off | out | the byte offset of the member, or of the Type that holds a bit-field |
| Bits | out | none, or bits(Pos, Width) for a bit-field of Width bits from bit Pos |
Looks into anonymous members. A bit-field follows the bits before it unless it would cross a boundary of its type's size; a member that is not a bit-field starts at the next byte its alignment allows.
| ✓ | specs_typesustruct, none, fieldtspecchar, dclsomec, base, none, none, fieldtspecint, dclsomea, base, none, num3, none−dec, fieldtspecint, dclsomeb, base, none, num30, none−dec, [], S, E0, E member_layoutS, a, E, T, Off, Bits | ⟹ | T = int, Off = 0, Bits = bits8, 3 |
| ✓ | specs_typesustruct, none, fieldtspecchar, dclsomec, base, none, none, fieldtspecint, dclsomea, base, none, num3, none−dec, fieldtspecint, dclsomeb, base, none, num30, none−dec, [], S, E0, E member_layoutS, b, E, T, Off, Bits | ⟹ | T = int, Off = 4, Bits = bits0, 30 |
| ✓ | specs_typesustruct, none, fieldtspecint, dclnone, base, none, num8, none−dec, fieldtspecchar, dclsomec, base, none, none, [], S, E0, E member_layoutS, c, E, T, Off, Bits type_size_alignS, E, Size, Align | ⟹ | T = char, Off = 1, Bits = none, Size = 2, Align = 1 |
| ✓ | specs_typesustruct, none, fieldtspecint, dclsomea, base, none, num3, none−dec, [], S, E0, E member_type_offsetS, a, E, _, _ | ⟹ | fails |
Integers
| Type | in | a type term |
Type is an integer type: bool, the character types, and the standard signed and unsigned integer types.
| Type | in/out | an integer type |
| Size | in/out | its width in bytes |
| Signedness | in/out | signed or unsigned |
| Rank | in/out | its integer conversion rank, from 0 for bool to 5 for long long |
C's integer types on x86-64 Linux. Plain char is signed, long is as wide as long long, and the three character types share a rank.
| ✓ | integer_typelong, S, G, R | ⟹ | S = 8, G = signed, R = 4 |
| ✓ | findallT, integer_typeT, 8, unsigned, _, Ts | ⟹ | Ts = ulong, ullong |
| 2587 | bool | 1 | unsigned | 0 |
| 2588 | char | 1 | signed | 1 |
| 2589 | schar | 1 | signed | 1 |
| 2590 | uchar | 1 | unsigned | 1 |
| 2591 | short | 2 | signed | 2 |
| 2592 | ushort | 2 | unsigned | 2 |
| 2593 | int | 4 | signed | 3 |
| 2594 | uint | 4 | unsigned | 3 |
| 2595 | long | 8 | signed | 4 |
| 2596 | ulong | 8 | unsigned | 4 |
| 2597 | llong | 8 | signed | 5 |
| 2598 | ullong | 8 | unsigned | 5 |
| Type | in/out | an integer type |
| Size | in/out | its width in bytes |
| Signedness | in/out | signed or unsigned |
integer_type/4 without the rank.
| ✓ | int_infouchar, S, G | ⟹ | S = 1, G = unsigned |
| Type | in/out | an integer type |
| Rank | in/out | its integer conversion rank |
| ✓ | rankschar, R | ⟹ | R = 1 |
| Signed | in | a signed integer type |
| Unsigned | out | the unsigned type of the same rank |
| ✓ | unsigned_ofllong, U | ⟹ | U = ullong |
| ✓ | unsigned_ofchar, U | ⟹ | U = uchar |
| ✓ | unsigned_ofuint, _ | ⟹ | fails |
| Type | in | a type term |
Type is an integer, floating-point or pointer type: one that has an IR class.
| ✓ | scalar_typeptrchar | ||
| ✓ | scalar_typestruct1 | ⟹ | fails |
| Type | in/out | a real floating type |
The real floating types. long double is the x87 80-bit extended format, as in clang for x86-64 (class x): sizeof 16, alignment 16, 64 significand bits, LDBL_MANT_DIG (docs/float.md).
| ✓ | float_typeldouble | ||
| ✓ | float_typeint | ⟹ | fails |
| Type | in | a type term |
Type is an integer or floating type.
| ✓ | arithmetic_typebool | ||
| ✓ | arithmetic_typeptrint | ⟹ | fails |
| Type | in | the type of an argument with no parameter type |
| Promoted | out | the type it is passed as |
The default argument promotions: float becomes double, and the integer promotions apply to the rest.
| ✓ | default_promotionfloat, P | ⟹ | P = double |
| ✓ | default_promotionchar, P | ⟹ | P = int |
| ✓ | default_promotionptrint, P | ⟹ | P = ptrint |
| Type | in | the type of an operand |
| Promoted | out | its type after the integer promotions |
int for an integer type of rank below int, which int can always represent here, otherwise Type itself, also for a type that is not an integer.
| ✓ | integer_promotionushort, P | ⟹ | P = int |
| ✓ | integer_promotionbool, P | ⟹ | P = int |
| ✓ | integer_promotionuint, P | ⟹ | P = uint |
| ✓ | integer_promotiondouble, P | ⟹ | P = double |
| TypeA | in | the type of one arithmetic operand |
| TypeB | in | the type of the other |
| Type | out | their common real type |
The usual arithmetic conversions. A floating operand wins; otherwise both are promoted, and when the signedness differs the unsigned type wins unless the signed one is wider. A signed type exactly as wide as a higher-ranked unsigned one gives way to its own unsigned type.
| ✓ | usual_arith_conversionint, double, T | ⟹ | T = double |
| ✓ | usual_arith_conversiondouble, ldouble, T | ⟹ | T = ldouble |
| ✓ | usual_arith_conversionldouble, float, T | ⟹ | T = ldouble |
| ✓ | usual_arith_conversionfloat, long, T | ⟹ | T = float |
| ✓ | usual_arith_conversionchar, short, T | ⟹ | T = int |
| ✓ | usual_arith_conversionint, uint, T | ⟹ | T = uint |
| ✓ | usual_arith_conversionuint, long, T | ⟹ | T = long |
| ✓ | usual_arith_conversionlong, ullong, T | ⟹ | T = ullong |
| ✓ | usual_arith_conversionllong, ulong, T | ⟹ | T = ullong |
| ✓ | usual_arith_conversionptrint, int, _ | ⟹ | fails |
| V | in | an integer |
| Type | in | an integer type |
| W | out | V converted to Type |
Conversion wraps modulo the width, as the standard requires for unsigned types and GCC and clang define for signed ones; bool is 0 or 1. Fails when Type is not an integer type. The cut commits to bool, which int_info/3 also describes as a byte.
| ✓ | convert_int300, uchar, W | ⟹ | W = 44 |
| ✓ | convert_int256, bool, W | ⟹ | W = 1 |
| ✓ | convert_int-1, uint, W | ⟹ | W = 4294967295 |
| ✓ | convert_int128, schar, W | ⟹ | W = -128 |
| ✓ | convert_int1, double, _ | ⟹ | fails |
| Signedness | in | signed or unsigned |
| U | in | a word, 0 =< U < M |
| M | in | the modulus of the word |
| W | out | the value of U read with Signedness |
| ✓ | signed_wordsigned, 255, 256, W | ⟹ | W = -1 |
| ✓ | signed_wordunsigned, 255, 256, W | ⟹ | W = 255 |
| Type | in/out | a scalar type |
| Class | in/out | its IR class |
w for an integer type of at most 4 bytes, l for an 8-byte one, p for a pointer, s and d for float and double, x for long double. Deterministic when Type is bound.
| ✓ | type_classushort, K | ⟹ | K = w |
| ✓ | type_classullong, K | ⟹ | K = l |
| ✓ | type_classptrvoid, K | ⟹ | K = p |
| ✓ | type_classfloat, K | ⟹ | K = s |
| ✓ | type_classldouble, K | ⟹ | K = x |
| ✓ | type_classstruct0, _ | ⟹ | fails |
Arithmetic constant expressions
A constant expression has a type and a value: an integer, IEEE bits for float/double, or ub where evaluating it is undefined (integer division by zero). Operators propagate ub, except from operands that && || and ?: do not evaluate (C11 6.6p3): 1 || 1/0 is 1, and 1/0 alone is not constant. Every operand must still be a constant expression.
The environment is a c_types environment, or pp for a #if expression, where every integer type acts as intmax_t or uintmax_t (6.10.1p4): a constant is a long or ulong, and so is the result of ! == && and the like. The preprocessor has replaced identifiers by 0.
Expr is an integer constant expression with Value of Type in Env. Throws not_constant when Expr is not one.
| Expr | in | an arithmetic constant expression |
| Env | in | the scope |
| Value | out | its value; for a floating Type, its unsigned IEEE bits, never a host float |
| Type | out | its type |
As const_value/4, but Type may be a floating type. Throws not_constant when Expr is not a constant expression.
| Chars | in | a floating constant's characters, as the lexer gives them |
| Bits | out | the IEEE bits of its value, correctly rounded (ieee.pl) |
| Type | out | float for an f or F suffix, ldouble for l or L, else double |
| ✓ | float_literal'1', '.', '5', f, B, T | ⟹ | B, T = 1069547520, float |
| Name | in/out | a GNU builtin that names a float constant |
| Type | in/out | float, double or ldouble |
| Bits | in/out | the IEEE bits of its value |
glibc's HUGE_VAL and INFINITY are builtin constant expressions, not libm calls: __builtin_huge_val and __builtin_inf with their f and l forms, and the NaNs of nan_builtin/3. The value bits stay exact, including in static storage.
| ✓ | float_builtin'__builtin_huge_valf', T, V | ⟹ | T, V = float, 2139095040 |
| Name | in/out | a GNU NaN builtin |
| Type | in/out | float, double or long double |
| Bits | in/out | the IEEE bits of its value (x87's 80 for long double) |
__builtin_nan(""), __builtin_nanf("") and __builtin_nanl(""), glibc's NAN, are constant expressions: the quiet NaN of Type with a zero payload, as nan("") is. Only the empty string is accepted; a payload string would name other bits.
| ✓ | nan_builtin'__builtin_nanf', T, V | ⟹ | T = float, V = 2143289344 |
| ✓ | nan_builtin'__builtin_inff', _, _ | ⟹ | fails |
| 2873 | '__builtin_nan' | double | 0x7ff8000000000000 |
| 2874 | '__builtin_nanf' | float | 0x7fc00000 |
| 2875 | '__builtin_nanl' | ldouble | 0x7fffc000000000000000 |
| V | in | a constant's value: an integer, float bits, or ub |
| From | in | its arithmetic type |
| To | in | an arithmetic type |
| W | in/out | V converted to To |
An undefined float-to-int conversion gives ub, like integer division by zero, and ub stays ub. W may be bound: a null pointer constant is asked for the value 0, and ub is then not 0.
| ✓ | const_convert300, int, uchar, W | ⟹ | W = 44 |
| ✓ | const_convert-1, int, int, W | ⟹ | W = -1 |
| ✓ | const_convert2, int, bool, W | ⟹ | W = 1 |
| ✓ | const_convert1092616192, float, int, W | ⟹ | W = 10 |
| ✓ | const_convert2139095040, float, int, W | ⟹ | W ≡ ub |
| ✓ | const_convertub, int, llong, W | ⟹ | W ≡ ub |
| ✓ | const_convertub, int, llong, 0 | ⟹ | fails |
Value is the value of the #if expression Expr; fails when Expr is not constant.
| XV | in | a value tree |
| Env | in | the scope, or pp in a #if expression |
| V | out | its value: an integer, or float bits |
XV is a constant expression of value V. Its types are the tree's: the conversions are those the elaborator chose, so this is arithmetic only. A node that is not a constant (a load, a call, an assignment, an address) does not fold. A comma operator folds, as an extension, only when both operands do: one with an effect on its left, as in the length of int[(c++, 5)], is not a constant, so that array has variable length and the effect happens.
Two addresses built from integer constants differ by a constant number of elements: ghostscript's offset_of, (char *)&((T *)0)->m - (char *)((T *)0), as clang folds it.
| ✓ | ⟹ | V = 5 | |
| ✓ | ⟹ | V = 5 | |
| ✓ | ⟹ | fails | |
| ✓ | ⟹ | fails | |
| ✓ | ⟹ | V = 4 | |
| ✓ | bind_ordinaryp, varnone, ptrint, E0, E foldedvallong, binsub, valptrint, loadobjptrint, idp, valptrint, castt, valint, int0, E, _ | ⟹ | fails |
6.4.4.1, 6.4.4.4
6.4.4.2
| XP | in | a pointer value tree |
| Env | in | the scope |
| A | out | the address it is, when it is built from an integer constant |
An integer constant cast to a pointer, moved by integer constants, and then a member or element of what it points to, is the address A, so a cast of it to an integer folds: offsetof's old spelling (size_t)&((T *)0)->m, and gperf's string pools. clang and GCC fold these in initializers and case labels (6.6p10 allows other forms of constant expression); nothing is dereferenced.
| ✓ | constant_addressvalptrchar, binadd, valptrchar, castt, valint, int0, valint, int4, E, A | ⟹ | A = 4 |
| ✓ | constant_addressvalptrint, addrobjint, derefvalptrint, binadd, valptrint, castt, valint, int16, valint, int2, E, A | ⟹ | A = 24 |
| ✓ | bind_ordinaryp, varnone, ptrint, E0, E constant_addressvalptrint, castt, objptrint, idp, E, _ | ⟹ | fails |
offsetof's member path.
a constant or operator result of type T0 has type T in Env: T0 itself, or in a #if expression intmax_t or uintmax_t (6.10.1p4). A #if expression has no floating constants (6.10.1p1), so a float type has none there.
V is ub if one of Values is, and otherwise as Goal gives it.
B is 1 if Goal holds, else 0.
the binary operator Op converts its operands as Kind (arith, shift, compare or logical) names.
| Kind | in | arith, shift, compare or logical, as binary_kind/2 names Op |
| Op | in | a binary operator |
| A | in | the left operand, Value-Type |
| B | in | the right operand, Value-Type |
| T | in | the type of the result |
| V | out | the value of the result, or ub |
Arithmetic converts both operands to T, a comparison both to their common type (binary_operands/6), and a shift only the left one, to T. A shift count that is negative or not below the width of T is undefined, so it is ub, as is a division by zero or a quotient that the signed type cannot represent. Other signed overflow wraps, as clang and GCC fold it (with a warning).
| ✓ | kind_binaryarith, sub, 1−uint, 2−int, uint, V | ⟹ | V = 4294967295 |
| ✓ | kind_binaryarith, div, 1−int, 0−int, int, V | ⟹ | V ≡ ub |
| ✓ | kind_binaryarith, div, -2147483648−int, -1−int, int, V | ⟹ | V ≡ ub |
| ✓ | kind_binaryarith, mod, -2147483648−int, -1−int, int, V | ⟹ | V ≡ ub |
| ✓ | kind_binaryarith, div, -2147483647−int, -1−int, int, V | ⟹ | V = 2147483647 |
| ✓ | kind_binaryarith, div, -2147483648−int, -1−long, long, V | ⟹ | V = 2147483648 |
| ✓ | kind_binaryarith, add, 2147483647−int, 1−int, int, V | ⟹ | V = -2147483648 |
| ✓ | kind_binaryshift, shl, 1−int, 30−int, int, V | ⟹ | V = 1073741824 |
| ✓ | kind_binaryshift, shl, 1−int, 32−int, int, V | ⟹ | V ≡ ub |
| ✓ | kind_binaryshift, shl, 1−long, 32−int, long, V | ⟹ | V = 4294967296 |
| ✓ | kind_binaryshift, shl, 1−int, 40−long, int, V | ⟹ | V ≡ ub |
| ✓ | kind_binaryshift, shr, 8−int, -1−int, int, V | ⟹ | V ≡ ub |
| ✓ | kind_binaryshift, shl, 1−int, 18446744073709551615−ullong, int, V | ⟹ | V ≡ ub |
| ✓ | kind_binarycompare, lt, -1−int, 0−uint, int, V | ⟹ | V = 0 |
| ✓ | kind_binarylogical, land, 0−int, ub−int, int, V | ⟹ | V = 0 |
Division by zero is undefined: R is ub.
Type is the type of an integer constant of value V: the first type in the C11 list for its suffix and radix that can represent it, or ullong when none can.
| 3270 | dec | none | int, long, llong |
| 3271 | dec | u | uint, ulong, ullong |
| 3272 | dec | l | long, llong |
| 3273 | dec | ul | ulong, ullong |
| 3274 | dec | ll | llong |
| 3275 | dec | ull | ullong |
| 3276 | other | none | int, uint, long, ulong, llong, ullong |
| 3277 | other | u | uint, ulong, ullong |
| 3278 | other | l | long, ulong, llong, ullong |
| 3279 | other | ul | ulong, ullong |
| 3280 | other | ll | llong, ullong |
| 3281 | other | ull | ullong |
Compatible and composite types
| T | in | a type |
| U | in | another type |
T and U are compatible: the same type, or alike in their parts where one leaves a part open, an array's size or a function's parameters. Integer types of the same width are still distinct, and a qualified type is compatible only with the same qualified type.
| ✓ | compatible_typearrayint, none, arrayint, 3 | ||
| ✓ | compatible_typelong, llong | ⟹ | fails |
| ✓ | compatible_typeptrint, ptrqualifiedconst, int | ⟹ | fails |
as compatible_type/2, where a transparent union parameter is also compatible with its members (composite_param/4).
| T | in | a type |
| U | in | a type compatible with T |
| C | out | their composite type |
The type that says what both say: an array's size where either knows it (a constant one over a variable one), a function's parameters where either has a prototype. Fails when T and U are not compatible. Variable lengths are compatible with any length (6.7.6.2p6).
A parameter's own qualifiers do not count (6.7.6.3p15).
An enum is compatible with its underlying integer type, but not with another enum (6.7.2.2p4): reflection marks each enum's type enum(Tag) (enum_key/3), which code generation drops.
A use of an enum before its definition, as a function's return type, has the placeholder uint (enum_type/5) and is the same type as a later use, whatever its definition's underlying type.
| ✓ | composite_typeptrarrayint, none, ptrarrayint, 3, C | ⟹ | C = ptrarrayint, 3 |
| ✓ | composite_typeptrarrayint, vlaidn, ptrarrayint, 3, C | ⟹ | C = ptrarrayint, 3 |
| ✓ | composite_typearrayint, 3, arrayint, dyntmpv1, C | ⟹ | C = arrayint, 3 |
| ✓ | composite_typefuncint, unspecified, fixed, funcint, ptrchar, fixed, C | ⟹ | C = funcint, ptrchar, fixed |
| ✓ | composite_typefuncint, qualifiedconst, int, fixed, funcint, int, fixed, C | ⟹ | C = funcint, int, fixed |
| ✓ | composite_typefuncint, ptrqualifiedconst, int, fixed, funcint, ptrint, fixed, _ | ⟹ | fails |
| ✓ | compatible_typequalifiedenum'E', uint, uint | ||
| ✓ | compatible_typequalifiedenum'E', uint, qualifiedenum'G', uint | ⟹ | fails |
| ✓ | composite_typequalifiedenum'E', uint, qualifiedenum'E', int, C | ⟹ | C ≡ int |
| ✓ | composite_typequalifiedenum'E', long, qualifiedenum'E', uint, C | ⟹ | C ≡ long |
| ✓ | composite_typequalifiedenum'E', int, qualifiedenum'E', long, _ | ⟹ | fails |
| ✓ | composite_typearrayint, 2, arrayint, 3, _ | ⟹ | fails |
| ✓ | composite_typefuncint, [], fixed, funcint, [], variadic, _ | ⟹ | fails |
| T | in | a type |
| U | in | a type compatible with T |
| C | out | their composite type |
| Env | in | the scope, or none |
As composite_type/3 (compatible_type/2). With an Env, not none, a transparent union parameter also agrees with each of its members' types (composite_param/4).
| ✓ | composite_typearrayint, none, arrayint, 3, C, none | ⟹ | C ≡ arrayint, 3 |
| ✓ | composite_typeptrfuncint, unspecified, fixed, ptrfuncint, int, fixed, C, none | ⟹ | C ≡ ptrfuncint, int, fixed |
the underlying types of two uses of one enum, T0 and U0, are T and U, with a use before the enum's definition (GNU's forward reference, as glibc's nss_files.h declares functions returning enum nss_status before nss.h defines it), whose placeholder underlying type is uint (enum_type/5), taken as the definition's: both name the one enum type, which nothing could use before it was complete.
reflected type T is enum K (enum_key/3), otherwise T1.
array sizes N and M, each none if unknown, agree on K.
parameter lists Ps and Qs, each unspecified without a prototype, agree on Cs.
A transparent union parameter agrees with a member's type, and the composite is that member's type, as clang's mergeTransparentUnionType. Members are stored unqualified, so the other parameter is compared without its qualifiers (it is a reflected type when c_lower compares qualified redeclarations).
| T0 | in | a reflected type |
| T | out | T0 without qualifiers, at any depth |
The type code generation uses: qualifiers say what a program may do with an object, not how it is stored.
| ✓ | unqualifiedptrqualifiedconst, char, T | ⟹ | T = ptrchar |
| ✓ | unqualifiedfuncqualified(volatile), int, unspecified, fixed, T | ⟹ | T = funcint, unspecified, fixed |
The typed tree
An expression elaborated is a tree whose every node carries its (reflected, qualified) type, with its category the node's principal functor:
X ::= obj(T, F) an lvalue designating an object of type T
| fun(T, F) a function designator of function type T
| val(T, F) a value of type T, not an lvalueThe conversions C applies implicitly are nodes of their own: where an operand is used for its value, value/2 wraps it in load(X) or decay(X). _Generic is resolved here: its tree is the selected association's. Every operand is elaborated but the arguments of a builtin, a statement expression's block and a compound literal's initializer, which stay as the source has them.
| E | in | a C expression, as c_parse reads it |
| Env | in | the scope it is read in |
| X | out | its typed tree |
X is E as C reads it: each node typed, and lvalues, function designators and values told apart. Fails when E cannot be typed.
__func__ is a static char array holding the enclosing function's name, so its size counts the name and the null; the lowerer binds enclosing(function) to that name for the function's body.
| ✓ | ⟹ | X = objarraychar, 5, predefined'__func__' | |
| ✓ | ⟹ | X = valptrint, addrobjint, idx | |
| ✓ | ⟹ | X = vallong, int3000000000 | |
| ✓ | ⟹ | X = valuint, int3000000000 | |
| ✓ | ⟹ | X = objint, derefvalptrint, binadd, valptrint, decayobjarrayint, 4, ida, valint, int1 | |
| ✓ | ⟹ | X = valshort, commavalshort, assign=, objshort, idx, valint, int1, valshort, loadobjshort, idx | |
| ✓ | ⟹ | X = objarraychar, 3, str104, 105 | |
| ✓ | ⟹ | fails | |
| ✓ | bind_ordinaryf, fnfuncint, int, variadic, f, extern, E0, E elaboratecallidf, chr1, chr2, E, X | ⟹ | X = valint, call_, valint, int1, valint, int2 |
| ✓ | bind_ordinaryf, fnfuncint, int, fixed, f, extern, E0, E elaboratecallidf, chr1, chr2, E, _ | ⟹ | fails |
| ✓ | ⟹ | fails | |
| ✓ | ⟹ | fails | |
| ✓ | bind_ordinaryc, varnone, int, E0, E1 bind_ordinaryqtypec, qualifiedconst, int, E1, E elaborateassign=, idc, chr1, E, _ | ⟹ | fails |
filc_test_support.h's zhas_union(p) only blocks optimizations in filcc, which erases the call (FilPizlonator); no library defines it.
an assignment to X has type T, X's own but for a bit-field, whose value has its promoted type (6.5.16p3).
| XV | in | a typed expression tree |
| T | out | its type without qualifiers, the type lowering gives it |
| ✓ | loweredvalqualifiedconst, int, idx, T | ⟹ | T ≡ int |
assignment Op stores B, a value of type TB, into an object of unqualified type TA: B as it is (6.5.16.1), or the result of the operator applied to the two (6.5.16.2).
each argument, elaborated in XArgs, may be assigned to the unqualified type of its parameter in Ps.
| Binding | in | what the identifier Name stands for in Env |
| Name | in | an ordinary identifier |
| Env | in | the scope, which records Name's reflected type |
| T | out | the type it has as an expression |
An enumeration constant has its enumeration's type. An object or function has the type its declaration reflects, qualifiers included, with array sizes that initializers have since completed.
| ✓ | ⟹ | T = qualifiedconst, int | |
| ✓ | bind_ordinaryqtypes, arraychar, none, E0, E identifier_typegvararraychar, 6, s, static, none, s, E, T | ⟹ | T = arraychar, 6 |
| RT | in | an object's reflected type (reflect_type/4), qualifiers kept |
An object of type RT is const: RT is const-qualified, or an array whose elements are (an array type's qualifiers are its elements'). Modifying it is undefined, so Fil-C gives a static one a read-only capability and filcc traps a store through a cast (clang marks such a global constant, volatile or not). A struct with a const member is not itself const, and C has no mutable members, so a const struct is read-only as a whole.
| ✓ | readonly_object_typequalifiedconst, int | ||
| ✓ | readonly_object_typequalifiedconst, (volatile), int | ||
| ✓ | readonly_object_typearrayqualifiedconst, int, 3 | ||
| ✓ | readonly_object_typequalifiedconst, arrayint, 3 | ||
| ✓ | readonly_object_typeint | ⟹ | fails |
| ✓ | readonly_object_typeptrqualifiedconst, char | ⟹ | fails |
| ✓ | readonly_object_typequalified(volatile), int | ⟹ | fails |
| ✓ | readonly_object_typestruct1 | ⟹ | fails |
S is reflected type RT with the array dimensions of declared type T, which an initializer may have completed since RT was recorded.
| SU | in | a struct or union type, perhaps qualified |
| F | in | a member name |
| Env | in | the scope that defines SU |
| T | out | the member's reflected type |
Looks into anonymous members. A qualified SU qualifies the member.
| ✓ | member_typestruct'builtin.va_list', fp_offset, E, T | ⟹ | T = uint |
| ✓ | member_typequalifiedconst, struct'builtin.va_list', reg_save_area, E, T | ⟹ | T = qualifiedconst, ptrvoid |
| ✓ | member_typestruct'builtin.va_list', gp, E, _ | ⟹ | fails |
one of Members, or a member of an anonymous one, is F of type T.
| Name | in/out | a GNU builtin spelling of a standard, POSIX or GNU library function |
| F | in/out | that function |
GCC and clang declare __builtin_F for these library functions F whether or not a header declares F, and a call is a call of F (bison parsers call __builtin_memcpy without <string.h>, as netcdf's dcetab.c does; glibc's own sources call __builtin_mempcpy and __builtin_stpcpy). builtin_declarations/1 declares each with F's prototype, named F in the object.
| ✓ | library_builtin'__builtin_memcpy', F | ⟹ | F = memcpy |
| ✓ | library_builtin'__builtin_mempcpy', F | ⟹ | F = mempcpy |
| ✓ | library_builtinmemcpy, _ | ⟹ | fails |
Library function F's prototype, with types named as proto_type/3 reads them.
| 3890 | memcpy | vp | vp, cvp, size |
| 3891 | memmove | vp | vp, cvp, size |
| 3892 | memset | vp | vp, int, size |
| 3893 | memcmp | int | cvp, cvp, size |
| 3894 | memchr | vp | cvp, int, size |
| 3895 | mempcpy | vp | vp, cvp, size |
| 3896 | strlen | size | ccp |
| 3897 | strcmp | int | ccp, ccp |
| 3898 | strncmp | int | ccp, ccp, size |
| 3899 | strcpy | cp | cp, ccp |
| 3900 | strncpy | cp | cp, ccp, size |
| 3901 | stpcpy | cp | cp, ccp |
| 3902 | strcat | cp | cp, ccp |
| 3903 | strncat | cp | cp, ccp, size |
| 3904 | strchr | cp | ccp, int |
| 3905 | strrchr | cp | ccp, int |
| 3906 | strstr | cp | ccp, ccp |
| 3907 | strdup | cp | ccp |
| 3908 | malloc | vp | size |
| 3909 | calloc | vp | size, size |
| 3910 | realloc | vp | vp, size |
| 3911 | free | void | vp |
| 3912 | abort | void | [] |
| 3913 | abs | int | int |
| 3914 | labs | long | long |
| Items | out | top-level declarations of every library_builtin/2 name |
Each declares __builtin_F with F's prototype and the asm label F, as if a unit began with void *__builtin_memcpy(void *, const void *, size_t) __asm__("memcpy"); and so on.
| Name | in | a label whose address GNU C's &&Name takes |
| Env | in | the scope of the function that defines it, where c_lower bound |
| E | out | the expression (void *)N, which &&Name stands for |
label_addr(Name) to the label's number N (1, 2, ... in the order the function takes the addresses)
A label's value is a void * that only goto * of the same function uses. nanofil gives it no capability: the address N is the label's number, an address constant (so static tables of labels work), and goto * dispatches on it among the function's address-taken labels, trapping on any other value. Values of labels of different functions may compare equal.
| ✓ | ⟹ | X = casttypetspecvoid, ptrbase, [], num3, none−dec | |
| ✓ | label_valuel, E, _ | ⟹ | fails |
| Name | in/out | a GNU object-size checking builtin, which glibc's _FORTIFY_SOURCE headers call |
| Chk | in/out | the library function that checks |
| Plain | in/out | the function it checks |
An undeclared Name called is a call of Chk with the same arguments when Chk is declared (glibc declares the printf family's). Else a string or memory function's last argument is the destination's object size, and when it folds to (size_t)-1, unknown, the call is one of Plain without it, as GCC and clang lower it; any other size is refused. Fil-C checks every access anyway.
| ✓ | checking_builtin'__builtin___memcpy_chk', C, P | ⟹ | C = '__memcpy_chk', P = memcpy |
| ✓ | checking_builtin'__builtin_memcpy', _, _ | ⟹ | fails |
Call is Plain of Args but the last, a constant object size of (size_t)-1, for a memory or string function.
| Name | in | __builtin_object_size or __builtin_dynamic_object_size |
| Args | in | its pointer and its constant type, 0 to 3 |
| Env | in | the scope the type is read in |
| V | out | the size it answers: (size_t)-1 for types 0 and 1, 0 for 2 and 3 |
nanofil always answers that the size is unknown, which GCC documents as a correct answer for any pointer; the pointer is not evaluated. Fails unless the type is a constant 0 to 3.
| ✓ | object_size'__builtin_object_size', idp, num1, none−dec, E, V | ⟹ | V = 18446744073709551615 |
| ✓ | object_size'__builtin_dynamic_object_size', idp, num2, none−dec, E, V | ⟹ | V = 0 |
| ✓ | object_size'__builtin_object_size', idp, num4, none−dec, E, _ | ⟹ | fails |
| Name | in | a GNU __atomic or clang __c11_atomic builtin |
| Args | in | its argument expressions |
| Env | in | the scope |
| R | out | expr(E), the plain C expression E it is, or builtin(T), a builtin call of type T that lowering makes atomic |
On an integer, __atomic_load_n(P, Order) is *P and, for a relaxed or release Order, __atomic_store_n(P, V, Order) is (void)(*P = V), both after Order: an aligned integer access is already atomic on x86-64, and none of those orders needs a fence there. A sequentially consistent integer store is the store and then a fence (mov; mfence, where clang emits xchg; both are seq_cst stores against plain-mov seq_cst loads). On a pointer, whose capability is a second word, both are builtin(T) at every order: lowering emits IR's atomic_load and atomic_store, which call Fil-C's helpers as clang does, single-copy atomic and sequentially consistent (docs/m4.md, Atomic builtins). *P is an integer or a pointer, and Order a constant: relaxed, consume, acquire or seq_cst for a load; relaxed, release or seq_cst for a store. Anything else fails (fault/4 says why). A store through a pointer to an _Atomic object is builtin(void) at every order, since assigning the object would be a seq_cst store.
<stdatomic.h>'s generic functions are clang's __c11_atomic builtins, whose pointer must point to an _Atomic object, here of an integer, pointer, float or double. __c11_atomic_load and __c11_atomic_store are __atomic_load_n and __atomic_store_n, with the same orders; __c11_atomic_init(P, V) stores V without atomicity, as clang does. A thread fence of a constant order is builtin(void), a fence only when seq_cst (x86-64 orders the rest itself); a signal fence orders nothing a single thread can observe.
The read-modify-writes are builtin(T) of the type they return, each one sequentially consistent atomic step whatever its orders, which lowering makes a loop of IR's cas (docs/m4.md): __c11_atomic_exchange and __c11_atomic_fetch_OP (add, sub, and, or, xor, nand, max, min) on an _Atomic integer return the old value, add and sub also on an _Atomic pointer, by elements, and exchange also on an _Atomic float or double; __c11_atomic_compare_exchange_strong and _weak, on any of these, return whether *P equaled *Expected, bit for bit (so 0.0 is not -0.0, as clang's cmpxchg of the bits has it), and write *P to *Expected only when not (a weak one never fails spuriously). GCC's __atomic_exchange_n, __atomic_fetch_OP, __atomic_OP_fetch (the new value), __atomic_compare_exchange_n, the __sync builtins and __atomic_test_and_set and __atomic_clear (a byte at any object pointer) work on a plain integer, or a pointer to exchange or compare it; their fetch-and-ops on a pointer are refused (clang adds bytes, not elements, and filcc cannot compile them). GCC's generic __atomic_load(P, R, O), __atomic_store(P, V, O), __atomic_exchange(P, V, R, O) and __atomic_compare_exchange(P, E, D, W, S, F) pass the values through pointers: they are *R = __atomic_load_n(P, O), __atomic_store_n(P, *V, O), *R = __atomic_exchange_n(P, *V, O) and __atomic_compare_exchange_n(P, E, *D, W, S, F), on the same integers and pointers.
| ✓ | bind_ordinaryp, varnone, ptrlong, E0, E atomic_builtin'__atomic_load', idp, idr, chr2, E, X | ⟹ | X = exprcasttypetspecvoid, base, assign=, underef, idr, callid'__atomic_load_n', idp, chr2 |
| ✓ | bind_ordinaryp, varnone, ptrstruct0, E0, E atomic_builtin'__atomic_load', idp, idr, chr2, E, _ | ⟹ | fails |
| ✓ | ⟹ | X = exprcommachr0, underef, idp | |
| ✓ | bind_ordinaryp, varnone, ptrptrint, E0, E atomic_builtin'__atomic_load_n', idp, chr2, E, X | ⟹ | X = builtinptrint |
| ✓ | bind_ordinaryp, varnone, ptrint, E0, E atomic_builtin'__atomic_store_n', idp, chr1, chr5, E, X | ⟹ | X = builtinvoid |
| ✓ | bind_ordinaryp, varnone, ptrqualifiedatomic, int, E0, E atomic_builtin'__atomic_store_n', idp, chr1, chr0, E, X | ⟹ | X = builtinvoid |
| ✓ | bind_ordinaryp, varnone, ptrint, E0, E atomic_builtin'__atomic_store_n', idp, chr1, chr2, E, _ | ⟹ | fails |
| ✓ | bind_ordinaryp, varnone, ptrdouble, E0, E atomic_builtin'__atomic_load_n', idp, chr0, E, _ | ⟹ | fails |
| ✓ | bind_ordinaryp, varnone, ptrqualifiedatomic, int, E0, E atomic_builtin'__c11_atomic_fetch_add', idp, chr1, chr5, E, X | ⟹ | X = builtinint |
| ✓ | bind_ordinaryp, varnone, ptrqualifiedatomic, ptrchar, E0, E bind_ordinarye, varnone, ptrptrchar, E, E1 atomic_builtin'__c11_atomic_compare_exchange_weak', idp, ide, ide, chr5, chr0, E1, X | ⟹ | X = builtinbool |
| ✓ | bind_ordinaryp, varnone, ptrlong, E0, E atomic_builtin'__sync_add_and_fetch', idp, chr1, E, X | ⟹ | X = builtinlong |
| ✓ | bind_ordinaryp, varnone, ptrptrint, E0, E atomic_builtin'__atomic_fetch_add', idp, chr1, chr5, E, _ | ⟹ | fails |
| ✓ | bind_ordinaryp, varnone, ptrqualifiedatomic, double, E0, E atomic_builtin'__c11_atomic_fetch_add', idp, chr1, chr5, E, _ | ⟹ | fails |
| ✓ | bind_ordinaryp, varnone, ptrqualifiedatomic, double, E0, E bind_ordinarye, varnone, ptrdouble, E, E1 atomic_builtin'__c11_atomic_compare_exchange_strong', idp, ide, chr0, chr5, chr5, E1, X | ⟹ | X = builtinbool |
| ✓ | bind_ordinaryp, varnone, ptrdouble, E0, E atomic_builtin'__atomic_exchange_n', idp, chr0, chr5, E, _ | ⟹ | fails |
| ✓ | bind_ordinaryp, varnone, ptrqualifiedatomic, ptrvoid, E0, E atomic_builtin'__c11_atomic_load', idp, chr0, E, X | ⟹ | X = builtinptrvoid |
| ✓ | bind_ordinaryp, varnone, ptrqualifiedatomic, double, E0, E atomic_builtin'__c11_atomic_load', idp, chr5, E, X | ⟹ | X = exprcommachr5, underef, idp |
| ✓ | bind_ordinaryp, varnone, ptrqualifiedatomic, int, E0, E atomic_builtin'__c11_atomic_init', idp, chr1, E, X | ⟹ | X = builtinvoid |
| ✓ | atomic_builtin'__c11_atomic_thread_fence', chr2, E, X | ⟹ | X = builtinvoid |
| ✓ | ⟹ | fails | |
| ✓ | bind_ordinaryp, varnone, ptrint, E0, E atomic_builtin'__c11_atomic_fetch_add', idp, chr1, chr5, E, _ | ⟹ | fails |
An _Atomic struct or union (atomic_su_type/3): load, store and init are its lvalue conversion and assignment, and exchange and compare-exchange builtins of its type; the arithmetic ones are refused.
atomic_su_type/3GCC's generic atomic Name on Args is E, its _n form on the values the pointers in Args point to.
| Name | in/out | an atomic read-modify-write builtin |
| Family | in/out | c11 on an _Atomic object, gnu or sync on a plain one |
| Op | in/out | how it combines the old value and its operand (xchg: the operand alone) |
| Result | in/out | old or new: the value it returns |
| ✓ | rmw_builtin'__atomic_add_fetch', F, Op, R | ⟹ | F, Op, R = gnu, add, new |
| ✓ | rmw_builtin'__sync_fetch_and_or', F, Op, R | ⟹ | F, Op, R = sync, or, old |
| P | in | a compare-exchange's object pointer, to an integer, pointer, float or double |
| E | in | its expected-value pointer |
| Env | in | the scope |
E points to an object of *P's unqualified type, to any pointer when *P is a pointer, or is a void *: clang initializes the parameter from E as by assignment, and void * converts to any object pointer (GLib's g_atomic_int_compare_and_exchange passes void *&old).
| Name | in | an __atomic or __c11_atomic builtin |
| Args | in | its arguments |
| Env | in | the scope |
| Orders | out | its memory-order arguments that are not constant, at least one |
| Args1 | out | Args with each of those replaced by 5, __ATOMIC_SEQ_CST |
clang accepts a memory order known only at run time and chooses the order then; nanofil uses seq_cst, which is at least as strong as any order, after evaluating the argument for its effects. liburcu passes cmm_to_c11(CMM_RELAXED), a call to a static inline function.
E is O, for its effects, then E0.
the argument positions of builtin Name's memory orders.
P points to an _Atomic object.
P points to an _Atomic S, a struct or union (atomic_su_type/3).
as atomic_operands/5, with a float or double *P too.
an atomic read-modify-write builtin.
atomic_operands(+P, +O, +Env, +Orders, -T[, -Order]): P points to an integer or a pointer of unqualified type T and O is a constant memory order Order among Orders (__ATOMIC_RELAXED 0, CONSUME 1, ACQUIRE 2, RELEASE 3, ACQ_REL 4, SEQ_CST 5).
| Name | in/out | a clang rotate builtin |
| Dir | in/out | left or right |
| Type | in/out | the unsigned type it rotates: uchar, ushort, uint or ulong |
| ✓ | rotate_builtin'__builtin_rotateleft32', D, T | ⟹ | D, T = left, uint |
| Name | in/out | a builtin's name |
| Type | in/out | the type of a call of it |
A call of builtin Name, when Name is not declared, has Type. The bit builtins count trailing or leading zero bits, or one bits, of an unsigned int, long or long long, giving an int; the byte swaps give the unsigned type of their width.
clang's rotates __builtin_rotateleftN and __builtin_rotaterightN (N 8, 16, 32 or 64) give the unsigned type of width N, as their operands are.
__builtin_isnan(x) is nonzero for a NaN of any floating type (an integer converts to double first). __builtin_flt_rounds() is FLT_ROUNDS, the current rounding direction: 0 toward zero, 1 to nearest, 2 upward, 3 downward.
__builtin_assume_aligned(p, align[, offset]) is p as a void *; the alignment it promises changes no bounds, so nanofil drops it.
| ✓ | builtin_type'__builtin_ctzll', T | ⟹ | T = int |
| ✓ | builtin_type'__builtin_bswap16', T | ⟹ | T = ushort |
| ✓ | builtin_type'__builtin_bswap64', T | ⟹ | T = ulong |
| ✓ | builtin_type'__builtin_rotateleft8', T | ⟹ | T = uchar |
| ✓ | builtin_type'__builtin_rotateright64', T | ⟹ | T = ulong |
| ✓ | builtin_type'__builtin_rotateleft128', _ | ⟹ | fails |
| ✓ | builtin_type'__builtin_isnan', T | ⟹ | T = int |
| ✓ | builtin_type'__builtin_fabsl', T | ⟹ | T = ldouble |
| ✓ | builtin_type'__builtin_assume_aligned', T | ⟹ | T = ptrvoid |
| ✓ | builtin_type'__builtin_ctzlll', _ | ⟹ | fails |
| Name | in/out | a feature __builtin_cpu_supports(Name) tests |
| Sym | in/out | the runtime's word array, __cpu_model or __cpu_features2 |
| Off | in/out | the byte offset of the 32-bit word in it |
| Mask | in/out | the feature's bits in that word |
__builtin_cpu_supports("name") is 1 when the word at Off in Sym, which __cpu_indicator_init (__builtin_cpu_init) fills from cpuid, has all of Mask set, else 0, as clang's x86 lowering reads it; any other string is 0 (clang warns: an invalid cpu feature string). The table is measured from the pinned filcc's IR for each name it accepts.
| ✓ | cpu_featureavx2, '__cpu_model', 12, 1024 | ||
| ✓ | cpu_feature'x86-64-v3', '__cpu_features2', 8, 2 | ||
| ✓ | cpu_featurelm, _, _, _ | ⟹ | fails |
| 4494 | cmov | '__cpu_model' | 12 | 0x1 |
| 4495 | mmx | '__cpu_model' | 12 | 0x2 |
| 4496 | popcnt | '__cpu_model' | 12 | 0x4 |
| 4497 | sse | '__cpu_model' | 12 | 0x8 |
| 4498 | sse2 | '__cpu_model' | 12 | 0x10 |
| 4499 | sse3 | '__cpu_model' | 12 | 0x20 |
| 4500 | ssse3 | '__cpu_model' | 12 | 0x40 |
| 4501 | 'sse4.1' | '__cpu_model' | 12 | 0x80 |
| 4502 | 'sse4.2' | '__cpu_model' | 12 | 0x100 |
| 4503 | avx | '__cpu_model' | 12 | 0x200 |
| 4504 | avx2 | '__cpu_model' | 12 | 0x400 |
| 4505 | sse4a | '__cpu_model' | 12 | 0x800 |
| 4506 | fma4 | '__cpu_model' | 12 | 0x1000 |
| 4507 | xop | '__cpu_model' | 12 | 0x2000 |
| 4508 | fma | '__cpu_model' | 12 | 0x4000 |
| 4509 | avx512f | '__cpu_model' | 12 | 0x8000 |
| 4510 | bmi | '__cpu_model' | 12 | 0x10000 |
| 4511 | bmi2 | '__cpu_model' | 12 | 0x20000 |
| 4512 | aes | '__cpu_model' | 12 | 0x40000 |
| 4513 | pclmul | '__cpu_model' | 12 | 0x80000 |
| 4514 | avx512vl | '__cpu_model' | 12 | 0x100000 |
| 4515 | avx512bw | '__cpu_model' | 12 | 0x200000 |
| 4516 | avx512dq | '__cpu_model' | 12 | 0x400000 |
| 4517 | avx512cd | '__cpu_model' | 12 | 0x800000 |
| 4518 | avx512vbmi | '__cpu_model' | 12 | 0x4000000 |
| 4519 | avx512ifma | '__cpu_model' | 12 | 0x8000000 |
| 4520 | avx5124vnniw | '__cpu_model' | 12 | 0x10000000 |
| 4521 | avx5124fmaps | '__cpu_model' | 12 | 0x20000000 |
| 4522 | avx512vpopcntdq | '__cpu_model' | 12 | 0x40000000 |
| 4523 | avx512vbmi2 | '__cpu_model' | 12 | 0x80000000 |
| 4524 | gfni | '__cpu_features2' | 0 | 0x1 |
| 4525 | vpclmulqdq | '__cpu_features2' | 0 | 0x2 |
| 4526 | avx512vnni | '__cpu_features2' | 0 | 0x4 |
| 4527 | avx512bitalg | '__cpu_features2' | 0 | 0x8 |
| 4528 | avx512bf16 | '__cpu_features2' | 0 | 0x10 |
| 4529 | avx512vp2intersect | '__cpu_features2' | 0 | 0x20 |
| 4530 | adx | '__cpu_features2' | 0 | 0x100 |
| 4531 | cldemote | '__cpu_features2' | 0 | 0x400 |
| 4532 | clflushopt | '__cpu_features2' | 0 | 0x800 |
| 4533 | clwb | '__cpu_features2' | 0 | 0x1000 |
| 4534 | clzero | '__cpu_features2' | 0 | 0x2000 |
| 4535 | enqcmd | '__cpu_features2' | 0 | 0x10000 |
| 4536 | f16c | '__cpu_features2' | 0 | 0x20000 |
| 4537 | fsgsbase | '__cpu_features2' | 0 | 0x40000 |
| 4538 | lwp | '__cpu_features2' | 0 | 0x1000000 |
| 4539 | lzcnt | '__cpu_features2' | 0 | 0x2000000 |
| 4540 | movbe | '__cpu_features2' | 0 | 0x4000000 |
| 4541 | movdir64b | '__cpu_features2' | 0 | 0x8000000 |
| 4542 | movdiri | '__cpu_features2' | 0 | 0x10000000 |
| 4543 | mwaitx | '__cpu_features2' | 0 | 0x20000000 |
| 4544 | pconfig | '__cpu_features2' | 0 | 0x80000000 |
| 4545 | pku | '__cpu_features2' | 4 | 0x1 |
| 4546 | prfchw | '__cpu_features2' | 4 | 0x4 |
| 4547 | ptwrite | '__cpu_features2' | 4 | 0x8 |
| 4548 | rdpid | '__cpu_features2' | 4 | 0x10 |
| 4549 | rdrnd | '__cpu_features2' | 4 | 0x20 |
| 4550 | rdseed | '__cpu_features2' | 4 | 0x40 |
| 4551 | rtm | '__cpu_features2' | 4 | 0x80 |
| 4552 | serialize | '__cpu_features2' | 4 | 0x100 |
| 4553 | sgx | '__cpu_features2' | 4 | 0x200 |
| 4554 | sha | '__cpu_features2' | 4 | 0x400 |
| 4555 | shstk | '__cpu_features2' | 4 | 0x800 |
| 4556 | tbm | '__cpu_features2' | 4 | 0x1000 |
| 4557 | tsxldtrk | '__cpu_features2' | 4 | 0x2000 |
| 4558 | vaes | '__cpu_features2' | 4 | 0x4000 |
| 4559 | waitpkg | '__cpu_features2' | 4 | 0x8000 |
| 4560 | wbnoinvd | '__cpu_features2' | 4 | 0x10000 |
| 4561 | xsave | '__cpu_features2' | 4 | 0x20000 |
| 4562 | xsavec | '__cpu_features2' | 4 | 0x40000 |
| 4563 | xsaveopt | '__cpu_features2' | 4 | 0x80000 |
| 4564 | xsaves | '__cpu_features2' | 4 | 0x100000 |
| 4565 | 'amx-tile' | '__cpu_features2' | 4 | 0x200000 |
| 4566 | 'amx-int8' | '__cpu_features2' | 4 | 0x400000 |
| 4567 | 'amx-bf16' | '__cpu_features2' | 4 | 0x800000 |
| 4568 | uintr | '__cpu_features2' | 4 | 0x1000000 |
| 4569 | hreset | '__cpu_features2' | 4 | 0x2000000 |
| 4570 | kl | '__cpu_features2' | 4 | 0x4000000 |
| 4571 | widekl | '__cpu_features2' | 4 | 0x10000000 |
| 4572 | avxvnni | '__cpu_features2' | 4 | 0x20000000 |
| 4573 | avx512fp16 | '__cpu_features2' | 4 | 0x40000000 |
| 4574 | 'x86-64' | '__cpu_features2' | 4 | 0x80000000 |
| 4575 | 'x86-64-v2' | '__cpu_features2' | 8 | 0x1 |
| 4576 | 'x86-64-v3' | '__cpu_features2' | 8 | 0x2 |
| 4577 | 'x86-64-v4' | '__cpu_features2' | 8 | 0x4 |
| 4578 | avxifma | '__cpu_features2' | 8 | 0x8 |
| 4579 | avxvnniint8 | '__cpu_features2' | 8 | 0x10 |
| 4580 | avxneconvert | '__cpu_features2' | 8 | 0x20 |
| 4581 | cmpccxadd | '__cpu_features2' | 8 | 0x40 |
| 4582 | 'amx-fp16' | '__cpu_features2' | 8 | 0x80 |
| 4583 | prefetchi | '__cpu_features2' | 8 | 0x100 |
| 4584 | raoint | '__cpu_features2' | 8 | 0x200 |
| 4585 | 'amx-complex' | '__cpu_features2' | 8 | 0x400 |
| 4586 | avxvnniint16 | '__cpu_features2' | 8 | 0x800 |
| 4587 | sm3 | '__cpu_features2' | 8 | 0x1000 |
| 4588 | sha512 | '__cpu_features2' | 8 | 0x2000 |
| 4589 | sm4 | '__cpu_features2' | 8 | 0x4000 |
| 4590 | apxf | '__cpu_features2' | 8 | 0x8000 |
| 4591 | usermsr | '__cpu_features2' | 8 | 0x10000 |
| 4592 | 'avx10.1-256' | '__cpu_features2' | 8 | 0x20000 |
| 4593 | 'avx10.1-512' | '__cpu_features2' | 8 | 0x40000 |
| 4594 | 'avx10.2-256' | '__cpu_features2' | 8 | 0x80000 |
| 4595 | 'avx10.2-512' | '__cpu_features2' | 8 | 0x100000 |
| Name | in/out | a vendor, processor type or subtype __builtin_cpu_is(Name) tests |
| Off | in/out | the byte offset in __cpu_model of the vendor (0), type (4) or subtype (8) |
| Value | in/out | the value that word has for Name |
__builtin_cpu_is("name") is 1 when the 32-bit word at Off in __cpu_model equals Value, else 0; clang refuses any other name. Measured from the pinned filcc's IR.
| ✓ | cpu_model_valueintel, 0, 1 | ||
| ✓ | cpu_model_valuehaswell, 8, 13 | ||
| ✓ | cpu_model_valuek8, _, _ | ⟹ | fails |
| 4609 | intel | 0 | 1 |
| 4610 | amd | 0 | 2 |
| 4611 | atom | 4 | 1 |
| 4612 | core2 | 4 | 2 |
| 4613 | corei7 | 4 | 3 |
| 4614 | amdfam10h | 4 | 4 |
| 4615 | amdfam15h | 4 | 5 |
| 4616 | amdfam17h | 4 | 10 |
| 4617 | amdfam19h | 4 | 15 |
| 4618 | amdfam1ah | 4 | 20 |
| 4619 | bonnell | 4 | 1 |
| 4620 | silvermont | 4 | 6 |
| 4621 | slm | 4 | 6 |
| 4622 | knl | 4 | 7 |
| 4623 | knm | 4 | 11 |
| 4624 | goldmont | 4 | 12 |
| 4625 | 'goldmont-plus' | 4 | 13 |
| 4626 | tremont | 4 | 14 |
| 4627 | sierraforest | 4 | 17 |
| 4628 | grandridge | 4 | 18 |
| 4629 | clearwaterforest | 4 | 19 |
| 4630 | nehalem | 8 | 1 |
| 4631 | westmere | 8 | 2 |
| 4632 | sandybridge | 8 | 3 |
| 4633 | ivybridge | 8 | 12 |
| 4634 | haswell | 8 | 13 |
| 4635 | broadwell | 8 | 14 |
| 4636 | skylake | 8 | 15 |
| 4637 | 'skylake-avx512' | 8 | 16 |
| 4638 | cannonlake | 8 | 17 |
| 4639 | 'icelake-client' | 8 | 18 |
| 4640 | 'icelake-server' | 8 | 19 |
| 4641 | cascadelake | 8 | 21 |
| 4642 | tigerlake | 8 | 22 |
| 4643 | cooperlake | 8 | 23 |
| 4644 | sapphirerapids | 8 | 24 |
| 4645 | alderlake | 8 | 25 |
| 4646 | raptorlake | 8 | 25 |
| 4647 | meteorlake | 8 | 25 |
| 4648 | rocketlake | 8 | 27 |
| 4649 | graniterapids | 8 | 30 |
| 4650 | 'graniterapids-d' | 8 | 31 |
| 4651 | arrowlake | 8 | 32 |
| 4652 | 'arrowlake-s' | 8 | 33 |
| 4653 | lunarlake | 8 | 33 |
| 4654 | pantherlake | 8 | 34 |
| 4655 | gracemont | 8 | 25 |
| 4656 | emeraldrapids | 8 | 24 |
| 4657 | diamondrapids | 8 | 36 |
| 4658 | barcelona | 8 | 4 |
| 4659 | shanghai | 8 | 5 |
| 4660 | istanbul | 8 | 6 |
| 4661 | btver1 | 4 | 8 |
| 4662 | btver2 | 4 | 9 |
| 4663 | bdver1 | 8 | 7 |
| 4664 | bdver2 | 8 | 8 |
| 4665 | bdver3 | 8 | 9 |
| 4666 | bdver4 | 8 | 10 |
| 4667 | znver1 | 8 | 11 |
| 4668 | znver2 | 8 | 20 |
| 4669 | znver3 | 8 | 26 |
| 4670 | znver4 | 8 | 29 |
| 4671 | znver5 | 8 | 35 |
| Name | in/out | a type-generic classification builtin glibc's math.h uses |
__builtin_isinf_sign(x) is 1 for +inf, -1 for -inf, else 0; __builtin_isfinite, __builtin_isnormal and __builtin_signbit are 1 or 0; __builtin_fpclassify(Nan, Inf, Normal, Subnormal, Zero, x) is the argument for x's class. A float classifies as itself, an integer or double as a double, and a long double by its x87 bits, as clang does: an unnormal, pseudo-infinity or pseudo-NaN (a nonzero exponent without the explicit integer bit) is a NaN, and a pseudo-denormal (a zero exponent with it) is FP_NORMAL to fpclassify but not isnormal. __builtin_signbitl(x) is __builtin_signbit((long double)x), and __builtin_signbitf(x) __builtin_signbit((float)x).
__builtin_isinf(x) is 1 for either infinity, else 0.
| ✓ | classify_builtin'__builtin_fpclassify' | ||
| ✓ | classify_builtin'__builtin_isinf' |
| Name | in/out | a GNU math builtin |
| F | in/out | the libm function it computes |
__builtin_ceil(x) and __builtin_floor(x) are ceil(x) and floor(x) of x converted to double, even with F undeclared. Clang computes them inline (roundsd); a call of libm's F gives the same exact result, signed zeros and NaNs included.
| ✓ | libm_builtin'__builtin_floor', F | ⟹ | F = floor |
| ✓ | libm_builtin'__builtin_sqrt', _ | ⟹ | fails |
| ✓ | libm_builtin'__builtin_round', _ | ⟹ | fails |
| Name | in/out | a GNU square root builtin |
| F | in/out | the libm function it computes |
| Type | in/out | the type of its argument and result |
__builtin_sqrt(x), __builtin_sqrtf(x) and __builtin_sqrtl(x) are the square root of x converted to Type, correctly rounded: the IR's sqrt under -fno-math-errno (NANOFIL_MATH_ERRNO=0), as glibc's libm is built and where its own sqrt is this builtin, so a call would recurse. Under the default -fmath-errno libm's F also sets errno to EDOM for x < 0, so the builtin is a call of F, as clang's is (cogl's cogl_sqrti); a long double one is then refused.
| ✓ | root_builtin'__builtin_sqrt', F, T | ⟹ | F, T = sqrt, double |
| ✓ | root_builtin'__builtin_sqrtl', F, T | ⟹ | F, T = sqrtl, ldouble |
| ✓ | root_builtin'__builtin_sqrtll', _, _ | ⟹ | fails |
| 4735 | '__builtin_sqrt' | sqrt | double |
| 4736 | '__builtin_sqrtf' | sqrtf | float |
| 4737 | '__builtin_sqrtl' | sqrtl | ldouble |
| Name | in/out | a GNU builtin that rounds toward zero |
| Type | in/out | the type of its argument and result, float or double |
__builtin_trunc(x) and __builtin_truncf(x) are x rounded toward zero: a magnitude below 2^52 (2^23 for float) through the integer it truncates to, with x's sign so -0.5 gives -0; any other x is already integral, infinite or a NaN, which x + 0 quiets. glibc's own trunc and truncf are these builtins. trunc never sets errno.
| ✓ | trunc_builtin'__builtin_truncf', T | ⟹ | T = float |
| ✓ | trunc_builtin'__builtin_truncl', _ | ⟹ | fails |
| Name | in/out | a GNU builtin that edits a float's sign bit |
| Op | in/out | abs, which clears it, or copysign, which takes it from the second argument |
| Type | in/out | the type of its arguments and result |
__builtin_fabs(x) is x with its sign bit clear, and __builtin_copysign(x, y) is x with y's sign bit, with their f (float) and l (long double) forms: only that bit changes, so a NaN keeps its payload and -0 becomes +0. clang computes them inline (llvm.fabs, llvm.copysign), and glibc's own fabs and copysign are these builtins, so they are never a call.
| ✓ | sign_builtin'__builtin_fabsf', Op, T | ⟹ | Op, T = abs, float |
| ✓ | sign_builtin'__builtin_copysignl', Op, T | ⟹ | Op, T = copysign, ldouble |
| ✓ | sign_builtin'__builtin_fabsll', _, _ | ⟹ | fails |
| 4772 | '__builtin_fabs' | abs | double |
| 4773 | '__builtin_fabsf' | abs | float |
| 4774 | '__builtin_fabsl' | abs | ldouble |
| 4775 | '__builtin_copysign' | copysign | double |
| 4776 | '__builtin_copysignf' | copysign | float |
| 4777 | '__builtin_copysignl' | copysign | ldouble |
| Name | in/out | a clang builtin that immintrin.h's SSE through SSE4.2 intrinsics call |
| Params | in/out | its parameter types |
| Ret | in/out | its result type |
c_lower computes each element by element (vector_builtin//4). A count, lane or shuffle-control parameter typed int must be a constant where the instruction encodes it as an immediate.
| ✓ | vector_builtin'__builtin_ia32_pmovmskb128', vecchar, 16, 16, int | ||
| ✓ | vector_builtin'__builtin_ia32_psrlwi128', P, R | ⟹ | P = vecshort, 8, 16, int, R = vecshort, 8, 16 |
| Name256 | in/out | an AVX2 builtin on 32-byte vectors |
| Name128 | in/out | the SSE builtin it applies to each 128-bit lane |
Most AVX2 integer instructions operate on the two 128-bit lanes of their 32-byte operands independently, each as the SSE instruction of the same name does, with the same immediate; a shift's count vector is 16 bytes and applies to both lanes (shift_count_builtin/1).
| ✓ | lane_builtinpshufb256, pshufb128 | ||
| ✓ | lane_builtinpsrad256, psrad128 | ||
| ✓ | lane_builtinpermvarsi256, _ | ⟹ | fails |
| Name | in/out | a clang builtin whose result type follows its operands |
__builtin_shufflevector(A, B, I...) picks elements of A and B, a vector of as many elements as there are constant indexes I; __builtin_convertvector(V, T) converts each element of V to T's; the elementwise builtins apply to each element and have their operand's type. __builtin_nondeterministic_value(V) is some value of V's type, V unevaluated (avxintrin.h's undefined upper halves); nanofil makes it zero.
| Name | in | a generic vector builtin (__builtin_shufflevector, __builtin_convertvector, __builtin_elementwise_*) |
| Args | in | the call's arguments |
| Env | in | the scope |
| T | out | the vec(E, N, Size) type of the call |
Fails when the operands' vector types do not agree as Name needs.
| Name | in/out | a bit-counting builtin |
| Op | in/out | ctz, clz, popcount or ffs (one plus the index of the lowest set bit, 0 for 0) |
| Type | in/out | its operand's type: uint, ulong or ullong |
| ✓ | bit_builtin'__builtin_clzl', Op, T | ⟹ | Op, T = clz, ulong |
| ✓ | bit_builtin'__builtin_popcount', Op, T | ⟹ | Op, T = popcount, uint |
| ✓ | bit_builtin'__builtin_ffsl', Op, T | ⟹ | Op, T = ffs, ulong |
| ✓ | bit_builtin'__builtin_parity', _, _ | ⟹ | fails |
| Name | in | a bit-counting builtin (bit_builtin/3) |
| A | in | its argument expression |
| Env | in | the scope |
| V | out | the count, when A is an integer constant expression |
Clang folds a bit-counting builtin of a constant, so it may size an array or initialize a static (rhash's rhash_popcount). The operand converts to the builtin's unsigned type first. clz and ctz of 0 are undefined and do not fold.
| ✓ | bit_folded'__builtin_popcount', unneg, num1, none−dec, E, V | ⟹ | V = 32 |
| ✓ | bit_folded'__builtin_clzl', num1, none−dec, E, V | ⟹ | V = 63 |
| ✓ | bit_folded'__builtin_ctz', num8, none−dec, E, V | ⟹ | V = 3 |
| ✓ | bit_folded'__builtin_ffs', num0, none−dec, E, V | ⟹ | V = 0 |
| ✓ | bit_folded'__builtin_ctz', num0, none−dec, E, _ | ⟹ | fails |
| Name | in/out | a GNU checked-arithmetic builtin |
| Op | in/out | add, sub or mul |
| Fixed | in/out | the type of its operands, or generic for any integer types |
| ✓ | overflow_builtin'__builtin_saddl_overflow', Op, T | ⟹ | Op, T = add, long |
| ✓ | overflow_builtin'__builtin_mul_overflow', Op, T | ⟹ | Op, T = mul, generic |
T is the value type of a GNU statement expression with block Items: its last item's, once the items before it have declared their names.
Lvalues, values and designators
| X | in | a typed tree |
| Cat | out | lvalue, function or value, as its principal functor says |
| ✓ | tree_categoryobjint, idx, C | ⟹ | C = lvalue |
| ✓ | tree_categoryvalint, int1, C | ⟹ | C = value |
| 5084 | obj_, _ | lvalue |
| 5085 | fun_, _ | function |
| 5086 | val_, _ | value |
| X | in | a typed tree |
| V | out | X used for its value |
An array becomes a pointer to its first element, a function a pointer to it, and another lvalue the value it holds, unqualified.
| ✓ | valueobjqualifiedconst, int, idc, V | ⟹ | V = valint, loadobjqualifiedconst, int, idc |
| ✓ | valueobjarrayint, 4, ida, V | ⟹ | V = valptrint, decayobjarrayint, 4, ida |
| ✓ | valuefunfuncint, [], fixed, idf, V | ⟹ | V = valptrfuncint, [], fixed, decayfunfuncint, [], fixed, idf |
| ✓ | valuevalint, int7, V | ⟹ | V = valint, int7 |
| T | in | the unqualified integer type of a bit-field |
| W | in | its width in bits |
| P | out | the type of its value |
A bit-field's value is an int if an int can represent all its values, otherwise an unsigned int if that can, otherwise of its own type. C11 says so for _Bool, int and unsigned int; clang and GCC treat bit-fields of the other integer types alike.
| ✓ | bitfield_promotionuint, 31, P | ⟹ | P = int |
| ✓ | bitfield_promotionuint, 32, P | ⟹ | P = uint |
| ✓ | bitfield_promotionint, 32, P | ⟹ | P = int |
| ✓ | bitfield_promotionulong, 32, P | ⟹ | P = uint |
| ✓ | bitfield_promotionulong, 33, P | ⟹ | P = ulong |
| ✓ | bitfield_promotionlong, 33, P | ⟹ | P = long |
| ✓ | bitfield_promotionbool, 1, P | ⟹ | P = int |
| T | in | the type of an expression |
| V | out | the type of its value |
value/2 on types alone.
| ✓ | value_typequalified(volatile), long, V | ⟹ | V = long |
| ✓ | value_typearraychar, 3, V | ⟹ | V = ptrchar |
| ✓ | value_typefuncvoid, unspecified, fixed, V | ⟹ | V = ptrfuncvoid, unspecified, fixed |
| E | in | a C expression |
| Env | in | the scope it is read in |
| T | out | its reflected type, unevaluated |
Arrays and functions keep their types until a caller asks for value conversion.
| ✓ | ⟹ | T = arrayint, 4 |
| E | in | a C expression |
| Env | in | the scope it is read in |
| Cat | out | lvalue, function or value |
| ✓ | ⟹ | C = lvalue | |
| ✓ | ⟹ | C = function | |
| ✓ | ⟹ | C = value |
| E | in | a C expression |
| Env | in | the scope it is read in |
E is an lvalue whose type is not const, an array or incomplete, nor a struct or union with a const member, however deeply contained.
| ✓ | |||
| ✓ | bind_ordinaryc, varnone, int, E0, E1 bind_ordinaryqtypec, qualifiedconst, int, E1, E modifiable_lvalueidc, E | ⟹ | fails |
| ✓ | ⟹ | fails | |
| ✓ | modifiable_lvaluechr0, E | ⟹ | fails |
typed tree X designates a modifiable object.
T is const, or an aggregate or union with a const member or element, at any depth.
Operators
| Op | in | plus, neg, bnot or not |
| T0 | in | the value type of the operand |
| T | out | the type of the result |
+ and - need an arithmetic operand and ~ an integer, which they promote; ! needs a scalar and gives an int.
| ✓ | unary_operandneg, char, T | ⟹ | T = int |
| ✓ | unary_operandplus, ullong, T | ⟹ | T = ullong |
| ✓ | unary_operandnot, ptrint, T | ⟹ | T = int |
| ✓ | unary_operandbnot, double, _ | ⟹ | fails |
| ✓ | unary_operandneg, ptrint, _ | ⟹ | fails |
binary Op on operands of value types TA and TB has type T; && and || give an int (6.5.13-14).
| Op | in | a binary operator other than && and || |
| TA | in | the left operand's value type |
| TB | in | the right operand's value type, one of the two a vector |
| C | out | the vector type both operands convert to |
| T | out | the result's type |
GNU vector operators apply element by element. Both operands are vectors of the same element type and count, or one is a scalar, which converts to the element type and is repeated (an integer scalar for an integer vector). %, &, |, ^, << and >> need integer elements. A comparison gives a vector of signed integers as wide as the elements, each -1 where it holds and 0 where it does not.
| ✓ | vector_operandsadd, vecint, 4, 16, vecint, 4, 16, C, T | ⟹ | C = vecint, 4, 16, T = vecint, 4, 16 |
| ✓ | vector_operandsshl, vecshort, 8, 16, int, C, _ | ⟹ | C = vecshort, 8, 16 |
| ✓ | vector_operandseq, vecfloat, 4, 16, vecfloat, 4, 16, _, T | ⟹ | T = vecint, 4, 16 |
| ✓ | vector_operandslt, vecuchar, 16, 16, vecuchar, 16, 16, _, T | ⟹ | T = vecschar, 16, 16 |
| ✓ | vector_operandsadd, vecint, 4, 16, vecshort, 8, 16, _, _ | ⟹ | fails |
| ✓ | vector_operandsand, vecfloat, 4, 16, vecfloat, 4, 16, _, _ | ⟹ | fails |
| ✓ | vector_operandsadd, vecint, 4, 16, double, _, _ | ⟹ | fails |
| Op | in | a binary operator, not && or || |
| TA | in | the value type of the left operand |
| TB | in | the value type of the right operand |
| CA | out | the type the left operand converts to |
| CB | out | the type the right operand converts to |
| T | out | the type of the result |
A shift promotes each operand on its own and has the left one's type. Other arithmetic operands take their usual arithmetic type, the result's type unless Op compares; %, &, ^ and | need integers. A pointer and an integer add, or the pointer subtracts the integer, as the pointer and a long count of elements; two pointers subtract to a long count. A comparison with a pointer compares both operands as addresses, ulong. Fails if Op does not apply.
| ✓ | binary_operandsshl, char, ullong, CA, CB, T | ⟹ | CA = int, CB = ullong, T = int |
| ✓ | binary_operandsadd, int, uint, CA, CB, T | ⟹ | CA = uint, CB = uint, T = uint |
| ✓ | binary_operandslt, int, double, CA, CB, T | ⟹ | CA = double, CB = double, T = int |
| ✓ | binary_operandsadd, char, ptrint, CA, CB, T | ⟹ | CA = long, CB = ptrint, T = ptrint |
| ✓ | binary_operandssub, ptrint, ptrint, _, _, T | ⟹ | T = long |
| ✓ | binary_operandseq, ptrint, int, CA, CB, T | ⟹ | CA = ulong, CB = ulong, T = int |
| ✓ | binary_operandsmod, double, int, _, _, _ | ⟹ | fails |
| ✓ | binary_operandssub, int, ptrint, _, _, _ | ⟹ | fails |
binary Op applies to two operands of Domain types, arithmetic or integer.
R is complex T's real type, or arithmetic T. complex_side(+T, +R, -C): an operand of type T is converted to C, the complex of the common real type R if T is complex, else R: a real operand stays real (Annex G.5.1), so 2 * z multiplies each part.
| From | in | the unqualified value type of the operand |
| To | in | the type named in the cast |
Anything casts to void, and a scalar to a scalar, but a floating value not to or from a pointer. As in clang and GCC, a struct or union may also be cast to its own type.
A GNU vector casts to another of the same size, reinterpreting its bytes.
| From | in | the value type of the right operand |
| Null | in | null if the right operand is a null pointer constant, else value |
| To | in | the unqualified type of the object assigned |
A value of type From may be assigned to an object of type To, and so passed for a parameter of type To, returned from a function returning To, or initialize a To. Integers and pointers do not mix, but for a null pointer constant or a pointer tested as a bool, and function pointers must be compatible. Clang only warns about the other pointer pairs (incompatible object types, lost qualifiers, object and function pointers), and they are accepted.
GNU vectors assign when their element type and count agree, or, as clang's default -flax-vector-conversions=integer allows, when both hold integers and have the same size (immintrin.h's __m128i from a __v16qi).
| ✓ | assignabledouble, value, char | ||
| ✓ | assignablevecchar, 16, 16, value, vecllong, 2, 1 | ||
| ✓ | assignablevecfloat, 4, 16, value, vecint, 4, 16 | ⟹ | fails |
| ✓ | assignableptrint, value, bool | ||
| ✓ | assignableint, null, ptrint | ||
| ✓ | assignableptrint, value, ptrchar | ||
| ✓ | assignableint, value, ptrint | ⟹ | fails |
| ✓ | assignableptrfuncint, [], fixed, value, ptrfuncvoid, [], fixed | ⟹ | fails |
| ✓ | assignablestruct1, value, struct2 | ⟹ | fails |
as assignable/3; function pointers are compared with compatible_type/3 in Env.
A and B are complex or arithmetic, and one is complex: a value of one converts to the other (6.3.1.7).
| Arms | in | A-B, the second and third operands of ?: |
| Env | in | the scope they are read in |
| T | out | the type of the result |
From the arms' value types; fails if the arms are incompatible or cannot be typed.
| ✓ | ⟹ | T = ptrchar | |
| ✓ | ⟹ | T = ptrchar | |
| ✓ | bind_ordinaryp, varnone, ptrchar, E0, E1 bind_ordinaryd, varnone, double, E1, E conditional_typeidd−idp, E, _ | ⟹ | fails |
A and B, elaborated as XA and XB, used for their values, are ?: arms giving the result type T.
N is arm XV's nullness/3 when the other arm has the pointer type Other, the only case arms_type/5 reads it; else value. Folding every arm made nested ?: (gnulib's intprops) fold each subexpression once per enclosing ?:.
| E | in | an expression that can be typed |
| Env | in | the scope it is read in |
| To | in | the type of the object its value initializes |
| Loc | in | where E is, for the diagnostic |
E's value may be assigned to a To, as a return value or an initializer is; else the error names the two types. Arguments and assignment sources are checked where their calls and assignments elaborate.
value tree XV is a null pointer constant (null) or not (value).
| XV | in | a typed tree, used for its value |
| Env | in | the scope it was elaborated in |
XV is an integer constant expression of value 0, or one cast to void * (unqualified void).
| ✓ | |||
| ✓ | |||
| ✓ | operandcasttypequalconst, tspecvoid, ptrbase, [], chr0, E, XV, _ | ⟹ | fails |
| ✓ | ⟹ | fails | |
| ✓ | bind_ordinaryx, varnone, int, E0, E operandbinmul, idx, chr0, E, XV, _ | ⟹ | fails |
| TA | in | the value type of the second operand of ?: |
| NA | in | null if it is a null pointer constant, else value |
| TB | in | the value type of the third operand |
| NB | in | null if it is a null pointer constant, else value |
| T | out | the type of the result |
Arithmetic arms take their usual arithmetic type, like structs or unions their own. A null pointer constant takes the other arm's pointer type. Pointer arms point to the composite of their targets, qualified with both arms' qualifiers, or to void if either does. As in clang and GCC, a void arm makes the result void whatever the other arm, and incompatible pointer arms give void *. A pointer and an integer that is not a null pointer constant violate 6.5.15p3; clang and GCC only warn, and take the pointer type, converting the integer as a cast would (Ruby's error.c, RBOOL(c ? a == b : f())).
| ✓ | arms_typechar, value, short, value, T | ⟹ | T = int |
| ✓ | arms_typevoid, value, int, value, T | ⟹ | T = void |
| ✓ | arms_typeptrint, value, int, null, T | ⟹ | T = ptrint |
| ✓ | arms_typeint, null, ptrchar, value, T | ⟹ | T = ptrchar |
| ✓ | arms_typeptrqualifiedconst, int, value, ptrvoid, value, T | ⟹ | T = ptrqualifiedconst, void |
| ✓ | arms_typeptrarrayint, none, value, ptrarrayint, 3, value, T | ⟹ | T = ptrarrayint, 3 |
| ✓ | arms_typeptrint, value, ptrlong, value, T | ⟹ | T = ptrvoid |
| ✓ | arms_typeptrint, value, long, value, T | ⟹ | T = ptrint |
| ✓ | arms_typeuchar, value, ptrchar, value, T | ⟹ | T = ptrchar |
| ✓ | arms_typeptrint, value, double, value, _ | ⟹ | fails |
| ✓ | arms_typefloat, value, ptrint, value, _ | ⟹ | fails |
| ✓ | arms_typestruct1, value, struct2, value, _ | ⟹ | fails |
| E | in | the operand of __builtin_constant_p, unevaluated |
| Env | in | the environment the call is in |
| V | out | 1 if clang folds E to a constant, else 0 |
__builtin_constant_p(E) is an integer constant expression of type int, so __builtin_choose_expr and array sizes can test it (gnulib's string-desc.h picks a const or a mutable overload by it, systemd's CONST_MAX and LOG2U a constant form). It is 1 for an arithmetic constant expression and, as clang's evaluator gives it, for what folds to a pointer with no object (an integer constant cast to a pointer) or to the start of a string literal, under any casts, and for an element of a string literal read by * or a constant index within it. Anything else is 0: a variable, even one the program could fold, an address of an object, or a literal plus an offset.
| ✓ | builtin_constant_pbinadd, num3, none−dec, num4, none−dec, E, V | ⟹ | V = 1 |
| ✓ | builtin_constant_pstr"x", E, V | ⟹ | V = 1 |
| ✓ | builtin_constant_pcasttypetspeclong, base, str"x", E, V | ⟹ | V = 1 |
| ✓ | builtin_constant_pcasttypetspecvoid, ptrbase, [], num0, none−dec, E, V | ⟹ | V = 1 |
| ✓ | builtin_constant_pindexstr"ab", num2, none−dec, E, V | ⟹ | V = 1 |
| ✓ | builtin_constant_pindexstr"ab", num3, none−dec, E, V | ⟹ | V = 0 |
| ✓ | builtin_constant_pbinadd, str"ab", num1, none−dec, E, V | ⟹ | V = 0 |
| ✓ | ⟹ | V = 0 | |
| ✓ | ⟹ | V = 1 |
E, under casts, is a string literal or an integer constant: clang's pointer with no object or a literal's start.
E, under casts, is a string literal of N characters before its terminating null.
| T | in | the unqualified type of the operand of __builtin_classify_type |
| N | out | GCC's type class of T after value conversion |
__builtin_classify_type(E) is an integer constant expression of type int, E unevaluated: 0 for void, 1 for integer types (char and, in C, an enum among them), 4 for bool, 5 for pointers, which arrays and functions decay to, 8 for the real floating types, 12 for a struct and 13 for a union (GCC's typeclass.h numbering, as clang's EvaluateBuiltinClassifyType gives it). Other types (complex, vectors) are not classified.
| ✓ | type_class_numberuchar, 1 | ||
| ✓ | type_class_numberbool, 4 | ||
| ✓ | type_class_numberarrayint, 3, 5 | ||
| ✓ | type_class_numberfuncint, [], fixed, 5 | ||
| ✓ | type_class_numberldouble, 8 | ||
| ✓ | type_class_numberunion7, 13 | ||
| ✓ | type_class_numbervoid, 0 | ||
| ✓ | type_class_numberbadx, _ | ⟹ | fails |
| TA | in | a type name |
| TB | in | another |
| Env | in | the scope |
| V | out | 1 if their types are compatible, ignoring top-level qualifiers, else 0 |
GCC's __builtin_types_compatible_p(TA, TB), an integer constant expression of type int, by compatible_type/2 on the reflected types: a typedef is its type, an enum is compatible with its underlying type but not with another enum, and char, signed char and unsigned char are three types, as are long and long long.
| ✓ | types_compatible_valuetypequalconst, tspecint, base, typetspecint, base, E, V | ⟹ | V = 1 |
| ✓ | types_compatible_valuetypetspeclong, base, typetspeclong, tspeclong, base, E, V | ⟹ | V = 0 |
| ✓ | types_compatible_valuetypetspecint, arraybase, none, typetspecint, arraybase, num3, none−dec, E, V | ⟹ | V = 1 |
| ✓ | types_compatible_valuetypetspecint, ptrbase, [], typequalconst, tspecint, ptrbase, [], E, V | ⟹ | V = 0 |
| Name | in | __atomic_always_lock_free, __atomic_is_lock_free or __c11_atomic_is_lock_free |
| Size | in | the constant object size |
| XP | in | the pointer argument's value tree |
| PT | in | its unqualified type |
| Env | in | the scope |
| V | out | 1 or 0, the constant clang folds the call to |
filcc's x86-64 target inlines atomics of up to 16 bytes (cmpxchg16b), so its clang folds as here. A power-of-two Size up to 16 is lock-free if it is 1, the pointer is a null pointer constant, the builtin is __c11_atomic_is_lock_free, or the pointer's complete pointee type is at least Size-aligned. Otherwise __atomic_always_lock_free is 0 and the others are not constant (a libatomic call, which fails here). liburcu asserts _Static_assert(__atomic_always_lock_free(sizeof(*p), 0), ...).
| ✓ | ⟹ | V = 1 | |
| ✓ | ⟹ | V = 1 | |
| ✓ | ⟹ | V = 0 | |
| ✓ | ⟹ | V = 0 |
| Name | in | a builtin's name |
| Args | in | its arguments |
| E | out | the expression it evaluates, for its side effects |
A hint that changes no result: __builtin_assume(C) evaluates nothing, as clang's does not; __builtin_prefetch(P, ...) evaluates P and touches no memory, so a wild address is no access; x86's __builtin_ia32_pause, the spin-wait hint of _mm_pause, does nothing.
| ✓ | hint_builtin'__builtin_assume', idx, num0, none−dec | ||
| ✓ | hint_builtin'__builtin_prefetch', idp, num0, none−dec, idp |
| 5769 | '__builtin_assume' | _ | num0, none−dec |
| 5770 | '__builtin_prefetch' | P_ | P |
| 5771 | '__builtin_ia32_pause' | [] | num0, none−dec |
Generic selection
| ControlType | in | the controlling expression's type after lvalue, array and function conversion (no integer promotion) |
| Associations | in | the associations, assoc(TypeName, E) or assoc(default, E) |
| Env | in | the scope |
| Loc | in | where the _Generic is, for errors |
| Selected | out | the expression of the association whose type is compatible with ControlType, else the default's |
Only Selected is evaluated, and it keeps its value category. Association types must be unique, even those that do not match; a duplicate or no match is an error (sema_error/3).
Diagnoses
When an expression cannot be typed, fault/4 says why: it finds the first operand, in the order lowering evaluates them, that cannot be typed, and the rule it breaks. The messages are lowering's, which the reject tests hold to. Where lowering would first refuse something the elaborator accepts (a type nanofil does not support, a struct or union passed by value, a va_arg or a statement expression), fault/4 declines rather than misplace the blame, and lowering reports.
| E | in | a full expression |
| Env | in | the scope it is read in |
| Loc | in | where it is, for a diagnostic |
| X | out | its typed tree |
elaborate/3, raising the diagnosis at Loc when E cannot be typed. Fails, raising nothing, when fault/4 declines to say why.
| E | in | an expression that cannot be typed |
| Env | in | the scope it is read in |
| Format | out | the diagnostic's format |
| Args | out | its arguments, with types unqualified as lowering has them |
The first fault in E, in evaluation order, or first a type in E that nanofil does not support. Fails when it is not one the elaborator diagnoses.
| ✓ | ⟹ | F = 'undeclared identifier ~w', A = g | |
| ✓ | bind_ordinaryx, varnone, double, E0, E1 bind_ordinaryqtypex, qualifiedconst, double, E1, E faultbinmod, idx, chr2, E, F, A | ⟹ | F = 'invalid operands to ~w: ~w and ~w', A = mod, double, int |
| ✓ | bind_ordinaryp, varnone, ptrint, E0, E1 bind_ordinaryd, varnone, double, E1, E faultcondidg, idp, idd, E, F, A | ⟹ | F = 'incompatible ?: operands ~w and ~w', A = ptrint, double |
| ✓ | ⟹ | F = '-> on a non-pointer' | |
| ✓ | bind_ordinaryc, varnone, int, E0, E1 bind_ordinaryqtypec, qualifiedconst, int, E1, E faultassign=, idc, idg, E, F, A | ⟹ | F = '~w a read-only lvalue', A = 'assignment to' |
| ✓ | bind_ordinaryf, fnfuncvoid, ptrchar, int, fixed, f, extern, E0, E faultcallidf, chr0, chr1, idg, E, F, A | ⟹ | F = 'too many arguments to ~w', A = f |
| ✓ | bind_ordinaryf, fnfuncvoid, ptrchar, int, fixed, f, extern, E0, E faultcallidf, chr1, idg, E, F, A | ⟹ | F = 'cannot assign ~w to ~w', A = int, ptrchar |
| ✓ | ⟹ | F = 'a ~w value is not a scalar', A = struct'builtin.va_list' |
the first fault of ?: arms A and B, lowered first, then whether they go together (6.5.15p3).
E, tested for truth, cannot be typed or is not a scalar.
why assignment Op cannot store a value of type TB into an object of type TA.
lowering names a call's callee by its symbol when Fn names a function, else as a function pointer.
Args are too few or too many for parameters Ps of callee Sym.
the first argument, in order, that cannot be typed or passed for its parameter. Lowering may refuse a struct or union argument, or an unpromotable variadic one, before it reaches a later one, so those end the search.
lowering looks for members in XS, an lvalue or a struct or union value; it refuses other values.
T is E's value type as lowering has it, unqualified.
the first type name in E that names a type nanofil does not support, and why.
the fault of naming Field in SU, which has no such usable member: when SU's definition dropped a member nanofil refuses (a vector one without vectors), the refusal's reason, so x264_union128_sse_t's __m128 i reads as vector types not being supported rather than as a missing member.