1 /*
2 * Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #include "system_wrappers/include/denormal_disabler.h"
12
13 #include <cmath>
14 #include <limits>
15 #include <vector>
16
17 #include "rtc_base/checks.h"
18 #include "test/gtest.h"
19
20 namespace webrtc {
21 namespace {
22
23 constexpr float kSmallest = std::numeric_limits<float>::min();
24
25 // Float values such that, if used as divisors of `kSmallest`, the division
26 // produces a denormal or zero depending on whether denormals are enabled.
27 constexpr float kDenormalDivisors[] = {123.125f, 97.0f, 32.0f, 5.0f, 1.5f};
28
29 // Returns true if the result of `dividend` / `divisor` is a denormal.
30 // `dividend` and `divisor` must not be denormals.
DivisionIsDenormal(float dividend,float divisor)31 bool DivisionIsDenormal(float dividend, float divisor) {
32 RTC_DCHECK_GE(std::fabsf(dividend), kSmallest);
33 RTC_DCHECK_GE(std::fabsf(divisor), kSmallest);
34 volatile float division = dividend / divisor;
35 return division != 0.0f && std::fabsf(division) < kSmallest;
36 }
37
38 } // namespace
39
40 class DenormalDisablerParametrization : public ::testing::TestWithParam<bool> {
41 };
42
43 // Checks that +inf and -inf are not zeroed regardless of whether
44 // architecture and compiler are supported.
TEST_P(DenormalDisablerParametrization,InfNotZeroed)45 TEST_P(DenormalDisablerParametrization, InfNotZeroed) {
46 DenormalDisabler denormal_disabler(/*enabled=*/GetParam());
47 constexpr float kMax = std::numeric_limits<float>::max();
48 for (float x : {-2.0f, 2.0f}) {
49 SCOPED_TRACE(x);
50 volatile float multiplication = kMax * x;
51 EXPECT_TRUE(std::isinf(multiplication));
52 }
53 }
54
55 // Checks that a NaN is not zeroed regardless of whether architecture and
56 // compiler are supported.
TEST_P(DenormalDisablerParametrization,NanNotZeroed)57 TEST_P(DenormalDisablerParametrization, NanNotZeroed) {
58 DenormalDisabler denormal_disabler(/*enabled=*/GetParam());
59 volatile float kNan = std::sqrt(-1.0f);
60 EXPECT_TRUE(std::isnan(kNan));
61 }
62
63 INSTANTIATE_TEST_SUITE_P(DenormalDisabler,
64 DenormalDisablerParametrization,
65 ::testing::Values(false, true),
__anonc2cc9fec0202(const ::testing::TestParamInfo<bool>& info) 66 [](const ::testing::TestParamInfo<bool>& info) {
67 return info.param ? "enabled" : "disabled";
68 });
69
70 // Checks that denormals are not zeroed if `DenormalDisabler` is disabled and
71 // architecture and compiler are supported.
TEST(DenormalDisabler,DoNotZeroDenormalsIfDisabled)72 TEST(DenormalDisabler, DoNotZeroDenormalsIfDisabled) {
73 if (!DenormalDisabler::IsSupported()) {
74 GTEST_SKIP() << "Unsupported platform.";
75 }
76 ASSERT_TRUE(DivisionIsDenormal(kSmallest, kDenormalDivisors[0]))
77 << "Precondition not met: denormals must be enabled.";
78 DenormalDisabler denormal_disabler(/*enabled=*/false);
79 for (float x : kDenormalDivisors) {
80 SCOPED_TRACE(x);
81 EXPECT_TRUE(DivisionIsDenormal(-kSmallest, x));
82 EXPECT_TRUE(DivisionIsDenormal(kSmallest, x));
83 }
84 }
85
86 // Checks that denormals are zeroed if `DenormalDisabler` is enabled if
87 // architecture and compiler are supported.
TEST(DenormalDisabler,ZeroDenormals)88 TEST(DenormalDisabler, ZeroDenormals) {
89 if (!DenormalDisabler::IsSupported()) {
90 GTEST_SKIP() << "Unsupported platform.";
91 }
92 DenormalDisabler denormal_disabler(/*enabled=*/true);
93 for (float x : kDenormalDivisors) {
94 SCOPED_TRACE(x);
95 EXPECT_FALSE(DivisionIsDenormal(-kSmallest, x));
96 EXPECT_FALSE(DivisionIsDenormal(kSmallest, x));
97 }
98 }
99
100 // Checks that the `DenormalDisabler` dtor re-enables denormals if previously
101 // enabled and architecture and compiler are supported.
TEST(DenormalDisabler,RestoreDenormalsEnabled)102 TEST(DenormalDisabler, RestoreDenormalsEnabled) {
103 if (!DenormalDisabler::IsSupported()) {
104 GTEST_SKIP() << "Unsupported platform.";
105 }
106 ASSERT_TRUE(DivisionIsDenormal(kSmallest, kDenormalDivisors[0]))
107 << "Precondition not met: denormals must be enabled.";
108 {
109 DenormalDisabler denormal_disabler(/*enabled=*/true);
110 ASSERT_FALSE(DivisionIsDenormal(kSmallest, kDenormalDivisors[0]));
111 }
112 EXPECT_TRUE(DivisionIsDenormal(kSmallest, kDenormalDivisors[0]));
113 }
114
115 // Checks that the `DenormalDisabler` dtor keeps denormals disabled if
116 // architecture and compiler are supported and if previously disabled - i.e.,
117 // nested usage is supported.
TEST(DenormalDisabler,ZeroDenormalsNested)118 TEST(DenormalDisabler, ZeroDenormalsNested) {
119 if (!DenormalDisabler::IsSupported()) {
120 GTEST_SKIP() << "Unsupported platform.";
121 }
122 DenormalDisabler d1(/*enabled=*/true);
123 ASSERT_FALSE(DivisionIsDenormal(kSmallest, kDenormalDivisors[0]));
124 {
125 DenormalDisabler d2(/*enabled=*/true);
126 ASSERT_FALSE(DivisionIsDenormal(kSmallest, kDenormalDivisors[0]));
127 }
128 EXPECT_FALSE(DivisionIsDenormal(kSmallest, kDenormalDivisors[0]));
129 }
130
131 // Checks that `DenormalDisabler` does not zero denormals if architecture and
132 // compiler are not supported.
TEST(DenormalDisabler,DoNotZeroDenormalsIfUnsupported)133 TEST(DenormalDisabler, DoNotZeroDenormalsIfUnsupported) {
134 if (DenormalDisabler::IsSupported()) {
135 GTEST_SKIP() << "This test should only run on platforms without support "
136 "for DenormalDisabler.";
137 }
138 DenormalDisabler denormal_disabler(/*enabled=*/true);
139 for (float x : kDenormalDivisors) {
140 SCOPED_TRACE(x);
141 EXPECT_TRUE(DivisionIsDenormal(-kSmallest, x));
142 EXPECT_TRUE(DivisionIsDenormal(kSmallest, x));
143 }
144 }
145
146 } // namespace webrtc
147