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