329 lines
13 KiB
C#
329 lines
13 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.
|
|
//
|
|
|
|
namespace System.GCs {
|
|
|
|
using Microsoft.Bartok.Options;
|
|
using Microsoft.Bartok.Runtime;
|
|
|
|
using System.Threading;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
internal unsafe class SequentialStoreBuffer : RememberedSet {
|
|
|
|
internal static SequentialStoreBuffer instance;
|
|
|
|
private struct ThreadLocal {
|
|
internal UIntPtr overflowValue;
|
|
[AccessedByRuntime("referenced from halforgc.asm")]
|
|
internal UIntPtr *cursor;
|
|
[AccessedByRuntime("referenced from halforgc.asm")]
|
|
internal UIntPtr *limit;
|
|
}
|
|
|
|
[MixinConditional("SSB")]
|
|
[MixinConditional("AllThreadMixins")]
|
|
[Mixin(typeof(Thread))]
|
|
private class SequentialStoreBufferThread : Object {
|
|
[AccessedByRuntime("referenced from brtforgc.asm")]
|
|
internal ThreadLocal ssb;
|
|
}
|
|
|
|
private static SequentialStoreBufferThread MixinThread(Thread t) {
|
|
return (SequentialStoreBufferThread) (Object) t;
|
|
}
|
|
|
|
internal static void Initialize() {
|
|
Initialize(128, PageTable.PageSize);
|
|
}
|
|
|
|
internal static void Initialize(int count, UIntPtr size) {
|
|
writeBufferSize = ((int) size * count) / UIntPtr.Size;
|
|
chunkSize = (int) size;
|
|
UIntPtr memorySize = (UIntPtr) UIntPtr.Size * (uint) writeBufferSize;
|
|
writeBuffer = (UIntPtr *)
|
|
PageManager.AllocateNonheapMemory(null, memorySize);
|
|
VTable.Assert(writeBuffer != null);
|
|
AdjustChunkSize(1);
|
|
SequentialStoreBuffer.instance = (SequentialStoreBuffer)
|
|
BootstrapMemory.Allocate(typeof(SequentialStoreBuffer));
|
|
}
|
|
|
|
[Inline]
|
|
internal override void RecordReference(ref Object reference,
|
|
Object toObj)
|
|
{
|
|
Record((UIntPtr) Magic.toPointer(ref reference));
|
|
}
|
|
|
|
[Inline]
|
|
internal override void RecordReference(UIntPtr* fromAddr,
|
|
Object toObj)
|
|
{
|
|
Record((UIntPtr) fromAddr);
|
|
}
|
|
|
|
[Inline]
|
|
internal override void RecordClonedObject(Object clonedObject)
|
|
{
|
|
Record(Magic.addressOf(clonedObject) + 1);
|
|
}
|
|
|
|
internal override void Clean() {
|
|
for (int i = 0; i < Thread.threadTable.Length; i++) {
|
|
Thread t = Thread.threadTable[i];
|
|
if (t != null) {
|
|
Clean(t);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal override void Clean(Thread thread) {
|
|
UIntPtr wbCursor = (UIntPtr) MixinThread(thread).ssb.cursor;
|
|
UIntPtr wbLimit = (UIntPtr) MixinThread(thread).ssb.limit;
|
|
if (wbCursor < wbLimit) {
|
|
VTable.Assert(wbCursor != UIntPtr.Zero);
|
|
Util.MemClear(wbCursor, wbLimit - wbCursor);
|
|
}
|
|
}
|
|
|
|
[RequiredByBartok]
|
|
[Inline]
|
|
public static void Record(UIntPtr value) {
|
|
Thread currentThread = Thread.CurrentThread;
|
|
if (MixinThread(currentThread).ssb.cursor ==
|
|
MixinThread(currentThread).ssb.limit) {
|
|
RecordSlow(currentThread, value);
|
|
} else {
|
|
*MixinThread(currentThread).ssb.cursor = value;
|
|
MixinThread(currentThread).ssb.cursor++;
|
|
}
|
|
}
|
|
|
|
private static void RecordSlow(Thread currentThread, UIntPtr value) {
|
|
// Try to acquire a new chunk of the store buffer
|
|
while (writeBufferIndex < writeBufferSize) {
|
|
int oldIndex = writeBufferIndex;
|
|
int newIndex = oldIndex + chunkSize;
|
|
if (Interlocked.CompareExchange(ref writeBufferIndex, newIndex,
|
|
oldIndex) == oldIndex) {
|
|
// We secured a new block of write buffer for this thread
|
|
UIntPtr *cursor = writeBuffer + oldIndex;
|
|
*cursor = value;
|
|
cursor++;
|
|
MixinThread(currentThread).ssb.cursor = cursor;
|
|
MixinThread(currentThread).ssb.limit =
|
|
writeBuffer + newIndex;
|
|
return;
|
|
}
|
|
}
|
|
// We have run out of write barrier space
|
|
if (StopTheWorldCollector.CurrentPhase ==
|
|
StopTheWorldCollector.STWPhase.SingleThreaded) {
|
|
VTable.DebugBreak();
|
|
}
|
|
VTable.Assert(MixinThread(currentThread).ssb.overflowValue ==
|
|
UIntPtr.Zero);
|
|
MixinThread(currentThread).ssb.overflowValue = value;
|
|
GC.InvokeCollection(currentThread);
|
|
while (MixinThread(currentThread).ssb.overflowValue != UIntPtr.Zero) {
|
|
// Another thread must have taken charge of performing the
|
|
// collection and hadn't yet assigned a GCRequest to the
|
|
// current thread. Give the other thread a chance to do
|
|
// some work before we try invoking the collector again.
|
|
Thread.Yield();
|
|
GC.InvokeCollection(currentThread);
|
|
}
|
|
}
|
|
|
|
internal override void Uniquify() {
|
|
// Sort the write buffer.
|
|
|
|
// TODO: Would like sort that is in-place, O(n lg n) worst-case, and
|
|
// O(n) when nearly-sorted.
|
|
|
|
bool changed = true;
|
|
for(int max = writeBufferIndex; changed; --max) {
|
|
changed = false;
|
|
for(int i = 1; i < max; ++i) {
|
|
if(writeBuffer[i-1] > writeBuffer[i]) {
|
|
changed = true;
|
|
UIntPtr t = writeBuffer[i-1];
|
|
writeBuffer[i-1] = writeBuffer[i];
|
|
writeBuffer[i] = t;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove duplicates
|
|
int dest = 0;
|
|
UIntPtr last = UIntPtr.Zero;
|
|
for (int i = 0; i < writeBufferIndex; i++) {
|
|
UIntPtr current = *(writeBuffer + i);
|
|
if (current != last) {
|
|
VTable.Assert(current > last);
|
|
*(writeBuffer + dest) = current;
|
|
dest++;
|
|
last = current;
|
|
if ((current & 1) != 0) {
|
|
// The entire object is marked, skip interior addresses
|
|
UIntPtr objPtr = current - 1;
|
|
VTable vtable = Magic.fromAddress(objPtr).vtable;
|
|
UIntPtr size = ObjectLayout.ObjectSize(objPtr, vtable);
|
|
VTable.Assert(size > 0);
|
|
UIntPtr objLimit = objPtr + size;
|
|
i++;
|
|
while (i < writeBufferIndex &&
|
|
*(writeBuffer + i) < objLimit) {
|
|
i++;
|
|
}
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
writeBufferIndex = dest;
|
|
// Remove duplicates hiding in the overflow slot in thread objects!
|
|
for (int i = 0; i < Thread.threadTable.Length; i++) {
|
|
Thread t = Thread.threadTable[i];
|
|
if (t != null) {
|
|
UIntPtr overflowPtr = MixinThread(t).ssb.overflowValue;
|
|
if (overflowPtr != UIntPtr.Zero) {
|
|
int left = 0;
|
|
int right = writeBufferIndex;
|
|
while (left < right - 1) {
|
|
int mid = (left + right) / 2;
|
|
if (*(writeBuffer + mid) <= overflowPtr) {
|
|
left = mid;
|
|
} else {
|
|
right = mid;
|
|
}
|
|
}
|
|
UIntPtr foundPtr = *(writeBuffer + left);
|
|
if (overflowPtr == foundPtr) {
|
|
// Found an exact duplicate
|
|
MixinThread(t).ssb.overflowValue = UIntPtr.Zero;
|
|
continue;
|
|
} else if ((foundPtr & 1) != 0) {
|
|
UIntPtr objAddr = foundPtr - 1;
|
|
VTable vtable = Magic.fromAddress(objAddr).vtable;
|
|
UIntPtr size =
|
|
ObjectLayout.ObjectSize(objAddr, vtable);
|
|
if (overflowPtr < objAddr + size) {
|
|
// found a pointer into a checked object
|
|
MixinThread(t).ssb.overflowValue = UIntPtr.Zero;
|
|
}
|
|
} else if ((overflowPtr & 1) != 0) {
|
|
UIntPtr objAddr = overflowPtr - 1;
|
|
VTable vtable = Magic.fromAddress(objAddr).vtable;
|
|
UIntPtr size =
|
|
ObjectLayout.ObjectSize(objAddr, vtable);
|
|
UIntPtr objLimit = objAddr + size;
|
|
int skipCount = 0;
|
|
int probe = left + 1;
|
|
while (probe < writeBufferIndex &&
|
|
*(writeBuffer + probe) < objLimit) {
|
|
probe++;
|
|
skipCount++;
|
|
}
|
|
if (skipCount > 0) {
|
|
while (probe < writeBufferIndex) {
|
|
*(writeBuffer + left) =
|
|
*(writeBuffer + probe);
|
|
left++;
|
|
probe++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// REVIEW: This is marked inline because the adaptive collector calls
|
|
// it with two different NonNullReferenceVisitors and we need to avoid
|
|
// the virtual call. We really want to specialize the method.
|
|
[Inline]
|
|
internal override void Scan(NonNullReferenceVisitor ptrVisitor,
|
|
PageType genToCollect)
|
|
{
|
|
for (int i = 0; i < writeBufferIndex; i++) {
|
|
UIntPtr tag = *(writeBuffer + i);
|
|
if (tag != UIntPtr.Zero) {
|
|
if ((tag & 0x1) == 0) {
|
|
UIntPtr *fieldLoc = (UIntPtr *) tag;
|
|
if (*fieldLoc != UIntPtr.Zero) {
|
|
ptrVisitor.Visit(fieldLoc);
|
|
}
|
|
} else {
|
|
UIntPtr objectPtr = (UIntPtr) (tag - 1);
|
|
Object obj = Magic.fromAddress(objectPtr);
|
|
ptrVisitor.VisitReferenceFields(obj);
|
|
}
|
|
}
|
|
}
|
|
// Handle the overflow in the thread objects
|
|
for (int i = 0; i < Thread.threadTable.Length; i++) {
|
|
Thread t = Thread.threadTable[i];
|
|
if (t != null) {
|
|
UIntPtr tag = MixinThread(t).ssb.overflowValue;
|
|
if (tag != UIntPtr.Zero) {
|
|
if ((tag & 0x1) == 0) {
|
|
UIntPtr *fieldLoc = (UIntPtr *) tag;
|
|
if (*fieldLoc != UIntPtr.Zero) {
|
|
ptrVisitor.Visit(fieldLoc);
|
|
}
|
|
} else {
|
|
UIntPtr objectPtr = (UIntPtr) (tag - 1);
|
|
Object obj = Magic.fromAddress(objectPtr);
|
|
ptrVisitor.VisitReferenceFields(obj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal override void Reset() {
|
|
int threadCount = 0;
|
|
for (int i = 0; i < Thread.threadTable.Length; i++) {
|
|
Thread t = Thread.threadTable[i];
|
|
if (t != null) {
|
|
threadCount++;
|
|
MixinThread(t).ssb.overflowValue = UIntPtr.Zero;
|
|
MixinThread(t).ssb.cursor = null;
|
|
MixinThread(t).ssb.limit = null;
|
|
}
|
|
}
|
|
AdjustChunkSize(threadCount);
|
|
}
|
|
|
|
private static void AdjustChunkSize(int threadCount) {
|
|
writeBufferIndex = 0;
|
|
// Adjust the size of the write barrier chunks
|
|
int testSize = writeBufferSize;
|
|
while (threadCount > 0) {
|
|
threadCount >>= 1;
|
|
testSize >>= 1;
|
|
}
|
|
if (testSize < chunkSize) {
|
|
VTable.Assert(testSize > 0);
|
|
chunkSize = testSize;
|
|
}
|
|
}
|
|
|
|
internal static int chunkSize;
|
|
internal static UIntPtr *writeBuffer;
|
|
internal static int writeBufferIndex;
|
|
internal static int writeBufferSize;
|
|
|
|
}
|
|
|
|
}
|