//
// 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 class ObjectLayout
    {

        // Tags on GC information provided by compiler.
        internal const uint SPARSE_TAG              = 0x1;
        internal const uint DENSE_TAG               = 0x3;
        internal const uint PTR_VECTOR_TAG          = 0x5;
        internal const uint OTHER_VECTOR_TAG        = 0x7;
        internal const uint PTR_ARRAY_TAG           = 0x9;
        internal const uint OTHER_ARRAY_TAG         = 0xb;
        internal const uint STRING_TAG              = 0xd;
        internal const uint RESERVED_TAG            = 0xf;

        internal abstract class ObjectVisitor {
            // Must return the size of the visited object.
            internal abstract UIntPtr Visit(Object obj);
        }

        // Returns object size in bytes
        [NoBarriers]
        [ManualRefCounts]
        [NoHeapAllocation]
        [Inline]
        internal static UIntPtr ObjectSize(VTable vtable)
        {
            uint baseLength = unchecked((uint)vtable.baseLength);
            VTable.Assert(Util.UIntPtrPad((UIntPtr) baseLength) == baseLength);
            return (UIntPtr)baseLength;
        }

        // Returns string size in bytes
        [NoBarriers]
        [ManualRefCounts]
        [NoHeapAllocation]
        [Inline]
        internal static unsafe UIntPtr StringSize(VTable vtable,
                                                  uint arrayLength)
        {
            uint baseLength = unchecked((uint) vtable.baseLength);
            uint size = (baseLength + sizeof(char)*arrayLength);
            return Util.UIntPtrPad((UIntPtr) size);
        }

        // Returns array size in bytes
        [NoBarriers]
        [ManualRefCounts]
        [NoInline]
        [NoHeapAllocation]
        [Inline]
        internal static UIntPtr ArraySize(VTable vtable, uint numElements)
        {
            UIntPtr baseLength = (UIntPtr)
                unchecked((uint) vtable.baseLength);
            UIntPtr elementSize = (UIntPtr)
                unchecked((uint) vtable.arrayElementSize);
            UIntPtr size = baseLength + elementSize * numElements;
            return Util.UIntPtrPad(size);
        }

        // Returns object size in bytes
        [ManualRefCounts]
        [NoHeapAllocation]
        [Inline]
        internal unsafe static UIntPtr Sizeof(Object obj) {
            return ObjectSize(Magic.addressOf(obj), obj.vtable);
        }

        [ManualRefCounts]
        [NoHeapAllocation]
        internal unsafe static UIntPtr ObjectSize(UIntPtr objectBase,
                                                  VTable vtable)
        {
            uint objectTag =
                unchecked((uint) vtable.pointerTrackingMask) & 0xf;
            switch (objectTag) {
              case SPARSE_TAG:
              case DENSE_TAG: {
                  return ObjectSize(vtable);
              }
              case PTR_VECTOR_TAG:
              case OTHER_VECTOR_TAG: {
                  uint length = *(uint*) (objectBase + PostHeader.Size);
                  return ArraySize(vtable, length);
              }
              case PTR_ARRAY_TAG:
              case OTHER_ARRAY_TAG: {
                  uint length =
                      *(uint*)(objectBase + PostHeader.Size + sizeof(uint));
                  return ArraySize(vtable, length);
              }
              case STRING_TAG: {
                  uint length = *(uint*) (objectBase + PostHeader.Size);
                  return StringSize(vtable, length);
              }
              case RESERVED_TAG: {
                  VTable.Assert(false, "RESERVED_TAG was used!");
                  return UIntPtr.Zero;
              }
              default: {
                  // escape case
                  return ObjectSize(vtable);
              }
            }
        }

    }

}
