Permalink
Find file
97c1f9d Jan 8, 2016
273 lines (222 sloc) 8.27 KB
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
//
// GCSample.cpp
//
//
// This sample demonstrates:
//
// * How to initialize GC without the rest of CoreCLR
// * How to create a type layout information in format that the GC expects
// * How to implement fast object allocator and write barrier
// * How to allocate objects and work with GC handles
//
// An important part of the sample is the GC environment (gcenv.*) that provides methods for GC to interact
// with the OS and execution engine.
//
// The methods to interact with the OS should be no surprise - block memory allocation, synchronization primitives, etc.
//
// The important methods that the execution engine needs to provide to GC are:
//
// * Thread suspend/resume:
// static void SuspendEE(SUSPEND_REASON reason);
// static void RestartEE(bool bFinishedGC); //resume threads.
//
// * Enumeration of thread-local allocators:
// static void GcEnumAllocContexts (enum_alloc_context_func* fn, void* param);
//
// * Scanning of stack roots:
// static void GcScanRoots(promote_func* fn, int condemned, int max_gen, ScanContext* sc);
//
// The sample has trivial implementation for these methods. It is single threaded, and there are no stack roots to
// be reported. There are number of other callbacks that GC calls to optionally allow the execution engine to do its
// own bookkeeping.
//
// For now, the sample GC environment has some cruft in it to decouple the GC from Windows and rest of CoreCLR.
// It is something we would like to clean up.
//
#include "common.h"
#include "gcenv.h"
#include "gc.h"
#include "objecthandle.h"
#include "gcdesc.h"
//
// The fast paths for object allocation and write barriers is performance critical. They are often
// hand written in assembly code, etc.
//
Object * AllocateObject(MethodTable * pMT)
{
alloc_context * acontext = GetThread()->GetAllocContext();
Object * pObject;
size_t size = pMT->GetBaseSize();
uint8_t* result = acontext->alloc_ptr;
uint8_t* advance = result + size;
if (advance <= acontext->alloc_limit)
{
acontext->alloc_ptr = advance;
pObject = (Object *)result;
}
else
{
pObject = GCHeap::GetGCHeap()->Alloc(acontext, size, 0);
if (pObject == NULL)
return NULL;
}
pObject->RawSetMethodTable(pMT);
return pObject;
}
#if defined(BIT64)
// Card byte shift is different on 64bit.
#define card_byte_shift 11
#else
#define card_byte_shift 10
#endif
#define card_byte(addr) (((size_t)(addr)) >> card_byte_shift)
inline void ErectWriteBarrier(Object ** dst, Object * ref)
{
// if the dst is outside of the heap (unboxed value classes) then we
// simply exit
if (((uint8_t*)dst < g_lowest_address) || ((uint8_t*)dst >= g_highest_address))
return;
if((uint8_t*)ref >= g_ephemeral_low && (uint8_t*)ref < g_ephemeral_high)
{
// volatile is used here to prevent fetch of g_card_table from being reordered
// with g_lowest/highest_address check above. See comment in code:gc_heap::grow_brick_card_tables.
uint8_t* pCardByte = (uint8_t *)*(volatile uint8_t **)(&g_card_table) + card_byte((uint8_t *)dst);
if(*pCardByte != 0xFF)
*pCardByte = 0xFF;
}
}
void WriteBarrier(Object ** dst, Object * ref)
{
*dst = ref;
ErectWriteBarrier(dst, ref);
}
void PrintGCStats(GCHeap *pGCHeap)
{
printf("\nGC Stats:\n");
printf(" GetGcCount: %i\n", pGCHeap->GetGcCount());
printf(" Gen0: %i, Gen1: %i, Gen2: %i, Gen3(LOH): %i\n",
pGCHeap->CollectionCount(0), pGCHeap->CollectionCount(1), pGCHeap->CollectionCount(2), pGCHeap->CollectionCount(3));
printf(" GetLastGCDuration(0): %i\n", pGCHeap->GetLastGCDuration(0));
printf(" GetLastGCDuration(1): %i\n", pGCHeap->GetLastGCDuration(1));
printf(" GetLastGCDuration(2): %i\n", pGCHeap->GetLastGCDuration(2));
printf(" GetNumberOfHeaps: %i\n", pGCHeap->GetNumberOfHeaps());
printf(" GetHomeHeapNumber: %i\n", pGCHeap->GetHomeHeapNumber());
printf(" GetTotalBytesInUse: %i\n", pGCHeap->GetTotalBytesInUse());
}
int __cdecl main(int argc, char* argv[])
{
//
// Initialize system info
//
if (!GCToOSInterface::Initialize())
{
return -1;
}
//
// Initialize free object methodtable. The GC uses a special array-like methodtable as placeholder
// for collected free space.
//
static MethodTable freeObjectMT;
freeObjectMT.InitializeFreeObject();
g_pFreeObjectMethodTable = &freeObjectMT;
//
// Initialize handle table
//
if (!Ref_Initialize())
return -1;
//
// Initialize GC heap
//
GCHeap *pGCHeap = GCHeap::CreateGCHeap();
if (!pGCHeap)
return -1;
if (FAILED(pGCHeap->Initialize()))
return -1;
//
// Initialize current thread
//
ThreadStore::AttachCurrentThread();
//
// Create a Methodtable with GCDesc
//
class My : Object {
public:
Object * m_pOther1;
int dummy_inbetween;
Object * m_pOther2;
};
static struct My_MethodTable
{
// GCDesc
CGCDescSeries m_series[2];
size_t m_numSeries;
// The actual methodtable
MethodTable m_MT;
}
My_MethodTable;
// 'My' contains the MethodTable*
uint32_t baseSize = sizeof(My);
// GC expects the size of ObjHeader (extra void*) to be included in the size.
baseSize = baseSize + sizeof(ObjHeader);
// Add padding as necessary. GC requires the object size to be at least MIN_OBJECT_SIZE.
My_MethodTable.m_MT.m_baseSize = max(baseSize, MIN_OBJECT_SIZE);
My_MethodTable.m_MT.m_componentSize = 0; // Array component size
My_MethodTable.m_MT.m_flags = MTFlag_ContainsPointers;
My_MethodTable.m_numSeries = 2;
// The GC walks the series backwards. It expects the offsets to be sorted in descending order.
My_MethodTable.m_series[0].SetSeriesOffset(offsetof(My, m_pOther2));
My_MethodTable.m_series[0].SetSeriesCount(1);
My_MethodTable.m_series[0].seriessize -= My_MethodTable.m_MT.m_baseSize;
My_MethodTable.m_series[1].SetSeriesOffset(offsetof(My, m_pOther1));
My_MethodTable.m_series[1].SetSeriesCount(1);
My_MethodTable.m_series[1].seriessize -= My_MethodTable.m_MT.m_baseSize;
MethodTable * pMyMethodTable = &My_MethodTable.m_MT;
// Allocate instance of MyObject
Object * pObj = AllocateObject(pMyMethodTable);
if (pObj == NULL)
return -1;
// Create strong handle and store the object into it
OBJECTHANDLE oh = CreateGlobalHandle(pObj);
if (oh == NULL)
return -1;
for (int i = 0; i < 1000000; i++)
{
Object * pBefore = ((My *)ObjectFromHandle(oh))->m_pOther1;
// Allocate more instances of the same object
Object * p = AllocateObject(pMyMethodTable);
if (p == NULL)
return -1;
Object * pAfter = ((My *)ObjectFromHandle(oh))->m_pOther1;
// Uncomment this assert to see how GC triggered inside AllocateObject moved objects around
// assert(pBefore == pAfter);
if (pBefore != pAfter)
{
printf("%8i) pBefore = 0x%08x, pAfter = 0x%08x, diff = 0x%08x (%i)\n", i, pBefore, pAfter, pBefore - pAfter, pBefore - pAfter);
PrintGCStats(pGCHeap);
}
// Store the newly allocated object into a field using WriteBarrier
WriteBarrier(&(((My *)ObjectFromHandle(oh))->m_pOther1), p);
}
// Create weak handle that points to our object
OBJECTHANDLE ohWeak = CreateGlobalWeakHandle(ObjectFromHandle(oh));
if (ohWeak == NULL)
return -1;
// Destroy the strong handle so that nothing will be keeping out object alive
DestroyGlobalHandle(oh);
// Verify that the weak handle has not yet been cleared (i.e. before the GC has run)
assert(ObjectFromHandle(ohWeak) != NULL);
printf("\nBEFORE Final Call to pGCHeap->GarbageCollect()");
PrintGCStats(pGCHeap);
// Explicitly trigger full GC
pGCHeap->GarbageCollect();
printf("\nAFTER Final Call to pGCHeap->GarbageCollect()");
PrintGCStats(pGCHeap);
// Verify that the weak handle got cleared by the GC
assert(ObjectFromHandle(ohWeak) == NULL);
printf("Done\n");
return 0;
}