Merge reviewed vector pair guards and exact tail screen

This commit is contained in:
KOKO\Mimi
2026-10-14 22:02:20 +09:00
3 changed files with 286 additions and 33 deletions
@@ -494,3 +494,95 @@ Source/input/header/exe/DLL/build/runtime/thread/power identity가 전후 같았
scratch SHA도 같았다. 종료 후 관련 프로세스 0개를 확인하고 CPU 권한을 반환했다. scratch SHA도 같았다. 종료 후 관련 프로세스 0개를 확인하고 CPU 권한을 반환했다.
추가 profile/micro 또는 전체 시간 실행은 하지 않았다. 제한 시간에 중단된 후보끼리의 추가 profile/micro 또는 전체 시간 실행은 하지 않았다. 제한 시간에 중단된 후보끼리의
개선 비율을 계산하지 않는다. 최종 전체 검사·필수 원본 비교·공식 시간 목표는 미완료다. 개선 비율을 계산하지 않는다. 최종 전체 검사·필수 원본 비교·공식 시간 목표는 미완료다.
## Packet 후보의 잔여 비용과 출력 단계 해석의 한계
150초 timeout의 마지막 accepted increment는 100/time10/iteration2였다. 그러나
`prepared_analysis.cpp`는 ExecuteInto를 마친 뒤 모든 IterationRecords를 log.Write한다.
따라서 이 행은 실시간 진행 timestamp가 아니다. Log는 실행 시작 21:20:21에 생성되어
21:22:50에 마지막 작성됐고, 14,911,565-byte candidate tmp는 21:22:51에 생성됐다.
Cutoff에 출력 작성이 진행 중인 사실은 확인하지만, 출력이 전체 시간을 지배한다는
추론은 하지 않는다. 최종 파일 완성과 전체 elapsed는 여전히 미확인이다.
Writer BuildSolidPlan은 저장된 accepted frame을 CommitAcceptedFrame으로 재검증한다.
Pair/identity/physical force/energy gates를 반복하지만 full nonlinear Hex8 response를
프레임마다 다시 계산하지 않는다. 기준 inventory의 factory.Create+EvaluateLinear(zero)는
ModelPlan과 validator.Create에서 요소마다 각각 실행된다. 이는 소스상 중복 finding이며
실측 비용 근거 없이 최우선 후보로 선택하거나 검사를 생략하지 않는다.
같은 생산 `446d3fa`와 binary로 SW30 수집을 한 번 추가했다. Collector actual native4,
outer wrapper1, target native unknown을 유지한다. 기존 raw에서 함수·전체 callstacks·
summary를 export한 세 native와 export wrapper는 모두0이다.
[Collection native](../../.harness/performance/t3/vtune-dynamic-packet-boundary-collection-native.json),
[export native](../../.harness/performance/t3/vtune-dynamic-packet-boundary-export-native.json)를 보존한다.
수집과 export의 metadata를 분리하고 실제 parent PATH를 별도로 저장했다. Source/input/
exe/arithmetic/packet header·kernel·dispatch/parent PATH/thread/runtime DLL/power는
각각 전후 같았다. 환경 수집용 uv subprocess PATH와 actual parent PATH는 구분한다.
Target owned PID는 모두 정리했고 CPU 권한을 반환했다.
[Summary](../../.harness/performance/t3/vtune-dynamic-packet-boundary-summary.csv)의 elapsed는
29.771803초, CPU219.787666초, effective152.367546초, spin59.900463초다.
[함수 자료](../../.harness/performance/t3/vtune-dynamic-packet-boundary-functions.csv)는 packet Add
self9.638549, Product9.444756, Multiply3.902459 CPU 초와 scalar MultiplyPair6.631925,
TwoProduct5.432377 CPU 초를 보고한다. 전체 leaf별 callpath weight 합을 flat total과
대조한 packet inclusive는61.265380 CPU 초, EvaluateHighLow inclusive는133.386968 CPU
초다. Scalar bridge inclusive는 Add0.103136, Multiply0.039823, TwoProduct0.061110 CPU
초여서 fallback 지배 근거는 작다. [경로 자료](../../.harness/performance/t3/vtune-dynamic-packet-boundary-selected-paths.json)는
node를 path마다 한 번 포함하며, 중첩된 inclusive 그룹이나 inline attribution을 별도 비용으로
합산하지 않는다. 이 초기30초 표본은 후처리·출력 비용을 판정하지 못한다. Iomp spin의
PARDISO 기원이나 TBB 시간 중첩은 여전히 unknown이므로 전역 thread 정책을 바꾸지 않았다.
다음 후보는 packet Add/Multiply의 POD 왕복과 scalar normalized/zero/low-shape 검사를
동일 vector masks로 계산하는 최소 범위다. [증명 계획](../../.harness/performance/t3/packet-vector-guard-proposal.md)은
finite bit gate-beforeFP, left-invalid의 right predicate short-circuit neutralization,
canonical zero/RN64/overflow/sticky union/fallback/기존 FENV/ISA·COMDAT 경계를 명시한다.
이는 아직 구현·검증 전이며 산술 graph나 모델 gate를 생략하는 변경이 아니다.
추가 whole-model 실행이나 180초 수집은 수행하지 않았다.
## Vector guard와 exact tail screen의 합성 후보
계획의 조건부 독립 검토 이후 `0b5adf43e0ccbbb8780277f568815377a67f5028`에
private AVX2 cpp와 기존 Hex8 tests 두 파일만 변경했다. Normalized vector guard는
active·finite mask 이후 RN sum의 두 operand와 equality RHS를 모두 중립화한다.
Right active는 left normalized mask이며 left finite만으로 결정하지 않는다.
Canonical both-zero와 signed low-zero 분기를 유지하고 mixed/invalid packet은 기존
baseline bridge로 연결한다. 각 lane 산술 graph와 모델 실패 gate는 그대로다.
Tail screen은 finite/nonzero 이후 원래 exact LBE 위치에서 adjusted encoded exponent
합>=1076일 때만 scan 두 개를 생략한다. False이면 원래 exact LBE와−1074 비교를
실행한다. 충분조건을 필요조건으로 사용하지 않고 rounded product finite→명시 residual
FMA→error finite→TwoSum 순서를 유지한다. Public header·bridge·공통 math·CMake·FP
flags·일반 MKL은 불변이다. [고정 manifest](../../.harness/performance/t3/packet-guard-screen-0b5adf4/manifest.json)와
[독립 actual source 검토](../../.harness/performance/t3/source-numerical-review-packet-guard-screen.md)를 보존한다.
Corrected-prefix Debug·Release configure/build는 각각 native0과 6개 edge였다.
실제 discovery의 관련 78개 검사는 Debug10.24초·Release4.31초로 각각 native0이다.
두 구성 모두 실제 AVX2 선택, 기존 packet4630/uniform16/overflow4를 확인했다.
새 guard132/tail64 fixture는 left-invalid/right-overflow·sNaN·signedzero·mixed prefix·
subnormal·tail threshold1076/1075·conservative false·rounded overflow를 다룬다.
각 fixture에서 unchanged baseline과 selected evaluator를 별도 동일 초기 상태로 실행해
raw high/low·FE_ALL_EXCEPT·MXCSR status6bits를 비교했다. Prior inexact/denormal
상태를 포함하고 test 후 x87/MXCSR를 복원했다. Frozen original은 별도 raw-bit oracle다.
[Debug native](../../.harness/performance/t3/packet-guard-screen-debug-native.json),
[Release native](../../.harness/performance/t3/packet-guard-screen-release-native.json),
[Debug backend](../../.harness/performance/t3/packet-guard-screen-debug-backend.json),
[Release backend](../../.harness/performance/t3/packet-guard-screen-release-backend.json)을 보존한다.
각 구성의 두 TU symbols/disassembly 네 명령과 wrapper는 native0이다. AVX2 객체의
정의된 external 실행 심볼은 전용 evaluator 하나이고, 공통 math/HighLow/std::array
실행 정의는 없다. Baseline dispatch의 AVX/YMM/ANDN은0이며 kernel fused instruction은
명시 residual VFMSUB 하나다. Release Product의 네 `cmp rax,434h` 다음 unsigned JAE가
각각 BSF 두 개를 넘어 같은 join으로 가며 false는 원래 BSF 두 개를 수행한다.
[실제 codegen assertion](../../.harness/performance/t3/packet-guard-screen-codegen-assertion.json)은
각 branch와 전체 symbol/disassembly를 연결하고 실행 assertion native0을 보존한다.
새 정합 Debug LIB/header snapshot의 compile/run/비교 wrapper는 native0이다.
기존 actual-native0 old oracle는 identity를 확인하고 재사용했으며 old 실행은 반복하지
않았다. 새 응답9804행과 원본은 exact bytes와 SHA `DED8D2C380D15E297A73BDD018B1B840C1ADC0D8372934D9FC2EF2B628A36F63`가 같다.
[Snapshot native](../../.harness/performance/t3/packet-guard-screen-snapshot-native.json),
[exact 비교](../../.harness/performance/t3/packet-guard-screen-snapshot-comparison.json)를 보존한다.
[종료 identity](../../.harness/performance/t3/packet-guard-screen-delivery-identity.json)는 검토의
8개 source SHA 전후 일치와 두 구성의 exe/LIB/build SHA를 연결한다. 관련 actor0을
확인하고 CPU 권한을 반환했다. 이번 슬롯에서 whole-model/profile는0회다.
이 검사 통과는 실제 성능 개선이나 100초 목표 달성 증거가 아니며, 이전446의 timeout과
미실행101-frame·최종 전체/공식 검증 한계를 그대로 유지한다.
+54 -33
View File
@@ -29,14 +29,6 @@ bool FiniteScalar(double value) {
return (ScalarBits(value) & 0x7ff0000000000000ULL) != return (ScalarBits(value) & 0x7ff0000000000000ULL) !=
0x7ff0000000000000ULL; 0x7ff0000000000000ULL;
} }
/// @brief Preserves finite, canonical-zero and RN64 normalization gates.
bool NormalizedScalar(PacketPair value) {
if (!FiniteScalar(value.high) || !FiniteScalar(value.low)) return false;
if (value.high == 0 && value.low == 0)
return (ScalarBits(value.high) >> 63U) == 0 &&
(ScalarBits(value.low) >> 63U) == 0;
return value.high + value.low == value.high;
}
/// @brief Exact least-bit exponent; caller has checked finite/nonzero first. /// @brief Exact least-bit exponent; caller has checked finite/nonzero first.
int LowestBitExponentScalar(double value) { int LowestBitExponentScalar(double value) {
const auto bits = ScalarBits(value); const auto bits = ScalarBits(value);
@@ -51,6 +43,17 @@ int LowestBitExponentScalar(double value) {
return exponent + static_cast<int>(trailing); return exponent + static_cast<int>(trailing);
} }
/// @brief Proves the exact tail bound using integer exponents only.
/// @pre Both operands are finite and nonzero; false requires the exact scans.
bool TailBoundGuaranteed(double left, double right) {
const auto left_exponent = (ScalarBits(left) >> 52U) & 0x7ffU;
const auto right_exponent = (ScalarBits(right) >> 52U) & 0x7ffU;
const auto left_adjusted = left_exponent == 0 ? 1U : left_exponent;
const auto right_adjusted = right_exponent == 0 ? 1U : right_exponent;
// LBE >= adjusted exponent - 1075, including nonzero subnormals.
return left_adjusted + right_adjusted >= 1076U;
}
/// @brief Selects each lane without evaluating additional FP arithmetic. /// @brief Selects each lane without evaluating additional FP arithmetic.
__m256d Select(__m256d mask, __m256d selected, __m256d other) { __m256d Select(__m256d mask, __m256d selected, __m256d other) {
return _mm256_blendv_pd(other, selected, mask); return _mm256_blendv_pd(other, selected, mask);
@@ -70,6 +73,31 @@ __m256d Zero(__m256d value) {
_mm256_set1_epi64x(0x7fffffffffffffffLL)), _mm256_set1_epi64x(0x7fffffffffffffffLL)),
_mm256_setzero_si256())); _mm256_setzero_si256()));
} }
/// @brief Preserves finite/canonical-zero/RN64 gates for active lanes only.
/// @note Both sum operands and equality RHS are neutral before FP evaluation.
__m256d Normalized(VectorPair value, __m256d active) {
const auto finite_active = _mm256_and_pd(active,
_mm256_and_pd(Finite(value.high), Finite(value.low)));
const auto both_zero = _mm256_and_pd(Zero(value.high), Zero(value.low));
const auto canonical_zero = _mm256_castsi256_pd(_mm256_and_si256(
_mm256_cmpeq_epi64(_mm256_castpd_si256(value.high),
_mm256_setzero_si256()),
_mm256_cmpeq_epi64(_mm256_castpd_si256(value.low),
_mm256_setzero_si256())));
const auto sum_active = _mm256_andnot_pd(both_zero, finite_active);
const auto high = Select(sum_active, value.high, _mm256_setzero_pd());
const auto low = Select(sum_active, value.low, _mm256_setzero_pd());
const auto equality = _mm256_cmp_pd(_mm256_add_pd(high, low), high, _CMP_EQ_OQ);
return _mm256_and_pd(finite_active,
Select(both_zero, canonical_zero, equality));
}
/// @brief Retains left-to-right normalized short-circuit for every lane.
bool NormalizedInputs(VectorPair left, VectorPair right) {
const auto all = _mm256_castsi256_pd(_mm256_set1_epi64x(-1));
const auto left_valid = Normalized(left, all);
return _mm256_movemask_pd(Normalized(right, left_valid)) == 15;
}
/// @brief Preserves valid lanes and encodes invalid lanes as quiet NaN/+0. /// @brief Preserves valid lanes and encodes invalid lanes as quiet NaN/+0.
VectorPair InvalidOutside(VectorPair value, __m256d valid) { VectorPair InvalidOutside(VectorPair value, __m256d valid) {
return {Select(valid, value.high, _mm256_castsi256_pd( return {Select(valid, value.high, _mm256_castsi256_pd(
@@ -121,17 +149,12 @@ VectorPair ScalarMultiply(const PairPacket& left, const PairPacket& right) {
} }
/// @brief Retains normalized/zero branches and the checked per-lane Add graph. /// @brief Retains normalized/zero branches and the checked per-lane Add graph.
VectorPair Add(VectorPair left, VectorPair right, bool pair_shortcuts) { VectorPair Add(VectorPair left, VectorPair right, bool pair_shortcuts) {
const auto a = Store(left); if (!NormalizedInputs(left, right))
const auto b = Store(right); return ScalarAdd(left, right, pair_shortcuts);
int left_zero = 0; const int left_zero = _mm256_movemask_pd(
int right_zero = 0; _mm256_and_pd(Zero(left.high), Zero(left.low)));
for (std::size_t lane = 0; lane < 4; ++lane) { const int right_zero = _mm256_movemask_pd(
if (!NormalizedScalar(a.values[lane]) || _mm256_and_pd(Zero(right.high), Zero(right.low)));
!NormalizedScalar(b.values[lane]))
return ScalarAdd(left, right, pair_shortcuts);
if (a.values[lane].high == 0 && a.values[lane].low == 0) left_zero |= 1 << lane;
if (b.values[lane].high == 0 && b.values[lane].low == 0) right_zero |= 1 << lane;
}
if (pair_shortcuts) { if (pair_shortcuts) {
if (left_zero == 15) if (left_zero == 15)
return {right.high, Select(Zero(right.low), _mm256_setzero_pd(), right.low)}; return {right.high, Select(Zero(right.low), _mm256_setzero_pd(), right.low)};
@@ -168,8 +191,9 @@ VectorPair Product(__m256d left, __m256d right) {
if (!FiniteScalar(a[lane]) || if (!FiniteScalar(a[lane]) ||
!FiniteScalar(b[lane]) || !FiniteScalar(b[lane]) ||
a[lane] == 0 || b[lane] == 0 || a[lane] == 0 || b[lane] == 0 ||
LowestBitExponentScalar(a[lane]) + (!TailBoundGuaranteed(a[lane], b[lane]) &&
LowestBitExponentScalar(b[lane]) < -1074) LowestBitExponentScalar(a[lane]) +
LowestBitExponentScalar(b[lane]) < -1074))
special = true; special = true;
if (special) { if (special) {
PairPacket result; PairPacket result;
@@ -190,18 +214,15 @@ VectorPair Product(__m256d left, __m256d right) {
/// @brief Uses uniform low-zero branches with original four-product association; /// @brief Uses uniform low-zero branches with original four-product association;
/// mixed or invalid packets preserve the baseline checked path. /// mixed or invalid packets preserve the baseline checked path.
VectorPair Multiply(VectorPair left, VectorPair right) { VectorPair Multiply(VectorPair left, VectorPair right) {
const auto a = Store(left); if (!NormalizedInputs(left, right))
const auto b = Store(right); return ScalarMultiply(Store(left), Store(right));
int branch = -1; const int left_low_zero = _mm256_movemask_pd(Zero(left.low));
for (std::size_t lane = 0; lane < 4; ++lane) { const int right_low_zero = _mm256_movemask_pd(Zero(right.low));
const bool normalized = NormalizedScalar(a.values[lane]) && if ((left_low_zero != 0 && left_low_zero != 15) ||
NormalizedScalar(b.values[lane]); (right_low_zero != 0 && right_low_zero != 15))
if (!normalized) return ScalarMultiply(a, b); return ScalarMultiply(Store(left), Store(right));
const int shape = (a.values[lane].low != 0 ? 1 : 0) | const int branch = (left_low_zero == 0 ? 1 : 0) |
(b.values[lane].low != 0 ? 2 : 0); (right_low_zero == 0 ? 2 : 0);
if (branch != -1 && shape != branch) return ScalarMultiply(a, b);
branch = shape;
}
auto product = Product(left.high, right.high); auto product = Product(left.high, right.high);
if (branch == 3) { if (branch == 3) {
product = Add(product, Product(left.high, right.low), false); product = Add(product, Product(left.high, right.low), false);
+140
View File
@@ -309,6 +309,146 @@ TEST(Hex8HighLow, OverflowLaneIsNeutralizedBeforeResidualFma) {
RecordProperty("avx2_product_overflow_positions", 4); 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) { TEST(Hex8HighLow, PacketBackendSelectionRecoversAfterEnvironmentRestoration) {
const auto initial = fesa::hex8_internal::SelectMaterialPacketEvaluator(); const auto initial = fesa::hex8_internal::SelectMaterialPacketEvaluator();
const int rounding = std::fegetround(); const int rounding = std::fegetround();