/*******************************************************************/ /* 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; using System.Threading; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; /// /// This class defines a queue that is created as a linked list from /// each thread object through pointers stored in object headers. /// /// This queue is used for the concurrent mark sweep collector to allow /// it to trace through the heap without ever requiring allocation /// or locking. /// /// Within a thread this is a FIFO queue; all mutations are made at the /// head. /// internal struct ThreadHeaderQueue { [StructLayout(LayoutKind.Sequential)] [MixinConditional("ConcurrentMSGC")] [Mixin(typeof(PreHeader))] private struct PreHeaderQueue { internal MultiUseWord muw; internal UIntPtr link; } [MixinConditional("ConcurrentMSGC")] [Mixin(typeof(Object))] private class ThreadHeaderQueueObject : System.Object { internal new PreHeaderQueue preHeader; } [Inline] private static ThreadHeaderQueueObject MixinObject(Object obj) { return (ThreadHeaderQueueObject) obj; } [MixinConditional("ConcurrentMSGC")] [MixinConditional("AllThreadMixins")] [Mixin(typeof(Thread))] private class ThreadHeaderQueueThread : Object { [AccessedByRuntime("referenced in brtforgc.asm")] internal ThreadHeaderQueue gcQueue; } [Inline] private static ThreadHeaderQueueThread MixinThread(Thread t) { return (ThreadHeaderQueueThread) (Object) t; } /// /// Contains the pointer to an object that a thread is trying /// to insert. /// internal UIntPtr newHead; /// /// Contains the pointer to the first object in the queue, or Zero /// if the queue is empty. /// internal UIntPtr head; /// /// Contains a pointer to the object that was at the head of the /// queue the last time it was 'stolen' by a consuming thread. /// /// If this matches the current head value then there is nothing /// on the queue that has not been stolen. /// internal UIntPtr stolenHead; // Contains a count of nodes that have been stolen internal static long stolenCount; internal static UIntPtr TAIL_MARKER { get { return ~(UIntPtr)3U; } } /// /// Reset the queue. /// [Inline] internal static void Reset(Thread t) { MixinThread(t).gcQueue.Reset(); } [Inline] internal void Reset() { this.head = TAIL_MARKER; this.newHead = TAIL_MARKER; this.stolenHead = TAIL_MARKER; } /// /// Is the queue empty? /// internal static bool IsEmpty(Thread t) { return MixinThread(t).gcQueue.IsEmpty(); } private bool IsEmpty() { return (this.head == this.stolenHead); } [Inline] private static UIntPtr QueueField(Object obj) { return MixinObject(obj).preHeader.link; } [Inline] private static void SetQueueField(Object obj, UIntPtr value) { MixinObject(obj).preHeader.link = value; } private static bool ExchangeQueueField(Object obj, UIntPtr val, UIntPtr oldVal) { ThreadHeaderQueueObject obj2 = MixinObject(obj); return Interlocked.CompareExchange(ref obj2.preHeader.link, val, oldVal) == oldVal; } /// /// Get the mark value from the header word of the object. /// [Inline] internal static UIntPtr GcMark(Object obj) { VTable.Assert(obj != null, "Can not get mark of null"); return (QueueField(obj) & (UIntPtr)3U); } /// /// Sets the mark value in the header word of the object. /// [Inline] internal static void SetGcMark(UIntPtr objAddr, UIntPtr markBits) { SetGcMark(Magic.fromAddress(objAddr), markBits); } [Inline] internal static void SetGcMark(Object obj, UIntPtr markBits) { SetQueueField(obj, markBits); } internal static bool IsInQueue(Object obj) { return (QueueField(obj) == UIntPtr.Zero); } /// /// Link a new value at the head of the queue. It is assumed that /// if the object is unmarked, the value in the header word will /// simply be 'unmarkedColor'. Returns true if the object was /// marked an linked into the queue. /// [Inline] internal static bool Push(Thread t, UIntPtr objAddr, UIntPtr markedColor, UIntPtr unmarkedColor) { return MixinThread(t).gcQueue.Push(objAddr, markedColor, unmarkedColor); } [Inline] private bool Push(UIntPtr objAddr, UIntPtr markedColor, UIntPtr unmarkedColor) { VTable.Assert(objAddr != UIntPtr.Zero, "Can not push null!"); this.newHead = objAddr; Object obj = Magic.fromAddress(objAddr); if (ExchangeQueueField(obj, this.head + markedColor, unmarkedColor)) { this.head = objAddr; return true; } else { // Someone else enqueued the object, so restore this queue this.newHead = this.head; return false; } } /// /// Link a new value at the head of the queue, knowing that the /// object is a thread local object. /// [Inline] internal static void PushUnsafe(Thread t, Object obj, UIntPtr markBits) { MixinThread(t).gcQueue.PushUnsafe(obj, markBits); } [Inline] private void PushUnsafe(Object obj, UIntPtr markBits) { VTable.Assert(obj != null, "Can not push null!"); SetQueueField(obj, this.head + markBits); this.newHead = Magic.addressOf(obj); this.head = this.newHead; } /// /// Unlink a value from the head of the queue. This /// method is not thread safe. The method is supposed to be /// called only from the collector thread. /// [Inline] internal static Object Pop(Thread thread, UIntPtr markedColor) { return MixinThread(thread).gcQueue.Pop(markedColor); } [Inline] private Object Pop(UIntPtr markedColor) { VTable.Assert(!this.IsEmpty(), "Queue is empty!"); Object obj = Magic.fromAddress(this.head); this.head = QueueField(obj) & ~(UIntPtr)3U; SetQueueField(obj, markedColor); return obj; } /// /// Returns the number of values in the list. It is tolerant /// of concurrent additions, although additions after the initial /// read of the list head will not be counted. It is tolerant /// of concurrent steals from the list, although the count may /// be truncated if the list is stolen from during the count. /// internal int Count { get { UIntPtr cachedStolenHead = this.stolenHead; if (this.head == cachedStolenHead) { return 0; } Object obj = Magic.fromAddress(this.head); UIntPtr next = QueueField(obj) & ~(UIntPtr)3U; int listLength = 1; while (next != cachedStolenHead && this.stolenHead == cachedStolenHead) { listLength++; obj = Magic.fromAddress(next); next = QueueField(obj) & ~(UIntPtr)3U; } return listLength; } } internal static void DeadThreadNotification(Thread deadThread, UIntPtr markedColor) { StealSafe(Thread.CurrentThread, deadThread, markedColor); } /// /// This method attempts to take values from the passed-in queue /// and place it in the 'this' queue. It assumes that the 'this' /// queue is not concurrently added to. However, the method is /// tolerant of concurrent attempts to steal from the fromQueue. /// /// Rather than reading the old mark value from the header word /// of the tail object in the 'fromQueue', the new mark value in /// said object is going to be 'markedColor' /// /// If any values are stolen the method returns true. /// internal static bool Steal(Thread toThread, Thread fromThread, UIntPtr markedColor) { ThreadHeaderQueueThread myToThread = MixinThread(toThread); ThreadHeaderQueueThread myFromThread = MixinThread(fromThread); return myToThread.gcQueue.StealFrom(ref myFromThread.gcQueue, markedColor); } private bool StealFrom(ref ThreadHeaderQueue fromQueue, UIntPtr markedColor) { UIntPtr fromHead, fromTail; if (fromQueue.Steal(out fromHead, out fromTail)) { // Prepend the stolen list segment to our list this.newHead = fromHead; Object tailObject = Magic.fromAddress(fromTail); SetQueueField(tailObject, this.head + markedColor); this.head = fromHead; return true; } else { return false; } } /// /// This method attempts to take values from the passed-in queue /// and place it in the 'this' queue. The method is tolerant of /// concurrent attempts to add to the 'this' queue and is /// tolerant of concurrent attempts to steal from the fromQueue. /// /// Rather than reading the old mark value from the header word /// of the tail object in the 'fromQueue', the new mark value in /// said object is going to be 'markedColor' /// /// If any values are stolen the method returns true. /// internal static void StealSafe(Thread toThread, Thread fromThread, UIntPtr markedColor) { ThreadHeaderQueueThread myToThread = MixinThread(toThread); ThreadHeaderQueueThread myFromThread = MixinThread(fromThread); myToThread.gcQueue.StealFromSafe(ref myFromThread.gcQueue, markedColor); } private void StealFromSafe(ref ThreadHeaderQueue fromQueue, UIntPtr markedColor) { UIntPtr fromHead, fromTail; this.newHead = fromQueue.head; if (fromQueue.Steal(out fromHead, out fromTail)) { VTable.Assert(this.newHead == fromHead); // Prepend the stolen list segment to our list UIntPtr thisHead = this.head; while (Interlocked.CompareExchange(ref this.newHead, fromHead, thisHead) != thisHead) { thisHead = this.head; } // We have won exclusive rights to insert into 'this'. Object tailObject = Magic.fromAddress(fromTail); SetQueueField(tailObject, thisHead + markedColor); this.head = fromHead; } else { this.newHead = this.head; } } private bool Steal(out UIntPtr stolenHead, out UIntPtr stolenTail) { while (true) { UIntPtr thisStolen = this.stolenHead; UIntPtr thisHead = this.head; while (thisStolen != thisHead) { if (Interlocked.CompareExchange(ref this.stolenHead, thisHead, thisStolen) == thisStolen) { // We managed to steal part of the list. // Find the end of the stolen list segment. Object obj = Magic.fromAddress(thisHead); UIntPtr next = QueueField(obj) & ~(UIntPtr)3U; int listLength = 0; while (next != thisStolen) { listLength++; obj = Magic.fromAddress(next); next = QueueField(obj) & ~(UIntPtr)3U; } stolenCount += listLength; stolenHead = thisHead; stolenTail = Magic.addressOf(obj); return true; } thisStolen = this.stolenHead; thisHead = this.head; } if (this.newHead == thisHead) { // There is nothing to steal. stolenHead = UIntPtr.Zero; stolenTail = UIntPtr.Zero; return false; } // Someone must be in the process of inserting something // into the queue. Thread.Yield(); } } } }