1 /*
2 * Copyright (c) 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 "ecmascript/builtins/builtins_gc.h"
17 #include "ecmascript/mem/heap-inl.h"
18 #include "ecmascript/js_tagged_value-inl.h"
19 #include "ecmascript/interpreter/interpreter-inl.h"
20
21 namespace panda::ecmascript::builtins {
GetFreeHeapSize(EcmaRuntimeCallInfo * info)22 JSTaggedValue BuiltinsGc::GetFreeHeapSize(EcmaRuntimeCallInfo *info)
23 {
24 auto *heap = info->GetThread()->GetEcmaVM()->GetHeap();
25 auto size = heap->GetHeapLimitSize() - heap->GetHeapObjectSize();
26 return JSTaggedValue(static_cast<int64_t>(size));
27 }
28
GetReservedHeapSize(EcmaRuntimeCallInfo * info)29 JSTaggedValue BuiltinsGc::GetReservedHeapSize(EcmaRuntimeCallInfo *info)
30 {
31 auto *heap = info->GetThread()->GetEcmaVM()->GetHeap();
32 return JSTaggedValue(static_cast<int64_t>(heap->GetHeapLimitSize()));
33 }
34
GetUsedHeapSize(EcmaRuntimeCallInfo * info)35 JSTaggedValue BuiltinsGc::GetUsedHeapSize(EcmaRuntimeCallInfo *info)
36 {
37 auto *heap = info->GetThread()->GetEcmaVM()->GetHeap();
38 return JSTaggedValue(static_cast<int64_t>(heap->GetHeapObjectSize()));
39 }
40
GetObjectAddress(EcmaRuntimeCallInfo * info)41 JSTaggedValue BuiltinsGc::GetObjectAddress(EcmaRuntimeCallInfo *info)
42 {
43 JSHandle<JSTaggedValue> h = GetCallArg(info, 0);
44 return JSTaggedValue(reinterpret_cast<int64_t>(h->GetHeapObject()));
45 }
46
GetObjectSpaceType(EcmaRuntimeCallInfo * info)47 JSTaggedValue BuiltinsGc::GetObjectSpaceType(EcmaRuntimeCallInfo *info)
48 {
49 JSHandle<JSTaggedValue> h = GetCallArg(info, 0);
50 auto *region = Region::ObjectAddressToRange(h->GetHeapObject());
51 return JSTaggedValue(region->GetSpaceType());
52 }
53
RegisterNativeAllocation(EcmaRuntimeCallInfo * info)54 JSTaggedValue BuiltinsGc::RegisterNativeAllocation(EcmaRuntimeCallInfo *info)
55 {
56 JSHandle<JSTaggedValue> h = GetCallArg(info, 0);
57 auto *heap = const_cast<Heap *>(info->GetThread()->GetEcmaVM()->GetHeap());
58 auto size = h->GetInt();
59 if (size < 0) {
60 auto *thread = info->GetThread();
61 THROW_RANGE_ERROR_AND_RETURN(thread, "The value must be non negative", JSTaggedValue::Exception());
62 }
63 heap->IncreaseNativeBindingSize(size);
64 heap->TryTriggerFullMarkOrGCByNativeSize();
65 WaitAndHandleConcurrentMarkingFinished(heap);
66 return JSTaggedValue::Undefined();
67 }
68
RegisterNativeFree(EcmaRuntimeCallInfo * info)69 JSTaggedValue BuiltinsGc::RegisterNativeFree(EcmaRuntimeCallInfo *info)
70 {
71 JSHandle<JSTaggedValue> h = GetCallArg(info, 0);
72 auto *heap = const_cast<Heap *>(info->GetThread()->GetEcmaVM()->GetHeap());
73 auto size = h->GetInt();
74 if (size < 0) {
75 auto *thread = info->GetThread();
76 THROW_RANGE_ERROR_AND_RETURN(thread, "The value must be non negative", JSTaggedValue::Exception());
77 }
78 auto allocated = heap->GetNativeBindingSize();
79 heap->DecreaseNativeBindingSize(std::min(allocated, static_cast<size_t>(size)));
80 return JSTaggedValue::Undefined();
81 }
82
WaitForFinishGC(EcmaRuntimeCallInfo * info)83 JSTaggedValue BuiltinsGc::WaitForFinishGC(EcmaRuntimeCallInfo *info)
84 {
85 auto *heap = const_cast<Heap *>(info->GetThread()->GetEcmaVM()->GetHeap());
86 auto gcId = GetCallArg(info, 0)->GetInt();
87 if (gcId <= 0) {
88 return JSTaggedValue::Undefined();
89 }
90
91 WaitAndHandleConcurrentMarkingFinished(heap);
92
93 #ifndef NDEBUG
94 heap->EnableTriggerCollectionOnNewObject();
95 #endif
96 return JSTaggedValue::Undefined();
97 }
98
StartGC(EcmaRuntimeCallInfo * info)99 JSTaggedValue BuiltinsGc::StartGC(EcmaRuntimeCallInfo *info)
100 {
101 static int counter = 0;
102 auto *thread = info->GetThread();
103 auto *heap = const_cast<Heap *>(info->GetThread()->GetEcmaVM()->GetHeap());
104 auto cause = StringToGcType(thread, GetCallArg(info, 0).GetTaggedValue());
105 auto runGcInPlace = GetCallArg(info, 2).GetTaggedValue().ToBoolean();
106
107 if (cause == GC_TYPE_LAST) {
108 THROW_TYPE_ERROR_AND_RETURN(thread, "Invalid GC trigger type", JSTaggedValue::Exception());
109 }
110 switch (cause) {
111 case SHARED_GC:
112 case SHARED_PARTIAL_GC:
113 SharedHeap::GetInstance()->CollectGarbage<TriggerGCType::SHARED_GC, GCReason::EXTERNAL_TRIGGER>(thread);
114 return JSTaggedValue(0);
115 case SHARED_FULL_GC:
116 SharedHeap::GetInstance()->CollectGarbage<TriggerGCType::SHARED_FULL_GC, GCReason::EXTERNAL_TRIGGER>(
117 thread);
118 return JSTaggedValue(0);
119 case APPSPAWN_SHARED_FULL_GC:
120 SharedHeap::GetInstance()
121 ->CollectGarbage<TriggerGCType::APPSPAWN_SHARED_FULL_GC, GCReason::EXTERNAL_TRIGGER>(thread);
122 return JSTaggedValue(0);
123 default:
124 break;
125 }
126 if (cause != OLD_GC) {
127 // except OLD_GC all run in place implicitly
128 heap->CollectGarbage(cause, GCReason::EXTERNAL_TRIGGER);
129 return JSTaggedValue(0);
130 }
131
132 heap->SetMarkType(MarkType::MARK_FULL);
133 heap->TriggerConcurrentMarking();
134
135 if (heap->GetConcurrentMarker()->IsTriggeredConcurrentMark()) {
136 JSHandle<JSTaggedValue> hCallback = GetCallArg(info, 1);
137 if (!hCallback->IsUndefinedOrNull()) {
138 if (!hCallback->IsJSFunction()) {
139 THROW_TYPE_ERROR_AND_RETURN(thread, "Invalid GC callback", JSTaggedValue::Exception());
140 }
141
142 auto undefined = thread->GlobalConstants()->GetHandledUndefined();
143 auto *calleeInfo = EcmaInterpreter::NewRuntimeCallInfo(thread, hCallback, info->GetThis(), undefined, 0);
144 JSFunction::Call(calleeInfo);
145 }
146 }
147
148 if (runGcInPlace) {
149 WaitAndHandleConcurrentMarkingFinished(heap);
150 return JSTaggedValue(0);
151 }
152
153 #ifndef NDEBUG
154 heap->DisableTriggerCollectionOnNewObject();
155 #endif
156 return JSTaggedValue(++counter);
157 }
158
WaitAndHandleConcurrentMarkingFinished(Heap * heap)159 void BuiltinsGc::WaitAndHandleConcurrentMarkingFinished(Heap *heap)
160 {
161 if (heap->GetConcurrentMarker()->IsTriggeredConcurrentMark()) {
162 heap->WaitConcurrentMarkingFinished();
163 heap->GetConcurrentMarker()->HandleMarkingFinished(GCReason::EXTERNAL_TRIGGER);
164 }
165 }
166
AllocateArrayObject(EcmaRuntimeCallInfo * info)167 JSTaggedValue BuiltinsGc::AllocateArrayObject(EcmaRuntimeCallInfo *info)
168 {
169 auto *thread = info->GetThread();
170 auto *factory = thread->GetEcmaVM()->GetFactory();
171 ASSERT(thread != nullptr);
172 [[maybe_unused]] EcmaHandleScope handleScope(thread);
173
174 int64_t sizeInBytes = 0;
175
176 if (GetCallArg(info, 0)->IsInt()) {
177 sizeInBytes = GetCallArg(info, 0)->GetInt();
178 } else if (GetCallArg(info, 0)->IsDouble()) {
179 sizeInBytes = GetCallArg(info, 0)->GetDouble();
180 } else {
181 auto err = thread->GetEcmaVM()->GetFactory()->GetJSError(ErrorType::TYPE_ERROR, "The value must be an integer");
182 THROW_NEW_ERROR_AND_RETURN_VALUE(thread, err.GetTaggedValue(), JSTaggedValue::Exception());
183 }
184
185 if (sizeInBytes < 0) {
186 auto err = thread->GetEcmaVM()->GetFactory()->GetJSError(ErrorType::TYPE_ERROR, "The value must be positive");
187 THROW_NEW_ERROR_AND_RETURN_VALUE(thread, err.GetTaggedValue(), JSTaggedValue::Exception());
188 }
189
190 sizeInBytes = RoundUp(sizeInBytes, sizeof(TaggedType)) - JSArray::SIZE;
191 if (sizeInBytes < 0) {
192 sizeInBytes = 0;
193 }
194
195 uint32_t numElements = static_cast<uint32_t>(sizeInBytes / sizeof(TaggedType));
196 auto array = factory->NewJSArray();
197
198 if (numElements > 0) {
199 auto elements = factory->NewTaggedArray(numElements);
200 if (elements.IsEmpty()) {
201 return JSTaggedValue::Exception();
202 }
203
204 array->SetElements(thread, elements);
205 array->SetArrayLength(thread, numElements);
206 }
207
208 return array.GetTaggedValue();
209 }
210
StringToGcType(JSThread * thread,JSTaggedValue cause)211 TriggerGCType BuiltinsGc::StringToGcType(JSThread *thread, JSTaggedValue cause)
212 {
213 static_assert(GC_TYPE_LAST == 8, "Update this method after TrigerGCType change");
214 if (JSTaggedValue::StrictEqual(thread->GlobalConstants()->GetYoungGcCause(), cause)) {
215 return YOUNG_GC;
216 }
217 if (JSTaggedValue::StrictEqual(thread->GlobalConstants()->GetOldGcCause(), cause)) {
218 return OLD_GC;
219 }
220 if (JSTaggedValue::StrictEqual(thread->GlobalConstants()->GetFullGcCause(), cause)) {
221 return FULL_GC;
222 }
223 if (JSTaggedValue::StrictEqual(thread->GlobalConstants()->GetAppSpawnFullGcCause(), cause)) {
224 return APPSPAWN_FULL_GC;
225 }
226 if (JSTaggedValue::StrictEqual(thread->GlobalConstants()->GetSharedGcCause(), cause)) {
227 return SHARED_GC;
228 }
229 if (JSTaggedValue::StrictEqual(thread->GlobalConstants()->GetSharedPartialGcCause(), cause)) {
230 return SHARED_PARTIAL_GC;
231 }
232 if (JSTaggedValue::StrictEqual(thread->GlobalConstants()->GetSharedFullGcCause(), cause)) {
233 return SHARED_FULL_GC;
234 }
235 if (JSTaggedValue::StrictEqual(thread->GlobalConstants()->GetAppSpawnSharedFullGcCause(), cause)) {
236 return APPSPAWN_SHARED_FULL_GC;
237 }
238 return GC_TYPE_LAST;
239 }
240 } // namespace panda::ecmascript::builtins
241