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