/*******************************************************************/ /* 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. // /* This class provides fast-path code for a number of operations which potentially require additional information to be associated with each object in the heap: - Object ownership and versioning information needed by the STM implementation. - Location-insensitive hash codes that have been allocated to objects. - Monitor objects that have been associate with objects. - StringState bits associated with String objects Overview -------- Each object has a value of type MultiUseWord (MUW) held as a header word. This can be used directly for any *one* of the three purposes listed above. If more than one kind of usage is required on the same object then the MUW is "inflated" -- i.e. replaced by a value that indicates an external multi-use object (EMU) which contains space for all three purposes. NB: the StringState and HashCode words share the same storage locations, distinguished by value. We rely on ChooseHashCode to not use small integer values corresponding to the StringState enumeration. In addition, the MUW provides a single per-object 'mark' bit. This is used in the MemoryAccounting module when counting the volume of different kinds of object in the heap. Placing this bit in the MUW (rather than using spare vtable-bits as the GC does) allows memory accounting to be invoked at any time (e.g. after a crash during a GC). MUW structure ------------- The structure of the MUW is as follows: | 31 | 30 2 | 1 0 | | mark | payload | tag | The single mark bit is the MSB. By convention, all modules using the mark bit must (a) work with the world stopped and (b) leave all object's mark bits 0 after their operation. (They may assume this is true when they start). This restriction avoids needing to mask off the mark bit in common code paths in this file. The payload field forms the majority of the MUW. The contents of the payload are taken from bits 2..30 in the MUW, padded with two 0-bits at the least significant end. This means that the payload can hold a pointer to a 4-byte aligned address. The tag values distinguish between four states that the MUW can be in: 00 => The MUW is either unused (if the payload is 0), or holds the STM word for the object (if the payload is non-0). 01 => The payload holds the object's hash code or StringState bits. 10 => The payload refers to the Monitor for the object. 11 => The payload refers to an external multi-use object (EMU). Memory management ----------------- See detailed comments below about interaction between this file and the various garbage collectors. To do ----- - If we can guarantee that the GC will not relocate objects then we can avoid needing to place hashcodes in the MUW. This may slightly reduce the frequency of inflation. - The state used for fast-path monitor operations could be placed directly in the MUW's payload rather than in an external Monitor object. */ // Verbose runtime tracing //#define ENABLE_LOG_TRACING // Profile MUW inflation #if DEBUG #define ENABLE_PROFILING #endif // Occasionally perform unnecessary inflation //#define SHAKE namespace System { using Microsoft.Bartok.Runtime; using System.GCs; using System.Runtime.CompilerServices; using System.Threading; [CCtorIsRunDuringStartup] [NoLoggingForUndo] internal struct MultiUseWord { internal UIntPtr value; // Different tag values internal const uint STM_TAG = 0; internal const uint HASHCODE_TAG = 1; internal const uint MONITOR_TAG = 2; internal const uint INFLATED_TAG = 3; // Structure of the multi-use word internal const uint TAG_MASK = 0x00000003; //---------------------------------------------------------------------- // // Constructors static MultiUseWord() { } internal MultiUseWord(UIntPtr value) { this.value = value; } internal MultiUseWord(uint tag, UIntPtr payload) { VTable.Assert((tag & TAG_MASK) == tag); VTable.Assert((UIntPtr)(payload & PAYLOAD_MASK) == payload); this.value = (UIntPtr)(tag | (uint)payload); DebugPrint("MUW tag={0:x} uintptr-payload={1:x}\n", __arglist(tag, payload)); } internal MultiUseWord(uint tag, Object payload) { UIntPtr addr = Magic.addressOf(payload); VTable.Assert(!(payload is uint)); VTable.Assert((tag == MONITOR_TAG) || (tag == INFLATED_TAG)); VTable.Assert(payload != null); VTable.Assert((addr & PAYLOAD_MASK) == addr); VTable.Assert((addr & (payload.vtable.baseAlignment - 1)) == 0); this.value = (UIntPtr)((UIntPtr)tag |addr); DebugPrint("MUW tag={0:x} object-payload={1:x}\n", __arglist(tag, Magic.addressOf(payload))); } //---------------------------------------------------------------------- // Accessor methods internal uint GetTag() { uint tag = (uint)(this.value & INFLATED_TAG); return tag; } internal bool IsUnused() { bool result = (this.value == UIntPtr.Zero); return result; } internal bool IsMarked() { bool result = ((this.value & MARK_BIT_MASK) != 0); return result; } internal bool IsInflated() { bool result = ((this.value & TAG_MASK) == INFLATED_TAG); return result; } internal bool IsHashcode() { bool result = ((this.value & TAG_MASK) == HASHCODE_TAG); return result; } internal bool IsMonitor() { bool result = ((this.value & TAG_MASK) == MONITOR_TAG); return result; } internal bool IsMonitorOrInflatedTag() { bool result = (this.value & MONITOR_TAG) != 0; return result; } internal bool IsSTM() { bool result = ((this.value & TAG_MASK) == STM_TAG); return result; } internal bool Eq(MultiUseWord other) { return (other.value == this.value); } // Return the payload of the multi-use word as a UIntPtr. internal UIntPtr GetPayload() { UIntPtr result = (this.value & PAYLOAD_MASK); return result; } // Return the payload of the multi-use word as an object reference. // Asserts that the payload is non-null and that the object that it // refers to has strong enough alignment requirements for the number // of payload bits available. internal unsafe Object GetRefPayload() { UIntPtr payload = (UIntPtr) this.GetPayload(); Object result = Magic.fromAddress(payload); VTable.Assert(result != null); VTable.Assert(result.vtable != null); VTable.Assert((payload & (result.vtable.baseAlignment - 1)) == 0); return result; } // Equivalent to "(EMU) GetRefPayload()", but avoids an explicit // cast. This is needed during GCs that use the vtable word. internal unsafe EMU GetEMURefPayload() { UIntPtr payload = (UIntPtr) this.GetPayload(); EMU result = EMU.FromAddress(payload); VTable.Assert(result != null); VTable.Assert((payload & (result.vtable.baseAlignment - 1)) == 0); return result; } //---------------------------------------------------------------------- // // Get and set an object's MUW. [ManualRefCounts] internal static MultiUseWord GetForObject(Object obj) { MultiUseWord result = obj.preHeader.muw; DebugPrint("GetForObject({0:x}) = {1:x}\n", __arglist(Magic.addressOf(obj), result.value)); return result; } internal static void SetForObject(Object obj, MultiUseWord w) { DebugPrint("SetForObject({0:x}, {1:x})\n", __arglist(Magic.addressOf(obj), w.value)); obj.preHeader.muw = w; } internal static MultiUseWord CompareExchangeForObject(Object obj, MultiUseWord newWord, MultiUseWord oldWord) { DebugPrint("CompareExchangeForObject({0:x}, new={1:x}, old={2:x})\n", __arglist(Magic.addressOf(obj), newWord.value, oldWord.value)); UIntPtr saw = Interlocked.CompareExchange(ref obj.preHeader.muw.value, newWord.value, oldWord.value); DebugPrint("CompareExchangeForObject saw {0:x}\n", __arglist(saw)); return new MultiUseWord(saw); } // Get and set the underlying UIntPtr value held in the same // header word that is used for the MUW. These methods are needed // in MemoryAccounting where the header field is re-used (after // making a private copy of the MUW value). internal static UIntPtr GetValForObject(Object obj) { UIntPtr result = obj.preHeader.muw.value; DebugPrint("GetValForObject({0:x}) = {1:x}\n", __arglist(Magic.addressOf(obj), result)); return result; } internal static void SetValForObject(Object obj, UIntPtr w) { DebugPrint("SeValtForObject({0:x}, {1:x})\n", __arglist(Magic.addressOf(obj), w)); obj.preHeader.muw.value = w; } internal static UIntPtr PAYLOAD_MASK { get { return (UIntPtr.Size == 8)? (UIntPtr)0x7ffffffffffffffc : (UIntPtr)0x7ffffffc; } } internal static UIntPtr MARK_BIT_MASK { get { return (UIntPtr.Size == 8)? (UIntPtr)0x8000000000000000 : (UIntPtr)0x80000000; } } //---------------------------------------------------------------------- // // Shake debugging // // If SHAKE is #defined then we periodically choose to inflate an // object's multi-use-word when it is not necessary to do so. This // will help test coverage: this design is based on the assumption that // inflation is rare, so let's make sure that it actually works! // // Code guarded by "if (Occasionally()) { ... }" will run occasionally // when shake debugging is enabled, and should be entirely elided when // it is disabled. // // NB: Occasionally() is not thread safe, but note that even if races // allow shakeCtr to overshoot shakeLim then it doesn't matter: we'll // just go around another shakeLim times. private static int shakeCtr = 0; private static int shakeLim = 1; [Inline] internal static bool Occasionally() { bool result = false; #if SHAKE if ((shakeCtr++) % shakeLim == 0) { shakeLim += 10; shakeCtr = 1; result = true; } #endif return result; } //---------------------------------------------------------------------- // // Mark bit internal static bool IsMarked(Object obj) { MultiUseWord muw = GetForObject(obj); bool result; result = muw.IsMarked(); return result; } internal static void SetMark(Object obj, bool mark) { MultiUseWord muw = GetForObject(obj); MultiUseWord newMuw; if (mark) { newMuw = new MultiUseWord(muw.value | (UIntPtr)MARK_BIT_MASK); } else { newMuw = new MultiUseWord(muw.value & (UIntPtr)(~MARK_BIT_MASK)); } SetForObject(obj, newMuw); } //---------------------------------------------------------------------- // // Monitor // // The implementation of Monitor-related and Hashcode-related operations // is largely the same: // // - A "Get..." method deals with the fast path operations of either: // returning the Monitor/Hashcode directly from the MUW or from the // field containing it in the EMU. // // - A "Get...Slow" method is responsible for the slow path operations // of allocating a new monitor/hashcode and inflating a MUW if // necessary. [NoInline] internal static Monitor GetMonitorSlow(Object obj, MultiUseWord muw) { Monitor result = null; DebugPrint("GetMonitorSlow obj={0:x} muw={1:x}\n", __arglist(Magic.addressOf(obj), muw.value)); // ................................................................. // // Firstly: try to allocate a Monitor object and make this reachable // directly from the MUW. if (muw.IsUnused() && (!Occasionally())) { Thread thread = Thread.CurrentThread; MultiUseWord newWord; DebugPrint("MUW currently unused for {0:x}, allocating monitor\n", __arglist(Magic.addressOf(obj))); // Remember: either of these allocations may trigger a GC. Monitor m = new Monitor(); #if REFERENCE_COUNTING_GC || DEFERRED_REFERENCE_COUNTING_GC DebugPrint("+rc on monitor {0:x}\n", __arglist(Magic.addressOf(m))); IncrementRefCount(m); #else EMU emu = new EMU(); IncrementMonitorEMUAllocCount(); emu.Target = obj; emu.Monitor = m; #endif newWord = new MultiUseWord(MONITOR_TAG, m); DebugPrint("MUW currently unused for {0:x}, trying to install {1:x}\n", __arglist(Magic.addressOf(obj), newWord.value)); MultiUseWord now = CompareExchangeForObject(obj, newWord, muw); #if REFERENCE_COUNTING_GC || DEFERRED_REFERENCE_COUNTING_GC if (now.Eq(muw)) { DebugPrint("Installed it\n"); RegisterLostObjForVerifyHeap(obj, m); } else { DebugPrint("-rc on monitor {0:x}\n", __arglist(Magic.addressOf(m))); DecrementRefCount(m); } #else if (now.Eq(muw)) { DebugPrint("Installed it\n"); emu.Register(); } else { IncrementAbandonedEMUsCount(); } #endif muw = GetForObject(obj); DebugPrint("MUW now {0:x}\n", __arglist(muw.value)); } // ................................................................. // // Secondly: either pick up the Monitor reachable from the MUW, // or from the EMU (inflating if necessary). uint tag = muw.GetTag(); if (tag == MONITOR_TAG) { result = (Monitor)(muw.GetRefPayload()); } else { if (tag != INFLATED_TAG) { Inflate(obj); IncrementInflationToCount(MONITOR_TAG); muw = GetForObject(obj); tag = muw.GetTag(); } VTable.Assert(tag == INFLATED_TAG, "Multi-use word not inflated in GetMonitorSlow"); EMU emu = muw.GetEMURefPayload(); VTable.Assert(emu.monitor != UIntPtr.Zero, "Monitor should not be null"); result = emu.Monitor; } DebugPrint("Got monitor {0:x}\n", __arglist(Magic.addressOf(result))); return result; } internal static Monitor GetMonitor(Object obj) { Monitor result; DebugPrint("GetMonitor[Fast] obj={0:x} on {1:x} type {2:x}\n", __arglist(Magic.addressOf(obj), PageTable.Page(Magic.addressOf(obj)), PageTable.MyType(PageTable.Page(Magic.addressOf(obj))))); MultiUseWord muw = GetForObject(obj); uint tag = muw.GetTag(); DebugPrint("GetMonitor[Fast] muw={0:x} tag={1:x}\n", __arglist(muw.value, tag)); if (tag == MONITOR_TAG) { result = (Monitor)muw.GetRefPayload(); } else { if (tag == INFLATED_TAG) { EMU emu = muw.GetEMURefPayload(); if (emu.Monitor != null) { result = emu.Monitor; } else { result = GetMonitorSlow(obj, muw); } } else { result = GetMonitorSlow(obj, muw); } } DebugPrint("Got monitor {0:x}\n", __arglist(Magic.addressOf(result))); return result; } //---------------------------------------------------------------------- // // Hashcode // // See comments above regarding monitors. The implementation of // hashcodes follows the same general pattern aside from the addition // of Get/SetStringState. [NoInline] internal static int GetHashCodeSlow(Object obj, MultiUseWord muw) { int result; DebugPrint("GetHashCodeSlow obj={0:x} muw={1:x}\n", __arglist(Magic.addressOf(obj), muw.value)); // ................................................................. // // Firstly: try to choose a hash code place this directly in the MUW. if (muw.IsUnused() && (!Occasionally())) { MultiUseWord newWord; DebugPrint("MUW currently unused for {0:x}, choosing hash code\n", __arglist(Magic.addressOf(obj))); int h = ChooseHashCode(obj); newWord = new MultiUseWord(HASHCODE_TAG, (UIntPtr) h); DebugPrint("MUW currently unused for {0:x}, trying to install {1:x}\n", __arglist(Magic.addressOf(obj), newWord.value)); CompareExchangeForObject(obj, newWord, muw); muw = GetForObject(obj); DebugPrint("MUW now {0:x}\n", __arglist(muw.value)); } // ................................................................. // // Secondly: either pick up the hash code from the MUW, or from the EMU // (inflating if necessary). uint tag = muw.GetTag(); if (tag == HASHCODE_TAG) { result = (int)(muw.GetPayload()); } else { if (tag != INFLATED_TAG) { Inflate(obj); IncrementInflationToCount(HASHCODE_TAG); muw = GetForObject(obj); tag = muw.GetTag(); } VTable.Assert(tag == INFLATED_TAG, "Multi-use word not inflated in GetHashCodeSlow"); EMU emu = muw.GetEMURefPayload(); result = emu.HashCode; } DebugPrint("Got hash code {0:x}\n", __arglist(result)); return result; } internal static int GetHashCode(Object obj) { int result; DebugPrint("GetHashCode[Fast] obj={0:x}\n", __arglist(Magic.addressOf(obj))); MultiUseWord muw = GetForObject(obj); uint tag = muw.GetTag(); DebugPrint("GetHashCode[Fast] muw={0:x} tag={1:x}\n", __arglist(muw.value, tag)); if (tag == HASHCODE_TAG) { result = (int)(muw.GetPayload()); } else { if (tag == INFLATED_TAG) { EMU emu = muw.GetEMURefPayload(); result = emu.HashCode; } else { return GetHashCodeSlow(obj, muw); } } DebugPrint("Got hash code {0:x}\n", __arglist(result)); return result; } internal static StringState GetStringState(String str) { int result = GetHashCode(str); VTable.Assert(((result == (int)StringState.Undetermined) || (result == (int)StringState.HighChars) || (result == (int)StringState.FastOps) || (result == (int)StringState.SpecialSort))); return (StringState) result; } // Forcible set the StringState bits in the hashcode word. // // There is a subtle, benign and unlikely race in this code: // we may execute Interlocked.CompareExchange on the emu.HashCode // field at exactly the same time that another thread re-inflates // the object. The current code loops until it successfully // writes the intended value into the hashcode field. However, // note that if concurrent threads call SetHashCode then it might // be possible for a value to be re-written: the first thread // may update emu.HashCode, a second thread re-inflates the // object, a third thread then calls SetHashCode successfully // and then the first thread observes the race with the second // and writes its value again. This race is benign for the // case of StringState flags: the value written depends on the // immutable contents of the string. internal static void SetStringState(String str, StringState st) { bool done = false; DebugPrint("SetStringState str={0:x} hashcode={1:x}\n", __arglist(Magic.addressOf(str), st)); do { MultiUseWord oldMUW = GetForObject(str); uint tag = oldMUW.GetTag(); DebugPrint("SetStringState muw={0:x} tag={1:x}\n", __arglist(oldMUW.value, tag)); if ((oldMUW.IsUnused() && (!Occasionally())) || (tag == HASHCODE_TAG)) { MultiUseWord newMUW = new MultiUseWord(HASHCODE_TAG, (UIntPtr)st); MultiUseWord sawMUW = CompareExchangeForObject(str, newMUW, oldMUW); done = (sawMUW.Eq(oldMUW)); } else { if (tag != INFLATED_TAG) { Inflate(str); oldMUW = GetForObject(str); tag = oldMUW.GetTag(); } VTable.Assert(tag == INFLATED_TAG, "Multi-use word not inflated in SetStringState"); EMU emu = oldMUW.GetEMURefPayload(); int oldStringState = emu.hashCode; int sawStringState = Interlocked.CompareExchange(ref emu.hashCode, (int)st, oldStringState); MultiUseWord newMUW = GetForObject(str); done = ((sawStringState == oldStringState) && (newMUW.Eq(oldMUW))); } } while (!done); VTable.Assert(GetHashCode(str) == (int)st, "HashCode does not match StringState"); } // Choose a hashcode to use for the supplied object. The resulting // hashcode must fit in the payload in the MUW; in practice an address // can be used, but it may be worth revisiting this choice if using // a small nursery. internal static int ChooseHashCode(Object obj) { int result; if (obj is String) { // If the object is a string then this word holds its // StringState value instead. We call ChooseHashCode // when getting an object hash code for the first time, // or when inflating it. Only the latter applied for // String objects. result = 0; } else { // Hash code is int32. Unfortunately we cannot change that assumption. // The following cast will return only the lower bits in x64. // But it should not affect correctness. result = (int) (Magic.addressOf(obj) & PAYLOAD_MASK); } DebugPrint("Chosen hash code {0:x} for {1:x}\n", __arglist(result, Magic.addressOf(obj))); return result; } #if !VC //---------------------------------------------------------------------- // // STM // // The interaction between the STM and the multi-use-word is more complex // than that of GetMonitor and GetHashCode. // // The abstractions used are as follows: // // - A STMHandle provides a reference to the MUW on a // given object. It provides all of the methods through which the // STM implementation accesses the MUW. // // - A STMSnapshot represents a saved view of the MUW at a given // instant in time. The snapshot is used when validating a // transaction: if the current snapshot matches a saved snapshot // then no update has been committed to the object in-between. // // - A STMWord represents the meta-data that the STM itself wishes // to maintain about the object. Its structure is defined in // TryAll.cs, but in outline, it either holds a version number for // the object, or holds a pointer to an UpdateEnlistmentLog.Entry // structure for the transaction that's currently got the object // opened for update. // // When an object's MUW is used only for STM operations then the // STMSnapshot and the STMWord both come directly from the MUW. // // When an object has been used for one purpose other than the STM // then its STMWord is conceptually 0 (this is safe: it's not been // used by the STM so no updates have been committed to it). // // If an object has been inflated then the value in the MUW still // forms the STMSnapshot, but the STMWord is displaced to the // EMU. [NoLoggingForUndo] internal struct STMSnapshot { internal UIntPtr value; internal STMSnapshot (UIntPtr value) { #if REFERENCE_COUNTING_GC || DEFERRED_REFERENCE_COUNTING_GC VTable.Assert(false, "STM not supported with RC collector"); #endif this.value = value; } // Map from an STMSnapshot to an STMWord. Ordinarily we // hope the object has an STM_TAG and the STMWord comes directly // from the snapshot. [Inline] internal STMWord GetSTMWord() { uint snapshotTag = (this.value & TAG_MASK); STMWord result; if (snapshotTag == STM_TAG) { // STMWord is held in the MUW's payload result = new STMWord(this.value); } else if (snapshotTag == INFLATED_TAG) { // STMWord is held out-of-line in the EMU EMU emu = EMU.FromAddress((UIntPtr) (this.value & PAYLOAD_MASK)); result = emu.stmWord; } else { // STMWord is conceptually zero: the object has a hashcode // or Monitor, but has never been opened for update by the STM. result = new STMWord(UIntPtr.Zero); } DebugPrint("Got word {0:x} from snapshot {1:x}\n", __arglist(result.value, this.value)); return result; } // Fast test of whether a snapshot is quiescent, on the assumption // that the snapshot is likely to be an STM word. [Inline] internal uint ZeroIfMustBeQuiescent() { VTable.Assert(STM_TAG == 0); return (this.value & (TAG_MASK | STMWord.IS_OWNED_MASK)); } // Visit an STMSnapshot during GC. We need to update any // pointer-derived values that it contains. There are two // cases: // // a. It refers to a Monitor or an EMU structure: extract the // pointer, visit that, and then pack it back up with the // appropriate tag. // // b. It contains an STMWord: unpack that, then call the // STM's method than knows how to deal with STMWords. internal unsafe void Visit(NonNullReferenceVisitor v) { MultiUseWord muw = new MultiUseWord(this.value); DebugPrint("Visit STM snapshot {0:x}\n", __arglist(muw.value)); uint tag = muw.GetTag(); if (tag == MONITOR_TAG || tag == INFLATED_TAG) { // Pointer-derived value managed by MultiUseWord.cs UIntPtr payload = (UIntPtr) muw.GetPayload(); v.Visit(&payload); muw = new MultiUseWord(tag, payload); } if (tag == STM_TAG) { // Opaque value (possibly pointer-derived) managed by // TryAll.cs STMWord stmWord = GetSTMWord(); stmWord.Visit(v); muw = new MultiUseWord(tag, stmWord.value); } DebugPrint("Visit STM snapshot, result = {0:x}\n", __arglist(muw.value)); this.value = muw.value; } internal bool Eq(STMSnapshot other) { return (this.value == other.value); } internal bool Neq(STMSnapshot other) { return (this.value != other.value); } } [NoLoggingForUndo] internal struct STMHandle { internal UIntPtr addr; #if !DEBUG [Inline] [DisableBoundsChecks] [DisableNullChecks] #endif internal unsafe STMHandle(Object obj) { #if REFERENCE_COUNTING_GC || DEFERRED_REFERENCE_COUNTING_GC VTable.Assert(false, "STM not supported with RC collector"); #endif this.addr = Magic.addressOf(obj); } #if !DEBUG [Inline] [DisableBoundsChecks] [DisableNullChecks] #endif internal unsafe STMSnapshot GetSTMSnapshot() { Object obj = Magic.fromAddress(this.addr); return new STMSnapshot(obj.preHeader.muw.value); } internal unsafe void SetSTMWordAtAllocation(STMWord s) { VTable.Assert(GetSTMSnapshot().value == UIntPtr.Zero); VTable.Assert((s.value & TAG_MASK) == STM_TAG); Object obj = Magic.fromAddress(this.addr); obj.preHeader.muw.value = s.value; } internal unsafe void SetSTMWordAtGC(NonNullReferenceVisitor rv, STMWord s) { VTable.Assert((s.value & TAG_MASK) == STM_TAG); UIntPtr curSnapVal = GetSTMSnapshot().value; uint curTag = (curSnapVal & TAG_MASK); if (curTag == STM_TAG) { Object obj = Magic.fromAddress(this.addr); obj.preHeader.muw.value = s.value; } else { UIntPtr ptrVal = (UIntPtr)curSnapVal & (UIntPtr)PAYLOAD_MASK; rv.Visit(&ptrVal); EMU emu = EMU.FromAddress(ptrVal); emu.stmWord.value = s.value; } } // Attempt to update the STMWord for the object that this handle // refers to. // // If the object has not been inflated then the STM word is held // directly in the MUW: try to update it with a compare and swap. // // If the object has been inflated then the STM word is held out-of-line // in the EMU. Map from the supplied snapshot to the EMU, and then // do a compare and swap on the STMWord field that it contains. internal unsafe bool CompareExchangeSTMWord(UIntPtr newWord, UIntPtr oldWord, STMSnapshot oldSnapshot) { UIntPtr oldSnapVal = oldSnapshot.value; VTable.Assert((newWord & TAG_MASK) == 0); VTable.Assert((oldWord & TAG_MASK) == 0); uint oldTag = (oldSnapVal & TAG_MASK); bool result; VTable.Assert((newWord & MARK_BIT_MASK) == 0); VTable.Assert((oldWord & MARK_BIT_MASK) == 0); DebugPrint("CompareExchangeSTMWord on {0:x}, {1:x}/{2:x} -> {3:x}\n", __arglist(this.addr, oldSnapVal, oldWord, newWord)); if (oldTag == STM_TAG) { VTable.Assert(oldWord == oldSnapshot.value); Object obj = Magic.fromAddress(this.addr); result = (Interlocked.CompareExchange(ref obj.preHeader.muw.value, newWord, oldWord) == oldWord); } else { if (oldTag != INFLATED_TAG) { DebugPrint("CompareExchangeSTMWord triggered inflation\n"); Object obj = Magic.fromAddress(this.addr); Inflate(obj); IncrementInflationToCount(STM_TAG); oldSnapVal = GetForObject(obj).value; } EMU emu = new MultiUseWord(oldSnapVal).GetEMURefPayload(); result = (Interlocked.CompareExchange(ref emu.stmWord.value, newWord, oldWord) == oldWord); } return result; } internal unsafe void RefreshSTMWord(STMWord newWord) { DebugPrint("RefreshSTMValue on {0:x} to {1:x}\n", __arglist(this.addr, newWord.value)); Object obj = Magic.fromAddress(this.addr); UIntPtr curVal = obj.preHeader.muw.value; uint curTag = (curVal & TAG_MASK); VTable.Assert((curTag == STM_TAG) || (curTag == INFLATED_TAG)); if (curTag == STM_TAG) { VTable.Assert((newWord.value & TAG_MASK) == 0); UIntPtr saw = Interlocked.CompareExchange(ref obj.preHeader.muw.value, newWord.value, curVal); // NB: we have the object opened for update, and so if our // Interlocked.CompareExchange failed then it must be because another // thread triggered inflation. if (saw != curVal) { VTable.Assert((saw & TAG_MASK) == INFLATED_TAG); ReInflate(obj, newWord); } if (Occasionally()) { MultiUseWord m = GetForObject(obj); if (!(m.IsInflated())) { Inflate(obj); IncrementInflationToCount(STM_TAG); } } } else { ReInflate(obj, newWord); } } // Visit the object that the STMHandle refers to. This is usually used // with a visitor that treats the reference as weak; we return false // if the visitor null's out the object reference. internal unsafe bool Visit(NonNullReferenceVisitor v) { UIntPtr t = this.addr; v.Visit(&t); if (t == UIntPtr.Zero) { return false; } else { this.addr = t; return true; } } internal unsafe bool IsMarked() { Object obj = Magic.fromAddress(this.addr); MultiUseWord muw = obj.preHeader.muw; return muw.IsMarked(); } internal unsafe void SetMark(bool mark) { Object obj = Magic.fromAddress(this.addr); MultiUseWord muw = obj.preHeader.muw; MultiUseWord newMuw; if (mark) { newMuw = new MultiUseWord(muw.value | (UIntPtr)MARK_BIT_MASK); } else { newMuw = new MultiUseWord(muw.value & (UIntPtr)(~MARK_BIT_MASK)); } obj.preHeader.muw = newMuw; } internal static void ReInflate(Object obj, STMWord newWord) { MultiUseWord saw; EMU curEMU; // Allocate new EMU first so that we do not have to consider // GC during the rest of this function. EMU newEMU = new EMU(); IncrementReInflationCount(); // Transfer current emu contents to the new EMU. saw = GetForObject(obj); VTable.Assert(saw.IsInflated()); VTable.Assert(!saw.IsMarked()); curEMU = saw.GetEMURefPayload(); VTable.Assert(curEMU.Target == obj); newEMU.Target = obj; newEMU.Monitor = curEMU.Monitor; newEMU.HashCode = curEMU.HashCode; newEMU.stmWord = newWord; // Install new EMU in place of existing one curEMU.Target = null; SetForObject(obj, new MultiUseWord(INFLATED_TAG, newEMU)); newEMU.Register(); DebugPrint("ReInflation: EMU {0:x} becomes {1:x}\n", __arglist(Magic.addressOf(curEMU), Magic.addressOf(newEMU))); } } #endif //---------------------------------------------------------------------- // // Cause the multi-use word on "obj" to be inflated. // // This method guarantees that obj is inflated before it returns: // however, it may be that the inflation itself is performed by a // second thread if multiple threads race to inflate the same object. // // For simplicity of code we allocate a Monitor for each inflated // object. This will cause unnecessary monitor allocations in // applications where GetHashCode and STM operations are performed // on the same object (otherwise, at least one of the operations // requiring the MUW is a GetMonitor call). // // Why can't we just leave the Monitor field NULL and allocate the // Monitor lazily? The problem is how to avoid a race with the // STM which must do the same thing with its field in the EMU // object -- suppose thread A is about to re-inflate an object // just when thread B is about to install a Monitor in the EMU. // The monitor would be lost. // // If the extra Monitor allocations are a problem then we could // use a more complicated handshaking scheme whereby a thread that // is reinflating an object must install a distinct value in the // EMU's monitor field. internal static void Inflate(Object obj) { MultiUseWord saw = GetForObject(obj); while (!saw.IsInflated()) { VTable.Assert(!saw.IsInflated()); VTable.Assert(!saw.IsMarked()); // Remember: extract any reference-typed payload from "saw" // before we do anything that might trigger a GC. uint sawTag = saw.GetTag(); int hashCode = (sawTag == HASHCODE_TAG) ? (int)(saw.GetPayload()) : ChooseHashCode(obj); Monitor monitor = (sawTag == MONITOR_TAG) ? (Monitor)(saw.GetRefPayload()) : new Monitor(); EMU emu = new EMU(); IncrementFullEMUAllocCount(); #if REFERENCE_COUNTING_GC || DEFERRED_REFERENCE_COUNTING_GC if (sawTag != MONITOR_TAG) { DebugPrint("+rc on monitor {0:x}\n", __arglist(Magic.addressOf(monitor))); IncrementRefCount(monitor); } DebugPrint("+rc on emu {0:x}\n", __arglist(Magic.addressOf(emu))); IncrementRefCount(emu); #endif emu.Target = obj; emu.Monitor = monitor; emu.HashCode = hashCode; #if !VC emu.stmWord = ((saw.value & TAG_MASK) == STM_TAG) ? new STMWord(saw.value) : new STMWord(UIntPtr.Zero); #endif MultiUseWord newWord = new MultiUseWord(INFLATED_TAG,emu); DebugPrint("Inflating {0:x} from {1:x} to {2:x} with hashcode {3:x}\n", __arglist(Magic.addressOf(obj), saw.value, newWord.value, emu.HashCode)); MultiUseWord now = CompareExchangeForObject(obj, newWord, saw); if (now.Eq(saw)) { // Inflation successful #if REFERENCE_COUNTING_GC || DEFERRED_REFERENCE_COUNTING_GC RegisterLostObjForVerifyHeap(obj, emu); #else emu.Register(); #endif IncrementInflationFromCount(saw.GetTag()); break; } else { // Inflation not successful #if REFERENCE_COUNTING_GC || DEFERRED_REFERENCE_COUNTING_GC if (sawTag != MONITOR_TAG) { DebugPrint("-rc on monitor {0:x}\n", __arglist(Magic.addressOf(monitor))); DecrementRefCount(monitor); } DebugPrint("-rc on emu {0:x}\n", __arglist(Magic.addressOf(emu))); DecrementRefCount(emu); #endif IncrementAbandonedEMUsCount(); } saw = now; } } //---------------------------------------------------------------------- // // GC support (reference counting collector) // // NB: STM is not supported with the reference counting collector. This // is a limitation in TryAll.cs, not specifically here. // // The Monitor and EMU objects allocated here are managed as follows: // // - The compiler automatically inserts RC modifications when // casting the MUW's payload to a reference type (either a Monitor or // an EMU). // // - We manually increment and decrement the count on a Monitor or // EMU object while it is reachable from the multi-use word. // That is, we increment the count just before making it reachable // from the word, and we decrement the count (a) when the object // it's associated has count of 0, or (b) when we inflate a Monitor // with an EMU. // // - The Monitor and EMU objects are _not_ threaded on a list as they // are with tracing collectors. The tracing collectors need the // list so they can visit the Monitor and EMU objects that are alive // only because of references from multi-use words (the alternative // is for the GC to visit all MultiUseWords, but we anticipate most // holding scalars). // // The RC collector is responsible for calling RefCountGCDeadObjHook (below) // on each object that has a 0 count. Since we're called from within // the RC collector we use ManualRefCounts and tread carefully when // dealing with pointer-derived values -- i.e. directly extracting // them without automatic RC updates rather than going through the usual // accessors. [ManualRefCounts] internal static void RefCountGCDeadObjHook(MultiUseWord muw) { #if REFERENCE_COUNTING_GC || DEFERRED_REFERENCE_COUNTING_GC uint tag = muw.GetTag(); VTable.Assert(tag == MONITOR_TAG || tag == INFLATED_TAG, "tag == MONITOR_TAG || tag == INFLATED_TAG"); UIntPtr payload = (UIntPtr) muw.GetPayload(); Object refPayload = Magic.fromAddress(payload); if (tag == MONITOR_TAG) { Monitor m = (Monitor) refPayload; DebugPrint("-rc on monitor on dead obj {0:x}\n", __arglist(Magic.addressOf(m))); DecrementRefCount(m); } else { EMU emu = (EMU) refPayload; Monitor m = (Monitor) Magic.fromAddress(emu.monitor); DebugPrint("-rc on monitor on dead obj {0:x}\n", __arglist(Magic.addressOf(m))); DecrementRefCount(m); DebugPrint("-rc on emu on dead obj {0:x}\n", __arglist(Magic.addressOf(emu))); DecrementRefCount(emu); } #endif } //---------------------------------------------------------------------- // // GC support (tracing collectors) // // We must co-operate with the GC in order to (a) ensure that active EMU // objects and the Monitors reachable from them are treated as reachable, // (b) allow EMU to be reclaimed when their target is NULL or when their // target has died, and (c) update object header words if a collector // moves an EMU object. // // The implementation must be careful to work with all three tracing // collectors because of the different ways in which they process the // heap during collection (mark-sweep, semispace, sliding). In overview: // // Mark-sweep : This is the easy case. Objects are not moved and // their fields remain traversable during collection. // A bit in the vtable word is used as a mark bit and // so we cannot make ordinary method calls on an object // if it may be marked, and we cannot perform other // operations that use the vtable (e.g. checked casts). // // Semispace : Objects are moved during collection, with the vtable // word holding a forwarding pointer. As above, we must // avoid operations that may require the vtable. Furthermore, // if we are going to update an object during GC, we must // make sure that we update the to-space version and not // the from-space version. We deal with that by ensuring // that all objects accessed have already been visited. // // Sliding: This is a mark-compact collector. During the mark phase // an object's vtable word forms the head of a list of // references that will need to be updated. This list // is threaded through the object references themselves, // so we cannot traverse object-typed fields once an // object's fields have been visited. We avoid this by // building our own shadow list of EMU objects: we do not // visit the emuGCListShadow and nextShadow fields which // make up this list. // // We receive callbacks from these tracing collectors at four times: // // 1. PreGCHook is called with the world stopped but before any marking // is done on the EMU objects. At this point we collect recently // allocated EMU objects from per-thread lists onto a global list at // emuGCList. If we're using the sliding collector then we build a // duplicate shadow list. // When onlyNew parameter is true, we only scan recently allocated // EMU objects from per-thread list between GC. // When onlyNew parameter is false, we scan all the EMU objects // (newly allocated EMU + EMU that live after previous GC -- // emuGCListAll..emuGCListAllTail) // // 2. VisitStrongRefs is called during the mark phase (for mark-sweep // and sliding collectors) or during copying (for semispace). It is // responsible for visiting the EMU objects and sometimes the Monitor // objects reachable from them. The references to these are strong: // unneeded EMUs are unlinked from emuGCList at the end of a GC cycle // and reclaimed in the next. // // Monitor objects can be reclaimed if they are not in use (that is, // they are not held nor being waited on by any objects). In this // case, they are treated as weak references. If a thread has a // local reference to an object's monitor and is about to enter when // a GC occurs, then reclaiming the monitor will result in a lock // on a disconnected monitor. This only current works for // stop-the-world collectors as we would effectively need a read // barrier on the monitor field to avoid the locking/reclaiming // race - so collectors marked IsOnTheFlyCollector will always // treat the references to monitors as strong. // // NB: during VisitStrongRefs we cannot assume that the EMUs have not // already been visited. This may happen if e.g. they lie on the same // page as a pinned object. This is why we use a visit-before-use // access pattern. // // 3. VisitWeakRefs is called after the mark phase (for mark-sweep and // sliding collectors) or after copying (for semispace). It is no // longer possible to mark further objects, but it is possible to // inspect the mark bits via the weak ptr visitor that's passed in. // During this phase we find all of the EMUs whose targets have // died. We record this by nulling out the EMU's target field. // // 4. PostGCHook is called just before restarting the world. It can // traverse the heap normally again. It is responsible for updating // the MUWs in the objects headers to refer to the new location of // the EMU or Monitor. // At the end of PostGCHook, we update emuGCListAll so it has all // the surviving EMU objects. We also clear emuGCList. // // Because of the collectors' use of the vtable word, we are careful to // (a) avoid casts during VisitStrongRefs and VisitWeakRefs (hence // EMU.FromAddress, rather than Magic.fromAddress + cast), and // (b) avoid virtual method calls during VisitStrongRefs and VisitWeakRefs // (hence EMU.ToAddress is a static method). // TODO: Documentation above does not discuss concurrent collector. // Are there race conditions involving thread termination and collection // on emuGCList? private static UIntPtr emuGCList; private static UIntPtr emuGCListShadow; private static UIntPtr emuGCListAll; private static UIntPtr emuGCListAllTail; // Collect EMUs that survived previous collections (unless 'onlyNew' // is set). internal static void CollectFromPreviousCollections(bool onlyNew) { if (onlyNew) { // emuGCListAll has entries that we don't want to scan. We will // add the surviving entries to that list at the end of // PostGCHook. // // emuGCListAll and emuGCListAllTail are both valid for the list // of older items. } else { // We will prepend emuGCListAll to what we already have in // emuGCList (from thread termination) and then scan all of the // entries. if(emuGCListAllTail != UIntPtr.Zero) { EMU.FromAddress(emuGCListAllTail).next = emuGCList; emuGCList = emuGCListAll; } // emuGCListAll and emuGCListAllTail are now bogus and will be // replaced at the end of PostGCHook. } } // Called from 'thread', or with the world stopped. Collects // EMUs allocated by the thread onto the global list. internal unsafe static void CollectFromThread(Thread thread) { if (thread.externalMultiUseObjAllocListHead != UIntPtr.Zero) { DebugPrint("CollectFromThread: thread {0:x}\n", __arglist(Magic.addressOf(thread))); EMU firstEmu = EMU.FromAddress(thread.externalMultiUseObjAllocListHead); EMU lastEmu = EMU.FromAddress(thread.externalMultiUseObjAllocListTail); DebugPrint("CollectFromThread: got {0:x}..{1:x}\n", __arglist(Magic.addressOf(firstEmu), Magic.addressOf(lastEmu))); while (true) { UIntPtr currentFirst = emuGCList; lastEmu.next = currentFirst; // Splice per-thread list onto the global list. NB: in // most cases this can be a direct assignment, but in principle // a CAS is needed to deal with a race where two threads // finish concurrently. if (Interlocked.CompareExchange(ref emuGCList, Magic.addressOf(firstEmu), currentFirst) == currentFirst) { DebugPrint("CollectFromThread: installed\n"); thread.externalMultiUseObjAllocListHead = UIntPtr.Zero; thread.externalMultiUseObjAllocListTail = UIntPtr.Zero; break; } else { DebugPrint("CollectFromThread: race, retrying install\n"); } } } } // Collect recently allocated EMUs from per-thread lists onto // the global EMU list hanging off emuGCList. private static void CollectFromThreads(bool onlyNew) { Thread[] threadTable = Thread.threadTable; int limit = threadTable.Length; for (int i = 0; i < limit; i++) { Thread thread = threadTable[i]; if (thread != null) { CollectFromThread(thread); } } } // Called with the world stopped, before marking begins. Responsible // for updating our list of all EMU objects. internal unsafe static void PreGCHook(bool useShadows) { PreGCHook(useShadows, false); } internal unsafe static void PreGCHook(bool useShadows, bool onlyNew) { DebugPrint("GC: PrepareCollectEMUs\n"); // Collect EMUs that survived previous collections (unless 'onlyNew' // is set). CollectFromPreviousCollections(onlyNew); // Collect recently allocated EMUs from per-thread lists onto // the global EMU list hanging off emuGCList. CollectFromThreads(onlyNew); if (useShadows) { // Build shadow lists if necessary. These will not be visited during // GC: they're needed for the sliding collector which updates // fields that are visited during the mark phase. emuGCListShadow = emuGCList; EMU i = EMU.FromAddress(emuGCListShadow); while (i != null) { i.NextShadow = i.Next; i = i.Next; } } // count the number of emu in the list. ComputeEmuCount(useShadows); } // Called during the mark phase. Responsible for keeping alive the // EMU objects and the Monitors reachable from them. internal unsafe static void VisitStrongRefs(NonNullReferenceVisitor strongReferenceVisitor, bool useShadows) { DebugPrint("GC: VisitStrongRefs\n"); #if REFERENCE_COUNTING_GC || DEFERRED_REFERENCE_COUNTING_GC VisitStrongRefsRC(strongReferenceVisitor); #else VisitStrongRefsNonRC(strongReferenceVisitor, useShadows); #endif } // With the RC collector VisitStrongRefs is only called during VerifyHeap // when traversing the heap to find the objects that are reachable. We // need to visit any Monitor and EMU objects that are kept alive via // MUWs. // // Because this is not fast-path code, we just visit every object in the // heap and visit reference-derived values in the MUW field, and (for inflated // objects) in the Monitor field of the EMU. We keep a separate list of // Monitor and EMU objects that have been retained from objects in // non-GC pages (e.g. from locking on RuntimeType objects). #if REFERENCE_COUNTING_GC || DEFERRED_REFERENCE_COUNTING_GC internal unsafe static void VisitStrongRefsRC(NonNullReferenceVisitor strongRefVisitor) { VTable.Assert(VerificationMode, @"VerificationMode"); DebugPrint("GC: VisitStrongRefsRC"); VTable.Assert(emuGCList == UIntPtr.Zero); Visitor.Initialize(strongRefVisitor); SegregatedFreeList.VisitAllObjects(Visitor); } private static MUWVisitor Visitor = new MUWVisitor(); private class MUWVisitor : SegregatedFreeList.ObjectVisitor { private NonNullReferenceVisitor Visitor; [ManualRefCounts] internal override void VisitSmall(Object obj, UIntPtr memAddr) { this.Visit(obj); } [ManualRefCounts] internal override UIntPtr VisitLarge(Object obj) { return this.Visit(obj); } [ManualRefCounts] internal override unsafe UIntPtr Visit(Object obj) { MultiUseWord muw = GetForObject(obj); uint tag = muw.GetTag(); if (tag == MONITOR_TAG || tag == INFLATED_TAG) { UIntPtr payload = (UIntPtr) muw.GetPayload(); DebugPrint("GC: VisitStrongRefsRC visiting {0:x}\n", __arglist(payload)); this.Visitor.Visit(&payload); if (tag == INFLATED_TAG) { EMU emu = EMU.FromAddress((UIntPtr)muw.GetPayload()); UIntPtr m = emu.monitor; DebugPrint("GC: VisitStrongRefsRC visiting {0:x}\n", __arglist(m)); this.Visitor.Visit(&m); } } VTable vtable = obj.vtable; return ObjectLayout.ObjectSize(Magic.addressOf(obj), vtable); } [ManualRefCounts] internal void Initialize(NonNullReferenceVisitor visitor) { this.Visitor = visitor; } } private static void RegisterLostObjForVerifyHeap(Object owner, Object obj) { if (VerificationMode) { UIntPtr page = PageTable.Page(Magic.addressOf(owner)); PageType pageType = PageTable.Type(page); if (PageTable.IsNonGcPage(pageType)) { DebugPrint("GC: Registering permanent object {0:x} -> {1:x}\n", __arglist(Magic.addressOf(owner), Magic.addressOf(obj))); PermanentObj po = new PermanentObj(); po.Obj = obj; po.Next = PermanentObjList; PermanentObjList = po; } } } private static PermanentObj PermanentObjList; private class PermanentObj { internal Object Obj; internal PermanentObj Next; } #endif #if REFERENCE_COUNTING_GC [ManualRefCounts] internal static void IncrementRefCount(Object obj) { ReferenceCountingCollector.IncrementRefCount(obj); } [ManualRefCounts] internal static void DecrementRefCount(Object obj) { ReferenceCountingCollector.DecrementRefCount(obj); } internal static bool VerificationMode { [ManualRefCounts] get { return ReferenceCountingCollector.VerificationMode; } } #endif #if DEFERRED_REFERENCE_COUNTING_GC [ManualRefCounts] internal static void IncrementRefCount(Object obj) { DeferredReferenceCountingCollector.IncrementRefCount(obj); } [ManualRefCounts] internal static void DecrementRefCount(Object obj) { DeferredReferenceCountingCollector.DecrementRefCount(obj); } internal static bool VerificationMode { [ManualRefCounts] get { return DeferredReferenceCountingCollector.VerificationMode; } } #endif // With a tracing collector we visit Monitor and EMU objects by linking // them on emuGCList. This avoids us needing to visit MUWs on all objects // in the heap on every GC. #if !(REFERENCE_COUNTING_GC || DEFERRED_REFERENCE_COUNTING_GC) internal unsafe static void VisitStrongRefsNonRC(NonNullReferenceVisitor strongRefVisitor, bool useShadows) { DebugPrint("GC: VisitStrongRefsNonRC\n"); fixed (UIntPtr *loc = &emuGCList) { if (*loc != UIntPtr.Zero) { strongRefVisitor.Visit(loc); } } EMU emu = EMU.FromAddress(useShadows ? emuGCListShadow : emuGCList); while (emu != null) { // Visit the EMU's monitor field if (emu.monitor != UIntPtr.Zero) { Monitor m = Monitor.FromAddress(emu.monitor); if (!GC.installedGC.IsOnTheFlyCollector && m != null && !m.IsInUse() && emu.IsMonitor()) { // Do nothing. The monitor will be treated as a // weak reference instead. } else { DebugPrint ("GC: Visiting {0:x}'s monitor field {1:x}\n", __arglist(Magic.addressOf(emu), emu.monitor)); fixed (UIntPtr* loc = &emu.monitor) { strongRefVisitor.Visit(loc); } DebugPrint("GC: Now {0:x}\n", __arglist(emu.monitor)); } } #if !VC // Visit the EMU's STM word DebugPrint("GC: Visiting {0:x}'s STM word {1:x}\n", __arglist(Magic.addressOf(emu), emu.stmWord.value)); STMWord w = emu.stmWord; w.Visit(strongRefVisitor); emu.stmWord = w; DebugPrint("GC: Now {0:x}\n", __arglist(emu.stmWord.value)); #endif // Visit the EMU's next field DebugPrint("GC: Visiting {0:x}'s next field {1:x}\n", __arglist(Magic.addressOf(emu), emu.next)); fixed (UIntPtr *loc = &emu.next) { if (*loc != UIntPtr.Zero) { strongRefVisitor.Visit(loc); } } DebugPrint("GC: Now {0:x}\n", __arglist(emu.next)); emu = useShadows ? emu.NextShadow : emu.Next; } DebugPrint("GC: VisitStrongRefsNonRC done\n"); } #endif // Called after the mark phase. Responsible for identifying EMU objects // whose targets have become dead. This is indicated by the // weakReferenceVisitor nulling out the target field. Any such EMUs // are then cut out of the global list in PostGCHook. internal unsafe static void VisitWeakRefs(NonNullReferenceVisitor weakReferenceVisitor, bool useShadows) { DebugPrint("GC: VisitWeakRefs\n"); EMU emu = EMU.FromAddress(useShadows ? emuGCListShadow : emuGCList); while (emu != null) { if (emu.monitor != UIntPtr.Zero) { Monitor m = Monitor.FromAddress(emu.monitor); if (!GC.installedGC.IsOnTheFlyCollector && m != null && !m.IsInUse() && emu.IsMonitor()) { DebugPrint ("GC: Visiting {0:x}'s monitor field {1:x}\n", __arglist(Magic.addressOf(emu), emu.monitor)); fixed (UIntPtr *loc = &emu.monitor) { weakReferenceVisitor.Visit(loc); } DebugPrint("GC: Now {0:x}\n", __arglist(emu.monitor)); } } DebugPrint("GC: Visiting {0:x}'s target field {1:x}\n", __arglist(Magic.addressOf(emu), emu.target)); fixed (UIntPtr *loc = &emu.target) { if (*loc != UIntPtr.Zero) { weakReferenceVisitor.Visit(loc); } } DebugPrint("GC: Now {0:x}\n", __arglist(emu.target)); emu = useShadows ? emu.NextShadow : emu.Next; } } // Called just before starting the world. Responsible for excising // unneeded EMUs from the global list and for updating the MUW header // in objects whose EMU or Monitor may have moved. internal unsafe static void PostGCHook() { PostGCHook(false); } internal unsafe static void PostGCHook(bool onlyNew) { DebugPrint("GC: PostGCHook\n"); EMU emu = EMU.FromAddress(emuGCList); // We traverse emuGCList with prevLivingEMU pointing to the // most recent entry that should be kept in the list. EMU prevLivingEMU = null; bool inDeadRun = false; while (emu != null) { bool live = false; if (emu.target != UIntPtr.Zero) { // Either (a) the EMU really is still needed, or (b) the // EMU was allocated when a Monitor was provided for the // object and it's been replaced by a full EMU. // or (c) the emu was only a monitor which has been removed // and emu needs to be removed also MultiUseWord muw = GetForObject(emu.Target); DebugPrint("GC: {0:x} target non-null {1:x}, tag {2:x}\n", __arglist(Magic.addressOf(emu), emu.target, muw.GetTag())); VTable.Assert(muw.IsInflated() || muw.IsMonitor()); if (muw.IsMonitor()) { // MONITOR_TAG on the object: keep the EMU (it must be // a monitor EMU). VTable.Assert(emu.IsMonitor()); // This monitor was unused, thus set to zero when visiting // Set the muw in object to unused and remove emu if (emu.monitor == UIntPtr.Zero) { MultiUseWord muwTemp = new MultiUseWord(UIntPtr.Zero); SetForObject(emu.Target, muwTemp); emu.target = UIntPtr.Zero; } else { muw = new MultiUseWord(MONITOR_TAG, emu.Monitor); SetForObject(emu.Target, muw); live = true; } } else { // INFLATED_TAG on the object: only keep the EMU // if it's a full EMU. if (!emu.IsMonitor()) { muw = new MultiUseWord(INFLATED_TAG, emu); SetForObject(emu.Target, muw); live = true; } } } if (live) { // If this EMU is still needed then cut out any // run of dead predecessors in the list. DebugPrint("GC: {0:x} is alive\n", __arglist(EMU.ToAddress(emu))); if (inDeadRun) { DebugPrint("GC: {0:x} is new successor of {1:x}\n", __arglist(Magic.addressOf(emu), Magic.addressOf(prevLivingEMU))); if (prevLivingEMU == null) { emuGCList = Magic.addressOf(emu); } else { prevLivingEMU.next = Magic.addressOf(emu); } inDeadRun = false; } prevLivingEMU = emu; } else { // EMU is not needed. Either (a) the target was nulled // out by weakReferenceVisitor, or (b) a new EMU was // allocated for this target and target was nulled out at // that point. In either case we should cut it from the // list and let it be collected at the next GC. DebugPrint("GC: {0:x} is dead\n", __arglist(EMU.ToAddress(emu))); IncrementDeadEMUsCount(); inDeadRun = true; } emu = emu.Next; } if (inDeadRun) { DebugPrint("GC: {0:x} is new tail\n", __arglist(Magic.addressOf(prevLivingEMU))); if (prevLivingEMU == null) { emuGCList = UIntPtr.Zero; } else { prevLivingEMU.next = UIntPtr.Zero; } } if (onlyNew) { // Prepend surviving entries to the list already contained in // emuGCListAll...emuGCListAllTail. if (prevLivingEMU != null) { prevLivingEMU.next = emuGCListAll; emuGCListAll = emuGCList; if (emuGCListAllTail == UIntPtr.Zero) { emuGCListAllTail = Magic.addressOf(prevLivingEMU); } } } else { // Replace emuGCListAll with emuGCList because we copied the // "All" list into the list that we just processed. emuGCListAll = emuGCList; emuGCListAllTail = Magic.addressOf(prevLivingEMU); } // The emuGCList entries are contained in emuGCListAll, so now clear // emuGCList so that it can start receiving thread termination lists // for the next GC. emuGCList = UIntPtr.Zero; } //---------------------------------------------------------------------- // // Debugging and tracing [System.Diagnostics.Conditional("ENABLE_LOG_TRACING")] [NoInline] internal static void DebugPrint(String v, __arglist) { VTable.DebugPrint(v, new ArgIterator(__arglist)); } [System.Diagnostics.Conditional("ENABLE_LOG_TRACING")] [NoInline] internal static void DebugPrint(String v) { VTable.DebugPrint(v); } private static int[] InflationFromCount = new int[TAG_MASK + 1]; private static int[] InflationToCount = new int[TAG_MASK + 1]; private static int AbandonedEMUsCount = 0; private static int DeadEMUsCount = 0; private static int MonitorEMUAllocCount = 0; private static int FullEMUAllocCount = 0; private static int ReInflationCount = 0; [System.Diagnostics.Conditional("ENABLE_PROFILING")] internal static void IncrementInflationFromCount(uint tag) { VTable.Assert(tag >= 0 && tag <= TAG_MASK); InflationFromCount[tag] ++; } [System.Diagnostics.Conditional("ENABLE_PROFILING")] internal static void IncrementInflationToCount(uint tag) { VTable.Assert(tag >= 0 && tag <= TAG_MASK); InflationToCount[tag] ++; } [System.Diagnostics.Conditional("ENABLE_PROFILING")] internal static void IncrementReInflationCount() { ReInflationCount ++; } [System.Diagnostics.Conditional("ENABLE_PROFILING")] internal static void IncrementAbandonedEMUsCount() { AbandonedEMUsCount ++; } [System.Diagnostics.Conditional("ENABLE_PROFILING")] internal static void IncrementDeadEMUsCount() { DeadEMUsCount ++; } [System.Diagnostics.Conditional("ENABLE_PROFILING")] internal static void IncrementFullEMUAllocCount() { FullEMUAllocCount ++; } [System.Diagnostics.Conditional("ENABLE_PROFILING")] internal static void IncrementMonitorEMUAllocCount() { MonitorEMUAllocCount ++; } [System.Diagnostics.Conditional("ENABLE_PROFILING")] internal static void ComputeEmuCount(bool useShadows) { int total = 0; EMU emu = EMU.FromAddress(useShadows ? emuGCListShadow : emuGCList); while (emu != null) { total++; emu = useShadows ? emu.NextShadow : emu.Next; } Trace.Log(Trace.Area.Emu, "total {0} emu", __arglist(total)); } internal static void DumpTables() { #if ENABLE_PROFILING int monitorEMUs = 0; int fullEMUs = 0; int retargetedEMUs = 0; // Collect recently allocated EMUs from per-thread lists onto // the global EMU list hanging off emuGCList. CollectFromThreads(false); // Count EMUs on the global list EMU i = EMU.FromAddress(emuGCList); while (i != null) { if (i.Target == null) { retargetedEMUs ++; } else { MultiUseWord muw = GetForObject(i.Target); if (muw.IsMonitor()) { monitorEMUs ++; } else { VTable.Assert(muw.IsInflated()); fullEMUs ++; } } i = i.Next; } // Output stats VTable.DebugPrint(@" MultiUseWord stats: (non-thread-safe counters) Full EMUs allocated: {0} Monitor EMUs allocated: {1} Reinflated EMUs allocated: {2} Total EMUs allocated: {3} ", __arglist( FullEMUAllocCount, // 0 MonitorEMUAllocCount, // 1 ReInflationCount, // 2 FullEMUAllocCount + MonitorEMUAllocCount + ReInflationCount // 3 )); #if !(REFERENCE_COUNTING_GC || DEFERRED_REFERENCE_COUNTING_GC) VTable.DebugPrint(@" Of which: EMUs abandoned at birth: {0} EMUs found dead: {1} Moribund reinflated EMUs: {2} Live monitor EMUs: {3} Live full EMUs: {4} Total EMUs found : {5} ", __arglist(AbandonedEMUsCount, // 0 DeadEMUsCount, // 1 retargetedEMUs, // 2 monitorEMUs, // 3 fullEMUs, // 4 (AbandonedEMUsCount + DeadEMUsCount + retargetedEMUs + monitorEMUs + fullEMUs) //5 )); #endif VTable.DebugPrint(@" Inflation from STM: {0} from hashcode: {1} from monitor: {2} Inflation caused by STM: {3} caused by hashcode: {4} caused by monitor: {5} ", __arglist(InflationFromCount[STM_TAG], // 0 InflationFromCount[HASHCODE_TAG], // 1 InflationFromCount[MONITOR_TAG], // 2 InflationToCount[STM_TAG], // 3 InflationToCount[HASHCODE_TAG], // 4 InflationToCount[MONITOR_TAG] // 5 )); #endif } } internal class EMU { // Scalar fields holding scalar values internal int hashCode; #if !VC internal STMWord stmWord; #endif // Scalar fields holding references (accessed through properties below) internal UIntPtr target; internal UIntPtr monitor; internal UIntPtr next; internal UIntPtr nextShadow; public bool IsMonitor() { return (HashCode == 0); } //---------------------------------------------------------------------- // // Scalar properties public int HashCode { get { return hashCode; } set { hashCode = value; } } //---------------------------------------------------------------------- // // Accessor methods for scalar fields holding references public Object Target { get { return Magic.fromAddress(target); } set { target = Magic.addressOf(value); } } public Monitor Monitor { get { return (Monitor)(Magic.fromAddress(this.monitor)); } set { monitor = Magic.addressOf(value); } } public EMU Next { get { return FromAddress(this.next); } set { next = Magic.addressOf(value); } } public EMU NextShadow { get { return FromAddress(this.nextShadow); } set { nextShadow = Magic.addressOf(value); } } internal void Register() { Thread thread = Thread.CurrentThread; if (thread.externalMultiUseObjAllocListHead == UIntPtr.Zero) { VTable.Assert(thread.externalMultiUseObjAllocListTail == UIntPtr.Zero); thread.externalMultiUseObjAllocListHead = Magic.addressOf(this); thread.externalMultiUseObjAllocListTail = Magic.addressOf(this); } else { VTable.Assert(thread.externalMultiUseObjAllocListTail != UIntPtr.Zero); this.next = thread.externalMultiUseObjAllocListHead; thread.externalMultiUseObjAllocListHead = Magic.addressOf(this); } } //---------------------------------------------------------------------- // // Type conversion methods. Note: (1) we cannot do a checked cast // from Object to EMU during GC (because the GC may be using the // vtable word), (2) we don't make ToAddress an ordinary method in // case it's not compiled with devirtualization and the call goes // through the vtable. internal static EMU FromAddress(UIntPtr v) { return Magic.toEMU(Magic.fromAddress(v)); } internal static UIntPtr ToAddress(EMU i) { return Magic.addressOf(i); } } // The StringState enum is only used in String.cs, but kept here because // of the dependence between the values used and the format of the MultiUseWord. // We require that the values are all contained within PAYLOAD_MASK. public enum StringState { Undetermined = 0, // Undetermined == 0 assumed in MultiUseWord.cs HighChars = 4, FastOps = 8, SpecialSort = 12, } }