//
// 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.Threading;
    using System.Runtime.CompilerServices;

#if SINGULARITY
    using Microsoft.Singularity;
#endif

#if SINGULARITY_KERNEL
    using Microsoft.Singularity.Scheduling;
#endif

    [NoCCtor]
    internal abstract class BaseCollector : Collector
    {

        [Inline]
        protected bool IsValidGeneration(int generation)
        {
            return ((generation >= MinGeneration) &&
                    (generation <= MaxGeneration));
        }

#region Heap Lifetime Events

        internal override void Shutdown()
        {
        }

        internal override void DestructHeap()
        {
            PrintGCTiming();
            PrintAllocations();
            CollectorStatistics.Event(GCEvent.DestroyHeap);
            CollectorStatistics.Summary();
        }

#endregion

#region Notifications

        internal override void NewThreadNotification(Thread newThread,
                                                     bool initial)
        {
            Transitions.NewThreadNotification(newThread.threadIndex, initial);
        }

        internal override void DeadThreadNotification(Thread deadThread)
        {
        }

        internal override void ThreadStartNotification(int currentThreadIndex)
        {
#if !SINGULARITY
            Thread currentThread = Thread.threadTable[currentThreadIndex];
            PageManager.MarkThreadStack(currentThread);
#endif
        }

        internal override void ThreadEndNotification(Thread currentThread)
        {
        }

        internal override void ThreadDormantGCNotification(int threadIndex)
        {
        }

#endregion

#region Allocation

        [ManualRefCounts]
        [Inline]
        internal override Object AllocateObject(VTable vtable,
                                                Thread currentThread)
        {
            return AllocateObject(vtable,
                                  ObjectLayout.ObjectSize(vtable),
                                  vtable.baseAlignment,
                                  currentThread);
        }

        [ManualRefCounts]
        [Inline]
        internal override Object AllocateObject(VTable vtable,
                                                UIntPtr numBytes,
                                                uint baseAlignment,
                                                Thread currentThread)
        {
            UIntPtr objectAddr =
                AllocateObjectMemory(numBytes, baseAlignment,
                                     currentThread);
            Object result = Magic.fromAddress(objectAddr);
            this.CreateObject(result, vtable, currentThread);
            return result;
        }

        [ManualRefCounts]
        internal override Array AllocateVector(VTable vtable,
                                               int numElements,
                                               Thread currentThread)
        {
            UIntPtr numBytes =
                ObjectLayout.ArraySize(vtable, unchecked((uint)numElements));
            UIntPtr vectorAddr =
                AllocateObjectMemory(numBytes, vtable.baseAlignment,
                                     currentThread);
            Array result = Magic.toArray(Magic.fromAddress(vectorAddr));
            CreateObject(result, vtable, currentThread);
            result.InitializeVectorLength(numElements);
            return result;
        }

        [ManualRefCounts]
        internal override  Array AllocateArray(VTable vtable,
                                               int rank,
                                               int totalElements,
                                               Thread currentThread)
        {
            UIntPtr numBytes =
                ObjectLayout.ArraySize(vtable, unchecked((uint)totalElements));
            UIntPtr arrayAddr =
                AllocateObjectMemory(numBytes, vtable.baseAlignment,
                                     currentThread);
            Array result = Magic.toArray(Magic.fromAddress(arrayAddr));
            CreateObject(result, vtable, currentThread);
            result.InitializeArrayLength(rank, totalElements);
            return result;
        }

        [ManualRefCounts]
        internal override String AllocateString(int stringLength,
                                                Thread currentThread)
        {
            VTable vtable =
                Magic.toRuntimeType(typeof(System.String)).classVtable;
            UIntPtr numBytes =
                ObjectLayout.StringSize(vtable,
                                        unchecked((uint) (stringLength+1)));
            UIntPtr stringAddr =
                AllocateObjectMemory(numBytes, unchecked((uint) UIntPtr.Size),
                                     currentThread);
            String result = Magic.toString(Magic.fromAddress(stringAddr));
            CreateObject(result, vtable, currentThread);
            result.InitializeStringLength(stringLength);
            return result;
        }

        [ManualRefCounts]
        [AssertDevirtualize]
        [Inline]
        protected virtual void CreateObject(Object obj, VTable vtable,
                                            Thread currentThread)
        {
            Barrier.InitObject(obj, vtable);
        }

#endregion

#region Profiling

        internal override void SetProfiler(GCProfiler profiler) {
            if (GcProfiler != null) {
                throw new InvalidOperationException("Only one GCProfiler can be active in a process");
            }
            ProfileRoots = new ProfileRootsDelegate(ProfileScanRoots);
            ProfileObjects = new ProfileObjectsDelegate(ProfileScanObjects);

            GcProfiler = profiler;
        }

        // A profiler can request a scan of all Roots, passing in a
        // visitor for callback.
        private void ProfileScanRoots(NonNullReferenceVisitor visitor) {
            CallStack.ScanStacks(visitor, visitor);
            Thread.VisitBootstrapData(visitor);
#if SINGULARITY_KERNEL
            Kernel.VisitSpecialData(visitor);
#endif
            MultiUseWord.VisitStrongRefs(visitor,
                                         false /* Don't use shadows */);
            StaticData.ScanStaticData(visitor);
        }

        // A profiler can request a scan of all Objects in the heap,
        // passing in a visitor for callback.
        private
        void ProfileScanObjects(SegregatedFreeList.ObjectVisitor visitor)
        {
#if !SINGULARITY
            VTable.Assert((System.GC.installedGC as MarkSweepCollector != null)
                          || (System.GC.installedGC as ConcurrentMSCollector != null)
                          || (System.GC.installedGC as ReferenceCountingCollector != null)
                          || (System.GC.installedGC as DeferredReferenceCountingCollector != null),
                          "ProfileScanObjects is only valid for MarkSweep, ConcurrentMS, " 
                          + "ReferenceCounting, and DeferredReferenceCounting collectors");
#endif
            SegregatedFreeList.VisitAllObjects(visitor);
        }

        internal override void ProfileAllocation(Object obj)
        {
            if (GC.IsProfiling && !HeapDamaged) {
                UIntPtr size = ObjectLayout.Sizeof(obj);
                GcProfiler.NotifyAllocation(Magic.addressOf(obj),
                                            obj.GetType(), size);
            }
        }

        protected static GCProfiler GcProfiler;
        protected static ProfileRootsDelegate ProfileRoots;
        protected static ProfileObjectsDelegate ProfileObjects;
        protected static bool HeapDamaged;

#endregion

#region Accounting

        private  static UIntPtr     newBytesSinceGC;
        internal static long        gcTotalBytes;
        internal static int         gcTotalCount;
        internal static long        gcTotalTime;
        private  static long        maxPauseTime;
        private  static long        pauseCount;

        // Abstraction violation.  Use only for debugging!
        internal static UIntPtr DebugNewBytesSinceGC {
            get { return newBytesSinceGC; }
        }

        internal static bool NewBytesSinceGCExceeds(UIntPtr limit)
        {
            return newBytesSinceGC >= limit;
        }

        internal static void IncrementNewBytesSinceGC(UIntPtr increment)
        {
            newBytesSinceGC += increment;
        }

        internal static void StartGCCycle()
        {
            gcTotalBytes += (long) newBytesSinceGC;
            newBytesSinceGC = UIntPtr.Zero;
            gcTotalCount++;
        }

        internal static void RegisterPause(int pauseTicks)
        {
            gcTotalTime += pauseTicks;
            if (maxPauseTime < pauseTicks) {
                maxPauseTime = pauseTicks;
            }
            pauseCount++;
        }

        internal virtual void RegisterHeapSize(ulong heapSize)
        {
        }

        internal virtual void RegisterNewObject(ulong objectSize)
        {
        }

        internal virtual void PrintGCTiming()
        {
            if (VTable.enableGCTiming || VTable.enableFinalGCTiming) {
#if SINGULARITY
                DebugStub.WriteLine("Total GC Time (ms): {0}",
                                    __arglist(gcTotalTime));
#else
                Console.Error.WriteLine("Total GC Time (ms): "+gcTotalTime);
                Console.Error.WriteLine("Max. Pause Time (ms): "+maxPauseTime);
                if (BaseCollector.pauseCount != 0) {
                    Console.Error.WriteLine("Avg. Pause Time (ms): "+
                                            gcTotalTime/pauseCount);
                } else {
                    Console.Error.WriteLine("Avg. Pause Time (ms): 0");
                }
#endif
            }
        }

        internal virtual void PrintAllocations()
        {
        }

#endregion

#region Helper functions

        internal static void AllThreadRendezvous(int currentThreadIndex)
        {
            Transitions.MakeGCRequests(currentThreadIndex);
            for (int i = 0; i < Thread.threadTable.Length; i++) {
                if (Thread.threadTable[i] == null ||
                    i == currentThreadIndex) {
                    continue;
                }
                CollectorStatistics.Event(GCEvent.StopThread, i);
                while (!Transitions.TakeGCControl(i) &&
                       !Transitions.UnderGCControl(i) &&
                       Transitions.HasGCRequest(i) &&
                       Thread.threadTable[i] != null) {
                    // NOTE: there is no code in this loop that could
                    // cause a signal on an event to be consumed.
                    Thread.WaitForGCEvent(currentThreadIndex);
                }
            }
        }

        internal static void AllThreadRelease(int currentThreadIndex)
        {
            for (int i = 0; i < Thread.threadTable.Length; i++) {
#if SINGULARITY_KERNEL
                if (Scheduler.IsIdleThread(i)) {
                    continue;
                }
#endif
                if (i == currentThreadIndex) {
                    if (Transitions.HasGCRequest(i)) {
                        Transitions.ClearGCRequest(i);
                    }
                } else if (Transitions.UnderGCControl(i)) {
                    Transitions.ReleaseGCControl(i);
                }
                // Signal all threads to ensure that the GC process didn't
                // accidentally "gobble" an event signal that was meant for
                // something else.
                if (Thread.threadTable[i] != null) {
                    Thread.SignalGCEvent(i);
                }
            }
        }

#endregion
    }

}
