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

    internal abstract class ReferenceVisitor {

        internal struct ObjectDescriptor {
            [ManualRefCounts]
            [Inline]
            internal ObjectDescriptor(VTable vtable, UIntPtr objectBase) :
                this(vtable, objectBase, UIntPtr.Zero, UIntPtr.Zero,
                     null, null)
            {
            }

            [ManualRefCounts]
            [Inline]
            internal ObjectDescriptor(VTable vtable, UIntPtr objectBase,
                                      UIntPtr secondBase) :
                this(vtable, objectBase, secondBase, UIntPtr.Zero, null, null)
            {
            }

            [ManualRefCounts]
            [Inline]
            internal ObjectDescriptor(VTable vtable,
                                      UIntPtr objectBase,
                                      UIntPtr secondBase,
                                      UIntPtr extra) :
                this(vtable, objectBase, secondBase, extra, null, null)
            {}

            [ManualRefCounts]
            [Inline]
            internal ObjectDescriptor(VTable vtable,
                                      UIntPtr objectBase,
                                      UIntPtr secondBase,
                                      UIntPtr extra,
                                      Object realObjectBase,
                                      Object realSecondBase)
            {
                this.vtable = vtable;
                this.objectBase = objectBase;
                this.secondBase = secondBase;
                this.extra = extra;
                this.realObjectBase = realObjectBase;
                this.realSecondBase = realSecondBase;
            }

            // this struct is never in the heap, so the fields should
            // be accessed the quick way.  (and no, this struct isn't
            // always accessed through a local var - it might be accessed
            // via a managed pointer in the NoInline reference fields 
            // visitor methods.)
            
            [NoBarriers]
            internal new VTable vtable;
            [NoBarriers]
            internal UIntPtr objectBase;
            [NoBarriers]
            internal UIntPtr secondBase;
            [NoBarriers]
            internal UIntPtr extra;
            [NoBarriers]
            internal Object realObjectBase;
            [NoBarriers]
            internal Object realSecondBase;
        }

        // Rationale for cutting the fact that no stack probes are permitted in this
        // tree of calls:
        //
        // There is no rationale.  It is a bug which is tracked with all the cases that
        // require fixing.
        //
        // Bug 436
        [NoStackLinkCheckTransCut]
        [ManualRefCounts]
        [Inline]
        internal virtual UIntPtr VisitReferenceFields(Object obj)
        {
            return this.VisitReferenceFields(Magic.addressOf(obj),
                                             obj.vtable);
        }

        [Inline]
        internal abstract UIntPtr VisitReferenceFields(UIntPtr objectBase,
                                                       VTable vtable);

        [Inline]
        protected abstract unsafe
        void Filter(UIntPtr *location, ref ObjectDescriptor objDesc);

        // Rationale for cutting the fact that no stack probes are permitted in this
        // tree of calls:
        //
        // There is no rationale.  This is a bug which has been filed.  It is a temporary
        // hack to break the recursion in this method.  Recursion needs to open up an
        // opportunity for stack probing.  Eventually, perhaps this method can be
        // rewritten to avoid the recursion.
        //
        // Bug 436
        [NoStackLinkCheckTransCut]
        [ManualRefCounts]
        [NoInline]
        protected unsafe
        UIntPtr VisitReferenceFieldsTemplateNoInline(ref ObjectDescriptor objDesc)
        {
            return VisitReferenceFieldsTemplate(ref objDesc);
        }

        [ManualRefCounts]
        [Inline]
        protected unsafe
        UIntPtr VisitReferenceFieldsTemplate(ref ObjectDescriptor objDesc)
        {
            UIntPtr pointerTracking = objDesc.vtable.pointerTrackingMask;
            uint objectTag = (pointerTracking & 0xf);
            UIntPtr size;
            switch (objectTag) {
              case ObjectLayout.SPARSE_TAG: {
                  UIntPtr *sparseObject = (UIntPtr *) objDesc.objectBase;
                  size = ObjectLayout.ObjectSize(objDesc.vtable);
                  pointerTracking >>= 4;
                  while (pointerTracking != 0) {
                      uint index = pointerTracking & 0xf;
                      pointerTracking >>= 4;
                      // The cast to int prevents C# from taking the
                      // index * sizeof(UIntPtr) to long:
                      UIntPtr *loc = sparseObject + (int) index;
                      this.Filter(loc, ref objDesc);
                  }
                  break;
              }
              case ObjectLayout.DENSE_TAG: {
                  // skip vtable
                  int postHeaderSize = PostHeader.Size;
                  UIntPtr *denseObject = (UIntPtr *)
                      (objDesc.objectBase + postHeaderSize);
                  size = ObjectLayout.ObjectSize(objDesc.vtable);
                  pointerTracking >>= 4;
                  while (pointerTracking != 0) {
                      if ((pointerTracking & ((UIntPtr)0x1)) != 0) {
                          this.Filter(denseObject, ref objDesc);
                      }
                      pointerTracking >>= 1;
                      denseObject++;
                  }
                  break;
              }
              case ObjectLayout.PTR_VECTOR_TAG: {
                  int postHeaderSize = PostHeader.Size;
                  uint length = *(uint*)(objDesc.objectBase + postHeaderSize);
                  size = ObjectLayout.ArraySize(objDesc.vtable, length);
                  int preHeaderSize = PreHeader.Size;
                  UIntPtr *elementAddress = (UIntPtr *)
                      (objDesc.objectBase + objDesc.vtable.baseLength -
                       preHeaderSize);
                  for (uint i = 0; i < length; i++, elementAddress++) {
                      this.Filter(elementAddress, ref objDesc);
                  }
                  break;
              }
              case ObjectLayout.OTHER_VECTOR_TAG: {
                  int postHeaderSize = PostHeader.Size;
                  uint length = *(uint*)(objDesc.objectBase + postHeaderSize);
                  size = ObjectLayout.ArraySize(objDesc.vtable, length);
                  if (objDesc.vtable.arrayOf == StructuralType.Struct) {
                      // pretend the struct is boxed and account for the
                      // presence of the vtable field
                      VTable elementVTable = objDesc.vtable.arrayElementClass;
                      UIntPtr elementMask = elementVTable.pointerTrackingMask;
                      // A structure with no references will have a SPARSE
                      // descriptor with no offset values.
                      if (elementMask != (UIntPtr) ObjectLayout.SPARSE_TAG) {
                          int preHeaderSize = PreHeader.Size;
                          UIntPtr elementAddress = (objDesc.objectBase +
                                                    objDesc.vtable.baseLength -
                                                    preHeaderSize -
                                                    postHeaderSize);
                          int elementSize = objDesc.vtable.arrayElementSize;
                          objDesc.vtable = elementVTable;
                          for (uint i = 0; i < length; i++) {
                              objDesc.objectBase = elementAddress;
                              this.VisitReferenceFieldsTemplateNoInline(ref objDesc);
                              elementAddress += elementSize;
                          }
                      }
                  }
                  break;
              }
              case ObjectLayout.PTR_ARRAY_TAG: {
                  int postHeaderSize = PostHeader.Size;
                  uint length = *(uint*)(objDesc.objectBase + postHeaderSize +
                                         sizeof(uint));
                  size = ObjectLayout.ArraySize(objDesc.vtable, length);
                  int preHeaderSize = PreHeader.Size;
                  UIntPtr *elementAddress = (UIntPtr *)
                      (objDesc.objectBase + objDesc.vtable.baseLength -
                       preHeaderSize);
                  for (uint i = 0; i < length; i++, elementAddress++) {
                      this.Filter(elementAddress, ref objDesc);
                  }
                  break;
              }
              case ObjectLayout.OTHER_ARRAY_TAG: {
                  int postHeaderSize = PostHeader.Size;
                  uint length = *(uint*)(objDesc.objectBase + postHeaderSize +
                                         sizeof(uint));
                  size = ObjectLayout.ArraySize(objDesc.vtable, length);
                  if (objDesc.vtable.arrayOf == StructuralType.Struct) {
                      // pretend the struct is boxed and account for the
                      // presence of the PostHeader
                      VTable elementVTable = objDesc.vtable.arrayElementClass;
                      UIntPtr elementMask = elementVTable.pointerTrackingMask;
                      // A structure with no references will have a SPARSE
                      // descriptor with no offset values.
                      if (elementMask != (UIntPtr) ObjectLayout.SPARSE_TAG) {
                          int preHeaderSize = PreHeader.Size;
                          int elementSize = objDesc.vtable.arrayElementSize;
                          UIntPtr elementAddress =
                              objDesc.objectBase + objDesc.vtable.baseLength -
                              preHeaderSize - postHeaderSize;
                          objDesc.vtable = elementVTable;
                          for (uint i = 0; i < length; i++) {
                              objDesc.objectBase = elementAddress;
                              this.VisitReferenceFieldsTemplateNoInline(ref objDesc);
                              elementAddress += elementSize;
                          }
                      }
                  }
                  break;
              }
              case ObjectLayout.STRING_TAG: {
                  int postHeaderSize = PostHeader.Size;
                  uint arrayLength =
                      *(uint*)(objDesc.objectBase + postHeaderSize);
                  size = ObjectLayout.StringSize(objDesc.vtable, arrayLength);
                  break;
              }
              default: {
                  // escape case
                  VTable.Assert((objectTag & 0x1) == 0,
                                "ReferenceVisitor: (objectTag & 0x1) == 0");
                  UIntPtr *largeObject = (UIntPtr *) objDesc.objectBase;
                  size = ObjectLayout.ObjectSize(objDesc.vtable);
                  int *pointerDescription = (int *) pointerTracking;
                  int count = *pointerDescription;
                  for (int i = 1; i <= count; i++) {
                      UIntPtr *loc = largeObject + *(pointerDescription+i);
                      this.Filter(loc, ref objDesc);
                  }
                  break;
              }
            }
            return size;
        }

        // Rationale for cutting the fact that no stack probes are permitted in this
        // tree of calls:
        //
        // There is no rationale.  This is a bug which has been filed.  It is a temporary
        // hack to break the recursion in this method.  Recursion needs to open up an
        // opportunity for stack probing.  Eventually, perhaps this method can be
        // rewritten to avoid the recursion.
        //
        // Bug 436
        [NoStackLinkCheckTransCut]
        [ManualRefCounts]
        protected unsafe
        void VisitReferenceFieldsTemplate(ref ObjectDescriptor objDesc,
                                          int count)
        {
            UIntPtr pointerTracking = objDesc.vtable.pointerTrackingMask;
            uint objectTag = (pointerTracking & 0xf);
            switch (objectTag) {
              case ObjectLayout.PTR_VECTOR_TAG:
              case ObjectLayout.PTR_ARRAY_TAG: {
                  UIntPtr *elementAddress = (UIntPtr *) objDesc.objectBase;
                  for (int i = 0; i < count; i++, elementAddress++) {
                      this.Filter(elementAddress, ref objDesc);
                  }
                  break;
              }
              case ObjectLayout.OTHER_VECTOR_TAG:
              case ObjectLayout.OTHER_ARRAY_TAG: {
                  if (objDesc.vtable.arrayOf == StructuralType.Struct) {
                      // pretend the struct is boxed and account for the
                      // presence of the vtable field
                      VTable elementVTable = objDesc.vtable.arrayElementClass;
                      UIntPtr elementMask = elementVTable.pointerTrackingMask;
                      // A structure with no references will have a SPARSE
                      // descriptor with no offset values.
                      if (elementMask != (UIntPtr) ObjectLayout.SPARSE_TAG) {
                          int postHeaderSize = PostHeader.Size;
                          objDesc.objectBase -= postHeaderSize;
                          objDesc.secondBase -= postHeaderSize;
                          objDesc.extra += postHeaderSize;
                          int elementSize = objDesc.vtable.arrayElementSize;
                          objDesc.vtable = elementVTable;
                          for (int i = 0; i < count; i++) {
                              this.VisitReferenceFieldsTemplateNoInline(ref objDesc);
                              objDesc.objectBase += elementSize;
                              objDesc.secondBase += elementSize;
                              objDesc.extra -= elementSize;
                          }
                          objDesc.objectBase += postHeaderSize;
                          objDesc.secondBase += postHeaderSize;
                          objDesc.extra -= postHeaderSize;
                      }
                  }
                  break;
              }
              default: {
                  throw new Exception("Indexing non-indexed type");
              }
            }
        }

    }

    internal abstract class DirectReferenceVisitor : ReferenceVisitor
    {

        [Inline]
        internal abstract unsafe void Visit(UIntPtr *location);

    }

    // This visitor should be used in concurrent settings.
    internal abstract class MutableReferenceVisitor : DirectReferenceVisitor
    {

#region HELP_DEVIRT
        // TODO: Evaluate the performance impact of these [Inline] annotations,
        // which appeared as part of the CoCo code dump.

        // This method simply forces the compiler to generate a copy
        // of VisitReferenceFieldsTemplate in this class.
        [Inline]
        [ManualRefCounts]
        internal override UIntPtr VisitReferenceFields(Object obj)
        {
            return this.VisitReferenceFields(Magic.addressOf(obj),
                                             obj.vtable);
        }

        // This method simply forces the compiler to generate a copy
        // of VisitReferenceFieldsTemplate in this class.
        [Inline]
        [ManualRefCounts]
        internal override UIntPtr VisitReferenceFields(UIntPtr objectBase,
                                                       VTable vtable)
        {
            ObjectDescriptor objDesc =
                new ObjectDescriptor(vtable, objectBase);
            return VisitReferenceFieldsTemplate(ref objDesc);
        }
#endregion

        [Inline]
        protected override unsafe
        void Filter(UIntPtr *location, ref ObjectDescriptor objDesc)
        {
            this.Visit(location);
        }

    }

    // This visitor should only be used in situations where the
    // reference fields are known to not be modified by other threads
    // while the visitor is traversing the object.
    internal abstract class NonNullReferenceVisitor : DirectReferenceVisitor
    {

#region HELP_DEVIRT
        // This method simply forces the compiler to generate a copy
        // of VisitReferenceFieldsTemplate in this class.
        [ManualRefCounts]
        [Inline]
        internal override UIntPtr VisitReferenceFields(Object obj)
        {
            return this.VisitReferenceFields(Magic.addressOf(obj),
                                             obj.vtable);
        }

        // This method simply forces the compiler to generate a copy
        // of VisitReferenceFieldsTemplate in this class.
        [ManualRefCounts]
        [Inline]
        internal override UIntPtr VisitReferenceFields(UIntPtr objectBase,
                                                       VTable vtable)
        {
            ObjectDescriptor objDesc =
                new ObjectDescriptor(vtable, objectBase);
            return VisitReferenceFieldsTemplate(ref objDesc);
        }
#endregion

        [Inline]
        protected override unsafe
        void Filter(UIntPtr *location, ref ObjectDescriptor objDesc)
        {
            if (*location != UIntPtr.Zero) {
                this.Visit(location);
            }
        }

    }

    internal abstract class OffsetReferenceVisitor : ReferenceVisitor
    {

        internal abstract void FieldOffset(UIntPtr offset,
                                           ref ObjectDescriptor objDesc);

#region HELP_DEVIRT
        // This method simply forces the compiler to generate a copy
        // of VisitReferenceFieldsTemplate in this class.
        [ManualRefCounts]
        internal override sealed
        UIntPtr VisitReferenceFields(UIntPtr objectBase, VTable vtable)
        {
            ObjectDescriptor objDesc =
                new ObjectDescriptor(vtable, objectBase);
            return VisitReferenceFieldsTemplate(ref objDesc);
        }
#endregion

        [Inline]
        protected override unsafe sealed
        void Filter(UIntPtr *location, ref ObjectDescriptor objDesc)
        {
            UIntPtr offset = ((UIntPtr) location) - objDesc.objectBase;
            this.FieldOffset(offset, ref objDesc);
        }

    }

}
