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