blob: 61c8efc6bf30e0d0e90a32081387db9e1da99831 [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#pragma once
#if !FLOATVAR
/****************************************************************************
* CodeGenNumberThreadAllocator
*
* Recycler can't be used by multiple threads. This allocator is used by
* the JIT to allocate numbers in the background thread. It allocates
* pages directly from the OS and then allocates numbers from these pages,
* along with a linked list of chunks that hold the numbers so the native
* entry points can keep these numbers alive. Then this memory is
* integrated back into the main thread's recycler at the beginning of
* GC.
*
* This class requires intimate knowledge of how recycler allocates memory
* so the memory can be integrated back. So changes in the recycler
* will affect this allocator.
*
* An alternative solution is to record the number we need to create,
* and only create them on the main thread when the entry point is used.
* However, since we don't have the address of the number at JIT time,
* we will have to incur two indirections (loading the array of pointers to
* numbers and loading the numbers) in the JIT code which is not desirable.
*
* Implementation details:
*
* The segments can be integrated back to the recycler's page allocator
* immediately. They are all marked as used initially.
*
* Numbers and the linked list chunks are allocated in separate pages
* as number will be a leaf page when integrated back to the recycler
* and the linked list chunks will be normal pages.
*
* Generally, when a page is full, it is (almost) ready to be integrated
* back to the recycler. However the number pages need to wait until
* the referencing linked list chunk is integrated before it can be
* integrated (otherwise, the recycler will not see the reference that
* keeps the numbers alive). So when a number page is full, it will
* go to pendingReferenceNumberBlock list. Then, when a linked list
* chunk is full, all the pages in pendingReferenceNumberBlock can go to
* pendingFlushNumberBlock list and the linked list chunk page will
* go to pendingFlushChunkBlock.
*
* Once we finish jitting a function and the number link list is set on the
* entry point, these pages are ready to be integrated back to recycler
* and be moved to pendingIntegration*Pages lists. Access to the
* pendingIntegration*Pages are synchronized, therefore the main thread
* can do the integration before GC happens.
*
****************************************************************************/
struct CodeGenNumberChunk
{
static int const MaxNumberCount = 3;
Field(Js::JavascriptNumber*) numbers[MaxNumberCount];
Field(CodeGenNumberChunk*) next;
};
CompileAssert(
sizeof(CodeGenNumberChunk) == HeapConstants::ObjectGranularity ||
sizeof(CodeGenNumberChunk) == HeapConstants::ObjectGranularity * 2);
class CodeGenNumberThreadAllocator
{
friend struct XProcNumberPageSegmentManager;
public:
CodeGenNumberThreadAllocator(Recycler * recycler);
~CodeGenNumberThreadAllocator();
// All the public API's need to be guarded by critical sections.
// Multiple jit threads access this.
Js::JavascriptNumber * AllocNumber();
CodeGenNumberChunk * AllocChunk();
void Integrate();
void FlushAllocations();
private:
// All allocations are small allocations
const size_t BlockSize = SmallAllocationBlockAttributes::PageCount * AutoSystemInfo::PageSize;
void AllocNewNumberBlock();
void AllocNewChunkBlock();
size_t GetNumberAllocSize();
size_t GetChunkAllocSize();
CriticalSection cs;
Recycler * recycler;
PageSegment * currentNumberSegment;
PageSegment * currentChunkSegment;
char * numberSegmentEnd;
char * currentNumberBlockEnd;
char * nextNumber;
char * chunkSegmentEnd;
char * currentChunkBlockEnd;
char * nextChunk;
bool hasNewNumberBlock;
bool hasNewChunkBlock;
struct BlockRecord
{
BlockRecord(__in_ecount_pagesize char * blockAddress, PageSegment * segment)
: blockAddress(blockAddress), segment(segment)
{
}
char * blockAddress;
PageSegment * segment;
};
// Keep track of segments and pages that needs to be integrated to the recycler.
uint pendingIntegrationNumberSegmentCount;
uint pendingIntegrationChunkSegmentCount;
size_t pendingIntegrationNumberSegmentPageCount;
size_t pendingIntegrationChunkSegmentPageCount;
DListBase<PageSegment> pendingIntegrationNumberSegment;
DListBase<PageSegment> pendingIntegrationChunkSegment;
SListBase<BlockRecord, NoThrowHeapAllocator> pendingIntegrationNumberBlock;
SListBase<BlockRecord, NoThrowHeapAllocator> pendingIntegrationChunkBlock;
// These are finished pages during the code gen of the current function
// We can't integrate them until the code gen is done for the function,
// because the references for the number is not set on the entry point yet.
SListBase<BlockRecord, NoThrowHeapAllocator> pendingFlushNumberBlock;
SListBase<BlockRecord, NoThrowHeapAllocator> pendingFlushChunkBlock;
// Numbers are reference by the chunks, so we need to wait until that is ready
// to be flushed before the number page can be flushed. Otherwise, we might have number
// integrated back to the GC, but the chunk hasn't yet, thus GC won't see the reference.
SListBase<BlockRecord, NoThrowHeapAllocator> pendingReferenceNumberBlock;
};
class CodeGenNumberAllocator
{
public:
CodeGenNumberAllocator(CodeGenNumberThreadAllocator * threadAlloc, Recycler * recycler);
// We should never call this function if we are using tagged float
#if !FLOATVAR
Js::JavascriptNumber * Alloc();
#endif
CodeGenNumberChunk * Finalize();
private:
Recycler * recycler;
CodeGenNumberThreadAllocator * threadAlloc;
CodeGenNumberChunk * chunk;
CodeGenNumberChunk * chunkTail;
uint currentChunkNumberCount;
#if DBG
bool finalized;
#endif
};
namespace Js
{
class StaticType;
}
struct XProcNumberPageSegmentImpl : public XProcNumberPageSegment
{
XProcNumberPageSegmentImpl();
Js::JavascriptNumber* AllocateNumber(Func* func, double value);
unsigned int GetTotalSize() { return PageCount * AutoSystemInfo::PageSize; }
void* GetEndAddress() { return (void*)(this->pageAddress + PageCount * AutoSystemInfo::PageSize); }
void* GetCommitEndAddress() { return (void*)(this->pageAddress + this->committedEnd); }
static const uint BlockSize = SmallAllocationBlockAttributes::PageCount*AutoSystemInfo::PageSize;
static const uint PageCount = Memory::IdleDecommitPageAllocator::DefaultMaxAllocPageCount;
static uint sizeCat;
static void Initialize(bool recyclerVerifyEnabled, uint recyclerVerifyPad);
};
static_assert(sizeof(XProcNumberPageSegmentImpl) == sizeof(XProcNumberPageSegment), "should not have data member in XProcNumberPageSegmentImpl");
struct XProcNumberPageSegmentManager
{
CriticalSection cs;
XProcNumberPageSegmentImpl* segmentsList;
Recycler* recycler;
unsigned int integratedSegmentCount;
XProcNumberPageSegmentManager(Recycler* recycler);
~XProcNumberPageSegmentManager();
XProcNumberPageSegment * GetFreeSegment(Memory::ArenaAllocator* alloc);
Field(Js::JavascriptNumber*)* RegisterSegments(XProcNumberPageSegment* segments);
void Integrate();
};
#endif