//
// 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 the Bartok Depot and propagated to Singularity Depot.   */
/*******************************************************************/


namespace System {

  using Microsoft.Bartok.Runtime;
  using System.Threading;
  using System.Runtime.CompilerServices;
  using System.GCs;
#if SINGULARITY
  using Microsoft.Singularity;
#endif

  /// <summary>
  /// Finalizers are allocated for allocated objects that are finalizable.
  ///
  /// BUGBUG: During a garbage collection it should be possible to coalesce
  /// several FinalizerTable entries into a single entry. In order to
  /// facilitate this all indexes are currently allocated with reference to
  /// the first table. It should also be possible to compact the tables.
  /// </summary>
  [CCtorIsRunDuringStartup]
  [RequiredByBartok]
  internal struct Finalizer
  {
      // Objects are stored as their UIntPtr, asserted to have the low bit
      // clear.  Links in the free list are stored as the (index<<1 | 1).
      private static bool IsLink(int index)
      {
          return ((index & 1) != 0);
      }

      private static int LinkToIndex(int link)
      {
          VTable.Assert(IsLink(link));
          return (link >> 1);
      }

      private static int IndexToLink(int index)
      {
          return (index << 1) | 1;
      }

      /// <summary>
      /// Allocate a Finalizer entry for this object.
      /// </summary>
      [RequiredByBartok]
      internal static void RegisterCandidate(Object obj)
      {
#if !(REFERENCE_COUNTING_GC || DEFERRED_REFERENCE_COUNTING_GC)
          UIntPtr objPtr = Magic.addressOf(obj);
          UIntPtr page = PageTable.Page(objPtr);
          if (!PageTable.IsGcPage(page)) {
              return;
          }

          // It's tempting to do all manipulations of the CandidateTable with Interlocked
          // operations, but it would leave us open to the ABA problem.  For example,
          //
          //    candidateFreeList points to slot 5, which points to slot 7, and then 9.
          //    We start our CompareExchange loop based on these facts.
          //    Another thread intervenes.  It:
          //          Allocates slot 5 from the free list.
          //          Allocates slot 7 from the free list, so 9 is now at the top.
          //          It calls SuppressCandidate on #5, putting it back on the list,
          //                pointing at 9.
          //    Our thread performs the CompareExchange, removing 5 from the list &
          //          establishing 7 as the head.
          //
          // In order to avoid these (admittedly unlikely) corruptions, just use a
          // SpinLock to serialize all modifications of the CandidateTable.  But to
          // keep the SpinLock as a leaf lock and avoid deadlocks, ensure that we
          // never allocate or do anything else significant while holding the lock.

#if SINGULARITY
          bool disabled = Processor.DisableLocalPreemption();
#endif
          spinLock.Acquire();
          bool lockHeld = true;
          try {
restart:
              // Set index to point into the table at a free spot.
              int index = 0;
              UIntPtr[] table = null;

              // Grab from the free list.  Note that our free list can never be empty.
              // That's because it points either down a linked chain of previously allocated
              // and since free'd entries, or it points out past the high water mark.  We
              // may need to allocate memory to back that index, but the free list always
              // contains at least a virtual index.  And the slot that points past the
              // high water mark is always the last slot to be consumed from the free
              // list, so we grow only as a last resort.
              int logicalIndex = index = LinkToIndex(freeCandidateLink);

              // Relate that global index to a table and a local index.
              for (int i=0; i<CandidateTable.Length; i++) {
                  table = CandidateTable[i];
                  if (table == null) {
                      // Must be the first index into a new table.  We're going to
                      // create the new table, but we don't want to hold the spinLock
                      // during the create.  (We want the spinLock to be a leaf lock
                      // that cannot interact with any GC locks to cause deadlock).
                      VTable.Assert(index == 0);
                      lockHeld = false;
                      spinLock.Release();
#if SINGULARITY
                      Processor.RestoreLocalPreemption(disabled);
#endif
                      table = new UIntPtr[CandidateTable[i-1].Length*2];
#if SINGULARITY
                      disabled = Processor.DisableLocalPreemption();
#endif
                      spinLock.Acquire();
                      lockHeld = true;

                      // There is a race here.  If we lose the race, someone else will
                      // have extended the table.  If so, we use their official table and
                      // let the GC collect our extra one.  No need for interlocked
                      // operations since we are back inside the spinLock.
                      if (CandidateTable[i] == null) {
                          CandidateTable[i] = table;
                      }
                      // Regardless, the world has changed.
                      goto restart;
                  }

                  // Are we extending or are we chasing previously allocated empty slots?
                  if (index < table.Length) {
                      if (logicalIndex >= lastCandidateIndex) {
                          // we are extending past the high water mark.  Grow the free list
                          // ahead of us by 1 slot.
                          VTable.Assert(logicalIndex == lastCandidateIndex);
                          lastCandidateIndex = logicalIndex+1;
                          freeCandidateLink = IndexToLink(lastCandidateIndex);
                      } else {
                          // We are consuming the free list
                          freeCandidateLink = (int) table[index];
                          VTable.Assert(IsLink(freeCandidateLink));
                      }
                      break;
                  }
                  index -= table.Length;        // on to the next table
              }

              VTable.Assert(table != null, "Candidate Algorithmic inconsistency");

              // We have found the table!
              table[index] = Magic.addressOf(obj);

              // The following looks like a 64-bit porting issue (32-bit truncation).
              // But actually I'm just ensuring that object pointers have the low bit
              // cleared.
              VTable.Assert(!IsLink((int) objPtr));
          }
          finally {
              if (lockHeld) {
                  spinLock.Release();
#if SINGULARITY
                  Processor.RestoreLocalPreemption(disabled);
#endif
              }
          }
#endif // REFERENCE_COUNTING_GC
      }

