diff --git a/tests/unit/elements/hex8_high_low_test.cpp b/tests/unit/elements/hex8_high_low_test.cpp index 8c9bc9e..26261c3 100644 --- a/tests/unit/elements/hex8_high_low_test.cpp +++ b/tests/unit/elements/hex8_high_low_test.cpp @@ -142,7 +142,9 @@ TEST(Hex8HighLow, FourColumnMaterialPacketMatchesFrozenScalarBits) { {std::numeric_limits::min()}, {maximum}, {-maximum}, {1, tiny}, {1, -tiny}, {std::nextafter(1.0, 2.0)}, {1, 1}, {0, tiny}, {std::numeric_limits::infinity(), 0}, - {std::numeric_limits::quiet_NaN(), 0}}; + {std::numeric_limits::quiet_NaN(), 0}, + {std::numeric_limits::signaling_NaN(), 0}, + {0, std::numeric_limits::signaling_NaN()}}; for (std::size_t first = 0; first < boundary.size(); ++first) for (std::size_t second = 0; second < boundary.size(); ++second) { MaterialPacketInput input; @@ -230,6 +232,73 @@ TEST(Hex8HighLow, PacketSpecialLaneDoesNotChangeOtherColumns) { } } +TEST(Hex8HighLow, UniformDyadicPacketsExerciseEachLowZeroShape) { + std::size_t checks = 0, avx2_checks = 0; + for (int shape = 0; shape < 4; ++shape) + for (int low_weight = 0; low_weight < 2; ++low_weight) + for (int zero_accumulator = 0; zero_accumulator < 2; ++zero_accumulator) { + fesa::hex8_internal::MaterialPacketInput input; + input.weight = {0.5, low_weight != 0 ? std::ldexp(1.0, -57) : 0.0}; + for (std::size_t lane = 0; lane < 4; ++lane) { + const bool left_low = (shape & 1) != 0; + const bool right_low = (shape & 2) != 0; + // Every lane has the same normalized nonzero dyadic inputs. Component + // LBE sums are >= -116 and all Add intermediates are bounded by 4. + // Thus each Multiply low-shape is uniform and its component products + // bypass zero/unsupported-tail fallback. No mixed-zero Add fallback + // is possible; the initial zero accumulator is uniform across lanes. + input.deviatoric_left[lane] = {1, left_low ? std::ldexp(1.0, -54) : 0}; + input.deviatoric_right[lane] = {2, right_low ? std::ldexp(1.0, -54) : 0}; + input.shear_left[lane] = {0.5, left_low ? std::ldexp(1.0, -56) : 0}; + input.shear_right[lane] = {0.25, right_low ? std::ldexp(1.0, -57) : 0}; + input.tensor_left[lane] = {0.75, left_low ? std::ldexp(1.0, -56) : 0}; + input.gradient_dot[lane] = {0.125, right_low ? std::ldexp(1.0, -58) : 0}; + input.accumulated[lane] = zero_accumulator != 0 ? HighLow{} + : HighLow{0.0625}; + } + avx2_checks += ExpectFrozenMaterialPacket(input) ? 1U : 0U; + ++checks; + } + RecordProperty("uniform_low_shape_checks", static_cast(checks)); + RecordProperty("avx2_uniform_low_shape_checks", static_cast(avx2_checks)); +} + +TEST(Hex8HighLow, OverflowLaneIsNeutralizedBeforeResidualFma) { + const auto evaluator = fesa::hex8_internal::SelectMaterialPacketEvaluator(); + if (evaluator == fesa::hex8_internal::EvaluateMaterialPacketScalar) + GTEST_SKIP() << "AVX2 masked environment is not selected; routing unverified."; + std::fenv_t original; + ASSERT_EQ(std::fegetenv(&original), 0); + for (std::size_t bad = 0; bad < 4; ++bad) { + fesa::hex8_internal::MaterialPacketInput input; + input.weight = {0.5}; + for (std::size_t lane = 0; lane < 4; ++lane) { + input.deviatoric_left[lane] = { + lane == bad ? std::numeric_limits::max() : 1}; + input.deviatoric_right[lane] = {2}; + input.shear_left[lane] = {0.5}; + input.shear_right[lane] = {0.25}; + input.tensor_left[lane] = {0.75}; + input.gradient_dot[lane] = {0.125}; + input.accumulated[lane] = {0.0625}; + } + // All operands are finite/nonzero and max*2 has LBE sum 972, so Product + // reaches its vector multiply; only the post-product finite mask rejects it. + const auto expected = FrozenMaterialPacket(input); + EXPECT_EQ(std::feclearexcept(FE_ALL_EXCEPT), 0); + fesa::hex8_internal::PairPacket output; + evaluator(input, &output); + const bool extra_invalid = (std::fetestexcept(FE_INVALID) & FE_INVALID) != 0; + EXPECT_EQ(std::fesetenv(&original), 0); + EXPECT_FALSE(extra_invalid); + for (std::size_t lane = 0; lane < 4; ++lane) { + EXPECT_EQ(PairBits(output[lane].high), PairBits(expected[lane].high)); + EXPECT_EQ(PairBits(output[lane].low), PairBits(expected[lane].low)); + } + } + RecordProperty("avx2_product_overflow_positions", 4); +} + TEST(Hex8HighLow, PacketBackendSelectionRecoversAfterEnvironmentRestoration) { const auto initial = fesa::hex8_internal::SelectMaterialPacketEvaluator(); const int rounding = std::fegetround();