summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tests/issues.targets3
-rw-r--r--tests/src/JIT/Stress/ABI/ABIs.cs191
-rw-r--r--tests/src/JIT/Stress/ABI/Callee.cs108
-rw-r--r--tests/src/JIT/Stress/ABI/Config.cs47
-rw-r--r--tests/src/JIT/Stress/ABI/Gen.cs187
-rw-r--r--tests/src/JIT/Stress/ABI/PInvokes.cs113
-rw-r--r--tests/src/JIT/Stress/ABI/Program.cs442
-rw-r--r--tests/src/JIT/Stress/ABI/Stubs.cs335
-rw-r--r--tests/src/JIT/Stress/ABI/TailCallEventListener.cs57
-rw-r--r--tests/src/JIT/Stress/ABI/TailCalls.cs74
-rw-r--r--tests/src/JIT/Stress/ABI/Types.cs85
-rw-r--r--tests/src/JIT/Stress/ABI/pinvokes_d.csproj38
-rw-r--r--tests/src/JIT/Stress/ABI/pinvokes_do.csproj38
-rw-r--r--tests/src/JIT/Stress/ABI/stubs_do.csproj38
-rw-r--r--tests/src/JIT/Stress/ABI/tailcalls_d.csproj38
-rw-r--r--tests/src/JIT/Stress/ABI/tailcalls_do.csproj38
16 files changed, 1832 insertions, 0 deletions
diff --git a/tests/issues.targets b/tests/issues.targets
index 5693737306..70d05e780f 100644
--- a/tests/issues.targets
+++ b/tests/issues.targets
@@ -247,6 +247,9 @@
<ExcludeList Include="$(XunitTestBinBase)/JIT/Methodical/tailcall/_il_reltest_implicit/*">
<Issue>Unix doesn't support slow tail calls #2556, arm32 doesn't support fast tail calls #13828.</Issue>
</ExcludeList>
+ <ExcludeList Include="$(XunitTestBinBase)/JIT/Stress/ABI/**/*">
+ <Issue>26105</Issue>
+ </ExcludeList>
</ItemGroup>
<!-- Arm64 All OS -->
diff --git a/tests/src/JIT/Stress/ABI/ABIs.cs b/tests/src/JIT/Stress/ABI/ABIs.cs
new file mode 100644
index 0000000000..5309371dec
--- /dev/null
+++ b/tests/src/JIT/Stress/ABI/ABIs.cs
@@ -0,0 +1,191 @@
+// 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;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+using System.Text;
+
+namespace ABIStress
+{
+ internal interface IAbi
+ {
+ // Different ABI's have different conditions on when a method can be
+ // the target of a tailcall. For example, on Windows x64 any struct
+ // larger than 8 bytes will be passed by reference to a copy on the
+ // local stack frame, which inhibits tailcalling. This is the
+ // collection of types we can use in tail callee while still allowing
+ // fast tail calls.
+ Type[] TailCalleeCandidateArgTypes { get; }
+
+ // Multiple calling conventions are supported in pinvokes on x86. This
+ // is a collection of the supported ones.
+ CallingConvention[] PInvokeConventions { get; }
+
+ // Fast tailcalling is only possible when the caller has more arg stack
+ // space then the callee. This approximates the size of the incoming
+ // arg stack area for an ABI. It is only an approximation as we do not
+ // want to implement the full ABI rules. This is fine as we generate a
+ // lot of functions with a lot of parameters so in practice most of
+ // them successfully tailcall.
+ int ApproximateArgStackAreaSize(List<TypeEx> parameters);
+ }
+
+ internal static class Util
+ {
+ public static int RoundUp(int value, int alignment)
+ {
+ return (value + alignment - 1) / alignment * alignment;
+ }
+ }
+
+ internal class Win86Abi : IAbi
+ {
+ public Type[] TailCalleeCandidateArgTypes { get; } =
+ new[]
+ {
+ typeof(byte), typeof(short), typeof(int), typeof(long),
+ typeof(float), typeof(double),
+ typeof(Vector<int>), typeof(Vector128<int>), typeof(Vector256<int>),
+ typeof(S1P), typeof(S2P), typeof(S2U), typeof(S3U),
+ typeof(S4P), typeof(S4U), typeof(S5U), typeof(S6U),
+ typeof(S7U), typeof(S8P), typeof(S8U), typeof(S9U),
+ typeof(S10U), typeof(S11U), typeof(S12U), typeof(S13U),
+ typeof(S14U), typeof(S15U), typeof(S16U), typeof(S17U),
+ typeof(S31U), typeof(S32U),
+ };
+
+ public CallingConvention[] PInvokeConventions { get; } = { CallingConvention.Cdecl, CallingConvention.StdCall, };
+
+ public int ApproximateArgStackAreaSize(List<TypeEx> parameters)
+ {
+ int size = 0;
+ foreach (TypeEx pm in parameters)
+ size += Util.RoundUp(pm.Size, 4);
+
+ return size;
+ }
+ }
+
+ internal class Win64Abi : IAbi
+ {
+ // On Win x64, only 1, 2, 4, and 8-byte sized structs can be passed on
+ // the stack. Other structs will be passed by reference and will
+ // require helper.
+ public Type[] TailCalleeCandidateArgTypes { get; } =
+ new[]
+ {
+ typeof(byte), typeof(short), typeof(int), typeof(long),
+ typeof(float), typeof(double),
+ typeof(S1P), typeof(S2P), typeof(S2U), typeof(S4P),
+ typeof(S4U), typeof(S8P), typeof(S8U),
+ };
+
+ public CallingConvention[] PInvokeConventions { get; } = { CallingConvention.Cdecl };
+
+ public int ApproximateArgStackAreaSize(List<TypeEx> parameters)
+ {
+ // 1, 2, 4 and 8 byte structs are passed directly by value,
+ // everything else by ref. That means all args on windows are 8
+ // bytes.
+ int size = parameters.Count * 8;
+
+ // On win64 there's always at least 32 bytes of stack space allocated.
+ size = Math.Max(size, 32);
+ return size;
+ }
+ }
+
+ internal class SysVAbi : IAbi
+ {
+ // For SysV everything can be passed by value.
+ public Type[] TailCalleeCandidateArgTypes { get; } =
+ new[]
+ {
+ typeof(byte), typeof(short), typeof(int), typeof(long),
+ typeof(float), typeof(double),
+ // Vector128 is disabled for now due to
+ // https://github.com/dotnet/coreclr/issues/26022
+ typeof(Vector<int>), /*typeof(Vector128<int>),*/ typeof(Vector256<int>),
+ typeof(S1P), typeof(S2P), typeof(S2U), typeof(S3U),
+ typeof(S4P), typeof(S4U), typeof(S5U), typeof(S6U),
+ typeof(S7U), typeof(S8P), typeof(S8U), typeof(S9U),
+ typeof(S10U), typeof(S11U), typeof(S12U), typeof(S13U),
+ typeof(S14U), typeof(S15U), typeof(S16U), typeof(S17U),
+ typeof(S31U), typeof(S32U),
+ };
+
+ public CallingConvention[] PInvokeConventions { get; } = { CallingConvention.Cdecl };
+
+ public int ApproximateArgStackAreaSize(List<TypeEx> parameters)
+ {
+ int size = 0;
+ foreach (TypeEx pm in parameters)
+ size += Util.RoundUp(pm.Size, 8);
+
+ return size;
+ }
+ }
+
+ internal class Arm64Abi : IAbi
+ {
+ // For Arm64 structs larger than 16 bytes are passed by-ref and will
+ // inhibit tailcalls, so we exclude those.
+ public Type[] TailCalleeCandidateArgTypes { get; } =
+ new[]
+ {
+ typeof(byte), typeof(short), typeof(int), typeof(long),
+ typeof(float), typeof(double),
+ typeof(Vector<int>), typeof(Vector128<int>), typeof(Vector256<int>),
+ typeof(S1P), typeof(S2P), typeof(S2U), typeof(S3U),
+ typeof(S4P), typeof(S4U), typeof(S5U), typeof(S6U),
+ typeof(S7U), typeof(S8P), typeof(S8U), typeof(S9U),
+ typeof(S10U), typeof(S11U), typeof(S12U), typeof(S13U),
+ typeof(S14U), typeof(S15U), typeof(S16U),
+ typeof(Hfa1), typeof(Hfa2),
+ };
+
+ public CallingConvention[] PInvokeConventions { get; } = { CallingConvention.Cdecl };
+
+ public int ApproximateArgStackAreaSize(List<TypeEx> parameters)
+ {
+ int size = 0;
+ foreach (TypeEx pm in parameters)
+ size += Util.RoundUp(pm.Size, 8);
+
+ return size;
+ }
+ }
+
+ internal class Arm32Abi : IAbi
+ {
+ // For arm32 everything can be passed by value
+ public Type[] TailCalleeCandidateArgTypes { get; } =
+ new[]
+ {
+ typeof(byte), typeof(short), typeof(int), typeof(long),
+ typeof(float), typeof(double),
+ typeof(S1P), typeof(S2P), typeof(S2U), typeof(S3U),
+ typeof(S4P), typeof(S4U), typeof(S5U), typeof(S6U),
+ typeof(S7U), typeof(S8P), typeof(S8U), typeof(S9U),
+ typeof(S10U), typeof(S11U), typeof(S12U), typeof(S13U),
+ typeof(S14U), typeof(S15U), typeof(S16U), typeof(S17U),
+ typeof(S31U), typeof(S32U),
+ typeof(Hfa1), typeof(Hfa2),
+ };
+
+ public CallingConvention[] PInvokeConventions { get; } = { CallingConvention.Cdecl };
+
+ public int ApproximateArgStackAreaSize(List<TypeEx> parameters)
+ {
+ int size = 0;
+ foreach (TypeEx pm in parameters)
+ size += Util.RoundUp(pm.Size, 4);
+
+ return size;
+ }
+ }
+}
diff --git a/tests/src/JIT/Stress/ABI/Callee.cs b/tests/src/JIT/Stress/ABI/Callee.cs
new file mode 100644
index 0000000000..d7f753cbf2
--- /dev/null
+++ b/tests/src/JIT/Stress/ABI/Callee.cs
@@ -0,0 +1,108 @@
+// 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;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Runtime.InteropServices;
+
+namespace ABIStress
+{
+ class Callee
+ {
+ private static readonly MethodInfo s_hashCodeAddMethod =
+ typeof(HashCode).GetMethods().Single(mi => mi.Name == "Add" && mi.GetParameters().Length == 1);
+ private static readonly MethodInfo s_hashCodeToHashCodeMethod =
+ typeof(HashCode).GetMethod("ToHashCode");
+
+ public Callee(string name, List<TypeEx> parameters)
+ {
+ Name = name;
+ Parameters = parameters;
+ ArgStackSizeApprox = Program.Abi.ApproximateArgStackAreaSize(Parameters);
+ }
+
+ public string Name { get; }
+ public List<TypeEx> Parameters { get; }
+ public int ArgStackSizeApprox { get; }
+ public DynamicMethod Method { get; private set; }
+ public Dictionary<CallingConvention, Type> PInvokeDelegateTypes { get; private set; }
+
+ public void Emit()
+ {
+ if (Method != null)
+ return;
+
+ Method = new DynamicMethod(
+ Name, typeof(int), Parameters.Select(t => t.Type).ToArray(), typeof(Program));
+
+ ILGenerator g = Method.GetILGenerator();
+ LocalBuilder hashCode = g.DeclareLocal(typeof(HashCode));
+
+ if (Config.Verbose)
+ Program.EmitDumpValues("Callee's incoming args", g, Parameters.Select((t, i) => new ArgValue(t, i)));
+
+ g.Emit(OpCodes.Ldloca, hashCode);
+ g.Emit(OpCodes.Initobj, typeof(HashCode));
+
+ for (int i = 0; i < Parameters.Count; i++)
+ {
+ TypeEx pm = Parameters[i];
+ g.Emit(OpCodes.Ldloca, hashCode);
+ g.Emit(OpCodes.Ldarg, checked((short)i));
+ g.Emit(OpCodes.Call, s_hashCodeAddMethod.MakeGenericMethod(pm.Type));
+ }
+
+ g.Emit(OpCodes.Ldloca, hashCode);
+ g.Emit(OpCodes.Call, s_hashCodeToHashCodeMethod);
+ g.Emit(OpCodes.Ret);
+ }
+
+ private static ModuleBuilder s_delegateTypesModule;
+ private static ConstructorInfo s_unmanagedFunctionPointerCtor =
+ typeof(UnmanagedFunctionPointerAttribute).GetConstructor(new[] { typeof(CallingConvention) });
+
+ public void EmitPInvokeDelegateTypes()
+ {
+ if (PInvokeDelegateTypes != null)
+ return;
+
+ PInvokeDelegateTypes = new Dictionary<CallingConvention, Type>();
+
+ if (s_delegateTypesModule == null)
+ {
+ AssemblyBuilder delegates = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("ABIStress_Delegates"), AssemblyBuilderAccess.RunAndCollect);
+ s_delegateTypesModule = delegates.DefineDynamicModule("ABIStress_Delegates");
+ }
+
+ foreach (CallingConvention cc in Program.Abi.PInvokeConventions)
+ {
+ // This code is based on DelegateHelpers.cs in System.Linq.Expressions.Compiler
+ TypeBuilder tb =
+ s_delegateTypesModule.DefineType(
+ $"{Name}_Delegate_{cc}",
+ TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.AutoClass,
+ typeof(MulticastDelegate));
+
+ tb.DefineConstructor(
+ MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.RTSpecialName,
+ CallingConventions.Standard,
+ new[] { typeof(object), typeof(IntPtr) })
+ .SetImplementationFlags(MethodImplAttributes.Runtime | MethodImplAttributes.Managed);
+
+ tb.DefineMethod(
+ "Invoke",
+ MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual,
+ typeof(int),
+ Parameters.Select(t => t.Type).ToArray())
+ .SetImplementationFlags(MethodImplAttributes.Runtime | MethodImplAttributes.Managed);
+
+ tb.SetCustomAttribute(new CustomAttributeBuilder(s_unmanagedFunctionPointerCtor, new object[] { cc }));
+ PInvokeDelegateTypes.Add(cc, tb.CreateType());
+ }
+ }
+ }
+}
diff --git a/tests/src/JIT/Stress/ABI/Config.cs b/tests/src/JIT/Stress/ABI/Config.cs
new file mode 100644
index 0000000000..18da545afb
--- /dev/null
+++ b/tests/src/JIT/Stress/ABI/Config.cs
@@ -0,0 +1,47 @@
+// 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 ABIStress
+{
+ internal class Config
+ {
+ internal const string TailCallerPrefix = "ABIStress_TailCaller";
+ internal const string TailCalleePrefix = "ABIStress_TailCallee";
+ internal const string PInvokerPrefix = "ABIStress_PInvoker";
+ internal const string PInvokeePrefix = "ABIStress_PInvokee";
+
+ internal const string InstantiatingStubPrefix = "ABIStress_InstantiatingStub_";
+
+ internal static StressModes StressModes { get; set; } = StressModes.None;
+ // The base seed. This value combined with the index of the
+ // caller/pinvoker/callee will uniquely determine how it is generated
+ // and which callee is used.
+ internal const int Seed = 0xeadbeef;
+ internal const int MinParams = 1;
+ internal static int MaxParams { get; set; } = 25;
+ // The number of callees to use. When stressing tailcalls, this is the number of tailcallee parameter lists to pregenerate.
+ // These parameter lists are pregenerated because we generate tailcallers
+ // by first selecting a random parameter list. A callee is then
+ // selected; to ensure we can actually do a fast tail call, we try to
+ // select a callee which requires less incoming arg space.
+ // For pinvokes this is the number of callees to use.
+ internal const int NumCallees = 10000;
+ internal static bool Verbose { get; set; }
+ }
+
+ [Flags]
+ internal enum StressModes
+ {
+ None = 0,
+ TailCalls = 0x1,
+ PInvokes = 0x2,
+ InstantiatingStubs = 0x4,
+ UnboxingStubs = 0x8,
+ SharedGenericUnboxingStubs = 0x10,
+
+ All = TailCalls | PInvokes | InstantiatingStubs | UnboxingStubs | SharedGenericUnboxingStubs,
+ }
+}
diff --git a/tests/src/JIT/Stress/ABI/Gen.cs b/tests/src/JIT/Stress/ABI/Gen.cs
new file mode 100644
index 0000000000..3cccb1a0f4
--- /dev/null
+++ b/tests/src/JIT/Stress/ABI/Gen.cs
@@ -0,0 +1,187 @@
+// 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;
+using System.Diagnostics;
+using System.Linq;
+using System.Numerics;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Runtime.CompilerServices;
+using System.Runtime.Intrinsics;
+
+namespace ABIStress
+{
+ // This class allows us to generate random values of specified types.
+ internal static class Gen
+ {
+ private static unsafe TVec GenConstantVector<TVec, TElem>(Random rand)
+ where TVec : unmanaged where TElem : unmanaged
+ {
+ int outerSize = sizeof(TVec);
+ int innerSize = sizeof(TElem);
+ Debug.Assert(outerSize % innerSize == 0);
+ Span<TElem> elements = stackalloc TElem[outerSize / innerSize];
+ for (int i = 0; i < elements.Length; i++)
+ elements[i] = (TElem)GenConstant(typeof(TElem), null, rand);
+
+ return Unsafe.ReadUnaligned<TVec>(ref Unsafe.As<TElem, byte>(ref elements[0]));
+ }
+
+ internal static object GenConstant(Type type, FieldInfo[] fields, Random rand)
+ {
+ if (type == typeof(byte))
+ return (byte)rand.Next(byte.MinValue, byte.MaxValue + 1);
+
+ if (type == typeof(short))
+ return (short)rand.Next(short.MinValue, short.MaxValue + 1);
+
+ if (type == typeof(int))
+ return (int)rand.Next();
+
+ if (type == typeof(long))
+ return ((long)rand.Next() << 32) | (uint)rand.Next();
+
+ if (type == typeof(float))
+ return (float)rand.Next(short.MaxValue);
+
+ if (type == typeof(double))
+ return (double)rand.Next();
+
+ if (type == typeof(Vector<int>))
+ return GenConstantVector<Vector<int>, int>(rand);
+
+ if (type == typeof(Vector128<int>))
+ return GenConstantVector<Vector128<int>, int>(rand);
+
+ if (type == typeof(Vector256<int>))
+ return GenConstantVector<Vector256<int>, int>(rand);
+
+ Debug.Assert(fields != null);
+ return Activator.CreateInstance(type, fields.Select(fi => GenConstant(fi.FieldType, null, rand)).ToArray());
+ }
+ }
+
+ // Values are expressions of a specified type. We allow these values access
+ // to incoming arguments and require both that we can compute them and also
+ // that we can emit IL code that loads them on the top of the stack.
+ internal abstract class Value
+ {
+ public Value(TypeEx type)
+ {
+ Type = type;
+ }
+
+ public TypeEx Type { get; }
+
+ public abstract object Get(object[] args);
+ public abstract void Emit(ILGenerator il);
+ }
+
+ internal class ArgValue : Value
+ {
+ public ArgValue(TypeEx type, int index) : base(type)
+ {
+ Index = index;
+ }
+
+ public int Index { get; }
+
+ public override object Get(object[] args) => args[Index];
+ public override void Emit(ILGenerator il)
+ {
+ il.Emit(OpCodes.Ldarg, checked((short)Index));
+ }
+ }
+
+ internal class FieldValue : Value
+ {
+ public FieldValue(Value val, int fieldIndex) : base(new TypeEx(val.Type.Fields[fieldIndex].FieldType))
+ {
+ Value = val;
+ FieldIndex = fieldIndex;
+ }
+
+ public Value Value { get; }
+ public int FieldIndex { get; }
+
+ public override object Get(object[] args)
+ {
+ object value = Value.Get(args);
+ value = Value.Type.Fields[FieldIndex].GetValue(value);
+ return value;
+ }
+
+ public override void Emit(ILGenerator il)
+ {
+ Value.Emit(il);
+ il.Emit(OpCodes.Ldfld, Value.Type.Fields[FieldIndex]);
+ }
+ }
+
+ internal class ConstantValue : Value
+ {
+ public ConstantValue(TypeEx type, object value) : base(type)
+ {
+ Value = value;
+ }
+
+ public object Value { get; }
+
+ public override object Get(object[] args) => Value;
+ public override void Emit(ILGenerator il)
+ {
+ if (Type.Fields == null)
+ {
+ EmitLoadPrimitive(il, Value);
+ return;
+ }
+
+ foreach (FieldInfo field in Type.Fields)
+ EmitLoadPrimitive(il, field.GetValue(Value));
+
+ il.Emit(OpCodes.Newobj, Type.Ctor);
+ }
+
+ private static unsafe void EmitLoadBlittable<T>(ILGenerator il, T val) where T : unmanaged
+ {
+ LocalBuilder local = il.DeclareLocal(typeof(T));
+ for (int i = 0; i < sizeof(T); i++)
+ {
+ il.Emit(OpCodes.Ldloca, local);
+ il.Emit(OpCodes.Ldc_I4, i);
+ il.Emit(OpCodes.Add);
+ il.Emit(OpCodes.Ldc_I4, (int)Unsafe.Add(ref Unsafe.As<T, byte>(ref val), i));
+ il.Emit(OpCodes.Stind_I1);
+ }
+
+ il.Emit(OpCodes.Ldloc, local);
+ }
+
+ internal static void EmitLoadPrimitive(ILGenerator il, object val)
+ {
+ Type ty = val.GetType();
+ if (ty == typeof(byte))
+ il.Emit(OpCodes.Ldc_I4, (int)(byte)val);
+ else if (ty == typeof(short))
+ il.Emit(OpCodes.Ldc_I4, (int)(short)val);
+ else if (ty == typeof(int))
+ il.Emit(OpCodes.Ldc_I4, (int)val);
+ else if (ty == typeof(long))
+ il.Emit(OpCodes.Ldc_I8, (long)val);
+ else if (ty == typeof(float))
+ il.Emit(OpCodes.Ldc_R4, (float)val);
+ else if (ty == typeof(double))
+ il.Emit(OpCodes.Ldc_R8, (double)val);
+ else if (ty == typeof(Vector<int>))
+ EmitLoadBlittable(il, (Vector<int>)val);
+ else if (ty == typeof(Vector128<int>))
+ EmitLoadBlittable(il, (Vector128<int>)val);
+ else if (ty == typeof(Vector256<int>))
+ EmitLoadBlittable(il, (Vector256<int>)val);
+ else
+ throw new NotSupportedException("Other primitives are currently not supported");
+ }
+ }
+}
diff --git a/tests/src/JIT/Stress/ABI/PInvokes.cs b/tests/src/JIT/Stress/ABI/PInvokes.cs
new file mode 100644
index 0000000000..0198dbf82d
--- /dev/null
+++ b/tests/src/JIT/Stress/ABI/PInvokes.cs
@@ -0,0 +1,113 @@
+// 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;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection.Emit;
+using System.Runtime.InteropServices;
+
+namespace ABIStress
+{
+ internal partial class Program
+ {
+ private static readonly List<DynamicMethod> s_keepRooted = new List<DynamicMethod>();
+ private static readonly Dictionary<int, Callee> s_pinvokees = new Dictionary<int, Callee>();
+ private static bool DoPInvokes(int callerIndex)
+ {
+ string callerName = Config.PInvokerPrefix + callerIndex;
+ Random rand = new Random(GetSeed(callerName));
+ List<TypeEx> pms = RandomParameters(s_allTypes, rand);
+
+ int calleeIndex = rand.Next(0, Config.NumCallees);
+ Callee callee;
+ if (!s_pinvokees.TryGetValue(calleeIndex, out callee))
+ {
+ callee = CreateCallee(Config.PInvokeePrefix + calleeIndex, s_pinvokeeCandidateArgTypes);
+ callee.Emit();
+ callee.EmitPInvokeDelegateTypes();
+ s_pinvokees.Add(calleeIndex, callee);
+ }
+
+ DynamicMethod caller = new DynamicMethod(
+ callerName, typeof(int[]), pms.Select(t => t.Type).ToArray(), typeof(Program).Module);
+
+ // We need to keep callers rooted due to a stale cache bug in the runtime related to calli.
+ s_keepRooted.Add(caller);
+
+ ILGenerator g = caller.GetILGenerator();
+
+ // Create the args to pass to the callee from the caller.
+ List<Value> args = GenCallerToCalleeArgs(pms, callee.Parameters, rand);
+
+ if (Config.Verbose)
+ EmitDumpValues("Caller's incoming args", g, pms.Select((p, i) => new ArgValue(p, i)));
+
+ // Create array to store results in
+ LocalBuilder resultsArrLocal = g.DeclareLocal(typeof(int[]));
+ g.Emit(OpCodes.Ldc_I4, callee.PInvokeDelegateTypes.Count);
+ g.Emit(OpCodes.Newarr, typeof(int));
+ g.Emit(OpCodes.Stloc, resultsArrLocal);
+
+ // Emit pinvoke calls for each calling convention. Keep delegates rooted in a list.
+ LocalBuilder resultLocal = g.DeclareLocal(typeof(int));
+ List<Delegate> delegates = new List<Delegate>();
+ int resultIndex = 0;
+ foreach (var (cc, delegateType) in callee.PInvokeDelegateTypes)
+ {
+ Delegate dlg = callee.Method.CreateDelegate(delegateType);
+ delegates.Add(dlg);
+
+ if (Config.Verbose)
+ EmitDumpValues($"Caller's args to {cc} calli", g, args);
+
+ foreach (Value v in args)
+ v.Emit(g);
+
+ IntPtr ptr = Marshal.GetFunctionPointerForDelegate(dlg);
+ g.Emit(OpCodes.Ldc_I8, (long)ptr);
+ g.Emit(OpCodes.Conv_I);
+ g.EmitCalli(OpCodes.Calli, cc, typeof(int), callee.Parameters.Select(p => p.Type).ToArray());
+ g.Emit(OpCodes.Stloc, resultLocal);
+
+ g.Emit(OpCodes.Ldloc, resultsArrLocal);
+ g.Emit(OpCodes.Ldc_I4, resultIndex); // where to store result
+ g.Emit(OpCodes.Ldloc, resultLocal); // result
+ g.Emit(OpCodes.Stelem_I4);
+ resultIndex++;
+ }
+
+ g.Emit(OpCodes.Ldloc, resultsArrLocal);
+ g.Emit(OpCodes.Ret);
+
+ (object callerResult, object calleeResult) =
+ InvokeCallerCallee(caller, pms, callee.Method, args, rand);
+
+ // The pointers used in the calli instructions are only valid while the delegates are alive,
+ // so keep these alive until we're done executing.
+ GC.KeepAlive(delegates);
+
+ int[] results = (int[])callerResult;
+
+ bool allCorrect = true;
+ for (int i = 0; i < results.Length; i++)
+ {
+ if (results[i] == (int)calleeResult)
+ continue;
+
+ allCorrect = false;
+ string callType = callee.PInvokeDelegateTypes.ElementAt(i).Key.ToString();
+ Console.WriteLine("Mismatch in {0}: expected {1}, got {2}", callType, calleeResult, results[i]);
+ }
+
+ if (!allCorrect)
+ {
+ WriteSignature(caller);
+ WriteSignature(callee.Method);
+ }
+
+ return allCorrect;
+ }
+ }
+}
diff --git a/tests/src/JIT/Stress/ABI/Program.cs b/tests/src/JIT/Stress/ABI/Program.cs
new file mode 100644
index 0000000000..51792a2afd
--- /dev/null
+++ b/tests/src/JIT/Stress/ABI/Program.cs
@@ -0,0 +1,442 @@
+// 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;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Numerics;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.ComTypes;
+using System.Runtime.Intrinsics;
+using System.Text;
+
+namespace ABIStress
+{
+ internal partial class Program
+ {
+ private static int Main(string[] args)
+ {
+ static void Usage()
+ {
+ Console.WriteLine("Usage: [--verbose] [--caller-index <number>] [--num-calls <number>] [--tailcalls] [--pinvokes] [--max-params <number>] [--no-ctrlc-summary]");
+ Console.WriteLine("Usage: [--verbose] [--caller-index <number>] [--num-calls <number>] [--tailcalls] [--pinvokes] [--instantiatingstubs] [--unboxingstubs] [--sharedgenericunboxingstubs] [--max-params <number>] [--no-ctrlc-summary]");
+ Console.WriteLine("Either --caller-index or --num-calls must be specified.");
+ Console.WriteLine("Example: --num-calls 100");
+ Console.WriteLine(" Stress first 100 tailcalls and pinvokes");
+ Console.WriteLine("Example: --tailcalls --caller-index 37 --verbose");
+ Console.WriteLine(" Stress tailcaller 37, verbose output");
+ Console.WriteLine("Example: --pinvokes --num-calls 1000");
+ Console.WriteLine(" Stress first 1000 pinvokes");
+ Console.WriteLine("Example: --tailcalls --num-calls 100 --max-params 2");
+ Console.WriteLine(" Stress 100 tailcalls with either 1 or 2 parameters");
+ }
+
+ if (args.Contains("-help") || args.Contains("--help") || args.Contains("-h"))
+ {
+ Usage();
+ return 1;
+ }
+
+ Config.Verbose = args.Contains("--verbose");
+
+ if (args.Contains("--tailcalls"))
+ Config.StressModes |= StressModes.TailCalls;
+ if (args.Contains("--pinvokes"))
+ Config.StressModes |= StressModes.PInvokes;
+ if (args.Contains("--instantiatingstubs"))
+ Config.StressModes |= StressModes.InstantiatingStubs;
+ if (args.Contains("--unboxingstubs"))
+ Config.StressModes |= StressModes.UnboxingStubs;
+ if (args.Contains("--sharedgenericunboxingstubs"))
+ Config.StressModes |= StressModes.SharedGenericUnboxingStubs;
+
+ if (Config.StressModes == StressModes.None)
+ Config.StressModes = StressModes.All;
+
+ int callerIndex = -1;
+ int numCalls = -1;
+ int argIndex;
+ if ((argIndex = Array.IndexOf(args, "--caller-index")) != -1)
+ callerIndex = int.Parse(args[argIndex + 1]);
+ if ((argIndex = Array.IndexOf(args, "--num-calls")) != -1)
+ numCalls = int.Parse(args[argIndex + 1]);
+ if ((argIndex = Array.IndexOf(args, "--max-params")) != -1)
+ Config.MaxParams = int.Parse(args[argIndex + 1]);
+
+ if ((callerIndex == -1) == (numCalls == -1))
+ {
+ Usage();
+ return 1;
+ }
+
+ bool ctrlCSummary = !args.Contains("--no-ctrlc-summary");
+
+ if (Config.StressModes.HasFlag(StressModes.TailCalls))
+ Console.WriteLine("Stressing tailcalls");
+ if (Config.StressModes.HasFlag(StressModes.PInvokes))
+ Console.WriteLine("Stressing pinvokes");
+ if (Config.StressModes.HasFlag(StressModes.InstantiatingStubs))
+ Console.WriteLine("Stressing instantiatingstubs");
+ if (Config.StressModes.HasFlag(StressModes.UnboxingStubs))
+ Console.WriteLine("Stressing unboxingstubs");
+ if (Config.StressModes.HasFlag(StressModes.SharedGenericUnboxingStubs))
+ Console.WriteLine("Stressing sharedgenericunboxingstubs");
+
+ using var tcel = new TailCallEventListener();
+
+ int mismatches = 0;
+ if (callerIndex != -1)
+ {
+ if (!DoCall(callerIndex))
+ mismatches++;
+ }
+ else
+ {
+ bool abortLoop = false;
+ if (ctrlCSummary)
+ {
+ Console.CancelKeyPress += (sender, args) =>
+ {
+ args.Cancel = true;
+ abortLoop = true;
+ };
+ }
+
+ for (int i = 0; i < numCalls && !abortLoop; i++)
+ {
+ if (!DoCall(i))
+ mismatches++;
+
+ if ((i + 1) % 50 == 0)
+ {
+ Console.Write("{0} callers done", i + 1);
+ if (Config.StressModes.HasFlag(StressModes.TailCalls))
+ Console.Write($" ({tcel.NumSuccessfulTailCalls} successful tailcalls tested)");
+ Console.WriteLine();
+ }
+ }
+ }
+
+ if (Config.StressModes.HasFlag(StressModes.TailCalls))
+ {
+ Console.WriteLine("{0} tailcalls tested", tcel.NumSuccessfulTailCalls);
+ lock (tcel.FailureReasons)
+ {
+ if (tcel.FailureReasons.Count != 0)
+ {
+ int numRejected = tcel.FailureReasons.Values.Sum();
+ Console.WriteLine("{0} rejected tailcalls. Breakdown:", numRejected);
+ foreach (var (reason, count) in tcel.FailureReasons.OrderByDescending(kvp => kvp.Value))
+ Console.WriteLine("[{0:00.00}%]: {1}", count / (double)numRejected * 100, reason);
+ }
+ }
+ }
+
+ Console.WriteLine($" Done with {mismatches} mismatches");
+ return 100 + mismatches;
+ }
+
+ private static bool DoCall(int index)
+ {
+ bool result = true;
+ if (Config.StressModes.HasFlag(StressModes.TailCalls))
+ result &= DoTailCall(index);
+ if (Config.StressModes.HasFlag(StressModes.PInvokes))
+ result &= DoPInvokes(index);
+ if (Config.StressModes.HasFlag(StressModes.UnboxingStubs))
+ {
+ result &= DoStubCall(index, staticMethod: false, onValueType: true, typeGenericShape: GenericShape.NotGeneric, methodGenericShape: GenericShape.NotGeneric);
+ result &= DoStubCall(index, staticMethod: false, onValueType: true, typeGenericShape: GenericShape.NotGeneric, methodGenericShape: GenericShape.GenericOverReferenceType);
+ }
+ if (Config.StressModes.HasFlag(StressModes.SharedGenericUnboxingStubs))
+ {
+ result &= DoStubCall(index, staticMethod: false, onValueType: true, typeGenericShape: GenericShape.GenericOverReferenceType, methodGenericShape: GenericShape.NotGeneric);
+ }
+ if (Config.StressModes.HasFlag(StressModes.InstantiatingStubs))
+ {
+ result &= DoStubCall(index, staticMethod: false, onValueType: false, typeGenericShape: GenericShape.NotGeneric, methodGenericShape: GenericShape.GenericOverReferenceType);
+ result &= DoStubCall(index, staticMethod: true, onValueType: false, typeGenericShape: GenericShape.GenericOverReferenceType, methodGenericShape: GenericShape.NotGeneric);
+ result &= DoStubCall(index, staticMethod: true, onValueType: true, typeGenericShape: GenericShape.GenericOverReferenceType, methodGenericShape: GenericShape.NotGeneric);
+ result &= DoStubCall(index, staticMethod: true, onValueType: false, typeGenericShape: GenericShape.GenericOverValueType, methodGenericShape: GenericShape.GenericOverReferenceType);
+ result &= DoStubCall(index, staticMethod: true, onValueType: false, typeGenericShape: GenericShape.GenericOverReferenceType, methodGenericShape: GenericShape.GenericOverValueType);
+ }
+ return result;
+ }
+
+ private static Callee CreateCallee(string name, TypeEx[] candidateParamTypes)
+ {
+ Random rand = new Random(GetSeed(name));
+ List<TypeEx> pms = RandomParameters(candidateParamTypes, rand);
+ var tc = new Callee(name, pms);
+ return tc;
+ }
+
+ private static int GetSeed(string name)
+ => Fnv1a(BitConverter.GetBytes(Config.Seed).Concat(Encoding.UTF8.GetBytes(name)).ToArray());
+
+ private static int Fnv1a(byte[] data)
+ {
+ uint hash = 2166136261;
+ foreach (byte b in data)
+ {
+ hash ^= b;
+ hash *= 16777619;
+ }
+
+ return (int)hash;
+ }
+
+ private static List<TypeEx> RandomParameters(TypeEx[] candidateParamTypes, Random rand)
+ {
+ List<TypeEx> pms = new List<TypeEx>(rand.Next(Config.MinParams, Config.MaxParams + 1));
+ for (int j = 0; j < pms.Capacity; j++)
+ pms.Add(candidateParamTypes[rand.Next(candidateParamTypes.Length)]);
+
+ return pms;
+ }
+
+ private static List<Value> GenCallerToCalleeArgs(List<TypeEx> callerParameters, List<TypeEx> calleeParameters, Random rand)
+ {
+ List<Value> args = new List<Value>(calleeParameters.Count);
+ List<Value> candidates = new List<Value>();
+ for (int j = 0; j < args.Capacity; j++)
+ {
+ TypeEx targetTy = calleeParameters[j];
+ // Collect candidate args. For each parameter to the caller we might be able to just
+ // forward it or one of its fields.
+ candidates.Clear();
+ CollectCandidateArgs(targetTy.Type, callerParameters, candidates);
+
+ if (candidates.Count > 0)
+ {
+ args.Add(candidates[rand.Next(candidates.Count)]);
+ }
+ else
+ {
+ // No candidates to forward, so just create a new value here dynamically.
+ args.Add(new ConstantValue(targetTy, Gen.GenConstant(targetTy.Type, targetTy.Fields, rand)));
+ }
+ }
+
+ return args;
+ }
+
+ private static void CollectCandidateArgs(Type targetTy, List<TypeEx> pms, List<Value> candidates)
+ {
+ for (int i = 0; i < pms.Count; i++)
+ {
+ TypeEx pm = pms[i];
+ Value arg = null;
+ if (pm.Type == targetTy)
+ candidates.Add(arg = new ArgValue(pm, i));
+
+ if (pm.Fields == null)
+ continue;
+
+ for (int j = 0; j < pm.Fields.Length; j++)
+ {
+ FieldInfo fi = pm.Fields[j];
+ if (fi.FieldType != targetTy)
+ continue;
+
+ arg ??= new ArgValue(pm, i);
+ candidates.Add(new FieldValue(arg, j));
+ }
+ }
+ }
+
+ private static (object callerResult, object calleeResult) InvokeCallerCallee(
+ DynamicMethod caller, List<TypeEx> callerParameters,
+ DynamicMethod callee, List<Value> passedArgs,
+ Random rand)
+ {
+ object[] outerArgs = callerParameters.Select(p => Gen.GenConstant(p.Type, p.Fields, rand)).ToArray();
+ object[] innerArgs = passedArgs.Select(v => v.Get(outerArgs)).ToArray();
+
+ if (Config.Verbose)
+ {
+ WriteSignature(caller);
+ WriteSignature(callee);
+
+ Console.WriteLine("Invoking caller through reflection with args");
+ for (int j = 0; j < outerArgs.Length; j++)
+ {
+ Console.Write($"arg{j}=");
+ DumpObject(outerArgs[j]);
+ }
+ }
+
+ //object callerResult = caller.Invoke(null, outerArgs);
+ object callerResult = InvokeMethodDynamicallyButWithoutReflection(caller, outerArgs);
+
+ if (Config.Verbose)
+ {
+ Console.WriteLine("Invoking callee through reflection with args");
+ for (int j = 0; j < innerArgs.Length; j++)
+ {
+ Console.Write($"arg{j}=");
+ DumpObject(innerArgs[j]);
+ }
+ }
+ //object calleeResult = callee.Method.Invoke(null, innerArgs);
+ object calleeResult = InvokeMethodDynamicallyButWithoutReflection(callee, innerArgs);
+
+ return (callerResult, calleeResult);
+ }
+
+ // This function works around a reflection bug on ARM64:
+ // https://github.com/dotnet/coreclr/issues/25993
+ private static object InvokeMethodDynamicallyButWithoutReflection(MethodInfo mi, object[] args)
+ {
+ DynamicMethod dynCaller = new DynamicMethod(
+ $"DynCaller", typeof(object), new Type[0], typeof(Program).Module);
+
+ ILGenerator g = dynCaller.GetILGenerator();
+ foreach (var arg in args)
+ new ConstantValue(new TypeEx(arg.GetType()), arg).Emit(g);
+
+ g.Emit(OpCodes.Call, mi);
+ if (mi.ReturnType.IsValueType)
+ g.Emit(OpCodes.Box, mi.ReturnType);
+ g.Emit(OpCodes.Ret);
+
+ Func<object> f = (Func<object>)dynCaller.CreateDelegate(typeof(Func<object>));
+ return f();
+ }
+
+ private static void WriteSignature(MethodInfo mi)
+ {
+ string ns = typeof(S1P).Namespace;
+ // The normal output will include a bunch of namespaces before types which just clutter things.
+ Console.WriteLine(mi.ToString().Replace(ns + ".", ""));
+ }
+
+ private static readonly MethodInfo s_writeString = typeof(Console).GetMethod("Write", new[] { typeof(string) });
+ private static readonly MethodInfo s_writeLineString = typeof(Console).GetMethod("WriteLine", new[] { typeof(string) });
+ private static readonly MethodInfo s_dumpValue = typeof(Program).GetMethod("DumpValue", BindingFlags.NonPublic | BindingFlags.Static);
+
+ private static void DumpObject(object o)
+ {
+ TypeEx ty = new TypeEx(o.GetType());
+ if (ty.Fields != null)
+ {
+ Console.WriteLine();
+ foreach (FieldInfo field in ty.Fields)
+ Console.WriteLine(" {0}={1}", field.Name, field.GetValue(o));
+
+ return;
+ }
+
+ Console.WriteLine(o);
+ }
+
+ private static void DumpValue<T>(T value)
+ {
+ DumpObject(value);
+ }
+
+ // Dumps the value on the top of the stack, consuming the value.
+ private static void EmitDumpValue(ILGenerator g, Type ty)
+ {
+ MethodInfo instantiated = s_dumpValue.MakeGenericMethod(ty);
+ g.Emit(OpCodes.Call, instantiated);
+ }
+
+ public static void EmitDumpValues(string listName, ILGenerator g, IEnumerable<Value> values)
+ {
+ g.Emit(OpCodes.Ldstr, $"{listName}:");
+ g.Emit(OpCodes.Call, s_writeLineString);
+ int index = 0;
+ foreach (Value v in values)
+ {
+ g.Emit(OpCodes.Ldstr, $"arg{index}=");
+ g.Emit(OpCodes.Call, s_writeString);
+
+ v.Emit(g);
+ EmitDumpValue(g, v.Type.Type);
+ index++;
+ }
+ }
+
+ private static readonly TypeEx[] s_allTypes =
+ new[]
+ {
+ typeof(byte), typeof(short), typeof(int), typeof(long),
+ typeof(float), typeof(double),
+ typeof(Vector<int>), typeof(Vector128<int>), typeof(Vector256<int>),
+ typeof(S1P), typeof(S2P), typeof(S2U), typeof(S3U),
+ typeof(S4P), typeof(S4U), typeof(S5U), typeof(S6U),
+ typeof(S7U), typeof(S8P), typeof(S8U), typeof(S9U),
+ typeof(S10U), typeof(S11U), typeof(S12U), typeof(S13U),
+ typeof(S14U), typeof(S15U), typeof(S16U), typeof(S17U),
+ typeof(S31U), typeof(S32U),
+ typeof(Hfa1), typeof(Hfa2),
+ }.Select(t => new TypeEx(t)).ToArray();
+
+ private static readonly IAbi s_abi = SelectAbi();
+
+ public static IAbi Abi => s_abi;
+
+ private static readonly TypeEx[] s_tailCalleeCandidateArgTypes =
+ s_abi.TailCalleeCandidateArgTypes.Select(t => new TypeEx(t)).ToArray();
+
+ // We cannot marshal generic types so we cannot just use all types for pinvokees.
+ // This can be relaxed once https://github.com/dotnet/coreclr/pull/23899 is merged.
+ private static readonly TypeEx[] s_pinvokeeCandidateArgTypes =
+ new[]
+ {
+ typeof(byte), typeof(short), typeof(int), typeof(long),
+ typeof(float), typeof(double),
+ typeof(S1P), typeof(S2P), typeof(S2U), typeof(S3U),
+ typeof(S4P), typeof(S4U), typeof(S5U), typeof(S6U),
+ typeof(S7U), typeof(S8P), typeof(S8U), typeof(S9U),
+ typeof(S10U), typeof(S11U), typeof(S12U), typeof(S13U),
+ typeof(S14U), typeof(S15U), typeof(S16U), typeof(S17U),
+ typeof(S31U), typeof(S32U),
+ typeof(Hfa1), typeof(Hfa2),
+ }.Select(t => new TypeEx(t)).ToArray();
+
+ private static IAbi SelectAbi()
+ {
+ Console.WriteLine("OSVersion: {0}", Environment.OSVersion);
+ Console.WriteLine("OSArchitecture: {0}", RuntimeInformation.OSArchitecture);
+ Console.WriteLine("ProcessArchitecture: {0}", RuntimeInformation.ProcessArchitecture);
+
+ if (Environment.OSVersion.Platform == PlatformID.Win32NT)
+ {
+ if (IntPtr.Size == 8)
+ {
+ Console.WriteLine("Selecting win64 ABI");
+ return new Win64Abi();
+ }
+
+ Console.WriteLine("Selecting win86 ABI");
+ return new Win86Abi();
+ }
+
+ if (Environment.OSVersion.Platform == PlatformID.Unix)
+ {
+ if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
+ {
+ Console.WriteLine("Selecting arm64 ABI.");
+ return new Arm64Abi();
+ }
+ if (RuntimeInformation.ProcessArchitecture == Architecture.Arm)
+ {
+ Console.WriteLine("Selecting armhf ABI.");
+ return new Arm32Abi();
+ }
+
+ Trace.Assert(RuntimeInformation.ProcessArchitecture == Architecture.X64);
+ Console.WriteLine("Selecting SysV ABI");
+ return new SysVAbi();
+ }
+
+ throw new NotSupportedException($"Platform {Environment.OSVersion.Platform} is not supported");
+ }
+ }
+}
diff --git a/tests/src/JIT/Stress/ABI/Stubs.cs b/tests/src/JIT/Stress/ABI/Stubs.cs
new file mode 100644
index 0000000000..11dbcc6edc
--- /dev/null
+++ b/tests/src/JIT/Stress/ABI/Stubs.cs
@@ -0,0 +1,335 @@
+// 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;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace ABIStress
+{
+ public class StubsTestHelpers
+ {
+ public static void CompareNumbers(int actual, int expected)
+ {
+ if (actual != expected)
+ throw new Exception($"Magic number {actual} didn't match expected{expected}");
+ }
+
+ public static void IsTypeHandleObject(RuntimeTypeHandle rth, string scenario)
+ {
+ if (Type.GetTypeFromHandle(rth) != typeof(object))
+ {
+ throw new Exception($"Type handle isn't object for scenario {scenario}");
+ }
+ }
+ public static void IsTypeHandleInt(RuntimeTypeHandle rth, string scenario)
+ {
+ if (Type.GetTypeFromHandle(rth) != typeof(int))
+ {
+ throw new Exception($"Type handle isn't int for scenario {scenario}");
+ }
+ }
+ }
+ internal partial class Program
+ {
+ private static Dictionary<int, Callee> s_instantiatingStubCallees = new Dictionary<int, Callee>();
+ private static volatile ModuleBuilder s_stubTypesModule = null;
+ private static int s_stubTypesCreated = 0;
+
+ private static MethodInfo s_gcHandleFromIntPtr = typeof(GCHandle).GetMethod("FromIntPtr");
+ private static MethodInfo s_gcHandle_getTarget = typeof(GCHandle).GetMethod("get_Target");
+ private static MethodInfo s_compareNumbers = typeof(StubsTestHelpers).GetMethod("CompareNumbers");
+ private static MethodInfo s_isTypeHandleObject = typeof(StubsTestHelpers).GetMethod("IsTypeHandleObject");
+ private static MethodInfo s_isTypeHandleInt = typeof(StubsTestHelpers).GetMethod("IsTypeHandleInt");
+
+ enum GenericShape
+ {
+ NotGeneric,
+ GenericOverReferenceType,
+ GenericOverValueType
+ }
+
+ private static void EmitTypeHandleCheck(ILGenerator g, GenericShape genericShape, GenericTypeParameterBuilder[] typeParamArr, string scenario)
+ {
+ if (genericShape == GenericShape.NotGeneric)
+ return;
+ g.Emit(OpCodes.Ldtoken, typeParamArr[0]);
+ g.Emit(OpCodes.Ldstr, scenario);
+ if (genericShape == GenericShape.GenericOverReferenceType)
+ g.Emit(OpCodes.Call, s_isTypeHandleObject);
+ if (genericShape == GenericShape.GenericOverValueType)
+ g.Emit(OpCodes.Call, s_isTypeHandleInt);
+ }
+
+ private static Type GetDelegateType(List<TypeEx> parameters, Type returnType)
+ {
+ Type[] genericArguments = parameters.Select(t => t.Type).Append(returnType).ToArray();
+ switch (parameters.Count)
+ {
+ case 0:
+ return typeof(Func<>).MakeGenericType(genericArguments);
+ case 1:
+ return typeof(Func<,>).MakeGenericType(genericArguments);
+ case 2:
+ return typeof(Func<,,>).MakeGenericType(genericArguments);
+ case 3:
+ return typeof(Func<,,,>).MakeGenericType(genericArguments);
+ case 4:
+ return typeof(Func<,,,,>).MakeGenericType(genericArguments);
+ case 5:
+ return typeof(Func<,,,,,>).MakeGenericType(genericArguments);
+ case 6:
+ return typeof(Func<,,,,,,>).MakeGenericType(genericArguments);
+ case 7:
+ return typeof(Func<,,,,,,,>).MakeGenericType(genericArguments);
+ case 8:
+ return typeof(Func<,,,,,,,,>).MakeGenericType(genericArguments);
+ case 9:
+ return typeof(Func<,,,,,,,,,>).MakeGenericType(genericArguments);
+ case 10:
+ return typeof(Func<,,,,,,,,,,>).MakeGenericType(genericArguments);
+ case 11:
+ return typeof(Func<,,,,,,,,,,,>).MakeGenericType(genericArguments);
+ case 12:
+ return typeof(Func<,,,,,,,,,,,,>).MakeGenericType(genericArguments);
+ case 13:
+ return typeof(Func<,,,,,,,,,,,,,>).MakeGenericType(genericArguments);
+ case 14:
+ return typeof(Func<,,,,,,,,,,,,,,>).MakeGenericType(genericArguments);
+ case 15:
+ return typeof(Func<,,,,,,,,,,,,,,,>).MakeGenericType(genericArguments);
+ case 16:
+ return typeof(Func<,,,,,,,,,,,,,,,,>).MakeGenericType(genericArguments);
+
+ default:
+ throw new Exception();
+ }
+ }
+
+ private static bool DoStubCall(int callerIndex, bool staticMethod, bool onValueType, GenericShape typeGenericShape, GenericShape methodGenericShape)
+ {
+ string callerNameSeed = Config.InstantiatingStubPrefix + "Caller" + callerIndex; // Use a consistent seed value here so that the various various of unboxing/instantiating stubs are generated with the same arg shape
+ string callerName = callerNameSeed + (staticMethod ? "Static" : "Instance") + (onValueType ? "Class" : "ValueType") + typeGenericShape.ToString() + methodGenericShape.ToString();
+ Random rand = new Random(GetSeed(callerNameSeed));
+ List<TypeEx> pms;
+ do
+ {
+ pms = RandomParameters(s_allTypes, rand);
+ } while (pms.Count > 16);
+
+ Type delegateType = GetDelegateType(pms, typeof(int));
+
+ Callee callee = new Callee(callerName+"Callee", pms);// CreateCallee(Config.PInvokeePrefix + calleeIndex, s_allTypes);
+ callee.Emit();
+
+ Delegate calleeDelegate = callee.Method.CreateDelegate(delegateType);
+
+ int newStubCount = Interlocked.Increment(ref s_stubTypesCreated);
+ if ((s_stubTypesModule == null) || (newStubCount % 1000) == 0)
+ {
+ AssemblyBuilder stubsAssembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("ABIStress_Stubs" + newStubCount), AssemblyBuilderAccess.RunAndCollect);
+ s_stubTypesModule = stubsAssembly.DefineDynamicModule("ABIStress_Stubs" + newStubCount);
+ }
+
+ // This code is based on DelegateHelpers.cs in System.Linq.Expressions.Compiler
+ TypeBuilder tb =
+ s_stubTypesModule.DefineType(
+ $"{callerName}_GenericTarget",
+ TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.AutoClass,
+ onValueType ? typeof(object) : typeof(ValueType));
+ GenericTypeParameterBuilder[] typeParamsType = null;
+ if (typeGenericShape != GenericShape.NotGeneric)
+ typeParamsType = tb.DefineGenericParameters(new string[] { "T" });
+
+ FieldInfo fieldDeclaration = tb.DefineField("MagicValue", typeof(int), FieldAttributes.Public);
+
+ Type typeofInstantiatedType;
+ FieldInfo fieldInfoMagicValueField;
+ if (typeGenericShape == GenericShape.NotGeneric)
+ {
+ typeofInstantiatedType = tb;
+ fieldInfoMagicValueField = fieldDeclaration;
+ }
+ else
+ {
+ typeofInstantiatedType = tb.MakeGenericType(typeParamsType[0]);
+ fieldInfoMagicValueField = TypeBuilder.GetField(typeofInstantiatedType, fieldDeclaration);
+ }
+
+ ConstructorBuilder cb = tb.DefineConstructor(
+ MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.RTSpecialName,
+ CallingConventions.Standard,
+ new Type[] { typeof(int) });
+ cb.SetImplementationFlags(MethodImplAttributes.Managed);
+
+ ILGenerator g = cb.GetILGenerator();
+ g.Emit(OpCodes.Ldarg, 0);
+ g.Emit(OpCodes.Call, typeof(object).GetConstructor(Array.Empty<Type>()));
+ g.Emit(OpCodes.Ldarg, 0);
+ g.Emit(OpCodes.Ldarg_1);
+ g.Emit(OpCodes.Stfld, fieldInfoMagicValueField);
+ g.Emit(OpCodes.Ret);
+
+ MethodAttributes methodAttributes = MethodAttributes.Public | MethodAttributes.HideBySig;
+
+ if (staticMethod)
+ methodAttributes |= MethodAttributes.Static;
+
+ MethodBuilder mbInstance = tb.DefineMethod(
+ "Method",
+ methodAttributes,
+ callee.Method.ReturnType,
+ callee.Parameters.Select(t => t.Type).ToArray());
+
+ GenericTypeParameterBuilder[] typeParamsMethod = null;
+ if (methodGenericShape != GenericShape.NotGeneric)
+ typeParamsMethod = mbInstance.DefineGenericParameters(new string[] { "T" });
+
+ mbInstance.SetImplementationFlags(MethodImplAttributes.Managed);
+
+ int magicNumberEmbeddedInObject = rand.Next();
+
+ GCHandle gchCallee = GCHandle.Alloc(callee.Method.CreateDelegate(delegateType));
+ IntPtr gchCalleeIntPtr = GCHandle.ToIntPtr(gchCallee);
+ g = mbInstance.GetILGenerator();
+
+ if (!staticMethod)
+ {
+ // Verify random number made it intact, and this parameter was handled correctly
+
+ g.Emit(OpCodes.Ldarg_0);
+ g.Emit(OpCodes.Ldfld, fieldInfoMagicValueField);
+ g.Emit(OpCodes.Ldc_I4, magicNumberEmbeddedInObject);
+ g.Emit(OpCodes.Call, s_compareNumbers);
+ }
+
+ // Verify generic args are as expected
+ EmitTypeHandleCheck(g, typeGenericShape, typeParamsType, "type");
+ EmitTypeHandleCheck(g, methodGenericShape, typeParamsMethod, "method");
+
+ // Make the call to callee
+ LocalBuilder gcHandleLocal = g.DeclareLocal(typeof(GCHandle));
+ // Load GCHandle of callee delegate
+ g.Emit(OpCodes.Ldc_I8, (long)gchCalleeIntPtr);
+ g.Emit(OpCodes.Conv_I);
+ g.Emit(OpCodes.Call, s_gcHandleFromIntPtr);
+ g.Emit(OpCodes.Stloc, gcHandleLocal);
+ // Resolve to target
+ g.Emit(OpCodes.Ldloca, gcHandleLocal);
+ g.Emit(OpCodes.Call, s_gcHandle_getTarget);
+ // Cast to delegate type
+ g.Emit(OpCodes.Castclass, delegateType);
+ // Load all args
+ int argOffset = 1;
+ if (staticMethod)
+ argOffset = 0;
+ for (int i = 0; i < pms.Count; i++)
+ g.Emit(OpCodes.Ldarg, argOffset + i);
+
+ // Call delegate invoke method
+ g.Emit(OpCodes.Callvirt, delegateType.GetMethod("Invoke"));
+ // ret
+ g.Emit(OpCodes.Ret);
+ Type calleeTypeOpen = tb.CreateType();
+ Type calleeType;
+ switch(typeGenericShape)
+ {
+ case GenericShape.NotGeneric:
+ calleeType = calleeTypeOpen;
+ break;
+ case GenericShape.GenericOverReferenceType:
+ calleeType = calleeTypeOpen.MakeGenericType(typeof(object));
+ break;
+ case GenericShape.GenericOverValueType:
+ calleeType = calleeTypeOpen.MakeGenericType(typeof(int));
+ break;
+ default:
+ throw new Exception("Unknown case");
+ }
+
+ MethodInfo targetMethodOpen = calleeType.GetMethod("Method");
+ MethodInfo targetMethod;
+
+ switch (methodGenericShape)
+ {
+ case GenericShape.NotGeneric:
+ targetMethod = targetMethodOpen;
+ break;
+ case GenericShape.GenericOverReferenceType:
+ targetMethod = targetMethodOpen.MakeGenericMethod(typeof(object));
+ break;
+ case GenericShape.GenericOverValueType:
+ targetMethod = targetMethodOpen.MakeGenericMethod(typeof(int));
+ break;
+ default:
+ throw new Exception("Unknown case");
+ }
+
+ Delegate targetMethodToCallDel;
+
+ if (staticMethod)
+ {
+ targetMethodToCallDel = targetMethod.CreateDelegate(delegateType);
+ }
+ else
+ {
+ targetMethodToCallDel = targetMethod.CreateDelegate(delegateType, Activator.CreateInstance(calleeType, magicNumberEmbeddedInObject));
+ }
+
+ GCHandle gchTargetMethod = GCHandle.Alloc(targetMethodToCallDel);
+
+ // CALLER Dynamic method
+ DynamicMethod caller = new DynamicMethod(
+ callerName, typeof(int), pms.Select(t => t.Type).ToArray(), typeof(Program).Module);
+
+ g = caller.GetILGenerator();
+
+ // Create the args to pass to the callee from the caller.
+ List<Value> args = GenCallerToCalleeArgs(pms, callee.Parameters, rand);
+
+ if (Config.Verbose)
+ EmitDumpValues("Caller's incoming args", g, pms.Select((p, i) => new ArgValue(p, i)));
+
+ if (Config.Verbose)
+ EmitDumpValues($"Caller's args to {callerName} call", g, args);
+
+ gcHandleLocal = g.DeclareLocal(typeof(GCHandle));
+ g.Emit(OpCodes.Ldc_I8, (long)GCHandle.ToIntPtr(gchTargetMethod));
+ g.Emit(OpCodes.Conv_I);
+ g.Emit(OpCodes.Call, s_gcHandleFromIntPtr);
+ g.Emit(OpCodes.Stloc, gcHandleLocal);
+ // Resolve to target
+ g.Emit(OpCodes.Ldloca, gcHandleLocal);
+ g.Emit(OpCodes.Call, s_gcHandle_getTarget);
+ // Cast to delegate type
+ g.Emit(OpCodes.Castclass, delegateType);
+
+ foreach (Value v in args)
+ v.Emit(g);
+
+ // Call delegate invoke method
+ g.Emit(OpCodes.Callvirt, delegateType.GetMethod("Invoke"));
+ // ret
+ g.Emit(OpCodes.Ret);
+
+ (object callerResult, object calleeResult) = InvokeCallerCallee(caller, pms, callee.Method, args, rand);
+
+ gchCallee.Free();
+ gchTargetMethod.Free();
+ if (callerResult.Equals(calleeResult))
+ return true;
+
+ Console.WriteLine("Mismatch in stub call: expected {0}, got {1}", calleeResult, callerResult);
+ Console.WriteLine(callerName);
+ WriteSignature(caller);
+ WriteSignature(callee.Method);
+ return false;
+
+ }
+ }
+}
diff --git a/tests/src/JIT/Stress/ABI/TailCallEventListener.cs b/tests/src/JIT/Stress/ABI/TailCallEventListener.cs
new file mode 100644
index 0000000000..771271330c
--- /dev/null
+++ b/tests/src/JIT/Stress/ABI/TailCallEventListener.cs
@@ -0,0 +1,57 @@
+// 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;
+using System.Collections.Generic;
+using System.Diagnostics.Tracing;
+
+namespace ABIStress
+{
+ // This is an event listener that allows us to track how many tailcalls are
+ // being considered and how many tailcall decisions are succeeding. This
+ // mainly allows us to give some output tracking how "healthy" the test is
+ // (i.e. how much it actually tests tailcalls).
+ internal class TailCallEventListener : EventListener
+ {
+ public int NumCallersSeen { get; set; }
+ public int NumSuccessfulTailCalls { get; set; }
+ public Dictionary<string, int> FailureReasons { get; } = new Dictionary<string, int>();
+
+ protected override void OnEventSourceCreated(EventSource eventSource)
+ {
+ if (eventSource.Name != "Microsoft-Windows-DotNETRuntime")
+ return;
+
+ EventKeywords jitTracing = (EventKeywords)0x1000; // JITTracing
+ EnableEvents(eventSource, EventLevel.Verbose, jitTracing);
+ }
+
+ protected override void OnEventWritten(EventWrittenEventArgs data)
+ {
+ string GetData(string name) => data.Payload[data.PayloadNames.IndexOf(name)].ToString();
+
+ switch (data.EventName)
+ {
+ case "MethodJitTailCallFailed":
+ if (GetData("MethodBeingCompiledName").StartsWith(Config.TailCallerPrefix))
+ {
+ NumCallersSeen++;
+ string failReason = GetData("FailReason");
+ lock (FailureReasons)
+ {
+ FailureReasons[failReason] = FailureReasons.GetValueOrDefault(failReason) + 1;
+ }
+ }
+ break;
+ case "MethodJitTailCallSucceeded":
+ if (GetData("MethodBeingCompiledName").StartsWith(Config.TailCallerPrefix))
+ {
+ NumCallersSeen++;
+ NumSuccessfulTailCalls++;
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/tests/src/JIT/Stress/ABI/TailCalls.cs b/tests/src/JIT/Stress/ABI/TailCalls.cs
new file mode 100644
index 0000000000..9d3e514510
--- /dev/null
+++ b/tests/src/JIT/Stress/ABI/TailCalls.cs
@@ -0,0 +1,74 @@
+// 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;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection.Emit;
+
+namespace ABIStress
+{
+ internal partial class Program
+ {
+ private static List<Callee> s_tailCallees;
+ private static bool DoTailCall(int callerIndex)
+ {
+ // We pregenerate tail callee parameter lists because we want to be able to select
+ // a callee with less arg stack space than this caller.
+ if (s_tailCallees == null)
+ {
+ s_tailCallees =
+ Enumerable.Range(0, Config.NumCallees)
+ .Select(i => CreateCallee(Config.TailCalleePrefix + i, s_tailCalleeCandidateArgTypes))
+ .ToList();
+ }
+
+ string callerName = Config.TailCallerPrefix + callerIndex;
+ Random rand = new Random(GetSeed(callerName));
+ List<TypeEx> callerParams;
+ List<Callee> callable;
+ do
+ {
+ callerParams = RandomParameters(s_tailCalleeCandidateArgTypes, rand);
+ int argStackSizeApprox = s_abi.ApproximateArgStackAreaSize(callerParams);
+ callable = s_tailCallees.Where(t => t.ArgStackSizeApprox < argStackSizeApprox).ToList();
+ } while (callable.Count <= 0);
+
+ int calleeIndex = rand.Next(callable.Count);
+ Callee callee = callable[calleeIndex];
+ callee.Emit();
+
+ DynamicMethod caller = new DynamicMethod(
+ callerName, typeof(int), callerParams.Select(t => t.Type).ToArray(), typeof(Program).Module);
+
+ ILGenerator g = caller.GetILGenerator();
+
+ // Create the args to pass to the callee from the caller.
+ List<Value> args = GenCallerToCalleeArgs(callerParams, callee.Parameters, rand);
+
+ if (Config.Verbose)
+ {
+ EmitDumpValues("Caller's incoming args", g, callerParams.Select((p, i) => new ArgValue(p, i)));
+ EmitDumpValues("Caller's args to tailcall", g, args);
+ }
+
+ foreach (Value v in args)
+ v.Emit(g);
+
+ g.Emit(OpCodes.Tailcall);
+ g.EmitCall(OpCodes.Call, callee.Method, null);
+ g.Emit(OpCodes.Ret);
+
+ (object callerResult, object calleeResult) = InvokeCallerCallee(caller, callerParams, callee.Method, args, rand);
+
+ if (callerResult.Equals(calleeResult))
+ return true;
+
+ Console.WriteLine("Mismatch in tailcall: expected {0}, got {1}", calleeResult, callerResult);
+ WriteSignature(caller);
+ WriteSignature(callee.Method);
+ return false;
+ }
+ }
+}
diff --git a/tests/src/JIT/Stress/ABI/Types.cs b/tests/src/JIT/Stress/ABI/Types.cs
new file mode 100644
index 0000000000..d38f211cc2
--- /dev/null
+++ b/tests/src/JIT/Stress/ABI/Types.cs
@@ -0,0 +1,85 @@
+// 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;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+namespace ABIStress
+{
+ // This class wraps around a Type supplying easier access to the correct
+ // order of fields when constructing them. Reflection APIs do not give any
+ // guarantees around the order of fields when using Type.GetFields(), so
+ // this one makes sure we get everything correctly ordered and also caches
+ // the ctor we need to use.
+ internal class TypeEx
+ {
+ public Type Type { get; }
+ public int Size { get; }
+ public FieldInfo[] Fields { get; }
+ public ConstructorInfo Ctor { get; }
+
+ public TypeEx(Type t)
+ {
+ Type = t;
+ // Marshal.SizeOf(Type) overload does not work for generic types so
+ // we use the workaround below.
+ Size = Marshal.SizeOf(Activator.CreateInstance(t));
+ if (!t.IsOurStructType())
+ return;
+
+ Fields = Enumerable.Range(0, 10000).Select(i => t.GetField($"F{i}")).TakeWhile(fi => fi != null).ToArray();
+ Ctor = t.GetConstructor(Fields.Select(f => f.FieldType).ToArray());
+ }
+ }
+
+ // These structs will be used in generated callers and callees. U suffix =
+ // unpromotable, P suffix = promotable by the JIT. Note that fields must be
+ // named Fi with i sequential.
+ public struct S1P { public byte F0; public S1P(byte f0) => F0 = f0; }
+ public struct S2P { public short F0; public S2P(short f0) => F0 = f0; }
+ public struct S2U { public byte F0, F1; public S2U(byte f0, byte f1) => (F0, F1) = (f0, f1); }
+ public struct S3U { public byte F0, F1, F2; public S3U(byte f0, byte f1, byte f2) => (F0, F1, F2) = (f0, f1, f2); }
+ public struct S4P { public int F0; public S4P(int f0) => F0 = f0; }
+ public struct S4U { public byte F0, F1, F2, F3; public S4U(byte f0, byte f1, byte f2, byte f3) => (F0, F1, F2, F3) = (f0, f1, f2, f3); }
+ public struct S5U { public byte F0, F1, F2, F3, F4; public S5U(byte f0, byte f1, byte f2, byte f3, byte f4) => (F0, F1, F2, F3, F4) = (f0, f1, f2, f3, f4); }
+ public struct S6U { public byte F0, F1, F2, F3, F4, F5; public S6U(byte f0, byte f1, byte f2, byte f3, byte f4, byte f5) => (F0, F1, F2, F3, F4, F5) = (f0, f1, f2, f3, f4, f5); }
+ public struct S7U { public byte F0, F1, F2, F3, F4, F5, F6; public S7U(byte f0, byte f1, byte f2, byte f3, byte f4, byte f5, byte f6) => (F0, F1, F2, F3, F4, F5, F6) = (f0, f1, f2, f3, f4, f5, f6); }
+ public struct S8P { public long F0; public S8P(long f0) => F0 = f0; }
+ public struct S8U { public byte F0, F1, F2, F3, F4, F5, F6, F7; public S8U(byte f0, byte f1, byte f2, byte f3, byte f4, byte f5, byte f6, byte f7) => (F0, F1, F2, F3, F4, F5, F6, F7) = (f0, f1, f2, f3, f4, f5, f6, f7); }
+ public struct S9U { public byte F0, F1, F2, F3, F4, F5, F6, F7, F8; public S9U(byte f0, byte f1, byte f2, byte f3, byte f4, byte f5, byte f6, byte f7, byte f8) => (F0, F1, F2, F3, F4, F5, F6, F7, F8) = (f0, f1, f2, f3, f4, f5, f6, f7, f8); }
+ public struct S10U { public byte F0, F1, F2, F3, F4, F5, F6, F7, F8, F9; public S10U(byte f0, byte f1, byte f2, byte f3, byte f4, byte f5, byte f6, byte f7, byte f8, byte f9) => (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9) = (f0, f1, f2, f3, f4, f5, f6, f7, f8, f9); }
+ public struct S11U { public byte F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10; public S11U(byte f0, byte f1, byte f2, byte f3, byte f4, byte f5, byte f6, byte f7, byte f8, byte f9, byte f10) => (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10) = (f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10); }
+ public struct S12U { public byte F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11; public S12U(byte f0, byte f1, byte f2, byte f3, byte f4, byte f5, byte f6, byte f7, byte f8, byte f9, byte f10, byte f11) => (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11) = (f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11); }
+ public struct S13U { public byte F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12; public S13U(byte f0, byte f1, byte f2, byte f3, byte f4, byte f5, byte f6, byte f7, byte f8, byte f9, byte f10, byte f11, byte f12) => (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12) = (f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12); }
+ public struct S14U { public byte F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13; public S14U(byte f0, byte f1, byte f2, byte f3, byte f4, byte f5, byte f6, byte f7, byte f8, byte f9, byte f10, byte f11, byte f12, byte f13) => (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13) = (f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13); }
+ public struct S15U { public byte F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14; public S15U(byte f0, byte f1, byte f2, byte f3, byte f4, byte f5, byte f6, byte f7, byte f8, byte f9, byte f10, byte f11, byte f12, byte f13, byte f14) => (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14) = (f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14); }
+ public struct S16U { public byte F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15; public S16U(byte f0, byte f1, byte f2, byte f3, byte f4, byte f5, byte f6, byte f7, byte f8, byte f9, byte f10, byte f11, byte f12, byte f13, byte f14, byte f15) => (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15) = (f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15); }
+ public struct S17U { public byte F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16; public S17U(byte f0, byte f1, byte f2, byte f3, byte f4, byte f5, byte f6, byte f7, byte f8, byte f9, byte f10, byte f11, byte f12, byte f13, byte f14, byte f15, byte f16) => (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16) = (f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16); }
+ public struct S31U { public byte F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27, F28, F29, F30; public S31U(byte f0, byte f1, byte f2, byte f3, byte f4, byte f5, byte f6, byte f7, byte f8, byte f9, byte f10, byte f11, byte f12, byte f13, byte f14, byte f15, byte f16, byte f17, byte f18, byte f19, byte f20, byte f21, byte f22, byte f23, byte f24, byte f25, byte f26, byte f27, byte f28, byte f29, byte f30) => (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27, F28, F29, F30) = (f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, f29, f30); }
+ public struct S32U { public byte F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27, F28, F29, F30, F31; public S32U(byte f0, byte f1, byte f2, byte f3, byte f4, byte f5, byte f6, byte f7, byte f8, byte f9, byte f10, byte f11, byte f12, byte f13, byte f14, byte f15, byte f16, byte f17, byte f18, byte f19, byte f20, byte f21, byte f22, byte f23, byte f24, byte f25, byte f26, byte f27, byte f28, byte f29, byte f30, byte f31) => (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27, F28, F29, F30, F31) = (f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, f29, f30, f31); }
+ public struct Hfa1 { public float F0, F1; public Hfa1(float f0, float f1) => (F0, F1) = (f0, f1); }
+ public struct Hfa2 { public double F0, F1, F2, F3; public Hfa2(double f0, double f1, double f2, double f3) => (F0, F1, F2, F3) = (f0, f1, f2, f3); }
+
+ internal static class TypeExtensions
+ {
+ public static bool IsOurStructType(this Type t)
+ {
+ return
+ t == typeof(S1P) || t == typeof(S2P) ||
+ t == typeof(S2U) || t == typeof(S3U) ||
+ t == typeof(S4P) || t == typeof(S4U) ||
+ t == typeof(S5U) || t == typeof(S6U) ||
+ t == typeof(S7U) || t == typeof(S8P) ||
+ t == typeof(S8U) || t == typeof(S9U) ||
+ t == typeof(S10U) || t == typeof(S11U) ||
+ t == typeof(S12U) || t == typeof(S13U) ||
+ t == typeof(S14U) || t == typeof(S15U) ||
+ t == typeof(S16U) || t == typeof(S17U) ||
+ t == typeof(S31U) || t == typeof(S32U) ||
+ t == typeof(Hfa1) || t == typeof(Hfa2);
+ }
+ }
+}
diff --git a/tests/src/JIT/Stress/ABI/pinvokes_d.csproj b/tests/src/JIT/Stress/ABI/pinvokes_d.csproj
new file mode 100644
index 0000000000..e8a0e9c0b3
--- /dev/null
+++ b/tests/src/JIT/Stress/ABI/pinvokes_d.csproj
@@ -0,0 +1,38 @@
+<?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>{11F01248-05A0-4FA0-9345-75273E7276C9}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+ <IlasmRoundTrip>true</IlasmRoundTrip>
+ <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
+ <JitOptimizationSensitive>true</JitOptimizationSensitive>
+ <GCStressIncompatible>true</GCStressIncompatible>
+ </PropertyGroup>
+ <!-- Default configurations to help VS understand the configurations -->
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "></PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "></PropertyGroup>
+ <PropertyGroup>
+ <DebugType>Full</DebugType>
+ <Optimize>False</Optimize>
+ <CLRTestExecutionArguments>--pinvokes --num-calls 1000 --no-ctrlc-summary</CLRTestExecutionArguments>
+ </PropertyGroup>
+ <ItemGroup>
+ <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+ <Visible>False</Visible>
+ </CodeAnalysisDependentAssemblyPaths>
+ </ItemGroup>
+ <ItemGroup>
+ <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="*.cs" />
+ </ItemGroup>
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+ <PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' "></PropertyGroup>
+</Project>
diff --git a/tests/src/JIT/Stress/ABI/pinvokes_do.csproj b/tests/src/JIT/Stress/ABI/pinvokes_do.csproj
new file mode 100644
index 0000000000..f8a41d2258
--- /dev/null
+++ b/tests/src/JIT/Stress/ABI/pinvokes_do.csproj
@@ -0,0 +1,38 @@
+<?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>{11F01248-05A0-4FA0-9345-75273E7276C9}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+ <IlasmRoundTrip>true</IlasmRoundTrip>
+ <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
+ <JitOptimizationSensitive>true</JitOptimizationSensitive>
+ <GCStressIncompatible>true</GCStressIncompatible>
+ </PropertyGroup>
+ <!-- Default configurations to help VS understand the configurations -->
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "></PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "></PropertyGroup>
+ <PropertyGroup>
+ <DebugType>Full</DebugType>
+ <Optimize>True</Optimize>
+ <CLRTestExecutionArguments>--pinvokes --num-calls 1000 --no-ctrlc-summary</CLRTestExecutionArguments>
+ </PropertyGroup>
+ <ItemGroup>
+ <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+ <Visible>False</Visible>
+ </CodeAnalysisDependentAssemblyPaths>
+ </ItemGroup>
+ <ItemGroup>
+ <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="*.cs" />
+ </ItemGroup>
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+ <PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' "></PropertyGroup>
+</Project>
diff --git a/tests/src/JIT/Stress/ABI/stubs_do.csproj b/tests/src/JIT/Stress/ABI/stubs_do.csproj
new file mode 100644
index 0000000000..9e9b4c129a
--- /dev/null
+++ b/tests/src/JIT/Stress/ABI/stubs_do.csproj
@@ -0,0 +1,38 @@
+<?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>{11F01248-05A0-4FA0-9345-75273E7276C9}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+ <IlasmRoundTrip>true</IlasmRoundTrip>
+ <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
+ <JitOptimizationSensitive>true</JitOptimizationSensitive>
+ <GCStressIncompatible>true</GCStressIncompatible>
+ </PropertyGroup>
+ <!-- Default configurations to help VS understand the configurations -->
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "></PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "></PropertyGroup>
+ <PropertyGroup>
+ <DebugType>Full</DebugType>
+ <Optimize>True</Optimize>
+ <CLRTestExecutionArguments>--instantiatingstubs --unboxingstubs --sharedgenericunboxingstubs --num-calls 100 --max-params 5 --no-ctrlc-summary</CLRTestExecutionArguments>
+ </PropertyGroup>
+ <ItemGroup>
+ <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+ <Visible>False</Visible>
+ </CodeAnalysisDependentAssemblyPaths>
+ </ItemGroup>
+ <ItemGroup>
+ <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="*.cs" />
+ </ItemGroup>
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+ <PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' "></PropertyGroup>
+</Project>
diff --git a/tests/src/JIT/Stress/ABI/tailcalls_d.csproj b/tests/src/JIT/Stress/ABI/tailcalls_d.csproj
new file mode 100644
index 0000000000..4a8ec1eefd
--- /dev/null
+++ b/tests/src/JIT/Stress/ABI/tailcalls_d.csproj
@@ -0,0 +1,38 @@
+<?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>{11F01248-05A0-4FA0-9345-75273E7276C9}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+ <IlasmRoundTrip>true</IlasmRoundTrip>
+ <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
+ <JitOptimizationSensitive>true</JitOptimizationSensitive>
+ <GCStressIncompatible>true</GCStressIncompatible>
+ </PropertyGroup>
+ <!-- Default configurations to help VS understand the configurations -->
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "></PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "></PropertyGroup>
+ <PropertyGroup>
+ <DebugType>Full</DebugType>
+ <Optimize>False</Optimize>
+ <CLRTestExecutionArguments>--tailcalls --num-calls 1000 --no-ctrlc-summary</CLRTestExecutionArguments>
+ </PropertyGroup>
+ <ItemGroup>
+ <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+ <Visible>False</Visible>
+ </CodeAnalysisDependentAssemblyPaths>
+ </ItemGroup>
+ <ItemGroup>
+ <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="*.cs" />
+ </ItemGroup>
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+ <PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' "></PropertyGroup>
+</Project>
diff --git a/tests/src/JIT/Stress/ABI/tailcalls_do.csproj b/tests/src/JIT/Stress/ABI/tailcalls_do.csproj
new file mode 100644
index 0000000000..26df2a99a4
--- /dev/null
+++ b/tests/src/JIT/Stress/ABI/tailcalls_do.csproj
@@ -0,0 +1,38 @@
+<?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>{11F01248-05A0-4FA0-9345-75273E7276C9}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+ <IlasmRoundTrip>true</IlasmRoundTrip>
+ <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
+ <JitOptimizationSensitive>true</JitOptimizationSensitive>
+ <GCStressIncompatible>true</GCStressIncompatible>
+ </PropertyGroup>
+ <!-- Default configurations to help VS understand the configurations -->
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "></PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "></PropertyGroup>
+ <PropertyGroup>
+ <DebugType>Full</DebugType>
+ <Optimize>True</Optimize>
+ <CLRTestExecutionArguments>--tailcalls --num-calls 1000 --no-ctrlc-summary</CLRTestExecutionArguments>
+ </PropertyGroup>
+ <ItemGroup>
+ <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+ <Visible>False</Visible>
+ </CodeAnalysisDependentAssemblyPaths>
+ </ItemGroup>
+ <ItemGroup>
+ <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="*.cs" />
+ </ItemGroup>
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+ <PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' "></PropertyGroup>
+</Project>