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

  [NoCCtor]
  [RequiredByBartok]
  internal class ARCSCollector : SingleThreadedRCCollector {
      [PreInitRefCounts]
      public new static void Initialize() {
          SingleThreadedRCCollector.Initialize();

          incrementer =
              (RefCountIncrementer)BootstrapMemory.
              Allocate(typeof(RefCountIncrementer));
          decrementer =
              (RefCountDecrementer)BootstrapMemory.
              Allocate(typeof(RefCountDecrementer));

          instance =
              (ARCSCollector)BootstrapMemory.
              Allocate(typeof(ARCSCollector));
      }

      [NoInline]
      [ManualRefCounts]
      public override void VerifyHeap(bool beforeCollection) {
          preVerifyHeap();

          // Deallocate objects on the delayed deallocation list.
          purgeDeallocationList(decrementer);

          postVerifyHeap(beforeCollection);
      }


      [MixinConditional("ARCSGC")]
      [Mixin(typeof(Object))]
      internal class ARCSGCObject : System.Object {
          internal new TrialDeletion.PreHeader preHeader;
      }


      internal override void EnableHeap() {
      }

      internal override void DestructHeap() {
          if (VTable.enableGCVerify) {
              VerifyHeap(true);
          }
          base.DestructHeap();
          if (VTable.enableGCProfiling) {
              if (RCCollector.ProfilingMode) {
                  EmitRefCountsProfile();
              }
          }
      }

      [NoInline]
      [InlineIntoOnce]
      [ManualRefCounts]
      [RequiredByBartok]
      internal static unsafe void NonNullIncrementRefCount(Object obj) {
          uint refState = BasicIncrementRefCount(obj);

          td.IncrementCycleProcessing(refState, obj);
      }

          // Exclude the object from leaked cycle processing.
          if ((refState & RSMasks.acyclicFlag) != 0) {
#if DEBUG
              PLCLink* pLinkAddr = td.getPLCLink(obj);
              VTable.Assert(pLinkAddr == null,
                            @"pLinkAddr == null");
#endif // DEBUG
          } else {
              PLCLink* pLinkAddr = td.getPLCLink(obj);
              // If the object hasn't already been removed from
              // the PLC ("potentially leaked cycle") list, remove it.
              if (pLinkAddr != null) {
                  // Reset the PLC link pointer in the object.
                  td.setPLCLink(obj, null);
                  td.removeFromPLCList(pLinkAddr);
              }
          }
      }

      [Inline]
      [InlineIntoOnce]
      [ManualRefCounts]
      [RequiredByBartok]
      internal static void AcyclicIncrementRefCount(Object obj) {
          if (obj == null) {
              return;
          }

          NonNullAcyclicIncrementRefCount(obj);
      }

      [Inline]
      [InlineIntoOnce]
      [ManualRefCounts]
      [RequiredByBartok]
      internal static void NonNullAcyclicIncrementRefCount(Object obj) {
          uint refState = BasicIncrementRefCount(obj);

          VTable.Assert((refState & RSMasks.acyclicFlag) != 0,
                        @"(refState & RSMasks.acyclicFlag) != 0");
      }

      [Inline]
      [ManualRefCounts]
      [RequiredByBartok]
      internal static uint BasicIncrementRefCount(Object obj) {
          VTable.Assert(obj != null &&
                        (obj.REF_STATE & RSMasks.refCount) <
                        RSMasks.refCount,
                        @"obj != null &&
                        (obj.REF_STATE & RSMasks.refCount) <
                        RSMasks.refCount");

          uint refState = obj.REF_STATE;
          if ((refState & countingONFlagMask) == 0) {
              return;
          }
          VTable.Assert(refState != 0,
                        @"refState != 0");

          obj.REF_STATE = refState+1;

          return refState;
      }


      [NoInline]
      [ManualRefCounts]
      [RequiredByBartok]
      internal static unsafe void NonNullDecrementRefCount(Object obj) {
          VTable.Assert(obj != null &&
                        (obj.REF_STATE & RSMasks.refCount) > 0,
                        @"obj != null &&
                        (obj.REF_STATE & RSMasks.refCount) > 0");

          uint refState = obj.REF_STATE;
          if ((refState & RSMasks.refCount) == 1) {
              VTable.Assert((refState & RSMasks.countingFlag) != 0,
                            @"(refState & RSMasks.countingFlag) != 0");

              MultiUseWord muw = MultiUseWord.GetForObject(obj);
              if (muw.IsMonitorOrInflatedTag()) {
                  MultiUseWord.RefCountGCDeadObjHook(muw);
              }

              // Set aside the object for a delayed deallocation.
              deallocateLazily(obj);
          } else if ((refState & RSMasks.countingFlag) == 0) {
              return;
          }

          obj.REF_STATE = refState-1;
      }

      [NoInline]
      [InlineIntoOnce]
      [ManualRefCounts]
      [RequiredByBartok]
      internal static unsafe void DecrementRefCount(Object obj) {
          if (obj == null) {
              return;
          }

          NonNullDecrementRefCount(obj);
      }


      [RequiredByBartok]
      internal static void IncrementReferentRefCounts(UIntPtr objAddr,
                                                      VTable vtable) {
          UpdateReferentRefCounts(objAddr, vtable, incrementer);
      }

      [RequiredByBartok]
      internal static void DecrementReferentRefCounts(UIntPtr objAddr,
                                                      VTable vtable) {
          UpdateReferentRefCounts(objAddr, vtable, decrementer);
      }

      [ManualRefCounts]
      internal static unsafe void IncrementReferentRefCounts
          (UIntPtr objAddr,
           VTable vtable,
           int start,
           int span) {
          UpdateReferentRefCounts(objAddr, vtable, start, span,
                                  incrementer);
      }

      [ManualRefCounts]
      internal static unsafe void DecrementReferentRefCounts
          (UIntPtr objAddr,
           VTable vtable,
           int start,
           int span) {
          UpdateReferentRefCounts(objAddr, vtable, start, span,
                                  decrementer);
      }


      [Inline]
      [ManualRefCounts]
      protected static void deallocateObjects() {
          deallocateObjects(decrementer);
      }

      [NoInline]
      [ManualRefCounts]
      protected static void purgeDeallocationList() {
          deallocateObjects(decrementer);
      }


      [ManualRefCounts]
      private static UIntPtr getDfsFinishingTime(Object obj) {
          return ((RCGCVerificationObject)obj).preHeader.
                 dfsFinishingTime;
      }

      [ManualRefCounts]
      private static void setDfsFinishingTime(Object obj,
                                              UIntPtr time) {
          ((RCGCVerificationObject)obj).preHeader.dfsFinishingTime =
              time;
      }


      private class RefCountIncrementer : NonNullReferenceVisitor {
          [ManualRefCounts]
          internal override unsafe void Visit(UIntPtr* loc) {
              UIntPtr objAddr = *loc;
              Object obj = Magic.fromAddress(objAddr);
              IncrementRefCount(obj);
          }
      }

      private class RefCountDecrementer : NonNullReferenceVisitor {
          [ManualRefCounts]
          internal override unsafe void Visit(UIntPtr* loc) {
              UIntPtr objAddr = *loc;
              Object obj = Magic.fromAddress(objAddr);
              DecrementRefCount(obj);
          }
      }


      private abstract class ObjectVisitor :
          SegregatedFreeList.ObjectVisitor {
          [ManualRefCounts]
          internal override void VisitSmall(Object obj,
                                            UIntPtr memAddr) {
              this.Visit(obj);
          }

          [ManualRefCounts]
          internal override UIntPtr VisitLarge(Object obj) {
              return this.Visit(obj);
          }

          internal abstract override UIntPtr Visit(Object obj);
      }


      private class BackupInitializer : ObjectVisitor {
          [ManualRefCounts]
          internal override UIntPtr Visit(Object obj) {
              setBackupRefcount(obj, UIntPtr.Zero);

              VTable vtable = obj.vtable;
              return ObjectLayout.
                     ObjectSize(Magic.addressOf(obj), vtable);
          }
      }

      private class BackupRefCount : NonNullReferenceVisitor {
          [ManualRefCounts]
          internal override unsafe void Visit(UIntPtr* loc) {
              UIntPtr addr = *loc;

              UIntPtr page = PageTable.Page(addr);
              if (!PageTable.IsGcPage(page)) {
                  PageType pageType = PageTable.Type(page);
                  VTable.Assert(pageType == PageType.NonGC ||
                                pageType == PageType.Stack,
                                @"pageType == PageType.NonGC ||
                                pageType == PageType.Stack");

                  return;
              }

              UIntPtr objAddr = SegregatedFreeList.Find(addr);
              incrementBackupRefCount.Traverse(objAddr);
          }
      }

      private class IncrementBackupRefCount : NonNullReferenceVisitor {
          [ManualRefCounts]
          internal void Traverse(UIntPtr objAddr) {
              Object obj = Magic.fromAddress(objAddr);
              UIntPtr count = getBackupRefcount(obj);
              setBackupRefcount(obj, count+1);
              if (obj.GcMark((UIntPtr)1)) {
                  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 addr = *loc;

              UIntPtr page = PageTable.Page(addr);
              if (!PageTable.IsGcPage(page)) {
                  PageType pageType = PageTable.Type(page);
                  VTable.Assert(pageType == PageType.NonGC ||
                                pageType == PageType.Stack,
                                @"pageType == PageType.NonGC ||
                                pageType == PageType.Stack");

                  return;
              }

              UIntPtr objAddr = SegregatedFreeList.Find(addr);
              Object obj = Magic.fromAddress(objAddr);
              UIntPtr count = getBackupRefcount(obj);
              setBackupRefcount(obj, count+1);
              if (obj.GcMark((UIntPtr)1)) {
                  this.workList.Write(objAddr);
              }
          }

          private UIntPtrQueue workList;
      }

      private class RootsScanner : NonNullReferenceVisitor {
          internal void Initialize(NonNullReferenceVisitor v) {
              this.visitor = v;
          }

          [ManualRefCounts]
          internal override unsafe void Visit(UIntPtr* loc) {
              UIntPtr pageLoc = PageTable.Page((UIntPtr)loc);
              PageType pageType = PageTable.Type(pageLoc);
              if (pageType != PageType.NonGC &&
                  pageType != PageType.Stack) {
                  VTable.Assert(PageTable.IsGcPage(pageLoc),
                                @"PageTable.IsGcPage(pageLoc)");

                  return;
              }

              uint addr = (uint)*loc;
              if (pageType == PageType.NonGC || (addr & 0x03) == 0) {
                  this.visitor.Visit(loc);
              }
              if (pageType == PageType.Stack) {
                  *loc = (UIntPtr)(addr | 0x01);
              }
          }

          NonNullReferenceVisitor visitor;
      }

      private class ResetRoots : NonNullReferenceVisitor {
          [ManualRefCounts]
          internal override unsafe void Visit(UIntPtr* loc) {
              UIntPtr pageLoc = PageTable.Page((UIntPtr)loc);
              PageType pageType = PageTable.Type(pageLoc);
              if (pageType != PageType.NonGC &&
                  pageType != PageType.Stack) {
                  VTable.Assert(PageTable.IsGcPage(pageLoc),
                                @"PageTable.IsGcPage(pageLoc)");

                  return;
              }

              if (pageType == PageType.Stack) {
                  *loc = (UIntPtr)((uint)*loc & 0xfffffffc);
              }
          }
      }

      private class LeakAccumulator : ObjectVisitor {
          internal UIntPtr Size;

          internal void Initialize() {
              this.Size = UIntPtr.Zero;
          }

          [ManualRefCounts]
          internal override unsafe UIntPtr Visit(Object obj) {
              VTable vtable = obj.vtable;
              UIntPtr size =
                  ObjectLayout.ObjectSize(Magic.addressOf(obj), vtable);

              uint refState = obj.REF_STATE;
              UIntPtr refCount = (UIntPtr)(refState & RSMasks.refCount);
              if ((refState & RSMasks.countingFlag) != 0 &&
                  refCount > 0) {
                  // This object is considered live by the
                  // RC collector.
                  UIntPtr count = getBackupRefcount(obj);
                  if (count == 0) {
                      // But it is actually unreachable.
                      this.Size += size;
                  }
              }

              return size;
          }
      }

      private class LeakedNodesDFS : ObjectVisitor {
          [ManualRefCounts]
          internal override unsafe UIntPtr Visit(Object obj) {
              VTable vtable = obj.vtable;
              UIntPtr size =
                  ObjectLayout.ObjectSize(Magic.addressOf(obj), vtable);

              uint refState = obj.REF_STATE;
              UIntPtr refCount = (UIntPtr)(refState & RSMasks.refCount);
              if ((refState & RSMasks.countingFlag) != 0 &&
                  refCount > 0) {
                  UIntPtr count = getBackupRefcount(obj);
                  if (count == 0) {
                      UIntPtr objAddr = Magic.addressOf(obj);
                      dfsMarker.Visit(&objAddr);
                  }
              }

              return size;
          }
      }

      private class DFSMarker : NonNullReferenceVisitor {
          internal void Initialize() {
              this.time = UIntPtr.Zero;
          }

          [ManualRefCounts]
          internal override unsafe void Visit(UIntPtr* loc) {
              UIntPtr objAddr = *loc;

              UIntPtr page = PageTable.Page(objAddr);
              if (!PageTable.IsGcPage(page)) {
                  PageType pageType = PageTable.Type(page);
                  VTable.Assert(pageType == PageType.NonGC ||
                                pageType == PageType.Stack,
                                @"pageType == PageType.NonGC ||
                                pageType == PageType.Stack");

                  return;
              }

              Object obj = Magic.fromAddress(objAddr);
              if (obj.GcMark((UIntPtr)1)) {
                  this.time = this.time+1;
                  setDfsDiscoveryTime(obj, this.time);

                  UIntPtr vtableAddr = Magic.addressOf(obj.vtable);
                  this.Visit(&vtableAddr);
                  this.VisitReferenceFields(obj);

                  this.time = this.time+1;
                  setDfsFinishingTime(obj, this.time);
              }
          }

          private UIntPtr time;
      }

      private class LeakedCycleClosure : ObjectVisitor {
          [ManualRefCounts]
          internal override unsafe UIntPtr Visit(Object obj) {
              VTable vtable = obj.vtable;
              UIntPtr size =
                  ObjectLayout.ObjectSize(Magic.addressOf(obj), vtable);

              uint refState = obj.REF_STATE;
              UIntPtr refCount = (UIntPtr)(refState & RSMasks.refCount);
              if ((refState & RSMasks.countingFlag) != 0 &&
                  refCount > 0) {
                  UIntPtr count = getBackupRefcount(obj);
                  if (count == 0) {
                      UIntPtr dTime = getDfsDiscoveryTime(obj);
                      UIntPtr fTime = getDfsFinishingTime(obj);
                      cycleClosure.Initialize(dTime, fTime);
                      cycleClosure.VisitReferenceFields(obj);
                  }
              }

              return size;
          }
      }

      private class CycleClosure : NonNullReferenceVisitor {
          internal void Initialize(UIntPtr dTime, UIntPtr fTime) {
              this.predDiscoveryTime = dTime;
              this.predFinishingTime = fTime;
          }

          [ManualRefCounts]
          internal override unsafe void Visit(UIntPtr* loc) {
              UIntPtr objAddr = *loc;

              UIntPtr page = PageTable.Page(objAddr);
              if (!PageTable.IsGcPage(page)) {
                  PageType pageType = PageTable.Type(page);
                  VTable.Assert(pageType == PageType.NonGC ||
                                pageType == PageType.Stack,
                                @"pageType == PageType.NonGC ||
                                pageType == PageType.Stack");

                  return;
              }

              Object obj = Magic.fromAddress(objAddr);
              UIntPtr dTime = getDfsDiscoveryTime(obj);
              UIntPtr fTime = getDfsFinishingTime(obj);
              VTable.Assert(this.predDiscoveryTime > UIntPtr.Zero &&
                            this.predFinishingTime > UIntPtr.Zero &&
                            dTime > UIntPtr.Zero &&
                            fTime > UIntPtr.Zero,
                            @"this.predDiscoveryTime > UIntPtr.Zero &&
                            this.predFinishingTime > UIntPtr.Zero &&
                            dTime > UIntPtr.Zero &&
                            fTime > UIntPtr.Zero");

              if (dTime < this.predDiscoveryTime &&
                  this.predDiscoveryTime < this.predFinishingTime &&
                  this.predFinishingTime < fTime) {
                  // A back edge is incident on this node;
                  // therefore, the node is part of a cycle.
                  backupRefCount.Visit(&objAddr);
              }
          }

          private UIntPtr predDiscoveryTime;
          private UIntPtr predFinishingTime;
      }

      private class ResetTraversal : ObjectVisitor {
          [ManualRefCounts]
          internal override unsafe UIntPtr Visit(Object obj) {
              obj.GcMark(UIntPtr.Zero);
              VTable vtable = obj.vtable;
              UIntPtr size =
                  ObjectLayout.ObjectSize(Magic.addressOf(obj), vtable);

              return size;
          }
      }

      private class BFSMarker : NonNullReferenceVisitor {
          internal void Initialize(bool isVisited) {
              this.isVisited = (UIntPtr)(isVisited ? 1 : 0);
          }

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

          [ManualRefCounts]
          internal override unsafe void Visit(UIntPtr* loc) {
              UIntPtr addr = *loc;

              UIntPtr page = PageTable.Page(addr);
              if (!PageTable.IsGcPage(page)) {
                  PageType pageType = PageTable.Type(page);
                  VTable.Assert(pageType == PageType.NonGC ||
                                pageType == PageType.Stack,
                                @"pageType == PageType.NonGC ||
                                pageType == PageType.Stack");

                  return;
              }

              UIntPtr objAddr = SegregatedFreeList.Find(addr);
              Object obj = Magic.fromAddress(objAddr);
              if (obj.GcMark(this.isVisited)) {
                  this.workList.Write(objAddr);
              }
          }

          private UIntPtr isVisited;
          private UIntPtrQueue workList;
      }

      private class LeakedRoots : ObjectVisitor {
          internal void Initialize() {
              bfsMarker.Initialize(true);
          }

          [ManualRefCounts]
          internal override unsafe UIntPtr Visit(Object obj) {
              VTable vtable = obj.vtable;
              UIntPtr size =
                  ObjectLayout.ObjectSize(Magic.addressOf(obj), vtable);

              uint refState = obj.REF_STATE;
              UIntPtr refCount = (UIntPtr)(refState & RSMasks.refCount);
              if ((refState & RSMasks.countingFlag) != 0 &&
                  refCount > 0) {
                  UIntPtr count = getBackupRefcount(obj);
                  if (count == 0 && obj.GcMark() == UIntPtr.Zero) {
                      bfsMarker.Traverse(obj);
                  }
              }

              return size;
          }
      }

      private class LeakedRootsCounter : ObjectVisitor {
          internal uint Total;

          internal void Initialize() {
              this.Total = 0;
          }

          [ManualRefCounts]
          internal override unsafe UIntPtr Visit(Object obj) {
              VTable vtable = obj.vtable;
              UIntPtr size =
                  ObjectLayout.ObjectSize(Magic.addressOf(obj),
                                          vtable);

              uint refState = obj.REF_STATE;
              UIntPtr refCount = (UIntPtr)(refState & RSMasks.refCount);
              if ((refState & RSMasks.countingFlag) != 0 &&
                  refCount > 0) {
                  UIntPtr count = getBackupRefcount(obj);
                  if (count == 0 && obj.GcMark() == UIntPtr.Zero) {
                      this.Total++;
                  }
              }

              return size;
          }
      }
  }
      private static NonNullReferenceVisitor incrementer;
      private static NonNullReferenceVisitor decrementer;

}


