Merge reviewed vector pair guards and exact tail screen
This commit is contained in:
@@ -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·최종 전체/공식 검증 한계를 그대로 유지한다.
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user