Skip to content

v0.2.1

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 31 Aug 18:46
· 51 commits to main since this release
v0.2.1
7f74322

Added

  • The C type universe in rucc-types, interned, so that two spellings of the same type are the same four byte id and type equality is an integer comparison rather than a structural walk. A type keeps its sugar: a typedef node remembers the name it was written as, and its canonical form is stored beside it, so a diagnostic can print size_t where the user wrote size_t while the rules that need the type behind it ask for the canonical id and get an answer without walking anything. Canonicalisation reaches below the outermost node, so a pointer to a typedef of an array canonicalises to a pointer to the array, and a const on a typedef of an array lands on the element the way the standard says it does.

  • _Atomic is a type rather than a qualifier, which is the shape the standard describes and also the only shape that gets the layout right. A sixteen byte structure is aligned to eight and _Atomic of it is aligned to sixteen, so a type system that treated the two as one type with a flag set would disagree with itself about where the object goes.

  • Layout computation driven by the target description rather than by the host, for every type except records. long is four bytes on Windows and eight on Linux, long double is eight bytes on Apple and sixteen on SysV x86-64, and a _Complex is two of its component with the component's own alignment. _BitInt is laid out like the smallest standard integer until it outgrows one and then like an array of a granule, and the granule is 64 bits on x86-64 and RISC-V and 128 on AArch64, which is a new field in the target description rather than a #[cfg]. Every one of these numbers was measured against GCC 13 on x86-64 Linux and clang on AArch64 Darwin rather than recalled. A type with no size says which kind of no size it has, because an incomplete type, a function type and an array too large for the address space are three different diagnostics.

  • Record layout in rucc-types: member offsets, bit-field packing, zero width bit-fields, packed, #pragma pack, aligned on a member and on the record, anonymous members and flexible array members. Whoever walks the members hands them to layout_record and gets back an offset for each one, in bits, so a bit-field and an ordinary member are described the same way. Every rule was measured against gcc 13.3 on x86-64 Linux and clang on AArch64 Darwin over about fifty structures, reading the bit positions back out of the compiled program rather than trusting the sizes, and the two compilers agreed on every case except where long double differs, which is a fact about the member and not about the record. Two of the rules are not what the documents suggest: a bit-field whose alignment has been capped by #pragma pack stays where it is rather than moving to the capped boundary, and an unnamed bit-field occupies its bits without giving the record its type's alignment, so struct { char c; int :20; } is four bytes aligned to one while the same structure with the field named is four bytes aligned to four.

  • The integer promotions and the usual arithmetic conversions in rucc-types, which decide what type an arithmetic expression has. The answers were read out of gcc 13.3 and clang 18 with _Generic naming the type of every interesting pair rather than derived from the standard, and the standard was then used to explain what was measured. All three of the C23 changes are in: bool is a real type, an enumeration promotes through whatever it is represented in rather than through a type the implementation picked, and _BitInt does not promote at all, so _BitInt(8) + _BitInt(8) stays eight bits wide where char + char is an int. A _BitInt is ranked against the standard types by its width, so a _BitInt(40) outranks an int and loses to a long, and a standard type wins a tie at equal width. Bit-fields are promoted by their width rather than by the type they were declared with, and a bit-field wider than an int keeps its declared type, which is what both compilers do and what the older wording would have got wrong by eight bits.

  • Type compatibility and the composite type in rucc-types, which is the relation declaration merging is stated in: whether two declarations of one name are talking about the same thing, and what type is left when they are. It is looser than identity on purpose, so int f(int a[3]) and int f(int *a) are different types and the same function, an enumeration is compatible with whatever it is represented in, and an array with a size is compatible with one without. The composite is the type that knows both halves, so extern int a[]; int a[4]; ends up as an array of four and void f(); void f(int); ends up as the prototype, which is what lets the calls written in between be checked against something. The rule for an old style declaration against a prototype is the one gcc states in its own diagnostic, that an argument type with a default promotion cannot match an empty parameter name list, so void f(int); void f(); merges and void f(float); void f(); conflicts. Records use the C23 rule that the same tag with the same members is the same type, with a guard so that a self referential structure compared against another declaration of itself terminates instead of going round forever. Two divergences are recorded rather than resolved: clang 18 implements that C23 record rule and gcc 13.3 still rejects the redefinition outright, and clang treats an empty parameter list as a prototype even in C17 mode where gcc keeps the old meaning.

Fixed

  • The __has_* operators answer in ordinary text and not only inside a #if. GCC and clang both implement them as builtin macros, so a header may write #define HAVE_COLD __has_attribute(cold) and then use HAVE_COLD in a declaration, and until now that carried the unexpanded call to the use site instead of the answer. The three whose operand is a header name, __has_include, __has_include_next and __has_embed, are refused outside a directive rather than answered, because by then the line has been scanned as ordinary tokens and <stdio.h> is a run of comparisons with no header name left in it. Both compilers make that an error too.

What's Changed

  • Answer the _has* operators in ordinary text by @tamnd in #49
  • Add the interned type universe and layout to rucc-types by @tamnd in #50
  • Lay out records, bit-fields included by @tamnd in #51
  • Add the integer promotions and the usual arithmetic conversions by @tamnd in #52
  • Add type compatibility and the composite type by @tamnd in #53
  • Release 0.2.1 by @tamnd in #54

Full Changelog: v0.2.0...v0.2.1