diff --git a/stage0/astgen.c b/stage0/astgen.c index 44bd5b7c17..441cfa683d 100644 --- a/stage0/astgen.c +++ b/stage0/astgen.c @@ -3385,15 +3385,68 @@ static bool hex_float_fits_f64(const char* buf) { return total_sig_bits <= available_bits; } +// 128-bit multiply: (a_hi:a_lo) * b -> (r_hi:r_lo), returns true on overflow. +static bool mul128x64(uint64_t a_hi, uint64_t a_lo, uint64_t b, + uint64_t* r_hi, uint64_t* r_lo) { + // Compute a_lo * b as 128-bit result using mul64. + uint64_t lo_hi, lo_lo; + mul64(a_lo, b, &lo_hi, &lo_lo); + // Compute a_hi * b; if a_hi != 0 and result overflows 64 bits, overflow. + uint64_t hi_prod = 0; + if (a_hi != 0) { + if (b != 0 && a_hi > UINT64_MAX / b) + return true; // a_hi * b overflows uint64_t + hi_prod = a_hi * b; + } + // Combine: result = hi_prod * 2^64 + lo_hi * 2^64 + lo_lo + // = (hi_prod + lo_hi) * 2^64 + lo_lo + uint64_t sum_hi = hi_prod + lo_hi; + if (sum_hi < hi_prod) + return true; // overflow + *r_hi = sum_hi; + *r_lo = lo_lo; + return false; +} + +// 128-bit divide: (a_hi:a_lo) / b -> (q_hi:q_lo), remainder in *rem. +static void div128x64(uint64_t a_hi, uint64_t a_lo, uint64_t b, + uint64_t* q_hi, uint64_t* q_lo, uint64_t* rem) { + // High part division. + *q_hi = a_hi / b; + uint64_t r = a_hi % b; + // Now divide (r * 2^64 + a_lo) by b, where r < b. + // Bit-by-bit long division for the low 64-bit quotient. + uint64_t quot = 0; + uint64_t remainder = r; + for (int i = 63; i >= 0; i--) { + uint64_t bit = (a_lo >> i) & 1; + bool will_overflow = (remainder >> 63) != 0; + remainder = (remainder << 1) | bit; + if (will_overflow || remainder >= b) { + remainder -= b; + quot |= (uint64_t)1 << i; + } + } + *q_lo = quot; + *rem = remainder; +} + // Returns true if the decimal float string round-trips through f64. // A decimal value m * 10^e round-trips iff m * 5^e fits in 53 binary bits // (for e >= 0) or m is divisible by 5^|e| and the quotient fits in 53 bits // (for e < 0). Hex floats are handled separately by the caller. +// +// Upstream Zig parses as f128 and checks the f64 round-trip at f128 precision. +// Since C (especially tcc) lacks f128 support, we use 128-bit integer +// arithmetic for the algebraic round-trip test. This handles mantissas up to +// ~38 significant digits. For the rare case of >38 digits (e.g. 2^128 written +// in decimal), we fall back to a strtold heuristic. static bool decimal_float_fits_f64(const char* buf) { - uint64_t m = 0; + // Parse mantissa into 128-bit integer (m_hi:m_lo). + uint64_t m_hi = 0, m_lo = 0; int e = 0; bool seen_dot = false; - bool m_overflow = false; + bool m_overflow = false; // true if mantissa overflows 128 bits const char* p = buf; // Skip sign. @@ -3408,11 +3461,20 @@ static bool decimal_float_fits_f64(const char* buf) { } if (!m_overflow) { uint64_t digit = (uint64_t)(*p - '0'); - uint64_t next = m * 10 + digit; - if (next / 10 != m) { + uint64_t new_hi, new_lo; + if (mul128x64(m_hi, m_lo, 10, &new_hi, &new_lo)) { m_overflow = true; } else { - m = next; + // Add digit. + uint64_t sum_lo = new_lo + digit; + uint64_t carry = (sum_lo < new_lo) ? 1 : 0; + uint64_t sum_hi = new_hi + carry; + if (sum_hi < new_hi) { + m_overflow = true; + } else { + m_hi = sum_hi; + m_lo = sum_lo; + } } } if (seen_dot) @@ -3439,35 +3501,33 @@ static bool decimal_float_fits_f64(const char* buf) { } // Strip trailing zeros from mantissa. - while (m > 0 && m % 10 == 0) { - m /= 10; + while (m_hi != 0 || m_lo != 0) { + uint64_t q_hi, q_lo, rem; + div128x64(m_hi, m_lo, 10, &q_hi, &q_lo, &rem); + if (rem != 0) + break; + m_hi = q_hi; + m_lo = q_lo; e++; } - if (m == 0) + if (m_hi == 0 && m_lo == 0) return true; if (m_overflow) { - // Mantissa overflows uint64_t (> 19 digits). We can't use the - // exact algebraic test, so check the f64 value's significant bits. - // Values with few significant bits (e.g. 2^113) are very likely - // exact matches, confirmed by strtold. Values with many significant - // bits (e.g. 5.999...e-01 with 21 digits) conservatively get f128. + // Mantissa overflows 128-bit integer (> ~38 digits). This is rare. + // Fall back to strtold heuristic for values like 2^128 in decimal. double d = strtod(buf, NULL); if (d == 0.0) - return false; // non-zero value underflows in f64 - int exp; - double frac = frexp(fabs(d), &exp); + return false; + int fexp; + double frac = frexp(fabs(d), &fexp); uint64_t sig = (uint64_t)ldexp(frac, 53); - // Count trailing zero bits in the f64 significand. uint32_t trailing = 0; uint64_t s = sig; while (s > 0 && (s & 1) == 0) { s >>= 1; trailing++; } - // If the f64 value has ≤ 43 significant bits (≥ 10 trailing zeros), - // the decimal string almost certainly represents this exact value. - // Confirm with strtold comparison. if (trailing >= 10) { long double ld = strtold(buf, NULL); return ((long double)d == ld); @@ -3479,29 +3539,37 @@ static bool decimal_float_fits_f64(const char* buf) { // odd part of m contributes to the significand bit count. Powers of 2 // just shift the binary exponent and don't affect whether the value // fits in f64's 53-bit significand. - while (m > 0 && (m & 1) == 0) - m >>= 1; + while ((m_hi != 0 || m_lo != 0) && (m_lo & 1) == 0) { + m_lo = (m_lo >> 1) | (m_hi << 63); + m_hi >>= 1; + } if (e >= 0) { if (e > 23) return false; - uint64_t hi, lo; - mul64(m, pow5_table[e], &hi, &lo); - if (hi != 0) + uint64_t r_hi, r_lo; + if (mul128x64(m_hi, m_lo, pow5_table[e], &r_hi, &r_lo)) return false; - return lo < ((uint64_t)1 << 53); + if (r_hi != 0) + return false; + return r_lo < ((uint64_t)1 << 53); } else { int ae = -e; if (ae > 27) return false; uint64_t div = pow5_table[ae]; - if (m % div != 0) + uint64_t q_hi, q_lo, rem; + div128x64(m_hi, m_lo, div, &q_hi, &q_lo, &rem); + if (rem != 0) return false; - uint64_t quotient = m / div; // Factor out any remaining powers of 2 from the quotient. - while (quotient > 0 && (quotient & 1) == 0) - quotient >>= 1; - return quotient < ((uint64_t)1 << 53); + while ((q_hi != 0 || q_lo != 0) && (q_lo & 1) == 0) { + q_lo = (q_lo >> 1) | (q_hi << 63); + q_hi >>= 1; + } + if (q_hi != 0) + return false; + return q_lo < ((uint64_t)1 << 53); } } diff --git a/stage0/stages_test.zig b/stage0/stages_test.zig index c33053ebd9..888d3e225a 100644 --- a/stage0/stages_test.zig +++ b/stage0/stages_test.zig @@ -302,12 +302,12 @@ const corpus_files = .{ "../lib/compiler_rt/int_from_float_test.zig", "../lib/compiler_rt/int_from_float.zig", "../lib/compiler_rt/int.zig", - //"../lib/compiler_rt/log10.zig", - //"../lib/compiler_rt/log2.zig", - //"../lib/compiler_rt/log.zig", + "../lib/compiler_rt/log10.zig", + "../lib/compiler_rt/log2.zig", + "../lib/compiler_rt/log.zig", "../lib/compiler_rt/memcmp.zig", "../lib/compiler_rt/memcpy.zig", - //"../lib/compiler_rt/memmove.zig", + "../lib/compiler_rt/memmove.zig", "../lib/compiler_rt/memset.zig", "../lib/compiler_rt/modti3_test.zig", "../lib/compiler_rt/modti3.zig", @@ -316,7 +316,7 @@ const corpus_files = .{ "../lib/compiler_rt/muldc3.zig", "../lib/compiler_rt/muldf3.zig", "../lib/compiler_rt/mulf3_test.zig", - //"../lib/compiler_rt/mulf3.zig", + "../lib/compiler_rt/mulf3.zig", "../lib/compiler_rt/mulhc3.zig", "../lib/compiler_rt/mulhf3.zig", "../lib/compiler_rt/mulodi4_test.zig", @@ -356,9 +356,9 @@ const corpus_files = .{ "../lib/compiler_rt/popcount.zig", "../lib/compiler_rt/powiXf2_test.zig", "../lib/compiler_rt/powiXf2.zig", - //"../lib/compiler_rt/rem_pio2f.zig", - //"../lib/compiler_rt/rem_pio2_large.zig", - //"../lib/compiler_rt/rem_pio2.zig", + "../lib/compiler_rt/rem_pio2f.zig", + "../lib/compiler_rt/rem_pio2_large.zig", + "../lib/compiler_rt/rem_pio2.zig", "../lib/compiler_rt/round.zig", "../lib/compiler_rt/shift_test.zig", "../lib/compiler_rt/shift.zig", @@ -397,11 +397,11 @@ const corpus_files = .{ "../lib/compiler_rt/ucmpsi2_test.zig", "../lib/compiler_rt/ucmpti2_test.zig", "../lib/compiler_rt/udivmoddi4_test.zig", - //"../lib/compiler_rt/udivmodei4.zig", + "../lib/compiler_rt/udivmodei4.zig", "../lib/compiler_rt/udivmodsi4_test.zig", "../lib/compiler_rt/udivmodti4_test.zig", "../lib/compiler_rt/udivmodti4.zig", - //"../lib/compiler_rt/udivmod.zig", + "../lib/compiler_rt/udivmod.zig", "../lib/compiler_rt/udivti3.zig", "../lib/compiler_rt/umodti3.zig", "../lib/compiler_rt/unorddf2.zig", @@ -475,7 +475,7 @@ const corpus_files = .{ "../lib/std/coff.zig", "../lib/std/compress/flate/BlockWriter.zig", "../lib/std/compress/flate/Compress.zig", - //"../lib/std/compress/flate/Decompress.zig", + "../lib/std/compress/flate/Decompress.zig", "../lib/std/compress/flate/HuffmanEncoder.zig", "../lib/std/compress/flate/Lookup.zig", "../lib/std/compress/flate/Token.zig", @@ -500,7 +500,7 @@ const corpus_files = .{ "../lib/std/crypto/25519/edwards25519.zig", "../lib/std/crypto/25519/field.zig", "../lib/std/crypto/25519/ristretto255.zig", - //"../lib/std/crypto/25519/scalar.zig", + "../lib/std/crypto/25519/scalar.zig", "../lib/std/crypto/25519/x25519.zig", "../lib/std/crypto/aegis.zig", "../lib/std/crypto/aes/aesni.zig", @@ -531,7 +531,7 @@ const corpus_files = .{ "../lib/std/crypto/codecs.zig", "../lib/std/crypto/ecdsa.zig", "../lib/std/crypto/errors.zig", - //"../lib/std/crypto/ff.zig", + "../lib/std/crypto/ff.zig", "../lib/std/crypto/ghash_polyval.zig", "../lib/std/crypto/hash_composition.zig", "../lib/std/crypto/hkdf.zig", @@ -544,19 +544,19 @@ const corpus_files = .{ "../lib/std/crypto/pbkdf2.zig", "../lib/std/crypto/pcurves/common.zig", "../lib/std/crypto/pcurves/p256/field.zig", - //"../lib/std/crypto/pcurves/p256/p256_64.zig", - //"../lib/std/crypto/pcurves/p256/p256_scalar_64.zig", + "../lib/std/crypto/pcurves/p256/p256_64.zig", + "../lib/std/crypto/pcurves/p256/p256_scalar_64.zig", "../lib/std/crypto/pcurves/p256/scalar.zig", "../lib/std/crypto/pcurves/p256.zig", "../lib/std/crypto/pcurves/p384/field.zig", - //"../lib/std/crypto/pcurves/p384/p384_64.zig", - //"../lib/std/crypto/pcurves/p384/p384_scalar_64.zig", + "../lib/std/crypto/pcurves/p384/p384_64.zig", + "../lib/std/crypto/pcurves/p384/p384_scalar_64.zig", "../lib/std/crypto/pcurves/p384/scalar.zig", "../lib/std/crypto/pcurves/p384.zig", "../lib/std/crypto/pcurves/secp256k1/field.zig", "../lib/std/crypto/pcurves/secp256k1/scalar.zig", - //"../lib/std/crypto/pcurves/secp256k1/secp256k1_64.zig", - //"../lib/std/crypto/pcurves/secp256k1/secp256k1_scalar_64.zig", + "../lib/std/crypto/pcurves/secp256k1/secp256k1_64.zig", + "../lib/std/crypto/pcurves/secp256k1/secp256k1_scalar_64.zig", "../lib/std/crypto/pcurves/secp256k1.zig", "../lib/std/crypto/pcurves/tests/p256.zig", "../lib/std/crypto/pcurves/tests/p384.zig", @@ -566,8 +566,8 @@ const corpus_files = .{ "../lib/std/crypto/salsa20.zig", "../lib/std/crypto/scrypt.zig", "../lib/std/crypto/Sha1.zig", - //"../lib/std/crypto/sha2.zig", - //"../lib/std/crypto/sha3.zig", + "../lib/std/crypto/sha2.zig", + "../lib/std/crypto/sha3.zig", "../lib/std/crypto/siphash.zig", "../lib/std/crypto/test.zig", "../lib/std/crypto/timing_safe.zig", @@ -623,7 +623,7 @@ const corpus_files = .{ "../lib/std/fs/test.zig", "../lib/std/fs/wasi.zig", "../lib/std/fs.zig", - //"../lib/std/gpu.zig", + "../lib/std/gpu.zig", "../lib/std/hash/Adler32.zig", "../lib/std/hash/auto_hash.zig", "../lib/std/hash/benchmark.zig", @@ -902,7 +902,7 @@ const corpus_files = .{ "../lib/std/tar/Writer.zig", "../lib/std/tar.zig", "../lib/std/testing/FailingAllocator.zig", - //"../lib/std/testing.zig", + "../lib/std/testing.zig", "../lib/std/Thread/Condition.zig", "../lib/std/Thread/Futex.zig", "../lib/std/Thread/Mutex/Recursive.zig",