unsafe private static IntPtr MulticastDelegateInvoke(ref CallConversionParameters conversionParams) { // Create a transition block on the stack. // Note that SizeOfFrameArgumentArray does overflow checks with sufficient margin to prevent overflows here int nStackBytes = conversionParams._calleeArgs.SizeOfFrameArgumentArray(); int dwAllocaSize = TransitionBlock.GetNegSpaceSize() + sizeof(TransitionBlock) + nStackBytes; IntPtr invokeTargetPtr = Intrinsics.AddrOf((InvokeTargetDel)InvokeTarget); for (int i = 0; i < conversionParams.MulticastDelegateCallCount; i++) { conversionParams.PrepareNextMulticastDelegateCall(i); conversionParams._copyReturnValue = (i == (conversionParams.MulticastDelegateCallCount - 1)); RuntimeAugments.RunFunctionWithConservativelyReportedBuffer(dwAllocaSize, invokeTargetPtr, ref conversionParams); System.Diagnostics.DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); } return conversionParams._invokeReturnValue; }
unsafe private static void InvokeTarget(void* allocatedStackBuffer, ref CallConversionParameters conversionParams) { byte* callerTransitionBlock = conversionParams._callerTransitionBlock; byte* calleeTransitionBlock = ((byte*)allocatedStackBuffer) + TransitionBlock.GetNegSpaceSize(); // // Setup some of the special parameters on the output transition block // void* thisPointer = conversionParams.ThisPointer; void* callerRetBuffer = conversionParams.CallerReturnBuffer; void* VASigCookie = conversionParams.VarArgSigCookie; void* instantiatingStubArgument = (void*)conversionParams.InstantiatingStubArgument; { Debug.Assert((thisPointer != null && conversionParams._calleeArgs.HasThis()) || (thisPointer == null && !conversionParams._calleeArgs.HasThis())); if (thisPointer != null) { *((void**)(calleeTransitionBlock + ArgIterator.GetThisOffset())) = thisPointer; } Debug.Assert((callerRetBuffer != null && conversionParams._calleeArgs.HasRetBuffArg()) || (callerRetBuffer == null && !conversionParams._calleeArgs.HasRetBuffArg())); if (callerRetBuffer != null) { *((void**)(calleeTransitionBlock + conversionParams._calleeArgs.GetRetBuffArgOffset())) = callerRetBuffer; } Debug.Assert((VASigCookie != null && conversionParams._calleeArgs.IsVarArg()) || (VASigCookie == null && !conversionParams._calleeArgs.IsVarArg())); if (VASigCookie != null) { *((void**)(calleeTransitionBlock + conversionParams._calleeArgs.GetVASigCookieOffset())) = VASigCookie; } Debug.Assert((instantiatingStubArgument != null && conversionParams._calleeArgs.HasParamType()) || (instantiatingStubArgument == null && !conversionParams._calleeArgs.HasParamType())); if (instantiatingStubArgument != null) { *((void**)(calleeTransitionBlock + conversionParams._calleeArgs.GetParamTypeArgOffset())) = instantiatingStubArgument; } } #if CCCONVERTER_TRACE if (thisPointer != null) CallingConventionConverterLogger.WriteLine(" ThisPtr = " + new IntPtr(thisPointer).LowLevelToString()); if (callerRetBuffer != null) CallingConventionConverterLogger.WriteLine(" RetBuf = " + new IntPtr(callerRetBuffer).LowLevelToString()); if (VASigCookie != null) CallingConventionConverterLogger.WriteLine(" VASig = " + new IntPtr(VASigCookie).LowLevelToString()); if (instantiatingStubArgument != null) CallingConventionConverterLogger.WriteLine(" InstArg = " + new IntPtr(instantiatingStubArgument).LowLevelToString()); #endif object[] argumentsAsObjectArray = null; IntPtr pinnedResultObject = IntPtr.Zero; CallDescrData callDescrData = default(CallDescrData); IntPtr functionPointerToCall = conversionParams.FunctionPointerToCall; // // Setup the rest of the parameters on the ouput transition block by copying them from the input transition block // int ofsCallee; int ofsCaller; TypeHandle thDummy; TypeHandle thValueType; IntPtr argPtr; #if CALLDESCR_FPARGREGS FloatArgumentRegisters* pFloatArgumentRegisters = null; #endif { uint arg = 0; while (true) { // Setup argument offsets. ofsCallee = conversionParams._calleeArgs.GetNextOffset(); ofsCaller = int.MaxValue; // Check to see if we've handled all the arguments that we are to pass to the callee. if (TransitionBlock.InvalidOffset == ofsCallee) { if (!conversionParams._conversionInfo.IsAnyDynamicInvokerThunk) ofsCaller = conversionParams._callerArgs.GetNextOffset(); break; } #if CALLDESCR_FPARGREGS // Under CALLDESCR_FPARGREGS -ve offsets indicate arguments in floating point registers. If we // have at least one such argument we point the call worker at the floating point area of the // frame (we leave it null otherwise since the worker can perform a useful optimization if it // knows no floating point registers need to be set up). if ((ofsCallee < 0) && (pFloatArgumentRegisters == null)) pFloatArgumentRegisters = (FloatArgumentRegisters*)(calleeTransitionBlock + TransitionBlock.GetOffsetOfFloatArgumentRegisters()); #endif byte* pDest = calleeTransitionBlock + ofsCallee; byte* pSrc = null; int stackSizeCallee = int.MaxValue; int stackSizeCaller = int.MaxValue; bool isCalleeArgPassedByRef = false; bool isCallerArgPassedByRef = false; // // Compute size and pointer to caller's arg // { if (conversionParams._conversionInfo.IsClosedStaticDelegate) { if (arg == 0) { // Do not advance the caller's ArgIterator yet argPtr = conversionParams.ClosedStaticDelegateThisPointer; pSrc = (byte*)&argPtr; stackSizeCaller = IntPtr.Size; isCallerArgPassedByRef = false; } else { ofsCaller = conversionParams._callerArgs.GetNextOffset(); pSrc = callerTransitionBlock + ofsCaller; stackSizeCaller = conversionParams._callerArgs.GetArgSize(); isCallerArgPassedByRef = conversionParams._callerArgs.IsArgPassedByRef(); } stackSizeCallee = conversionParams._calleeArgs.GetArgSize(); isCalleeArgPassedByRef = conversionParams._calleeArgs.IsArgPassedByRef(); } else if (conversionParams._conversionInfo.IsAnyDynamicInvokerThunk) { // The caller's ArgIterator for delegate or reflection dynamic invoke thunks has a different (and special) signature than // the target method called by the delegate. We do not use it when setting up the callee's transition block. // Input arguments are not read from the caller's transition block, but form the dynamic invoke infrastructure. // Get all arguments info from the callee's ArgIterator instead. int index; InvokeUtils.DynamicInvokeParamLookupType paramLookupType; RuntimeTypeHandle argumentRuntimeTypeHandle; CorElementType argType = conversionParams._calleeArgs.GetArgType(out thValueType); if (argType == CorElementType.ELEMENT_TYPE_BYREF) { TypeHandle thByRefArgType; conversionParams._calleeArgs.GetByRefArgType(out thByRefArgType); Debug.Assert(!thByRefArgType.IsNull()); argumentRuntimeTypeHandle = thByRefArgType.GetRuntimeTypeHandle(); } else { argumentRuntimeTypeHandle = (thValueType.IsNull() ? typeof(object).TypeHandle : thValueType.GetRuntimeTypeHandle()); } object invokeParam = InvokeUtils.DynamicInvokeParamHelperCore( argumentRuntimeTypeHandle, out paramLookupType, out index, conversionParams._calleeArgs.IsArgPassedByRef() ? InvokeUtils.DynamicInvokeParamType.Ref : InvokeUtils.DynamicInvokeParamType.In); if (paramLookupType == InvokeUtils.DynamicInvokeParamLookupType.ValuetypeObjectReturned) { CallConversionParameters.s_pinnedGCHandles._dynamicInvokeArgHandle.Target = invokeParam; argPtr = RuntimeAugments.GetRawAddrOfPinnedObject((IntPtr)CallConversionParameters.s_pinnedGCHandles._dynamicInvokeArgHandle) + IntPtr.Size; } else { Debug.Assert(paramLookupType == InvokeUtils.DynamicInvokeParamLookupType.IndexIntoObjectArrayReturned); Debug.Assert((invokeParam is object[]) && index < ((object[])invokeParam).Length); CallConversionParameters.s_pinnedGCHandles._dynamicInvokeArgHandle.Target = ((object[])invokeParam)[index]; pinnedResultObject = RuntimeAugments.GetRawAddrOfPinnedObject((IntPtr)CallConversionParameters.s_pinnedGCHandles._dynamicInvokeArgHandle); if (conversionParams._calleeArgs.IsArgPassedByRef()) { // We need to keep track of the array of parameters used by the InvokeUtils infrastructure, so we can copy // back results of byref parameters conversionParams._dynamicInvokeParams = conversionParams._dynamicInvokeParams ?? (object[])invokeParam; // Use wrappers to pass objects byref (Wrappers can handle both null and non-null input byref parameters) conversionParams._dynamicInvokeByRefObjectArgs = conversionParams._dynamicInvokeByRefObjectArgs ?? new DynamicInvokeByRefArgObjectWrapper[conversionParams._dynamicInvokeParams.Length]; // The wrapper objects need to be pinned while we take the address of the byref'd object, and copy it to the callee // transition block (which is conservatively reported). Once the copy is done, we can safely unpin the wrapper object. if (pinnedResultObject == IntPtr.Zero) { // Input byref parameter has a null value conversionParams._dynamicInvokeByRefObjectArgs[index] = new DynamicInvokeByRefArgObjectWrapper(); CallConversionParameters.s_pinnedGCHandles._dynamicInvokeArgHandle.Target = conversionParams._dynamicInvokeByRefObjectArgs[index]; argPtr = RuntimeAugments.GetRawAddrOfPinnedObject((IntPtr)CallConversionParameters.s_pinnedGCHandles._dynamicInvokeArgHandle) + IntPtr.Size; } else { // Input byref parameter has a non-null value conversionParams._dynamicInvokeByRefObjectArgs[index] = new DynamicInvokeByRefArgObjectWrapper { _object = conversionParams._dynamicInvokeParams[index] }; CallConversionParameters.s_pinnedGCHandles._dynamicInvokeArgHandle.Target = conversionParams._dynamicInvokeByRefObjectArgs[index]; argPtr = RuntimeAugments.GetRawAddrOfPinnedObject((IntPtr)CallConversionParameters.s_pinnedGCHandles._dynamicInvokeArgHandle) + IntPtr.Size; } } else { argPtr = new IntPtr(&pinnedResultObject); } } if (conversionParams._calleeArgs.IsArgPassedByRef()) { pSrc = (byte*)&argPtr; } else { pSrc = (byte*)argPtr; } stackSizeCaller = stackSizeCallee = conversionParams._calleeArgs.GetArgSize(); isCallerArgPassedByRef = isCalleeArgPassedByRef = conversionParams._calleeArgs.IsArgPassedByRef(); } else { ofsCaller = conversionParams._callerArgs.GetNextOffset(); pSrc = callerTransitionBlock + ofsCaller; stackSizeCallee = conversionParams._calleeArgs.GetArgSize(); stackSizeCaller = conversionParams._callerArgs.GetArgSize(); isCalleeArgPassedByRef = conversionParams._calleeArgs.IsArgPassedByRef(); isCallerArgPassedByRef = conversionParams._callerArgs.IsArgPassedByRef(); } } Debug.Assert(stackSizeCallee == stackSizeCaller); if (conversionParams._conversionInfo.IsObjectArrayDelegateThunk) { // Box (if needed) and copy arguments to an object array instead of the callee's transition block argumentsAsObjectArray = argumentsAsObjectArray ?? new object[conversionParams._callerArgs.NumFixedArgs()]; conversionParams._callerArgs.GetArgType(out thValueType); if (thValueType.IsNull()) { Debug.Assert(!isCallerArgPassedByRef); Debug.Assert(conversionParams._callerArgs.GetArgSize() == IntPtr.Size); argumentsAsObjectArray[arg] = Unsafe.As<IntPtr, Object>(ref *(IntPtr*)pSrc); } else { if (isCallerArgPassedByRef) { argumentsAsObjectArray[arg] = RuntimeAugments.Box(thValueType.GetRuntimeTypeHandle(), new IntPtr(*((void**)pSrc))); } else { argumentsAsObjectArray[arg] = RuntimeAugments.Box(thValueType.GetRuntimeTypeHandle(), new IntPtr(pSrc)); } } } else { if (isCalleeArgPassedByRef == isCallerArgPassedByRef) { // Argument copies without adjusting calling convention. switch (stackSizeCallee) { case 1: case 2: case 4: *((int*)pDest) = *((int*)pSrc); break; case 8: *((long*)pDest) = *((long*)pSrc); break; default: if (isCalleeArgPassedByRef) { // even though this argument is passed by value, the actual calling convention // passes a pointer to the value of the argument. Debug.Assert(isCallerArgPassedByRef); // Copy the pointer from the incoming arguments to the outgoing arguments. *((void**)pDest) = *((void**)pSrc); } else { // In this case, the valuetype is passed directly on the stack, even though it is // a non-integral size. Buffer.MemoryCopy(pSrc, pDest, stackSizeCallee, stackSizeCallee); } break; } } else { // Calling convention adjustment. Used to handle conversion from universal shared generic form to standard // calling convention and vice versa if (isCalleeArgPassedByRef) { // Pass as the byref pointer a pointer to the position in the transition block of the input argument *((void**)pDest) = pSrc; } else { // Copy into the destination the data pointed at by the pointer in the source(caller) data. Buffer.MemoryCopy(*(byte**)pSrc, pDest, stackSizeCaller, stackSizeCaller); } } #if CCCONVERTER_TRACE CallingConventionConverterLogger.WriteLine(" Arg" + arg.LowLevelToString() + " " + (isCalleeArgPassedByRef ? "ref = " : " = ") + new IntPtr(*(void**)pDest).LowLevelToString() + " - RTTH = " + conversionParams._calleeArgs.GetEETypeDebugName((int)arg) + " - StackSize = " + stackSizeCallee.LowLevelToString()); #endif } if (conversionParams._conversionInfo.IsAnyDynamicInvokerThunk) { // The calleeTransitionBlock is GC-protected, so we can now safely unpin the return value of DynamicInvokeParamHelperCore, // since we just copied it to the callee TB. CallConversionParameters.s_pinnedGCHandles._dynamicInvokeArgHandle.Target = ""; } arg++; } } if (conversionParams._conversionInfo.IsAnyDynamicInvokerThunk) { IntPtr argSetupStatePtr = conversionParams.GetArgSetupStateDataPointer(); InvokeUtils.DynamicInvokeArgSetupPtrComplete(argSetupStatePtr); } uint fpReturnSize = conversionParams._calleeArgs.GetFPReturnSize(); if (conversionParams._conversionInfo.IsObjectArrayDelegateThunk) { Debug.Assert(conversionParams._callerArgs.HasRetBuffArg() == conversionParams._calleeArgs.HasRetBuffArg()); pinnedResultObject = conversionParams.InvokeObjectArrayDelegate(argumentsAsObjectArray); } else { if ((TransitionBlock.InvalidOffset != ofsCaller) != conversionParams._conversionInfo.CallerHasExtraParameterWhichIsFunctionTarget && !conversionParams._conversionInfo.IsAnyDynamicInvokerThunk) { // The condition on the loop above is only verifying that callee has reach the end of its arguments. // Here we check to see that caller has done so as well. Environment.FailFast("Argument mismatch between caller and callee"); } if (conversionParams._conversionInfo.CallerHasExtraParameterWhichIsFunctionTarget && !conversionParams._conversionInfo.CalleeMayHaveParamType) { int stackSizeCaller = conversionParams._callerArgs.GetArgSize(); Debug.Assert(stackSizeCaller == IntPtr.Size); void* pSrc = callerTransitionBlock + ofsCaller; functionPointerToCall = *((IntPtr*)pSrc); ofsCaller = conversionParams._callerArgs.GetNextOffset(); if (TransitionBlock.InvalidOffset != ofsCaller) { Environment.FailFast("Argument mismatch between caller and callee"); } } callDescrData.pSrc = calleeTransitionBlock + sizeof(TransitionBlock); callDescrData.numStackSlots = conversionParams._calleeArgs.SizeOfFrameArgumentArray() / ArchitectureConstants.STACK_ELEM_SIZE; #if CALLDESCR_ARGREGS callDescrData.pArgumentRegisters = (ArgumentRegisters*)(calleeTransitionBlock + TransitionBlock.GetOffsetOfArgumentRegisters()); #endif #if CALLDESCR_FPARGREGS callDescrData.pFloatArgumentRegisters = pFloatArgumentRegisters; #endif callDescrData.fpReturnSize = fpReturnSize; callDescrData.pTarget = (void*)functionPointerToCall; ReturnBlock returnBlockForIgnoredData = default(ReturnBlock); if (conversionParams._callerArgs.HasRetBuffArg() == conversionParams._calleeArgs.HasRetBuffArg()) { // If there is no return buffer explictly in use, return to a buffer which is conservatively reported // by the universal transition frame. // OR // If there IS a return buffer in use, the function doesn't really return anything in the normal // return value registers, but CallDescrThunk will always copy a pointer sized chunk into the // ret buf. Make that ok by giving it a valid location to stash bits. callDescrData.pReturnBuffer = (void*)(callerTransitionBlock + TransitionBlock.GetOffsetOfReturnValuesBlock()); } else if (conversionParams._calleeArgs.HasRetBuffArg()) { // This is the case when the caller doesn't have a return buffer argument, but the callee does. // In that case the return value captured by CallDescrWorker is ignored. // When CallDescrWorkerInternal is called, have it return values into a temporary unused buffer // In actuality its returning its return information into the return value block already, but that return buffer // was setup as a passed in argument instead of being filled in by the CallDescrWorker function directly. callDescrData.pReturnBuffer = (void*)&returnBlockForIgnoredData; } else { // If there is no return buffer explictly in use by the callee, return to a buffer which is conservatively reported // by the universal transition frame. // This is the case where HasRetBuffArg is false for the callee, but the caller has a return buffer. // In this case we need to capture the direct return value from callee into a buffer which may contain // a gc reference (or not), and then once the call is complete, copy the value into the return buffer // passed by the caller. (Do not directly use the return buffer provided by the caller, as CallDescrWorker // does not properly use a write barrier, and the actual return buffer provided may be on the GC heap.) callDescrData.pReturnBuffer = (void*)(callerTransitionBlock + TransitionBlock.GetOffsetOfReturnValuesBlock()); } ////////////////////////////////////////////////////////////// //// Call the Callee ////////////////////////////////////////////////////////////// RuntimeAugments.CallDescrWorker(new IntPtr(&callDescrData)); System.Diagnostics.DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); } // For dynamic invoke thunks, we need to copy back values of reference type parameters that were passed byref if (conversionParams._conversionInfo.IsAnyDynamicInvokerThunk && conversionParams._dynamicInvokeParams != null) { for (int i = 0; i < conversionParams._dynamicInvokeParams.Length; i++) { if (conversionParams._dynamicInvokeByRefObjectArgs[i] == null) continue; object byrefObjectArgValue = conversionParams._dynamicInvokeByRefObjectArgs[i]._object; conversionParams._dynamicInvokeParams[i] = byrefObjectArgValue; } } if (!conversionParams._copyReturnValue) return; bool forceByRefUnused; CorElementType returnType; // Note that the caller's ArgIterator for delegate dynamic invoke thunks has a different (and special) signature than // the target method called by the delegate. Use the callee's ArgIterator instead to get the return type info if (conversionParams._conversionInfo.IsAnyDynamicInvokerThunk) { returnType = conversionParams._calleeArgs.GetReturnType(out thValueType, out forceByRefUnused); } else { returnType = conversionParams._callerArgs.GetReturnType(out thValueType, out forceByRefUnused); } int returnSize = TypeHandle.GetElemSize(returnType, thValueType); // Unbox result of object array delegate call if (conversionParams._conversionInfo.IsObjectArrayDelegateThunk && !thValueType.IsNull() && pinnedResultObject != IntPtr.Zero) pinnedResultObject += IntPtr.Size; // Process return values if ((conversionParams._callerArgs.HasRetBuffArg() && !conversionParams._calleeArgs.HasRetBuffArg()) || (conversionParams._callerArgs.HasRetBuffArg() && conversionParams._conversionInfo.IsObjectArrayDelegateThunk)) { // We should never get here for dynamic invoke thunks Debug.Assert(!conversionParams._conversionInfo.IsAnyDynamicInvokerThunk); // The CallDescrWorkerInternal function will have put the return value into the return buffer, as register return values are // extended to the size of a register, we can't just ask the CallDescrWorker to write directly into the return buffer. // Thus we copy only the correct amount of data here to the real target address byte* incomingRetBufPointer = *((byte**)(callerTransitionBlock + conversionParams._callerArgs.GetRetBuffArgOffset())); void* sourceBuffer = conversionParams._conversionInfo.IsObjectArrayDelegateThunk ? (void*)pinnedResultObject : callDescrData.pReturnBuffer; Debug.Assert(sourceBuffer != null || conversionParams._conversionInfo.IsObjectArrayDelegateThunk); if (sourceBuffer == null) { // object array delegate thunk result is a null object. We'll fill the return buffer with 'returnSize' zeros in that case gcSafeMemzeroPointer(incomingRetBufPointer, returnSize); } else { // Because we are copying into a caller provided buffer, we can't use a simple memory copy, we need to use a // gc protected copy as the actual return buffer may be on the heap. bool useGCSafeCopy = false; if ((returnType == CorElementType.ELEMENT_TYPE_CLASS) || !thValueType.IsNull()) { // The GC Safe copy assumes that memory pointers are pointer-aligned and copy length is a multiple of pointer-size if (isPointerAligned(incomingRetBufPointer) && isPointerAligned(sourceBuffer) && (returnSize % sizeof(IntPtr) == 0)) { useGCSafeCopy = true; } } if (useGCSafeCopy) { RuntimeAugments.BulkMoveWithWriteBarrier(new IntPtr(incomingRetBufPointer), new IntPtr(sourceBuffer), returnSize); } else { Buffer.MemoryCopy(sourceBuffer, incomingRetBufPointer, returnSize, returnSize); } } #if CALLINGCONVENTION_CALLEE_POPS // Don't setup the callee pop argument until after copying into the ret buff. We may be using the location // of the callee pop argument to keep track of the ret buff location SetupCallerPopArgument(callerTransitionBlock, conversionParams._callerArgs); #endif #if _TARGET_X86_ SetupCallerActualReturnData(callerTransitionBlock); // On X86 the return buffer pointer is returned in eax. t_NonArgRegisterReturnSpace.returnValue = new IntPtr(incomingRetBufPointer); conversionParams._invokeReturnValue = ReturnIntegerPointReturnThunk; return; #else // Because the return value was really returned on the heap, simply return as if void was returned. conversionParams._invokeReturnValue = ReturnVoidReturnThunk; return; #endif } else { #if CALLINGCONVENTION_CALLEE_POPS SetupCallerPopArgument(callerTransitionBlock, conversionParams._callerArgs); #endif // The CallDescrWorkerInternal function will have put the return value into the return buffer. // Here we copy the return buffer data into the argument registers for the return thunks. // // A return thunk takes an argument(by value) that is what is to be returned. // // The simplest case is the one where there is no return value bool dummyBool; if (conversionParams._callerArgs.GetReturnType(out thDummy, out dummyBool) == CorElementType.ELEMENT_TYPE_VOID) { conversionParams._invokeReturnValue = ReturnVoidReturnThunk; return; } // The second simplest case is when there is a return buffer argument for both the caller and callee // In that case, we simply treat this as if we are returning void #if _TARGET_X86_ // Except on X86 where the return buffer is returned in the eax register, and looks like an integer return #else if (conversionParams._callerArgs.HasRetBuffArg() && conversionParams._calleeArgs.HasRetBuffArg()) { Debug.Assert(!conversionParams._conversionInfo.IsObjectArrayDelegateThunk); Debug.Assert(!conversionParams._conversionInfo.IsAnyDynamicInvokerThunk); conversionParams._invokeReturnValue = ReturnVoidReturnThunk; return; } #endif void* returnValueToCopy = (void*)(callerTransitionBlock + TransitionBlock.GetOffsetOfReturnValuesBlock()); if (conversionParams._conversionInfo.IsObjectArrayDelegateThunk) { if (!thValueType.IsNull()) { returnValueToCopy = (void*)pinnedResultObject; #if _TARGET_X86_ Debug.Assert(returnSize <= sizeof(ReturnBlock)); if (returnValueToCopy == null) { // object array delegate thunk result is a null object. We'll fill the return buffer with 'returnSize' zeros in that case memzeroPointer((byte*)(&((TransitionBlock*)callerTransitionBlock)->m_returnBlock), returnSize); } else { if (isPointerAligned(&((TransitionBlock*)callerTransitionBlock)->m_returnBlock) && isPointerAligned(returnValueToCopy) && (returnSize % sizeof(IntPtr) == 0)) RuntimeAugments.BulkMoveWithWriteBarrier(new IntPtr(&((TransitionBlock*)callerTransitionBlock)->m_returnBlock), new IntPtr(returnValueToCopy), returnSize); else Buffer.MemoryCopy(returnValueToCopy, &((TransitionBlock*)callerTransitionBlock)->m_returnBlock, returnSize, returnSize); } #endif } else { returnValueToCopy = (void*)&pinnedResultObject; #if _TARGET_X86_ ((TransitionBlock*)callerTransitionBlock)->m_returnBlock.returnValue = pinnedResultObject; #endif } } else if (conversionParams._conversionInfo.IsAnyDynamicInvokerThunk && !thValueType.IsNull()) { Debug.Assert(returnValueToCopy != null); if (!conversionParams._callerArgs.HasRetBuffArg() && conversionParams._calleeArgs.HasRetBuffArg()) returnValueToCopy = (void*)(new IntPtr(*((void**)returnValueToCopy)) + IntPtr.Size); // Need to box value type before returning it object returnValue = RuntimeAugments.Box(thValueType.GetRuntimeTypeHandle(), new IntPtr(returnValueToCopy)); CallConversionParameters.s_pinnedGCHandles._returnObjectHandle.Target = returnValue; pinnedResultObject = RuntimeAugments.GetRawAddrOfPinnedObject((IntPtr)CallConversionParameters.s_pinnedGCHandles._returnObjectHandle); returnValueToCopy = (void*)&pinnedResultObject; #if _TARGET_X86_ ((TransitionBlock*)callerTransitionBlock)->m_returnBlock.returnValue = pinnedResultObject; #endif } // Handle floating point returns // The previous fpReturnSize was the callee fpReturnSize. Now reset to the caller return size to handle // returning to the caller. fpReturnSize = conversionParams._callerArgs.GetFPReturnSize(); if (fpReturnSize != 0) { // We should never get here for delegate dynamic invoke thunks (the return type is always a boxed object) Debug.Assert(!conversionParams._conversionInfo.IsAnyDynamicInvokerThunk); #if CALLDESCR_FPARGREGSARERETURNREGS Debug.Assert(fpReturnSize <= sizeof(FloatArgumentRegisters)); memzeroPointerAligned(calleeTransitionBlock + TransitionBlock.GetOffsetOfFloatArgumentRegisters(), sizeof(FloatArgumentRegisters)); if (returnValueToCopy == null) { // object array delegate thunk result is a null object. We'll fill the return buffer with 'returnSize' zeros in that case Debug.Assert(conversionParams._conversionInfo.IsObjectArrayDelegateThunk); memzeroPointer(callerTransitionBlock + TransitionBlock.GetOffsetOfFloatArgumentRegisters(), (int)fpReturnSize); } else { Buffer.MemoryCopy(returnValueToCopy, callerTransitionBlock + TransitionBlock.GetOffsetOfFloatArgumentRegisters(), (int)fpReturnSize, (int)fpReturnSize); } conversionParams._invokeReturnValue = ReturnVoidReturnThunk; return; #else #if CALLDESCR_FPARGREGS #error Case not yet handled #endif Debug.Assert(fpReturnSize <= sizeof(ArgumentRegisters)); #if _TARGET_X86_ SetupCallerActualReturnData(callerTransitionBlock); t_NonArgRegisterReturnSpace = ((TransitionBlock*)callerTransitionBlock)->m_returnBlock; #else #error Platform not implemented #endif if (fpReturnSize == 4) { conversionParams._invokeReturnValue = ReturnFloatingPointReturn4Thunk; } else { conversionParams._invokeReturnValue = ReturnFloatingPointReturn8Thunk; } return; #endif } #if _TARGET_X86_ SetupCallerActualReturnData(callerTransitionBlock); t_NonArgRegisterReturnSpace = ((TransitionBlock*)callerTransitionBlock)->m_returnBlock; conversionParams._invokeReturnValue = ReturnIntegerPointReturnThunk; return; #else // If we reach here, we are returning value in the integer registers. if (conversionParams._conversionInfo.IsObjectArrayDelegateThunk && (!thValueType.IsNull())) { if (returnValueToCopy == null) { // object array delegate thunk result is a null object. We'll fill the return buffer with 'returnSize' zeros in that case memzeroPointer(callerTransitionBlock + TransitionBlock.GetOffsetOfArgumentRegisters(), returnSize); } else { if (isPointerAligned(callerTransitionBlock + TransitionBlock.GetOffsetOfArgumentRegisters()) && isPointerAligned(returnValueToCopy) && (returnSize % sizeof(IntPtr) == 0)) RuntimeAugments.BulkMoveWithWriteBarrier(new IntPtr(callerTransitionBlock + TransitionBlock.GetOffsetOfArgumentRegisters()), new IntPtr(returnValueToCopy), returnSize); else Buffer.MemoryCopy(returnValueToCopy, callerTransitionBlock + TransitionBlock.GetOffsetOfArgumentRegisters(), returnSize, returnSize); } } else { Debug.Assert(returnValueToCopy != null); Buffer.MemoryCopy(returnValueToCopy, callerTransitionBlock + TransitionBlock.GetOffsetOfArgumentRegisters(), ArchitectureConstants.ENREGISTERED_RETURNTYPE_INTEGER_MAXSIZE_PRIMITIVE, ArchitectureConstants.ENREGISTERED_RETURNTYPE_INTEGER_MAXSIZE_PRIMITIVE); } conversionParams._invokeReturnValue = ReturnIntegerPointReturnThunk; #endif } }
unsafe private static IntPtr CallConversionThunk(IntPtr callerTransitionBlockParam, IntPtr callConversionId) { CallConversionParameters conversionParams = default(CallConversionParameters); try { conversionParams = new CallConversionParameters(CallConversionInfo.GetConverter(callConversionId.ToInt32()), callerTransitionBlockParam); #if CCCONVERTER_TRACE System.Threading.Interlocked.Increment(ref s_numConversionsExecuted); CallingConventionConverterLogger.WriteLine("CallConversionThunk executing... COUNT = " + s_numConversionsExecuted.LowLevelToString()); CallingConventionConverterLogger.WriteLine("Executing thunk of type " + conversionParams._conversionInfo.ThunkKindString() + ": "); #endif if (conversionParams._conversionInfo.IsMulticastDelegate) { MulticastDelegateInvoke(ref conversionParams); System.Diagnostics.DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); } else { // Create a transition block on the stack. // Note that SizeOfFrameArgumentArray does overflow checks with sufficient margin to prevent overflows here int nStackBytes = conversionParams._calleeArgs.SizeOfFrameArgumentArray(); int dwAllocaSize = TransitionBlock.GetNegSpaceSize() + sizeof(TransitionBlock) + nStackBytes; IntPtr invokeTargetPtr = Intrinsics.AddrOf((InvokeTargetDel)InvokeTarget); RuntimeAugments.RunFunctionWithConservativelyReportedBuffer(dwAllocaSize, invokeTargetPtr, ref conversionParams); System.Diagnostics.DebugAnnotations.PreviousCallContainsDebuggerStepInCode(); } return conversionParams._invokeReturnValue; } finally { conversionParams.ResetPinnedObjects(); } }