      /// <summary>
      /// Loop through the candidate structure and zero a reference to
      /// this object.
      /// </summary>
      internal static void SuppressCandidate(Object obj)
      {
#if !(REFERENCE_COUNTING_GC || DEFERRED_REFERENCE_COUNTING_GC)
          // It's tempting to just hold the spinLock after we find this object and
          // link it into the freeList.  However, SuppressFinalize and ReRegisterForFinalize
          // could be called on multiple threads for the same object simultaneously.  So
          // we really need a lock around the whole thing.
          int logicalIndex = 0;
#if SINGULARITY
          bool disabled = Processor.DisableLocalPreemption();
#endif
          spinLock.Acquire();
          try {
              UIntPtr objPtr = Magic.addressOf(obj);
              for (int i=0; i < CandidateTable.Length; i++) {
                  if (CandidateTable[i] != null) {
                      for (int j=0; j < CandidateTable[i].Length; j++, logicalIndex++) {
                          if (CandidateTable[i][j] == objPtr) {
                              CandidateTable[i][j] = (UIntPtr) freeCandidateLink;
                              freeCandidateLink = IndexToLink(logicalIndex);
                              return;
                          }
                      }
                  }
              }
          }
          finally {
              spinLock.Release();
#if SINGULARITY
              Processor.RestoreLocalPreemption(disabled);
#endif
          }
#endif // REFERENCE_COUNTING_GC
      }

      /// <summary>
      /// Moves all candidates to the run queue.
      /// </summary>
      internal static void Shutdown()
      {
#if !(REFERENCE_COUNTING_GC || DEFERRED_REFERENCE_COUNTING_GC)
          if (running) {
              WaitForPending();
              running = false;

              // Wake up the finalizer thread, so that it will exit
              WorkExistsForFinalizerThread.Set();

              finalizerThread.Join();
          }

          // It looks like this stage of shutdown occurs when the app is single-threaded.
          // So we don't need the spinLock to protect against races if the app calls
          // SuppressFinalize or ReRegisterForFinalize on another thread.
          for (int i=0; i < CandidateTable.Length; i++) {
              if (CandidateTable[i] != null) {
                  for (int j=0; j < CandidateTable[i].Length; j++) {
                      UIntPtr candidate = CandidateTable[i][j];
                      if (candidate == UIntPtr.Zero) {
                          // The high water mark of the table.
                          return;
                      }
                      if (!IsLink((int) candidate)) {
                          try {
                              Object obj = Magic.fromAddress(candidate);
                              Magic.callFinalizer(obj);
                          } catch {
                              // Throw it away!
                          }
                      }
                  }
              }
          }
#endif // REFERENCE_COUNTING_GC
      }

