// // Copyright (c) Microsoft Corporation. All rights reserved. // /*******************************************************************/ /* 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. */ /*******************************************************************/ namespace System.GCs { using Microsoft.Bartok.Runtime; using System.Runtime.CompilerServices; using System.Threading; #if SINGULARITY using Microsoft.Singularity; #if SINGULARITY_PROCESS using Microsoft.Singularity.V1.Services; // Used for timing, only #elif SINGULARITY_KERNEL using Microsoft.Singularity.Scheduling; #endif #else using Microsoft.Win32; #endif using MarkingPhase = CMSMarking.MarkingPhase; /// /// This class implements a semi-concurrent version of MarkSweep. /// /// The goal for this collector is to perform as much of the processing /// as possible during mutator time, and also have a fixed upper bound /// on pause times. /// /// Additionally, the collector will be required in the future to /// communicate with the scheduler to prevent real time applications /// from competing with the GC. /// /// /// TODO: BUGBUG: NEED TO REPLICATE THE ALLOCATION COLOR LOGIC TO THE /// INLINED ALLOCATION SEQUENCE. /// [NoCCtor] internal class ConcurrentMSCollector: BaseCollector { internal static bool UseAnyThrottle { get { return true; } } internal static bool UseYieldThrottle { get { return UseAnyThrottle && false; } } internal static bool UseSleepThrottle { get { return UseAnyThrottle && true; } } internal static bool UseOverlappingPhases { get { return false; } } internal static bool UseSTWTracingPhase { get { return GC.enableSTWRetrace; } } private static MarkingPhase CurrentMarkingPhase { [NoStackLinkCheck] get { return CMSMarking.CurrentMarkingPhase; } } internal enum ReclamationPhase { Dummy, // Not used! Idle, // No reclamation is in progress Reclaiming, // Reclamation is in progress } internal static ReclamationPhase CurrentReclamationPhase; #if SINGULARITY private static TimeSpan infiniteWait; private static TimeSpan minimumWait; #else private static int infiniteWait; private static int minimumWait; #endif /// /// Current mark state. This is flipped between collections. /// internal static UIntPtr markedColor { get { return CMSMarking.markedColor; } set { CMSMarking.markedColor = value; } } internal static UIntPtr unmarkedColor { get { return CMSMarking.unmarkedColor; } set { CMSMarking.unmarkedColor = value; } } internal static UIntPtr reclamationColor; private const uint markOne = 1U; private const uint markTwo = 2U; private const uint markThree = 3U; #if SINGULARITY // // Note: This is updated to allow a large SINGULARITY build to advance // through starting all CPU's before triggering the first collection. // // If we enter the marking phase while starting the second processor // it will crash while trying to setup its processor context to point // to the current thread which must be valid for the marking list. // private const int minimumCollectionTrigger = (1 << 25); // 32 MB #else private const int minimumCollectionTrigger = (1 << 24); // 16 MB #endif private const int maximumCollectionTrigger = (1 << 26); // 64 MB /// /// Amount of memory to allocate between collections. /// BUGBUG: This heuristic is not very good for CMS. /// private static UIntPtr collectionTrigger; internal static ConcurrentMSCollector instance; // Pointer visitors used for marking, etc internal static MarkReferenceVisitor markReferenceVisitor; internal static UpdateReferenceVisitor updateReferenceVisitor; internal static NonNullReferenceVisitor stackMarkReferenceVisitor; internal static NonNullReferenceVisitor stackMarkPinnedReferenceVisitor; internal static UnmarkVisitor unmarkVisitor; // Object visitors used for sweeping, etc internal static SweepVisitor sweepVisitor; // What to do when we encounter a partially free page internal static SegregatedFreeList.PartialFreePageVisitor partialFreePageVisitor; // Which threads are in what marking stage? private static UIntPtr[] threadColor; // Threads waiting for a particular collection to finish private static int[] waitingThreads; private static int firstWaitingThread; private const int waitingThreadsNullValue = -1; private const int waitingThreadsUnusedValue = -2; private const uint noColor = 0xffffffff; // The threads that performs the collection work. MUST MAKE // SURE THAT THESE ARE PINNED IN COCO. Currently that's OK // because all threads are pinned, for other reasons. [NoBarriers] private static Thread markThread; [NoBarriers] private static Thread workerThread1; [NoBarriers] private static Thread workerThread2; private static ulong totalTraceTime; private static ulong totalSTWTraceTime; private static ulong totalSweepTime; private static ulong numTraces; private static ulong numSTWTraces; private static ulong numSweeps; private static AutoResetEvent sweepPhaseMutex; // A trivial handshake executes no code other than clearing the // GCRequest. A trivial handshake's only purpose is to indicicate // that the mutator is not in the middle of a "complex" GC operation // such as a barrier operation or unlinking an element from a list. internal static bool TrivialHandshake; internal static bool IncludeMUWInHandshake; /// /// Does this collector compute a root set with threads running? /// internal override bool IsOnTheFlyCollector { get { return true; } } [NoBarriers] [PreInitRefCounts] internal static void InitializeAllButVisitors() { SegregatedFreeList.Initialize(); unmarkedColor = (UIntPtr) markOne; markedColor = (UIntPtr) markTwo; reclamationColor = (UIntPtr) markThree; // threadColor = new UIntPtr[Thread.maxThreads]; threadColor = (UIntPtr[]) BootstrapMemory.Allocate(typeof(UIntPtr[]), Thread.maxThreads); for (int i = 0; i < threadColor.Length; i++) { threadColor[i] = (UIntPtr) noColor; } TrivialHandshake = true; collectionTrigger = (UIntPtr) minimumCollectionTrigger; IncludeMUWInHandshake = true; CMSMarking.currentMarkingPhase = (int) MarkingPhase.Idle; CurrentReclamationPhase = ReclamationPhase.Idle; #if SINGULARITY infiniteWait = TimeSpan.Infinite; minimumWait = TimeSpan.FromMilliseconds(1); #else infiniteWait = Timeout.Infinite; minimumWait = 1; #endif } /// /// Initialise the collector and allow allocations to commence. /// [PreInitRefCounts] public static void Initialize() { InitializeAllButVisitors(); // instance = new ConcurrentMSCollector(); ConcurrentMSCollector.instance = (ConcurrentMSCollector) BootstrapMemory.Allocate(typeof(ConcurrentMSCollector)); // markReferenceVisitor = new MarkReferenceVisitor(); markReferenceVisitor = (MarkReferenceVisitor) BootstrapMemory.Allocate(typeof(MarkReferenceVisitor)); // updateReferenceVisitor = new UpdateReferenceVisitor(); updateReferenceVisitor = (UpdateReferenceVisitor) BootstrapMemory.Allocate(typeof(UpdateReferenceVisitor)); // stackMarkReferenceVisitor = new StackMarkReferenceVisitor(); stackMarkReferenceVisitor = (StackMarkReferenceVisitor) BootstrapMemory.Allocate(typeof(StackMarkReferenceVisitor)); stackMarkPinnedReferenceVisitor = stackMarkReferenceVisitor; unmarkVisitor = (UnmarkVisitor) BootstrapMemory.Allocate(typeof(UnmarkVisitor)); // sweepVisitor = new SweepVisitor(); sweepVisitor = (SweepVisitor) BootstrapMemory.Allocate(typeof(SweepVisitor)); // set the null partial free page visitor partialFreePageVisitor = SegregatedFreeList.nullPartialFreePageVisitor; } internal override void Collect(Thread currentThread, int generation) { try { if (!GC.IsUserCollectionRequest(generation) && Transitions.HasGCRequest(currentThread.threadIndex) && TrivialHandshake) { // Someone asked for a handshake, and it is a trivial // handshake, so there is no need to go through // the overhead of saving and restoring the callee-save // registers in CollectBodyTransition. Transitions.ClearGCRequest(currentThread.threadIndex); // BUGBUG: consider restoring the following // markThread.SignalEvent(); } else { GC.CollectTransition(currentThread, generation); } } catch (Exception e) { VTable.DebugPrint("Garbage collection failed with exception"); VTable.DebugPrint(e.GetType().Name); VTable.DebugBreak(); } } /// /// Perform a collection. Depending on the current phase of collection /// this method will either: /// /// 1. Start a new collection and schedule the mark thread /// 2. Notice that a collection is underway and exit /// 3. Clean up after a collection /// /// BUGBUG: The interaction between the collector needs work! /// internal override void CollectStoppable(int currentThreadIndex, int generation) { if (!GC.IsUserCollectionRequest(generation) && Transitions.HasGCRequest(currentThreadIndex)) { // We are GC safe, so we may do this Transitions.TakeDormantControlNoGC(currentThreadIndex); if (Transitions.TakeGCControl(currentThreadIndex)) { if (UseSTWTracingPhase && CurrentMarkingPhase == MarkingPhase.StopTheWorld) { Thread.SignalGCEvent(markThread.threadIndex); } else { MutatorHandshake(currentThreadIndex); Transitions.ReleaseGCControl(currentThreadIndex); // BUGBUG: consider restoring the following // markThread.SignalEvent(); } } Transitions.TakeMutatorControlNoGC(currentThreadIndex); } else if (GC.IsRealCollectionRequest(generation)) { if (generation >= 0) { AddThreadToWaitList(currentThreadIndex); } AddCollectionRequest(); if (generation >= 0) { Thread currentThread = Thread.threadTable[currentThreadIndex]; while (ThreadIsWaiting(currentThreadIndex)) { // NOTE: if there was a GC request and this was a // user-requested collection, then the stack scanning // will happen in here. // NOTE: there is no code in this loop that could // cause a signal on an event to be consumed. currentThread.WaitForEvent(infiniteWait); } } } } internal override void CheckForNeededGCWork(Thread currentThread) { if (Transitions.HasGCRequest(currentThread.threadIndex)) { GC.InvokeCollection(currentThread); } } internal static void CollectorHandshake(Thread collectorThread) { Transitions.MakeGCRequests(collectorThread.threadIndex); // Handshake with all the (other) threads. for(int i=0; i < Thread.threadTable.Length; i++) { #if SINGULARITY_KERNEL if (Scheduler.IsIdleThread(i)) { continue; } #endif // Is there an unscanned thread here? while (Transitions.HasGCRequest(i)) { if (Transitions.TakeGCControl(i)) { MutatorHandshake(i); Transitions.ReleaseGCControl(i); } else if (Thread.threadTable[i] == null) { // The thread must have terminated but the // state hasn't yet changed to DormantState. break; } else { // NOTE: there is no code in this loop that could // cause a signal on an event to be consumed without // being putting back. collectorThread.WaitForEvent(minimumWait); } } } if (ConcurrentMSCollector.IncludeMUWInHandshake && !ConcurrentMSCollector.TrivialHandshake && !UseSTWTracingPhase) { MultiUseWord.CollectFromPreviousCollections(false); } } private static void MutatorHandshake(int threadIndex) { #if SINGULARITY_KERNEL Microsoft.Singularity.Processor currentProcessor = Microsoft.Singularity.Processor.CurrentProcessor; if (currentProcessor.InInterruptContext) { Tracing.Log(Tracing.Fatal, "Attempt to perform a collector handshake from an interrupt context!"); VTable.DebugPrint("Attempt to perform a collector handshake from withing an interrupt context"); VTable.DebugBreak(); } #endif if (!ConcurrentMSCollector.TrivialHandshake) { Thread thread = Thread.threadTable[threadIndex]; if (thread != null) { ScanThreadRoots(thread); if (IncludeMUWInHandshake && !UseSTWTracingPhase) { MultiUseWord.CollectFromThread(thread); } } } } private static void ScanThreadRoots(Thread thread) { long start = CollectorStatistics.PerformanceCounter; CollectorStatistics.Event(GCEvent.StackScanStart, thread.threadIndex); CallStack.ScanStack(thread, stackMarkReferenceVisitor, stackMarkPinnedReferenceVisitor); threadColor[thread.threadIndex] = markedColor; // Report the pause long pause = CollectorStatistics.PerformanceCounter - start; CollectorStatistics.Event(GCEvent.StackScanComplete, pause); } internal static bool terminateCollectorThreads; // REVIEW: merge this with terminateCollectorThreads, maybe internal static bool killCollectorThreads; /* non-Singularity thread killage */ internal static void TraceThreadNotification() { terminateCollectorThreads = true; GC.Collect(); } /// /// This method is run by the collector threads. /// private static void CollectionTask() { try { CollectionLoop(); } catch (Exception e) { VTable.DebugPrint("Collection task failed with exception "); VTable.DebugPrint(e.GetType().Name); VTable.DebugBreak(); } } private static void CollectionLoop() { #if !SINGULARITY if (fDebug) { VTable.DebugPrint("cms thread = "); VTable.DebugPrint((ulong)Win32Native.GetCurrentThreadId()); VTable.DebugPrint("\n"); } #endif Thread currentThread = Thread.CurrentThread; #if SINGULARITY_PROCESS currentThread.MakeServiceThread(new Thread.StopServiceNotice(TraceThreadNotification)); #endif while (!terminateCollectorThreads && !killCollectorThreads) { // Wait to be told to start working. while (!TakeChargeOfTraceRequest() && !killCollectorThreads) { // NOTE: there is no code in this loop that could // cause a signal on an event to be consumed. currentThread.WaitForEvent(infiniteWait); } if (killCollectorThreads) { break; } StartGCCycle(); instance.PreTraceHook(); #if SINGULARITY DebugStub.WriteLine("~~~~~ Start Concurrent Marking [data={0:x8}, pid={1:x3}]", __arglist(SegregatedFreeList.TotalBytes, PageTable.processTag >> 16)); #endif if (fDebug) { VTable.DebugPrint("~~~~~ Start Concurrent Marking\n"); } int startTicks = Environment.TickCount; if (GC.IsProfiling) { GcProfiler.NotifyPreGC(instance.MinGeneration); } markThread = currentThread; advanceMarkColors(); MarkReferenceVisitor.markThread = currentThread; // Construct the root set. CollectorStatistics.Event(GCEvent.ComputeRootSet); StartRootMarkingPhase(); // Start the process of recycling pages partialFreePageVisitor.Start(); SegregatedFreeList .RecycleGlobalPagesPhase1(partialFreePageVisitor); // One handshake to ensure that everyone starts snooping CollectorHandshake(currentThread); // Complete the process of recycling pages SegregatedFreeList .RecycleGlobalPagesPhase2(partialFreePageVisitor); partialFreePageVisitor.Finish(); // Another handshake to ensure that all updates started by // other threads prior to their handshake are done and // snooping will affect all new updates. CollectorHandshake(currentThread); // A third handshake to get the threads to process their // own roots. The collector thread needs to scan its // roots, too. ConcurrentMSCollector.TrivialHandshake = false; CollectorHandshake(currentThread); // In order to scan the call stack of the current thread, // we need a TransitionRecord for the thread. At this // point we don't have one, so we have to go through // CollectBodyTransition to get one. Transitions.MakeGCRequest(currentThread.threadIndex); GC.InvokeCollection(currentThread); ConcurrentMSCollector.TrivialHandshake = true; Finalizer.PrepareCollectFinalizers(); Thread.VisitBootstrapData(markReferenceVisitor); #if SINGULARITY_KERNEL Kernel.VisitSpecialData(markReferenceVisitor); #endif MultiUseWord.VisitStrongRefs(markReferenceVisitor, false /* Don't use shadows */); #if !VC TryAllManager.VisitStrongRefs(markReferenceVisitor); #endif instance.PostRootScanHook(); StaticData.ScanStaticData(markReferenceVisitor); CollectorStatistics.Event(GCEvent.RootSetComputed); int waitingThreadList = StealWaitingThreadsList(); // We are now in the concurrent tracing phase. ResetCollectorRequests(); StartTracingPhase(); markReferenceVisitor.Init(); CollectorStatistics.Event(GCEvent.TraceStart, ReservedMemory); // Trace live objects from the root set. markReferenceVisitor.ProcessGrayObjects(); if (killCollectorThreads) { break; } // If we want to use a STW phase to redo the tracing (for // measurement purposes, most likely), now is the time to // do so. if (UseSTWTracingPhase) { int preSTWTrace = Environment.TickCount; StartSTWPhase(); bool oldTrivialHandshake = TrivialHandshake; TrivialHandshake = false; BaseCollector.AllThreadRendezvous(markThread.threadIndex); TrivialHandshake = oldTrivialHandshake; STWInitialize(); STWScanStacks(stackMarkReferenceVisitor, stackMarkPinnedReferenceVisitor); Finalizer.PrepareCollectFinalizers(); Thread.VisitBootstrapData(markReferenceVisitor); #if SINGULARITY_KERNEL Kernel.VisitSpecialData(markReferenceVisitor); #endif MultiUseWord.VisitStrongRefs(markReferenceVisitor, false /* Don't use shadows */); #if !VC TryAllManager.VisitStrongRefs(markReferenceVisitor); #endif StaticData.ScanStaticData(markReferenceVisitor); markReferenceVisitor.Init(); markReferenceVisitor.ProcessGrayObjects(); BaseCollector.AllThreadRelease(markThread.threadIndex); FinishSTWPhase(); int postSTWTrace = Environment.TickCount; totalSTWTraceTime += (ulong)(postSTWTrace-preSTWTrace); numSTWTraces ++; } CollectorStatistics.Event(GCEvent.TraceSpecial); // Mark weak references that do not track resurrection as dead. WeakReference.Process(updateReferenceVisitor, true, true); // Resurrect any finalization candidates. Finalizer.ResurrectCandidates(updateReferenceVisitor, markReferenceVisitor, true); // Complete closure from finalized objects. markReferenceVisitor.ProcessGrayObjects(); if (killCollectorThreads) { break; } // Mark appropriate weak references as dead WeakReference.Process(updateReferenceVisitor, true, false); MultiUseWord.VisitWeakRefs(updateReferenceVisitor, false /* Don't use shadows */); #if !VC TryAllManager.VisitWeakRefs(updateReferenceVisitor); #endif MultiUseWord.PostGCHook(); // Reset thread queues. They should all be empty. markReferenceVisitor.Cleanup(); int middleTicks = Environment.TickCount; totalTraceTime += (ulong)(middleTicks - startTicks); numTraces ++; #if SINGULARITY DebugStub.WriteLine("~~~~~ Finish Concurrent Marking [data={0:x8}, pid={1:x3} ms={2:d6}]", __arglist(SegregatedFreeList.TotalBytes, PageTable.processTag >> 16, middleTicks - startTicks)); #endif if (fDebug) { VTable.DebugPrint("~~~~~ Finish Concurrent Marking\n"); } instance.PostTraceHook(); markThread = nextWorkerThread(currentThread); sweepPhaseMutex.WaitOne(); try { reclamationColor = unmarkedColor; FinishTracingPhase(); SatisfyCollectorRequest(); // May start another trace phase // Sweep garbage objects StartReclamationPhase(); #if SINGULARITY DebugStub.WriteLine("~~~~~ Start Concurrent Reclamation [data={0:x8}, pid={1:x3}]", __arglist(SegregatedFreeList.TotalBytes, PageTable.processTag >> 16)); #endif if (fDebug) { VTable.DebugPrint("~~~~~ Start Concurrent Reclamation\n"); } CollectorStatistics.Event(GCEvent.SweepStart, ReservedMemory); instance.PreSweepHook(); Sweep(); if (killCollectorThreads) { break; } instance.PostSweepHook(); // Clean up after the collection CollectorStatistics.Event(GCEvent.SweepSpecial, ReservedMemory); Finalizer.ReleaseCollectFinalizers(); if (GC.IsProfiling) { // Allocations may occur inside the PostGCHook. Hopefully a // sufficiently limited quantity that we don't recursively // trigger a GC. GcProfiler.NotifyPostGC(ProfileRoots, ProfileObjects); } CollectorStatistics.Event(GCEvent.SweepPreCommit, ReservedMemory); // Commit accounting changes CommitSweep(); CollectorStatistics.Event(GCEvent.CollectionComplete, ReservedMemory); // Determine a new collection trigger UIntPtr testTrigger = (UIntPtr) ReservedMemory >> 2; UIntPtr minTrigger = (UIntPtr) minimumCollectionTrigger; UIntPtr maxTrigger = (UIntPtr) maximumCollectionTrigger; collectionTrigger = (testTrigger > minTrigger) ? (testTrigger < maxTrigger ? testTrigger : maxTrigger) : minTrigger; // Finish up and wake up any GC requesting threads. SignalWaitingThreads(waitingThreadList); FinishReclamationPhase(); } finally { sweepPhaseMutex.Set(); } instance.PostReclamationHook(); int endTicks = Environment.TickCount; totalSweepTime += (ulong)(endTicks - middleTicks); numSweeps ++; #if SINGULARITY DebugStub.WriteLine("~~~~~ Finish Concurrent Reclamation [data={0:x8}, pid={1:x3} ms={2:d6}]", __arglist(ReservedMemory, PageTable.processTag >> 16, endTicks - middleTicks)); #endif if (fDebug) { VTable.DebugPrint("~~~~~ Finish Concurrent Reclamation\n"); } if (VTable.enableDumpMemStats) { VTable.DebugPrint("Heap mem usage: {0:x8}\n", __arglist(ReservedMemory)); } } } private static void STWInitialize() { for (int i = 0; i < Thread.threadTable.Length; i++) { Thread t = Thread.threadTable[i]; if (t != null) { ThreadHeaderQueue.Reset(t); } } SegregatedFreeList.VisitAllObjects(unmarkVisitor); } private static void STWScanStacks(NonNullReferenceVisitor VisitThreadReference, NonNullReferenceVisitor VisitPinnedReference) { for (int i = 0; i < Thread.threadTable.Length; i++) { Thread t = Thread.threadTable[i]; if (t != null) { CallStack.ScanStack(t, VisitThreadReference, VisitPinnedReference); if (IncludeMUWInHandshake) { MultiUseWord.CollectFromThread(t); } } } if (IncludeMUWInHandshake) { MultiUseWord.CollectFromPreviousCollections(false); } } internal virtual void PreTraceHook() { } internal virtual void PostRootScanHook() { } internal virtual void PostTraceHook() { } internal virtual void PostReclamationHook() { } internal virtual void PreSweepHook() { } internal virtual void PostSweepHook() { } /// /// Allocate memory for a new object, potentially triggering a /// collection. /// [Inline] internal override UIntPtr AllocateObjectMemory(UIntPtr numBytes, uint alignment, Thread currentThread) { UIntPtr resultAddr = SegregatedFreeList.AllocateFast(currentThread, numBytes, alignment); if (resultAddr == UIntPtr.Zero) { resultAddr = AllocateObjectMemorySlow(numBytes, alignment, currentThread); } return resultAddr; } [NoInline] internal virtual UIntPtr AllocateObjectMemorySlow(UIntPtr numBytes, uint alignment, Thread currentThread) { if (Transitions.HasGCRequest(currentThread.threadIndex)) { GC.InvokeCollection(currentThread); } else if (NewBytesSinceGCExceeds(collectionTrigger) && GC.allocationGCInhibitCount == 0) { if (CurrentMarkingPhase == MarkingPhase.Idle) { GC.InvokeCollection(currentThread); } else if (currentThread!=workerThread1 && currentThread!=workerThread2 && NewBytesSinceGCExceeds(collectionTrigger<<1)) { // Slow down the allocating thread a bit if (UseSleepThrottle && (NewBytesSinceGCExceeds((collectionTrigger<<3)+ (collectionTrigger<<2)) || TotalMemory > (1<<30)+(1<<29))) { // Context switches didn't help, so let's try // suspending ourselves for a bit. Thread.Sleep(1); } else if (UseYieldThrottle) { // If there are more threads than processors, // forcing a context switch should slow down // the mutator thread. Thread.Yield(); } } } return SegregatedFreeList.AllocateSlow(currentThread, numBytes, alignment); } [NoInline] [CalledRarely] protected void MarkOnAlloc(Thread currentThread, Object obj) { ThreadHeaderQueue.PushPrivateObject(currentThread, obj, markedColor); } [Inline] protected override void CreateObject(Object obj, VTable vtable, Thread currentThread) { // We expect the color to be assigned before the vtable field // is initialized. This ensures that every real object has a // valid color. UIntPtr markBits = threadColor[currentThread.threadIndex]; ThreadHeaderQueue.SetGcMark(obj, markBits); // The vtable field must be initialized before the object is // inserted into a list of objects to be scanned. base.CreateObject(obj, vtable, currentThread); // If necessary, mark the object for future scanning if (CurrentMarkingPhase == MarkingPhase.ComputingRoots) { MarkOnAlloc(currentThread, obj); } ProfileAllocation(obj); } /// /// Return the generation for an object. We only have one /// generation, so we always return generation zero. /// internal override int GetGeneration(Object obj) { Verifier.genericObjectVisitor.Visit(obj); return MinGeneration; } /// /// The maximum generation. For MarkSweep this is generation zero. /// internal override int MaxGeneration { get { return (int)PageType.Owner0; } } /// /// The minimum generation. For MarkSweep this is generation zero. /// internal override int MinGeneration { get { return (int)PageType.Owner0; } } /// /// This returns the total amount of memory that is allocated within /// the collected heap. /// internal override long TotalMemory { get { return ReservedMemory; } } private static long ReservedMemory { get { return (long)(SegregatedFreeList.TotalPages*PageTable.PageSize); } } internal static int GetEnvironmentValue(String name, int defaultValue) { String valueString = null; #if !SINGULARITY valueString = Environment.GetEnvironmentVariable(name); #endif if (valueString == null) { return defaultValue; } else { return Int32.Parse(valueString); } } internal override void EnableHeap() { // waitingThreads = new int[Thread.maxThreads] waitingThreads = new int[Thread.maxThreads]; for (int i = 0; i < waitingThreads.Length; i++) { waitingThreads[i] = waitingThreadsUnusedValue; } firstWaitingThread = -1; // Construct the collector thread(s) #if SINGULARITY_KERNEL workerThread1 = Thread.CreateThread(Process.kernelProcess, new ThreadStart(CollectionTask)); if (UseOverlappingPhases) { workerThread2 = Thread.CreateThread(Process.kernelProcess, new ThreadStart(CollectionTask)); } #else workerThread1 = new Thread(new ThreadStart(CollectionTask)); if (UseOverlappingPhases) { workerThread2 = new Thread(new ThreadStart(CollectionTask)); } #endif workerThread1.Start(); if (UseOverlappingPhases) { workerThread2.Start(); } markThread = workerThread1; sweepPhaseMutex = new AutoResetEvent(true); } internal override void Shutdown() { if (GC.IsProfiling) { GcProfiler.NotifyShutdown(); } killCollectorThreads = true; workerThread1.SignalEvent(); if (workerThread2!=null) { workerThread2.SignalEvent(); } workerThread1.Join(); if (workerThread2!=null) { workerThread2.Join(); } } /// /// Destroy the heap. Nothing to do here. /// internal override void DestructHeap() { base.DestructHeap(); if (VTable.enableFinalGCTiming) { VTable.DebugPrint("total trace time = "); VTable.DebugPrint((long) totalTraceTime); VTable.DebugPrint("\n"); VTable.DebugPrint("total sweep time = "); VTable.DebugPrint((long) totalSweepTime); VTable.DebugPrint("\n"); VTable.DebugPrint("num traces = "); VTable.DebugPrint((long) numTraces); VTable.DebugPrint("\n"); VTable.DebugPrint("num sweeps = "); VTable.DebugPrint((long) numSweeps); VTable.DebugPrint("\n"); } } /// /// Verify the heap. /// internal override void VerifyHeap(bool beforeCollection) { SegregatedFreeList.VisitAllObjects(VerifyVisitor.visitor); Verifier.segregatedFreeListVerifier.VerifyHeap(); } private static char ToHexDigit(int number, int position) { int digit = (number >> (position * 4)) & 0xf; return (char) (digit + ((digit <= 9) ? '0' : ('A' - 10))); } private static int flag; // = 0; private static void DebugTrace(String text, int threadIndex) { while (Interlocked.CompareExchange(ref flag, 1, 0) != 0) { } VTable.DebugPrint(text+" "+ ToHexDigit(threadIndex, 2)+ ToHexDigit(threadIndex, 1)+ ToHexDigit(threadIndex, 0)+ "\n"); Interlocked.Exchange(ref flag, 0); } /// Routines to keep track of requests for collection work private static int collectorStack; // 0:idle, 1:work, 2+:work+pending internal static void AddCollectionRequest() { int stackHeight = Interlocked.Increment(ref collectorStack); VTable.Assert(stackHeight > 0); if (stackHeight == 1) { MakeTraceRequest(); } } private static void ResetCollectorRequests() { Interlocked.Exchange(ref collectorStack, 1); } private static void SatisfyCollectorRequest() { int stackHeight = Interlocked.Decrement(ref collectorStack); VTable.Assert(stackHeight >= 0); if (stackHeight > 0 ) { MakeTraceRequest(); } } /// Routines to keep track of threads that must be notified when /// a collection has been completed. private static void AddThreadToWaitList(int threadIndex) { int listHead = firstWaitingThread; waitingThreads[threadIndex] = listHead; while (Interlocked.CompareExchange(ref firstWaitingThread, threadIndex, listHead) != listHead) { listHead = firstWaitingThread; waitingThreads[threadIndex] = listHead; } } private static bool ThreadIsWaiting(int threadIndex) { return (waitingThreads[threadIndex] != waitingThreadsUnusedValue); } private static int StealWaitingThreadsList() { return Interlocked.Exchange(ref firstWaitingThread, waitingThreadsNullValue); } private static void SignalWaitingThreads(int listHead) { while (listHead != waitingThreadsNullValue) { int threadIndex = listHead; listHead = waitingThreads[threadIndex]; waitingThreads[threadIndex] = waitingThreadsUnusedValue; Thread.threadTable[threadIndex].SignalEvent(); } } /// Routines to control the commencement of the tracing phase private static void MakeTraceRequest() { MarkingPhase oldPhase = (MarkingPhase) Interlocked.Exchange(ref CMSMarking.currentMarkingPhase, (int) MarkingPhase.Requested); VTable.Assert(oldPhase == MarkingPhase.Idle); markThread.SignalEvent(); } private static bool TakeChargeOfTraceRequest() { MarkingPhase oldPhase = (MarkingPhase) Interlocked.CompareExchange(ref CMSMarking.currentMarkingPhase, (int) MarkingPhase.Preparing, (int) MarkingPhase.Requested); return (oldPhase == MarkingPhase.Requested); } // Routines to keep track of what phases the collector threads are in. private static void StartRootMarkingPhase() { CMSMarking.referenceCheckIsFast = false; MarkingPhase oldPhase = (MarkingPhase) Interlocked.Exchange(ref CMSMarking.currentMarkingPhase, (int) MarkingPhase.ComputingRoots); VTable.Assert(oldPhase == MarkingPhase.Preparing); } private static void StartTracingPhase() { MarkingPhase oldPhase = (MarkingPhase) Interlocked.Exchange(ref CMSMarking.currentMarkingPhase, (int) MarkingPhase.Tracing); VTable.Assert(oldPhase == MarkingPhase.ComputingRoots); } private static void StartSTWPhase() { MarkingPhase oldPhase = (MarkingPhase) Interlocked.Exchange(ref CMSMarking.currentMarkingPhase, (int) MarkingPhase.StopTheWorld); VTable.Assert(oldPhase == MarkingPhase.Tracing); } private static void FinishSTWPhase() { MarkingPhase oldPhase = (MarkingPhase) Interlocked.Exchange(ref CMSMarking.currentMarkingPhase, (int) MarkingPhase.Tracing); VTable.Assert(oldPhase == MarkingPhase.StopTheWorld); } private static void FinishTracingPhase() { MarkingPhase oldPhase = (MarkingPhase) Interlocked.Exchange(ref CMSMarking.currentMarkingPhase, (int) MarkingPhase.Idle); VTable.Assert(oldPhase == MarkingPhase.Tracing); CMSMarking.referenceCheckIsFast = true; } private static void StartReclamationPhase() { CurrentReclamationPhase = ReclamationPhase.Reclaiming; } private static void FinishReclamationPhase() { CurrentReclamationPhase = ReclamationPhase.Idle; } // Routines to manage marking colors private static void advanceMarkColors() { unmarkedColor = markedColor; markedColor = nextColor(markedColor); } private static UIntPtr nextColor(UIntPtr originalColor) { switch ((uint) originalColor) { case markOne: return (UIntPtr) markTwo; case markTwo: return (UIntPtr) markThree; case markThree: return (UIntPtr) markOne; default: throw new Exception("nextColor failure!"); } } private static Thread nextWorkerThread(Thread currentThread) { if (UseOverlappingPhases) { return ((currentThread == workerThread1) ? workerThread2 : workerThread1); } else { return currentThread; } } /// /// Walk the allocation structures and reclaim any free cells. /// [NoInline] private static void Sweep() { SegregatedFreeList.VisitAllObjects(sweepVisitor); } /// /// Update alloc heap to account for data just freed. /// private static void CommitSweep() { SegregatedFreeList.CommitFreedData(); } // This is a quick-and-dirty check of whether something is an object. // All objects have a vtable. A VTable is an object that itself has a // vtable. All VTable objects have the same vtable, which we rely // upon for this fast check. This predicate will gracefully fail in // corrupted memory situations where a real type check will cause a // catastrophic failure. private static bool IsPossiblyObject(Object obj) { // First, check for a null reference if (obj == null) { return false; } // Second, check for a null vtable VTable vtable = obj.vtable; if (vtable == null) { return false; } // Third, check for a null vtable of the vtable VTable vtableVtable = vtable.vtable; if (vtableVtable == null) { return false; } // Finally, rely on the invariant that all vtables have the // same vtable. return (vtableVtable == vtableVtable.vtable); } /// /// Routines for updating pointers to new locations of marked objects /// No objects are resurrected using this mechanism. /// As this is mark sweep the value does not need to be updated. /// internal class UpdateReferenceVisitor: MutableReferenceVisitor { internal virtual UIntPtr ForwardIfNecessary(UIntPtr addr) { return addr; } internal unsafe override void Visit(UIntPtr *loc) { while (true) { UIntPtr foundAddr = *loc; // Ignore pointers out of our memory area if (foundAddr == UIntPtr.Zero || PageTable.IsForeignAddr(foundAddr)) { return; } UIntPtr page = PageTable.Page(foundAddr); PageType pageType = PageTable.Type(page); if (!PageTable.IsGcPage(pageType)) { VTable.Assert((PageTable.IsNonGcPage(pageType) && PageTable.IsMyPage(page)) || PageTable.IsStackPage(pageType), "update.visit invalid page"); return; } VTable.Assert(PageTable.IsMyPage(page)); UIntPtr forwardedAddr = ForwardIfNecessary(foundAddr); Object obj = Magic.fromAddress(forwardedAddr); VTable.Assert(IsPossiblyObject(obj), "update visit: bad object/vtable"); if (ThreadHeaderQueue.GcMark(obj) == unmarkedColor) { // The object is not live *loc = UIntPtr.Zero; return; } // The object is alive, install forwarded value, // if necessary. if (foundAddr == forwardedAddr || (Interlocked.CompareExchange(loc, forwardedAddr, foundAddr) == foundAddr)) { return; } } } } /// /// Select the generation to collect (always generation 0) /// internal override int CollectionGeneration(int genRequest) { return MinGeneration; } /// /// This visitor is the core of the tracing functionality. /// It builds up a buffer of references (the root set), and /// then at a later point the tracing thread processes these /// buffers. /// internal class MarkReferenceVisitor : MutableReferenceVisitor { [NoBarriers] internal static Thread markThread; [Inline] protected override unsafe void Filter(UIntPtr *location, ref ObjectDescriptor objDesc) { this.Visit(location); } [Inline] internal override unsafe void Visit(UIntPtr *location) { this.VisitValueMaybeNull(*location); } // This method simply forces the compiler to generate a copy // of VisitReferenceFieldsTemplate in this class. [ManualRefCounts] [Inline] internal override UIntPtr VisitReferenceFields(Object obj) { return this.VisitReferenceFields(Magic.addressOf(obj), obj.vtable); } // This method simply forces the compiler to generate a copy // of VisitReferenceFieldsTemplate in this class. [ManualRefCounts] [Inline] internal override UIntPtr VisitReferenceFields(UIntPtr objectBase, VTable vtable) { ObjectDescriptor objDesc = new ObjectDescriptor(vtable, objectBase); return VisitReferenceFieldsTemplate(ref objDesc); } [Inline] internal void VisitValueNonNull(UIntPtr addr) { // that's a slow assertion to make! //VTable.Assert(Thread.CurrentThread == markThread); VisitValueNonNull(addr, markThread); } [Inline] internal void VisitValueMaybeNull(UIntPtr addr) { // that's a slow assertion to make! //VTable.Assert(Thread.CurrentThread == markThread); VisitValueMaybeNull(addr, markThread); } [Inline] internal void VisitValueAnyThreadMaybeNull(UIntPtr addr) { VisitValueMaybeNull(addr, Thread.CurrentThread); } [Inline] internal void VisitValueMaybeNull(UIntPtr addr, Thread currentThread) { // Ignore null pointers if (addr == UIntPtr.Zero) { return; } VisitValueNonNull(addr, currentThread); } [Inline] internal void VisitValueNonNull(UIntPtr addr, Thread currentThread) { // Ignore pointers to strange memory areas if (PageTable.IsForeignAddr(addr)) { return; } UIntPtr page = PageTable.Page(addr); PageType pageType = PageTable.Type(page); if (!PageTable.IsGcPage(pageType)) { VTable.Assert((PageTable.IsNonGcPage(pageType) && PageTable.IsMyPage(page)) || PageTable.IsStackPage(pageType), "value.visit invalid page"); return; } VTable.Assert(PageTable.IsMyPage(page)); Object obj = Magic.fromAddress(addr); VTable.Assert(IsPossiblyObject(obj), "mark visit: bad object/vtable"); CMSMarking.MarkObject(addr, currentThread); } /// /// Process all marked objects from queues stored in /// thread objects. /// internal virtual void ProcessGrayObjects() { ThreadHeaderQueue.LocalList workList = new ThreadHeaderQueue.LocalList(); while (!killCollectorThreads && AcquireWork(ref workList)) { ProcessMyGrayObjects(ref workList); } } [NoBarriers] internal virtual void ProcessMyGrayObjects(ref ThreadHeaderQueue.LocalList workList) { while (!killCollectorThreads && !workList.IsEmpty()) { // Pop the next value Object obj = workList.Pop(markedColor); // Visit Fields this.VisitReferenceFields(obj); } } /// /// Look through other threads and see if any have some values on /// their queues that we can acquire. /// private bool AcquireWork(ref ThreadHeaderQueue.LocalList workList) { bool foundWork = false; UIntPtr stealColor = (UIntPtr) markedColor; do { ThreadHeaderQueue.transferAttempt = false; Thread[] threadTable = Thread.threadTable; // Attempt to acquire work from live threads for (int i = 0; i < threadTable.Length; i++) { Thread thread = threadTable[i]; if (thread != null && workList.StealFrom(thread, stealColor)) { foundWork = true; } } } while (!foundWork && ThreadHeaderQueue.transferAttempt); return foundWork; } internal virtual void Init() { } /// /// Clean up after processing all queues. This involves calling /// reset on each thread's queue. /// internal virtual void Cleanup() { Thread[] threadTable = Thread.threadTable; for (int i = 0; i < threadTable.Length; i++) { Thread t = threadTable[i]; if (t != null) { VTable.Assert(ThreadHeaderQueue.IsEmpty(t)); ThreadHeaderQueue.Reset(t); } } } } /// /// This class maps an interior pointer back to the containing object /// pointer and then passes it on to the object marking visitor. /// internal class StackMarkReferenceVisitor : NonNullReferenceVisitor { internal unsafe virtual void ProcessObjectPtr(UIntPtr objectPtr, UIntPtr *loc, UIntPtr addr) { markReferenceVisitor.VisitValueAnyThreadMaybeNull(objectPtr); } /// /// Visit an interior pointer stored in loc. /// internal unsafe override void Visit(UIntPtr *loc) { UIntPtr addr = *loc; // Ignore pointers out of our memory area if (PageTable.IsForeignAddr(addr)) { return; } UIntPtr page = PageTable.Page(addr); PageType pageType = PageTable.Type(page); if (!PageTable.IsGcPage(pageType)) { VTable.Assert((PageTable.IsNonGcPage(pageType) && PageTable.IsMyPage(page)) || PageTable.IsStackPage(pageType) || PageTable.IsSharedPage(pageType), "interior.visit invalid page"); return; } UIntPtr objectPtr = SegregatedFreeList.Find(addr); VTable.Assert(IsPossiblyObject(Magic.fromAddress(objectPtr)), "stack visit: bad object/vtable"); ProcessObjectPtr(objectPtr, loc, addr); } } /// /// This class is used to visit every object in the heap /// replacing markedColor with unmarkedColor. It is used /// only in the StopTheWorld phase of the collector. /// internal class UnmarkVisitor : SegregatedFreeList.ObjectVisitor { internal override void VisitSmall(Object obj, UIntPtr memAddr) { if (ThreadHeaderQueue.GcMark(obj) == markedColor) { ThreadHeaderQueue.SetGcMark(obj, unmarkedColor); } } internal override UIntPtr VisitLarge(Object obj) { if (ThreadHeaderQueue.GcMark(obj) == markedColor) { ThreadHeaderQueue.SetGcMark(obj, unmarkedColor); } return ObjectLayout.Sizeof(obj); } } /// /// This class is used to visit every object, determine if it /// is marked and free it if not. /// internal class SweepVisitor : SegregatedFreeList.ObjectVisitor { private SegregatedFreeList.TempList tempList; internal override void VisitSmall(Object obj, UIntPtr memAddr) { if (ThreadHeaderQueue.GcMark(obj) == reclamationColor) { // Not marked. tempList.Add(memAddr); } } internal override void VisitSmallPageEnd() { SegregatedFreeList.FreeSmallList(ref tempList); } internal override UIntPtr VisitLarge(Object obj) { UIntPtr objectSize = ObjectLayout.Sizeof(obj); if (ThreadHeaderQueue.GcMark(obj) == reclamationColor) { // Not marked. SegregatedFreeList.FreeLarge(obj); } return objectSize; } internal override bool Continue { get { return !killCollectorThreads; } } } /// /// Find the object address for a given interior pointer. /// internal override UIntPtr FindObjectAddr(UIntPtr interiorPtr) { return SegregatedFreeList.Find(interiorPtr); } /// /// Visit all objects in the heap with a specified visitor. /// internal override void VisitObjects(ObjectLayout.ObjectVisitor objectVisitor, UIntPtr lowAddr, UIntPtr highAddr) { VTable.Assert(PageTable.PageAligned(lowAddr), "low not page aligned"); VTable.Assert(PageTable.PageAligned(highAddr), "high not page aligned"); UIntPtr lowPage = PageTable.Page(lowAddr); UIntPtr highPage = PageTable.Page(highAddr); SegregatedFreeList.VisitObjects(lowPage, highPage, objectVisitor); } /// /// A new thread has been created, set any allocator/collector state. /// internal override void NewThreadNotification(Thread newThread, bool initial) { base.NewThreadNotification(newThread, initial); threadColor[newThread.threadIndex] = markedColor; ThreadHeaderQueue.Reset(newThread); SegregatedFreeList.NewThreadNotification(newThread, initial); if (CurrentMarkingPhase == MarkingPhase.ComputingRoots) { Transitions.MakeGCRequest(newThread.threadIndex); } } internal override void DeadThreadNotification(Thread deadThread) { MultiUseWord.CollectFromThread(deadThread); SegregatedFreeList.DeadThreadNotification(deadThread); ThreadHeaderQueue.DeadThreadNotification(deadThread, markedColor); threadColor[deadThread.threadIndex] = (UIntPtr) noColor; base.DeadThreadNotification(deadThread); } internal override void ThreadStartNotification(int currentThreadIndex) { base.ThreadStartNotification(currentThreadIndex); threadColor[currentThreadIndex] = markedColor; if (CurrentMarkingPhase == MarkingPhase.ComputingRoots) { Transitions.MakeGCRequest(currentThreadIndex); } } internal override void ThreadDormantGCNotification(int threadIndex) { // We could scan our own stack, but instead we try to get // some work done while the Trace thread scans our stack. if (UseSTWTracingPhase && CurrentMarkingPhase == MarkingPhase.StopTheWorld) { Thread.SignalGCEvent(markThread.threadIndex); } base.ThreadDormantGCNotification(threadIndex); } /// /// This class is used to verify that there are no dangling pointers. /// private class VerifyVisitor : SegregatedFreeList.ObjectVisitor { internal static VerifyVisitor visitor = new VerifyVisitor(); internal override void VisitSmall(Object obj, UIntPtr memAddr) { if (ThreadHeaderQueue.GcMark(obj) == markedColor) { VerifyMarkVisitor.visitor.VisitReferenceFields(obj); } else { VTable.Assert(ThreadHeaderQueue.GcMark(obj) == unmarkedColor); } } internal override UIntPtr VisitLarge(Object obj) { UIntPtr size; if (ThreadHeaderQueue.GcMark(obj) == markedColor) { // The object has the mark color, so it should only // reference other objects with the mark color. size = VerifyMarkVisitor.visitor.VisitReferenceFields(obj); } else { VTable.Assert(ThreadHeaderQueue.GcMark(obj) == unmarkedColor); size = ObjectLayout.Sizeof(obj); } return size; } } /// /// This class is used to check that all the pointers within a marked /// object point into other marked objects. /// private class VerifyMarkVisitor : MutableReferenceVisitor { internal static VerifyMarkVisitor visitor = new VerifyMarkVisitor(); internal unsafe override void Visit(UIntPtr *loc) { UIntPtr addr = *loc; UIntPtr page = PageTable.Page(addr); if (PageTable.IsGcPage(page)) { Object obj = Magic.fromAddress(addr); VTable.Assert(ThreadHeaderQueue.GcMark(obj) == markedColor, "dangling pointer!"); } } } /// /// This method loops through all non-null threads and asserts that /// no thread has any work on its marking queue. /// private static void VerifyEmptyQueues() { Thread[] threadTable = Thread.threadTable; for (int i = 0; i < threadTable.Length; i++) { Thread t = threadTable[i]; if (t != null) { VTable.Assert(ThreadHeaderQueue.IsEmpty(t), "Non-empty Queue!"); } } } private static void VerifyResetQueues() { Thread[] threadTable = Thread.threadTable; for (int i = 0; i < threadTable.Length; i++) { Thread t = threadTable[i]; if (t != null) { VTable.Assert(ThreadHeaderQueue.IsReset(t), "Non-reset queue"); } } } /// /// This method walks through all objects in the heap to ensure /// that no objects have values in their queue header field /// private static void VerifyQueueHeaders() { SegregatedFreeList.VisitAllObjects(VerifyHeaderVisitor.visitor); } /// /// This visitor trivially asserts that the objects queue header /// field is zero. /// private class VerifyHeaderVisitor : SegregatedFreeList.ObjectVisitor { internal static VerifyHeaderVisitor visitor = new VerifyHeaderVisitor(); /// /// Visit small objects, checking queue header. /// internal unsafe override void VisitSmall(Object obj, UIntPtr memAddr) { VTable.Deny(ThreadHeaderQueue.IsInQueue(obj), "Object in ThreadHeaderQueue"); } internal override UIntPtr VisitLarge(Object obj) { VTable.Deny(ThreadHeaderQueue.IsInQueue(obj), "Object in ThreadHeaderQueue"); return ObjectLayout.Sizeof(obj); } } private static bool fDebug { get { return false; } } private static bool fDebugPhasing { get { return false; } } } }