/*******************************************************************/ /* WARNING */ /* This file should be identical in the Bartok and Singularity */ /* depots. Master copy resides in Bartok Depot. Changes should be */ /* made to Bartok Depot and propagated to Singularity Depot. */ /*******************************************************************/ // // Copyright (c) Microsoft Corporation. All rights reserved. // namespace System.GCs { using System.Threading; using System.Runtime.CompilerServices; using Microsoft.Bartok.Runtime; internal class MemoryAccounting { private static BumpAllocator localArea; private static ManagedPtrAccounting traceablePtrs; private static ManagedPtrAccounting traceablePinnedPtrs; private static StaticPtrAccounting staticPtrs; private static AssertRTypeHeaders assertRTypeHeaders; private static ObjectLayout.ObjectVisitor forGCAssertRTypeHeaders; private static RuntimeTypeReckoner runtimeTypeReckoner; private static ObjectLayout.ObjectVisitor forGCRuntimeTypeReckoner; private static RuntimeTypeMapper runtimeTypeMapper; private static ObjectLayout.ObjectVisitor forGCRuntimeTypeMapper; private static InstanceReckoner instanceReckoner; private static ObjectLayout.ObjectVisitor forGCInstanceReckoner; private const int TABLE_SIZE = 1024; private static RuntimeTypeAccounting[] table; private static UIntPtr totalSize; private static bool initialized; internal static long MaxHeapWatermark; internal static double AvgHeapSize; internal static long Watermarks; internal unsafe static void Initialize(GCType gcType) { traceablePtrs = new ManagedPtrAccounting(); traceablePinnedPtrs = new ManagedPtrAccounting(); staticPtrs = new StaticPtrAccounting(); assertRTypeHeaders = new AssertRTypeHeaders(); forGCAssertRTypeHeaders = assertRTypeHeaders; runtimeTypeReckoner = new RuntimeTypeReckoner(); forGCRuntimeTypeReckoner = runtimeTypeReckoner; runtimeTypeMapper = new RuntimeTypeMapper(); forGCRuntimeTypeMapper = runtimeTypeMapper; instanceReckoner = new InstanceReckoner(); forGCInstanceReckoner = instanceReckoner; switch(gcType) { case GCType.MarkSweepCollector: { forGCAssertRTypeHeaders = new SegregatedFreeList.ObjectVisitorWrapper(assertRTypeHeaders); forGCRuntimeTypeReckoner = new SegregatedFreeList.ObjectVisitorWrapper(runtimeTypeReckoner); forGCRuntimeTypeMapper = new SegregatedFreeList.ObjectVisitorWrapper(runtimeTypeMapper); forGCInstanceReckoner = new SegregatedFreeList.ObjectVisitorWrapper(instanceReckoner); break; } } table = new RuntimeTypeAccounting[TABLE_SIZE]; MemoryAccounting.initialized = true; } internal unsafe static void Report(GCType gcType) { if(!MemoryAccounting.initialized) { VTable.DebugPrint("MemoryAccounting invoked before" + " initialization was completed!"); return; } VTable.DebugPrint("\nCollector: "); switch(gcType) { case GCType.AdaptiveCopyingCollector: { VTable.DebugPrint("Adaptive Copying\n"); break; } case GCType.MarkSweepCollector: { VTable.DebugPrint("Mark-Sweep\n"); break; } case GCType.SemispaceCollector: { VTable.DebugPrint("Semispace\n"); break; } case GCType.SlidingCollector: { VTable.DebugPrint("Sliding\n"); break; } case GCType.ConcurrentMSCollector: { VTable.DebugPrint("Concurrent Mark-Sweep\n"); break; } case GCType.ReferenceCountingCollector: { VTable.DebugPrint("Reference Counting\n"); VTable.NotImplemented(); return; // Not supported yet. } #if !SINGULARITY case GCType.DeferredReferenceCountingCollector: { VTable.DebugPrint("Deferred Reference Counting\n"); VTable.NotImplemented(); return; // Not supported yet. } #endif default: { VTable.NotReached("Unknown GC type: "+gcType); break; } } uint pageSize = (uint)(PageTable.PageSize >> 10); VTable.DebugPrint("\nPage size: {0}KB\nVM size: {1} KB\n", __arglist(pageSize, pageSize*(uint)PageTable.pageTableCount)); ReportNonGCDetails(); ReportStackDetails(); ReportHeapDetails(); } internal unsafe static void ReportNonGCDetails() { VTable.DebugPrint("\nNon-GC data details:\n"); uint totalPageCount = TotalNumPages(PageType.NonGC); VTable.DebugPrint("\tTotal number of pages: {0}", __arglist(totalPageCount)); VTable.DebugPrint("\n\tMemory accounting storage: {0}B", __arglist((uint)totalSize)); staticPtrs.Initialize(); uint staticSize = StaticData.ScanStaticPointerData(staticPtrs); VTable.DebugPrint("\n\tCompile-time allocated data: {0}B", __arglist(staticSize)); VTable.DebugPrint("\n\tNumber of pointers: {0}", __arglist(staticPtrs.PtrCount)); uint bootstrapPageCount = totalPageCount - (uint)PageTable.PageCount((UIntPtr)staticSize); VTable.DebugPrint("\n\tNumber of bootstrap pages: {0}\n", __arglist(bootstrapPageCount)); } internal unsafe static void ReportStackDetails() { VTable.DebugPrint("\nStack details:\n"); VTable.DebugPrint("\tTotal number of reserved pages: {0}", __arglist(TotalNumPages(PageType.Stack))); for (int i = 0; i < Thread.threadTable.Length; i++) { traceablePtrs.Initialize(); traceablePinnedPtrs.Initialize(); Thread t = Thread.threadTable[i]; if (t == null) { continue; } uint numStackFrames = CallStack.ScanStack(t, traceablePtrs, traceablePinnedPtrs); VTable.DebugPrint("\n\tThread: {0}", __arglist(i)); VTable.DebugPrint("\n\t\tStack frame count: {0}", __arglist(numStackFrames)); VTable.DebugPrint("\n\t\tManaged ptrs to static data: {0}", __arglist (traceablePtrs.managedPtrsToStaticData)); VTable.DebugPrint("\n\t\tManaged ptrs to stack: {0}", __arglist(traceablePtrs.managedPtrsToStack)); VTable.DebugPrint("\n\t\tExterior managed heap ptrs: {0}", __arglist (traceablePtrs.exteriorManagedHeapPtrs)); VTable.DebugPrint("\n\t\tInterior managed heap ptrs: {0}", __arglist (traceablePtrs.interiorManagedHeapPtrs)); #if !SINGULARITY UIntPtr stackTop = (UIntPtr) CallStack.StackMarker(t); uint stackSize = 0; if (stackTop != UIntPtr.Zero) { VTable.Assert(CallStack.StackBase(t) > stackTop, @"t.asmStackBase > stackTop"); stackSize = (uint)(CallStack.StackBase(t)-stackTop); } VTable.DebugPrint("\n\t\tApprox. stack size: {0}B", __arglist(stackSize)); #endif // REVIEW: Why are these here? VTable.Assert(traceablePinnedPtrs. managedPtrsToStaticData == 0, @"traceablePinnedPtrs. managedPtrsToStaticData == 0"); VTable.Assert(traceablePinnedPtrs. managedPtrsToStack == 0, @"traceablePinnedPtrs. managedPtrsToStack == 0"); VTable.DebugPrint("\n\t\tPinned managed heap ptrs: {0}", __arglist (traceablePinnedPtrs.exteriorManagedHeapPtrs + traceablePinnedPtrs.interiorManagedHeapPtrs)); } VTable.DebugPrint("\n"); } internal static void ReportHeapDetails() { VTable.DebugPrint("\nHeap details:\n"); uint pageCount = 0; for (UIntPtr i = UIntPtr.Zero; i < PageTable.pageTableCount; i++) { if (PageTable.IsMyGcPage(i)) { pageCount++; } } VTable.DebugPrint("\tTotal number of heap pages: {0}", __arglist(pageCount)); // The following obtains counts of heap objects against types. UIntPtr lowPage = UIntPtr.Zero; UIntPtr highPage = PageTable.pageTableCount; assertRuntimeTypeHeaders(lowPage, highPage); // First count the RuntimeType instances for heap objects. runtimeTypeReckoner.Initialize(true); visitAllObjects(forGCRuntimeTypeReckoner, lowPage, highPage); // Next, create a table for RuntimeType instance accounting. // NOTE: Storage for the table is marked "non-GC". Since // static data accounting is done before this, it's okay. int numSlots = runtimeTypeReckoner.Count; if(numSlots > TABLE_SIZE) { VTable.DebugPrint("Need {0} slots, have {1}\n", __arglist(numSlots, TABLE_SIZE)); VTable.NotReached("MemoryAccounting table not large enough"); } // Associate a table slot for each RuntimeType instance. runtimeTypeMapper.Initialize(false, MemoryAccounting.table); visitAllObjects(forGCRuntimeTypeMapper, lowPage, highPage); // Map each relevant RuntimeType instance to its table slot. for (uint i = 0; i < numSlots; i++) { RuntimeType rType = MemoryAccounting.table[i].RuntimeTypeObject; VTable.Assert(!MultiUseWord.IsMarked(rType), @"!MultiUseWord.IsMarked(rType)"); MemoryAccounting.table[i].SavedMUW = MultiUseWord.GetForObject(rType); MultiUseWord.SetValForObject(rType, (UIntPtr)i); } // Count heap object instances by RuntimeType using table. instanceReckoner.Initialize(MemoryAccounting.table); visitAllObjects(forGCInstanceReckoner, lowPage, highPage); // Bubble sort the table in decreasing order of total size. for (int i = 0; i < numSlots; i++) { for (int j = numSlots-1; j > i; j--) { if (MemoryAccounting.table[j].TotalSize > MemoryAccounting.table[j-1].TotalSize) { // Swap contents. RuntimeTypeAccounting temp = MemoryAccounting.table[j]; MemoryAccounting.table[j] = MemoryAccounting.table[j-1]; MemoryAccounting.table[j-1] = temp; } } } // Display table. VTable.DebugPrint("\n\tCounts of objects against types:\n"); for (uint i = 0; i < numSlots; i++) { if((uint)MemoryAccounting.table[i].TotalSize < 1024) { continue; } VTable.DebugPrint ("\t\t{0,36} instances: {1,6}, bytes: {2,10}\n", __arglist(MemoryAccounting.table[i].RuntimeTypeObject.Name, (uint)MemoryAccounting.table[i].Count, (uint)MemoryAccounting.table[i].TotalSize)); } // Reset book-keeping information maintained in headers and the global // table. for (uint i = 0; i < numSlots; i++) { RuntimeType rType = MemoryAccounting.table[i].RuntimeTypeObject; MultiUseWord.SetForObject (rType, MemoryAccounting.table[i].SavedMUW); VTable.Assert(!MultiUseWord.IsMarked(rType), "@!MultiUseWord.IsMarked(rType)"); MemoryAccounting.table[i].RuntimeTypeObject = null; MemoryAccounting.table[i].SavedMUW = new MultiUseWord(new UIntPtr(0)); MemoryAccounting.table[i].TotalSize = new UIntPtr(0); MemoryAccounting.table[i].Count = 0; } } internal static void ReportHeapWatermarks() { VTable.DebugPrint("Max. Heap: {0}KB ", __arglist(MaxHeapWatermark >> 10)); VTable.DebugPrint("Avg. Heap: {0}KB ", __arglist(((ulong)AvgHeapSize) >> 10)); VTable.DebugPrint("\n"); } [ManualRefCounts] internal static void RecordHeapWatermarks() { long totalMemory = GC.installedGC.TotalMemory; if (totalMemory > MaxHeapWatermark) { MaxHeapWatermark = totalMemory; } AvgHeapSize = (AvgHeapSize*Watermarks+totalMemory)/(Watermarks+1); Watermarks++; } internal static uint TotalNumPages(PageType kind) { uint pageCount = 0; for (UIntPtr i = UIntPtr.Zero; i < PageTable.pageTableCount; i++) { if (PageTable.IsMyPage(i)) { PageType pageType = PageTable.Type(i); if (pageType == kind) { pageCount++; } } } return pageCount; } private static void visitAllObjects(ObjectLayout.ObjectVisitor visitor, UIntPtr lowPage, UIntPtr highPage) { for (UIntPtr first = lowPage; first < highPage; first++) { if (PageTable.IsMyGcPage(first)) { UIntPtr last = first+1; while (last < highPage) { if (!PageTable.IsMyGcPage(last)) { break; } last++; } UIntPtr start = PageTable.PageAddr(first); UIntPtr end = PageTable.PageAddr(last); GC.installedGC.VisitObjects(visitor, start, end); first = last; } } } [System.Diagnostics.Conditional("DEBUG")] private static void assertRuntimeTypeHeaders(UIntPtr lowPage, UIntPtr highPage) { visitAllObjects(forGCAssertRTypeHeaders, lowPage, highPage); } private class ManagedPtrAccounting : NonNullReferenceVisitor { internal uint managedPtrsToStaticData; internal uint managedPtrsToStack; internal uint interiorManagedHeapPtrs; internal uint exteriorManagedHeapPtrs; internal void Initialize() { managedPtrsToStaticData = 0; managedPtrsToStack = 0; interiorManagedHeapPtrs = 0; exteriorManagedHeapPtrs = 0; } internal override unsafe void Visit(UIntPtr* loc) { // is a traceable pointer; its referent // either resides in the heap, the stack or in // the static data area. UIntPtr addr = *loc; UIntPtr page = PageTable.Page(addr); VTable.Assert(PageTable.IsMyPage(page), "MemoryAccounting: !IsMyPage"); if (!PageTable.IsGcPage(page)) { PageType pageType = PageTable.Type(page); VTable.Assert(pageType == PageType.NonGC || pageType == PageType.Stack || pageType == PageType.Shared, "unexpected page type"); if (pageType == PageType.NonGC) { // A managed pointer into the static data area. managedPtrsToStaticData++; } else { // A managed pointer into the stack area. managedPtrsToStack++; } return; } UIntPtr objAddr = GC.installedGC.FindObjectAddr(addr); if (objAddr != addr) { // A "truly" interior pointer into a heap object. interiorManagedHeapPtrs++; } else { exteriorManagedHeapPtrs++; } } } private class StaticPtrAccounting : NonNullReferenceVisitor { internal uint PtrCount; internal void Initialize() { PtrCount = 0; } internal override unsafe void Visit(UIntPtr* loc) { // is an address of a reference location // in the static data area. UIntPtr page = PageTable.Page((UIntPtr)loc); PageType pageType = PageTable.Type(page); VTable.Assert(pageType == PageType.NonGC, @"pageType == PageType.NonGC"); PtrCount++; } } private struct RuntimeTypeAccounting { // Reference to the accounted RuntimeType object. internal RuntimeType RuntimeTypeObject; // Copy of the accounted RuntimeType MultiUseWord. internal MultiUseWord SavedMUW; // Total size of all instances having this RuntimeType. internal UIntPtr TotalSize; // Number of instances having this RuntimeType. internal uint Count; } private class AssertRTypeHeaders : ObjectLayout.ObjectVisitor { internal override unsafe UIntPtr Visit(Object obj) { VTable vtable = obj.vtable; RuntimeType rType = vtable.vtableType; VTable.Assert(!MultiUseWord.IsMarked(rType), "@!MultiUseWord.IsMarked(rType)"); return ObjectLayout.ObjectSize(Magic.addressOf(obj), vtable); } } private class RuntimeTypeReckoner : ObjectLayout.ObjectVisitor { internal int Count; internal void Initialize(bool isVisitedFlag) { this.isVisitedFlag = isVisitedFlag; this.Count = 0; } internal override unsafe UIntPtr Visit(Object obj) { VTable vtable = obj.vtable; RuntimeType rType = vtable.vtableType; if (MultiUseWord.IsMarked(rType) != this.isVisitedFlag) { this.Count++; MultiUseWord.SetMark(rType, this.isVisitedFlag); } return ObjectLayout.ObjectSize(Magic.addressOf(obj), vtable); } private bool isVisitedFlag; } private class RuntimeTypeMapper : ObjectLayout.ObjectVisitor { internal void Initialize(bool isVisitedFlag, RuntimeTypeAccounting[] table) { this.isVisitedFlag = isVisitedFlag; this.tableIndex = 0; this.accounts = table; } internal override unsafe UIntPtr Visit(Object obj) { VTable vtable = obj.vtable; RuntimeType rType = vtable.vtableType; if (MultiUseWord.IsMarked(rType) != this.isVisitedFlag) { VTable.Assert(this.tableIndex < this.accounts.Length, @"this.tableIndex < this.accounts.Length"); this.accounts[this.tableIndex].RuntimeTypeObject = rType; this.accounts[this.tableIndex].TotalSize = UIntPtr.Zero; this.accounts[this.tableIndex].Count = 0; MultiUseWord.SetMark(rType, this.isVisitedFlag); this.tableIndex++; } return ObjectLayout.ObjectSize(Magic.addressOf(obj), vtable); } private bool isVisitedFlag; private uint tableIndex; private RuntimeTypeAccounting[] accounts; } private class InstanceReckoner : ObjectLayout.ObjectVisitor { internal void Initialize(RuntimeTypeAccounting[] table) { this.accounts = table; } internal override unsafe UIntPtr Visit(Object obj) { VTable vtable = obj.vtable; RuntimeType rType = vtable.vtableType; uint tableIndex = (uint) MultiUseWord.GetValForObject(rType); UIntPtr objAddr = Magic.addressOf(obj); this.accounts[tableIndex].TotalSize += ObjectLayout.ObjectSize(objAddr, vtable); this.accounts[tableIndex].Count++; return ObjectLayout.ObjectSize(objAddr, vtable); } private RuntimeTypeAccounting[] accounts; } } }