      /// <summary>
      /// Wait until the finalization queue has been emptied.
      /// </summary>
      internal static void WaitForPending() {
#if !(REFERENCE_COUNTING_GC || DEFERRED_REFERENCE_COUNTING_GC)
          if (Thread.CurrentThread == Finalizer.finalizerThread) {
              return;
          }

          WaitForPendingShouldReturn.WaitOne();

#endif // REFERENCE_COUNTING_GC
      }

      /// <summary>
      /// BUGBUG: TODO
      /// </summary>
      internal static void CompactTables() {
      }

      [PreInitRefCounts]
      static unsafe Finalizer() {
#if !(REFERENCE_COUNTING_GC || DEFERRED_REFERENCE_COUNTING_GC)
          // There is a security issue here.  On 64-bit systems, a malicious SIP
          // could theoretically create more than 2 billion finalizable objects.
          // At that point, the use of 'int' in this file is unsafe, and could
          // allow multiple objects to share the same index.  However, it looks
          // like the candidate table, as allocated below, will support less than
          // 1/2 billion objects.  So we should be okay.
          CandidateTable = (UIntPtr[][])
              GCs.BootstrapMemory.Allocate(typeof(UIntPtr[][]), 24);

          CandidateTableShadow = (UIntPtr[][])
              GCs.BootstrapMemory.Allocate(typeof(UIntPtr[][]), 24);

          RunFinalizerTable = (UIntPtr[][])
              GCs.BootstrapMemory.Allocate(typeof(UIntPtr[][]), 24);

          RunFinalizerTableShadow = (UIntPtr[][])
              GCs.BootstrapMemory.Allocate(typeof(UIntPtr[][]), 24);

          CandidateTable[0] = (UIntPtr[])
              GCs.BootstrapMemory.Allocate(typeof(UIntPtr[]), 10);

          RunFinalizerTable[0] = (UIntPtr[])
              GCs.BootstrapMemory.Allocate(typeof(UIntPtr[]), 10);

          RunFinalizerTableShadow[0] = (UIntPtr[])
              GCs.BootstrapMemory.Allocate(typeof(UIntPtr[]), 10);

          lastCandidateIndex = 0;
          freeCandidateLink = IndexToLink(lastCandidateIndex);
#endif // REFERENCE_COUNTING_GC
      }


      /// <summary>
      /// Start the finalization thread.
      /// </summary>
      internal static void StartFinalizerThread() {
#if !(REFERENCE_COUNTING_GC || DEFERRED_REFERENCE_COUNTING_GC)
          if (GC.gcType == Microsoft.Bartok.Runtime.GCType.NullCollector) {
              return;
          }

// HACK: Threading issues are preventing use of a Finalizer thread.
//       Disabling for ARM so the GC can be used without it.
#if !OS_WINCE && !ISA_ARM
          WorkExistsForFinalizerThread = new AutoResetEvent(false);
          WaitForPendingShouldReturn = new ManualResetEvent(true);

#if SINGULARITY_KERNEL
          spinLock = new SpinLock(SpinLock.Types.Finalizer);
          finalizerThread = Thread.CreateThread(null,
                                                new ThreadStart(ThreadLoop));
          VTable.Assert(finalizerThread != null);
          running = true;
          finalizerThread.Start();
#elif SINGULARITY_PROCESS
          finalizerThread = new Thread(new ThreadStart(ThreadLoop));
          finalizerThread.SetIgnoredByJoinAll();
          running = true;
          finalizerThread.Start();
#else // !SINGULARITY
          finalizerThread = new Thread(new ThreadStart(ThreadLoop));
          running = true;
          finalizerThread.Start();
#endif // !SINGULARITY
#endif // REFERENCE_COUNTING_GC
#endif // !OS_WINCE
      }

      /// <summary>
      /// The thread loops doing this work forever.
      /// </summary>
      private static void ThreadLoop() {
#if !(REFERENCE_COUNTING_GC || DEFERRED_REFERENCE_COUNTING_GC)
          while(running) {
              if (WaitingToRun == 0) {
                  WaitForPendingShouldReturn.Set();
                  WorkExistsForFinalizerThread.WaitOne();
                  continue;
              }

              // Get some work
              Object current = GetObjectFromRun();
              VTable.Assert(current != null,
                            "No finalizer found, but WaitingToRun != 0");
              try {
                  Magic.callFinalizer(current);
              } catch {
                  // throw it away!
              }
              Interlocked.Decrement(ref WaitingToRun);
          }
          WaitForPendingShouldReturn.Set();

          // now that the finalizer thread has shutdown, we need to prevent
          // a GC from occuring
          Interlocked.Increment(ref GC.allocationGCInhibitCount);
#endif // REFERENCE_COUNTING_GC
      }

