Merge reviewed vector pair guards and exact tail screen
This commit is contained in:
@@ -309,6 +309,146 @@ TEST(Hex8HighLow, OverflowLaneIsNeutralizedBeforeResidualFma) {
|
||||
RecordProperty("avx2_product_overflow_positions", 4);
|
||||
}
|
||||
|
||||
#if defined(_M_X64) || defined(__SSE2__)
|
||||
// Runs scalar and selected packet from identical FP state. The frozen original
|
||||
// supplies raw-bit results; the unchanged baseline supplies FE/MXCSR status.
|
||||
void ExpectPacketFlagsAndBits(
|
||||
const fesa::hex8_internal::MaterialPacketInput& input, bool prior_status) {
|
||||
std::fenv_t original;
|
||||
ASSERT_EQ(std::fegetenv(&original), 0);
|
||||
const auto original_csr = _mm_getcsr();
|
||||
const auto evaluator = fesa::hex8_internal::SelectMaterialPacketEvaluator();
|
||||
struct Observation {
|
||||
fesa::hex8_internal::PairPacket output;
|
||||
int flags;
|
||||
unsigned int status;
|
||||
};
|
||||
const auto observe = [&](fesa::hex8_internal::MaterialPacketEvaluator backend) {
|
||||
EXPECT_EQ(std::fesetenv(&original), 0);
|
||||
_mm_setcsr(original_csr & ~0x3fU);
|
||||
EXPECT_EQ(std::feclearexcept(FE_ALL_EXCEPT), 0);
|
||||
if (prior_status) {
|
||||
EXPECT_EQ(std::feraiseexcept(FE_INEXACT), 0);
|
||||
_mm_setcsr(_mm_getcsr() | 0x02U); // Preserve prior denormal status too.
|
||||
}
|
||||
Observation value{};
|
||||
backend(input, &value.output);
|
||||
value.flags = std::fetestexcept(FE_ALL_EXCEPT);
|
||||
value.status = _mm_getcsr() & 0x3fU;
|
||||
return value;
|
||||
};
|
||||
const auto scalar = observe(fesa::hex8_internal::EvaluateMaterialPacketScalar);
|
||||
const auto selected = observe(evaluator);
|
||||
const auto expected = FrozenMaterialPacket(input);
|
||||
// Restore both x87 and MXCSR before assertions, including NaN bit assertions.
|
||||
EXPECT_EQ(std::fesetenv(&original), 0);
|
||||
_mm_setcsr(original_csr);
|
||||
EXPECT_EQ(selected.flags, scalar.flags);
|
||||
EXPECT_EQ(selected.status, scalar.status);
|
||||
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||
SCOPED_TRACE(lane);
|
||||
EXPECT_EQ(PairBits(selected.output.values[lane].high),
|
||||
PairBits(expected.values[lane].high));
|
||||
EXPECT_EQ(PairBits(selected.output.values[lane].low),
|
||||
PairBits(expected.values[lane].low));
|
||||
EXPECT_EQ(PairBits(scalar.output.values[lane].high),
|
||||
PairBits(expected.values[lane].high));
|
||||
EXPECT_EQ(PairBits(scalar.output.values[lane].low),
|
||||
PairBits(expected.values[lane].low));
|
||||
}
|
||||
}
|
||||
|
||||
fesa::hex8_internal::MaterialPacketInput DyadicPacketFixture() {
|
||||
fesa::hex8_internal::MaterialPacketInput input{};
|
||||
input.weight = {0.5, 0};
|
||||
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||
input.deviatoric_left.values[lane] = {1, 0};
|
||||
input.deviatoric_right.values[lane] = {2, 0};
|
||||
input.shear_left.values[lane] = {0.5, 0};
|
||||
input.shear_right.values[lane] = {0.25, 0};
|
||||
input.tensor_left.values[lane] = {0.75, 0};
|
||||
input.gradient_dot.values[lane] = {0.125, 0};
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
TEST(Hex8HighLow, PacketVectorGuardsPreserveScalarFlagsAndBits) {
|
||||
if (fesa::hex8_internal::SelectMaterialPacketEvaluator() ==
|
||||
fesa::hex8_internal::EvaluateMaterialPacketScalar)
|
||||
GTEST_SKIP() << "AVX2 masked environment is not selected.";
|
||||
using fesa::hex8_internal::PacketPair;
|
||||
const double maximum = std::numeric_limits<double>::max();
|
||||
const double snan = std::numeric_limits<double>::signaling_NaN();
|
||||
const double tiny = std::numeric_limits<double>::denorm_min();
|
||||
const std::vector<PacketPair> invalid_left{
|
||||
{-0.0, 0}, {0, -0.0}, {1, 1}, {snan, 0}, {0, snan}};
|
||||
const std::vector<PacketPair> dangerous_right{
|
||||
{maximum, maximum}, {snan, 0}, {0, snan}};
|
||||
std::size_t checks = 0;
|
||||
for (const auto left : invalid_left)
|
||||
for (const auto right : dangerous_right)
|
||||
for (std::size_t bad = 0; bad < 4; ++bad) {
|
||||
auto input = DyadicPacketFixture();
|
||||
input.deviatoric_left.values[bad] = left;
|
||||
input.deviatoric_right.values[bad] = right;
|
||||
// Later lanes must still contribute the unchanged bridge's RN status.
|
||||
const auto later = (bad + 1) % 4;
|
||||
input.deviatoric_left.values[later] = {1, std::ldexp(1.0, -54)};
|
||||
input.deviatoric_right.values[later] = {tiny, 0};
|
||||
ExpectPacketFlagsAndBits(input, false);
|
||||
ExpectPacketFlagsAndBits(input, true);
|
||||
checks += 2;
|
||||
}
|
||||
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||
auto mixed = DyadicPacketFixture();
|
||||
mixed.deviatoric_left.values[lane] = {1, std::ldexp(1.0, -54)};
|
||||
mixed.deviatoric_left.values[(lane + 1) % 4] = {maximum, maximum};
|
||||
ExpectPacketFlagsAndBits(mixed, false);
|
||||
ExpectPacketFlagsAndBits(mixed, true);
|
||||
auto signed_low = DyadicPacketFixture();
|
||||
signed_low.deviatoric_left.values[lane].low = -0.0;
|
||||
signed_low.accumulated.values[lane] = {-0.0, 0};
|
||||
ExpectPacketFlagsAndBits(signed_low, false);
|
||||
checks += 3;
|
||||
}
|
||||
RecordProperty("avx2_guard_flags_and_bit_checks", static_cast<int>(checks));
|
||||
}
|
||||
|
||||
TEST(Hex8HighLow, PacketTailScreenRetainsExactBoundaryAndFallback) {
|
||||
if (fesa::hex8_internal::SelectMaterialPacketEvaluator() ==
|
||||
fesa::hex8_internal::EvaluateMaterialPacketScalar)
|
||||
GTEST_SKIP() << "AVX2 masked environment is not selected.";
|
||||
const auto odd = [](int exponent) {
|
||||
return std::nextafter(std::ldexp(1.0, exponent),
|
||||
std::numeric_limits<double>::infinity());
|
||||
};
|
||||
const double tiny = std::numeric_limits<double>::denorm_min();
|
||||
const std::array<std::array<double, 2>, 8> operands{{
|
||||
{odd(-500), odd(-470)}, // screen true, exact LBE sum -1074
|
||||
{odd(-500), odd(-471)}, // screen false, exact rejection -1075
|
||||
{std::ldexp(1.0, -500), std::ldexp(1.0, -471)}, // false but supported
|
||||
{tiny, odd(52)}, {tiny, odd(51)}, {3 * tiny, odd(52)},
|
||||
{std::numeric_limits<double>::max(), 2}, {0, odd(52)}}};
|
||||
std::size_t checks = 0;
|
||||
for (const auto& pair : operands)
|
||||
for (std::size_t special = 0; special < 4; ++special) {
|
||||
fesa::hex8_internal::MaterialPacketInput input{};
|
||||
input.weight = {1, 0};
|
||||
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||
const double sign = lane % 2 == 0 ? 1.0 : -1.0;
|
||||
input.deviatoric_left.values[lane] =
|
||||
{sign * (lane == special ? pair[0] : odd(-500)), 0};
|
||||
input.deviatoric_right.values[lane] =
|
||||
{lane == special ? pair[1] : odd(-470), 0};
|
||||
}
|
||||
ExpectPacketFlagsAndBits(input, false);
|
||||
ExpectPacketFlagsAndBits(input, true);
|
||||
checks += 2;
|
||||
}
|
||||
RecordProperty("avx2_tail_screen_flags_and_bit_checks", static_cast<int>(checks));
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(Hex8HighLow, PacketBackendSelectionRecoversAfterEnvironmentRestoration) {
|
||||
const auto initial = fesa::hex8_internal::SelectMaterialPacketEvaluator();
|
||||
const int rounding = std::fegetround();
|
||||
|
||||
Reference in New Issue
Block a user