//
// 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.       */
/*******************************************************************/

// #define DEBUG

namespace System.GCs {

  using Microsoft.Bartok.Options;
  using Microsoft.Bartok.Runtime;
  using System.Runtime.InteropServices;
  using System.Runtime.CompilerServices;
  using System.Threading;
  using System.Collections;

  [RequiredByBartok]
  internal class TrialDeletion {
      [PreInitRefCounts]
      public static void Initialize() {
          Object plcLinkObj = BootstrapMemory.Allocate(typeof(PLCLink));
          firstPLCLink =
              (PLCLink*)(Magic.addressOf(plcLinkObj)+PostHeader.Size);
          plcListVtable = ((RuntimeType)typeof(PLCLink[])).classVtable;
          plcListChunkBytes =
              ObjectLayout.ArraySize(plcListVtable, plcListChunkLength);
      }


      /*
       * Reserve bits in the RS field for marking purposes,
       * and for flagging acyclic objects.
       */
      [MixinConditional("TrialDeletion")]
      [Mixin(typeof(RCCollector.RSMasks))]
      internal new struct RSMasks {
          internal const uint markFlag =                0x40000000;
          internal const uint acyclicFlag =             0x20000000;
      }

      [MixinConditional("TrialDeletion")]
      [Mixin(typeof(Microsoft.Bartok.Runtime.PreHeader))]
      internal unsafe struct PreHeader {
          internal PLCLink* plcLink;
      }

      // Each link in the "potentially leaked cycles" list.
      internal unsafe struct PLCLink {
          // The next link in the list of potentially leaked cycles.
          public PLCLink* next;

          // Address of an object lying on a potentially leaked cycle;
          public UIntPtr objAddr;
      }

      private static unsafe PLCLink* getPLCLink(Object obj) {
          return ((PreHeader)(obj.preHeader)).plcLink;
      }

      private static unsafe void setPLCLink(Object obj, PLCLink* link) {
          ((PreHeader)(obj.preHeader)).plcLink = link;
      }

      private static InternalIncrementer internalIncrementer;
      private static InternalDecrementer internalDecrementer;
      private static InternalScanner internalScanner;
      private static InternalReclaimer internalReclaimer;

      private static PLCLink* firstPLCLink;
      private static VTable plcListVtable;
      private static UIntPtr plcListChunkBytes;
      private static PLCLink* plcListChunk;
      private static uint numPLCChunks;

      private const uint plcListChunkLength = 1 << 12;
      private const uint maxPLCChunks = 256;

      private static ulong maxCyclicGarbage;
      private static ulong totalCyclicGarbage;
      private static uint cycleCollections;
      private static bool forceCycleCollectionAtEnd {
          get { return false; }
      }

      [PreInitRefCounts]
      public static unsafe void Initialize() {
          internalIncrementer =
              (InternalIncrementer)BootstrapMemory.
              Allocate(typeof(InternalIncrementer));
          internalDecrementer =
              (InternalDecrementer)BootstrapMemory.
              Allocate(typeof(InternalDecrementer));
          internalScanner =
              (InternalScanner)BootstrapMemory.
              Allocate(typeof(InternalScanner));
          internalReclaimer =
              (InternalReclaimer)BootstrapMemory.
              Allocate(typeof(InternalReclaimer));
      }