      /// <summary>
      /// This method must be called to prepare the Finalizer data
      /// structures for a collection.
      /// </summary>
      internal unsafe static void PrepareCollectFinalizers() {
#if !(REFERENCE_COUNTING_GC || DEFERRED_REFERENCE_COUNTING_GC)
          CompactTables();
          madeRunnable = false;

          for (int i=0; i < CandidateTable.Length; i++) {
              CandidateTableShadow[i] = CandidateTable[i];
          }
          for (int i=0; i < RunFinalizerTable.Length; i++) {
              RunFinalizerTableShadow[i] = RunFinalizerTable[i];
          }
#endif // REFERENCE_COUNTING_GC
      }

      /// <summary>
      /// Finish up after a collection.
      /// </summary>
      internal static void ReleaseCollectFinalizers() {
#if !(REFERENCE_COUNTING_GC || DEFERRED_REFERENCE_COUNTING_GC)
          if (madeRunnable) {
              // Review: For now we will assert against a GC running after the
              // finalization thread has shut down. However, if this assert
              // fires and the situation is somewhat reasonable, then we should
              // allow a GC to occur.
              VTable.Assert(running,
                  "Attempt to request finalizer while finalization thread is not running.");
              WaitForPendingShouldReturn.Reset();
              WorkExistsForFinalizerThread.Set();
          }
#endif // REFERENCE_COUNTING_GC
      }

      /// <summary>
      /// Ensure that any references from the Bootstrap space into the
      /// managed heap are traced during a collection avoiding dangling
      /// pointers.
      /// </summary>
      internal unsafe static
      void VisitBootstrapData(DirectReferenceVisitor visitor)
      {
#if !(REFERENCE_COUNTING_GC || DEFERRED_REFERENCE_COUNTING_GC)
          VisitAllRunFinalizer(visitor, true, false);

          // Trace other GC data. Being careful to NOT trace the Shadow
          visitor.VisitReferenceFields(CandidateTable);
          visitor.VisitReferenceFields(RunFinalizerTable);
          visitor.VisitReferenceFields(RunFinalizerTable[0]);
#endif // REFERENCE_COUNTING_GC
      }

      /// <summary>
      /// This method visits all the objects in the RunFinalizer structures.
      /// </summary>
      private unsafe static
      void VisitAllRunFinalizer(DirectReferenceVisitor visitor,
                                bool copyFirst, bool markedOnly)
      {
#if !(REFERENCE_COUNTING_GC || DEFERRED_REFERENCE_COUNTING_GC)
          // Visit all RunFinalizer objects.
          for (int i=0; i < RunFinalizerTableShadow.Length; i++) {
              UIntPtr[] table = copyFirst
                  ? RunFinalizerTable[i]
                  : RunFinalizerTableShadow[i];

              if (table != null) {
                  for (int j=0; j < table.Length; j++) {
                      fixed (UIntPtr *loc = &table[j]) {
                          if (*loc != UIntPtr.Zero) {
                              if (markedOnly) {
                                  if ((*loc & 1) == 1) {
                                      *loc = *loc & (~(UIntPtr)1);
                                  } else {
                                      continue;
                                  }
                              }
                              visitor.Visit(loc);
                          }
                      }
                  }
              }
          }
#endif // REFERENCE_COUNTING_GC
      }

