• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2021-2025 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/intrinsics/gc_task_tracker.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 
28 namespace ark::ets::intrinsics {
29 
ClampToSizeT(EtsLong n)30 static inline size_t ClampToSizeT(EtsLong n)
31 {
32     if constexpr (sizeof(EtsLong) > sizeof(size_t)) {
33         if (UNLIKELY(n > static_cast<EtsLong>(std::numeric_limits<size_t>::max()))) {
34             return std::numeric_limits<size_t>::max();
35         }
36     }
37     return n;
38 }
39 
GCCauseFromInt(EtsInt cause)40 static GCTaskCause GCCauseFromInt(EtsInt cause)
41 {
42     if (cause == 0_I) {
43         return GCTaskCause::YOUNG_GC_CAUSE;
44     }
45     if (cause == 1_I) {
46         return GCTaskCause::HEAP_USAGE_THRESHOLD_CAUSE;
47     }
48     if (cause == 2_I) {
49         return GCTaskCause::MIXED;
50     }
51     if (cause == 3_I) {
52         return GCTaskCause::OOM_CAUSE;
53     }
54     UNREACHABLE();
55     return GCTaskCause::INVALID_CAUSE;
56 }
57 
58 /**
59  * The function triggers specific GC.
60  * @param cause - integer denotes type of GC. Possible values are: YOUNG_CAUSE = 0, THRESHOLD_CAUSE = 1,
61  *                MIXED_CAUSE = 2, FULL_CAUSE = 3
62  * @param isRunGcInPlace - option to run GC in place
63  * @return gc id. The id should be passed to waitForFinishGC to ensure the GC is finished.
64  *  - The function may return 0 in case the GC is executed in-place. It means there is no need to wait such GC.
65  *  - The function may return -1 in case the task is canceled.
66  */
StdGCStartGC(EtsInt cause,EtsObject * callback,EtsBoolean isRunGcInPlace)67 extern "C" EtsLong StdGCStartGC(EtsInt cause, EtsObject *callback, EtsBoolean isRunGcInPlace)
68 {
69     auto *coroutine = EtsCoroutine::GetCurrent();
70     ASSERT(coroutine != nullptr);
71     bool runGcInPlace = (isRunGcInPlace == 1) ? true : Runtime::GetOptions().IsRunGcInPlace("ets");
72 
73     GCTaskCause reason = GCCauseFromInt(cause);
74     auto *gc = coroutine->GetVM()->GetGC();
75     if (!gc->CheckGCCause(reason)) {
76         PandaStringStream eMsg;
77         eMsg << mem::GCStringFromType(gc->GetType()) << " does not support " << reason << " cause";
78         ThrowEtsException(coroutine, panda_file_items::class_descriptors::ILLEGAL_ARGUMENT_EXCEPTION, eMsg.str());
79         return -1;
80     }
81     auto &gcTaskTracker = GCTaskTracker::InitIfNeededAndGet(gc);
82     auto task = MakePandaUnique<GCTask>(reason);
83     uint32_t id = task->GetId();
84     if (callback != nullptr) {
85         auto *callbackRef = coroutine->GetPandaVM()->GetGlobalObjectStorage()->Add(callback->GetCoreType(),
86                                                                                    mem::Reference::ObjectType::GLOBAL);
87         gcTaskTracker.SetCallbackForTask(id, callbackRef);
88         // Run GC in place, because need to run callback in managed part
89         runGcInPlace = true;
90     }
91 
92     // Young GC runs in place
93     if (reason == GCTaskCause::YOUNG_GC_CAUSE) {
94         runGcInPlace = true;
95     }
96     if (runGcInPlace) {
97         return gc->WaitForGCInManaged(*task) ? 0 : -1;
98     }
99     // Run GC in GC-thread
100     if ((reason == GCTaskCause::HEAP_USAGE_THRESHOLD_CAUSE) && gc->IsPostponeEnabled()) {
101         ThrowEtsException(coroutine, panda_file_items::class_descriptors::ILLEGAL_STATE_EXCEPTION,
102                           "Calling GC threshold not in place after calling postponeGCStart");
103         return -1;
104     }
105     gcTaskTracker.AddTaskId(id);
106     if (!gc->Trigger(std::move(task))) {
107         gcTaskTracker.RemoveId(id);
108         return -1;
109     }
110     return static_cast<EtsLong>(id);
111 }
112 
113 /**
114  * The function returns when the specified GC gets finished.
115  * @param gc_id - id of the GC which is returned by startGc.
116  * If gc_id is 0 or -1 the function returns immediately.
117  */
StdGCWaitForFinishGC(EtsLong gcId)118 extern "C" void StdGCWaitForFinishGC(EtsLong gcId)
119 {
120     ManagedThread *thread = ManagedThread::GetCurrent();
121     ASSERT(thread != nullptr);
122     if (gcId <= 0) {
123         return;
124     }
125     auto id = static_cast<uint64_t>(gcId);
126     ASSERT(GCTaskTracker::IsInitialized());
127     ScopedNativeCodeThread s(thread);
128     while (GCTaskTracker::InitIfNeededAndGet(thread->GetVM()->GetGC()).HasId(id)) {
129         constexpr uint64_t WAIT_TIME_MS = 2U;
130         os::thread::NativeSleep(WAIT_TIME_MS);
131     }
132 }
133 
StdGCIsScheduledGCTriggered()134 extern "C" EtsBoolean StdGCIsScheduledGCTriggered()
135 {
136     auto thread = ManagedThread::GetCurrent();
137     ASSERT(thread != nullptr);
138     auto *vm = thread->GetVM();
139     auto *trigger = vm->GetGCTrigger();
140 
141     if (trigger->GetType() != mem::GCTriggerType::ON_NTH_ALLOC) {
142         return ToEtsBoolean(false);
143     }
144     auto schedTrigger = reinterpret_cast<mem::SchedGCOnNthAllocTrigger *>(vm->GetGCTrigger());
145     return ToEtsBoolean(schedTrigger->IsTriggered());
146 }
147 
StdGCPostponeGCStart()148 extern "C" void StdGCPostponeGCStart()
149 {
150     auto coroutine = EtsCoroutine::GetCurrent();
151     ASSERT(coroutine != nullptr);
152     auto *gc = coroutine->GetVM()->GetGC();
153     if (!gc->IsPostponeGCSupported()) {
154         ThrowEtsException(coroutine, panda_file_items::class_descriptors::UNSUPPORTED_OPERATION_EXCEPTION,
155                           "GC postpone is not supported for this GC type");
156         return;
157     }
158     if (gc->IsPostponeEnabled()) {
159         ThrowEtsException(coroutine, panda_file_items::class_descriptors::ILLEGAL_STATE_EXCEPTION,
160                           "Calling postponeGCStart without calling postponeGCEnd");
161         return;
162     }
163     gc->PostponeGCStart();
164 }
165 
StdGCPostponeGCEnd()166 extern "C" void StdGCPostponeGCEnd()
167 {
168     auto coroutine = EtsCoroutine::GetCurrent();
169     ASSERT(coroutine != nullptr);
170     auto *gc = coroutine->GetVM()->GetGC();
171     if (!gc->IsPostponeGCSupported()) {
172         ThrowEtsException(coroutine, panda_file_items::class_descriptors::UNSUPPORTED_OPERATION_EXCEPTION,
173                           "GC postpone is not supported for this GC type");
174         return;
175     }
176     if (!gc->IsPostponeEnabled()) {
177         ThrowEtsException(coroutine, panda_file_items::class_descriptors::ILLEGAL_STATE_EXCEPTION,
178                           "Calling postponeGCEnd without calling postponeGCStart");
179         return;
180     }
181     gc->PostponeGCEnd();
182 }
183 
184 template <class ResArrayType>
StdGCAllocatePinnedPrimitiveTypeArray(EtsLong length)185 [[nodiscard]] static ResArrayType *StdGCAllocatePinnedPrimitiveTypeArray(EtsLong length)
186 {
187     auto *coroutine = EtsCoroutine::GetCurrent();
188     ASSERT(coroutine != nullptr);
189 
190     if (length < 0) {
191         ThrowEtsException(coroutine, panda_file_items::class_descriptors::NEGATIVE_ARRAY_SIZE_ERROR,
192                           "The value must be non negative");
193         return nullptr;
194     }
195     auto *vm = coroutine->GetVM();
196     if (!vm->GetGC()->IsPinningSupported()) {
197         ThrowEtsException(coroutine, panda_file_items::class_descriptors::UNSUPPORTED_OPERATION_EXCEPTION,
198                           "Object pinning does not support with current GC");
199         return nullptr;
200     }
201     auto *array = ResArrayType::Create(length, SpaceType::SPACE_TYPE_OBJECT, true);
202 
203     if (array == nullptr) {
204         PandaStringStream ss;
205         ss << "Could not allocate array of " << length << " elements";
206         ThrowEtsException(coroutine, panda_file_items::class_descriptors::OUT_OF_MEMORY_ERROR, ss.str());
207         return nullptr;
208     }
209 
210     return array;
211 }
212 
StdGCAllocatePinnedBooleanArray(EtsLong length)213 extern "C" EtsBooleanArray *StdGCAllocatePinnedBooleanArray(EtsLong length)
214 {
215     return StdGCAllocatePinnedPrimitiveTypeArray<EtsBooleanArray>(length);
216 }
217 
StdGCAllocatePinnedByteArray(EtsLong length)218 extern "C" EtsByteArray *StdGCAllocatePinnedByteArray(EtsLong length)
219 {
220     return StdGCAllocatePinnedPrimitiveTypeArray<EtsByteArray>(length);
221 }
222 
StdGCAllocatePinnedCharArray(EtsLong length)223 extern "C" EtsCharArray *StdGCAllocatePinnedCharArray(EtsLong length)
224 {
225     return StdGCAllocatePinnedPrimitiveTypeArray<EtsCharArray>(length);
226 }
227 
StdGCAllocatePinnedShortArray(EtsLong length)228 extern "C" EtsShortArray *StdGCAllocatePinnedShortArray(EtsLong length)
229 {
230     return StdGCAllocatePinnedPrimitiveTypeArray<EtsShortArray>(length);
231 }
232 
StdGCAllocatePinnedIntArray(EtsLong length)233 extern "C" EtsIntArray *StdGCAllocatePinnedIntArray(EtsLong length)
234 {
235     return StdGCAllocatePinnedPrimitiveTypeArray<EtsIntArray>(length);
236 }
237 
StdGCAllocatePinnedLongArray(EtsLong length)238 extern "C" EtsLongArray *StdGCAllocatePinnedLongArray(EtsLong length)
239 {
240     return StdGCAllocatePinnedPrimitiveTypeArray<EtsLongArray>(length);
241 }
242 
StdGCAllocatePinnedFloatArray(EtsLong length)243 extern "C" EtsFloatArray *StdGCAllocatePinnedFloatArray(EtsLong length)
244 {
245     return StdGCAllocatePinnedPrimitiveTypeArray<EtsFloatArray>(length);
246 }
247 
StdGCAllocatePinnedDoubleArray(EtsLong length)248 extern "C" EtsDoubleArray *StdGCAllocatePinnedDoubleArray(EtsLong length)
249 {
250     return StdGCAllocatePinnedPrimitiveTypeArray<EtsDoubleArray>(length);
251 }
252 
StdGCGetObjectSpaceType(EtsObject * obj)253 extern "C" EtsInt StdGCGetObjectSpaceType(EtsObject *obj)
254 {
255     ASSERT(obj != nullptr);
256     auto *vm = Thread::GetCurrent()->GetVM();
257     SpaceType objSpaceType =
258         PoolManager::GetMmapMemPool()->GetSpaceTypeForAddr(static_cast<void *>(obj->GetCoreType()));
259     ASSERT_PRINT(IsHeapSpace(objSpaceType), SpaceTypeToString(objSpaceType));
260     if (objSpaceType == SpaceType::SPACE_TYPE_OBJECT && vm->GetGC()->IsGenerational()) {
261         if (vm->GetHeapManager()->IsObjectInYoungSpace(obj->GetCoreType())) {
262             const EtsInt youngSpace = 4;
263             return youngSpace;
264         }
265         const EtsInt tenuredSpace = 5;
266         return tenuredSpace;
267     }
268     return SpaceTypeToIndex(objSpaceType);
269 }
270 
StdGCPinObject(EtsObject * obj)271 extern "C" void StdGCPinObject(EtsObject *obj)
272 {
273     ASSERT(obj != nullptr);
274     auto *coroutine = EtsCoroutine::GetCurrent();
275     ASSERT(coroutine != nullptr);
276 
277     auto *vm = coroutine->GetVM();
278     auto *gc = vm->GetGC();
279     if (!gc->IsPinningSupported()) {
280         ThrowEtsException(coroutine, panda_file_items::class_descriptors::UNSUPPORTED_OPERATION_EXCEPTION,
281                           "Object pinning does not support with current gc");
282         return;
283     }
284     vm->GetHeapManager()->PinObject(obj->GetCoreType());
285 }
286 
StdGCUnpinObject(EtsObject * obj)287 extern "C" void StdGCUnpinObject(EtsObject *obj)
288 {
289     ASSERT(obj != nullptr);
290     auto *vm = Thread::GetCurrent()->GetVM();
291     vm->GetHeapManager()->UnpinObject(obj->GetCoreType());
292 }
293 
StdGCGetObjectAddress(EtsObject * obj)294 extern "C" EtsLong StdGCGetObjectAddress(EtsObject *obj)
295 {
296     ASSERT(obj != nullptr);
297     return reinterpret_cast<EtsLong>(obj);
298 }
299 
StdGetObjectSize(EtsObject * obj)300 extern "C" EtsLong StdGetObjectSize(EtsObject *obj)
301 {
302     ASSERT(obj != nullptr);
303     return static_cast<EtsLong>(obj->GetCoreType()->ObjectSize());
304 }
305 
306 // Function schedules GC before n-th allocation by setting counter to the specific GC trigger.
307 // Another call may reset the counter.  In this case the last counter will be used to trigger the GC.
StdGCScheduleGCAfterNthAlloc(EtsInt counter,EtsInt cause)308 extern "C" void StdGCScheduleGCAfterNthAlloc(EtsInt counter, EtsInt cause)
309 {
310     auto *coroutine = EtsCoroutine::GetCurrent();
311     ASSERT(coroutine != nullptr);
312 
313     if (counter < 0) {
314         ThrowEtsException(coroutine, panda_file_items::class_descriptors::ILLEGAL_ARGUMENT_EXCEPTION,
315                           "counter for allocation is negative");
316         return;
317     }
318     GCTaskCause reason = GCCauseFromInt(cause);
319 
320     auto *vm = coroutine->GetVM();
321     auto *gc = vm->GetGC();
322     if (!gc->CheckGCCause(reason)) {
323         PandaStringStream eMsg;
324         eMsg << mem::GCStringFromType(gc->GetType()) << " does not support " << reason << " cause";
325         ThrowEtsException(coroutine, panda_file_items::class_descriptors::ILLEGAL_ARGUMENT_EXCEPTION, eMsg.str());
326         return;
327     }
328     mem::GCTrigger *trigger = vm->GetGCTrigger();
329     if (trigger->GetType() != mem::GCTriggerType::ON_NTH_ALLOC) {
330         ThrowEtsException(coroutine, panda_file_items::class_descriptors::UNSUPPORTED_OPERATION_EXCEPTION,
331                           "VM is running with unsupported GC trigger");
332         return;
333     }
334     auto schedTrigger = reinterpret_cast<mem::SchedGCOnNthAllocTrigger *>(trigger);
335     schedTrigger->ScheduleGc(reason, counter);
336 }
337 
StdGetFreeHeapSize()338 extern "C" EtsLong StdGetFreeHeapSize()
339 {
340     const auto *coroutine = EtsCoroutine::GetCurrent();
341     ASSERT(coroutine != nullptr);
342     return static_cast<EtsLong>(coroutine->GetPandaVM()->GetHeapManager()->GetFreeMemory());
343 }
344 
StdGetUsedHeapSize()345 extern "C" EtsLong StdGetUsedHeapSize()
346 {
347     const auto *coroutine = EtsCoroutine::GetCurrent();
348     ASSERT(coroutine != nullptr);
349     auto *headManager = coroutine->GetPandaVM()->GetHeapManager();
350     auto totalMemory = headManager->GetTotalMemory();
351     auto freeMemory = headManager->GetFreeMemory();
352     ASSERT(totalMemory >= freeMemory);
353     return static_cast<EtsLong>(totalMemory - freeMemory);
354 }
355 
StdGetReservedHeapSize()356 extern "C" EtsLong StdGetReservedHeapSize()
357 {
358     const auto *coroutine = EtsCoroutine::GetCurrent();
359     ASSERT(coroutine != nullptr);
360     return static_cast<EtsLong>(coroutine->GetPandaVM()->GetHeapManager()->GetMaxMemory());
361 }
362 
StdGCRegisterNativeAllocation(EtsLong size)363 extern "C" void StdGCRegisterNativeAllocation(EtsLong size)
364 {
365     auto *coroutine = EtsCoroutine::GetCurrent();
366     ASSERT(coroutine != nullptr);
367     if (size < 0) {
368         ThrowEtsException(coroutine, panda_file_items::class_descriptors::NEGATIVE_ARRAY_SIZE_ERROR,
369                           "The value must be non negative");
370         return;
371     }
372 
373     ScopedNativeCodeThread s(ManagedThread::GetCurrent());
374     coroutine->GetVM()->GetGC()->RegisterNativeAllocation(ClampToSizeT(size));
375 }
376 
StdGCRegisterNativeFree(EtsLong size)377 extern "C" void StdGCRegisterNativeFree(EtsLong size)
378 {
379     auto *coroutine = EtsCoroutine::GetCurrent();
380     ASSERT(coroutine != nullptr);
381     if (size < 0) {
382         ThrowEtsException(coroutine, panda_file_items::class_descriptors::NEGATIVE_ARRAY_SIZE_ERROR,
383                           "The value must be non negative");
384         return;
385     }
386 
387     ScopedNativeCodeThread s(ManagedThread::GetCurrent());
388     coroutine->GetVM()->GetGC()->RegisterNativeFree(ClampToSizeT(size));
389 }
390 
391 }  // namespace ark::ets::intrinsics
392