//
// 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 abstract class RCCollector : Collector {
      [PreInitRefCounts]
      public static void Initialize() {
          SegregatedFreeList.Initialize();
      }


      // This is a compiler intrinsic whose value is controlled by
      // /StageControl.RCCollectorVerifyRefCounts.
      internal static extern bool VerificationMode {
          [Intrinsic]
          [MethodImplAttribute(MethodImplOptions.InternalCall)]
          get;
      }

      // This is a compiler intrinsic whose value is controlled by
      // /StageControl.RCCollectorVerifyLeakedCycles.
      internal static extern bool VerifyLeakedCycles {
          [Intrinsic]
          [MethodImplAttribute(MethodImplOptions.InternalCall)]
          get;
      }

      // This is a compiler intrinsic whose value is controlled by
      // /StageControl.RCCollectorGenerateProfile.
      internal static extern bool ProfilingMode {
          [Intrinsic]
          [MethodImplAttribute(MethodImplOptions.InternalCall)]
          get;
      }

      [MixinConditional("RCGC")]
      [Mixin(typeof(PostHeader))]
      [StructLayout(LayoutKind.Sequential)]
      [RequiredByBartok]
      internal struct PostHeaderRCGC {
          [CompilerInitField(2)]
          internal uint refState;
#if !SINGULARITY
          [RequiredByBartok]
#endif
          internal VTable vtableObject;
      }

      [MixinConditional("RCGC")]
      [Mixin(typeof(Object))]
      internal class RCGCObject : System.Object {
          internal new PostHeaderRCGC postHeader;

          internal new uint REF_STATE {
              [Inline]
              [ManualRefCounts]
              [MixinOverride]
              get {
                  return this.postHeader.refState;
              }
              [Inline]
              [ManualRefCounts]
              [MixinOverride]
              set {
                  this.postHeader.refState = value;
              }
          }
      }

      [MixinConditional("RCGCVerification")]
      [Mixin(typeof(PreHeader))]
      [RequiredByBartok]
      internal struct PreHeaderRCGCVerification {
          internal UIntPtr backupRefcount;
          internal UIntPtr dfsDiscoveryTime;
          internal UIntPtr dfsFinishingTime;
      }

      [MixinConditional("RCGCVerification")]
      [Mixin(typeof(Object))]
      internal class RCGCVerificationObject : System.Object {
          internal new PreHeaderRCGCVerification preHeader;
      }

      /*
       * Every object maintains a 32-bit "reference state" (RS).
       * The RS consists of a 29-bit reference count, with the
       * remaining bits available for flagging purposes.
       *
       * In a 32-bit architecture, 4GB of memory is addressable.
       * Of this, the upper 2GB is usually reserved for use by
       * the system. So given an object size of at least
       * 12 bytes (sync block index, RS field and the vtable),
       * there can't be more than 2^28 objects. The reference
       * count can be more, due to multiple references from an
       * object to the same target (think of one large array of
       * references), but will be less than 2^29.
       *
       * Of the remaining three bits, one is reserved to ignore
       * reference-counting (RC) operations on objects.
       */

      internal struct RSMasks {
          internal const uint refCount =                0x1fffffff;

          internal const uint countingFlag =            0x80000000;
      }

      // Used only in profiling mode.
      internal struct UpdatePairs {
          public int Increments;
          public int Decrements;

          public static UpdatePairs operator+(UpdatePairs a,
                                              UpdatePairs b) {
              a.Increments += b.Increments;
              a.Decrements += b.Decrements;

              return a;
          }

          public int Total {
              get {
                  return Increments+Decrements;
              }
          }
      } // Increment and decrement counts of one kind of RC update.

      internal struct AcctRecord {
          // General RC update, on a "maybe-null" reference.
          public UpdatePairs MaybeNull;
          // General RC update, but on a non-null reference.
          public UpdatePairs NonNull;

          public UpdatePairs VtableOperand;
          public UpdatePairs RuntimeTypeOperand;

          public String MethodName;

          public static AcctRecord operator+(AcctRecord a,
                                             AcctRecord b) {
              VTable.Assert(Magic.addressOf(a.MethodName) ==
                            Magic.addressOf(b.MethodName),
                            @"Magic.addressOf(a.MethodName) ==
                            Magic.addressOf(b.MethodName)");

              a.MaybeNull += b.MaybeNull;
              a.NonNull += b.NonNull;

              a.VtableOperand += b.VtableOperand;
              a.RuntimeTypeOperand += b.RuntimeTypeOperand;

              return a;
          }

          public int CompareTo(AcctRecord rhs) {
              int lhsTotal = MaybeNull.Total+NonNull.Total;
              int rhsTotal = rhs.MaybeNull.Total+rhs.NonNull.Total;

              return lhsTotal < rhsTotal;
          }

          public void DispCountsHeader() {
              VTable.DebugPrint("GIncs\t\tGDecs");
              VTable.DebugPrint("\t\tNIncs\t\tNDecs");
              VTable.DebugPrint("\t\tV+\t\tV-");
              VTable.DebugPrint("\t\tR+\t\tR-");
          }

          public void DispMethodNameHeader() {
              VTable.DebugPrint("\t\tMethod");
          }

          public void DispCounts() {
              VTable.DebugPrint(MaybeNull.Increments);
              if (MaybeNull.Increments < 10000000) {
                  VTable.DebugPrint("\t\t");
              } else {
                  VTable.DebugPrint("\t");
              }
              VTable.DebugPrint(MaybeNull.Decrements);
              if (MaybeNull.Decrements < 10000000) {
                  VTable.DebugPrint("\t\t");
              } else {
                  VTable.DebugPrint("\t");
              }
              VTable.DebugPrint(NonNull.Increments);
              if (NonNull.Increments < 10000000) {
                  VTable.DebugPrint("\t\t");
              } else {
                  VTable.DebugPrint("\t");
              }
              VTable.DebugPrint(NonNull.Decrements);
              if (NonNull.Decrements < 10000000) {
                  VTable.DebugPrint("\t\t");
              } else {
                  VTable.DebugPrint("\t");
              }
          }

          public void DispMethodName() {
              VTable.DebugPrint(MethodName);
          }

          public void Disp() {
              DispCounts();
              DispMethodName();
          }
      }


      internal override long TotalMemory {
          get {
              UIntPtr pageCount = UIntPtr.Zero;
              for (UIntPtr i = UIntPtr.Zero;
                   i < PageTable.pageTableCount; i++) {
                  if (PageTable.IsMyGcPage(i) &&
                      PageTable.Type(i) !=
                      SegregatedFreeList.INIT_PAGE) {
                      pageCount++;
                  }
              }
              return (long)PageTable.RegionSize(pageCount);
          }
      }


      internal override UIntPtr FindObjectAddr(UIntPtr interiorPtr) {
          return SegregatedFreeList.Find(interiorPtr);
      }

      internal override void VisitObjects
          (ObjectLayout.ObjectVisitor objVisitor,
           UIntPtr lowAddr,
           UIntPtr highAddr) {
          UIntPtr lowPage = PageTable.Page(lowAddr);
          UIntPtr highPage = PageTable.Page(highAddr);
          SegregatedFreeList.VisitObjects(lowPage,
                                          highPage,
                                          objVisitor);
      }


      [NoInline]
      [ManualRefCounts]
      [RequiredByBartok]
      internal static unsafe bool AccumulateRCUpdates(String methodName,
                                                      int methodIndex,
                                                      uint maxIndex,
                                                      AcctRecord rec) {
          VTable.Assert(RCCollector.ProfilingMode,
                        @"RCCollector.ProfilingMode");

          // Return if the page table hasn't been set up yet.
          if (PageTable.pageTableCount == UIntPtr.Zero) {
              return false;
          }

          if (methods == null) {
              // Allocate up front storage for the accounting records.
              //
              // This is requisitioned directly from the memory
              // manager. Care should be taken to ensure that
              // AccumulateRCUpdates does not indirectly call
              // methods that may have compiler-inserted RC updates.
              VTable vtable =
                  ((RuntimeType)typeof(AcctRecord[])).classVtable;
              UIntPtr size =
                  ObjectLayout.ArraySize(vtable, maxIndex+1);

              BumpAllocator profileData =
                  new BumpAllocator(PageType.NonGC);
              UIntPtr profileDataStart =
                  MemoryManager.AllocateMemory(size);
              profileData.SetRange(profileDataStart, size);
              PageManager.SetStaticDataPages(profileDataStart, size);

              methods =
                  (AcctRecord[])Allocate(ref profileData, vtable, size);
              VTable.Assert(methods != null,
                            @"methods != null");

              *(uint*)(Magic.addressOf(methods)+
                  PostHeader.Size) = maxIndex+1;
          }

          VTable.Assert(methods.Length == maxIndex+1,
                        @"methods.Length == maxIndex+1");

          if (methods[methodIndex].methodName == null) {
              methodNames[methodIndex].methodName = methodName;
          }
          // Not "methodNames[methodIndex].methodName == methodName"
          // because the Equality operator carries compiler-inserted
          // RC updates!
          VTable.Assert(Magic.addressOf(methodNames[methodIndex].
                                        methodName) ==
                        Magic.addressOf(methodName),
                        @"Magic.addressOf(methodNames[methodIndex].
                                          methodName) ==
                        Magic.addressOf(methodName)");

          methods[methodIndex] += rec;

          return true;
      }

      [NoInline]
      [ManualRefCounts]
      internal static void EmitRefCountsProfile() {
          VTable.Assert(RCCollector.ProfilingMode,
                        @"RCCollector.ProfilingMode");

          if (methods == null) { // No RC updates present.
              return;
          }

          // Bubble sort in decreasing order of sums.
          for (int i = 0; i < methods.Length; i++) {
              for (int j = methods.Length-1; j > i; j--) {
                  if (methods[j].CompareTo(methods[j-1])) {
                      // Swap contents.
                      AcctRecord temp = methods[j];
                      methods[j] = methods[j-1];
                      methods[j-1] = temp;
                  }
              }
          }

          VTable.DebugPrint("\n");
          AcctRecord.DispCountsHeader();
          AcctRecord.DispMethodNameHeader();
          VTable.DebugPrint("\n");
          for (int i = 0; i < methods.Length; i++) {
              if (methods[i].increments.Total == 0 &&
                  methods[i].decrements.Total == 0) {
                  continue;
              }
              methods[i].DispCounts();
              methods[i].DispMethodName();
              VTable.DebugPrint("\n");
          }
      }


      private static Object Allocate(ref BumpAllocator profileData,
                                     VTable vtable,
                                     UIntPtr numBytes) {
          UIntPtr resultAddr =
              profileData.AllocateFast(numBytes, vtable.baseAlignment);
          Object result = Magic.fromAddress(resultAddr);
            result.REF_STATE = 1 ;
          result.vtable = vtable;
          return result;
      }

      private static AcctRecord[] methods;
  }
}
