private static unsafe IntPtr ResolveCallOnReferenceTypeCacheMiss(IntPtr context, IntPtr callDescIntPtr, object contextObject, out IntPtr auxResult) { auxResult = IntPtr.Zero; NonGenericConstrainedCallDesc* callDesc = (NonGenericConstrainedCallDesc*)callDescIntPtr; IntPtr target = RuntimeAugments.ResolveDispatch(contextObject, callDesc->_constrainedMethodType, callDesc->_constrainedMethodSlot); return GetThunkThatDereferencesThisPointerAndTailCallsTarget(target); }
private static unsafe IntPtr ResolveCallOnReferenceType(ref object thisPtr, IntPtr callDescIntPtr) #endif { return(RuntimeAugments.RuntimeCacheLookup(thisPtr.GetType().TypeHandle.ToIntPtr(), callDescIntPtr, (IntPtr context, IntPtr callDescPtr, object contextObject, ref IntPtr auxResult) => { NonGenericConstrainedCallDesc *callDesc = (NonGenericConstrainedCallDesc *)callDescPtr; IntPtr target = RuntimeAugments.ResolveDispatch(contextObject, callDesc->_constrainedMethodType, callDesc->_constrainedMethodSlot); return GetThunkThatDereferencesThisPointerAndTailCallsTarget(target); }, thisPtr, out _)); }
// Resolve a constrained call in case where the call is an MDIL constrained call directly through a function pointer located in the generic dictionary // This can only happen if there is a call from shared generic code to a structure which implements multiple of the same generic interface, and which instantiation // is decided by the exact type of the caller. For instance // // interface IFunc<T> // { // void M(); // } // // struct UnusualCase : IFunc<object>, IFunc<string> // { // void IFunc<object>.M() { Console.WriteLine("In IFunc<object>");} // void IFunc<string>.M() { Console.WriteLine("In IFunc<object>");} // } // class Caller<T,U> where T : IFunc<U> // { // void Call(T c) // { // c.M(); // } // } // // If Caller is instantiated as Caller<UnusualCase,object>, or Caller<UnusualCase,string> we will generate code for Caller<UnusualCase,__Canon>.Call(UnusualCase) // However, that code will not be able to exactly specify the target of the call. It will need to use the generic dictionary. unsafe private static IntPtr ResolveDirectConstrainedCall(IntPtr callerTransitionBlockParam, IntPtr callDescIntPtr) { NonGenericConstrainedCallDesc* callDesc = (NonGenericConstrainedCallDesc*)callDescIntPtr; Debug.Assert(RuntimeAugments.IsInterface(callDesc->_constrainedMethodType)); IntPtr targetOnTypeVtable = RuntimeAugments.ResolveDispatchOnType(callDesc->_constraintType, callDesc->_constrainedMethodType, callDesc->_constrainedMethodSlot); IntPtr exactTarget = RuntimeAugments.GetCodeTarget(targetOnTypeVtable); IntPtr underlyingTargetIfUnboxingAndInstantiatingStub; if (TypeLoaderEnvironment.TryGetTargetOfUnboxingAndInstantiatingStub(exactTarget, out underlyingTargetIfUnboxingAndInstantiatingStub)) { // If this is an unboxing and instantiating stub, get the underlying pointer. The caller of this function is required to have already setup the // instantiation argument exactTarget = underlyingTargetIfUnboxingAndInstantiatingStub; } callDesc->_exactTarget = exactTarget; return exactTarget; }
private unsafe static IntPtr ResolveCallOnValueType(IntPtr unused, IntPtr callDescIntPtr) #endif { NonGenericConstrainedCallDesc *callDesc = (NonGenericConstrainedCallDesc *)callDescIntPtr; IntPtr exactTarget = IntPtr.Zero; IntPtr targetOnTypeVtable = RuntimeAugments.ResolveDispatchOnType(callDesc->_constraintType, callDesc->_constrainedMethodType, callDesc->_constrainedMethodSlot); bool decodeUnboxing = true; if (!RuntimeAugments.IsInterface(callDesc->_constrainedMethodType)) { // Non-interface constrained call on a valuetype to a method that isn't GetHashCode/Equals/ToString?!?! if (callDesc->_constrainedMethodSlot > s_MaxObjectVTableSlot) { throw new NotSupportedException(); } RuntimeTypeHandle baseTypeHandle; bool gotBaseType = RuntimeAugments.TryGetBaseType(callDesc->_constraintType, out baseTypeHandle); Debug.Assert(gotBaseType); if (targetOnTypeVtable == RuntimeAugments.ResolveDispatchOnType(baseTypeHandle, callDesc->_constrainedMethodType, callDesc->_constrainedMethodSlot)) { // In this case, the valuetype does not override the base types implementation of ToString(), GetHashCode(), or Equals(object) decodeUnboxing = false; } } if (decodeUnboxing) { exactTarget = RuntimeAugments.GetCodeTarget(targetOnTypeVtable); if (RuntimeAugments.IsGenericType(callDesc->_constraintType)) { IntPtr fatFunctionPointerTarget; if (TypeLoaderEnvironment.TryGetTargetOfUnboxingAndInstantiatingStub(exactTarget, out fatFunctionPointerTarget)) { // If this is an unboxing and instantiating stub, use seperate table, find target, and create fat function pointer exactTarget = FunctionPointerOps.GetGenericMethodFunctionPointer(fatFunctionPointerTarget, callDesc->_constraintType.ToIntPtr()); } else { IntPtr newExactTarget; if (CallConverterThunk.TryGetNonUnboxingFunctionPointerFromUnboxingAndInstantiatingStub(exactTarget, callDesc->_constraintType, out newExactTarget)) { // CallingConventionConverter determined non-unboxing stub exactTarget = newExactTarget; } else { // Target method was a method on a generic, but it wasn't a shared generic, and thus none of the above // complex unboxing stub digging logic was necessary. Do nothing, and use exactTarget as discovered // from GetCodeTarget } } } } else { // Create a fat function pointer, where the instantiation argument is ConstraintType, and the target is BoxAndToString, BoxAndGetHashCode, or BoxAndEquals IntPtr realTarget; switch (callDesc->_constrainedMethodSlot) { case s_ToStringSlot: realTarget = s_boxAndToStringFuncPtr; break; case s_GetHashCodeSlot: realTarget = s_boxAndGetHashCodeFuncPtr; break; case s_EqualsSlot: realTarget = s_boxAndEqualsFuncPtr; break; default: throw new NotSupportedException(); } exactTarget = FunctionPointerOps.GetGenericMethodFunctionPointer(realTarget, callDesc->_constraintType.ToIntPtr()); } // Ensure that all threads will have their function pointers completely published before updating callDesc. // as the ExactTarget is read from callDesc by binder generated code without a barrier, we need a barrier here // to ensure that the new function pointer data is valid on all threads Interlocked.MemoryBarrier(); // Its possible for multiple threads to race to set exact target. Check to see we always set the same value if (callDesc->_exactTarget != IntPtr.Zero) { Debug.Assert(callDesc->_exactTarget == exactTarget); } callDesc->_exactTarget = exactTarget; return(exactTarget); }
public static unsafe IntPtr Get(RuntimeTypeHandle constraintType, RuntimeTypeHandle constrainedMethodType, int constrainedMethodSlot, bool directConstrainedCall = false) { lock (s_nonGenericConstrainedCallDescs) { // Get list of constrained call descs associated with a given type LowLevelList <IntPtr> associatedCallDescs; if (!s_nonGenericConstrainedCallDescs.TryGetValue(constraintType, out associatedCallDescs)) { associatedCallDescs = new LowLevelList <IntPtr>(); s_nonGenericConstrainedCallDescs.Add(constraintType, associatedCallDescs); } // Perform linear scan of associated call descs to see if one matches for (int i = 0; i < associatedCallDescs.Count; i++) { NonGenericConstrainedCallDesc *callDesc = (NonGenericConstrainedCallDesc *)associatedCallDescs[i]; Debug.Assert(constraintType.Equals(callDesc->_constraintType)); if (callDesc->_constrainedMethodSlot != constrainedMethodSlot) { continue; } if (!callDesc->_constrainedMethodType.Equals(constrainedMethodType)) { continue; } // Found matching entry. return(associatedCallDescs[i]); } // Did not find match, allocate a new one and add it to the lookup list IntPtr newCallDescPtr = MemoryHelpers.AllocateMemory(sizeof(NonGenericConstrainedCallDesc)); NonGenericConstrainedCallDesc *newCallDesc = (NonGenericConstrainedCallDesc *)newCallDescPtr; newCallDesc->_exactTarget = IntPtr.Zero; if (directConstrainedCall) { newCallDesc->_lookupFunc = RuntimeAugments.GetUniversalTransitionThunk(); } else { if (RuntimeAugments.IsValueType(constraintType)) { newCallDesc->_lookupFunc = s_resolveCallOnValueTypeFuncPtr; } else { newCallDesc->_lookupFunc = s_resolveCallOnReferenceTypeFuncPtr; } } newCallDesc->_constraintType = constraintType; newCallDesc->_constrainedMethodSlot = constrainedMethodSlot; newCallDesc->_constrainedMethodType = constrainedMethodType; associatedCallDescs.Add(newCallDescPtr); return(newCallDescPtr); } }
private static unsafe IntPtr ResolveCallOnValueType(IntPtr unused, IntPtr callDescIntPtr) #endif { NonGenericConstrainedCallDesc *callDesc = (NonGenericConstrainedCallDesc *)callDescIntPtr; IntPtr exactTarget = IntPtr.Zero; IntPtr targetOnTypeVtable = RuntimeAugments.ResolveDispatchOnType(callDesc->_constraintType, callDesc->_constrainedMethodType, callDesc->_constrainedMethodSlot); bool decodeUnboxing = true; if (!RuntimeAugments.IsInterface(callDesc->_constrainedMethodType)) { // Non-interface constrained call on a valuetype to a method that isn't GetHashCode/Equals/ToString?!?! if (callDesc->_constrainedMethodSlot > s_MaxObjectVTableSlot) { throw new NotSupportedException(); } RuntimeTypeHandle baseTypeHandle; bool gotBaseType = RuntimeAugments.TryGetBaseType(callDesc->_constraintType, out baseTypeHandle); Debug.Assert(gotBaseType); if (targetOnTypeVtable == RuntimeAugments.ResolveDispatchOnType(baseTypeHandle, callDesc->_constrainedMethodType, callDesc->_constrainedMethodSlot)) { // In this case, the valuetype does not override the base types implementation of ToString(), GetHashCode(), or Equals(object) decodeUnboxing = false; } } if (decodeUnboxing) { exactTarget = TypeLoaderEnvironment.ConvertUnboxingFunctionPointerToUnderlyingNonUnboxingPointer(targetOnTypeVtable, callDesc->_constraintType); } else { // Create a fat function pointer, where the instantiation argument is ConstraintType, and the target is BoxAndToString, BoxAndGetHashCode, or BoxAndEquals IntPtr realTarget; switch (callDesc->_constrainedMethodSlot) { case s_ToStringSlot: realTarget = s_boxAndToStringFuncPtr; break; case s_GetHashCodeSlot: realTarget = s_boxAndGetHashCodeFuncPtr; break; case s_EqualsSlot: realTarget = s_boxAndEqualsFuncPtr; break; default: throw new NotSupportedException(); } exactTarget = FunctionPointerOps.GetGenericMethodFunctionPointer(realTarget, callDesc->_constraintType.ToIntPtr()); } // Ensure that all threads will have their function pointers completely published before updating callDesc. // as the ExactTarget is read from callDesc by binder generated code without a barrier, we need a barrier here // to ensure that the new function pointer data is valid on all threads Interlocked.MemoryBarrier(); // Its possible for multiple threads to race to set exact target. Check to see we always set the same value if (callDesc->_exactTarget != IntPtr.Zero) { Debug.Assert(callDesc->_exactTarget == exactTarget); } callDesc->_exactTarget = exactTarget; return(exactTarget); }