• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2023-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 <cstddef>
17 #include <cstdint>
18 #include <optional>
19 #include "libpandabase/macros.h"
20 #include "libpandabase/utils/bit_utils.h"
21 #include "mem/vm_handle.h"
22 #include "runtime/include/runtime.h"
23 #include "runtime/include/object_header.h"
24 #include "runtime/include/thread_scopes.h"
25 #include "runtime/include/mem/panda_containers.h"
26 #include "plugins/ets/runtime/ets_handle.h"
27 #include "plugins/ets/runtime/ets_coroutine.h"
28 #include "plugins/ets/runtime/types/ets_class.h"
29 #include "plugins/ets/runtime/types/ets_shared_memory.h"
30 #include "plugins/ets/runtime/types/ets_shared_memory-inl.h"
31 
32 namespace ark::ets {
33 
Wait(std::optional<uint64_t> timeout)34 bool EtsSharedMemory::Waiter::Wait(std::optional<uint64_t> timeout)
35 {
36     auto mutex = &EtsCoroutine::GetCurrent()->GetPandaVM()->GetAtomicsMutex();
37     if (timeout.has_value()) {
38         auto ms = timeout.value();
39         return cv_.TimedWait(mutex, ms);
40     }
41 
42     cv_.Wait(mutex);
43     return false;
44 }
45 
SignalAll()46 void EtsSharedMemory::Waiter::SignalAll()
47 {
48     cv_.SignalAll();
49 }
50 
Create(size_t length)51 EtsSharedMemory *EtsSharedMemory::Create(size_t length)
52 {
53     auto *currentCoro = EtsCoroutine::GetCurrent();
54     [[maybe_unused]] EtsHandleScope scope(currentCoro);
55 
56     auto cls = currentCoro->GetPandaVM()->GetClassLinker()->GetSharedMemoryClass();
57     // Note: This object must be non-movable since the 'waiter_' pointer is shared between different threads
58     EtsHandle<EtsSharedMemory> hmem(currentCoro, EtsSharedMemory::FromEtsObject(EtsObject::CreateNonMovable(cls)));
59     auto *arrayPtr = EtsByteArray::Create(length, SpaceType::SPACE_TYPE_NON_MOVABLE_OBJECT);
60     ObjectAccessor::SetObject(currentCoro, hmem.GetPtr(), MEMBER_OFFSET(EtsSharedMemory, array_),
61                               arrayPtr->GetCoreType());
62     hmem->SetHeadWaiter(nullptr);
63     return hmem.GetPtr();
64 }
65 
LinkWaiter(Waiter & waiter)66 void EtsSharedMemory::LinkWaiter(Waiter &waiter)
67 {
68     waiter.SetPrev(nullptr);
69     waiter.SetNext(GetHeadWaiter());
70     SetHeadWaiter(&waiter);
71 }
72 
UnlinkWaiter(Waiter & waiter)73 void EtsSharedMemory::UnlinkWaiter(Waiter &waiter)
74 {
75     auto prev = waiter.GetPrev();
76     auto next = waiter.GetNext();
77 
78     if (prev != nullptr) {
79         prev->SetNext(next);
80     }
81     if (next != nullptr) {
82         next->SetPrev(prev);
83     }
84     if (GetHeadWaiter() == &waiter) {
85         SetHeadWaiter(nullptr);
86     }
87 }
88 
GetLength()89 size_t EtsSharedMemory::GetLength()
90 {
91     auto *currentCoro = EtsCoroutine::GetCurrent();
92     auto *arrayPtr = reinterpret_cast<EtsByteArray *>(
93         ObjectAccessor::GetObject(currentCoro, this, MEMBER_OFFSET(EtsSharedMemory, array_)));
94     return arrayPtr->GetLength();
95 }
96 
97 namespace {
98 
PrintWaiters(EtsHandle<EtsSharedMemory> & buffer)99 std::string PrintWaiters(EtsHandle<EtsSharedMemory> &buffer)
100 {
101     std::stringstream stream;
102     auto curWaiter = buffer->GetHeadWaiter();
103     while (curWaiter != nullptr) {
104         stream << reinterpret_cast<size_t>(curWaiter) << " -> ";
105         curWaiter = curWaiter->GetNext();
106     }
107     return stream.str();
108 }
109 
IsLittleEndian()110 bool IsLittleEndian()
111 {
112     int32_t x = 1;
113     return *reinterpret_cast<int8_t *>(&x) == static_cast<int8_t>(1);
114 }
115 
116 template <typename IntegerType, typename UIntegerType>
AssembleFromBytes(EtsSharedMemory & mem,uint32_t index,uint32_t (* getByteIndex)(uint32_t,uint32_t))117 IntegerType AssembleFromBytes(EtsSharedMemory &mem, uint32_t index, uint32_t (*getByteIndex)(uint32_t, uint32_t))
118 {
119     UIntegerType value = 0;
120     for (uint32_t i = 0; i < sizeof(IntegerType); i++) {
121         auto curByteIndex = getByteIndex(index, i);
122         auto curByte = bit_cast<UIntegerType>(static_cast<IntegerType>(mem.GetElement<int8_t>(curByteIndex)));
123         value |= curByte << (8U * i);
124     }
125     return bit_cast<IntegerType>(value);
126 }
127 
LittleEndianGetByteIndex(uint32_t index,uint32_t i)128 uint32_t LittleEndianGetByteIndex(uint32_t index, uint32_t i)
129 {
130     return index + i;
131 }
132 
133 template <typename IntegerType>
BigEndianGetByteIndex(uint32_t index,uint32_t i)134 uint32_t BigEndianGetByteIndex(uint32_t index, uint32_t i)
135 {
136     return index + sizeof(IntegerType) - 1 - i;
137 }
138 
139 template <typename IntegerType, typename UIntegerType>
AssembleFromBytes(EtsSharedMemory & mem,uint32_t index)140 IntegerType AssembleFromBytes(EtsSharedMemory &mem, uint32_t index)
141 {
142     return IsLittleEndian()
143                ? AssembleFromBytes<IntegerType, UIntegerType>(mem, index, LittleEndianGetByteIndex)
144                : AssembleFromBytes<IntegerType, UIntegerType>(mem, index, BigEndianGetByteIndex<IntegerType>);
145 }
146 
147 template <typename IntegerType, typename UIntegerType>
Wait(EtsSharedMemory * mem,uint32_t byteOffset,IntegerType expectedValue,std::optional<uint64_t> timeout)148 EtsSharedMemory::WaitResult Wait(EtsSharedMemory *mem, uint32_t byteOffset, IntegerType expectedValue,
149                                  std::optional<uint64_t> timeout)
150 {
151     auto coroutine = EtsCoroutine::GetCurrent();
152     [[maybe_unused]] EtsHandleScope scope(coroutine);
153     EtsHandle<EtsSharedMemory> thisHandle(coroutine, mem);
154 
155     ScopedNativeCodeThread n(coroutine);
156     os::memory::LockHolder lock(coroutine->GetPandaVM()->GetAtomicsMutex());
157     ScopedManagedCodeThread m(coroutine);
158 
159     auto witnessedValue = AssembleFromBytes<IntegerType, UIntegerType>(*(thisHandle.GetPtr()), byteOffset);
160     LOG(DEBUG, ATOMICS) << "Wait: witnesseed_value=" << witnessedValue << ", expected_value=" << expectedValue;
161     if (witnessedValue == expectedValue) {
162         // Only stack allocations
163 
164         // 1. Add waiter
165         auto waiter = EtsSharedMemory::Waiter(byteOffset);
166         thisHandle->LinkWaiter(waiter);
167         LOG(DEBUG, ATOMICS) << "Wait: added waiter: " << reinterpret_cast<size_t>(&waiter)
168                             << ", list: " << PrintWaiters(thisHandle);
169 
170         // 2. Wait
171         bool timedOut = false;
172         while (!waiter.IsNotified() && !timedOut) {
173             ScopedNativeCodeThread nCv(coroutine);
174 
175             timedOut = waiter.Wait(timeout);
176             LOG(DEBUG, ATOMICS) << "Wait: woke up, waiter: " << reinterpret_cast<size_t>(&waiter);
177         }
178 
179         // 3. Remove waiter
180         thisHandle->UnlinkWaiter(waiter);
181         LOG(DEBUG, ATOMICS) << "Wait: removed waiter: " << reinterpret_cast<size_t>(&waiter)
182                             << ", list: " << PrintWaiters(thisHandle);
183         return timedOut ? EtsSharedMemory::WaitResult::TIMED_OUT : EtsSharedMemory::WaitResult::OK;
184     }
185 
186     LOG(DEBUG, ATOMICS) << "Wait: not-equal, returning";
187     return EtsSharedMemory::WaitResult::NOT_EQUAL;
188 }
189 
190 }  // namespace
191 
WaitI32(uint32_t byteOffset,int32_t expectedValue,std::optional<uint64_t> timeout)192 EtsSharedMemory::WaitResult EtsSharedMemory::WaitI32(uint32_t byteOffset, int32_t expectedValue,
193                                                      std::optional<uint64_t> timeout)
194 {
195     return Wait<int32_t, uint32_t>(this, byteOffset, expectedValue, timeout);
196 }
197 
WaitI64(uint32_t byteOffset,int64_t expectedValue,std::optional<uint64_t> timeout)198 EtsSharedMemory::WaitResult EtsSharedMemory::WaitI64(uint32_t byteOffset, int64_t expectedValue,
199                                                      std::optional<uint64_t> timeout)
200 {
201     return Wait<int64_t, uint64_t>(this, byteOffset, expectedValue, timeout);
202 }
203 
NotifyI32(uint32_t byteOffset,std::optional<uint32_t> count)204 int32_t EtsSharedMemory::NotifyI32(uint32_t byteOffset, std::optional<uint32_t> count)
205 {
206     auto coroutine = EtsCoroutine::GetCurrent();
207     [[maybe_unused]] EtsHandleScope scope(coroutine);
208     EtsHandle<EtsSharedMemory> thisHandle(coroutine, this);
209 
210     ScopedNativeCodeThread n(coroutine);
211     os::memory::LockHolder lock(coroutine->GetPandaVM()->GetAtomicsMutex());
212     ScopedManagedCodeThread m(coroutine);
213 
214     auto waiter = thisHandle->GetHeadWaiter();
215     uint32_t notifiedCount = 0;
216     LOG(DEBUG, ATOMICS) << "Notify: started, head waiter: " << reinterpret_cast<size_t>(waiter);
217     while (waiter != nullptr && (!count.has_value() || notifiedCount < count.value())) {
218         LOG(DEBUG, ATOMICS) << "Notify: vising waiter: " << reinterpret_cast<size_t>(waiter)
219                             << ", list: " << PrintWaiters(thisHandle);
220         if (waiter->GetOffset() == byteOffset) {
221             ScopedNativeCodeThread nCv(coroutine);
222 
223             waiter->SetNotified();
224             LOG(DEBUG, ATOMICS) << "Notify: notifying waiter " << reinterpret_cast<size_t>(waiter)
225                                 << ", list: " << PrintWaiters(thisHandle);
226             waiter->SignalAll();
227             notifiedCount += 1;
228         }
229         waiter = waiter->GetNext();
230     }
231     LOG(DEBUG, ATOMICS) << "Notify: notified " << notifiedCount << " waiters";
232 
233     return static_cast<int32_t>(notifiedCount);
234 }
235 
236 }  // namespace ark::ets