1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System;
6 using System.Runtime.CompilerServices;
7 
8 using Internal.Runtime;
9 using Internal.Runtime.CompilerServices;
10 
11 namespace System.Runtime
12 {
13     [System.Runtime.CompilerServices.EagerStaticClassConstructionAttribute]
14     internal static class CastableObjectSupport
15     {
16         private static object s_castFailCanary = new object();
17         private static CastableObjectCacheEntry<IntPtr>[] s_ThunkBasedDispatchCellTargets = new CastableObjectCacheEntry<IntPtr>[16];
18 
19         private static ThunksHeap s_thunksHeap;
20 
21         internal interface ICastableObject
22         // TODO!! BEGIN REMOVE THIS CODE WHEN WE REMOVE ICASTABLE
23             : ICastable
24         // TODO!! END REMOVE THIS CODE WHEN WE REMOVE ICASTABLE
25         {
26             // This is called if casting this object to the given interface type would otherwise fail. Casting
27             // here means the IL isinst and castclass instructions in the case where they are given an interface
28             // type as the target type.
29             //
30             // A return value of non-null indicates the cast is valid.
31             // The return value (if non-null) must be an object instance that implements the specified interface.
32             //
33             // If null is returned when this is called as part of a castclass then the usual InvalidCastException
34             // will be thrown unless an alternate exception is assigned to the castError output parameter. This
35             // parameter is ignored on successful casts or during the evaluation of an isinst (which returns null
36             // rather than throwing on error).
37             //
38             // The results of this call are cached
39             //
40             // The results of this call should be semantically  invariant for the same object, interface type pair.
41             // That is because this is the only guard placed before an interface invocation at runtime. It is possible
42             // that this call may occur more than once for a given pair, and it is possible that the results of multiple calls
43             // may remain in use over time.
CastToInterface(EETypePtr interfaceType, bool produceCastErrorException, out Exception castError)44             object CastToInterface(EETypePtr interfaceType, bool produceCastErrorException, out Exception castError);
45         }
46 
47         internal struct CastableObjectCacheEntry<V>
48         {
49             public IntPtr Key;
50             public V Value;
51         }
52 
53         internal class CastableObject
54         {
55             public CastableObjectCacheEntry<object>[] Cache;
56         }
57 
58         // cache must be a size which is a power of two.
CacheLookup(CastableObjectCacheEntry<V>[] cache, IntPtr keyToLookup)59         internal static unsafe V CacheLookup<V>(CastableObjectCacheEntry<V>[] cache, IntPtr keyToLookup)
60         {
61             uint hashcode = unchecked((uint)keyToLookup.ToInt64());
62             uint cacheMask = (uint)cache.Length - 1;
63             uint bucket = hashcode & cacheMask;
64             uint curbucket = bucket;
65 
66             // hash algorithm is open addressing with linear probing
67 
68             while (curbucket < cache.Length)
69             {
70                 if (cache[curbucket].Key == keyToLookup)
71                     return cache[curbucket].Value;
72                 if (cache[curbucket].Key == default(IntPtr))
73                     return default(V);
74                 curbucket++;
75             }
76 
77             // Handle wrap-around case
78             curbucket = 0;
79             while (curbucket < bucket)
80             {
81                 if (cache[curbucket].Key == keyToLookup)
82                     return cache[curbucket].Value;
83                 if (cache[curbucket].Key == default(IntPtr))
84                     return default(V);
85                 curbucket++;
86             }
87 
88             return default(V);
89         }
90 
GetCachePopulation(CastableObjectCacheEntry<V>[] cache)91         internal static unsafe int GetCachePopulation<V>(CastableObjectCacheEntry<V>[] cache)
92         {
93             int population = 0;
94             for (int i = 0; i < cache.Length; i++)
95             {
96                 if (cache[i].Key != default(IntPtr))
97                     population++;
98             }
99 
100             return population;
101         }
102 
AddToExistingCache(CastableObjectCacheEntry<V>[] cache, IntPtr key, V value)103         internal static unsafe void AddToExistingCache<V>(CastableObjectCacheEntry<V>[] cache, IntPtr key, V value)
104         {
105             uint hashcode = unchecked((uint)key.ToInt64());
106             uint cacheMask = (uint)cache.Length - 1;
107             uint bucket = hashcode & cacheMask;
108             uint curbucket = bucket;
109 
110             // hash algorithm is open addressing with linear probing
111 
112             while (curbucket < cache.Length)
113             {
114                 if (cache[curbucket].Key == default(IntPtr))
115                 {
116                     cache[curbucket].Key = key;
117                     cache[curbucket].Value = value;
118                     return;
119                 }
120                 curbucket++;
121             }
122 
123             // Handle wrap-around case
124             curbucket = 0;
125             while (curbucket < bucket)
126             {
127                 if (cache[curbucket].Key == default(IntPtr))
128                 {
129                     cache[curbucket].Key = key;
130                     cache[curbucket].Value = value;
131                     return;
132                 }
133                 curbucket++;
134             }
135 
136             EH.FallbackFailFast(RhFailFastReason.InternalError, null);
137             return;
138         }
139 
140         /// <summary>
141         /// Add the newly allocated thunk of a CastableObject dispatch cell call to the cache if possible. (OOM errors may cause caching failure.
142         /// An OOM is specified not to introduce new failure points though.)
143         /// </summary>
AddToThunkCache(IntPtr pDispatchCell, IntPtr pThunkTarget)144         internal static unsafe void AddToThunkCache(IntPtr pDispatchCell, IntPtr pThunkTarget)
145         {
146             // Expand old cache if it isn't big enough.
147             if (GetCachePopulation(s_ThunkBasedDispatchCellTargets) > (s_ThunkBasedDispatchCellTargets.Length / 2))
148             {
149                 CastableObjectCacheEntry<IntPtr>[] oldCache = s_ThunkBasedDispatchCellTargets;
150                 try
151                 {
152                     s_ThunkBasedDispatchCellTargets = new CastableObjectCacheEntry<IntPtr>[oldCache.Length * 2];
153                 }
154                 catch (OutOfMemoryException)
155                 {
156                     // Failed to allocate a bigger cache.  That is fine, keep the old one.
157                 }
158 
159                 for (int i = 0; i < oldCache.Length; i++)
160                 {
161                     if (oldCache[i].Key != default(IntPtr))
162                     {
163                         AddToExistingCache(s_ThunkBasedDispatchCellTargets, oldCache[i].Key, oldCache[i].Value);
164                     }
165                 }
166             }
167 
168             AddToExistingCache(s_ThunkBasedDispatchCellTargets, pDispatchCell, pThunkTarget);
169         }
170 
171         /// <summary>
172         /// Add the results of a CastableObject call to the cache if possible. (OOM errors may cause caching failure. An OOM is specified not
173         /// to introduce new failure points though.)
174         /// </summary>
AddToCastableCache(ICastableObject castableObject, EEType* interfaceType, object objectForType)175         internal static unsafe void AddToCastableCache(ICastableObject castableObject, EEType* interfaceType, object objectForType)
176         {
177             CastableObjectCacheEntry<object>[] cache = Unsafe.As<CastableObject>(castableObject).Cache;
178             bool setNewCache = false;
179 
180             // If there is no cache, allocate one
181             if (cache == null)
182             {
183                 try
184                 {
185                     cache = new CastableObjectCacheEntry<object>[8];
186                 }
187                 catch (OutOfMemoryException)
188                 {
189                     // Failed to allocate a cache.  That is fine, simply return.
190                     return;
191                 }
192 
193                 setNewCache = true;
194             }
195 
196             // Expand old cache if it isn't big enough.
197             if (GetCachePopulation(cache) > (cache.Length / 2))
198             {
199                 setNewCache = true;
200                 CastableObjectCacheEntry<object>[] oldCache = cache;
201                 try
202                 {
203                     cache = new CastableObjectCacheEntry<object>[oldCache.Length * 2];
204                 }
205                 catch (OutOfMemoryException)
206                 {
207                     // Failed to allocate a bigger cache.  That is fine, keep the old one.
208                 }
209 
210                 for (int i = 0; i < oldCache.Length; i++)
211                 {
212                     if (oldCache[i].Key != default(IntPtr))
213                     {
214                         AddToExistingCache(cache, oldCache[i].Key, oldCache[i].Value);
215                     }
216                 }
217             }
218 
219             AddToExistingCache(cache, new IntPtr(interfaceType), objectForType);
220 
221             if (setNewCache)
222             {
223                 Unsafe.As<CastableObject>(castableObject).Cache = cache;
224             }
225 
226             return;
227         }
228 
GetCastableTargetIfPossible(ICastableObject castableObject, EEType* interfaceType, bool produceException, ref Exception exception)229         internal static unsafe object GetCastableTargetIfPossible(ICastableObject castableObject, EEType* interfaceType, bool produceException, ref Exception exception)
230         {
231             CastableObjectCacheEntry<object>[] cache = Unsafe.As<CastableObject>(castableObject).Cache;
232 
233             object targetObjectInitial = null;
234 
235             if (cache != null)
236             {
237                 targetObjectInitial = CacheLookup(cache, new IntPtr(interfaceType));
238                 if (targetObjectInitial != null)
239                 {
240                     if (targetObjectInitial != s_castFailCanary)
241                         return targetObjectInitial;
242                     else if (!produceException)
243                         return null;
244                 }
245             }
246 
247             // Call into the object to determine if the runtime can perform the cast. This will return null if it fails.
248             object targetObject = castableObject.CastToInterface(new EETypePtr(new IntPtr(interfaceType)), produceException, out exception);
249 
250             // If the target object is null, and that result has already been cached, just return null now.
251             // Otherwise, we need to store the canary in the cache so future failing "is" checks can be fast
252             if (targetObject == null)
253             {
254                 if (targetObjectInitial != null)
255                     return null;
256                 else
257                     targetObject = s_castFailCanary;
258             }
259 
260             InternalCalls.RhpAcquireCastCacheLock();
261             // Assuming we reach here, we should attempt to add the newly discovered targetObject to the per-object cache
262 
263             // First, check to see if something is already there
264 
265             // we may have replaced the cache object since the earlier acquisition in this method. Re-acquire the cache object
266             // here.
267             cache = Unsafe.As<CastableObject>(castableObject).Cache;
268             object targetObjectInCache = null;
269 
270             if (cache != null)
271                 targetObjectInCache = CacheLookup(cache, new IntPtr(interfaceType));
272 
273             if (targetObjectInCache == null)
274             {
275                 // If the target object still isn't in the cache by this point, add it now
276                 AddToCastableCache(castableObject, interfaceType, targetObject);
277                 targetObjectInCache = targetObject;
278             }
279             InternalCalls.RhpReleaseCastCacheLock();
280 
281             if (targetObjectInCache != s_castFailCanary)
282                 return targetObjectInCache;
283             else
284                 return null;
285         }
286 
GetCastableObjectDispatchCellThunk(EEType* pInstanceType, IntPtr pDispatchCell)287         internal static unsafe IntPtr GetCastableObjectDispatchCellThunk(EEType* pInstanceType, IntPtr pDispatchCell)
288         {
289             IntPtr pTargetCode = CacheLookup(s_ThunkBasedDispatchCellTargets, pDispatchCell);
290             if (pTargetCode != default(IntPtr))
291                 return pTargetCode;
292 
293             InternalCalls.RhpAcquireCastCacheLock();
294             {
295                 // Look in the cache again after taking the lock
296 
297                 pTargetCode = CacheLookup(s_ThunkBasedDispatchCellTargets, pDispatchCell);
298                 if (pTargetCode != default(IntPtr))
299                     return pTargetCode;
300 
301                 // Allocate a new thunk. Failure to allocate one will result in a fail-fast. We don't return nulls from this API.
302 
303                 if (s_thunksHeap == null)
304                 {
305                     s_thunksHeap = ThunksHeap.CreateThunksHeap(InternalCalls.RhpGetCastableObjectDispatch_CommonStub());
306                     if (s_thunksHeap == null)
307                         EH.FallbackFailFast(RhFailFastReason.InternalError, null);
308                 }
309 
310                 pTargetCode = s_thunksHeap.AllocateThunk();
311                 if (pTargetCode == IntPtr.Zero)
312                     EH.FallbackFailFast(RhFailFastReason.InternalError, null);
313 
314                 s_thunksHeap.SetThunkData(pTargetCode, pDispatchCell, InternalCalls.RhpGetCastableObjectDispatchHelper_TailCalled());
315 
316                 AddToThunkCache(pDispatchCell, pTargetCode);
317             }
318             InternalCalls.RhpReleaseCastCacheLock();
319 
320             return pTargetCode;
321         }
322 
323         [RuntimeExport("RhpCastableObjectResolve")]
RhpCastableObjectResolve(IntPtr callerTransitionBlockParam, IntPtr pCell)324         unsafe private static IntPtr RhpCastableObjectResolve(IntPtr callerTransitionBlockParam, IntPtr pCell)
325         {
326             IntPtr locationOfThisPointer = callerTransitionBlockParam + TransitionBlock.GetThisOffset();
327             object pObject = Unsafe.As<IntPtr, Object>(ref *(IntPtr*)locationOfThisPointer);
328 
329             DispatchCellInfo cellInfo;
330             InternalCalls.RhpGetDispatchCellInfo(pCell, out cellInfo);
331             if (cellInfo.CellType != DispatchCellType.InterfaceAndSlot)
332             {
333                 // Dispatch cell used for castable object resolve is not InterfaceAndSlot. This should not be possible
334                 // as all metadata based cells should have been converted to interface and slot cells by this time.
335                 EH.FallbackFailFast(RhFailFastReason.InternalError, null);
336                 return IntPtr.Zero;
337             }
338 
339             EEType* pInterfaceType = cellInfo.InterfaceType.ToPointer();
340 
341             Exception e = null;
342             object targetObject = GetCastableTargetIfPossible((ICastableObject)pObject, pInterfaceType, false, ref e);
343             if (targetObject == null)
344                 EH.FailFastViaClasslib(RhFailFastReason.InternalError, null, pObject.EEType->GetAssociatedModuleAddress());
345 
346             Unsafe.As<IntPtr, Object>(ref *(IntPtr*)locationOfThisPointer) = targetObject;
347 
348             InternalCalls.RhpSetTLSDispatchCell(pCell);
349             return InternalCalls.RhpGetTailCallTLSDispatchCell();
350         }
351     }
352 }
353