Fix shuffling thunk for Unix AMD64 (#16904)
authorJan Vorlicek <janvorli@microsoft.com>
Tue, 13 Mar 2018 18:56:34 +0000 (19:56 +0100)
committerGitHub <noreply@github.com>
Tue, 13 Mar 2018 18:56:34 +0000 (19:56 +0100)
The shufflign thunk was generated incorrectly for some edge cases when
a struct was passed in a register or a pair of registers in the
destination, but on stack in the source.
This change implements a new algorithm that ensures that argument slots
are never overwritten before their current value is moved out.

It also adds an extensive regression test that checks various
interesting combinations of arguments that were causing issues before.

src/vm/comdelegate.cpp
src/vm/i386/stublinkerx86.cpp
tests/src/Regressions/coreclr/GitHub_16833/Test16833.csproj [new file with mode: 0644]
tests/src/Regressions/coreclr/GitHub_16833/test16833.cs [new file with mode: 0644]

index bd3a46c..173a8fe 100644 (file)
@@ -214,6 +214,32 @@ public:
 
 #endif
 
+#if defined(UNIX_AMD64_ABI) && defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
+// Return an index of argument slot. First indices are reserved for general purpose registers,
+// the following ones for float registers and then the rest for stack slots.
+// This index is independent of how many registers are actually used to pass arguments.
+int GetNormalizedArgumentSlotIndex(UINT16 offset)
+{
+    int index;
+
+    if (offset & ShuffleEntry::FPREGMASK)
+    {
+        index = NUM_ARGUMENT_REGISTERS + (offset & ShuffleEntry::OFSREGMASK);
+    }
+    else if (offset & ShuffleEntry::REGMASK)
+    {
+        index = offset & ShuffleEntry::OFSREGMASK;
+    }
+    else
+    {
+        // stack slot
+        index = NUM_ARGUMENT_REGISTERS + NUM_FLOAT_ARGUMENT_REGISTERS + (offset & ShuffleEntry::OFSMASK);
+    }
+
+    return index;
+}
+#endif // UNIX_AMD64_ABI && FEATURE_UNIX_AMD64_STRUCT_PASSING
+
 VOID GenerateShuffleArray(MethodDesc* pInvoke, MethodDesc *pTargetMeth, SArray<ShuffleEntry> * pShuffleEntryArray)
 {
     STANDARD_VM_CONTRACT;
@@ -352,6 +378,10 @@ VOID GenerateShuffleArray(MethodDesc* pInvoke, MethodDesc *pTargetMeth, SArray<S
     ArgLocDesc sArgSrc;
     ArgLocDesc sArgDst;
 
+#if defined(UNIX_AMD64_ABI) && defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
+    int argSlots = NUM_FLOAT_ARGUMENT_REGISTERS + NUM_ARGUMENT_REGISTERS + sArgPlacerSrc.SizeOfArgStack() / sizeof(size_t);
+#endif // UNIX_AMD64_ABI && FEATURE_UNIX_AMD64_STRUCT_PASSING
+
     // If the target method in non-static (this happens for open instance delegates), we need to account for
     // the implicit this parameter.
     if (sSigDst.HasThis())
@@ -367,7 +397,6 @@ VOID GenerateShuffleArray(MethodDesc* pInvoke, MethodDesc *pTargetMeth, SArray<S
 
         entry.srcofs = iteratorSrc.GetNextOfs();
         entry.dstofs = iteratorDst.GetNextOfs();
-
         pShuffleEntryArray->Append(entry);
     }
 
@@ -392,77 +421,80 @@ VOID GenerateShuffleArray(MethodDesc* pInvoke, MethodDesc *pTargetMeth, SArray<S
             pShuffleEntryArray->Append(entry);
     }
 
-#if defined(UNIX_AMD64_ABI) && defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
-    // The shuffle entries are produced in two passes on Unix AMD64. The first pass generates shuffle entries for
-    // all cases except of shuffling struct argument from stack to registers, which is performed in the second pass
-    // The reason is that if such structure argument contained floating point field and it was followed by a 
-    // floating point argument, generating code for transferring the structure from stack into registers would
-    // overwrite the xmm register of the floating point argument before it could actually be shuffled.
-    // For example, consider this case:
-    // struct S { int x; float y; };
-    // void fn(long a, long b, long c, long d, long e, S f, float g);
-    // src: rdi = this, rsi = a, rdx = b, rcx = c, r8 = d, r9 = e, stack: f, xmm0 = g
-    // dst: rdi = a, rsi = b, rdx = c, rcx = d, r8 = e, r9 = S.x, xmm0 = s.y, xmm1 = g
-    for (int pass = 0; pass < 2; pass++)
-#endif // UNIX_AMD64_ABI && FEATURE_UNIX_AMD64_STRUCT_PASSING
+    // Iterate all the regular arguments. mapping source registers and stack locations to the corresponding
+    // destination locations.
+    while ((ofsSrc = sArgPlacerSrc.GetNextOffset()) != TransitionBlock::InvalidOffset)
     {
-        // Iterate all the regular arguments. mapping source registers and stack locations to the corresponding
-        // destination locations.
-        while ((ofsSrc = sArgPlacerSrc.GetNextOffset()) != TransitionBlock::InvalidOffset)
+        ofsDst = sArgPlacerDst.GetNextOffset();
+
+        // Find the argument location mapping for both source and destination signature. A single argument can
+        // occupy a floating point register, a general purpose register, a pair of registers of any kind or
+        // a stack slot.
+        sArgPlacerSrc.GetArgLoc(ofsSrc, &sArgSrc);
+        sArgPlacerDst.GetArgLoc(ofsDst, &sArgDst);
+
+        ShuffleIterator iteratorSrc(&sArgSrc);
+        ShuffleIterator iteratorDst(&sArgDst);
+
+        // Shuffle each slot in the argument (register or stack slot) from source to destination.
+        while (iteratorSrc.HasNextOfs())
         {
-            ofsDst = sArgPlacerDst.GetNextOffset();
+            // Locate the next slot to shuffle in the source and destination and encode the transfer into a
+            // shuffle entry.
+            entry.srcofs = iteratorSrc.GetNextOfs();
+            entry.dstofs = iteratorDst.GetNextOfs();
+
+            // Only emit this entry if it's not a no-op (i.e. the source and destination locations are
+            // different).
+            if (entry.srcofs != entry.dstofs)
+                pShuffleEntryArray->Append(entry);
+        }
+
+        // We should have run out of slots to shuffle in the destination at the same time as the source.
+        _ASSERTE(!iteratorDst.HasNextOfs());
+    }
 
 #if defined(UNIX_AMD64_ABI) && defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
-            bool shuffleStructFromStackToRegs = (ofsSrc != TransitionBlock::StructInRegsOffset) && (ofsDst == TransitionBlock::StructInRegsOffset);
-            if (((pass == 0) && shuffleStructFromStackToRegs) || 
-                ((pass == 1) && !shuffleStructFromStackToRegs))
-            {
-                continue;
-            }
-#endif // UNIX_AMD64_ABI && FEATURE_UNIX_AMD64_STRUCT_PASSING
-            // Find the argument location mapping for both source and destination signature. A single argument can
-            // occupy a floating point register (in which case we don't need to do anything, they're not shuffled)
-            // or some combination of general registers and the stack.
-            sArgPlacerSrc.GetArgLoc(ofsSrc, &sArgSrc);
-            sArgPlacerDst.GetArgLoc(ofsDst, &sArgDst);
+    // The Unix AMD64 ABI can cause a struct to be passed on stack for the source and in registers for the destination.
+    // That can cause some arguments that are passed on stack for the destination to be passed in registers in the source.
+    // An extreme example of that is e.g.:
+    //   void fn(int, int, int, int, int, struct {int, double}, double, double, double, double, double, double, double, double, double, double)
+    // For this signature, the shuffle needs to move slots as follows (please note the "forward" movement of xmm registers):
+    //   RDI->RSI, RDX->RCX, R8->RDX, R9->R8, stack[0]->R9, xmm0->xmm1, xmm1->xmm2, ... xmm6->xmm7, xmm7->stack[0], stack[1]->xmm0, stack[2]->stack[1], stack[3]->stack[2]
+    // To prevent overwriting of slots before they are moved, we need to sort the move operations.
 
-            ShuffleIterator iteratorSrc(&sArgSrc);
-            ShuffleIterator iteratorDst(&sArgDst);
+    NewArrayHolder<bool> filledSlots = new bool[argSlots];
 
-            // Shuffle each slot in the argument (register or stack slot) from source to destination.
-            while (iteratorSrc.HasNextOfs())
-            {
-                // Locate the next slot to shuffle in the source and destination and encode the transfer into a
-                // shuffle entry.
-                entry.srcofs = iteratorSrc.GetNextOfs();
-                entry.dstofs = iteratorDst.GetNextOfs();
-
-                // Only emit this entry if it's not a no-op (i.e. the source and destination locations are
-                // different).
-                if (entry.srcofs != entry.dstofs)
-                    pShuffleEntryArray->Append(entry);
-            }
+    bool reordered;
+    do
+    {
+        reordered = false;
 
-            // We should have run out of slots to shuffle in the destination at the same time as the source.
-            _ASSERTE(!iteratorDst.HasNextOfs());
+        for (int i = 0; i < argSlots; i++)
+        {
+            filledSlots[i] = false;
         }
-#if defined(UNIX_AMD64_ABI) && defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
-        if (pass == 0)
+        for (int i = 0; i < pShuffleEntryArray->GetCount(); i++)
         {
-            // Reset the iterator for the 2nd pass
-            sSigSrc.Reset();
-            sSigDst.Reset();
+            entry = (*pShuffleEntryArray)[i];
 
-            sArgPlacerSrc = ArgIterator(&sSigSrc);
-            sArgPlacerDst = ArgIterator(&sSigDst);
-
-            if (sSigDst.HasThis())
+            // If the slot that we are moving the argument to was filled in already, we need to move this entry in front
+            // of the entry that filled it in.
+            if (filledSlots[GetNormalizedArgumentSlotIndex(entry.srcofs)])
             {
-                sArgPlacerSrc.GetNextOffset();
+                int j;
+                for (j = i; (*pShuffleEntryArray)[j].dstofs != entry.srcofs; j--)
+                    (*pShuffleEntryArray)[j] = (*pShuffleEntryArray)[j - 1];
+
+                (*pShuffleEntryArray)[j] = entry;
+                reordered = true;
             }
+
+            filledSlots[GetNormalizedArgumentSlotIndex(entry.dstofs)] = true;
         }
-#endif // UNIX_AMD64_ABI && FEATURE_UNIX_AMD64_STRUCT_PASSING
     }
+    while (reordered);
+#endif // UNIX_AMD64_ABI && FEATURE_UNIX_AMD64_STRUCT_PASSING
 
     entry.srcofs = ShuffleEntry::SENTINEL;
     entry.dstofs = 0;
index 28a012e..ff3ec48 100644 (file)
@@ -3834,28 +3834,56 @@ VOID StubLinkerCPU::EmitShuffleThunk(ShuffleEntry *pShuffleEntryArray)
     {
         if (pEntry->srcofs & ShuffleEntry::REGMASK)
         {
-            // If source is present in register then destination must also be a register
-            _ASSERTE(pEntry->dstofs & ShuffleEntry::REGMASK);
-            // Both the srcofs and dstofs must be of the same kind of registers - float or general purpose.
-            _ASSERTE((pEntry->dstofs & ShuffleEntry::FPREGMASK) == (pEntry->srcofs & ShuffleEntry::FPREGMASK));
-
-            int dstRegIndex = pEntry->dstofs & ShuffleEntry::OFSREGMASK;
+            // Source in a general purpose or float register, destination in the same kind of a register or on stack
             int srcRegIndex = pEntry->srcofs & ShuffleEntry::OFSREGMASK;
 
-            if (pEntry->srcofs & ShuffleEntry::FPREGMASK) 
+            if (pEntry->dstofs & ShuffleEntry::REGMASK)
             {
-                // movdqa dstReg, srcReg
-                X64EmitMovXmmXmm((X86Reg)(kXMM0 + dstRegIndex), (X86Reg)(kXMM0 + srcRegIndex));
+                // Source in register, destination in register
+
+                // Both the srcofs and dstofs must be of the same kind of registers - float or general purpose.
+                _ASSERTE((pEntry->dstofs & ShuffleEntry::FPREGMASK) == (pEntry->srcofs & ShuffleEntry::FPREGMASK));
+                int dstRegIndex = pEntry->dstofs & ShuffleEntry::OFSREGMASK;
+
+                if (pEntry->srcofs & ShuffleEntry::FPREGMASK) 
+                {
+                    // movdqa dstReg, srcReg
+                    X64EmitMovXmmXmm((X86Reg)(kXMM0 + dstRegIndex), (X86Reg)(kXMM0 + srcRegIndex));
+                }
+                else
+                {
+                    // mov dstReg, srcReg
+                    X86EmitMovRegReg(c_argRegs[dstRegIndex], c_argRegs[srcRegIndex]);
+                }
             }
             else
             {
-                // mov dstReg, srcReg
-                X86EmitMovRegReg(c_argRegs[dstRegIndex], c_argRegs[srcRegIndex]);
+                // Source in register, destination on stack
+                int dstOffset = (pEntry->dstofs + 1) * sizeof(void*);
+
+                if (pEntry->srcofs & ShuffleEntry::FPREGMASK) 
+                {
+                    if (pEntry->dstofs & ShuffleEntry::FPSINGLEMASK)
+                    {
+                        // movss [rax + dst], srcReg
+                        X64EmitMovSSToMem((X86Reg)(kXMM0 + srcRegIndex), SCRATCH_REGISTER_X86REG, dstOffset);
+                    }
+                    else
+                    {
+                        // movsd [rax + dst], srcReg
+                        X64EmitMovSDToMem((X86Reg)(kXMM0 + srcRegIndex), SCRATCH_REGISTER_X86REG, dstOffset);
+                    }
+                }
+                else
+                {
+                    // mov [rax + dst], srcReg
+                    X86EmitIndexRegStore (SCRATCH_REGISTER_X86REG, dstOffset, c_argRegs[srcRegIndex]);
+                }
             }
         }
         else if (pEntry->dstofs & ShuffleEntry::REGMASK)
         {
-            // source must be on the stack
+            // Source on stack, destination in register
             _ASSERTE(!(pEntry->srcofs & ShuffleEntry::REGMASK));
 
             int dstRegIndex = pEntry->dstofs & ShuffleEntry::OFSREGMASK;
@@ -3882,10 +3910,8 @@ VOID StubLinkerCPU::EmitShuffleThunk(ShuffleEntry *pShuffleEntryArray)
         }
         else
         {
-            // source must be on the stack
+            // Source on stack, destination on stack
             _ASSERTE(!(pEntry->srcofs & ShuffleEntry::REGMASK));
-
-            // dest must be on the stack
             _ASSERTE(!(pEntry->dstofs & ShuffleEntry::REGMASK));
 
             // mov r10, [rax + src]
diff --git a/tests/src/Regressions/coreclr/GitHub_16833/Test16833.csproj b/tests/src/Regressions/coreclr/GitHub_16833/Test16833.csproj
new file mode 100644 (file)
index 0000000..10ccb29
--- /dev/null
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{D1303490-9864-4CC7-9FC0-964B371C7D8C}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <CLRTestKind>BuildAndRun</CLRTestKind>
+    <CLRTestPriority>1</CLRTestPriority>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+  </PropertyGroup>
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <ItemGroup>
+    <!-- Add Compile Object Here -->
+    <Compile Include="test16833.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="../../../Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
+  </ItemGroup>
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+  <PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' ">
+  </PropertyGroup>
+</Project>
\ No newline at end of file
diff --git a/tests/src/Regressions/coreclr/GitHub_16833/test16833.cs b/tests/src/Regressions/coreclr/GitHub_16833/test16833.cs
new file mode 100644 (file)
index 0000000..35e670b
--- /dev/null
@@ -0,0 +1,385 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using System;
+
+namespace TestShufflingThunk
+{
+    // This is a regression test for shuffling thunk creation on Unix AMD64. The calling convention causes some interesting shuffles that
+    // the code before the fix was not handling properly.
+    struct SLongLong
+    {
+        public long x;
+        public long y;
+        public override string ToString()
+        {
+            return $"[{x}, {y}]";
+        }
+    }
+
+    struct SIntDouble
+    {
+        public int x;
+        public double y;
+        public override string ToString()
+        {
+            return $"[{x}, {y}]";
+        }
+    }
+
+    struct SInt
+    {
+        public int x;
+        public override string ToString()
+        {
+            return $"[{x}]";
+        }
+    }
+
+    struct SLargeReturnStruct
+    {
+        public long x;
+        public long y;
+        public long z;
+        public string s;
+        public override string ToString()
+        {
+            return $"{s} -> [{x}, {y}, {z}]";
+        }
+    }
+
+    class TestClass
+    {
+        public static readonly string Test1Result = "Test1:  1, 2, 3, 4, [5, 6], 7";
+        public static string Test1(int i1, int i2, int i3, int i4, SLongLong s, int i5)
+        {
+            return $"Test1:  {i1}, {i2}, {i3}, {i4}, {s}, {i5}";
+        }
+        public static string Test2(int i1, int i2, int i3, int i4, SIntDouble s, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10, int i5)
+        {
+            return $"Test2:  {i1}, {i2}, {i3}, {i4}, {s}, {f1}, {f2}, {f3}, {f4}, {f5}, {f6}, {f7}, {f8}, {f9}, {f10}, {i5}";
+        }
+        public static string Test3(int i1, int i2, int i3, int i4, int i5, SInt s, int i6)
+        {
+            return $"Test3:  {i1}, {i2}, {i3}, {i4}, {i5}, {s}, {i6}";
+        }
+        public static string Test4(int i1, int i2, int i3, int i4, SIntDouble s, double i5)
+        {
+            return $"Test4:  {i1}, {i2}, {i3}, {i4}, {s}, {i5}";
+        }
+        public static string Test5(int i1, int i2, int i3, int i4, SLongLong s)
+        {
+            return $"Test5:  {i1}, {i2}, {i3}, {i4}, {s}";
+        }
+        public static string Test6(int i1, int i2, int i3, int i4, int i5, SIntDouble s, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10)
+        {
+            return $"Test6:  {i1}, {i2}, {i3}, {i4}, {i5}, {s}, {f1}, {f2}, {f3}, {f4}, {f5}, {f6}, {f7}, {f8}, {f9}, {f10}";
+        }
+
+        public static SLargeReturnStruct Test1RB(int i1, int i2, int i3, int i4, SLongLong s, int i5)
+        {
+            string args = $"Test1RB:  {i1}, {i2}, {i3}, {i4}, {s}, {i5}";
+            return new SLargeReturnStruct { x = -1, y = -2, z = -3, s = args };
+        }
+        public static SLargeReturnStruct Test2RB(int i1, int i2, int i3, int i4, SIntDouble s, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10, int i5)
+        {
+            string args = $"Test2RB:  {i1}, {i2}, {i3}, {i4}, {s}, {f1}, {f2}, {f3}, {f4}, {f5}, {f6}, {f7}, {f8}, {f9}, {f10}, {i5}";
+            return new SLargeReturnStruct { x = -1, y = -2, z = -3, s = args };
+        }
+        public static SLargeReturnStruct Test3RB(int i1, int i2, int i3, int i4, int i5, SInt s, int i6)
+        {
+            string args = $"Test3RB:  {i1}, {i2}, {i3}, {i4}, {i5}, {s}, {i6}";
+            return new SLargeReturnStruct { x = -1, y = -2, z = -3, s = args };
+        }
+        public static SLargeReturnStruct Test4RB(int i1, int i2, int i3, int i4, SIntDouble s, double i5)
+        {
+            string args = $"Test4RB:  {i1}, {i2}, {i3}, {i4}, {s}, {i5}";
+            return new SLargeReturnStruct { x = -1, y = -2, z = -3, s = args };
+        }
+        public static SLargeReturnStruct Test5RB(int i1, int i2, int i3, int i4, SLongLong s)
+        {
+            string args = $"Test5RB:  {i1}, {i2}, {i3}, {i4}, {s}";
+            return new SLargeReturnStruct { x = -1, y = -2, z = -3, s = args };
+        }
+        public static SLargeReturnStruct Test6RB(int i1, int i2, int i3, int i4, int i5, SIntDouble s, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10)
+        {
+            string args = $"Test6RB:  {i1}, {i2}, {i3}, {i4}, {i5}, {s}, {f1}, {f2}, {f3}, {f4}, {f5}, {f6}, {f7}, {f8}, {f9}, {f10}";
+            return new SLargeReturnStruct { x = -1, y = -2, z = -3, s = args };
+        }
+
+        public string Test1M(int i1, int i2, int i3, int i4, SLongLong s, int i5)
+        {
+            return $"Test1M: i1, {i2}, {i3}, {i4}, {s}, {i5}";
+        }
+        public string Test2M(int i1, int i2, int i3, int i4, SIntDouble s, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10, int i5)
+        {
+            return $"Test2M: i1, {i2}, {i3}, {i4}, {s}, {f1}, {f2}, {f3}, {f4}, {f5}, {f6}, {f7}, {f8}, {f9}, {f10}, {i5}";
+        }
+        public string Test3M(int i1, int i2, int i3, int i4, int i5, SInt s, int i6)
+        {
+            return $"Test3M: i1, {i2}, {i3}, {i4}, {i5}, {s}, {i6}";
+        }
+        public string Test4M(int i1, int i2, int i3, int i4, SIntDouble s, double i5)
+        {
+            return $"Test4M: i1, {i2}, {i3}, {i4}, {s}, {i5}";
+        }
+        public string Test5M(int i1, int i2, int i3, int i4, SLongLong s)
+        {
+            return $"Test5M: i1, {i2}, {i3}, {i4}, {s}";
+        }
+        public string Test6M(int i1, int i2, int i3, int i4, int i5, SIntDouble s, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10)
+        {
+            return $"Test6M: i1, {i2}, {i3}, {i4}, {i5}, {s}, {f1}, {f2}, {f3}, {f4}, {f5}, {f6}, {f7}, {f8}, {f9}, {f10}";
+        }
+        public SLargeReturnStruct Test1MRB(int i1, int i2, int i3, int i4, SLongLong s, int i5)
+        {
+            string args = $"Test1MRB: {i1}, {i2}, {i3}, {i4}, {s}, {i5}";
+            return new SLargeReturnStruct { x = -1, y = -2, z = -3, s = args };
+        }
+        public SLargeReturnStruct Test2MRB(int i1, int i2, int i3, int i4, SIntDouble s, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10, int i5)
+        {
+            string args = $"Test2MRB: {i1}, {i2}, {i3}, {i4}, {s}, {f1}, {f2}, {f3}, {f4}, {f5}, {f6}, {f7}, {f8}, {f9}, {f10}, {i5}";
+            return new SLargeReturnStruct { x = -1, y = -2, z = -3, s = args };
+        }
+        public SLargeReturnStruct Test3MRB(int i1, int i2, int i3, int i4, int i5, SInt s, int i6)
+        {
+            string args = $"Test3MRB: {i1}, {i2}, {i3}, {i4}, {i5}, {s}, {i6}";
+            return new SLargeReturnStruct { x = -1, y = -2, z = -3, s = args };
+        }
+        public SLargeReturnStruct Test4MRB(int i1, int i2, int i3, int i4, SIntDouble s, double i5)
+        {
+            string args = $"Test4MRB: {i1}, {i2}, {i3}, {i4}, {s}, {i5}";
+            return new SLargeReturnStruct { x = -1, y = -2, z = -3, s = args };
+        }
+        public SLargeReturnStruct Test5MRB(int i1, int i2, int i3, int i4, SLongLong s)
+        {
+            string args = $"Test5MRB: {i1}, {i2}, {i3}, {i4}, {s}";
+            return new SLargeReturnStruct { x = -1, y = -2, z = -3, s = args };
+        }
+        public SLargeReturnStruct Test6MRB(int i1, int i2, int i3, int i4, int i5, SIntDouble s, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10)
+        {
+            string args = $"Test6MRB: {i1}, {i2}, {i3}, {i4}, {i5}, {s}, {f1}, {f2}, {f3}, {f4}, {f5}, {f6}, {f7}, {f8}, {f9}, {f10}";
+            return new SLargeReturnStruct { x = -1, y = -2, z = -3, s = args };
+        }
+    }
+
+    class Test16833
+    {
+        delegate string Delegate2m(TestClass tc, int i1, int i2, int i3, int i4, SIntDouble s, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10, int i5);
+        delegate string Delegate6m(TestClass tc, int i1, int i2, int i3, int i4, int i5, SIntDouble s, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10);
+        delegate SLargeReturnStruct Delegate2mrb(TestClass tc, int i1, int i2, int i3, int i4, SIntDouble s, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10, int i5);
+        delegate SLargeReturnStruct Delegate6mrb(TestClass tc, int i1, int i2, int i3, int i4, int i5, SIntDouble s, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10);
+
+        static void CheckResult(ref int exitCode, string test, string result, string expected)
+        {
+            if (result != expected)
+            {
+                Console.WriteLine($"Test {test} failed. Expected \"{expected}\", got \"{result}\"");
+                exitCode = 1;
+            }
+        }
+
+        static int Main(string[] args)
+        {
+            int exitCode = 100;
+
+            string result;
+
+            var func1 = (Func<int, int, int, int, SLongLong, int, string>)Delegate.CreateDelegate(
+                typeof(Func<int, int, int, int, SLongLong, int, string>),
+                typeof(TestClass).GetMethod(nameof(TestClass.Test1)));
+            
+            SLongLong s1 = new SLongLong { x = 5, y = 6};
+            result = func1(1, 2, 3, 4, s1, 7);
+            CheckResult(ref exitCode, nameof(TestClass.Test1), result, "Test1:  1, 2, 3, 4, [5, 6], 7");
+
+            var func2 = (Func<int, int, int, int, SIntDouble, double, double, double, double, double, double, double, double, double, double, int, string>)Delegate.CreateDelegate(
+                typeof(Func<int, int, int, int, SIntDouble, double, double, double, double, double, double, double, double, double, double, int, string>),
+                typeof(TestClass).GetMethod(nameof(TestClass.Test2)));
+
+            SIntDouble s2 = new SIntDouble { x = 5, y = 6.0 };
+            result = func2(1, 2, 3, 4, s2, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17);
+            CheckResult(ref exitCode, nameof(TestClass.Test2), result, "Test2:  1, 2, 3, 4, [5, 6], 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17");
+
+            var func3 = (Func<int, int, int, int, int, SInt, int, string>)Delegate.CreateDelegate(
+                typeof(Func<int, int, int, int, int, SInt, int, string>),
+                typeof(TestClass).GetMethod(nameof(TestClass.Test3)));
+
+            SInt s3 = new SInt { x = 6 };
+            result = func3(1, 2, 3, 4, 5, s3, 7);
+            CheckResult(ref exitCode, nameof(TestClass.Test3), result, "Test3:  1, 2, 3, 4, 5, [6], 7");
+
+            var func4 = (Func<int, int, int, int, SIntDouble, double, string>)Delegate.CreateDelegate(
+                typeof(Func<int, int, int, int, SIntDouble, double, string>),
+                typeof(TestClass).GetMethod(nameof(TestClass.Test4)));
+
+            SIntDouble s4 = new SIntDouble { x = 5, y = 6.0 };
+            result = func4(1, 2, 3, 4, s4, 7.0);
+            CheckResult(ref exitCode, nameof(TestClass.Test4), result, "Test4:  1, 2, 3, 4, [5, 6], 7");
+
+            var func5 = (Func<int, int, int, int, SLongLong, string>)Delegate.CreateDelegate(
+                typeof(Func<int, int, int, int, SLongLong, string>),
+                typeof(TestClass).GetMethod(nameof(TestClass.Test5)));
+
+            SLongLong s5 = new SLongLong { x = 5, y = 6 };
+            result = func5(1, 2, 3, 4, s1);
+            CheckResult(ref exitCode, nameof(TestClass.Test5), result, "Test5:  1, 2, 3, 4, [5, 6]");
+
+            var func6 = (Func<int, int, int, int, int, SIntDouble, double, double, double, double, double, double, double, double, double, double, string>)Delegate.CreateDelegate(
+                typeof(Func<int, int, int, int, int, SIntDouble, double, double, double, double, double, double, double, double, double, double, string>),
+                typeof(TestClass).GetMethod(nameof(TestClass.Test6)));
+
+            SIntDouble s6 = new SIntDouble { x = 6, y = 7.0 };
+            result = func6(1, 2, 3, 4, 5, s6, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0);
+            CheckResult(ref exitCode, nameof(TestClass.Test6), result, "Test6:  1, 2, 3, 4, 5, [6, 7], 8, 9, 10, 11, 12, 13, 14, 15, 16, 17");
+
+            TestClass tc = new TestClass();
+
+            var func1m = (Func<TestClass, int, int, int, int, SLongLong, int, string>)Delegate.CreateDelegate(
+                typeof(Func<TestClass, int, int, int, int, SLongLong, int, string>),
+                null, 
+                typeof(TestClass).GetMethod(nameof(TestClass.Test1M)));
+
+            result = func1m(tc, 1, 2, 3, 4, s1, 7);
+            CheckResult(ref exitCode, nameof(TestClass.Test1M), result, "Test1M: i1, 2, 3, 4, [5, 6], 7");
+
+            var func2m = (Delegate2m)Delegate.CreateDelegate(
+                typeof(Delegate2m),
+                null,
+                typeof(TestClass).GetMethod(nameof(TestClass.Test2M)));
+
+            result = func2m(tc, 1, 2, 3, 4, s2, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17);
+            CheckResult(ref exitCode, nameof(TestClass.Test2M), result, "Test2M: i1, 2, 3, 4, [5, 6], 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17");
+
+            var func3m = (Func<TestClass, int, int, int, int, int, SInt, int, string>)Delegate.CreateDelegate(
+                typeof(Func<TestClass, int, int, int, int, int, SInt, int, string>),
+                null,
+                typeof(TestClass).GetMethod(nameof(TestClass.Test3M)));
+
+            result = func3m(tc, 1, 2, 3, 4, 5, s3, 7);
+            CheckResult(ref exitCode, nameof(TestClass.Test3M), result, "Test3M: i1, 2, 3, 4, 5, [6], 7");
+
+            var func4m = (Func<TestClass, int, int, int, int, SIntDouble, double, string>)Delegate.CreateDelegate(
+                typeof(Func<TestClass, int, int, int, int, SIntDouble, double, string>),
+                null,
+                typeof(TestClass).GetMethod(nameof(TestClass.Test4M)));
+
+            result = func4m(tc, 1, 2, 3, 4, s4, 7.0);
+            CheckResult(ref exitCode, nameof(TestClass.Test4M), result, "Test4M: i1, 2, 3, 4, [5, 6], 7");
+
+            var func5m = (Func<TestClass, int, int, int, int, SLongLong, string>)Delegate.CreateDelegate(
+                typeof(Func<TestClass, int, int, int, int, SLongLong, string>),
+                null,
+                typeof(TestClass).GetMethod(nameof(TestClass.Test5M)));
+
+            result = func5m(tc, 1, 2, 3, 4, s1);
+            CheckResult(ref exitCode, nameof(TestClass.Test5M), result, "Test5M: i1, 2, 3, 4, [5, 6]");
+
+            var func6m = (Delegate6m)Delegate.CreateDelegate(
+                typeof(Delegate6m),
+                null,
+                typeof(TestClass).GetMethod(nameof(TestClass.Test6M)));
+
+            result = func6m(tc, 1, 2, 3, 4, 5, s6, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0);
+            CheckResult(ref exitCode, nameof(TestClass.Test6M), result, "Test6M: i1, 2, 3, 4, 5, [6, 7], 8, 9, 10, 11, 12, 13, 14, 15, 16, 17");
+
+            var func1rb = (Func<int, int, int, int, SLongLong, int, SLargeReturnStruct>)Delegate.CreateDelegate(
+                typeof(Func<int, int, int, int, SLongLong, int, SLargeReturnStruct>),
+                typeof(TestClass).GetMethod(nameof(TestClass.Test1RB)));
+
+
+            SLargeReturnStruct result1 = func1rb(1, 2, 3, 4, s1, 7);
+            CheckResult(ref exitCode, nameof(TestClass.Test1RB), result1.ToString(), "Test1RB:  1, 2, 3, 4, [5, 6], 7 -> [-1, -2, -3]");
+
+            var func2rb = (Func<int, int, int, int, SIntDouble, double, double, double, double, double, double, double, double, double, double, int, SLargeReturnStruct>)Delegate.CreateDelegate(
+                typeof(Func<int, int, int, int, SIntDouble, double, double, double, double, double, double, double, double, double, double, int, SLargeReturnStruct>),
+                typeof(TestClass).GetMethod(nameof(TestClass.Test2RB)));
+
+            SLargeReturnStruct result2 = func2rb(1, 2, 3, 4, s2, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17);
+            CheckResult(ref exitCode, nameof(TestClass.Test2RB), result2.ToString(), "Test2RB:  1, 2, 3, 4, [5, 6], 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 -> [-1, -2, -3]");
+
+            var func3rb = (Func<int, int, int, int, int, SInt, int, SLargeReturnStruct>)Delegate.CreateDelegate(
+                typeof(Func<int, int, int, int, int, SInt, int, SLargeReturnStruct>),
+                typeof(TestClass).GetMethod(nameof(TestClass.Test3RB)));
+
+            SLargeReturnStruct result3 = func3rb(1, 2, 3, 4, 5, s3, 7);
+            CheckResult(ref exitCode, nameof(TestClass.Test3RB), result3.ToString(), "Test3RB:  1, 2, 3, 4, 5, [6], 7 -> [-1, -2, -3]");
+
+            var func4rb = (Func<int, int, int, int, SIntDouble, double, SLargeReturnStruct>)Delegate.CreateDelegate(
+                typeof(Func<int, int, int, int, SIntDouble, double, SLargeReturnStruct>),
+                typeof(TestClass).GetMethod(nameof(TestClass.Test4RB)));
+
+            SLargeReturnStruct result4 = func4rb(1, 2, 3, 4, s4, 7.0);
+            CheckResult(ref exitCode, nameof(TestClass.Test4RB), result4.ToString(), "Test4RB:  1, 2, 3, 4, [5, 6], 7 -> [-1, -2, -3]");
+
+            var func5rb = (Func<int, int, int, int, SLongLong, SLargeReturnStruct>)Delegate.CreateDelegate(
+                typeof(Func<int, int, int, int, SLongLong, SLargeReturnStruct>),
+                typeof(TestClass).GetMethod(nameof(TestClass.Test5RB)));
+
+            SLargeReturnStruct result5 = func5rb(1, 2, 3, 4, s1);
+            CheckResult(ref exitCode, nameof(TestClass.Test5RB), result5.ToString(), "Test5RB:  1, 2, 3, 4, [5, 6] -> [-1, -2, -3]");
+
+            var func6rb = (Func<int, int, int, int, int, SIntDouble, double, double, double, double, double, double, double, double, double, double, SLargeReturnStruct>)Delegate.CreateDelegate(
+                typeof(Func<int, int, int, int, int, SIntDouble, double, double, double, double, double, double, double, double, double, double, SLargeReturnStruct>),
+                typeof(TestClass).GetMethod(nameof(TestClass.Test6RB)));
+
+            SLargeReturnStruct result6 = func6rb(1, 2, 3, 4, 5, s6, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0);
+            CheckResult(ref exitCode, nameof(TestClass.Test6RB), result6.ToString(), "Test6RB:  1, 2, 3, 4, 5, [6, 7], 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 -> [-1, -2, -3]");
+
+            var func1mrb = (Func<TestClass, int, int, int, int, SLongLong, int, SLargeReturnStruct>)Delegate.CreateDelegate(
+                typeof(Func<TestClass, int, int, int, int, SLongLong, int, SLargeReturnStruct>),
+                null,
+                typeof(TestClass).GetMethod(nameof(TestClass.Test1MRB)));
+
+            SLargeReturnStruct result1mrb = func1mrb(tc, 1, 2, 3, 4, s1, 7);
+            CheckResult(ref exitCode, nameof(TestClass.Test1MRB), result1mrb.ToString(), "Test1MRB: 1, 2, 3, 4, [5, 6], 7 -> [-1, -2, -3]");
+
+            var func2mrb = (Delegate2mrb)Delegate.CreateDelegate(
+                typeof(Delegate2mrb),
+                null,
+                typeof(TestClass).GetMethod(nameof(TestClass.Test2MRB)));
+
+            SLargeReturnStruct result2mrb = func2mrb(tc, 1, 2, 3, 4, s2, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17);
+            CheckResult(ref exitCode, nameof(TestClass.Test2MRB), result2mrb.ToString(), "Test2MRB: 1, 2, 3, 4, [5, 6], 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 -> [-1, -2, -3]");
+
+            var func3mrb = (Func<TestClass, int, int, int, int, int, SInt, int, SLargeReturnStruct>)Delegate.CreateDelegate(
+                typeof(Func<TestClass, int, int, int, int, int, SInt, int, SLargeReturnStruct>),
+                null,
+                typeof(TestClass).GetMethod(nameof(TestClass.Test3MRB)));
+
+            SLargeReturnStruct result3mrb = func3mrb(tc, 1, 2, 3, 4, 5, s3, 7);
+            CheckResult(ref exitCode, nameof(TestClass.Test3MRB), result3mrb.ToString(), "Test3MRB: 1, 2, 3, 4, 5, [6], 7 -> [-1, -2, -3]");
+
+            var func4mrb = (Func<TestClass, int, int, int, int, SIntDouble, double, SLargeReturnStruct>)Delegate.CreateDelegate(
+                typeof(Func<TestClass, int, int, int, int, SIntDouble, double, SLargeReturnStruct>),
+                null,
+                typeof(TestClass).GetMethod(nameof(TestClass.Test4MRB)));
+
+            SLargeReturnStruct result4mrb = func4mrb(tc, 1, 2, 3, 4, s4, 7.0);
+            CheckResult(ref exitCode, nameof(TestClass.Test4MRB), result4mrb.ToString(), "Test4MRB: 1, 2, 3, 4, [5, 6], 7 -> [-1, -2, -3]");
+
+            var func5mrb = (Func<TestClass, int, int, int, int, SLongLong, SLargeReturnStruct>)Delegate.CreateDelegate(
+                typeof(Func<TestClass, int, int, int, int, SLongLong, SLargeReturnStruct>),
+                null,
+                typeof(TestClass).GetMethod(nameof(TestClass.Test5MRB)));
+
+            SLargeReturnStruct result5mrb = func5mrb(tc, 1, 2, 3, 4, s1);
+            CheckResult(ref exitCode, nameof(TestClass.Test5MRB), result5mrb.ToString(), "Test5MRB: 1, 2, 3, 4, [5, 6] -> [-1, -2, -3]");
+
+            var func6mrb = (Delegate6mrb)Delegate.CreateDelegate(
+                typeof(Delegate6mrb),
+                null,
+                typeof(TestClass).GetMethod(nameof(TestClass.Test6MRB)));
+
+            SLargeReturnStruct result6mrb = func6mrb(tc, 1, 2, 3, 4, 5, s6, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0);
+            CheckResult(ref exitCode, nameof(TestClass.Test6MRB), result6mrb.ToString(), "Test6MRB: 1, 2, 3, 4, 5, [6, 7], 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 -> [-1, -2, -3]");
+
+            if (exitCode == 100)
+            {
+                Console.WriteLine("Test SUCCEEDED");
+            }
+
+            return exitCode;
+        }
+    }
+}