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/mem/heap-inl.h"
17 #include "ecmascript/mem/jit_fort.h"
18 #include "ecmascript/jit/jit.h"
19 #if defined(JIT_ENABLE_CODE_SIGN) && !defined(JIT_FORT_DISABLE)
20 #include <sys/ioctl.h>
21 #include <sys/prctl.h>
22
23 #define XPM_JITFORT_ENABLE_OPCODE 3
24 #define XPM_MAGIC 'x'
25 #define XPM_SET_JITFORT_ENABLE _IOW(XPM_MAGIC, XPM_JITFORT_ENABLE_OPCODE, unsigned long)
26 #endif
27
28 namespace panda::ecmascript {
29
30 template <>
FreeListAllocator(BaseHeap *heap, MemDescPool *pool, JitFort *fort)31 FreeListAllocator<MemDesc>::FreeListAllocator(BaseHeap *heap, MemDescPool *pool, JitFort *fort)
32 : memDescPool_(pool), heap_(heap)
33 {
34 freeList_ = std::make_unique<FreeObjectList<MemDesc>>(fort);
35 }
36
JitFort()37 JitFort::JitFort()
38 {
39 jitFortMem_ = PageMap(JIT_FORT_REG_SPACE_MAX,
40 PageProtectProt(Jit::GetInstance()->IsDisableCodeSign() || !IsResourceAvailable()),
41 DEFAULT_REGION_SIZE, nullptr, MAP_JITFORT);
42 jitFortBegin_ = reinterpret_cast<uintptr_t>(jitFortMem_.GetMem());
43 jitFortSize_ = JIT_FORT_REG_SPACE_MAX;
44 memDescPool_ = new MemDescPool(jitFortBegin_, jitFortSize_);
45 allocator_ = new FreeListAllocator<MemDesc>(nullptr, memDescPool_, this);
46 InitRegions();
47 LOG_JIT(DEBUG) << "JitFort Begin " << (void *)JitFortBegin() << " end " << (void *)(JitFortBegin() + JitFortSize());
48 }
49
~JitFort()50 JitFort::~JitFort()
51 {
52 constexpr size_t numRegions = JIT_FORT_REG_SPACE_MAX / DEFAULT_REGION_SIZE;
53 for (size_t i = 0; i < numRegions; i++) {
54 regions_[i]->DestroyFreeObjectSets();
55 delete regions_[i];
56 }
57 delete allocator_;
58 delete memDescPool_;
59 PageUnmap(jitFortMem_);
60 }
61
InitRegions()62 void JitFort::InitRegions()
63 {
64 constexpr size_t numRegions = JIT_FORT_REG_SPACE_MAX / DEFAULT_REGION_SIZE;
65 for (size_t i = 0; i < numRegions; i++) {
66 uintptr_t mem = reinterpret_cast<uintptr_t>(jitFortMem_.GetMem()) + i*DEFAULT_REGION_SIZE;
67 uintptr_t end = mem + DEFAULT_REGION_SIZE;
68 JitFortRegion *region = new JitFortRegion(nullptr, mem, end, RegionSpaceFlag::IN_MACHINE_CODE_SPACE,
69 memDescPool_);
70 regions_[i] = region;
71 }
72 AddRegion();
73 }
74
AddRegion()75 bool JitFort::AddRegion()
76 {
77 if (nextFreeRegionIdx_ < MAX_JIT_FORT_REGIONS) {
78 allocator_->AddFree(regions_[nextFreeRegionIdx_]);
79 regionList_.AddNode(regions_[nextFreeRegionIdx_]);
80 nextFreeRegionIdx_++;
81 return true;
82 }
83 return false;
84 }
85
86 // Fort buf allocation is in multiples of FORT_BUF_ALIGN
FortAllocSize(size_t instrSize)87 size_t JitFort::FortAllocSize(size_t instrSize)
88 {
89 return(AlignUp(instrSize, FORT_BUF_ALIGN));
90 }
91
Allocate(MachineCodeDesc *desc)92 uintptr_t JitFort::Allocate(MachineCodeDesc *desc)
93 {
94 LockHolder lock(mutex_);
95
96 size_t size = FortAllocSize(desc->instructionsSize);
97 auto ret = allocator_->Allocate(size);
98 if (ret == ToUintPtr(nullptr)) {
99 if (AddRegion()) {
100 LOG_JIT(DEBUG) << "JitFort: Allocate - AddRegion";
101 ret = allocator_->Allocate(size);
102 }
103 }
104 if (ret == ToUintPtr(nullptr)) {
105 LOG_JIT(DEBUG) << "JitFort:: Allocate return nullptr for size " << size;
106 return ret;
107 }
108 // Record allocation to keep it from being collected by the next
109 // JitFort::UpdateFreeSpace in case corresponding Machine code object is not
110 // marked for sweep yet by then.
111 ASSERT((ret & FORT_BUF_ADDR_MASK) == 0);
112 MarkJitFortMemAwaitInstall(ret, size);
113 LOG_JIT(DEBUG) << "JitFort:: Allocate " << (void *)ret << " - " << (void *)(ret+size-1) <<
114 " size " << size << " instructionsSize " << desc->instructionsSize;
115 return ret;
116 }
117
118 // Called by GC thread during Mark to mark Fort buf alive.
119 // Encoding details in GCBitset for marking live Fort buffers:
120 // Begin of a live Fort buf:
121 // - 10: a live Jit Fort buf that has been installed
122 // - 11: a live Jit Fort buf that has not been installed yet
123 // End of a live Fort buf:
124 // - 01
125 // This encoding requires 4 GCBitset bits to mark a live JitFort
126 // buffer, which makes the minimum Fort buffer allocation size 32 bytes.
MarkJitFortMemAlive(MachineCode *obj)127 void JitFort::MarkJitFortMemAlive(MachineCode *obj)
128 {
129 size_t size = FortAllocSize(obj->GetInstructionsSize());
130 uintptr_t addr = obj->GetText();
131 uintptr_t endAddr = addr + size - 1;
132 uint32_t regionIdx = AddrToFortRegionIdx(addr);
133 regions_[regionIdx]->AtomicMark(reinterpret_cast<void *>(addr));
134 regions_[regionIdx]->AtomicMark(reinterpret_cast<void *>(endAddr));
135 LOG_JIT(DEBUG) << "MarkFortMemAlive: addr " << (void *)addr << " size " << size
136 << " regionIdx " << regionIdx << " instructionsSize " << obj->GetInstructionsSize();
137 }
138
139 // Called by Jit Compile thread during JitFort Allocate to mark Fort buf
140 // awaiting install. Need mutex (JitGCLockHolder) for region gcBitset access.
141 // See JitFort::MarkJitFortMemAlive comments for mark bit encoding in GC bitset.
MarkJitFortMemAwaitInstall(uintptr_t addr, size_t size)142 void JitFort::MarkJitFortMemAwaitInstall(uintptr_t addr, size_t size)
143 {
144 uintptr_t endAddr = addr + size - 1;
145 uint32_t regionIdx = AddrToFortRegionIdx(addr);
146 regions_[regionIdx]->AtomicMark(reinterpret_cast<void *>(addr));
147 regions_[regionIdx]->AtomicMark(reinterpret_cast<void *>(addr + sizeof(uint64_t))); // mark next bit
148 regions_[regionIdx]->AtomicMark(reinterpret_cast<void *>(endAddr));
149 LOG_JIT(DEBUG) << "MarkFortMemAwaitInstall: addr " << (void *)addr << " size " << size
150 << " regionIdx " << regionIdx;
151 }
152
153 // Called by JS/Main thread during SafePoint to clear Fort buf AwaitInstall bit
154 // See JitFort::MarkJitFortMemAlive comments for mark bit encoding in GC bitset.
MarkJitFortMemInstalled(MachineCode *obj)155 void JitFort::MarkJitFortMemInstalled(MachineCode *obj)
156 {
157 size_t size = FortAllocSize(obj->GetInstructionsSize());
158 uintptr_t addr = obj->GetText();
159 uint32_t regionIdx = AddrToFortRegionIdx(addr);
160 regions_[regionIdx]->GetGCBitset()->ClearMark(addr + sizeof(uint64_t)); // clear next bit
161 LOG_JIT(DEBUG) << "MarkFortMemInstalled: addr " << (void *)addr << " size " << size
162 << " regionIdx " << regionIdx << " instructionsSize " << obj->GetInstructionsSize();
163 }
164
AddrToFortRegionIdx(uint64_t addr)165 uint32_t JitFort::AddrToFortRegionIdx(uint64_t addr)
166 {
167 ASSERT(InRange(addr));
168 uint32_t regionIdx = ((addr - JitFortBegin()) & ~(DEFAULT_REGION_MASK)) >> REGION_SIZE_LOG2;
169 ASSERT(regionIdx < MAX_JIT_FORT_REGIONS);
170 return regionIdx;
171 }
172
173 /*
174 * Called from GC worker thread duing Old/Full GC Sweep (AsyncSweep). Mutex is needed
175 * to ensure exclusive access to JitFort memory by GC thread when it frees JitFort mem
176 * blocks, and by Jit compiled thread when it allocates Fort mem.
177 */
178 void JitFort::UpdateFreeSpace()
179 {
IsEnableJitFort()180 if (!Jit::GetInstance()->IsEnableJitFort()) {
181 return;
182 }
183
184 LockHolder lock(mutex_);
185
186 if (!regionList_.GetLength()) { // LCOV_EXCL_BR_LINE
187 return;
188 }
189 LOG_JIT(DEBUG) << "UpdateFreeSpace enter: " << "Fort space allocated: "
190 << allocator_->GetAllocatedSize()
191 << " available: " << allocator_->GetAvailableSize();
192 allocator_->RebuildFreeList();
193 JitFortRegion *region = regionList_.GetFirst();
194 while (region) {
195 FreeRegion(region);
196 region = region->GetNext();
197 }
198 LOG_JIT(DEBUG) << "UpdateFreeSpace exit: allocator_->GetAvailableSize "
199 << allocator_->GetAvailableSize();
200 }
201
FreeRegion(JitFortRegion *region)202 void JitFort::FreeRegion(JitFortRegion *region)
203 {
204 LOG_JIT(DEBUG) << "JitFort FreeRegion " << (void*)(region->GetBegin());
205
206 uintptr_t freeStart = region->GetBegin();
207 region->GetGCBitset()->IterateMarkedBitsConst(
208 region->GetBegin(), region->GetGCBitsetSize(),
209 [this, ®ion, &freeStart](void *mem, size_t size) {
210 ASSERT(region->InRange(ToUintPtr(mem)));
211 (void) region;
212 uintptr_t freeEnd = ToUintPtr(mem);
213 if (freeStart != freeEnd) {
214 allocator_->Free(freeStart, freeEnd - freeStart, true);
215 }
216 freeStart = freeEnd + size;
217 });
218 uintptr_t freeEnd = region->GetEnd();
219 if (freeStart != freeEnd) {
220 allocator_->Free(freeStart, freeEnd - freeStart, true);
221 }
222 }
223
InRange(uintptr_t address) const224 bool JitFort::InRange(uintptr_t address) const
225 {
226 return address >= jitFortBegin_ && (jitFortBegin_ + jitFortSize_) > 1 &&
227 address <= (jitFortBegin_ + jitFortSize_ - 1);
228 }
229
PrepareSweeping()230 void JitFort::PrepareSweeping()
231 {
232 isSweeping_.store(false, std::memory_order_release);
233 }
234
235 // concurrent sweep - only one of the AsyncSweep task will do JitFort sweep
AsyncSweep()236 void JitFort::AsyncSweep()
237 {
238 bool expect = false;
239 if (isSweeping_.compare_exchange_strong(expect, true, std::memory_order_seq_cst)) {
240 LOG_JIT(DEBUG) << "JitFort::AsyncSweep";
241 UpdateFreeSpace();
242 }
243 }
244
245 // non-concurrent sweep
Sweep()246 void JitFort::Sweep()
247 {
248 LOG_JIT(DEBUG) << "JitFort::Sweep";
249 UpdateFreeSpace();
250 }
251
252 // Used by JitFort::UpdateFreeSpace call path to find corresponding
253 // JitFortRegion for a free block in JitFort space, in order to put the blk into
254 // the corresponding free set of the JitFortRegion the free block belongs.
ObjectAddressToRange(uintptr_t addr)255 JitFortRegion *JitFort::ObjectAddressToRange(uintptr_t addr)
256 {
257 return regions_[AddrToFortRegionIdx(addr)];
258 }
259
MarkStartAddr(bool awaitInstall, uintptr_t startAddr, uint32_t index, uint32_t &word)260 void JitFortGCBitset::MarkStartAddr(bool awaitInstall, uintptr_t startAddr, uint32_t index, uint32_t &word)
261 {
262 if (!awaitInstall) {
263 ClearMark(startAddr);
264 word &= ~(1u << index);
265 } else {
266 word &= ~(1u << index);
267 word &= ~(1u << (index+1));
268 }
269 }
270
MarkEndAddr(bool awaitInstall, uintptr_t endAddr, uint32_t index, uint32_t &word)271 void JitFortGCBitset::MarkEndAddr(bool awaitInstall, uintptr_t endAddr, uint32_t index, uint32_t &word)
272 {
273 if (!awaitInstall) {
274 ClearMark(endAddr - 1);
275 }
276 word &= ~(1u << index);
277 }
278
279 // See JitFort::MarkJitFortMemAlive comments for mark bit encoding in JitFort GC bitset.
280 template <typename Visitor>
IterateMarkedBitsConst(uintptr_t regionAddr, size_t bitsetSize, Visitor visitor)281 void JitFortGCBitset::IterateMarkedBitsConst(uintptr_t regionAddr, size_t bitsetSize, Visitor visitor)
282 {
283 bool awaitInstall = false;
284 uintptr_t startAddr = 0;
285 uintptr_t endAddr = 0;
286
287 auto words = Words();
288 uint32_t wordCount = WordCount(bitsetSize);
289 uint32_t index = BIT_PER_WORD;
290 for (uint32_t i = 0; i < wordCount; i++) {
291 uint32_t word = words[i];
292 while (word != 0) {
293 index = static_cast<uint32_t>(__builtin_ctz(word));
294 ASSERT(index < BIT_PER_WORD);
295 if (!startAddr) {
296 startAddr = regionAddr + (index << TAGGED_TYPE_SIZE_LOG);
297 awaitInstall = Test(regionAddr + ((index+1) << TAGGED_TYPE_SIZE_LOG));
298 MarkStartAddr(awaitInstall, startAddr, index, word);
299 } else {
300 endAddr = regionAddr + ((index+1) << TAGGED_TYPE_SIZE_LOG);
301 LOG_JIT(DEBUG) << "Live Jit Mem " << (void *)startAddr << " size " << endAddr-startAddr;
302 visitor(reinterpret_cast<void *>(startAddr), endAddr - startAddr);
303 MarkEndAddr(awaitInstall, endAddr, index, word);
304 awaitInstall = false;
305 startAddr = 0;
306 }
307 }
308 regionAddr += TAGGED_TYPE_SIZE * BIT_PER_WORD;
309 }
310 }
311
312 bool JitFort::isResourceAvailable_ = true;
313 bool JitFort::IsResourceAvailable()
314 {
315 return isResourceAvailable_;
316 }
317 void JitFort::InitJitFortResource()
318 {
319 #if defined(JIT_ENABLE_CODE_SIGN) && !defined(JIT_FORT_DISABLE)
320 ECMA_BYTRACE_NAME(HITRACE_TAG_ARK, "JIT::InitJitFortResource");
321 if (!Jit::GetInstance()->IsAppJit()) {
322 int fd = open("/dev/xpm", O_RDWR);
323 if (fd < 0) {
324 isResourceAvailable_ = false;
325 LOG_JIT(ERROR) << "Failed to init jitfort resource, open xpm failed: " << strerror(errno);
326 return;
327 }
328 int rc = ioctl(fd, XPM_SET_JITFORT_ENABLE, 0);
329 if (rc < 0) {
330 isResourceAvailable_ = false;
331 LOG_JIT(ERROR) << "Failed to init jitfort resource, enable xpm failed: " << strerror(errno);
332 close(fd);
333 return;
334 }
335 close(fd);
336 }
337 constexpr int prSetJitFort = 0x6a6974;
338 constexpr int jitFortInit = 5;
339 int res = prctl(prSetJitFort, jitFortInit, 0);
340 if (res < 0) {
341 isResourceAvailable_ = false;
342 LOG_JIT(ERROR) << "Failed to init jitfort resource: " << strerror(errno);
343 return;
344 }
345 res = prctl(prSetJitFort, jitFortInit, 0);
346 if (res >= 0 || errno != EEXIST) {
347 isResourceAvailable_ = false;
348 LOG_JIT(ERROR) << "jitfort not support";
349 }
350 #endif
351 }
352
353 MemDescPool::MemDescPool(uintptr_t fortBegin, size_t fortSize)
354 : fortBegin_(fortBegin), fortSize_(fortSize)
355 {
356 Expand();
357 }
358
359 MemDescPool::~MemDescPool()
360 {
361 for (const auto& block : memDescBlocks_) {
362 if (block) {
363 free(block);
364 }
365 }
366 }
367
368 MemDesc *MemDescPool::GetDesc()
369 {
370 if (IsEmpty(freeList_)) {
371 Expand();
372 }
373 if (!IsEmpty(freeList_)) { // LCOV_EXCL_BR_LINE
374 MemDesc *res = freeList_;
375 freeList_ = freeList_->GetNext();
376 allocated_++;
377 if (allocated_-returned_ > highwater_) {
378 highwater_ = allocated_ - returned_;
379 }
380 return res;
381 }
382 return nullptr;
383 }
384
385 void MemDescPool::Expand()
386 {
387 void *block = malloc(sizeof(MemDesc) * MEMDESCS_PER_BLOCK);
388 if (block) {
389 memDescBlocks_.push_back(block);
390 for (size_t i = 0; i < MEMDESCS_PER_BLOCK; ++i) {
391 Add(new (ToVoidPtr(reinterpret_cast<uintptr_t>(block) + i*sizeof(MemDesc))) MemDesc());
392 }
393 }
394 }
395
396 void MemDescPool::Add(MemDesc *desc)
397 {
398 desc->SetNext(freeList_);
399 freeList_ = desc;
400 }
401
402 } // namespace panda::ecmascript
403