      /// <summary>
      /// Find all candidates that have become unreachable.
      /// </summary>
      internal unsafe static
      void ResurrectCandidates(DirectReferenceVisitor forwardVisitor,
                               DirectReferenceVisitor resurrectVisitor,
                               bool copyFirst)
      {
#if !(REFERENCE_COUNTING_GC || DEFERRED_REFERENCE_COUNTING_GC)
          UIntPtr[] runTable = null;
          int runTableIndex = 0;
          int runIndex = 0;

          // For the concurrent collector, ResurrectCandidates could happen
          // while the application threads are calling SuppressFinalize and
          // ReRegisterForFinalize.  So we need to use the spinLock to prevent
          // races.  But we do not want to hold the spinLock while allocating
          // (i.e. when we grow the RunFinalizerTable[Shadow]) because our
          // spinLock is a leaf lock.  We don't want to worry about deadlocks
          // involving the spinLock and any locking that occurs as part of a
          // GC provoked by an allocation attempt.
#if SINGULARITY
          bool disabled = Processor.DisableLocalPreemption();
#endif
          spinLock.Acquire();
          bool lockHeld = true;
          try {
              int logicalIndex = 0;
              for (int i=0; i < CandidateTableShadow.Length; i++) {
                  UIntPtr[] table = copyFirst
                      ? CandidateTable[i]
                      : CandidateTableShadow[i];

                  if (table == null) {
                      VTable.Assert(logicalIndex == lastCandidateIndex);
                      break;
                  }
                  for (int j=0; j < table.Length; j++, logicalIndex++) {
                      if (table[j] == UIntPtr.Zero) {
                          VTable.Assert(logicalIndex == lastCandidateIndex);
                          break;
                      }
                      fixed (UIntPtr *loc = &table[j]) {
                          if (!IsLink((int) *loc)) {
                              UIntPtr oldVal = *loc;
                              forwardVisitor.Visit(loc);
                              if (*loc == UIntPtr.Zero) {
                                  // Put this slot onto the CandidateTable's free list
                                  *loc = (UIntPtr) freeCandidateLink;
                                  freeCandidateLink = IndexToLink(logicalIndex);

                                  // marching forward through the RunFinalizer[Shadow] table, find an
                                  // empty slot to install this object.  Maintain the cursor across
                                  // objects, so we can efficiently transfer an entire batch.

                                  for (; runTableIndex < RunFinalizerTableShadow.Length; runTableIndex++) {
                                      runTable = copyFirst
                                          ? RunFinalizerTable[runTableIndex]
                                          : RunFinalizerTableShadow[runTableIndex];

                                      if (runTable == null) {
                                          // Create a new table
                                          int length = RunFinalizerTableShadow[runTableIndex-1].Length*2;

                                          lockHeld = false;
                                          spinLock.Release();
#if SINGULARITY
                                          Processor.RestoreLocalPreemption(disabled);
#endif
                                          UIntPtr[] newTable = new UIntPtr[length];
#if SINGULARITY
                                          disabled = Processor.DisableLocalPreemption();
#endif
                                          spinLock.Acquire();
                                          lockHeld = true;

                                          // There is no race with the RunFinalizerTable[Shadow].
                                          // The spinLock serializes access to the CandidateTable[Shadow].
                                          runTable = newTable;
                                          RunFinalizerTable[runTableIndex] = newTable;
                                          RunFinalizerTableShadow[runTableIndex] = newTable;
                                          UIntPtr tableAddr = Magic.addressOf(newTable);
                                          resurrectVisitor.Visit(&tableAddr);
                                          resurrectVisitor.VisitReferenceFields(RunFinalizerTable[runTableIndex]);
                                          resurrectVisitor.VisitReferenceFields(RunFinalizerTableShadow[runTableIndex]);
                                      }

                                      for (; runIndex < runTable.Length; runIndex++) {
                                          if (runTable[runIndex] == UIntPtr.Zero) {
                                              goto outer;
                                          }
                                      }
                                      runIndex -= runTable.Length;
                                      VTable.Assert(runIndex == 0);     // ready for next sub-table
                                  }
outer:
                                  // We found an empty slot in the RunFinalizerTable[Shadow],
                                  // where we can put our ready Candidate.  It's also possible
                                  // to reach this label by falling through after exhausting the
                                  // entire table.  This will result in an exception when we
                                  // attempt to over-index the array.  It's not clear what more
                                  // protections are required... the process has exceeded an
                                  // unlikely and hard-wired capacity limit.
                                  Interlocked.Increment(ref WaitingToRun);
                                  madeRunnable = true;

                                  if (copyFirst) {
                                      RunFinalizerTable[runTableIndex][runIndex]
                                          = oldVal | new UIntPtr(1);
                                  } else {
                                      RunFinalizerTableShadow[runTableIndex][runIndex]
                                          = oldVal | new UIntPtr(1);
                                  }
                              }
                          }
                      }
                  }
              }
          }
          finally {
              if (lockHeld) {
                  spinLock.Release();
#if SINGULARITY
                  Processor.RestoreLocalPreemption(disabled);
#endif
              }
          }

          if (madeRunnable) {
              // Resurrect objects!
              VisitAllRunFinalizer(resurrectVisitor, copyFirst, true);
          }
#endif // REFERENCE_COUNTING_GC
      }

