/*******************************************************************/ /* 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 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; using Microsoft.Singularity.X86; #endif #endif using Microsoft.Win32; [NoCCtor] [RequiredByBartok] internal class CoCoMSCollector: ConcurrentMSCollector { internal static new CoCoMSCollector instance; internal static Thread cocoThread; internal static bool didStartTrace; internal static bool didEndTrace; internal static bool wantCoCo; internal static bool doingCoCo; internal static int markingForCoCo; // actually a boolean internal static bool inFixUp; internal static bool die; internal static ulong numCopied; internal static int delayCnt; internal static Object interlock; internal static int pinTime; internal static int prepTime; internal static int copyTime; internal static int forwardTime; internal static int timingBefore; internal static int timingAfterPin; internal static int timingAfterPrep; internal static int timingAfterCopy; internal static int timingAfter; internal static UIntPtr maxSpaceOverhead; internal static int sizeFracLim; internal static int sizeLim; internal static int pageFragThres; internal static int pinPenalty; internal static int cocoDelay; internal static UIntPtr lastSmallPagesRecycle; internal static NonNullReferenceVisitor normalStackMarker; internal static NonNullReferenceVisitor nopStackMarker; internal static NonNullReferenceVisitor pinStackMarker; [NoBarriers] [PreInitRefCounts] public static new void Initialize() { ConcurrentMSCollector.InitializeAllButVisitors(); // instance = new CoCoMSCollector(); ConcurrentMSCollector.instance = CoCoMSCollector.instance = (CoCoMSCollector) BootstrapMemory.Allocate(typeof(CoCoMSCollector)); ConcurrentMSCollector.markReferenceVisitor = (MarkReferenceVisitor) BootstrapMemory.Allocate(typeof(MarkAndForwardReferenceVisitor)); CoCoMSCollector.normalStackMarker = (NonNullReferenceVisitor) BootstrapMemory.Allocate(typeof(StackForwardReferenceVisitor)); CoCoMSCollector.pinStackMarker = (NonNullReferenceVisitor) BootstrapMemory.Allocate(typeof(StackMarkPinnedReferenceVisitor)); CoCoMSCollector.nopStackMarker = (NonNullReferenceVisitor) BootstrapMemory.Allocate(typeof(StackNopReferenceVisitor)); ConcurrentMSCollector.stackMarkReferenceVisitor = CoCoMSCollector.normalStackMarker; ConcurrentMSCollector.stackMarkPinnedReferenceVisitor = CoCoMSCollector.normalStackMarker; ConcurrentMSCollector.updateReferenceVisitor = (UpdateReferenceVisitor) BootstrapMemory.Allocate(typeof(UpdateAndForwardReferenceVisitor)); ConcurrentMSCollector.partialFreePageVisitor = (SegregatedFreeList.PartialFreePageVisitor) BootstrapMemory.Allocate(typeof(TaggingPartialFreePageVisitor)); // sweepVisitor = new SweepVisitor(); sweepVisitor = (SweepVisitor) BootstrapMemory.Allocate(typeof(SweepVisitor)); } internal static int EnvInt(int defVal, string name) { string str=Environment.GetEnvironmentVariable(name); if (str==null) { return defVal; } else { return Int32.Parse(str); } } internal override void EnableHeap() { interlock=new Object(); MultiUseWord.GetMonitor(interlock); CoCoBarrier.InitLate(); // REVIEW: add some bartok args instead sizeFracLim = EnvInt( 10 , "COCO_SIZE_FRAC_LIM" ); sizeLim = EnvInt( -1 , "COCO_SIZE_LIM" ); pageFragThres = EnvInt( 2 , "COCO_PAGE_FRAG_THRES" ); pinPenalty = EnvInt( 10 , "COCO_PIN_PENALTY" ); cocoDelay = EnvInt( 8 , "COCO_COPY_DELAY" ); if (EnvInt(0,"COCO_FORCE_SLOW")!=0) { CoCoBarrier.ForceSlow(); } if (EnvInt(0,"COCO_FORCE_NOT_IDLE")!=0) { CoCoBarrier.ForceNotIdle(); } if (EnvInt(0,"COCO_FORCE_FORWARDING")!=0) { CoCoBarrier.ForceForwarding(); } if (EnvInt(0,"COCO_FORCE_PINNING")!=0) { CoCoBarrier.ForcePinning(); } base.EnableHeap(); cocoThread=new Thread(new ThreadStart(CoCoLoop)); cocoThread.Start(); } internal override void Shutdown() { base.Shutdown(); lock (interlock) { die = true; Monitor.PulseAll(interlock); } cocoThread.Join(); if (VTable.enableFinalGCTiming) { VTable.DebugPrint("CoCo completed "); VTable.DebugPrint(cycles); VTable.DebugPrint(" cycles, started "); VTable.DebugPrint(cyclesStarted); VTable.DebugPrint(" cycles, and copied "); VTable.DebugPrint(numCopied); VTable.DebugPrint(" objects.\n"); VTable.DebugPrint("CoCo took "); VTable.DebugPrint((ulong)pinTime); VTable.DebugPrint("+"); VTable.DebugPrint((ulong)prepTime); VTable.DebugPrint("+"); VTable.DebugPrint((ulong)copyTime); VTable.DebugPrint("+"); VTable.DebugPrint((ulong)forwardTime); VTable.DebugPrint("="); VTable.DebugPrint((ulong)(pinTime+prepTime+copyTime+forwardTime)); VTable.DebugPrint(" ms.\n"); VTable.DebugPrint("max space overhead = "); VTable.DebugPrint((ulong)maxSpaceOverhead); VTable.DebugPrint("\n"); CoCoBarrier.PrintStats(); } } internal override void ThreadStartNotification(int currentThreadIndex) { base.ThreadStartNotification(currentThreadIndex); CoCoBarrier.ThreadStart(Thread.threadTable[currentThreadIndex]); } internal override UIntPtr AllocateObjectMemorySlow(UIntPtr numBytes, uint alignment, Thread currentThread) { CoCoBarrier.ClientHandshake(); return base.AllocateObjectMemorySlow(numBytes, alignment, currentThread); } internal static void InterlockWithCopier(ref bool condition) { lock (interlock) { if (fVerbose) { VTable.DebugPrint(" ~~ acquired lock, setting condition\n"); } condition = true; if (fVerbose) { VTable.DebugPrint(" ~~ pulsing = "); VTable.DebugPrint((ulong)Magic.addressOf(interlock)); VTable.DebugPrint("\n"); } Monitor.PulseAll(interlock); if (fVerbose) { VTable.DebugPrint(" ~~ waiting\n"); } while (condition) { Monitor.Wait(interlock); } } } internal override void PreTraceHook() { if (fVerbose) { VTable.DebugPrint(" ~~~~~ PreTraceHook ~ waiting\n"); } InterlockWithCopier(ref didStartTrace); if (fVerbose) { VTable.DebugPrint(" ~~~~~ PreTraceHook ~ awoken\n"); } } internal override void PostRootScanHook() { if (inFixUp) { timingAfterCopy = Environment.TickCount; CoCoBarrier.ChangePhase(CoCoBarrier.Phase.Idle,true,true); } } internal override void PostTraceHook() { if (fVerbose) { VTable.DebugPrint(" ~~~~~ PostTraceHook ~ waiting\n"); } InterlockWithCopier(ref didEndTrace); if (fVerbose) { VTable.DebugPrint(" ~~~~~ PostTraceHook ~ awoken\n"); } } internal override void PostReclamationHook() { } internal override void PreSweepHook() { CoCoBarrier.ExchangeReadyForCoCo(false); } internal override void PostSweepHook() { CoCoBarrier.ExchangeReadyForCoCo(true); } internal static void CoCoLoop() { if (fDebug) { VTable.DebugPrint("coco thread = "); VTable.DebugPrint((ulong)Win32Native.GetCurrentThreadId()); VTable.DebugPrint("\n"); VTable.DebugPrint("CoCo at "); VTable.DebugPrint((ulong)Magic.addressOf(Thread.CurrentThread)); VTable.DebugPrint("\n"); } for (;;) { lock (interlock) { doingCoCo=false; for (;;) { if (die) { return; } else if (didStartTrace) { didStartTrace=false; Monitor.PulseAll(interlock); } else if (didEndTrace) { didEndTrace=false; Monitor.PulseAll(interlock); if (wantCoCo) { break; } } Monitor.Wait(interlock); } wantCoCo=false; } // now further tracing is BLOCKED cyclesStarted++; timingBefore = Environment.TickCount; if (fDebug) { VTable.DebugPrint("+++++ Start Concurrent Copying\n"); } CoCoBarrier.EnablePinning(); doingCoCo=true; ConcurrentMSCollector.stackMarkReferenceVisitor = CoCoMSCollector.nopStackMarker; ConcurrentMSCollector.stackMarkPinnedReferenceVisitor = CoCoMSCollector.pinStackMarker; // Perform a scan of all call stacks, including the call // stack of the CoCo thread. ConcurrentMSCollector.TrivialHandshake = false; ConcurrentMSCollector.IncludeMUWInHandshake = false; ConcurrentMSCollector.CollectorHandshake(cocoThread); // 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(cocoThread.threadIndex); GC.InvokeCollection(cocoThread); ConcurrentMSCollector.TrivialHandshake = true; ConcurrentMSCollector.IncludeMUWInHandshake = true; ConcurrentMSCollector.stackMarkReferenceVisitor = CoCoMSCollector.normalStackMarker; ConcurrentMSCollector.stackMarkPinnedReferenceVisitor = CoCoMSCollector.normalStackMarker; timingAfterPin = Environment.TickCount; if (fDebug) { VTable.DebugPrint("+++++ Copying\n"); } if (CoCoBarrier.instance.NeedsPrepPhase) { CoCoBarrier.ChangePhase(CoCoBarrier.Phase.Prep,false,true); } timingAfterPrep = Environment.TickCount; CoCoBarrier.ChangePhase(CoCoBarrier.Phase.Copy,true,true); numCopied+=CoCoBarrier.instance.Copy(); CoCoBarrier.ChangePhase(CoCoBarrier.Phase.Fixup,true,true); AddCollectionRequest(); // wait for a complete collector cycle. This is for fixup. if (fDebug) { VTable.DebugPrint("+++++ Fixup: Waiting to start tracing\n"); } lock (interlock) { while (!didStartTrace && !die) { Monitor.Wait(interlock); } if (die) { return; } didStartTrace=false; inFixUp=true; Monitor.PulseAll(interlock); } if (fDebug) { VTable.DebugPrint("+++++ Fixup: Waiting to end tracing\n"); } lock (interlock) { while (!didEndTrace && !die) { Monitor.Wait(interlock); } if (die) { return; } didEndTrace=false; doingCoCo=false; inFixUp=false; Monitor.PulseAll(interlock); } timingAfter = Environment.TickCount; CoCoBarrier.ChangePhase(CoCoBarrier.Phase.Idle,false,false); if (fDebug) { VTable.DebugPrint("+++++ Finish Concurrent Copying\n"); } pinTime+=(timingAfterPin-timingBefore); prepTime+=(timingAfterPrep-timingAfterPin); copyTime+=(timingAfterCopy-timingAfterPrep); forwardTime+=(timingAfter-timingAfterCopy); cycles++; } } internal class MarkAndForwardReferenceVisitor : MarkReferenceVisitor { [NoBarriers] ulong cnt; [NoBarriers] bool amMarkingForCoCo; [NoBarriers] UIntPtr spaceOverhead; internal override void Init() { base.Init(); cnt=0; amMarkingForCoCo=(markingForCoCo!=0); spaceOverhead=UIntPtr.Zero; if (fDebug && amMarkingForCoCo) { VTable.DebugPrint("+++++ Marking For CoCo\n"); } } internal override void Cleanup() { base.Cleanup(); if (spaceOverhead>maxSpaceOverhead) { maxSpaceOverhead=spaceOverhead; } if (amMarkingForCoCo) { markingForCoCo=0; #if !ARM && !ISA_ARM // HACK: MemoryBarrier is unimplemented in ARM // and causes compile-time failures when building // mscorlib in sepcomp mode. This change will break // CoCo if built with ARM. Thread.MemoryBarrier(); #endif } if (fDebug) { VTable.DebugPrint(" $$$ tagged "); VTable.DebugPrint(cnt); VTable.DebugPrint(" objects\n"); } } [NoInline] internal unsafe void ForwardAndVisit(UIntPtr *loc) { UIntPtr addr=*loc; if (addr==UIntPtr.Zero) { return; } UIntPtr forward= CoCoBarrier.instance.ToSpaceImplNonNull(Magic.fromAddress(addr)); if (forward != addr) { CoCoBarrier.CAS(loc,forward,addr); // if this CAS fails it means that someone stored // a different pointer into the field, but in that // case the pointer stored would have already been // forwarded thanks to the CoCo write barrier. } VisitValueNonNull(forward); } [Inline] protected override unsafe void Filter(UIntPtr *location, ref ObjectDescriptor objDesc) { this.Visit(location); } /// /// Visit an object reference. /// [Inline] internal unsafe override void Visit(UIntPtr *loc) { if (CoCoBarrier.forwarding) { ForwardAndVisit(loc); } else { VisitValueMaybeNull(*loc); } } // 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); } [NoInline] //[NoBarriers] void ProcessObjectsSlow(ref ThreadHeaderQueue.LocalList workList) { while (!ConcurrentMSCollector.killCollectorThreads && !workList.IsEmpty()) { // Pop the next value Object obj = workList.Pop(markedColor); if (fVerbose) { VTable.DebugPrint("cms popped: "); VTable.DebugPrint((ulong)Magic.addressOf(obj)); VTable.DebugPrint("\n"); } // let CoCo do some stuff ScanHook(obj); // Visit Fields this.VisitReferenceFields(obj); } } //[NoBarriers] internal override void ProcessMyGrayObjects(ref ThreadHeaderQueue.LocalList workList) { if (amMarkingForCoCo) { ProcessObjectsSlow(ref workList); } else { // hand-inlined from ConcurrentMSCollector. needed // to ensure that the VisitReferenceFields call gets // inlined. while (!ConcurrentMSCollector.killCollectorThreads && !workList.IsEmpty()) { // Pop the next value Object obj = workList.Pop(markedColor); if (CoCoBarrier.fVerifyToSpaceMark && !CoCoBarrier.instance.IsInToSpace(Magic.addressOf(obj))) { VTable.DebugBreak(); } // Visit Fields this.VisitReferenceFields(obj); } } } internal unsafe void ScanHook(Object obj) { UIntPtr page=PageTable.Page(Magic.addressOf(obj)); if (PageTable.Type(page)!=SegregatedFreeList.SMALL_OBJ_PAGE) { //VTable.DebugPrint(" not tagging because this isn't a small object page"); return; } SegregatedFreeList.PageHeader *ph= (SegregatedFreeList.PageHeader*)PageTable.PageAddr(page); if (!new CoCoPageUserValue(ph->userValue).Marked) { //VTable.DebugPrint(" not tagging because the page isn't marked\n"); return; } if (obj is EMU || obj is Monitor || obj is Thread || obj is ThreadHeaderQueue) { CoCoBarrier.NotifyPin(Magic.addressOf(obj)); if (fVerbose) { VTable.DebugPrint(" $$ not tagging object because it's a monitor or EMU\n"); } return; } if (doingCoCo) { //VTable.DebugPrint(" not tagging object because doingCoCo\n"); return; } if (!CoCoBarrier.instance.ObjectIsNotCopied(obj)) { if (fVerbose) { VTable.DebugPrint(" not tagging object because object is already in the process of being copied.\n"); } return; } if (fVerbose && obj.GetType() != typeof(Object)) { VTable.DebugPrint(" $$ tagging a non-System.Object; type is "); VTable.DebugPrint(obj.GetType().Name); VTable.DebugPrint("\n"); } // REVIEW: I wish that there was an easier way of // doing this. Object copy; if (obj is Array) { Array a=(Array)obj; if (a.IsVector) { copy=GC.AllocateVector(a.vtable,a.Length); } else { copy=GC.AllocateArray(a.vtable,a.Rank,a.Length); } } else if (obj is String) { String s=(String)obj; // REVIEW: this is not nice. copy=GC.AllocateString(s.ArrayLength-1); } else { copy=GC.AllocateObject(obj.vtable); } VTable.Assert(ObjectLayout.Sizeof(copy) == ObjectLayout.Sizeof(obj), "Copy is not same size as original"); spaceOverhead+=ObjectLayout.Sizeof(copy); bool first=!CoCoBarrier.instance.AnyTaggedForCopying; UIntPtr thisSpaceOverhead; if (CoCoBarrier.instance.TagObjectForCopy(obj,copy, out thisSpaceOverhead)) { cnt++; if (first) { lock (interlock) { if (!wantCoCo && !doingCoCo) { wantCoCo=true; } } } } spaceOverhead+=thisSpaceOverhead; } } internal class UpdateAndForwardReferenceVisitor: UpdateReferenceVisitor { internal override UIntPtr ForwardIfNecessary(UIntPtr addr) { return CoCoBarrier.ToSpaceAsPtr(addr); } } internal class TaggingPartialFreePageVisitor: SegregatedFreeList.PartialFreePageVisitor { UIntPtr cnt; UIntPtr unmarkedCnt; UIntPtr delayedCnt; ulong emptyCnt; bool delay; internal override void Start() { cnt=UIntPtr.Zero; unmarkedCnt=UIntPtr.Zero; delayedCnt=UIntPtr.Zero; emptyCnt=0; if (fDebug) { VTable.DebugPrint(" $$$ delayCnt = "); VTable.DebugPrint((ulong)delayCnt); VTable.DebugPrint(", markingForCoCo = "); VTable.DebugPrint((ulong)markingForCoCo); VTable.DebugPrint("\n"); } if (delayCnt==0) { delay=(markingForCoCo!=0); // delay if there are still pages marked } else { delayCnt--; delay=true; } if (fDebug) { VTable.DebugPrint(" $$$ delayCnt = "); VTable.DebugPrint((ulong)delayCnt); VTable.DebugPrint(", markingForCoCo = "); VTable.DebugPrint((ulong)markingForCoCo); VTable.DebugPrint(", delay = "); VTable.DebugPrint(delay?"yes":"no"); VTable.DebugPrint("\n"); } } internal override void Finish() { lastSmallPagesRecycle=SegregatedFreeList.SmallPages; if (fDebug) { VTable.DebugPrint(" $$$ marked "); VTable.DebugPrint((ulong)cnt); VTable.DebugPrint(", unmarked "); VTable.DebugPrint((ulong)unmarkedCnt); VTable.DebugPrint(", delayed "); VTable.DebugPrint((ulong)delayedCnt); VTable.DebugPrint(", and freed "); VTable.DebugPrint(emptyCnt); VTable.DebugPrint(".\n"); } if (cnt!=0) { delayCnt=cocoDelay; markingForCoCo=1; #if !ARM && !ISA_ARM // HACK: MemoryBarrier is unimplemented in ARM // and causes compile-time failures when building // mscorlib in sepcomp mode. This change will break // CoCo if built with ARM. Thread.MemoryBarrier(); #endif } } internal override void ObserveEmptyPage() { emptyCnt++; } internal override unsafe SegregatedFreeList.PartialPageAction VisitPage(UIntPtr page, SegregatedFreeList.PageHeader *ph, int cells) { if (fDebugPage) { VTable.DebugPrint(" $$ visiting page.\n"); } CoCoPageUserValue v=new CoCoPageUserValue(ph->userValue); if (inFixUp && v.Marked) { unmarkedCnt++; v.Marked=false; if (v.Pinned) { v.Pinned=false; v.Version=pinPenalty; /* use the Version to delay any future evacuation attempts for this page */ } } else if (!doingCoCo && !delay) { if (v.Version>=1) { // this page has a non-zero Version - this means // that any attempts to evacuate it should be delayed. v.Version--; delayedCnt++; if (fDebugPage) { VTable.DebugPrint(" $$ page had non-zero version.\n"); } } else { if (fDebugPage) { VTable.DebugPrint(" $$ cnt = "); VTable.DebugPrint((ulong)cnt); VTable.DebugPrint(", lastSmallPagesRecycle = "); VTable.DebugPrint((ulong)lastSmallPagesRecycle); VTable.DebugPrint(", sizeFracLim = "); VTable.DebugPrint((ulong)sizeFracLim); VTable.DebugPrint(", sizeLim = "); VTable.DebugPrint((ulong)sizeLim); VTable.DebugPrint(", PageSize = "); VTable.DebugPrint((ulong)PageTable.PageSize); VTable.DebugPrint(", freeCount = "); VTable.DebugPrint((ulong)ph->freeCount); VTable.DebugPrint(", pageFragThres = "); VTable.DebugPrint((ulong)pageFragThres); VTable.DebugPrint(", cells = "); VTable.DebugPrint((ulong)cells); VTable.DebugPrint("\n"); } if ((long)cnt < ((long)lastSmallPagesRecycle /(long)sizeFracLim) && (sizeLim<0 || cnt*PageTable.PageSize < (UIntPtr)sizeLim) && ph->freeCount*pageFragThres >= cells) { cnt++; v.Marked=true; } } } else { if (fDebugPage) { VTable.DebugPrint(" $$ not doing anything about page.\n"); } } ph->userValue=v.Bits; if (v.Marked) { return SegregatedFreeList.PartialPageAction.CommitFull; } else { return SegregatedFreeList.PartialPageAction.CommitFree; } } } internal class StackForwardReferenceVisitor : ConcurrentMSCollector.StackMarkReferenceVisitor { internal override unsafe void ProcessObjectPtr(UIntPtr realPtr, UIntPtr *loc, UIntPtr addr) { UIntPtr forward=CoCoBarrier.ToSpaceAsPtr(realPtr); if (forward!=realPtr) { *loc=addr-realPtr+forward; } markReferenceVisitor.VisitValueAnyThreadMaybeNull(forward); } } internal class StackNopReferenceVisitor : NonNullReferenceVisitor { [Inline] internal override unsafe void Visit(UIntPtr *location) { // do nothing } } internal class StackMarkPinnedReferenceVisitor : ConcurrentMSCollector.StackMarkReferenceVisitor { internal override unsafe void ProcessObjectPtr(UIntPtr realPtr, UIntPtr *loc, UIntPtr addr) { // FIXME: DoPin() already does FindObjectForInteriorPtr CoCoBarrier.instance.DoPin(realPtr,CoCoBarrier.Pinner.StackScan); } } private static ulong cyclesStarted; private static ulong cycles; private static bool fVerbose { get { return false; } } private static bool fDebug { get { return false; } } private static bool fDebugPage { get { return false; } } } }