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