379 lines
16 KiB
C#
379 lines
16 KiB
C#
/*******************************************************************/
|
|
/* 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. */
|
|
/*******************************************************************/
|
|
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
//#define DEBUG_OFFSETTABLE
|
|
|
|
namespace System.GCs {
|
|
|
|
using Microsoft.Bartok.Runtime;
|
|
using System.Threading;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
internal unsafe class OffsetTable {
|
|
|
|
// OffsetTable is to identify objects in cards. Each card has one
|
|
// entry in the offsetTable.
|
|
// An entry in the offsetTable for a card has a special value
|
|
// (NoObjPtrToCard) if no object pointer points to the card.
|
|
// Otherwise, it is an offset of the last object in the card
|
|
// relative to the beginning of the card.
|
|
// Note: an object pointer points to the byte below its header,
|
|
// not the header itself. That is vtable for non reference counting
|
|
// collector, and refCount for reference counting.
|
|
|
|
private static byte *offsetTableBase;
|
|
|
|
// Special offset indicating that in a card, there is no object
|
|
// pointer to it. We make it as -1, and the effective
|
|
// offset of an pointer is within 0~127 when an offsetTable entry
|
|
// is 1 byte, or 0~2**15-1 if it is 2 bytes. However, to avoid time
|
|
// to initialize the offsetTable with -1, we simply add an bias 1
|
|
// to all the them, such that the special offset is 0, and the effective
|
|
// ones are within 1~128 (or 1~2**15). Correspondingly, in fetching
|
|
// the offset from the able, we should deduct the bias to get the real
|
|
// offset.
|
|
|
|
private static UIntPtr NoObjPtrToCard {
|
|
get { return (UIntPtr)0x0;}
|
|
}
|
|
|
|
// The bias.
|
|
|
|
private static byte OffsetBias {
|
|
get { return 0x1;}
|
|
}
|
|
|
|
// Assume pointers are naturally aligned within a card. Then
|
|
// its least 2 or 3 significant bits are 0 and need not be recorded.
|
|
|
|
private static byte OffsetIgnorableBits {
|
|
get {
|
|
VTable.Assert(UIntPtr.Size == 4 || UIntPtr.Size == 8,
|
|
"Not supported pointer type");
|
|
if (UIntPtr.Size == 4) {
|
|
return 2;
|
|
} else {
|
|
return 3;
|
|
}
|
|
}
|
|
}
|
|
|
|
// An entry in the offsetTable needs to be either 1 byte or 2 bytes,
|
|
// depending on the size of a card.
|
|
// Given one byte for an entry, we can represent 128 effective offsets,
|
|
// and one special offset NoObjPtrToCard. For x64,
|
|
// UIntPtr.Size*128 = 2**10. For x86, UIntPtr.Size * 128 = 2**9.
|
|
// That is, the size of a card is no bigger than 2**10 or 2**9.
|
|
// Given two bytes for an entry, we can represent 2**15 effective
|
|
// offsets, and one special offset. The size of card is no bigger than
|
|
// 2**18 or 2**17.
|
|
|
|
private static byte EntryBytes {
|
|
get {
|
|
VTable.Assert(UIntPtr.Size == 4 || UIntPtr.Size == 8,
|
|
"Not supported pointer type");
|
|
if ((UIntPtr.Size == 4 && CardTable.CardBits <= 9) ||
|
|
(UIntPtr.Size == 8 && CardTable.CardBits <= 10)) {
|
|
return 1;
|
|
} else {
|
|
VTable.Assert((UIntPtr.Size == 4 && CardTable.CardBits <= 17) ||
|
|
(UIntPtr.Size == 8 && CardTable.CardBits <= 18),
|
|
"Card size is unreasonable large");
|
|
return 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static void Initialize(UIntPtr totalCards) {
|
|
UIntPtr offsetTablePtr = PageManager.AllocateNonheapMemory(null,
|
|
((UIntPtr)EntryBytes)*totalCards);
|
|
VTable.Assert(offsetTablePtr != UIntPtr.Zero, "OffsetTable.Initialize: no memory");
|
|
|
|
offsetTableBase = (byte*)(offsetTablePtr - CardTable.FirstCardNo);
|
|
|
|
// The memory of offset table should have already been cleaned
|
|
|
|
for (UIntPtr c=CardTable.FirstCardNo; c<CardTable.FirstCardNo+totalCards;c++) {
|
|
VTable.Assert(NoObjectPtrToTheCard(c), "offset table initialization error");
|
|
}
|
|
}
|
|
|
|
[Inline]
|
|
private static void SetNoObjPtrToCard(UIntPtr c)
|
|
{
|
|
VTable.Assert(CardTable.IsValidCardNo(c), "SetNoObjPtrToCard: invalid card no");
|
|
SetCompressedBiasedOffset(c, NoObjPtrToCard);
|
|
}
|
|
|
|
[Inline]
|
|
internal static bool NoObjectPtrToTheCard(UIntPtr c)
|
|
{
|
|
VTable.Assert(CardTable.IsValidCardNo(c), "NoObjectPtrToTheCard: invalid card no");
|
|
return (GetCompressedBiasedOffset(c) == NoObjPtrToCard);
|
|
}
|
|
|
|
[Inline]
|
|
internal static void SetOffset(UIntPtr c, UIntPtr offset)
|
|
{
|
|
VTable.Assert((offset >> OffsetIgnorableBits) << OffsetIgnorableBits
|
|
== offset, "offset should be multiple of pointer size");
|
|
VTable.Assert(NoObjectPtrToTheCard(c) || GetOffset(c) < offset,
|
|
"A smaller offset offset is set to card ");
|
|
|
|
SetCompressedBiasedOffset(c, (offset >> OffsetIgnorableBits) + (UIntPtr)OffsetBias);
|
|
}
|
|
|
|
[Inline]
|
|
internal static UIntPtr GetOffset(UIntPtr c)
|
|
{
|
|
UIntPtr offset = GetCompressedBiasedOffset(c) - (UIntPtr)OffsetBias;
|
|
return offset << OffsetIgnorableBits;
|
|
}
|
|
|
|
[Inline]
|
|
private static bool IsValidCompressedBiasedOffset(UIntPtr value)
|
|
{
|
|
return value <= ((UIntPtr)2<<(EntryBytes * 8 -1));
|
|
}
|
|
|
|
[Inline]
|
|
private static void SetCompressedBiasedOffset(UIntPtr c, UIntPtr value)
|
|
{
|
|
VTable.Assert(IsValidCompressedBiasedOffset(value), "Invalid compressed biased value");
|
|
if (EntryBytes == 1) {
|
|
*(offsetTableBase + c) = (byte)value;
|
|
} else {
|
|
*((short*)offsetTableBase + c) = (short)value;
|
|
}
|
|
}
|
|
|
|
[Inline]
|
|
private static UIntPtr GetCompressedBiasedOffset(UIntPtr c)
|
|
{
|
|
if (EntryBytes == 1) {
|
|
return (UIntPtr) (*(offsetTableBase + c));
|
|
} else {
|
|
return (UIntPtr)(*((short*)offsetTableBase + c));
|
|
}
|
|
}
|
|
|
|
// OffsetTable records all objects promoted or directly allocated in mature
|
|
// generation.
|
|
|
|
internal static void SetLast(UIntPtr objPtr)
|
|
{
|
|
VTable.Assert(PageTable.IsGcPage(PageTable.Page(objPtr)), "Not GC page");
|
|
|
|
UIntPtr card = CardTable.CardNo(objPtr);
|
|
UIntPtr mask = (UIntPtr)(CardTable.CardSize -1);
|
|
UIntPtr offset = objPtr & mask;
|
|
VTable.Assert(offset < CardTable.CardSize, "Overflow");
|
|
SetOffset(card, offset);
|
|
#if DEBUG_OFFSETTABLE
|
|
VTable.DebugPrint("SetLast objPtr {0:x8}, card {1:x8}, offset {2:x8}\n",
|
|
__arglist(objPtr, card, offset));
|
|
#endif
|
|
}
|
|
|
|
internal static UIntPtr FirstObjPtrWithFieldInCard(UIntPtr c)
|
|
{
|
|
VTable.Assert(CardTable.IsValidCardNo(c), "Not valid card no");
|
|
VTable.Assert(CardTable.IsMyLiveGcCard(c), "Not my live GC card");
|
|
|
|
UIntPtr cardAddr = CardTable.CardAddr(c);
|
|
UIntPtr nextCardAddr = CardTable.NextCardAddr(c);
|
|
|
|
// Scan backward. May go to zombie areas
|
|
|
|
UIntPtr cardBefore = c -1;
|
|
while (cardBefore >= CardTable.FirstCardNo &&
|
|
OffsetTable.NoObjectPtrToTheCard(cardBefore) &&
|
|
CardTable.IsMyGcCard(cardBefore)){
|
|
cardBefore --;
|
|
}
|
|
|
|
UIntPtr ptr;
|
|
|
|
if (cardBefore < CardTable.FirstCardNo ||
|
|
!CardTable.IsMyGcCard(cardBefore)) {
|
|
|
|
// this case happens when c is the first live card that has an object.
|
|
|
|
VTable.Assert(CardTable.IsMyGcCard(cardBefore+1),
|
|
"The next card must be a GC card");
|
|
VTable.Assert(CardTable.CardAddr(cardBefore+1) ==
|
|
CardTable.PageAddrOfCard(cardBefore+1),
|
|
"The next card must be at the boundary of a page");
|
|
|
|
UIntPtr pageAddr = CardTable.PageAddrOfCard(cardBefore+1);
|
|
ptr =PtrToNextObject(pageAddr, (UIntPtr)PreHeader.Size, nextCardAddr);
|
|
} else {
|
|
VTable.Assert(!OffsetTable.NoObjectPtrToTheCard(cardBefore),
|
|
"A card with an object must have been found");
|
|
ptr = CardTable.CardAddr(cardBefore) +
|
|
OffsetTable.GetOffset(cardBefore);
|
|
}
|
|
VTable.Assert(ptr < nextCardAddr, "No objptr in this card");
|
|
|
|
// Scan forward. Update offset table in the course.
|
|
|
|
UIntPtr currentPtr = ptr;
|
|
while (currentPtr < cardAddr) {
|
|
UIntPtr size = OffsetTable.ObjectSize(currentPtr);
|
|
if (currentPtr + size - PreHeader.Size > cardAddr) {
|
|
VTable.Assert(CardTable.CardGeneration(CardTable.CardNo(currentPtr)) ==
|
|
CardTable.CardGeneration(c),
|
|
"The whole object must be in the same generation");
|
|
break;
|
|
} else {
|
|
ptr = currentPtr;
|
|
currentPtr = PtrToNextObject(currentPtr, size, nextCardAddr);
|
|
VTable.Assert(CardTable.CardNo(ptr) <= CardTable.CardNo(currentPtr),
|
|
"Previous ptr should be before current ptr");
|
|
if (CardTable.CardNo(ptr) < CardTable.CardNo(currentPtr)) {
|
|
UIntPtr ptrC = CardTable.CardNo(ptr);
|
|
UIntPtr offsetC = ptr - CardTable.CardAddr(ptrC);
|
|
if (offsetC > GetOffset(ptrC)) {
|
|
SetOffset(ptrC, offsetC);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
VTable.Assert(currentPtr != UIntPtr.Zero, "current ptr is zero");
|
|
VTable.Assert(currentPtr < nextCardAddr, "No objptr found in this card");
|
|
|
|
#if DEBUG_OFFSETTABLE
|
|
UIntPtr temp = OffsetTable.FirstPtrFromInteriorTable(c);
|
|
if (temp != currentPtr) {
|
|
VTable.DebugPrint("Found ptr ({0:x8}) inconsistent with first ptr from interior table({1:x8})\n",
|
|
__arglist(currentPtr, temp));
|
|
VTable.Assert(false);
|
|
}
|
|
#endif
|
|
|
|
return currentPtr;
|
|
}
|
|
|
|
// The next object after the current object and below the limit
|
|
|
|
[Inline]
|
|
internal static UIntPtr PtrToNextObject(UIntPtr objPtr, UIntPtr size, UIntPtr limit)
|
|
{
|
|
return BumpAllocator.SkipNonObjectData(objPtr + size, limit);
|
|
}
|
|
|
|
[Inline]
|
|
internal static void ClearCards(UIntPtr c1, UIntPtr c2)
|
|
{
|
|
for (UIntPtr i = c1; i <= c2; i++) {
|
|
SetNoObjPtrToCard(i);
|
|
}
|
|
#if DEBUG_OFFSETTABLE
|
|
VTable.DebugPrint("ClearLast from {0:x8} to {1:x8}\n", __arglist(c1, c2));
|
|
#endif
|
|
}
|
|
|
|
internal static void ClearLast(UIntPtr from, UIntPtr to)
|
|
{
|
|
UIntPtr first = CardTable.CardNo(from);
|
|
UIntPtr last = CardTable.CardNo(to);
|
|
ClearCards(first, last);
|
|
}
|
|
|
|
// Copied from InteriorPtrTable.cs
|
|
|
|
internal static unsafe UIntPtr ObjectSize(UIntPtr addr) {
|
|
UIntPtr vtableAddr = Allocator.GetObjectVTable(addr);
|
|
UIntPtr vtablePage = PageTable.Page(vtableAddr);
|
|
if (PageTable.IsGcPage(vtablePage)) {
|
|
// The vtable field is really a forwarding pointer
|
|
vtableAddr = Allocator.GetObjectVTable(vtableAddr);
|
|
} else {
|
|
// Clear the lowest bits, if set
|
|
vtableAddr &= ~((UIntPtr)3);
|
|
}
|
|
VTable.Assert((UIntPtr.Size == 8) ||
|
|
((vtableAddr & 0x80000000) == 0),
|
|
"Unaligned vtable addr");
|
|
VTable vtable =
|
|
Magic.toVTable(Magic.fromAddress(vtableAddr));
|
|
return ObjectLayout.ObjectSize(addr, vtable);
|
|
}
|
|
|
|
#if DEBUG_OFFSETTABLE
|
|
private static UIntPtr FirstPtrFromInteriorTable(UIntPtr c)
|
|
{
|
|
UIntPtr cardAddr = CardTable.CardAddr(c);
|
|
UIntPtr nextCardAddr = CardTable.NextCardAddr(c);
|
|
UIntPtr page = PageTable.Page(cardAddr);
|
|
UIntPtr pageAddr = PageTable.PageAddr(page);
|
|
UIntPtr currAddr;
|
|
|
|
if (page == 0) {
|
|
currAddr =PtrToNextObject(pageAddr,
|
|
(UIntPtr)PreHeader.Size, nextCardAddr);
|
|
} else {
|
|
short offset = PageTable.Extra(page);
|
|
currAddr = UIntPtr.Zero;
|
|
if (offset != InteriorPtrTable.OFFSET_NO_DATA) {
|
|
currAddr = pageAddr + (offset - InteriorPtrTable.OFFSET_SKEW);
|
|
}
|
|
|
|
// In general, we expect currAddr <= cardAddr. Or in the extreme
|
|
// case, when the object starts from the page boundary,
|
|
// currAddr - Object.HEADER_BYTES <= cardAddr. The contrary
|
|
// cases has to be handled by searching previous pages.
|
|
|
|
if (currAddr == UIntPtr.Zero ||
|
|
(currAddr > cardAddr &&
|
|
currAddr - PreHeader.Size > cardAddr)) {
|
|
|
|
// look from previous pages, in case that an object on
|
|
// them spans to the current page. In that case, we should
|
|
// should use that object's ptr.
|
|
|
|
currAddr = InteriorPtrTable.Last(page -1);
|
|
|
|
// Usually, Last() returns a pointer before or at the page
|
|
// boundary. However, there is one exception: when an object
|
|
// exactly fits to the last byte of the previous page, and the next
|
|
// object starts right from the page boundary (the first byte of
|
|
// the next page), then the pointer to this next object is returned.
|
|
// Example found: objPtr =3d09fa8, size=60, pageboundary=
|
|
// 3d0a000, next objPtr=3d0a008. Then returned pointer is
|
|
// 3d0a008, which is beyond the page boundary.
|
|
|
|
VTable.Assert(currAddr <= pageAddr ||
|
|
currAddr - PreHeader.Size <= pageAddr,
|
|
"object is expected before page or right at the beginning of it");
|
|
}
|
|
}
|
|
VTable.Assert(currAddr < nextCardAddr, "object is expected before next card");
|
|
|
|
while (currAddr < nextCardAddr) {
|
|
if (Allocator.IsAlignment(currAddr)) {
|
|
currAddr += UIntPtr.Size;
|
|
} else if (BumpAllocator.IsUnusedSpace(currAddr)) {
|
|
currAddr = PageTable.PagePad(currAddr) + PreHeader.Size;
|
|
} else {
|
|
UIntPtr size = InteriorPtrTable.ObjectSize(currAddr);
|
|
if (currAddr + size - PreHeader.Size > cardAddr) {
|
|
return currAddr;
|
|
}
|
|
currAddr += size;
|
|
}
|
|
}
|
|
VTable.Assert(false, "No obj ptr found by looking at interior table");
|
|
return UIntPtr.Zero;
|
|
}
|
|
#endif
|
|
}
|
|
}
|