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