1/*
2 * Copyright (c) 2022 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/snapshot/mem/snapshot.h"
17
18#include <cerrno>
19
20#include "ecmascript/compiler/pgo_type/pgo_type_manager.h"
21
22namespace panda::ecmascript {
23void Snapshot::Serialize(const CString &fileName)
24{
25    kungfu::AOTSnapshot &aotSnapshot = vm_->GetJSThread()->GetCurrentEcmaContext()->GetPTManager()->GetAOTSnapshot();
26    JSTaggedValue root = aotSnapshot.GetSnapshotData();
27    if (root == JSTaggedValue::Hole()) {
28        // root equals hole means no data stored.
29        LOG_COMPILER(ERROR) << "error: no data for ai file generation!";
30        return;
31    }
32    Serialize(root.GetTaggedObject(), nullptr, fileName);
33}
34
35void Snapshot::Serialize(TaggedObject *objectHeader, const JSPandaFile *jsPandaFile, const CString &fileName)
36{
37    std::string realPath;
38    if (!RealPath(std::string(fileName), realPath, false)) {
39        LOG_FULL(FATAL) << "snapshot file path error";
40    }
41    std::fstream writer(realPath.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
42    if (!writer.good()) {
43        writer.close();
44        LOG_FULL(ERROR) << "snapshot open file failed";
45        return;
46    }
47
48    SnapshotProcessor processor(vm_);
49    processor.Initialize();
50
51    std::unordered_map<uint64_t, std::pair<uint64_t, EncodeBit>> data;
52    CQueue<TaggedObject *> objectQueue;
53
54    if (objectHeader->GetClass()->GetObjectType() == JSType::PROGRAM) {
55        processor.SetProgramSerializeStart();
56    }
57
58    processor.EncodeTaggedObject(objectHeader, &objectQueue, &data);
59    size_t rootObjSize = objectQueue.size();
60    processor.ProcessObjectQueue(&objectQueue, &data);
61    WriteToFile(writer, jsPandaFile, rootObjSize, processor);
62}
63
64void Snapshot::Serialize(uintptr_t startAddr, size_t size, const CString &fileName)
65{
66    std::string realPath;
67    if (!RealPath(std::string(fileName), realPath, false)) {
68        LOG_FULL(FATAL) << "snapshot file path error";
69    }
70    std::fstream writer(realPath.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
71    if (!writer.good()) {
72        writer.close();
73        LOG_FULL(ERROR) << "snapshot open file failed";
74        return;
75    }
76
77    SnapshotProcessor processor(vm_);
78    processor.Initialize();
79
80    std::unordered_map<uint64_t, std::pair<uint64_t, EncodeBit>> data;
81    CQueue<TaggedObject *> objectQueue;
82
83    ObjectSlot start(startAddr);
84    ObjectSlot end(startAddr + size * sizeof(JSTaggedType));
85    processor.EncodeTaggedObjectRange(start, end, &objectQueue, &data);
86
87    size_t rootObjSize = objectQueue.size();
88    processor.ProcessObjectQueue(&objectQueue, &data);
89    WriteToFile(writer, nullptr, rootObjSize, processor);
90}
91
92void Snapshot::SerializeBuiltins(const CString &fileName)
93{
94    std::string realPath;
95    if (!RealPath(std::string(fileName), realPath, false)) {
96        LOG_FULL(FATAL) << "snapshot file path error";
97    }
98    std::fstream write(realPath.c_str(), std::ios::out | std::ios::binary | std::ios::app);
99    if (!write.good()) {
100        write.close();
101        LOG_FULL(ERROR) << "snapshot open file failed";
102        return;
103    }
104    // if builtins.snapshot file has exist, return directly
105    if (write.tellg()) {
106        LOG_FULL(DEBUG) << "snapshot already exist";
107        write.close();
108        return;
109    }
110
111    SnapshotProcessor processor(vm_);
112    processor.Initialize();
113    processor.SetBuiltinsSerializeStart();
114
115    std::unordered_map<uint64_t, std::pair<uint64_t, EncodeBit>> data;
116    CQueue<TaggedObject *> objectQueue;
117
118    auto globalEnvHandle = vm_->GetGlobalEnv();
119    auto constant = const_cast<GlobalEnvConstants *>(vm_->GetJSThread()->GlobalConstants());
120    constant->VisitRangeSlot([&objectQueue, &data, &processor]([[maybe_unused]] Root type,
121                                                               ObjectSlot start, ObjectSlot end) {
122        processor.EncodeTaggedObjectRange(start, end, &objectQueue, &data);
123    });
124    processor.EncodeTaggedObject(*globalEnvHandle, &objectQueue, &data);
125    size_t rootObjSize = objectQueue.size();
126    processor.ProcessObjectQueue(&objectQueue, &data);
127    WriteToFile(write, nullptr, rootObjSize, processor);
128}
129
130bool Snapshot::DeserializeInternal(SnapshotType type, const CString &snapshotFile, SnapshotProcessor &processor,
131                                   MemMap &fileMap)
132{
133    if (fileMap.GetOriginAddr() == nullptr) {
134        LOG_FULL(FATAL) << "file mmap failed";
135        UNREACHABLE();
136    }
137    auto readFile = ToUintPtr(fileMap.GetOriginAddr());
138    auto hdr = *ToNativePtr<const SnapShotHeader>(readFile);
139    if (!hdr.Verify(GetLastVersion())) {
140        FileUnMap(fileMap);
141        LOG_ECMA(ERROR) << "file verify failed.";
142        return false;
143    }
144    uintptr_t oldSpaceBegin = readFile + sizeof(SnapShotHeader);
145    uintptr_t stringBegin = oldSpaceBegin + hdr.oldSpaceObjSize + hdr.nonMovableObjSize +
146        hdr.machineCodeObjSize + hdr.snapshotObjSize + hdr.hugeObjSize;
147    uintptr_t stringEnd = stringBegin + hdr.stringSize;
148    [[maybe_unused]] EcmaHandleScope stringHandleScope(vm_->GetJSThread());
149    processor.DeserializeString(stringBegin, stringEnd);
150
151    processor.DeserializeObjectExcludeString(oldSpaceBegin, hdr.oldSpaceObjSize, hdr.nonMovableObjSize,
152                                             hdr.machineCodeObjSize, hdr.snapshotObjSize, hdr.hugeObjSize);
153
154#if !defined(CROSS_PLATFORM)
155    FileUnMap(MemMap(fileMap.GetOriginAddr(), hdr.pandaFileBegin));
156#endif
157    std::shared_ptr<JSPandaFile> jsPandaFile;
158    if (static_cast<uint32_t>(fileMap.GetSize()) > hdr.pandaFileBegin) {
159        uintptr_t pandaFileMem = readFile + hdr.pandaFileBegin;
160        auto pf = panda_file::File::OpenFromMemory(os::mem::ConstBytePtr(ToNativePtr<std::byte>(pandaFileMem),
161            static_cast<uint32_t>(fileMap.GetSize()) - hdr.pandaFileBegin, os::mem::MmapDeleter));
162        jsPandaFile = JSPandaFileManager::GetInstance()->NewJSPandaFile(pf.release(), "");
163    }
164    // relocate object field
165    processor.Relocate(type, jsPandaFile.get(), hdr.rootObjectSize);
166    processor.AddRootObjectToAOTFileManager(type, snapshotFile);
167    LOG_COMPILER(INFO) << "loaded ai file: " << snapshotFile.c_str();
168    return true;
169}
170
171bool Snapshot::Deserialize(SnapshotType type, const CString &snapshotFile, bool isBuiltins)
172{
173    std::string realPath;
174    if (!RealPath(std::string(snapshotFile), realPath, false)) {
175        LOG_FULL(FATAL) << "snapshot file path error";
176        UNREACHABLE();
177    }
178
179    std::ifstream file(realPath);
180    if (!file.good()) {
181        return false;
182    }
183    file.close();
184
185    SnapshotProcessor processor(vm_);
186    if (isBuiltins) {
187        processor.SetBuiltinsDeserializeStart();
188    }
189
190    MemMap fileMap = FileMap(realPath.c_str(), FILE_RDONLY, PAGE_PROT_READWRITE);
191    return DeserializeInternal(type, snapshotFile, processor, fileMap);
192}
193
194#if defined(CROSS_PLATFORM) && defined(ANDROID_PLATFORM)
195bool Snapshot::Deserialize(SnapshotType type, const CString &snapshotFile, [[maybe_unused]] std::function<bool
196    (std::string fileName, uint8_t **buff, size_t *buffSize)> ReadAOTCallBack, bool isBuiltins)
197{
198    SnapshotProcessor processor(vm_);
199    if (isBuiltins) {
200        processor.SetBuiltinsDeserializeStart();
201    }
202
203    std::string fileName = std::string(snapshotFile);
204    uint8_t *buff = nullptr;
205    size_t buffSize = 0;
206    MemMap fileMap = {};
207    size_t found = fileName.find_last_of("/");
208    if (found != std::string::npos) {
209        fileName = fileName.substr(found + 1);
210    }
211
212    LOG_ECMA(INFO) << "Call JsAotReader to load: " << fileName;
213    if (ReadAOTCallBack(fileName, &buff, &buffSize)) {
214        fileMap = MemMap(buff, buffSize);
215    }
216
217    return DeserializeInternal(type, snapshotFile, processor, fileMap);
218}
219#endif
220
221size_t Snapshot::AlignUpPageSize(size_t spaceSize)
222{
223    if (spaceSize % Constants::PAGE_SIZE_ALIGN_UP == 0) {
224        return spaceSize;
225    }
226    return Constants::PAGE_SIZE_ALIGN_UP * (spaceSize / Constants::PAGE_SIZE_ALIGN_UP + 1);
227}
228
229void Snapshot::WriteToFile(std::fstream &writer, const JSPandaFile *jsPandaFile,
230                           size_t size, SnapshotProcessor &processor)
231{
232    uint32_t totalStringSize = 0U;
233    CVector<uintptr_t> stringVector = processor.GetStringVector();
234    for (size_t i = 0; i < stringVector.size(); ++i) {
235        auto str = reinterpret_cast<EcmaString *>(stringVector[i]);
236        size_t objectSize = AlignUp(EcmaStringAccessor(str).ObjectSize(),
237            static_cast<size_t>(MemAlignment::MEM_ALIGN_OBJECT));
238        totalStringSize += objectSize;
239    }
240
241    std::vector<uint32_t> objSizeVector = processor.StatisticsObjectSize();
242    size_t totalObjSize = totalStringSize;
243    for (uint32_t objSize : objSizeVector) {
244        totalObjSize += objSize;
245    }
246    uint32_t pandaFileBegin = RoundUp(totalObjSize + sizeof(SnapShotHeader), Constants::PAGE_SIZE_ALIGN_UP);
247    SnapShotHeader hdr(GetLastVersion());
248    hdr.oldSpaceObjSize = objSizeVector[0]; // 0: oldSpaceObj
249    hdr.nonMovableObjSize = objSizeVector[1]; // 1: nonMovableObj
250    hdr.machineCodeObjSize = objSizeVector[2]; // 2: machineCodeObj
251    hdr.snapshotObjSize = objSizeVector[3]; // 3: snapshotObj
252    hdr.hugeObjSize = objSizeVector[4]; // 4: hugeObj
253    hdr.stringSize = totalStringSize;
254    hdr.pandaFileBegin = pandaFileBegin;
255    hdr.rootObjectSize = static_cast<uint32_t>(size);
256    writer.write(reinterpret_cast<char *>(&hdr), sizeof(hdr));
257    processor.WriteObjectToFile(writer);
258
259    for (size_t i = 0; i < stringVector.size(); ++i) {
260        auto str = reinterpret_cast<EcmaString *>(stringVector[i]);
261        size_t strSize = AlignUp(EcmaStringAccessor(str).ObjectSize(),
262            static_cast<size_t>(MemAlignment::MEM_ALIGN_OBJECT));
263        int index = 0; // 0 represents the line string. Natural number 1 represents the constant string.
264        if (EcmaStringAccessor(str).IsConstantString()) {
265            index = 1;
266        }
267        // Write the index in the head of string.
268        uint8_t headerSize = JSTaggedValue::TaggedTypeSize();
269        JSTaggedType indexHeader = JSTaggedValue(index).GetRawData();
270        writer.write(reinterpret_cast<char *>(&indexHeader), headerSize);
271        writer.write(reinterpret_cast<char *>(str) + headerSize, strSize - headerSize);
272        writer.flush();
273    }
274
275    ASSERT(static_cast<size_t>(writer.tellp()) == totalObjSize + sizeof(SnapShotHeader));
276    if (jsPandaFile) {
277        writer.seekp(pandaFileBegin);
278        writer.write(static_cast<const char *>(jsPandaFile->GetHeader()), jsPandaFile->GetFileSize());
279    }
280    writer.close();
281}
282}  // namespace panda::ecmascript
283