Skip to content

v0.2.6

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 01 Sep 00:53
· 25 commits to main since this release
v0.2.6
d3d1abd

Added

  • Floating point arithmetic in rucc-base, in software, so that folding a constant gives the same bits whoever compiles the compiler and whatever machine it runs on. The conversion from text was already here and is half of what a constant needs, and this is the other half: addition, subtraction, multiplication, division, comparison, conversion between the formats, and conversion to and from an integer, each of them correctly rounded to nearest with ties to even. Asking the host to do this instead is wrong in three separate ways, since the host may not have the format at all, its long double is eighty bits or a hundred and twenty eight or sixty four depending on where you ask, and a compiler whose output depends on the machine it ran on is not one you can reproduce a build with. Every operation computes the exact answer to more bits than the format has and rounds once, which is what correctly rounded means and what a second rounding in the middle would quietly destroy. The one that is not obvious is the subtraction: what gets shifted out of the smaller operand belongs to the number being taken away, so the answer is a little below what the bits that are left say it is, and taking one more off with the sticky bit set says exactly that without needing the bits themselves. A nan is a category now rather than something the encoding could hold and nothing could make, with the operations that have no answer producing one and saying so, and the difference between a division that has no answer and one whose answer is merely too large to be a number is two flags rather than one. The correctness argument is not the reasoning above, it is a hundred and twenty thousand random operations per run checked bit for bit against the host's own double and float, including the infinities, the nans and the subnormals, with the integer conversions checked against Rust's own saturating cast, which happens to fill in C's undefined behaviour the same way this does. The wide formats have no host to check against, so those are worked out by hand and asserted: a third in binary128, three of them adding back up to exactly one because the tie rounds up, and 2049 in half precision rounding down to 2048 because that tie rounds the other way.

  • The expression checking in rucc-sema, which is the first pass that puts nodes in the typed tree rather than defining what a node is or what a rule says. Every operator of 6.5 is checked except the ones that name a type, and each of them is the same three steps in the same order: check the operands, decide whether the types they turned out to have are ones the operator accepts, and write the conversions the operator performs before writing the operator itself. Nothing in it writes a conversion by hand, so the rules stay in the one place that already knew them. The cases worth calling out are the ones where the obvious implementation is wrong. A compound assignment is not a = a op b with the conversions left out: int i = 5; i /= 0.5; divides in double and gives ten, and a compiler that converts the right side to the left side's type first divides by zero instead, so the node now carries the type the operation is performed in, which is what clang calls the computation type and for the same reason. A shift takes the promoted type of its left operand and not the usual arithmetic conversions, so 1 << 1L is an int, which is the classic version of this bug. An argument beyond a prototype takes the default argument promotions, so a float passed to a variadic function widens to double, and a compiler that forgets it passes four bytes where va_arg reads eight. &a on an array is a pointer to the array rather than to its first element, which falls out of the address operator being the one place that does not ask for a value. And a member reached through an anonymous member is a chain of nodes rather than one, with the record's own names beating the ones inside it, because a single walk in declaration order lets an anonymous member's x hide the record's. The diagnostics are gcc 13.3's wording, measured rather than recalled, because the message is what a person building a real project sees and a build script that greps for incompatible pointer type is a real thing. What is deliberately not copied is the [-Wsomething] suffix, since that is the renderer naming an option this compiler does not have yet. Poisoning is the parser's rule and works the same way: an expression that has been diagnosed becomes an error node, an operator whose operand is one is poisoned in turn without a word said, and that is what stops one undeclared name producing an error for every operator it appears under.

  • The diagnostic sink moved out of rucc-parse and into rucc-diag as Errors, because semantic analysis needs exactly the same three things the parser needed, which are somewhere to put the diagnostics, a count of the ones that are errors, and a point at which the pass stops. The error limit is one number for the whole compiler rather than one per pass that happens to reach it, and it stays twenty, which is clang's measured default and the better of the two: gcc has no limit at all and will print every error a broken file produces, and the errors after the twentieth are almost always consequences of the ones before them.

What's Changed

  • sema: check expressions by @tamnd in #74
  • base: floating point arithmetic in software by @tamnd in #75
  • release: 0.2.6 by @tamnd in #76

Full Changelog: v0.2.5...v0.2.6