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