      [Inline]
      [ManualRefCounts]
      private static unsafe void addToPLCList(Object obj) {
          // Check if free PLC links are available.
          if (plcListChunk == null) {
              if (numPLCChunks < maxPLCChunks) {
                  // Allocate a chunk of PLC links.
                  int threadIndex = Thread.GetCurrentThreadIndex();
                  Thread t = Thread.threadTable[threadIndex];
                  UIntPtr resultAddr =
                      t.segregatedFreeList.
                      Allocate(plcListChunkBytes,
                               plcListVtable.baseAlignment);
                  Object result = Magic.fromAddress(resultAddr);
                  result.REF_STATE = 2 & ~countingONFlagMask;
                  result.vtable = plcListVtable;
                  numPLCChunks++;

                  // Point <plcListChunk> to the first element
                  // in the allocated array of PLC links and string
                  // the elements into a free PLC links' list.
                  plcListChunk = (PLCLink*)
                      (resultAddr+PostHeader.Size+UIntPtr.Size);
                  for (PLCLink* link = plcListChunk;
                       link < plcListChunk+
                       plcListChunkLength-1;
                       link++) {
                      link->next = link+1;
                  }
              } else {
                  processPLCList();
              }
          }
          VTable.Assert(plcListChunk != null,
                        @"plcListChunk != null");

          // Insert object into the PLC list by moving a link
          // from the free PLC links' chunk.
          firstPLCLink->objAddr = Magic.addressOf(obj);
          PLCLink* currPLCLink = plcListChunk;
          plcListChunk = plcListChunk->next;
          currPLCLink->next = firstPLCLink;
          firstPLCLink = currPLCLink;
          firstPLCLink->objAddr = UIntPtr.Zero;

          // Point the object to its link in the PLC list.
          setPLCLink(obj, firstPLCLink);
      }

      [ManualRefCounts]
      private static unsafe void removeFromPLCList(PLCLink* pLinkAddr) {
          // The object needs to be removed from the PLC list.
          PLCLink* currPLCLink = pLinkAddr;
#if DEBUG
          VTable.Assert(currPLCLink->next != null,
                        @"currPLCLink->next != null");
#endif // DEBUG

          PLCLink* linkToSkip = currPLCLink->next;
          PLCLink* linkAfter = linkToSkip->next;
          currPLCLink->next = linkAfter;
          linkToSkip->next = plcListChunk;
          plcListChunk = linkToSkip;

          // Update the PLC link pointer in the following object.
          if (linkAfter != null) {
              UIntPtr nextObjAddr = linkAfter->objAddr;
              Object nextObj = Magic.fromAddress(nextObjAddr);
#if DEBUG
              VTable.Assert(getPLCLink(nextObj) == linkToSkip,
                            @"getPLCLink(nextObj) == linkToSkip");
#endif // DEBUG

              setPLCLink(nextObj, currPLCLink);
          }
      }

