• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "ets_coroutine.h"
17 #include "types/ets_string.h"
18 #include "intrinsics/helpers/ets_to_string_cache.h"
19 #include "intrinsics/helpers/ets_intrinsics_helpers.h"
20 #include "ets_vm.h"
21 #include "gtest/gtest.h"
22 #include "runtime/include/runtime.h"
23 #include "runtime/include/thread.h"
24 #include "tests/runtime/types/ets_test_mirror_classes.h"
25 
26 #include <array>
27 #include <thread>
28 #include <random>
29 #include <sstream>
30 
31 #include "plugins/ets/runtime/intrinsics/helpers/ets_to_string_cache.cpp"
32 
33 namespace ark::ets::test {
34 
35 static constexpr uint32_t TEST_THREADS = 8;
36 static constexpr uint32_t TEST_ITERS = 1;
37 static constexpr uint32_t TEST_ARRAY_SIZE = 1000;
38 static constexpr int32_t VALUE_RANGE = 1000;
39 
40 enum GenType { COPY, SHUFFLE, INDEPENDENT };
41 
42 class EtsToStringCacheTest : public testing::Test {
43 public:
EtsToStringCacheTest(const char * gcType=nullptr)44     explicit EtsToStringCacheTest(const char *gcType = nullptr) : engine_(std::random_device {}())
45     {
46         // Logger::InitializeStdLogging(Logger::Level::INFO, Logger::ComponentMaskFromString("runtime") |
47         // Logger::ComponentMaskFromString("coroutines"));
48 
49         RuntimeOptions options;
50         options.SetShouldLoadBootPandaFiles(true);
51         options.SetShouldInitializeIntrinsics(false);
52         options.SetLoadRuntimes({"ets"});
53 
54         auto stdlib = std::getenv("PANDA_STD_LIB");
55         if (stdlib == nullptr) {
56             std::cerr << "PANDA_STD_LIB env variable should be set and point to etsstdlib.abc" << std::endl;
57             std::abort();
58         }
59         options.SetBootPandaFiles({stdlib});
60 
61         if (gcType != nullptr) {
62             options.SetGcType(gcType);
63         }
64         options.SetCompilerEnableJit(false);
65         if (!Runtime::Create(options)) {
66             UNREACHABLE();
67         }
68     }
69 
~EtsToStringCacheTest()70     ~EtsToStringCacheTest() override
71     {
72         Runtime::Destroy();
73     }
74 
75     NO_COPY_SEMANTIC(EtsToStringCacheTest);
76     NO_MOVE_SEMANTIC(EtsToStringCacheTest);
77 
SetUp()78     void SetUp() override
79     {
80         ASSERT(Runtime::GetCurrent() != nullptr);
81         ASSERT(PandaEtsVM::GetCurrent() != nullptr);
82         mainCoro_ = EtsCoroutine::CastFromThread(PandaEtsVM::GetCurrent()->GetCoroutineManager()->GetMainThread());
83         ASSERT(mainCoro_ != nullptr);
84     }
TestMainLoop(double value,bool needCheck)85     void TestMainLoop(double value, [[maybe_unused]] bool needCheck)
86     {
87         auto etsVm = mainCoro_->GetPandaVM();
88         auto *cache = etsVm->GetDoubleToStringCache();
89         auto *etsCoro = EtsCoroutine::GetCurrent();
90         [[maybe_unused]] auto [str, result] = cache->GetOrCacheImpl(etsCoro, value);
91 #ifndef NDEBUG
92         // don't always check to increase pressure
93         if (needCheck) {
94             ASSERT(!str->IsUtf16());
95             auto res = str->GetMutf8();
96 
97             intrinsics::helpers::FpToStringDecimalRadix(value,
98                                                         [&res](std::string_view expected) { ASSERT(expected == res); });
99 
100             auto resValue = std::stod(std::string(res));
101             auto eps = std::numeric_limits<double>::epsilon() * 2 * std::abs(value);
102             ASSERT(std::abs(resValue - value) < eps);
103         }
104 #endif
105     }
106 
TestConcurrentInsertion(const std::array<double,TEST_ARRAY_SIZE> & values)107     void TestConcurrentInsertion(const std::array<double, TEST_ARRAY_SIZE> &values)
108     {
109         auto runtime = Runtime::GetCurrent();
110         auto coro = mainCoro_->GetCoroutineManager()->CreateEntrypointlessCoroutine(runtime, runtime->GetPandaVM(),
111                                                                                     true, "worker");
112         std::mt19937 engine(std::random_device {}());
113         std::uniform_real_distribution<> dis(-VALUE_RANGE, VALUE_RANGE);
114         std::bernoulli_distribution bern(1.0 / TEST_THREADS);
115         coro->ManagedCodeBegin();
116         ASSERT(coro == EtsCoroutine::GetCurrent());
117         for (uint32_t i = 0; i < TEST_ITERS; i++) {
118             for (auto value : values) {
119                 bool needCheck = bern(engine);
120                 TestMainLoop(value, needCheck);
121             }
122         }
123 
124         coro->ManagedCodeEnd();
125         mainCoro_->GetCoroutineManager()->DestroyEntrypointlessCoroutine(coro);
126     }
127 
SetupSimple()128     void SetupSimple()
129     {
130         std::uniform_real_distribution<double> dist(-VALUE_RANGE, VALUE_RANGE);
131         std::generate(values_.begin(), values_.end(), [&dist, this]() { return dist(engine_); });
132     }
133 
SetupExp()134     void SetupExp()
135     {
136         std::uniform_int_distribution<int> dist(-VALUE_RANGE, VALUE_RANGE);
137         std::generate(values_.begin(), values_.end(), [&dist, this]() { return std::pow(2U, dist(engine_)); });
138     }
139 
SetupRepeatedHashes()140     void SetupRepeatedHashes()
141     {
142         std::uniform_real_distribution<double> dist(-VALUE_RANGE, VALUE_RANGE);
143 
144         std::generate(values_.begin(), values_.end(), [&dist, this]() {
145             constexpr auto UNIQUE_HASHES = 3;
146             double value;
147             do {
148                 value = dist(engine_);
149             } while (DoubleToStringCache::GetIndex(value) >= UNIQUE_HASHES);
150             return value;
151         });
152     }
153 
SetupRepeatedHashesAndValues()154     void SetupRepeatedHashesAndValues()
155     {
156         std::uniform_real_distribution<double> dist(-VALUE_RANGE, VALUE_RANGE);
157 
158         static constexpr auto UNIQUE_VALUES = 8;
159         std::generate(values_.begin(), values_.begin() + UNIQUE_VALUES, [&dist, this]() {
160             constexpr auto UNIQUE_HASHES = 2;
161             double value;
162             do {
163                 value = dist(engine_);
164             } while (DoubleToStringCache::GetIndex(value) >= UNIQUE_HASHES);
165             return value;
166         });
167         for (size_t i = UNIQUE_VALUES; i < values_.size(); i++) {
168             values_[i] = values_[i - UNIQUE_VALUES];
169         }
170     }
171 
SetupUniqueHashes()172     void SetupUniqueHashes()
173     {
174         std::uniform_real_distribution<double> dist(-VALUE_RANGE, VALUE_RANGE);
175 
176         std::unordered_map<uint32_t, double> cacheIndexToValue;
177         while (cacheIndexToValue.size() < DoubleToStringCache::SIZE / 2U) {
178             auto value = dist(engine_);
179             cacheIndexToValue[DoubleToStringCache::GetIndex(value)] = value;
180         }
181         auto it = cacheIndexToValue.begin();
182         std::generate(values_.begin(), values_.end(), [&cacheIndexToValue, &it]() {
183             if (it == cacheIndexToValue.end()) {
184                 it = cacheIndexToValue.begin();
185             }
186             return (*it++).second;
187         });
188     }
189 
GetMainCoro()190     EtsCoroutine *GetMainCoro()
191     {
192         return mainCoro_;
193     }
194 
GetEngine()195     std::mt19937 &GetEngine()
196     {
197         return engine_;
198     }
199 
200     template <typename T>
CheckCacheElementMembers()201     static void CheckCacheElementMembers()
202     {
203         ASSERT(detail::EtsToStringCacheElement<T>::STRING_OFFSET ==
204                MEMBER_OFFSET(detail::EtsToStringCacheElement<T>, data_));
205 
206         // We can call "classLinker->GetClass" only with MutatorLock or with disabled GC.
207         // So just for testing is necessary add "MutatorLock"
208         // NOTE: In the main place of use (in initialization VM), during execution method
209         // "EtsToStringCacheElement<T>::GetClass" GC is not started
210         PandaVM::GetCurrent()->GetMutatorLock()->WriteLock();
211         auto *klass = detail::EtsToStringCacheElement<T>::GetClass(EtsCoroutine::GetCurrent());
212         std::vector<MirrorFieldInfo> members {
213             MirrorFieldInfo("string", detail::EtsToStringCacheElement<T>::STRING_OFFSET),
214             MirrorFieldInfo("lock", detail::EtsToStringCacheElement<T>::FLAG_OFFSET),
215             MIRROR_FIELD_INFO(detail::EtsToStringCacheElement<T>, number_, "number")};
216         MirrorFieldInfo::CompareMemberOffsets(klass, members);
217         PandaVM::GetCurrent()->GetMutatorLock()->Unlock();
218     }
219 
220 protected:
DoTest(void (EtsToStringCacheTest::* setup)(),GenType genType)221     void DoTest(void (EtsToStringCacheTest::*setup)(), GenType genType)
222     {
223         (this->*setup)();
224         for (uint32_t i = 0; i < TEST_THREADS; i++) {
225             if (genType == GenType::SHUFFLE) {
226                 std::shuffle(values_.begin(), values_.end(), GetEngine());
227             } else if (genType == GenType::INDEPENDENT && i > 0) {
228                 (this->*setup)();
229             }
230             threadValues_[i] = values_;
231         }
232 
233         for (uint32_t i = 0; i < TEST_THREADS; i++) {
234             threads_[i] = std::thread([this, i]() { TestConcurrentInsertion(threadValues_[i]); });
235         }
236 
237         for (uint32_t i = 0; i < TEST_THREADS; i++) {
238             threads_[i].join();
239         }
240     }
241 
242 private:
243     std::array<double, TEST_ARRAY_SIZE> values_ {};
244     std::array<std::array<double, TEST_ARRAY_SIZE>, TEST_THREADS> threadValues_ {};
245 
246     std::array<std::thread, TEST_THREADS> threads_;
247     std::mt19937 engine_;
248     EtsCoroutine *mainCoro_ {};
249 };
250 
251 // NOLINTNEXTLINE(fuchsia-multiple-inheritance)
252 class EtsToStringCacheParamTest : public EtsToStringCacheTest,
253                                   public testing::WithParamInterface<std::tuple<const char *, GenType>> {
254 public:
EtsToStringCacheParamTest()255     EtsToStringCacheParamTest() : EtsToStringCacheTest(std::get<0>(GetParam())) {}
256 
DoTest(void (EtsToStringCacheTest::* setup)())257     void DoTest(void (EtsToStringCacheTest::*setup)())
258     {
259         EtsToStringCacheTest::DoTest(setup, std::get<1>(GetParam()));
260     }
261 };
262 
TEST_P(EtsToStringCacheParamTest,ConcurrentInsertion)263 TEST_P(EtsToStringCacheParamTest, ConcurrentInsertion)
264 {
265     DoTest(&EtsToStringCacheTest::SetupSimple);
266 }
267 
TEST_P(EtsToStringCacheParamTest,ConcurrentInsertionExp)268 TEST_P(EtsToStringCacheParamTest, ConcurrentInsertionExp)
269 {
270     DoTest(&EtsToStringCacheTest::SetupExp);
271 }
272 
TEST_P(EtsToStringCacheParamTest,ConcurrentInsertionRepeatedHashes)273 TEST_P(EtsToStringCacheParamTest, ConcurrentInsertionRepeatedHashes)
274 {
275     DoTest(&EtsToStringCacheTest::SetupRepeatedHashes);
276 }
277 
TEST_P(EtsToStringCacheParamTest,ConcurrentInsertionRepeatedHashesAndValues)278 TEST_P(EtsToStringCacheParamTest, ConcurrentInsertionRepeatedHashesAndValues)
279 {
280     DoTest(&EtsToStringCacheTest::SetupRepeatedHashesAndValues);
281 }
282 
TEST_P(EtsToStringCacheParamTest,ConcurrentInsertionUniqueHashes)283 TEST_P(EtsToStringCacheParamTest, ConcurrentInsertionUniqueHashes)
284 {
285     DoTest(&EtsToStringCacheTest::SetupUniqueHashes);
286 }
287 
288 INSTANTIATE_TEST_SUITE_P(EtsToStringCacheTestSuite, EtsToStringCacheParamTest,
289                          testing::Combine(testing::Values("stw", "gen-gc", "g1-gc"),
290                                           testing::Values(GenType::COPY, GenType::SHUFFLE, GenType::INDEPENDENT)));
291 
TEST_F(EtsToStringCacheTest,BitcastTestCached)292 TEST_F(EtsToStringCacheTest, BitcastTestCached)
293 {
294     auto &engine = GetEngine();
295     auto coro = GetMainCoro();
296     auto etsVm = coro->GetPandaVM();
297     std::uniform_int_distribution<uint32_t> dis(0, std::numeric_limits<uint32_t>::max());
298     coro->ManagedCodeBegin();
299     auto etsCoro = EtsCoroutine::GetCurrent();
300     ASSERT(coro == etsCoro);
301     auto *cache = etsVm->GetDoubleToStringCache();
302 
303     for (uint32_t i = 0; i < TEST_ARRAY_SIZE; i++) {
304         auto longValue = (static_cast<uint64_t>(dis(engine)) << BITS_PER_UINT32) | dis(engine);
305         auto value = bit_cast<double>(longValue);
306         auto *str = cache->GetOrCache(etsCoro, value);
307         ASSERT(!str->IsUtf16());
308         auto res = str->GetMutf8();
309 
310         bool correct;
311         auto eps = std::numeric_limits<double>::epsilon() * std::abs(value);
312         double resValue = 0;
313         if (std::isnan(value)) {
314             correct = res == "NaN";
315         } else {
316             std::istringstream iss {std::string(res)};
317             iss >> resValue;
318             correct = std::abs(resValue - value) <= eps;
319         }
320 
321         if (!correct) {
322             std::cerr << std::setprecision(intrinsics::helpers::DOUBLE_MAX_PRECISION) << "Error:\n"
323                       << "long: " << std::hex << longValue << "\n"
324                       << "double: " << value << "\n"
325                       << "str: " << res << "\n"
326                       << "delta: " << std::abs(resValue - value) << "\n"
327                       << "eps: " << eps << std::endl;
328             UNREACHABLE();
329         }
330     }
331 
332     coro->ManagedCodeEnd();
333 }
334 
TEST_F(EtsToStringCacheTest,BitcastTestRaw)335 TEST_F(EtsToStringCacheTest, BitcastTestRaw)
336 {
337     auto &engine = GetEngine();
338     std::uniform_int_distribution<uint32_t> dis(0, std::numeric_limits<uint32_t>::max());
339 
340     for (uint32_t i = 0; i < TEST_ARRAY_SIZE; i++) {
341         auto longValue = (static_cast<uint64_t>(dis(engine)) << BITS_PER_UINT32) | dis(engine);
342         auto value = bit_cast<double>(longValue);
343         std::string res;
344         intrinsics::helpers::FpToStringDecimalRadix(value, [&res](std::string_view expected) { res = expected; });
345 
346         bool correct;
347         auto eps = std::numeric_limits<double>::epsilon() * std::abs(value);
348         double resValue = 0;
349         if (std::isnan(value)) {
350             correct = res == "NaN";
351         } else {
352             std::istringstream iss {std::string(res)};
353             iss >> resValue;
354             correct = std::abs(resValue - value) <= eps;
355         }
356 
357         if (!correct) {
358             std::cerr << std::setprecision(intrinsics::helpers::DOUBLE_MAX_PRECISION) << "Error:\n"
359                       << "long: " << std::hex << longValue << "\n"
360                       << "double: " << value << "\n"
361                       << "str: " << res << "\n"
362                       << "delta: " << std::abs(resValue - value) << "\n"
363                       << "eps: " << eps << std::endl;
364             UNREACHABLE();
365         }
366     }
367 }
368 
SymEq(float x,float y)369 [[maybe_unused]] static bool SymEq(float x, float y)
370 {
371     if (std::isnan(x)) {
372         return std::isnan(y);
373     }
374     auto delta = std::abs(x - y);
375     auto eps = std::abs(x) * (2 * std::numeric_limits<float>::epsilon());
376     return delta <= eps;
377 }
378 
TEST_F(EtsToStringCacheTest,BitcastTestFloat)379 TEST_F(EtsToStringCacheTest, BitcastTestFloat)
380 {
381     auto &engine = GetEngine();
382     std::uniform_int_distribution<uint32_t> dis(0, std::numeric_limits<uint32_t>::max());
383 
384     for (uint32_t i = 0; i < TEST_ARRAY_SIZE; i++) {
385         auto intValue = dis(engine);
386         auto value = bit_cast<float>(intValue);
387         if (std::isnan(value)) {
388             continue;
389         }
390         {
391             std::string resFloat;
392             intrinsics::helpers::FpToStringDecimalRadix(value, [&resFloat](std::string_view str) { resFloat = str; });
393             float parsedFromFloat = 0;
394             std::istringstream iss {resFloat};
395             iss >> parsedFromFloat;
396             ASSERT_DO(SymEq(value, parsedFromFloat), std::cerr << "Error:\n"
397                                                                << "float value: " << value << "\n"
398                                                                << "float str: " << resFloat << "\n"
399                                                                << "float parsed: " << parsedFromFloat << "\n");
400         }
401         {
402             auto doubleValue = static_cast<double>(value);
403             std::string resDouble;
404             intrinsics::helpers::FpToStringDecimalRadix(doubleValue,
405                                                         [&resDouble](std::string_view str) { resDouble = str; });
406             float parsedFromDouble = 0;
407             std::istringstream iss {resDouble};
408             iss >> parsedFromDouble;
409             ASSERT(SymEq(value, parsedFromDouble));
410             ASSERT_DO(SymEq(value, parsedFromDouble), std::cerr << "Error:\n"
411                                                                 << "float value: " << value << "\n"
412                                                                 << "double str: " << resDouble << "\n"
413                                                                 << "float parsed: " << parsedFromDouble << "\n");
414         }
415     }
416 }
417 
TEST_F(EtsToStringCacheTest,ToStringCacheElementLayout)418 TEST_F(EtsToStringCacheTest, ToStringCacheElementLayout)
419 {
420     CheckCacheElementMembers<EtsDouble>();
421     CheckCacheElementMembers<EtsFloat>();
422     CheckCacheElementMembers<EtsLong>();
423 }
424 
425 }  // namespace ark::ets::test
426