using System;
using System.Linq;
using System.Threading.Tasks;
using Native = System.UInt32;
namespace Cosmos.Core.Memory
{
///
/// RAT (RAM Allocation Table) class.
///
unsafe static public class RAT
{
// RAT: RAM Allocation Table
//
// A byte table which defines the code which owns the page.
// Owners can further subdivide table types on their own and RAT
// code must not assume anything about contents of pages other
// than who owns them.
///
/// PageType class. Used like a enum to define the type of the page.
///
/// Only used to define page type.
static public class PageType
{
///
/// Empty page.
///
public const byte Empty = 0;
// Data Types from 1, special meanings from 255 down.
///
/// RAT type page.
///
public const byte RAT = 1;
///
/// Small heap page.
///
public const byte HeapSmall = 2;
///
/// Medium heap page.
///
public const byte HeapMedium = 3;
///
/// Large heap page.
///
public const byte HeapLarge = 4;
// Code
// Stack
// Disk Cache
// Extension of previous page.
///
/// Extension of pre-existing page.
///
public const byte Extension = 255;
}
///
/// Debug flag.
///
/// Used to bypass certain checks that will fail during tests and debugging.
static internal bool Debug = false;
///
/// Native Intel page size.
///
///
/// - x86 Page Size: 4k, 2m (PAE only), 4m.
/// - x64 Page Size: 4k, 2m
///
public const Native PageSize = 4096;
///
/// Start of area usable for heap, and also start of heap.
///
static private byte* mRamStart;
///
/// Size of heap.
///
static private Native mRamSize;
///
/// Number of pages in the heap.
///
/// Calculated from mSize.
static private Native mPageCount;
///
/// Pointer to the RAT.
///
/// Covers Data area only.
// We need a pointer as the RAT can move around in future with dynamic RAM etc.
static private byte* mRAT;
///
/// Init RAT.
///
/// A pointer to the start of the heap.
/// A heap size, in bytes.
/// Thrown if:
///
/// - RAM start or size is not page aligned.
///
///
static public void Init(byte* aStartPtr, Native aSize)
{
if ((Native)aStartPtr % PageSize != 0 && !Debug)
{
throw new Exception("RAM start must be page aligned.");
}
if (aSize % PageSize != 0)
{
throw new Exception("RAM size must be page aligned.");
}
mRamStart = aStartPtr;
mRamSize = aSize;
mPageCount = aSize / PageSize;
// We need one status byte for each block.
// Intel blocks are 4k (10 bits). So for 4GB, this means
// 32 - 12 = 20 bits, 1 MB for a RAT for 4GB. 0.025%
Native xRatPageCount = (mPageCount - 1) / PageSize + 1;
Native xRatPageBytes = xRatPageCount * PageSize;
mRAT = mRamStart + mRamSize - xRatPageBytes;
for (byte* p = mRAT; p < mRAT + xRatPageBytes - xRatPageCount; p++)
{
*p = PageType.Empty;
}
for (byte* p = mRAT + xRatPageBytes - xRatPageCount; p < mRAT + xRatPageBytes; p++)
{
*p = PageType.RAT;
}
Heap.Init();
}
///
/// Get page count.
///
/// A page type to count.
/// Native value.
static public Native GetPageCount(byte aType = 0)
{
Native xResult = 0;
byte xType = 0; // Could us nullable type instead of this + xCounting, but this is faster.
bool xCounting = false;
for (byte* p = mRAT; p < mRAT + mPageCount; p++)
{
if (*p == aType)
{
xType = *p;
xResult++;
xCounting = true;
}
else if (xCounting)
{
if (xType == PageType.Extension)
{
xResult++;
}
else
{
xCounting = false;
}
}
}
return xResult;
}
///
/// Alloc a block with a given type and size.
///
/// A type of block to alloc.
/// Number of bytes to alloc.
/// A pointer to the first page on success, null on failure.
static public void* AllocBytes(byte aType, Native aBytes)
{
return AllocPages(aType, aBytes / PageSize);
}
///
/// Alloc a given number of pages, all of the same type.
///
/// A type of pages to alloc.
/// Number of pages to alloc. (default = 1)
/// A pointer to the first page on success, null on failure.
static public void* AllocPages(byte aType, Native aPageCount = 1)
{
byte* xPos = null;
// Could combine with an external method or delegate, but will slow things down
// unless we can force it to be inlined.
//
// Alloc single blocks at bottom, larger blocks at top to help reduce fragmentation.
Native xCount = 0;
if (aPageCount == 1)
{
for (byte* p = mRAT; p < mRAT + mPageCount; p++)
{
if (*p == PageType.Empty)
{
xCount++;
if (xCount == aPageCount)
{
xPos = p - xCount + 1;
break;
}
}
else
{
xCount = 0;
}
}
}
else
{
// This loop will FAIL if mRAT is ever 0. This should be impossible though
// so we don't bother to account for such a case. xPos would also have issues.
for (byte* p = mRAT + mPageCount - 1; p >= mRAT; p--)
{
if (*p == PageType.Empty)
{
xCount++;
if (xCount == aPageCount)
{
xPos = p;
break;
}
}
else
{
xCount = 0;
}
}
}
// If we found enough space, mark it as used.
if (xPos != null)
{
byte* xResult = mRamStart + (xPos - mRAT) * PageSize;
*xPos = aType;
for (byte* p = xPos + 1; p < xPos + xCount; p++)
{
*p = PageType.Extension;
}
return xResult;
}
return null;
}
///
/// Get the first RAT address.
///
/// A pointer to the block.
/// Native value.
/// Thrown if page type is not found.
static public Native GetFirstRAT(void* aPtr)
{
var xPos = (Native)((byte*)aPtr - mRamStart) / RAT.PageSize;
// See note about when mRAT = 0 in Alloc.
for (byte* p = mRAT + xPos; p >= mRAT; p--)
{
if (*p != PageType.Extension)
{
return (Native)(p - mRAT);
}
}
throw new Exception("Page type not found. Likely RAT is rotten.");
}
///
/// Get the page type pointed by a pointer.
///
/// A pointer to the page to get the type of.
/// byte value.
/// Thrown if page type is not found.
static public byte GetPageType(void* aPtr)
{
return mRAT[GetFirstRAT(aPtr)];
}
///
/// Free page.
///
/// A index to the page to be freed.
static public void Free(Native aPageIdx)
{
byte* p = mRAT + aPageIdx;
*p = PageType.Empty;
for (; p < mRAT + mPageCount; p++)
{
if (*p != PageType.Extension)
{
break;
}
*p = PageType.Empty;
}
}
}
}