      [ManualRefCounts]
      private static unsafe void processPLCList() {
          int startTicks = 0;
          bool enableGCTiming = VTable.enableGCTiming;
          if (enableGCTiming) {
              VTable.enableGCTiming = false;
              startTicks = Environment.TickCount;
          }
          if (VTable.enableGCWatermarks) {
              MemoryAccounting.RecordHeapWatermarks();
          }

#if DEBUG
          VTable.Assert(firstPLCLink->objAddr == UIntPtr.Zero,
                        @"firstPLCLink->objAddr == UIntPtr.Zero");
#endif // DEBUG

          // Let S be the subgraph of heap objects reachable from
          // the PLC list. Decrement counts due to references in S.
          for (PLCLink* link = firstPLCLink->next; link != null;
               link = link->next) {
              UIntPtr objAddr = link->objAddr;
              VTable.Assert(objAddr != UIntPtr.Zero,
                            @"objAddr != UIntPtr.Zero");

              Object obj = Magic.fromAddress(objAddr);
              VTable.Assert((obj.REF_STATE &
                             countingONFlagMask) != 0,
                            @"(obj.REF_STATE &
                               countingONFlagMask) != 0");

              uint refState = obj.REF_STATE;
              if ((refState & markFlagMask) == 0) {
                  obj.REF_STATE = refState | markFlagMask;
                  internalDecrementer.Traverse(objAddr);
              }
          }

          // Objects that now have non-zero counts are those that
          // have references external to S incident on them.
          // Recompute counts due to reachability from such objects.
          for (PLCLink* link = firstPLCLink->next; link != null;
               link = link->next) {
              UIntPtr objAddr = link->objAddr;
              internalScanner.Traverse(objAddr);
          }

          // String together objects with reference count
          // of zero for reclamation.
          internalReclaimer.Initialize();
          for (PLCLink* link = firstPLCLink->next; link != null;
               link = link->next) {
              UIntPtr objAddr = link->objAddr;
              internalReclaimer.Traverse(objAddr);
          }
          ulong reclaimedBytes = 0;
          Object reclaimedObj = internalReclaimer.ReclaimedObjects;
          while (reclaimedObj != null) {
              if (VTable.enableGCProfiling) {
                  UIntPtr size = ObjectLayout.Sizeof(reclaimedObj);
                  reclaimedBytes += (ulong)size;
              }
              Object nextReclaimedObj = getNextLink(reclaimedObj);
              SegregatedFreeList.Free(reclaimedObj);
              reclaimedObj = nextReclaimedObj;
          }

          // Recycle the PLC list.
          if (firstPLCLink->next != null) {
              PLCLink* lastPLCLink = firstPLCLink;
              do {
                  lastPLCLink = lastPLCLink->next;
              } while (lastPLCLink->next != null);
              lastPLCLink->next = plcListChunk;
              plcListChunk = firstPLCLink->next;
              firstPLCLink->next = null;
          }

          // Release the memory used up by work lists.
          UIntPtrQueue.ReleaseStandbyPages(null);

          SegregatedFreeList.RecycleGlobalPages();
          SegregatedFreeList.CommitFreedData();
          GC.newBytesSinceGC = UIntPtr.Zero;

          if (enableGCTiming) {
              int elapsedTicks = Environment.TickCount - startTicks;
              System.GC.gcTotalTime += elapsedTicks;
              if (System.GC.maxPauseTime < elapsedTicks) {
                  System.GC.maxPauseTime = elapsedTicks;
              }
              System.GC.pauseCount++;
              VTable.enableGCTiming = true;
          }

          if (VTable.enableGCProfiling) {
              if (maxCyclicGarbage < reclaimedBytes) {
                  maxCyclicGarbage = reclaimedBytes;
              }
              totalCyclicGarbage += reclaimedBytes;
              cycleCollections++;
          }
      }


      private class InternalDecrementer : NonNullReferenceVisitor {
          [ManualRefCounts]
          internal void Traverse(UIntPtr objAddr) {
              Object obj = Magic.fromAddress(objAddr);
              this.VisitReferenceFields(obj);
              while (!this.workList.IsEmpty) {
                  objAddr = this.workList.Read();
                  obj = Magic.fromAddress(objAddr);
                  this.VisitReferenceFields(obj);
              }
          }

          [ManualRefCounts]
          internal override unsafe void Visit(UIntPtr* loc) {
              UIntPtr objAddr = *loc;
              Object obj = Magic.fromAddress(objAddr);
              uint refState = obj.REF_STATE;
              if ((refState & countingONFlagMask) == 0) {
                  return;
              }
              uint refCount = refState & refCountMask;
              VTable.Assert(refCount > 0,
                            @"refCount > 0");

              refState--;
              if ((refState & markFlagMask) != 0) {
                  obj.REF_STATE = refState;
              } else {
                  obj.REF_STATE = refState | markFlagMask;
                  this.workList.Write(objAddr);
              }
          }

          private UIntPtrQueue workList;
      }

      private class InternalScanner : NonNullReferenceVisitor {
          [ManualRefCounts]
          internal unsafe void Traverse(UIntPtr objAddr) {
              this.Visit(&objAddr);
              while (!this.workList.IsEmpty) {
                  objAddr = this.workList.Read();
                  Object obj = Magic.fromAddress(objAddr);
                  this.VisitReferenceFields(obj);
              }
          }