      /// <summary>
      /// Find and remove an object from the to be run list.  Remembers where
      /// the last object was found as a starting point for finding the next
      /// object.
      /// </summary>
      internal static Object GetObjectFromRun() {
#if (REFERENCE_COUNTING_GC || DEFERRED_REFERENCE_COUNTING_GC)
          return null;
#else
          bool first = true;
          int entryStart = Finalizer.LastEntryRun;

          for (int i = Finalizer.LastTableRun; ; i++) {
              if (i == RunFinalizerTableShadow.Length) {
                  i = 0;
              }

              UIntPtr[] table = RunFinalizerTable[i];

              if (table == null) {
                  i = 0;
                  table = RunFinalizerTable[0];
                  VTable.Assert(table != null, "lost RunFinalizerTable[0]");
              }

              for (int j = entryStart; j < table.Length; j++) {
                  if (table[j] != UIntPtr.Zero) {
                      Object obj = Magic.fromAddress(table[j] & ~(UIntPtr)1);
                      table[j] = UIntPtr.Zero;
                      Finalizer.LastTableRun = i;
                      Finalizer.LastEntryRun = j;
                      return obj;
                  }
                  if ((i == Finalizer.LastTableRun)
                      && (j == Finalizer.LastEntryRun)) {
                      if (first) {
                          // Triggers the very first time through the loops.
                          entryStart = 0;
                          first = false;
                      } else {
                          // Triggers after going around all of the tables.
                          return null;
                      }
                  }
              }
          }
#endif // REFERENCE_COUNTING_GC
      }

      /// <summary>
      /// The thread that runs finalizers.
      /// </summary>
      private static Thread finalizerThread;

      /// <summary>
      /// Did the current collection make any finalizers runnable?
      /// </summary>
      private static bool madeRunnable;

      /// <summary>
      /// Is the finalization thread running?
      /// </summary>
      private static bool running;

      /// <summary>
      /// The table of Finalization Candidates.
      /// </summary>
      private static UIntPtr[][] CandidateTable;

      /// <summary>
      /// A shadow of the candidates table (hidden from GC).
      /// </summary>
      private static UIntPtr[][] CandidateTableShadow;

      /// <summary>
      /// The table of objects to have finalizers run Candidates.
      /// </summary>
      private static UIntPtr[][] RunFinalizerTable;

      /// <summary>
      /// Controls sleeping on the finalizer thread.  The GC signals the
      /// finalizer thread when there is work to do.  The finalizer thread
      /// sleeps when there is no work remaining.  The use of an event prevents
      /// the GC from being blocked.
      /// </summary>
      private static AutoResetEvent WorkExistsForFinalizerThread;

      /// <summary>
      /// Controls sleeping in calls to WaitForPendingFinalizers.  The
      /// finalizer thread signals when work is complete.  The GC resets the
      /// signal when there is more work.  The use of an event should prevent
      /// deadlocks.
      /// </summary>
      private static ManualResetEvent WaitForPendingShouldReturn;

      /// <summary>
      /// A shadow of the run finalizer table (hidden from GC).
      /// </summary>
      private static UIntPtr[][] RunFinalizerTableShadow;

      /// <summary>
      /// This is one more than the highest index in use.
      /// </summary>
      private static int lastCandidateIndex;

      /// <summary>
      /// This is a link into the free list of entries in the CandidateTable.
      /// </summary>
      private static int freeCandidateLink;

      /// <summary>
      /// SpinLock to serialize modifications to the CandidateTable.
      /// </summary>
      private static SpinLock spinLock;

      /// <summary>
      /// This is the index of the table from which the last finalizer was run.
      /// Used as an optimization in finding the next finalizer to run.
      /// </summary>
      private static int LastTableRun;

      /// <summary>
      /// This is the index of the finalizer (in LastTableRun) that was last
      /// run.  Used as an optimization in finding the next finalizer to run.
      /// </summary>
      private static int LastEntryRun;

      /// <summary>
      /// The number of finalizers waiting to run
      /// </summary>
      private static int WaitingToRun;

  }
}
