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