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 return GCTaskCause::INVALID_CAUSE;
157 }
158
159 /**
160 * The function triggers specific GC.
161 * @param cause - integer denotes type of GC. Possible values are: YOUNG_CAUSE = 0, THRESHOLD_CAUSE = 1,
162 * MIXED_CAUSE = 2, FULL_CAUSE = 3
163 * @return gc id. The id should be passed to waitForFinishGC to ensure the GC is finished.
164 * - The function may return 0 in case the GC is executed in-place. It means there is no need to wait such GC.
165 * - The function may return -1 in case the task is canceled.
166 */
StdGCStartGC(EtsInt cause,EtsObject * callback)167 extern "C" EtsLong StdGCStartGC(EtsInt cause, EtsObject *callback)
168 {
169 auto *coroutine = EtsCoroutine::GetCurrent();
170 ASSERT(coroutine != nullptr);
171 bool runGcInPlace = Runtime::GetOptions().IsRunGcInPlace("ets");
172
173 GCTaskCause reason = GCCauseFromInt(cause);
174 if (reason == GCTaskCause::INVALID_CAUSE) {
175 ThrowEtsException(coroutine, panda_file_items::class_descriptors::ILLEGAL_ARGUMENT_EXCEPTION,
176 "Invalid GC cause");
177 return -1;
178 }
179 auto *gc = coroutine->GetVM()->GetGC();
180 if (!gc->CheckGCCause(reason)) {
181 PandaStringStream eMsg;
182 eMsg << mem::GCStringFromType(gc->GetType()) << " does not support " << reason << " cause";
183 ThrowEtsException(coroutine, panda_file_items::class_descriptors::ILLEGAL_ARGUMENT_EXCEPTION, eMsg.str());
184 return -1;
185 }
186 g_gGctaskTracker.InitIfNeeded(gc);
187 auto task = MakePandaUnique<GCTask>(reason);
188 uint32_t id = task->GetId();
189 if (callback != nullptr) {
190 auto *callbackRef = coroutine->GetPandaVM()->GetGlobalObjectStorage()->Add(callback->GetCoreType(),
191 mem::Reference::ObjectType::GLOBAL);
192 g_gGctaskTracker.SetCallbackForTask(id, callbackRef);
193 // Run GC in place, because need to run callback in managed part
194 runGcInPlace = true;
195 }
196
197 // Young GC runs in place
198 if (reason == GCTaskCause::YOUNG_GC_CAUSE) {
199 runGcInPlace = true;
200 }
201 if (runGcInPlace) {
202 return gc->WaitForGCInManaged(*task) ? 0 : -1;
203 }
204 // Run GC in GC-thread
205 if ((reason == GCTaskCause::HEAP_USAGE_THRESHOLD_CAUSE) && gc->IsPostponeEnabled()) {
206 ThrowEtsException(coroutine, panda_file_items::class_descriptors::ILLEGAL_STATE_EXCEPTION,
207 "Calling GC threshold not in place after calling postponeGCStart");
208 return -1;
209 }
210 g_gGctaskTracker.AddTaskId(id);
211 if (!gc->Trigger(std::move(task))) {
212 g_gGctaskTracker.RemoveId(id);
213 return -1;
214 }
215 return static_cast<EtsLong>(id);
216 }
217
218 /**
219 * The function returns when the specified GC gets finished.
220 * @param gc_id - id of the GC which is returned by startGc.
221 * If gc_id is 0 or -1 the function returns immediately.
222 */
StdGCWaitForFinishGC(EtsLong gcId)223 extern "C" void StdGCWaitForFinishGC(EtsLong gcId)
224 {
225 ManagedThread *thread = ManagedThread::GetCurrent();
226 ASSERT(thread != nullptr);
227 if (gcId <= 0) {
228 return;
229 }
230 auto id = static_cast<uint64_t>(gcId);
231 ASSERT(g_gGctaskTracker.IsInitialized());
232 ScopedNativeCodeThread s(thread);
233 while (g_gGctaskTracker.HasId(id)) {
234 constexpr uint64_t WAIT_TIME_MS = 10;
235 os::thread::NativeSleep(WAIT_TIME_MS);
236 }
237 }
238
StdGCIsScheduledGCTriggered()239 extern "C" EtsBoolean StdGCIsScheduledGCTriggered()
240 {
241 auto thread = ManagedThread::GetCurrent();
242 ASSERT(thread != nullptr);
243 auto *vm = thread->GetVM();
244 auto *trigger = vm->GetGCTrigger();
245
246 if (trigger->GetType() != mem::GCTriggerType::ON_NTH_ALLOC) {
247 return ToEtsBoolean(false);
248 }
249 auto schedTrigger = reinterpret_cast<mem::SchedGCOnNthAllocTrigger *>(vm->GetGCTrigger());
250 return ToEtsBoolean(schedTrigger->IsTriggered());
251 }
252
StdGCPostponeGCStart()253 extern "C" void StdGCPostponeGCStart()
254 {
255 auto coroutine = EtsCoroutine::GetCurrent();
256 ASSERT(coroutine != nullptr);
257 auto *gc = coroutine->GetVM()->GetGC();
258 if (!gc->IsPostponeGCSupported()) {
259 ThrowEtsException(coroutine, panda_file_items::class_descriptors::UNSUPPORTED_OPERATION_EXCEPTION,
260 "GC postpone is not supported for this GC type");
261 return;
262 }
263 if (gc->IsPostponeEnabled()) {
264 ThrowEtsException(coroutine, panda_file_items::class_descriptors::ILLEGAL_STATE_EXCEPTION,
265 "Calling postponeGCStart without calling postponeGCEnd");
266 return;
267 }
268 gc->PostponeGCStart();
269 }
270
StdGCPostponeGCEnd()271 extern "C" void StdGCPostponeGCEnd()
272 {
273 auto coroutine = EtsCoroutine::GetCurrent();
274 ASSERT(coroutine != nullptr);
275 auto *gc = coroutine->GetVM()->GetGC();
276 if (!gc->IsPostponeGCSupported()) {
277 ThrowEtsException(coroutine, panda_file_items::class_descriptors::UNSUPPORTED_OPERATION_EXCEPTION,
278 "GC postpone is not supported for this GC type");
279 return;
280 }
281 if (!gc->IsPostponeEnabled()) {
282 ThrowEtsException(coroutine, panda_file_items::class_descriptors::ILLEGAL_STATE_EXCEPTION,
283 "Calling postponeGCEnd without calling postponeGCStart");
284 return;
285 }
286 gc->PostponeGCEnd();
287 }
288
289 template <class ResArrayType>
StdGCAllocatePinnedPrimitiveTypeArray(EtsLong length)290 [[nodiscard]] static ResArrayType *StdGCAllocatePinnedPrimitiveTypeArray(EtsLong length)
291 {
292 auto *coroutine = EtsCoroutine::GetCurrent();
293 ASSERT(coroutine != nullptr);
294
295 if (length < 0) {
296 ThrowEtsException(coroutine, panda_file_items::class_descriptors::NEGATIVE_ARRAY_SIZE_ERROR,
297 "The value must be non negative");
298 return nullptr;
299 }
300 auto *vm = coroutine->GetVM();
301 if (!vm->GetGC()->IsPinningSupported()) {
302 ThrowEtsException(coroutine, panda_file_items::class_descriptors::UNSUPPORTED_OPERATION_EXCEPTION,
303 "Object pinning does not support with current GC");
304 return nullptr;
305 }
306 auto *array = ResArrayType::Create(length, SpaceType::SPACE_TYPE_OBJECT, true);
307
308 if (array == nullptr) {
309 PandaStringStream ss;
310 ss << "Could not allocate array of " << length << " elements";
311 ThrowEtsException(coroutine, panda_file_items::class_descriptors::OUT_OF_MEMORY_ERROR, ss.str());
312 return nullptr;
313 }
314
315 return array;
316 }
317
StdGCAllocatePinnedBooleanArray(EtsLong length)318 extern "C" EtsBooleanArray *StdGCAllocatePinnedBooleanArray(EtsLong length)
319 {
320 return StdGCAllocatePinnedPrimitiveTypeArray<EtsBooleanArray>(length);
321 }
322
StdGCAllocatePinnedByteArray(EtsLong length)323 extern "C" EtsByteArray *StdGCAllocatePinnedByteArray(EtsLong length)
324 {
325 return StdGCAllocatePinnedPrimitiveTypeArray<EtsByteArray>(length);
326 }
327
StdGCAllocatePinnedCharArray(EtsLong length)328 extern "C" EtsCharArray *StdGCAllocatePinnedCharArray(EtsLong length)
329 {
330 return StdGCAllocatePinnedPrimitiveTypeArray<EtsCharArray>(length);
331 }
332
StdGCAllocatePinnedShortArray(EtsLong length)333 extern "C" EtsShortArray *StdGCAllocatePinnedShortArray(EtsLong length)
334 {
335 return StdGCAllocatePinnedPrimitiveTypeArray<EtsShortArray>(length);
336 }
337
StdGCAllocatePinnedIntArray(EtsLong length)338 extern "C" EtsIntArray *StdGCAllocatePinnedIntArray(EtsLong length)
339 {
340 return StdGCAllocatePinnedPrimitiveTypeArray<EtsIntArray>(length);
341 }
342
StdGCAllocatePinnedLongArray(EtsLong length)343 extern "C" EtsLongArray *StdGCAllocatePinnedLongArray(EtsLong length)
344 {
345 return StdGCAllocatePinnedPrimitiveTypeArray<EtsLongArray>(length);
346 }
347
StdGCAllocatePinnedFloatArray(EtsLong length)348 extern "C" EtsFloatArray *StdGCAllocatePinnedFloatArray(EtsLong length)
349 {
350 return StdGCAllocatePinnedPrimitiveTypeArray<EtsFloatArray>(length);
351 }
352
StdGCAllocatePinnedDoubleArray(EtsLong length)353 extern "C" EtsDoubleArray *StdGCAllocatePinnedDoubleArray(EtsLong length)
354 {
355 return StdGCAllocatePinnedPrimitiveTypeArray<EtsDoubleArray>(length);
356 }
357
StdGCGetObjectSpaceType(EtsObject * obj)358 extern "C" EtsInt StdGCGetObjectSpaceType(EtsObject *obj)
359 {
360 if (obj == nullptr) {
361 auto *coroutine = EtsCoroutine::GetCurrent();
362 ASSERT(coroutine != nullptr);
363 ThrowEtsException(coroutine, panda_file_items::class_descriptors::NULL_POINTER_ERROR, "Non heap object");
364 return SpaceTypeToIndex(SpaceType::SPACE_TYPE_UNDEFINED);
365 }
366
367 auto *vm = Thread::GetCurrent()->GetVM();
368 SpaceType objSpaceType =
369 PoolManager::GetMmapMemPool()->GetSpaceTypeForAddr(static_cast<void *>(obj->GetCoreType()));
370 if (objSpaceType == SpaceType::SPACE_TYPE_OBJECT && vm->GetGC()->IsGenerational()) {
371 if (vm->GetHeapManager()->IsObjectInYoungSpace(obj->GetCoreType())) {
372 const EtsInt youngSpace = 4;
373 return youngSpace;
374 }
375 const EtsInt tenuredSpace = 5;
376 return tenuredSpace;
377 }
378 return SpaceTypeToIndex(objSpaceType);
379 }
380
StdGCPinObject(EtsObject * obj)381 extern "C" void StdGCPinObject(EtsObject *obj)
382 {
383 auto *coroutine = EtsCoroutine::GetCurrent();
384 ASSERT(coroutine != nullptr);
385 if (obj == nullptr) {
386 ThrowEtsException(coroutine, panda_file_items::class_descriptors::NULL_POINTER_ERROR,
387 "The value must be an object");
388 return;
389 }
390
391 auto *vm = coroutine->GetVM();
392 auto *gc = vm->GetGC();
393 if (!gc->IsPinningSupported()) {
394 ThrowEtsException(coroutine, panda_file_items::class_descriptors::UNSUPPORTED_OPERATION_EXCEPTION,
395 "Object pinning does not support with current gc");
396 return;
397 }
398 vm->GetHeapManager()->PinObject(obj->GetCoreType());
399 }
400
StdGCUnpinObject(EtsObject * obj)401 extern "C" void StdGCUnpinObject(EtsObject *obj)
402 {
403 if (obj == nullptr) {
404 auto *coroutine = EtsCoroutine::GetCurrent();
405 ASSERT(coroutine != nullptr);
406 ThrowEtsException(coroutine, panda_file_items::class_descriptors::NULL_POINTER_ERROR,
407 "The value must be an object");
408 return;
409 }
410
411 auto *vm = Thread::GetCurrent()->GetVM();
412 vm->GetHeapManager()->UnpinObject(obj->GetCoreType());
413 }
414
StdGCGetObjectAddress(EtsObject * obj)415 extern "C" EtsLong StdGCGetObjectAddress(EtsObject *obj)
416 {
417 return obj == nullptr ? 0 : reinterpret_cast<EtsLong>(obj->GetCoreType());
418 }
419
420 // Function schedules GC before n-th allocation by setting counter to the specific GC trigger.
421 // Another call may reset the counter. In this case the last counter will be used to trigger the GC.
StdGCScheduleGCAfterNthAlloc(EtsInt counter,EtsInt cause)422 extern "C" void StdGCScheduleGCAfterNthAlloc(EtsInt counter, EtsInt cause)
423 {
424 auto *coroutine = EtsCoroutine::GetCurrent();
425 ASSERT(coroutine != nullptr);
426
427 if (counter < 0) {
428 ThrowEtsException(coroutine, panda_file_items::class_descriptors::ILLEGAL_ARGUMENT_EXCEPTION,
429 "counter for allocation is negative");
430 return;
431 }
432 GCTaskCause reason = GCCauseFromInt(cause);
433 if (reason == GCTaskCause::INVALID_CAUSE) {
434 ThrowEtsException(coroutine, panda_file_items::class_descriptors::ILLEGAL_ARGUMENT_EXCEPTION,
435 "Invalid GC cause");
436 return;
437 }
438
439 auto *vm = coroutine->GetVM();
440 auto *gc = vm->GetGC();
441 if (!gc->CheckGCCause(reason)) {
442 PandaStringStream eMsg;
443 eMsg << mem::GCStringFromType(gc->GetType()) << " does not support " << reason << " cause";
444 ThrowEtsException(coroutine, panda_file_items::class_descriptors::ILLEGAL_ARGUMENT_EXCEPTION, eMsg.str());
445 return;
446 }
447 mem::GCTrigger *trigger = vm->GetGCTrigger();
448 if (trigger->GetType() != mem::GCTriggerType::ON_NTH_ALLOC) {
449 ThrowEtsException(coroutine, panda_file_items::class_descriptors::UNSUPPORTED_OPERATION_EXCEPTION,
450 "VM is running with unsupported GC trigger");
451 return;
452 }
453 auto schedTrigger = reinterpret_cast<mem::SchedGCOnNthAllocTrigger *>(trigger);
454 schedTrigger->ScheduleGc(reason, counter);
455 }
456
StdGetFreeHeapSize()457 extern "C" EtsLong StdGetFreeHeapSize()
458 {
459 const auto *coroutine = EtsCoroutine::GetCurrent();
460 ASSERT(coroutine != nullptr);
461 return static_cast<EtsLong>(coroutine->GetPandaVM()->GetHeapManager()->GetFreeMemory());
462 }
463
StdGetUsedHeapSize()464 extern "C" EtsLong StdGetUsedHeapSize()
465 {
466 const auto *coroutine = EtsCoroutine::GetCurrent();
467 ASSERT(coroutine != nullptr);
468 auto *headManager = coroutine->GetPandaVM()->GetHeapManager();
469 auto totalMemory = headManager->GetTotalMemory();
470 auto freeMemory = headManager->GetFreeMemory();
471 ASSERT(totalMemory >= freeMemory);
472 return static_cast<EtsLong>(totalMemory - freeMemory);
473 }
474
StdGetReservedHeapSize()475 extern "C" EtsLong StdGetReservedHeapSize()
476 {
477 const auto *coroutine = EtsCoroutine::GetCurrent();
478 ASSERT(coroutine != nullptr);
479 return static_cast<EtsLong>(coroutine->GetPandaVM()->GetHeapManager()->GetMaxMemory());
480 }
481
StdGCRegisterNativeAllocation(EtsLong size)482 extern "C" void StdGCRegisterNativeAllocation(EtsLong size)
483 {
484 auto *coroutine = EtsCoroutine::GetCurrent();
485 ASSERT(coroutine != nullptr);
486 if (size < 0) {
487 ThrowEtsException(coroutine, panda_file_items::class_descriptors::NEGATIVE_ARRAY_SIZE_ERROR,
488 "The value must be non negative");
489 return;
490 }
491
492 ScopedNativeCodeThread s(ManagedThread::GetCurrent());
493 coroutine->GetVM()->GetGC()->RegisterNativeAllocation(ClampToSizeT(size));
494 }
495
StdGCRegisterNativeFree(EtsLong size)496 extern "C" void StdGCRegisterNativeFree(EtsLong size)
497 {
498 auto *coroutine = EtsCoroutine::GetCurrent();
499 ASSERT(coroutine != nullptr);
500 if (size < 0) {
501 ThrowEtsException(coroutine, panda_file_items::class_descriptors::NEGATIVE_ARRAY_SIZE_ERROR,
502 "The value must be non negative");
503 return;
504 }
505
506 ScopedNativeCodeThread s(ManagedThread::GetCurrent());
507 coroutine->GetVM()->GetGC()->RegisterNativeFree(ClampToSizeT(size));
508 }
509
510 } // namespace ark::ets::intrinsics
511