From 0c29a9f8d88291322df063cf0ca81d526bdf7577 Mon Sep 17 00:00:00 2001 From: "KOKO\\Mimi" Date: Wed, 14 Oct 2026 23:14:21 +0900 Subject: [PATCH] fix(hex8): bridge nonfinite vector sums before error arithmetic --- src/fesa/elements/hex8_pair_simd_avx2.cpp | 46 ++++++++++----- tests/unit/elements/hex8_high_low_test.cpp | 65 ++++++++++++++++++++++ 2 files changed, 97 insertions(+), 14 deletions(-) diff --git a/src/fesa/elements/hex8_pair_simd_avx2.cpp b/src/fesa/elements/hex8_pair_simd_avx2.cpp index 3c7fd40..570b2a5 100644 --- a/src/fesa/elements/hex8_pair_simd_avx2.cpp +++ b/src/fesa/elements/hex8_pair_simd_avx2.cpp @@ -124,9 +124,14 @@ PairPacket Store(VectorPair value) { return {{{high[0], low[0]}, {high[1], low[1]}, {high[2], low[2]}, {high[3], low[3]}}}; } -/// @brief Retains the original per-lane subtraction/addition association and gates. -VectorPair TwoSum(__m256d left, __m256d right) { +/// @brief Retains finite-sum error association; false requests original graph. +/// @note False leaves output unreadable and preserves the necessary sum flags. +/// Prefix flags still require optimized scalar-oracle/codegen validation. +bool TwoSum(__m256d left, __m256d right, VectorPair* output) { const auto sum = _mm256_add_pd(left, right); + // Do not speculate Inf-Inf error arithmetic beyond a failed first sum. + // The caller retains original inputs for its existing baseline bridge. + if (_mm256_movemask_pd(Finite(sum)) != 15) return false; const auto bridge = _mm256_sub_pd(sum, left); const auto restored = _mm256_sub_pd(sum, bridge); const auto error = _mm256_add_pd(_mm256_sub_pd(left, restored), @@ -134,8 +139,9 @@ VectorPair TwoSum(__m256d left, __m256d right) { const auto valid = _mm256_and_pd(_mm256_and_pd(Finite(sum), Finite(bridge)), _mm256_and_pd(Finite(restored), Finite(error))); const auto exact_zero = _mm256_and_pd(Zero(sum), Zero(error)); - return InvalidOutside({Select(exact_zero, _mm256_setzero_pd(), sum), - Select(exact_zero, _mm256_setzero_pd(), error)}, valid); + *output = InvalidOutside({Select(exact_zero, _mm256_setzero_pd(), sum), + Select(exact_zero, _mm256_setzero_pd(), error)}, valid); + return true; } /// @brief Routes exceptional packets to baseline checked Add in lane order. VectorPair ScalarAdd(VectorPair left, VectorPair right, bool pair_shortcuts) { @@ -167,10 +173,15 @@ VectorPair Add(VectorPair left, VectorPair right, bool pair_shortcuts) { if (left_zero != 0 || right_zero != 0) return ScalarAdd(left, right, true); } - const auto head = TwoSum(left.high, right.high); - const auto tail = TwoSum(left.low, right.low); - const auto middle = TwoSum(head.low, tail.high); - const auto joined = TwoSum(head.high, middle.high); + VectorPair head; + VectorPair tail; + VectorPair middle; + VectorPair joined; + if (!TwoSum(left.high, right.high, &head) || + !TwoSum(left.low, right.low, &tail) || + !TwoSum(head.low, tail.high, &middle) || + !TwoSum(head.high, middle.high, &joined)) + return ScalarAdd(left, right, pair_shortcuts); const auto remainder = _mm256_add_pd( _mm256_add_pd(joined.low, middle.low), tail.low); const auto valid = _mm256_and_pd(Finite(remainder), _mm256_and_pd( @@ -179,9 +190,11 @@ VectorPair Add(VectorPair left, VectorPair right, bool pair_shortcuts) { _mm256_and_pd(Finite(middle.high), Finite(middle.low)), _mm256_and_pd(Finite(joined.high), Finite(joined.low)))))); // Original Add returns before its last TwoSum when a prior finite gate fails. - return InvalidOutside(TwoSum( - Select(valid, joined.high, _mm256_setzero_pd()), - Select(valid, remainder, _mm256_setzero_pd())), valid); + VectorPair result; + if (!TwoSum(Select(valid, joined.high, _mm256_setzero_pd()), + Select(valid, remainder, _mm256_setzero_pd()), &result)) + return ScalarAdd(left, right, pair_shortcuts); + return InvalidOutside(result, valid); } /// @brief Checks finite/zero/tail premises before products; neutralizes rejected /// lanes before the explicit residual FMA and final TwoSum. @@ -211,9 +224,14 @@ VectorPair Product(__m256d left, __m256d right) { const auto error = _mm256_fmsub_pd(Select(product_valid, left, one), Select(product_valid, right, one), Select(product_valid, product, one)); const auto valid = _mm256_and_pd(product_valid, Finite(error)); - return InvalidOutside(TwoSum( - Select(valid, product, _mm256_setzero_pd()), - Select(valid, error, _mm256_setzero_pd())), valid); + VectorPair result; + if (!TwoSum(Select(valid, product, _mm256_setzero_pd()), + Select(valid, error, _mm256_setzero_pd()), &result)) { + PairPacket scalar; + ScalarPacketTwoProduct(a, b, &scalar); + return Load(scalar); + } + return InvalidOutside(result, valid); } /// @brief Uses uniform low-zero branches with original four-product association; /// mixed or invalid packets preserve the baseline checked path. diff --git a/tests/unit/elements/hex8_high_low_test.cpp b/tests/unit/elements/hex8_high_low_test.cpp index 104979d..d1df9e7 100644 --- a/tests/unit/elements/hex8_high_low_test.cpp +++ b/tests/unit/elements/hex8_high_low_test.cpp @@ -496,6 +496,7 @@ fesa::hex8_internal::GeometryPacketInput GeometryDyadicFixture() { // Selected observation reuses the weighted pair but retains each checked Add. void ExpectGeometryPacketBitsAndFlags( const fesa::hex8_internal::GeometryPacketInput& input, bool prior_status) { + SCOPED_TRACE(prior_status); std::fenv_t original; ASSERT_EQ(std::fegetenv(&original), 0); const auto original_csr = _mm_getcsr(); @@ -649,6 +650,7 @@ TEST(Hex8HighLow, GeometryPacketPreservesOrderedOverflowAndPreparedInvalid) { const double maximum = std::numeric_limits::max(); std::size_t checks = 0; for (bool overflow_order : {false, true}) { + SCOPED_TRACE(overflow_order); fesa::hex8_internal::GeometryPacketInput input{}; input.weight = {0.5, 0}; for (std::size_t l = 0; l < 3; ++l) @@ -672,6 +674,69 @@ TEST(Hex8HighLow, GeometryPacketPreservesOrderedOverflowAndPreparedInvalid) { checks += 2; RecordProperty("avx2_geometry_ordered_invalid_checks", static_cast(checks)); } + +TEST(Hex8HighLow, GeometryPacketSumFallbackPreservesNearMaxAndSubnormalStatus) { + if (fesa::hex8_internal::SelectMaterialPacketEvaluator() == + fesa::hex8_internal::EvaluateMaterialPacketScalar) + GTEST_SKIP() << "Geometry AVX2 masked environment is not selected."; + const double maximum = std::numeric_limits::max(); + const double normal = std::ldexp(1.0, -1022); + const double tiny = std::ldexp(1.0, -1074); + const double quarter = std::ldexp(1.0, 969); + std::size_t checks = 0; + for (double tail : {0.0, normal, tiny}) + for (std::size_t bad = 0; bad < 4; ++bad) { + SCOPED_TRACE(tail); + SCOPED_TRACE(bad); + fesa::hex8_internal::GeometryPacketInput input{}; + input.weight = {0.5, 0}; + // Both pairs are normalized. Head sum is finite; middle contributes + // 2^970 and the joined sum overflows. Other lanes retain finite values. + input.gradient_stress[0][0] = {maximum, quarter}; + input.gradient_stress[0][1] = {quarter, tail}; + for (std::size_t lane = 0; lane < 4; ++lane) { + const double scale = lane == bad ? 1.0 : 0.5; + input.gradient_b[0].values[lane] = {scale, 0}; + input.gradient_b[1].values[lane] = {scale, 0}; + input.gradient_b[2].values[lane] = {1, 0}; + } + ExpectGeometryPacketBitsAndFlags(input, false); + ExpectGeometryPacketBitsAndFlags(input, true); + checks += 2; + } + fesa::hex8_internal::GeometryPacketInput finite{}; + finite.weight = {1, 0}; + finite.gradient_stress[0][0] = {normal, 0}; + finite.gradient_stress[0][1] = {normal + tiny, 0}; + for (std::size_t lane = 0; lane < 4; ++lane) { + finite.gradient_b[0].values[lane] = {1, 0}; + finite.gradient_b[1].values[lane] = {1, 0}; + } + // TwoSum(N,N+d) has finite normal inputs and a subnormal error d. + ExpectGeometryPacketBitsAndFlags(finite, false); + ExpectGeometryPacketBitsAndFlags(finite, true); + checks += 2; + // Mixed prefix: finite subnormal-error lanes run before another lane's head + // overflow. The bridge must preserve the whole packet's status union. + for (std::size_t bad = 0; bad < 4; ++bad) { + SCOPED_TRACE(bad); + fesa::hex8_internal::GeometryPacketInput mixed{}; + mixed.weight = {1, 0}; + mixed.gradient_stress[0][2] = {normal, 0}; + mixed.gradient_stress[1][2] = {normal + tiny, 0}; + mixed.gradient_stress[2][0] = {maximum, 0}; + mixed.gradient_stress[2][1] = {maximum, 0}; + for (std::size_t lane = 0; lane < 4; ++lane) { + mixed.gradient_b[0].values[lane] = {lane == bad ? 1.0 : 0.0, 0}; + mixed.gradient_b[1].values[lane] = {lane == bad ? 1.0 : 0.0, 0}; + mixed.gradient_b[2].values[lane] = {lane == bad ? 0.0 : 1.0, 0}; + } + ExpectGeometryPacketBitsAndFlags(mixed, false); + ExpectGeometryPacketBitsAndFlags(mixed, true); + checks += 2; + } + RecordProperty("avx2_geometry_sum_fallback_checks", static_cast(checks)); +} #endif TEST(Hex8HighLow, PacketBackendSelectionRecoversAfterEnvironmentRestoration) {