//
// 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;

    internal abstract class CopyScan
    {

        protected PageType pageType;
        protected BumpAllocator allocator;

        internal virtual void Initialize(PageType pageType)
        {
            this.pageType = pageType;
            this.allocator = new BumpAllocator(pageType);
        }

        internal abstract void Cleanup();

        internal virtual void TruncateAllocationArea()
        {
            this.allocator.Truncate();
        }

        protected abstract bool HaveWork { get; }

        protected abstract void AddWork(UIntPtr start, UIntPtr limit);

        protected abstract UIntPtr GetWork(out UIntPtr value2);

        internal Object Copy(Object obj)
        {
            UIntPtr size = ObjectLayout.Sizeof(obj);
            if (GenerationalGCData.IsLargeObjectSize(size)) {
                return CopyLarge(obj, size);
            } else {
                return CopySmall(obj, size);
            }
        }

        private Object CopyLarge(Object obj, UIntPtr size)
        {
            // Don't copy large objects.
            // Change the page type instead.
            UIntPtr startAddr = Magic.addressOf(obj) - PreHeader.Size;
            UIntPtr startPage = PageTable.Page(startAddr);
            UIntPtr endPage = PageTable.Page(startAddr+size-1)+1;
            VTable.Assert(this.pageType ==
                          PageTable.ZombieToLive(PageTable.Type(startPage)));
            PageTable.SetType(startPage, (endPage - startPage),
                              this.pageType);
            UIntPtr sizeOfPages = PageTable.PageSize * (endPage-startPage);
            Trace.Log(Trace.Area.Pointer,
                      "FwdRef: large object: {0} gen: {1} pagesize: {2}",
                      __arglist(Magic.addressOf(obj), this.pageType,
                                sizeOfPages));
            GenerationalGCData.gcPromotedTable[(int)this.pageType-1] +=
                sizeOfPages;
            this.AddWork(startAddr, startAddr + size);
            return obj;
        }

        private unsafe Object CopySmall(Object obj, UIntPtr size)
        {
            uint alignment = obj.vtable.baseAlignment;
            UIntPtr newObjectAddr =
                this.allocator.AllocateFast(size, alignment);
            if (newObjectAddr == UIntPtr.Zero) {
                int threadIndex = StopTheWorldGCData.collectorThreadIndex;
                Thread thread = Thread.threadTable[threadIndex];
                UIntPtr oldAllocNew = this.allocator.AllocNew;
                newObjectAddr =
                    this.allocator.AllocateSlow(size, alignment, thread);
                UIntPtr allocNew = this.allocator.AllocNew;
                if (oldAllocNew != allocNew) {
                    UIntPtr limitPtr = this.allocator.ReserveLimit;
                    this.NewAllocationRange(newObjectAddr, allocNew, limitPtr);
                }
            }
            this.NewObject(newObjectAddr, size);
            UIntPtr srcAddr = Magic.addressOf(obj) - PreHeader.Size;
            UIntPtr dstAddr = newObjectAddr - PreHeader.Size;
            Trace.Log(Trace.Area.Pointer,
                      "FwdRef: {0} -> {1}, gen={2}, size={3}",
                      __arglist(Magic.addressOf(obj), newObjectAddr,
                                this.pageType, size));
            VTable.Assert(this.pageType ==
                          PageTable.Type(PageTable.Page(dstAddr)));
            GenerationalGCData.gcPromotedTable[(int)this.pageType-1] +=
                size;
            Util.MemCopy(dstAddr, srcAddr, size);
            *obj.VTableFieldAddr = newObjectAddr;
            return Magic.fromAddress(newObjectAddr);
        }

        protected abstract void NewAllocationRange(UIntPtr objectAddr,
                                                   UIntPtr rangeStart,
                                                   UIntPtr rangeLimit);

        protected abstract void NewObject(UIntPtr objAddr, UIntPtr size);

        [Inline]
        internal abstract bool Scan(NonNullReferenceVisitor visitor);

    }

    internal abstract class CopyScanQueue : CopyScan
    {

        private UIntPtrQueue workList;

        internal override void Initialize(PageType pageType)
        {
            base.Initialize(pageType);
            this.workList = new UIntPtrQueue();
        }

        protected override bool HaveWork{
            get { return !this.workList.IsEmpty; }
        }

        protected override void AddWork(UIntPtr start, UIntPtr limit)
        {
            this.workList.Write(start, limit);
        }

        protected override UIntPtr GetWork(out UIntPtr value2)
        {
            return this.workList.Read(out value2);
        }

        internal override void Cleanup()
        {
            this.workList.Cleanup(false);
        }

    }
        

    internal abstract class CopyScanStack : CopyScan
    {

        private UIntPtrStack workList;

        internal override void Initialize(PageType pageType)
        {
            base.Initialize(pageType);
            this.workList = new UIntPtrStack();
        }

        protected override bool HaveWork {
            get { return !this.workList.IsEmpty; }
        }

        protected override void AddWork(UIntPtr start, UIntPtr limit)
        {
            this.workList.Write(start, limit);
        }

        protected override UIntPtr GetWork(out UIntPtr value2)
        {
            return this.workList.Read(out value2);
        }

        internal override void Cleanup()
        {
            this.workList.Cleanup(false);
        }

    }
        

    internal class CheneyScan : CopyScanQueue
    {

        private UIntPtr destinationLow;
        private UIntPtr destinationHigh;

        protected override void NewAllocationRange(UIntPtr objectAddr,
                                                   UIntPtr rangeStart,
                                                   UIntPtr rangeLimit)
        {
            if (rangeStart != destinationHigh) {
                // Switching to a new allocation range
                if (destinationLow != destinationHigh) {
                    this.AddWork(destinationLow, destinationHigh);
                }
                destinationLow = rangeStart;
            }
            destinationHigh = rangeLimit;
        }

        protected override void NewObject(UIntPtr objAddr, UIntPtr size)
        {
            // We don't need to do anything for individual objects
        }

        [Inline]
        internal override bool Scan(NonNullReferenceVisitor visitor)
        {
            bool localRepeat = false;
            bool globalRepeat = false;
            while (true) {
                while (this.HaveWork) {
                    localRepeat = true;
                    UIntPtr lowAddr, highAddr;
                    lowAddr = this.GetWork(out highAddr);
                    lowAddr += PreHeader.Size;
                    lowAddr =
                        BumpAllocator.SkipNonObjectData(lowAddr, highAddr);
                    while (lowAddr < highAddr) {
                        Object obj = Magic.fromAddress(lowAddr);
                        lowAddr += visitor.VisitReferenceFields(obj);
                        lowAddr =
                            BumpAllocator.SkipNonObjectData(lowAddr, highAddr);
                    }
                }
                if (this.destinationLow != this.allocator.AllocPtr) {
                    localRepeat = true;
                    UIntPtr lowAddr = this.destinationLow;
                    UIntPtr highAddr = this.allocator.AllocPtr;
                    this.destinationLow = highAddr;
                    this.AddWork(lowAddr, highAddr);
                }
                if (!localRepeat) {
                    // Exit the loop if we have done nothing this time around
                    break;
                }
                globalRepeat = true;
                localRepeat = false;
            }
            return globalRepeat;
        }

        internal override void Initialize(PageType pageType)
        {
            base.Initialize(pageType);
            this.destinationLow = UIntPtr.Zero;
            this.destinationHigh = UIntPtr.Zero;
        }

        internal override void Cleanup()
        {
            VTable.Assert(!this.HaveWork, "Cheney Scan: work left at cleanup");
            base.Cleanup();
        }

        internal override void TruncateAllocationArea()
        {
            UIntPtr lowAddr = this.destinationLow;
            UIntPtr highAddr = this.allocator.AllocPtr;
            if (lowAddr != highAddr) {
                this.AddWork(lowAddr, highAddr);
            }
            base.TruncateAllocationArea();
            this.destinationLow = UIntPtr.Zero;
            this.destinationHigh = UIntPtr.Zero;
        }
    }

    internal class HierarchicalScan : CopyScanQueue
    {

        private UIntPtr destinationLow;
        private UIntPtr destinationHigh;
        private UIntPtr sourceHigh;

        protected override unsafe void NewAllocationRange(UIntPtr objectAddr,
                                                          UIntPtr rangeStart,
                                                          UIntPtr rangeLimit)
        {
            UIntPtr preObjectAddr = objectAddr - PreHeader.Size;
            if (preObjectAddr >= destinationLow &&
                preObjectAddr <= destinationHigh) {
                // Extending the current allocation range
                VTable.Assert(rangeStart == destinationHigh);
                if (destinationLow != preObjectAddr) {
                    this.AddWork(destinationLow, preObjectAddr);
                }
            } else {
                // Switching to a new allocation range
                if (destinationLow != destinationHigh) {
                    this.AddWork(destinationLow, destinationHigh);
                }
            }
            this.sourceHigh = UIntPtr.Zero;
            destinationLow = preObjectAddr;
            destinationHigh = rangeLimit;
        }

        protected override void NewObject(UIntPtr objAdr, UIntPtr size)
        {
            // We don't need to do anything for individual objects
        }

        [Inline]
        internal override bool Scan(NonNullReferenceVisitor visitor)
        {
            bool globalRepeat = false;
            while (true) {
                UIntPtr lowAddr, highAddr;
                if (this.destinationLow != this.allocator.AllocPtr) {
                    lowAddr = this.destinationLow;
                    highAddr = this.allocator.AllocPtr;
                    this.destinationLow = highAddr;
                } else if (!this.HaveWork) {
                    // No more work to do
                    break;
                } else {
                    lowAddr = this.GetWork(out highAddr);
                }
                globalRepeat = true;
                this.sourceHigh = highAddr;
                lowAddr += PreHeader.Size;
                lowAddr =
                    BumpAllocator.SkipNonObjectData(lowAddr, highAddr);
                while (lowAddr < this.sourceHigh) {
                    Object obj = Magic.fromAddress(lowAddr);
                    lowAddr += visitor.VisitReferenceFields(obj);
                    lowAddr =
                        BumpAllocator.SkipNonObjectData(lowAddr, highAddr);
                }
                if (lowAddr < highAddr) {
                    // The scanning must have been aborted early due to
                    // overflow into a new page.  Put the rest of the
                    // range on the work list.
                    this.AddWork(lowAddr - PreHeader.Size, highAddr);
                }
            }
            return globalRepeat;
        }

        internal override void Initialize(PageType pageType)
        {
            base.Initialize(pageType);
            this.destinationLow = UIntPtr.Zero;
            this.destinationHigh = UIntPtr.Zero;
        }

        internal override void Cleanup()
        {
            VTable.Assert(!this.HaveWork,
                          "Hierarchical Scan: work left at cleanup");
            base.Cleanup();
        }

        internal override void TruncateAllocationArea()
        {
            UIntPtr lowAddr = this.destinationLow;
            UIntPtr highAddr = this.allocator.AllocPtr;
            if (lowAddr != highAddr) {
                this.AddWork(lowAddr, highAddr);
            }
            base.TruncateAllocationArea();
            this.destinationLow = UIntPtr.Zero;
            this.destinationHigh = UIntPtr.Zero;
        }
    }

    internal class NestedHierarchicalScan : CopyScanStack
    {

        protected UIntPtr destinationLow;
        protected UIntPtr destinationHigh;
        protected UIntPtr sourceHigh;

        protected override unsafe void NewAllocationRange(UIntPtr objectAddr,
                                                          UIntPtr rangeStart,
                                                          UIntPtr rangeLimit)
        {
            // BUGBUG:
        }

        protected override void NewObject(UIntPtr objAddr, UIntPtr size)
        {
            // BUGBUG:
        }

        [Inline]
        internal override bool Scan(NonNullReferenceVisitor visitor)
        {
            // BUGBUG:
            return false;
        }

        internal override void TruncateAllocationArea()
        {
            // BUGBUG:
        }

    }

}
