// ==++== // // Copyright (c) Microsoft Corporation. All rights reserved. // // ==--== // Verbose runtime tracing //#define ENABLE_DEBUG_PRINT // This class provides runtime support for managing delegates that may have // been passed in to native code. // // The problem with these delegates is that the GC may move the delegate // object without being aware of references from native code. Rather than // pinning the objects (which currently operates at a page granularity), we // allocate GC-stable indexes ("delegate idxs") which the native code uses in // place of the underlying Delegate reference. // // The entry points from compiled code are: // // GetCodePtr, which allocates a short function to enter a delegate, passing // in the delegate idx. // // ReleaseCodePtr, which deallocates the function and releases the delegate // idx. // // The NativeDelegateTable is used to map delegate idxs to Delegate references. // References from this table are ordinary strong references as far as the GC // is concerned. namespace System { using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.CompilerServices; using System.Threading; //| public abstract partial class Delegate : ICloneable { private IntPtr CodePtr; // equals returns true IIF the delegate is not null and has the // same target, method and invocation list as this object //| public override bool Equals(Object obj) { if (obj != null && obj is Delegate) { Delegate d = (Delegate) obj; if (IsStatic()) { if (_methodPtrAux == d._methodPtrAux) { return true; } } else { if (d._target == _target && d._methodPtr == _methodPtr) { return true; } } } return false; } //| public override int GetHashCode() { if (IsStatic()) return this._methodPtrAux.ToInt32(); else return this._methodPtr.ToInt32(); } // Combine creates a new delegate based upon the contents of the // delegates passed in. //| public static Delegate Combine(Delegate a, Delegate b) { //@TODO: Should this really check to see that they are both multicast // because it really is an error to try and combine non-multicasts? // Spec says that it returns null and only does the check if a and b // are both null. // boundary conditions -- if either (or both) delegates is null // return the other. if (a == null) return b; if (b == null) return a; // Verify that the types are the same... if (a.GetType() != b.GetType()) throw new ArgumentException("Arg_DlgtTypeMis"); return a.CombineImpl(b); } // This method creates a new delegate based upon the passed // array of delegates. //| public static Delegate Combine(Delegate[] delegates) { if (delegates == null || delegates.Length == 0) return null; Delegate d = delegates[0]; for (int i = 1; i < delegates.Length; i++) d = Combine(d,delegates[i]); return d; } // Return an array of delegates that represent the invocation list. // This is basically THIS for a Delegate. MulticastDelegates may // have multiple members. //| public virtual Delegate[] GetInvocationList() { Delegate[] d = new Delegate[1]; d[0] = this; return d; } // This routine will return the target //| public Object Target { get {return IsStatic() ? null : _target;} } //A quick test to see if this is a delegate to a static method. //@ToDo: Verify that this is a sufficient test. private bool IsStatic() { if (_target is Delegate) { return true; } return false; } // This will remove the value delegate from the source delegate // if it found. //| public static Delegate Remove(Delegate source, Delegate value) { if (source == null) return null; if (value == null) return source; return source.RemoveImpl(value); } //| public static Delegate RemoveAll(Delegate source, Delegate value) { Delegate newDelegate = null; do { newDelegate = source; source = Remove(source, value); } while (newDelegate != source); return newDelegate; } // This is an internal routine that is called to do the combine. We // use this to do the combine because the Combine routines are static // final methods. In Delegate, this simply throws a MulticastNotSupportedException // error. Multicast delegate must implement this. //| protected virtual Delegate CombineImpl(Delegate d) { throw new MulticastNotSupportedException("Multicast_Combine"); } // This is an internal routine that is called to do the remove. We use this // to do the remove because Remove is a static final method. Here we simply // make sure that d is equal to this and return null or this. //| protected virtual Delegate RemoveImpl(Delegate d) { if (_target == d._target && _methodPtr == d._methodPtr) return null; else return this; } //| public virtual Object Clone() { return MemberwiseClone(); } //| public static bool operator ==(Delegate d1, Delegate d2) { if ((Object)d1 == null) return (Object)d2 == null; return d1.Equals(d2); } //| public static bool operator != (Delegate d1, Delegate d2) { if ((Object)d1 == null) return (Object)d2 != null; return !d1.Equals(d2); } // GetCodePtr should be called by the marshalling code with the // delegate and the address of the call back stub for that // delegate class. [RequiredByBartok] private static IntPtr GetCodePtr(Delegate d,IntPtr callBackStub) { IntPtr codePtr = d.CodePtr; if (codePtr == IntPtr.Zero) { CriticalSection.AcquireMutex(); try { codePtr = d.CodePtr; if (codePtr == IntPtr.Zero) { int idx = AllocateNativeDelegateRecord(d); codePtr = CreateCodePtr(idx, callBackStub); d.CodePtr = codePtr; } } finally { CriticalSection.ReleaseMutex(); } } return codePtr; } ////////////////////////////////////////////////////////////////////// // internal static IntPtr FixedAlloc(int sizetdwBytes) { VTable.DebugBreak(); return IntPtr.Zero; } internal static void FixedFree(IntPtr handle) { VTable.DebugBreak(); } // Create a little piece of assembly code that adds idx onto the // argument list (by pushing it onto the stack) and jumps to the // call back stub. // The idx is added as the first argument. The calling // convention is assumed to be STDCALL. // // The call back stub is a function created by the Bartok code // generator and: // 1. Uses the idx to get to the delegate via the NativeDelegateTable // 2. Calls the invoke method of the delegate private static unsafe IntPtr CreateCodePtr(int idx, IntPtr callBackStub) { IntPtr buffer = FixedAlloc(32); if (buffer == IntPtr.Zero) { return IntPtr.Zero; } // The desired instructions: // push [sp] // mov [sp+4], idx // jmp callBackStub // byte* codeBuffer = (byte *) buffer.ToPointer(); // push [sp] *(codeBuffer) = 0xFF; *(codeBuffer+1) = 0x34; *(codeBuffer+2) = 0x24; // mov [sp+4], idx *(codeBuffer+3) = 0xC7; *(codeBuffer+4) = 0x44; *(codeBuffer+5) = 0x24; *(codeBuffer+6) = 0x04; *((int*) (codeBuffer+7)) = idx; // jmp callBackStub *(codeBuffer+11) = 0xE9; IntPtr disp = callBackStub - buffer; *((int *) (codeBuffer+12)) = (disp - 16).ToInt32(); // // pad the rest of the buffer with int 3 // for (int i = 16; i < 32; i++) { *(codeBuffer+i) = 0xcc; } return buffer; } // This method accepts a code pointer and allows the NativeDelegateRecord // associated with it to be collected. In order to call this method // safely it must be certain that no references to the code ptr exist // in native code. private static unsafe void ReleaseCodePtr(IntPtr codePtr) { byte* codeBuffer = (byte *)codePtr.ToPointer(); int idx = *(int*)(codeBuffer+7); Delegate del = IdxToDelegate(idx); del.CodePtr = IntPtr.Zero; CriticalSection.AcquireMutex(); try { FreeNativeDelegateRecord(idx); } finally { CriticalSection.ReleaseMutex(); } FixedFree(codePtr); } // // The CriticalSection protects the CodePtr fields during // initialization and the NativeDelegateTable arrays when claiming // indexes in them. // private static readonly Mutex CriticalSection; // // The NativeDelegateTable is held as a triangular array, starting // with small arrays of NativeDelegateRecord structures and using // successively larger arrays as the table is expanded. This avoids // frequent allocations in programs that pass a lot of delegates // to native code, and allows an idx to map to a (table,offset) pair // in O(log n) steps. // private static NativeDelegateRecord[][] NativeDelegateTable; // In a debug build, start the NativeDelegateTable size very small in order to // force testing of table expansion #if DEBUG private const int FIRST_TABLE_SIZE = 1; #else private const int FIRST_TABLE_SIZE = 16; #endif // // FreeListStartIdx is the idx is the head of the list of NativeDelegateTable // entries that have been freed. -1 indicates that the free list is empty. // private static int FreeListStartIdx; private const int FREE_LIST_EMPTY = -1; // // FreeExtentStartIdx is the idx of the start of the contiguous // extent of NativeDelegateTable entries. These are logically // considered at the end of the free list: we use entries from the free // list in preference to using 'pristine' space from the free extent. // private static int FreeExtentStartIdx; // // Each entry in the NativeDelegateTable currently holds an ordinary // reference to the Delegate and an integer used to form the free list // of unused records. We could overload a single field, but note that // (a) we would then need to special-case the GC visiting code and // that (b) during GC we could not simply iterate through the arrays because // we couldn't distinguish free-list entries for allocated ones. // // Of course, we could steal a bit in the object reference to distinguish // the free list, using odd values for the free list to avoid needing // to mask the contents in IdxToDelegate. // // Seems like needless complexity unless we find the table is getting // large. // private struct NativeDelegateRecord { Delegate del; int nextIdx; internal void Allocate(Delegate del) { VTable.Assert(this.del == null); this.del = del; } internal void DeAllocate(int nextIdx) { VTable.Assert(this.del != null); this.del = null; this.nextIdx = nextIdx; } internal Delegate Delegate() { VTable.Assert(this.del != null); return this.del; } internal int NextIdx() { VTable.Assert(this.del == null); return this.nextIdx; } } // Allocate a native delegate record for "del". The caller must hold // the CriticalSection. private static int AllocateNativeDelegateRecord(Delegate del) { int idx; int table; int offset; DebugPrint("AllocateNativeDelegateRecord (list={0}, extent {1})\n", __arglist(FreeListStartIdx, FreeExtentStartIdx)); if (FreeListStartIdx != FREE_LIST_EMPTY) { // There's space in the free list: use that int nextFreeIdx; idx = FreeListStartIdx; SplitIdx (idx, out table, out offset); nextFreeIdx = NativeDelegateTable[table][offset].NextIdx(); FreeListStartIdx = nextFreeIdx; } else { // There's no space in the free list, use the next slot in the // free extent. idx = FreeExtentStartIdx; SplitIdx (idx, out table, out offset); FreeExtentStartIdx ++; if (offset + 1 == NativeDelegateTable[table].Length) { // We've used the last slot in the free extent: allocate some // more. int currentLength = NativeDelegateTable[table].Length; int nextLength = currentLength * 2; DebugPrint("AllocateNativeDelegateRecord expanding to {0} len {1}\n", __arglist(table + 1, nextLength)); NativeDelegateTable[table + 1] = new NativeDelegateRecord[nextLength]; } } NativeDelegateTable[table][offset].Allocate(del); DebugPrint("AllocateNativeDelegateRecord got {0} (list={1}, extent {2})\n", __arglist(idx, FreeListStartIdx, FreeExtentStartIdx)); return idx; } private static void FreeNativeDelegateRecord(int idx) { int table; int offset; DebugPrint("FreeNativeDelegateRecord {0} (list={1}, extent {2})\n", __arglist(idx, FreeListStartIdx, FreeExtentStartIdx)); SplitIdx (idx, out table, out offset); NativeDelegateTable[table][offset].DeAllocate(FreeListStartIdx); FreeListStartIdx = idx; } [RequiredByBartok] private static Delegate IdxToDelegate(int idx) { int table; int offset; Delegate result; SplitIdx (idx, out table, out offset); result = NativeDelegateTable[table][offset].Delegate(); DebugPrint("IdxToDelegate {0} -> {1}\n", __arglist(idx, result)); return result; } private static void SplitIdx(int idx, out int table, out int offset) { table = 0; offset = idx; while (offset >= NativeDelegateTable[table].Length) { offset -= NativeDelegateTable[table].Length; table ++; } DebugPrint("SplitIdx {0} -> {1}.{2}\n", __arglist(idx, table, offset)); } static Delegate() { CriticalSection = new Mutex(); NativeDelegateTable = new NativeDelegateRecord[24][]; NativeDelegateTable[0] = new NativeDelegateRecord[FIRST_TABLE_SIZE]; FreeExtentStartIdx = 0; FreeListStartIdx = FREE_LIST_EMPTY; #if FALSE // Enable this code to force testing of the FreeNativeDelegateRecord fn for (int i = 0; i < FIRST_TABLE_SIZE * 2; i ++) { int idx = AllocateNativeDelegateRecord(CriticalSection); VTable.Assert (idx == i); } for (int i = 0; i < FIRST_TABLE_SIZE * 2; i ++) { FreeNativeDelegateRecord(i); } #endif } [System.Diagnostics.Conditional("ENABLE_DEBUG_PRINT")] [NoInline] internal static void DebugPrint(String v, __arglist) { VTable.DebugPrint(v, new ArgIterator(__arglist)); } } }