          [ManualRefCounts]
          internal override unsafe void Visit(UIntPtr* loc) {
              UIntPtr objAddr = *loc;
              Object obj = Magic.fromAddress(objAddr);
              uint refState = obj.REF_STATE;
              if ((refState & countingONFlagMask) == 0 ||
                  (refState & markFlagMask) == 0) {
                  return;
              }
              obj.REF_STATE = refState & ~markFlagMask;

              uint refCount = refState & refCountMask;
              if (refCount > 0) {
                  // This object isn't leaked data, so
                  // clear the PLC link in its header.
                  setPLCLink(obj, null);
                  internalIncrementer.Traverse(objAddr);
              } else {
                  this.workList.Write(objAddr);
              }
          }

          private UIntPtrQueue workList;
      }

      private class InternalIncrementer : NonNullReferenceVisitor {
          [ManualRefCounts]
          internal void Traverse(UIntPtr objAddr) {
              Object obj = Magic.fromAddress(objAddr);
              this.VisitReferenceFields(obj);
              while (!this.workList.IsEmpty) {
                  objAddr = this.workList.Read();
                  obj = Magic.fromAddress(objAddr);
                  this.VisitReferenceFields(obj);
              }
          }

          [ManualRefCounts]
          internal override unsafe void Visit(UIntPtr* loc) {
              UIntPtr objAddr = *loc;
              Object obj = Magic.fromAddress(objAddr);
              uint refState = obj.REF_STATE;
              if ((refState & countingONFlagMask) == 0) {
                  return;
              }
              uint refCount = refState & refCountMask;
              VTable.Assert(refCount < refCountMask,
                            @"refCount < refCountMask");

              refState++;
              if ((refState & markFlagMask) != 0) {
                  // The object hasn't been visited either
                  // by the scanner or the incrementer.
                  obj.REF_STATE = refState & ~markFlagMask;
                  this.workList.Write(objAddr);
                  // This object isn't leaked data, so
                  // clear the PLC link in its header.
                  setPLCLink(obj, null);
              } else if ((refState & refCountMask) == 1) {
                  // The object has been visited in the
                  // past, but only by the scanner.
                  obj.REF_STATE = refState;
                  this.workList.Write(objAddr);
                  // This object isn't leaked data, so
                  // clear the PLC link in its header.
                  setPLCLink(obj, null);
              } else {
                  obj.REF_STATE = refState;
#if DEBUG
                  // The PLC link in this object's header
                  // should already be cleared.
                  PLCLink* pLinkAddr = getPLCLink(obj);
                  VTable.Assert(pLinkAddr == null,
                                @"pLinkAddr == UIntPtr.Zero");
#endif // DEBUG
              }
          }

          private UIntPtrQueue workList;
      }

      private class InternalReclaimer : NonNullReferenceVisitor {
          internal Object ReclaimedObjects;

          [ManualRefCounts]
          internal void Initialize() {
              this.ReclaimedObjects = null;
          }

          [ManualRefCounts]
          internal unsafe void Traverse(UIntPtr objAddr) {
              this.Visit(&objAddr);
              while (!this.workList.IsEmpty) {
                  objAddr = this.workList.Read();
                  Object obj = Magic.fromAddress(objAddr);
                  this.VisitReferenceFields(obj);
              }
          }

          [ManualRefCounts]
          internal override unsafe void Visit(UIntPtr* loc) {
              UIntPtr objAddr = *loc;
              Object obj = Magic.fromAddress(objAddr);
              uint refState = obj.REF_STATE;
              if ((refState & countingONFlagMask) == 0) {
                  return;
              }
              uint refCount = refState & refCountMask;
              if (refCount == 0) {
                  setNextLink(obj, this.ReclaimedObjects);
                  obj.REF_STATE = ~countingONFlagMask;
                  this.ReclaimedObjects = obj;
                  this.workList.Write(objAddr);
              }
          }

          private UIntPtrQueue workList;
      }
  }
}
