v0.2.7
Pre-releaseAdded
-
The constant evaluator in
rucc-sema, which folds a checked expression to a value and is what a case label, an enumerator, a bit-field width, an array bound, a static initializer and aconstexprobject are each going to ask. It runs over the typed tree rather than the source tree, which is the whole reason it is short: the conversions are already nodes, the types are already decided, and the usual arithmetic conversions have already happened, so an addition here is an addition and not a re-derivation of what an addition means. The result is three-valued rather than two, because "not a constant" and "a constant that does something the language does not define" are different answers and only the first one is allowed to be silent. That is what letssizeof(int[1/0])stay quiet in a context where constancy is optional whileint a[1/0];is a diagnostic, and it is why the failure carries a flag saying whether the expression was already the subject of a diagnostic: an operand that is poisoned is not a constant, and saying so a second time is noise. Integer arithmetic is performed at the target's widths and not the host's, which for a_BitInt(37)is a width no host has. Signed overflow is computed twice on purpose, once exactly and once wrapping, so that the value handed back is the one the target would produce and the warning is decided by whether the exact answer fits, which is the only way to tell2147483647 + 1from an addition that merely came out large. Unsigned arithmetic wraps and never warns, because that is defined and warning about defined behaviour trains people to ignore warnings. The pair that has to be special-cased isINT_MIN / -1andINT_MIN % -1, since the quotient is not representable and the machine instruction traps rather than wrapping. Division by zero, a negative shift count and a shift count at or beyond the width each get a diagnostic with gcc 13.3's wording, measured rather than recalled, and the over-wide shift still folds, to zero or to minus one depending on whether an arithmetic right shift is being asked to keep a sign. Floating arithmetic goes through the software implementation added in 0.2.6 at the target's format, so folding is the same on every host.&&and||stop at the operand that settles them and a conditional folds only the arm it takes, which is what makesp != 0 && *pa constant zero rather than a dereference. Address constants are not here yet, since&xneeds a declaration to point at. -
The
-Woverflowwarning, which is the first thing to use the folding: an assignment or an initialization whose value will not fit in the type it is being stored in now says what the value was and what it becomes. The rule is subtler than it looks and is measured rather than recalled. gcc is silent forsigned char sc = 200;and forunsigned char uc = -1;and loud forunsigned char uc = 300;, so the test is not whether the value fits the target type but whether it fits the target's width in either signedness, which is a way of saying that reinterpreting a bit pattern is deliberate and losing bits is not. A cast is silent throughout, since(char)300is a request to truncate, so the evaluator itself says nothing about a conversion and the checker asks the question only where it is holding an assignment. Aboolis excluded, because everything that is not zero is one and no bits are lost by saying so. The value in the message is printed in hexadecimal when it is a floating one, which is not what gcc does and is what an honest compiler does until it has a shortest round trip printer, since a decimal spelling produced without one is a different number than the one being warned about. -
The type builder in
rucc-sema, which turns a specifier list and a declarator into a type and is what every declaration and every expression that names a type has been waiting for. The two halves of a C declaration are read in opposite directions, so the specifiers are a set and the declarator is a sequence folded from its far end onto what the set named, which is whyint (*f[3])(char)is an array of pointers to functions and not a function returning an array of pointers. The interesting part is not the fold, it is what a declarator is allowed to say and where. An array ofvoid, an array of functions, an array of a tag that has no definition yet, a function returning a function and a function returning an array are each refused with gcc 13.3's wording, in both the named and the abstract phrasing, sincedeclaration of 'a' as array of voidsanddeclaration of type name as array of voidsare the same rule about two different pieces of source and a compiler that only writes one of them has a message it cannot produce. The bracket qualifiers ofint a[static const 3]belong to the pointer the parameter becomes rather than to the array, and they are legal only on the step of the declarator nearest the name of a parameter, so the same brackets on an ordinary declaration are an error.[*]is a type only inside a prototype. A bound that folds to a constant is a fixed array, a bound that does not is a variable length array, which is a type and not a mistake except at file scope where there is no run time to evaluate it in, and two arrays written the same way are still two types because the two bounds are evaluated at two different moments and may not agree. A bound too large is measured in the element type rather than in the count, since it issizeofthat overflows and not the number in the brackets. A prototype is a scope of its own, which is what makes theninvoid f(int n, int a[n])mean the parameter and what makes it gone by the next declaration, and an empty parameter list says nothing about the parameters before C23 and says there are none from C23 onwards, which is visible in every call. Tags are looked up and declared here, sostruct S;andstruct S *p;build the type they should and both mentions are the same type, a tag that already means another kind of thing keeps meaning it rather than being rebound and turning one diagnostic into one per use, and C23'senum E : longis complete from the point it says so._Atomicis a type constructor and a qualifier in two different spellings that mean the same thing in the end, and it is refused on an array, on a function and on something already qualified._BitInt(N)has its width folded and range checked against clang, since gcc 13.3 does not have the type to be measured against. -
The members of a
structor aunionand the enumerators of anenuminrucc-sema, which is what completes the type builder and what every declaration of a real program is written against. A definition binds its tag before it reads its members, which is the whole reasonstruct S { struct S *next; };points at the type it is a member of rather than declaring a second one inside it, and it asks whether the tag is already bound in this scope rather than anywhere visible, because that is the difference between a definition of something new and a completion of the forward declaration above it. A second body for a tag that already has one is a redefinition and is reported, and the members of the refused one are still read and still laid out, so that one mistake is one diagnostic rather than one per member of it. The members themselves are where the constraints live. A member ofvoid, a member that is a function and a member of a type with no definition yet are each refused with gcc 13.3's wording, measured rather than recalled, the rest of the record is laid out around the hole, and a member declared twice is reported once and left out rather than laid out twice. A member with no declarator is an anonymous member when its specifiers are a record with a body and is a declaration that declares nothing otherwise, which is a warning and not an error because that is what gcc does withint;and there is no harm in it. A bit-field has its width folded here, since a width is an integer constant expression and the folding was the piece before this one, and it is measured against the value bits of its own type rather than against its size, so a_Bool b : 2is too wide while a_Bool b : 1is not. A width that is too wide is kept at the width its type has rather than dropped, which is where gcc leaves it and what keeps every member after it at the offset the program meant. A zero width with a name has nothing to name and an unnamed one moves the next member to the next boundary, which is what it is for. The flexible array member rules are a pass over the members after they are collected rather than a test on each one, because they are about where a member is and not about what it is: last, in astruct, and with something named before it. What an enumeration is represented in is C23's rule and was measured against gcc rather than read off the standard's wording, since the standard says an implementation defined type that holds every value and gcc's choice is the first ofunsigned int,int,unsigned long,long,unsigned long longandlong longthat does, which is not the order anyone guesses. The enumerator's own type is a separate question with a separate answer: it isintwherever the value fits in one, whatever the enumeration turned out to be kept in, soenum { A = 0xffffffff, B = 1 };has aBof typeintinside an enumeration of typeunsigned int. An enumerator with no value is one more than the one before it, and the one after the greatest value the type has is an overflow and says so rather than wrapping quietly. An underlying type the program wrote is a constraint rather than a suggestion, so an enumerator outside its range is an error and every enumerator of that enumeration has that type whether or not it would have fitted in anint.
Changed
rucc-typesanswers two new questions that folding needs and layout did not.integer_infogives the signedness and the value width of an integer type, which is not the same as its size for aboolor a_BitIntand is what wrapping a folded value depends on, and it sees through the sugar, the qualifiers,_Atomicand an enumeration's underlying type so that the caller does not have to.float_formatgives the format a floating type is computed in, which the size does not determine:long doubleis sixteen bytes on SysV x86-64 and eighty bits of x87 inside them, and a compiler that picked the format by the size would fold every one of those constants too finely.
What's Changed
- sema: fold constant expressions by @tamnd in #77
- sema: build a type from a specifier list and a declarator by @tamnd in #78
- sema: lay out record members and check enumerator lists by @tamnd in #79
- release: 0.2.7 by @tamnd in #80
Full Changelog: v0.2.6...v0.2.7