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