#include "Allocator/RegionSpace.h"
#include "Collector/Collector.h"
#include "Collector/CollectorResources.h"
#if defined(CANGJIE_SANITIZER_SUPPORT) || defined(CANGJIE_GWPASAN_SUPPORT)
#include "Sanitizer/SanitizerInterface.h"
#endif
#include "Common/ScopedObjectAccess.h"
#include "Heap.h"
namespace MapleRuntime {
MAddress RegionSpace::TryAllocateOnce(size_t allocSize, AllocType allocType)
{
if (UNLIKELY(allocType == AllocType::PINNED_OBJECT)) {
return regionManager.AllocPinned(allocSize);
}
if (UNLIKELY(allocSize >= regionManager.GetLargeObjectThreshold())) {
return regionManager.AllocLarge(allocSize);
}
AllocBuffer* allocBuffer = AllocBuffer::GetOrCreateAllocBuffer();
return allocBuffer->Allocate(allocSize, allocType);
}
bool RegionSpace::ShouldRetryAllocation(size_t& tryTimes, size_t size) const
{
if (!IsRuntimeThread() && tryTimes <= static_cast<size_t>(TryAllocationThreshold::RESCHEDULE)) {
CJThreadResched();
return true;
}
if (tryTimes < static_cast<size_t>(TryAllocationThreshold::TRIGGER_OOM)) {
if (Heap::GetHeap().IsGcStarted()) {
ScopedEnterSaferegion enterSaferegion(false);
Heap::GetHeap().GetCollectorResources().WaitForGCFinish();
} else {
Heap::GetHeap().GetCollector().RequestGC(GC_REASON_HEU, false);
}
return true;
}
if (tryTimes == static_cast<size_t>(TryAllocationThreshold::TRIGGER_OOM)) {
if (!Heap::GetHeap().IsGcStarted()) {
VLOG(REPORT, "gc is triggered for OOM");
Heap::GetHeap().GetCollector().RequestGC(GC_REASON_OOM, false);
} else {
ScopedEnterSaferegion enterSaferegion(false);
Heap::GetHeap().GetCollectorResources().WaitForGCFinish();
tryTimes--;
}
return true;
}
regionManager.DumpRegionStats("region statistics when gc ends", true);
VLOG(REPORT, "Cannot allocate memory of %zu(B), throw an OutOfMemory exception", size);
ExceptionManager::OutOfMemory();
return false;
}
MAddress RegionSpace::Allocate(size_t size, AllocType allocType)
{
size_t tryTimes = 0;
uintptr_t internalAddr = 0;
size_t allocSize = ToAllocSize(size);
do {
tryTimes++;
internalAddr = TryAllocateOnce(allocSize, allocType);
if (LIKELY(internalAddr != 0)) {
break;
}
if (IsGcThread()) {
return 0;
}
if (!ShouldRetryAllocation(tryTimes, size)) {
break;
}
(void)sched_yield();
} while (true);
if (internalAddr == 0) {
return 0;
}
#if defined(CANGJIE_TSAN_SUPPORT)
Sanitizer::TsanAllocObject(reinterpret_cast<void *>(internalAddr), allocSize);
#endif
return internalAddr + HEADER_SIZE;
}
void RegionSpace::Init(const HeapParam& vmHeapParam)
{
MemMap::Option opt = MemMap::DEFAULT_OPTIONS;
opt.tag = "cangjie_heap";
size_t heapSize = vmHeapParam.heapSize * 1024;
size_t totalSize = RegionManager::GetHeapMemorySize(heapSize);
size_t unitNum = RegionManager::GetHeapUnitCount(heapSize);
#if defined(CANGJIE_ASAN_SUPPORT)
opt.flags &= ~MAP_PRIVATE;
opt.flags |= MAP_SHARED;
DLOG(SANITIZER, "mmap flags set to 0x%x", opt.flags);
#endif
map = MemMap::MapMemory(totalSize, totalSize, opt);
#if defined(CANGJIE_SANITIZER_SUPPORT) || defined(CANGJIE_GWPASAN_SUPPORT)
Sanitizer::OnHeapAllocated(map->GetBaseAddr(), map->GetMappedSize());
#endif
Logger::GetLogger().SetMinimumLogLevel(CangjieRuntime::GetLogParam().logLevel);
MAddress metadata = reinterpret_cast<MAddress>(map->GetBaseAddr());
regionManager.Initialize(unitNum, metadata);
reservedStart = regionManager.GetRegionHeapStart();
reservedEnd = reinterpret_cast<MAddress>(map->GetMappedEndAddr());
#if defined(MRT_DUMP_ADDRESS)
VLOG(REPORT, "region metadata@%zx, heap @[0x%zx+%zu, 0x%zx)", metadata, reservedStart, reservedEnd - reservedStart,
reservedEnd);
#endif
Heap::OnHeapCreated(reservedStart);
Heap::OnHeapExtended(reservedEnd);
}
AllocBuffer* AllocBuffer::GetOrCreateAllocBuffer()
{
auto* buffer = AllocBuffer::GetAllocBuffer();
if (buffer == nullptr) {
buffer = new (std::nothrow) AllocBuffer();
CHECK_DETAIL(buffer != nullptr, "new region alloc buffer fail");
buffer->Init();
ThreadLocal::SetAllocBuffer(buffer);
}
return buffer;
}
AllocBuffer* AllocBuffer::GetAllocBuffer() { return ThreadLocal::GetAllocBuffer(); }
AllocBuffer::~AllocBuffer()
{
FlushRegion();
}
void AllocBuffer::Init()
{
static_assert(offsetof(AllocBuffer, tlRegion) == 0,
"need to modify the offset of this value in llvm-project at the same time");
tlRegion = RegionInfo::NullRegion();
ThreadLocal::InitializeCleaner();
Heap::GetHeap().RegisterAllocBuffer(*this);
}
void AllocBuffer::Fini()
{
Heap::GetHeap().RemoveAllocBuffer(*this);
}
MAddress AllocBuffer::Allocate(size_t totalSize, AllocType allocType)
{
MAddress addr = 0;
if (UNLIKELY(allocType == AllocType::RAW_POINTER_OBJECT)) {
return AllocateRawPointerObject(totalSize);
}
if (LIKELY(tlRegion != RegionInfo::NullRegion())) {
addr = tlRegion->Alloc(totalSize);
}
if (UNLIKELY(addr == 0)) {
addr = AllocateImpl(totalSize, allocType);
}
DLOG(ALLOC, "alloc 0x%zx(%zu)", addr, totalSize);
return addr;
}
MAddress AllocBuffer::AllocateImpl(size_t totalSize, AllocType allocType)
{
RegionSpace& theAllocator = reinterpret_cast<RegionSpace&>(Heap::GetHeap().GetAllocator());
RegionManager& manager = theAllocator.GetRegionManager();
if (LIKELY(tlRegion != RegionInfo::NullRegion())) {
MAddress addr = tlRegion->Alloc(totalSize);
if (addr != 0) {
return addr;
}
CHECK(tlRegion->IsThreadLocalRegion());
{
manager.RemoveThreadLocalRegion(tlRegion);
manager.EnlistFullThreadLocalRegion(tlRegion);
tlRegion = RegionInfo::NullRegion();
}
}
RegionInfo* r = preparedRegion.load(std::memory_order_acquire);
if (r != nullptr) {
preparedRegion.store(nullptr, std::memory_order_release);
tlRegion = r;
if (theAllocator.IsAsyncAllocationEnable()) {
theAllocator.AddHungryBuffer(*this);
Heap::GetHeap().GetFinalizerProcessor().NotifyToFeedAllocBuffers();
}
return r->Alloc(totalSize);
}
CJThreadPreemptOffCntAdd();
r = manager.AllocateThreadLocalRegion();
CJThreadPreemptOffCntSub();
if (UNLIKELY(r == nullptr)) {
return 0;
}
if (tlRegion == RegionInfo::NullRegion()) {
tlRegion = r;
return r->Alloc(totalSize);
}
MAddress addr = tlRegion->Alloc(totalSize);
if (addr != 0) {
if (!SetPreparedRegion(r)) {
manager.RemoveThreadLocalRegion(r);
manager.ReclaimRegion(r);
}
return addr;
}
manager.RemoveThreadLocalRegion(tlRegion);
manager.EnlistFullThreadLocalRegion(tlRegion);
tlRegion = r;
return r->Alloc(totalSize);
}
MAddress AllocBuffer::AllocateRawPointerObject(size_t totalSize)
{
RegionInfo* region = tlRawPointerRegions.GetHeadRegion();
if (region != nullptr) {
MAddress allocAddr = region->Alloc(totalSize);
if (allocAddr != 0) {
return allocAddr;
}
}
RegionManager& manager = reinterpret_cast<RegionSpace&>(Heap::GetHeap().GetAllocator()).GetRegionManager();
size_t needUnitNum = AlignUp(totalSize, RegionInfo::UNIT_SIZE) / RegionInfo::UNIT_SIZE;
if (totalSize <= manager.GetThreadLocalRegionSize()) {
region = manager.TakeRegion(needUnitNum, RegionInfo::UnitRole::SMALL_SIZED_UNITS);
if (region == nullptr) {
return 0;
}
tlRawPointerRegions.PrependRegion(region, RegionInfo::RegionType::TL_RAW_POINTER_REGION);
} else {
region = manager.TakeRegion(needUnitNum, RegionInfo::UnitRole::LARGE_SIZED_UNITS);
if (region == nullptr) {
return 0;
}
tlLargeRawPointerRegions.PrependRegion(region, RegionInfo::RegionType::TL_LARGE_RAW_POINTER_REGION);
}
MAddress allocAddr = region->Alloc(totalSize);
MRT_ASSERT(allocAddr != 0, "allocation failure");
return allocAddr;
}
void AllocBuffer::CommitRawPointerRegions()
{
RegionManager& manager = reinterpret_cast<RegionSpace&>(Heap::GetHeap().GetAllocator()).GetRegionManager();
manager.MergeRawPointerRegions(tlRawPointerRegions, tlLargeRawPointerRegions);
}
#if defined(MRT_DEBUG) && (MRT_DEBUG == 1)
bool RegionSpace::IsHeapObject(MAddress addr) const
{
return IsHeapAddress(addr);
}
#endif
void RegionSpace::FeedHungryBuffers()
{
ScopedObjectAccess soa;
AllocBufferManager::HungryBuffers hungryBuffers;
allocBufferManager->SwapHungryBuffers(hungryBuffers);
RegionInfo* region = nullptr;
for (auto* buffer : hungryBuffers) {
if (buffer->GetPreparedRegion() != nullptr) { continue; }
if (region == nullptr) {
region = regionManager.AllocateThreadLocalRegion(true);
if (region == nullptr) { return; }
}
if (buffer->SetPreparedRegion(region)) {
region = nullptr;
}
}
if (region != nullptr) {
regionManager.RemoveThreadLocalRegion(region);
regionManager.CollectRegion(region);
}
}
void AllocBuffer::FlushRegion()
{
if (LIKELY(tlRegion != RegionInfo::NullRegion()) && tlRegion != nullptr) {
RegionSpace& theAllocator = reinterpret_cast<RegionSpace&>(Heap::GetHeap().GetAllocator());
RegionManager& manager = theAllocator.GetRegionManager();
manager.RemoveThreadLocalRegion(tlRegion);
manager.EnlistFullThreadLocalRegion(tlRegion);
tlRegion = RegionInfo::NullRegion();
}
RegionInfo* prepared = preparedRegion.load();
if (LIKELY(prepared != RegionInfo::NullRegion()) && prepared != nullptr) {
RegionSpace& theAllocator = reinterpret_cast<RegionSpace&>(Heap::GetHeap().GetAllocator());
RegionManager& manager = theAllocator.GetRegionManager();
manager.RemoveThreadLocalRegion(prepared);
if (prepared->IsEmpty()) {
manager.ReclaimRegion(prepared);
} else {
manager.EnlistFullThreadLocalRegion(prepared);
}
preparedRegion.store(nullptr, std::memory_order_release);
}
}
}