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