• 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 "libpandabase/utils/utils.h"
17 #include "plugins/ets/runtime/ets_exceptions.h"
18 #include "plugins/ets/runtime/ets_panda_file_items.h"
19 #include "plugins/ets/runtime/ets_utils.h"
20 #include "plugins/ets/runtime/types/ets_method.h"
21 #include "plugins/ets/runtime/types/ets_object.h"
22 #include "plugins/ets/runtime/types/ets_string.h"
23 #include "runtime/include/thread.h"
24 #include "runtime/include/thread_scopes.h"
25 #include "runtime/handle_scope.h"
26 #include "runtime/handle_scope-inl.h"
27 #include "runtime/mem/refstorage/reference.h"
28 
29 namespace ark::ets::intrinsics {
30 
31 /**
32  * Class tracks GC tasks already processed by GC.
33  * Also the class tracks concurrent mark GC phase and calls
34  * the callback if it specified.
35  */
36 class GCTaskTracker : public mem::GCListener {
37 public:
38     void InitIfNeeded(mem::GC *gc);
39     bool IsInitialized();
40     void AddTaskId(uint64_t id);
41     bool HasId(uint64_t id);
42     void SetCallbackForTask(uint32_t taskId, mem::Reference *callbackRef);
43     void GCStarted(const GCTask &task, size_t heapSize) override;
44     void GCPhaseStarted(mem::GCPhase phase) override;
45     void GCFinished(const GCTask &task, size_t heapSizeBeforeGc, size_t heapSize) override;
46     void RemoveId(uint64_t id);
47 
48 private:
49     bool initialized_ = false;
50     std::vector<uint64_t> taskIds_ GUARDED_BY(lock_);
51     uint32_t currentTaskId_ = 0;
52     uint32_t callbackTaskId_ = 0;
53     mem::Reference *callbackRef_ = nullptr;
54     os::memory::Mutex lock_;
55 };
56 
InitIfNeeded(mem::GC * gc)57 void GCTaskTracker::InitIfNeeded(mem::GC *gc)
58 {
59     if (initialized_) {
60         return;
61     }
62     gc->AddListener(this);
63     initialized_ = true;
64 }
65 
IsInitialized()66 bool GCTaskTracker::IsInitialized()
67 {
68     return initialized_;
69 }
70 
AddTaskId(uint64_t id)71 void GCTaskTracker::AddTaskId(uint64_t id)
72 {
73     os::memory::LockHolder lock(lock_);
74     taskIds_.push_back(id);
75 }
76 
HasId(uint64_t id)77 bool GCTaskTracker::HasId(uint64_t id)
78 {
79     os::memory::LockHolder lock(lock_);
80     return std::find(taskIds_.begin(), taskIds_.end(), id) != taskIds_.end();
81 }
82 
SetCallbackForTask(uint32_t taskId,mem::Reference * callbackRef)83 void GCTaskTracker::SetCallbackForTask(uint32_t taskId, mem::Reference *callbackRef)
84 {
85     callbackTaskId_ = taskId;
86     callbackRef_ = callbackRef;
87 }
88 
GCStarted(const GCTask & task,size_t heapSize)89 void GCTaskTracker::GCStarted(const GCTask &task, [[maybe_unused]] size_t heapSize)
90 {
91     currentTaskId_ = task.GetId();
92 }
93 
GCPhaseStarted(mem::GCPhase phase)94 void GCTaskTracker::GCPhaseStarted(mem::GCPhase phase)
95 {
96     if (phase != mem::GCPhase::GC_PHASE_MARK || callbackRef_ == nullptr || currentTaskId_ != callbackTaskId_) {
97         return;
98     }
99     auto *coroutine = EtsCoroutine::GetCurrent();
100     auto *obj = reinterpret_cast<EtsObject *>(coroutine->GetPandaVM()->GetGlobalObjectStorage()->Get(callbackRef_));
101     Value arg(obj->GetCoreType());
102     os::memory::ReadLockHolder lock(*coroutine->GetPandaVM()->GetRendezvous()->GetMutatorLock());
103     LambdaUtils::InvokeVoid(coroutine, obj);
104 }
105 
GCFinished(const GCTask & task,size_t heapSizeBeforeGc,size_t heapSize)106 void GCTaskTracker::GCFinished(const GCTask &task, [[maybe_unused]] size_t heapSizeBeforeGc,
107                                [[maybe_unused]] size_t heapSize)
108 {
109     RemoveId(task.GetId());
110 }
111 
RemoveId(uint64_t id)112 void GCTaskTracker::RemoveId(uint64_t id)
113 {
114     currentTaskId_ = 0;
115     if (id == callbackTaskId_ && callbackRef_ != nullptr) {
116         EtsCoroutine::GetCurrent()->GetPandaVM()->GetGlobalObjectStorage()->Remove(callbackRef_);
117         callbackRef_ = nullptr;
118     }
119     if (id != 0) {
120         os::memory::LockHolder lock(lock_);
121         auto it = std::find(taskIds_.begin(), taskIds_.end(), id);
122         // There may be no such id if the corresponding GC has been triggered not by startGC
123         if (it != taskIds_.end()) {
124             taskIds_.erase(it);
125         }
126     }
127 }
128 
129 // NOLINTNEXTLINE(fuchsia-statically-constructed-objects)
130 GCTaskTracker g_gGctaskTracker;
131 
ClampToSizeT(EtsLong n)132 static inline size_t ClampToSizeT(EtsLong n)
133 {
134     if constexpr (sizeof(EtsLong) > sizeof(size_t)) {
135         if (UNLIKELY(n > static_cast<EtsLong>(std::numeric_limits<size_t>::max()))) {
136             return std::numeric_limits<size_t>::max();
137         }
138     }
139     return n;
140 }
141 
GCCauseFromInt(EtsInt cause)142 static GCTaskCause GCCauseFromInt(EtsInt cause)
143 {
144     if (cause == 0_I) {
145         return GCTaskCause::YOUNG_GC_CAUSE;
146     }
147     if (cause == 1_I) {
148         return GCTaskCause::HEAP_USAGE_THRESHOLD_CAUSE;
149     }
150     if (cause == 2_I) {
151         return GCTaskCause::MIXED;
152     }
153     if (cause == 3_I) {
154         return GCTaskCause::OOM_CAUSE;
155     }
156     if (cause == 4_I) {
157         return GCTaskCause::CROSSREF_CAUSE;
158     }
159     return GCTaskCause::INVALID_CAUSE;
160 }
161 
162 /**
163  * The function triggers specific GC.
164  * @param cause - integer denotes type of GC. Possible values are: YOUNG_CAUSE = 0, THRESHOLD_CAUSE = 1,
165  *                MIXED_CAUSE = 2, FULL_CAUSE = 3
166  * @param isRunGcInPlace - option to run GC in place
167  * @return gc id. The id should be passed to waitForFinishGC to ensure the GC is finished.
168  *  - The function may return 0 in case the GC is executed in-place. It means there is no need to wait such GC.
169  *  - The function may return -1 in case the task is canceled.
170  */
StdGCStartGC(EtsInt cause,EtsObject * callback,EtsBoolean isRunGcInPlace)171 extern "C" EtsLong StdGCStartGC(EtsInt cause, EtsObject *callback, EtsBoolean isRunGcInPlace)
172 {
173     auto *coroutine = EtsCoroutine::GetCurrent();
174     ASSERT(coroutine != nullptr);
175     bool runGcInPlace = (isRunGcInPlace == 1) ? true : Runtime::GetOptions().IsRunGcInPlace("ets");
176 
177     GCTaskCause reason = GCCauseFromInt(cause);
178     if (reason == GCTaskCause::INVALID_CAUSE) {
179         ThrowEtsException(coroutine, panda_file_items::class_descriptors::ILLEGAL_ARGUMENT_EXCEPTION,
180                           "Invalid GC cause");
181         return -1;
182     }
183     auto *gc = coroutine->GetVM()->GetGC();
184     if (!gc->CheckGCCause(reason)) {
185         PandaStringStream eMsg;
186         eMsg << mem::GCStringFromType(gc->GetType()) << " does not support " << reason << " cause";
187         ThrowEtsException(coroutine, panda_file_items::class_descriptors::ILLEGAL_ARGUMENT_EXCEPTION, eMsg.str());
188         return -1;
189     }
190     g_gGctaskTracker.InitIfNeeded(gc);
191     auto task = MakePandaUnique<GCTask>(reason);
192     uint32_t id = task->GetId();
193     if (callback != nullptr) {
194         auto *callbackRef = coroutine->GetPandaVM()->GetGlobalObjectStorage()->Add(callback->GetCoreType(),
195                                                                                    mem::Reference::ObjectType::GLOBAL);
196         g_gGctaskTracker.SetCallbackForTask(id, callbackRef);
197         // Run GC in place, because need to run callback in managed part
198         runGcInPlace = true;
199     }
200 
201     // Young GC runs in place
202     if (reason == GCTaskCause::YOUNG_GC_CAUSE) {
203         runGcInPlace = true;
204     }
205     if (runGcInPlace) {
206         return gc->WaitForGCInManaged(*task) ? 0 : -1;
207     }
208     // Run GC in GC-thread
209     if ((reason == GCTaskCause::HEAP_USAGE_THRESHOLD_CAUSE) && gc->IsPostponeEnabled()) {
210         ThrowEtsException(coroutine, panda_file_items::class_descriptors::ILLEGAL_STATE_EXCEPTION,
211                           "Calling GC threshold not in place after calling postponeGCStart");
212         return -1;
213     }
214     g_gGctaskTracker.AddTaskId(id);
215     if (!gc->Trigger(std::move(task))) {
216         g_gGctaskTracker.RemoveId(id);
217         return -1;
218     }
219     return static_cast<EtsLong>(id);
220 }
221 
222 /**
223  * The function returns when the specified GC gets finished.
224  * @param gc_id - id of the GC which is returned by startGc.
225  * If gc_id is 0 or -1 the function returns immediately.
226  */
StdGCWaitForFinishGC(EtsLong gcId)227 extern "C" void StdGCWaitForFinishGC(EtsLong gcId)
228 {
229     ManagedThread *thread = ManagedThread::GetCurrent();
230     ASSERT(thread != nullptr);
231     if (gcId <= 0) {
232         return;
233     }
234     auto id = static_cast<uint64_t>(gcId);
235     ASSERT(g_gGctaskTracker.IsInitialized());
236     ScopedNativeCodeThread s(thread);
237     while (g_gGctaskTracker.HasId(id)) {
238         constexpr uint64_t WAIT_TIME_MS = 10;
239         os::thread::NativeSleep(WAIT_TIME_MS);
240     }
241 }
242 
StdGCIsScheduledGCTriggered()243 extern "C" EtsBoolean StdGCIsScheduledGCTriggered()
244 {
245     auto thread = ManagedThread::GetCurrent();
246     ASSERT(thread != nullptr);
247     auto *vm = thread->GetVM();
248     auto *trigger = vm->GetGCTrigger();
249 
250     if (trigger->GetType() != mem::GCTriggerType::ON_NTH_ALLOC) {
251         return ToEtsBoolean(false);
252     }
253     auto schedTrigger = reinterpret_cast<mem::SchedGCOnNthAllocTrigger *>(vm->GetGCTrigger());
254     return ToEtsBoolean(schedTrigger->IsTriggered());
255 }
256 
StdGCPostponeGCStart()257 extern "C" void StdGCPostponeGCStart()
258 {
259     auto coroutine = EtsCoroutine::GetCurrent();
260     ASSERT(coroutine != nullptr);
261     auto *gc = coroutine->GetVM()->GetGC();
262     if (!gc->IsPostponeGCSupported()) {
263         ThrowEtsException(coroutine, panda_file_items::class_descriptors::UNSUPPORTED_OPERATION_EXCEPTION,
264                           "GC postpone is not supported for this GC type");
265         return;
266     }
267     if (gc->IsPostponeEnabled()) {
268         ThrowEtsException(coroutine, panda_file_items::class_descriptors::ILLEGAL_STATE_EXCEPTION,
269                           "Calling postponeGCStart without calling postponeGCEnd");
270         return;
271     }
272     gc->PostponeGCStart();
273 }
274 
StdGCPostponeGCEnd()275 extern "C" void StdGCPostponeGCEnd()
276 {
277     auto coroutine = EtsCoroutine::GetCurrent();
278     ASSERT(coroutine != nullptr);
279     auto *gc = coroutine->GetVM()->GetGC();
280     if (!gc->IsPostponeGCSupported()) {
281         ThrowEtsException(coroutine, panda_file_items::class_descriptors::UNSUPPORTED_OPERATION_EXCEPTION,
282                           "GC postpone is not supported for this GC type");
283         return;
284     }
285     if (!gc->IsPostponeEnabled()) {
286         ThrowEtsException(coroutine, panda_file_items::class_descriptors::ILLEGAL_STATE_EXCEPTION,
287                           "Calling postponeGCEnd without calling postponeGCStart");
288         return;
289     }
290     gc->PostponeGCEnd();
291 }
292 
293 template <class ResArrayType>
StdGCAllocatePinnedPrimitiveTypeArray(EtsLong length)294 [[nodiscard]] static ResArrayType *StdGCAllocatePinnedPrimitiveTypeArray(EtsLong length)
295 {
296     auto *coroutine = EtsCoroutine::GetCurrent();
297     ASSERT(coroutine != nullptr);
298 
299     if (length < 0) {
300         ThrowEtsException(coroutine, panda_file_items::class_descriptors::NEGATIVE_ARRAY_SIZE_ERROR,
301                           "The value must be non negative");
302         return nullptr;
303     }
304     auto *vm = coroutine->GetVM();
305     if (!vm->GetGC()->IsPinningSupported()) {
306         ThrowEtsException(coroutine, panda_file_items::class_descriptors::UNSUPPORTED_OPERATION_EXCEPTION,
307                           "Object pinning does not support with current GC");
308         return nullptr;
309     }
310     auto *array = ResArrayType::Create(length, SpaceType::SPACE_TYPE_OBJECT, true);
311 
312     if (array == nullptr) {
313         PandaStringStream ss;
314         ss << "Could not allocate array of " << length << " elements";
315         ThrowEtsException(coroutine, panda_file_items::class_descriptors::OUT_OF_MEMORY_ERROR, ss.str());
316         return nullptr;
317     }
318 
319     return array;
320 }
321 
StdGCAllocatePinnedBooleanArray(EtsLong length)322 extern "C" EtsBooleanArray *StdGCAllocatePinnedBooleanArray(EtsLong length)
323 {
324     return StdGCAllocatePinnedPrimitiveTypeArray<EtsBooleanArray>(length);
325 }
326 
StdGCAllocatePinnedByteArray(EtsLong length)327 extern "C" EtsByteArray *StdGCAllocatePinnedByteArray(EtsLong length)
328 {
329     return StdGCAllocatePinnedPrimitiveTypeArray<EtsByteArray>(length);
330 }
331 
StdGCAllocatePinnedCharArray(EtsLong length)332 extern "C" EtsCharArray *StdGCAllocatePinnedCharArray(EtsLong length)
333 {
334     return StdGCAllocatePinnedPrimitiveTypeArray<EtsCharArray>(length);
335 }
336 
StdGCAllocatePinnedShortArray(EtsLong length)337 extern "C" EtsShortArray *StdGCAllocatePinnedShortArray(EtsLong length)
338 {
339     return StdGCAllocatePinnedPrimitiveTypeArray<EtsShortArray>(length);
340 }
341 
StdGCAllocatePinnedIntArray(EtsLong length)342 extern "C" EtsIntArray *StdGCAllocatePinnedIntArray(EtsLong length)
343 {
344     return StdGCAllocatePinnedPrimitiveTypeArray<EtsIntArray>(length);
345 }
346 
StdGCAllocatePinnedLongArray(EtsLong length)347 extern "C" EtsLongArray *StdGCAllocatePinnedLongArray(EtsLong length)
348 {
349     return StdGCAllocatePinnedPrimitiveTypeArray<EtsLongArray>(length);
350 }
351 
StdGCAllocatePinnedFloatArray(EtsLong length)352 extern "C" EtsFloatArray *StdGCAllocatePinnedFloatArray(EtsLong length)
353 {
354     return StdGCAllocatePinnedPrimitiveTypeArray<EtsFloatArray>(length);
355 }
356 
StdGCAllocatePinnedDoubleArray(EtsLong length)357 extern "C" EtsDoubleArray *StdGCAllocatePinnedDoubleArray(EtsLong length)
358 {
359     return StdGCAllocatePinnedPrimitiveTypeArray<EtsDoubleArray>(length);
360 }
361 
StdGCGetObjectSpaceType(EtsObject * obj)362 extern "C" EtsInt StdGCGetObjectSpaceType(EtsObject *obj)
363 {
364     ASSERT(obj != nullptr);
365     auto *vm = Thread::GetCurrent()->GetVM();
366     SpaceType objSpaceType =
367         PoolManager::GetMmapMemPool()->GetSpaceTypeForAddr(static_cast<void *>(obj->GetCoreType()));
368     if (objSpaceType == SpaceType::SPACE_TYPE_OBJECT && vm->GetGC()->IsGenerational()) {
369         if (vm->GetHeapManager()->IsObjectInYoungSpace(obj->GetCoreType())) {
370             const EtsInt youngSpace = 4;
371             return youngSpace;
372         }
373         const EtsInt tenuredSpace = 5;
374         return tenuredSpace;
375     }
376     return SpaceTypeToIndex(objSpaceType);
377 }
378 
StdGCPinObject(EtsObject * obj)379 extern "C" void StdGCPinObject(EtsObject *obj)
380 {
381     ASSERT(obj != nullptr);
382     auto *coroutine = EtsCoroutine::GetCurrent();
383     ASSERT(coroutine != nullptr);
384 
385     auto *vm = coroutine->GetVM();
386     auto *gc = vm->GetGC();
387     if (!gc->IsPinningSupported()) {
388         ThrowEtsException(coroutine, panda_file_items::class_descriptors::UNSUPPORTED_OPERATION_EXCEPTION,
389                           "Object pinning does not support with current gc");
390         return;
391     }
392     vm->GetHeapManager()->PinObject(obj->GetCoreType());
393 }
394 
StdGCUnpinObject(EtsObject * obj)395 extern "C" void StdGCUnpinObject(EtsObject *obj)
396 {
397     ASSERT(obj != nullptr);
398     auto *vm = Thread::GetCurrent()->GetVM();
399     vm->GetHeapManager()->UnpinObject(obj->GetCoreType());
400 }
401 
StdGCGetObjectAddress(EtsObject * obj)402 extern "C" EtsLong StdGCGetObjectAddress(EtsObject *obj)
403 {
404     ASSERT(obj != nullptr);
405     return reinterpret_cast<EtsLong>(obj);
406 }
407 
StdGetObjectSize(EtsObject * obj)408 extern "C" EtsLong StdGetObjectSize(EtsObject *obj)
409 {
410     ASSERT(obj != nullptr);
411     return static_cast<EtsLong>(obj->GetCoreType()->ObjectSize());
412 }
413 
414 // Function schedules GC before n-th allocation by setting counter to the specific GC trigger.
415 // Another call may reset the counter.  In this case the last counter will be used to trigger the GC.
StdGCScheduleGCAfterNthAlloc(EtsInt counter,EtsInt cause)416 extern "C" void StdGCScheduleGCAfterNthAlloc(EtsInt counter, EtsInt cause)
417 {
418     auto *coroutine = EtsCoroutine::GetCurrent();
419     ASSERT(coroutine != nullptr);
420 
421     if (counter < 0) {
422         ThrowEtsException(coroutine, panda_file_items::class_descriptors::ILLEGAL_ARGUMENT_EXCEPTION,
423                           "counter for allocation is negative");
424         return;
425     }
426     GCTaskCause reason = GCCauseFromInt(cause);
427     if (reason == GCTaskCause::INVALID_CAUSE) {
428         ThrowEtsException(coroutine, panda_file_items::class_descriptors::ILLEGAL_ARGUMENT_EXCEPTION,
429                           "Invalid GC cause");
430         return;
431     }
432 
433     auto *vm = coroutine->GetVM();
434     auto *gc = vm->GetGC();
435     if (!gc->CheckGCCause(reason)) {
436         PandaStringStream eMsg;
437         eMsg << mem::GCStringFromType(gc->GetType()) << " does not support " << reason << " cause";
438         ThrowEtsException(coroutine, panda_file_items::class_descriptors::ILLEGAL_ARGUMENT_EXCEPTION, eMsg.str());
439         return;
440     }
441     mem::GCTrigger *trigger = vm->GetGCTrigger();
442     if (trigger->GetType() != mem::GCTriggerType::ON_NTH_ALLOC) {
443         ThrowEtsException(coroutine, panda_file_items::class_descriptors::UNSUPPORTED_OPERATION_EXCEPTION,
444                           "VM is running with unsupported GC trigger");
445         return;
446     }
447     auto schedTrigger = reinterpret_cast<mem::SchedGCOnNthAllocTrigger *>(trigger);
448     schedTrigger->ScheduleGc(reason, counter);
449 }
450 
StdGetFreeHeapSize()451 extern "C" EtsLong StdGetFreeHeapSize()
452 {
453     const auto *coroutine = EtsCoroutine::GetCurrent();
454     ASSERT(coroutine != nullptr);
455     return static_cast<EtsLong>(coroutine->GetPandaVM()->GetHeapManager()->GetFreeMemory());
456 }
457 
StdGetUsedHeapSize()458 extern "C" EtsLong StdGetUsedHeapSize()
459 {
460     const auto *coroutine = EtsCoroutine::GetCurrent();
461     ASSERT(coroutine != nullptr);
462     auto *headManager = coroutine->GetPandaVM()->GetHeapManager();
463     auto totalMemory = headManager->GetTotalMemory();
464     auto freeMemory = headManager->GetFreeMemory();
465     ASSERT(totalMemory >= freeMemory);
466     return static_cast<EtsLong>(totalMemory - freeMemory);
467 }
468 
StdGetReservedHeapSize()469 extern "C" EtsLong StdGetReservedHeapSize()
470 {
471     const auto *coroutine = EtsCoroutine::GetCurrent();
472     ASSERT(coroutine != nullptr);
473     return static_cast<EtsLong>(coroutine->GetPandaVM()->GetHeapManager()->GetMaxMemory());
474 }
475 
StdGCRegisterNativeAllocation(EtsLong size)476 extern "C" void StdGCRegisterNativeAllocation(EtsLong size)
477 {
478     auto *coroutine = EtsCoroutine::GetCurrent();
479     ASSERT(coroutine != nullptr);
480     if (size < 0) {
481         ThrowEtsException(coroutine, panda_file_items::class_descriptors::NEGATIVE_ARRAY_SIZE_ERROR,
482                           "The value must be non negative");
483         return;
484     }
485 
486     ScopedNativeCodeThread s(ManagedThread::GetCurrent());
487     coroutine->GetVM()->GetGC()->RegisterNativeAllocation(ClampToSizeT(size));
488 }
489 
StdGCRegisterNativeFree(EtsLong size)490 extern "C" void StdGCRegisterNativeFree(EtsLong size)
491 {
492     auto *coroutine = EtsCoroutine::GetCurrent();
493     ASSERT(coroutine != nullptr);
494     if (size < 0) {
495         ThrowEtsException(coroutine, panda_file_items::class_descriptors::NEGATIVE_ARRAY_SIZE_ERROR,
496                           "The value must be non negative");
497         return;
498     }
499 
500     ScopedNativeCodeThread s(ManagedThread::GetCurrent());
501     coroutine->GetVM()->GetGC()->RegisterNativeFree(ClampToSizeT(size));
502 }
503 
504 }  // namespace ark::ets::intrinsics
505