fix(hex8): bridge nonfinite vector sums before error arithmetic

This commit is contained in:
KOKO\Mimi
2026-10-14 23:14:21 +09:00
parent 96772651d8
commit 0c29a9f8d8
2 changed files with 97 additions and 14 deletions
+31 -13
View File
@@ -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),
*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.
@@ -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<double>::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<int>(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<double>::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<int>(checks));
}
#endif
TEST(Hex8HighLow, PacketBackendSelectionRecoversAfterEnvironmentRestoration) {