From cb083648a06e30b15fee6e6c638678ec2e03c261 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tom=C3=A1=C5=A1=20Rylek?= Date: Tue, 8 Aug 2023 20:43:50 +0200 Subject: [PATCH] Crossgen2 support for static virtual method resolution (take 2) (#87438) This change adds SVM resolution support to Crossgen2. We still resort to runtime JIT in case we cannot resolve the SVM call at compile time (typically for canonical generic methods); some of these cases are just due to current limitations of the JIT interface and can be fixed in the future. Thanks Tomas --- .../tools/Common/JitInterface/CorInfoTypes.cs | 3 + .../ReadyToRun/DelegateCtorSignature.cs | 10 +- .../DependencyAnalysis/ReadyToRun/GCRefMapNode.cs | 7 +- .../DependencyAnalysis/ReadyToRun/Import.cs | 2 + .../ReadyToRun/MethodFixupSignature.cs | 6 +- .../JitInterface/CorInfoImpl.ReadyToRun.cs | 157 +++++++++++++++++---- src/coreclr/vm/methodtable.cpp | 5 +- .../MethodBodyOnUnrelatedType.ilproj | 3 + .../Regression/GitHub_70385.il | 4 +- src/tests/readytorun/tests/main.cs | 17 +++ src/tests/readytorun/tests/test.cs | 18 +++ 11 files changed, 198 insertions(+), 34 deletions(-) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index b3dc1ff..47e7e6b 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -1365,6 +1365,9 @@ namespace Internal.JitInterface // token comes from devirtualizing a method CORINFO_TOKENKIND_DevirtualizedMethod = 0x800 | CORINFO_TOKENKIND_Method, + + // token comes from resolved static virtual method + CORINFO_TOKENKIND_ResolvedStaticVirtualMethod = 0x1000 | CORINFO_TOKENKIND_Method, }; // These are error codes returned by CompileMethod diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelegateCtorSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelegateCtorSignature.cs index c986357..66dbf74 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelegateCtorSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/DelegateCtorSignature.cs @@ -42,12 +42,20 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun { SignatureContext innerContext = builder.EmitFixup(factory, ReadyToRunFixupKind.DelegateCtor, _methodToken.Token.Module, factory.SignatureContext); + bool needsInstantiatingStub = _targetMethod.Method.HasInstantiation; + if (_targetMethod.Method.IsVirtual && _targetMethod.Method.Signature.IsStatic) + { + // For static virtual methods, we always require an instantiating stub as the method may resolve to a canonical representation + // at runtime without us being able to detect that at compile time. + needsInstantiatingStub |= (_targetMethod.Method.OwningType.HasInstantiation || _methodToken.ConstrainedType != null); + } + builder.EmitMethodSignature( _methodToken, enforceDefEncoding: false, enforceOwningType: false, innerContext, - isInstantiatingStub: _targetMethod.Method.HasInstantiation); + isInstantiatingStub: needsInstantiatingStub); builder.EmitTypeSignature(_delegateType, innerContext); } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GCRefMapNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GCRefMapNode.cs index fbf41d5..1e54ff3 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GCRefMapNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GCRefMapNode.cs @@ -88,12 +88,13 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun } else { - bool isUnboxingStub = false; + bool isStub = false; if (methodNode is DelayLoadHelperImport methodImport) { - isUnboxingStub = ((MethodFixupSignature)methodImport.ImportSignature.Target).IsUnboxingStub; + MethodFixupSignature signature = (MethodFixupSignature)methodImport.ImportSignature.Target; + isStub = signature.IsUnboxingStub || signature.IsInstantiatingStub; } - builder.GetCallRefMap(methodNode.Method, isUnboxingStub); + builder.GetCallRefMap(methodNode.Method, isStub); } if (methodIndex >= nextMethodIndex) { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Import.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Import.cs index 9e38401..3a5eb9f 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Import.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Import.cs @@ -19,6 +19,8 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun internal readonly MethodDesc CallingMethod; + public Signature Signature => ImportSignature.Target; + public Import(ImportSectionNode tableNode, Signature importSignature, MethodDesc callingMethod = null) { Table = tableNode; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs index 792430a..63bd020 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/MethodFixupSignature.cs @@ -35,7 +35,7 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun compilerContext.EnsureLoadableMethod(method.Method); compilerContext.EnsureLoadableType(_method.OwningType); - if (method.ConstrainedType != null) + if (method.ConstrainedType != null && !method.ConstrainedType.IsRuntimeDeterminedSubtype) compilerContext.EnsureLoadableType(method.ConstrainedType); } @@ -46,6 +46,10 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun public bool IsUnboxingStub => _method.Unboxing; + public TypeDesc ConstrainedType => _method.ConstrainedType; + + public bool NeedsInstantiationArg => _method.ConstrainedType?.IsCanonicalSubtype(CanonicalFormKind.Any) ?? false; + protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory factory) { DependencyList list = base.ComputeNonRelocationBasedDependencies(factory); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs index c57e731..cb00ffa 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -280,6 +280,15 @@ namespace Internal.JitInterface currentType = currentType.BaseType; } + foreach (DefType interfaceType in methodTargetOwner.RuntimeInterfaces) + { + if (interfaceType == instantiatedOwningType || + interfaceType.ConvertToCanonForm(CanonicalFormKind.Specific) == canonicalizedOwningType) + { + return methodTargetOwner; + } + } + Debug.Assert(false); throw new Exception(); } @@ -931,7 +940,23 @@ namespace Internal.JitInterface TypeDesc delegateTypeDesc = HandleToObject(delegateType); MethodDesc targetMethodDesc = HandleToObject(pTargetMethod.hMethod); Debug.Assert(!targetMethodDesc.IsUnboxingThunk()); - MethodWithToken targetMethod = new MethodWithToken(targetMethodDesc, HandleToModuleToken(ref pTargetMethod), constrainedType: null, unboxing: false, context: entityFromContext(pTargetMethod.tokenContext)); + + var typeOrMethodContext = (pTargetMethod.tokenContext == contextFromMethodBeingCompiled()) ? + MethodBeingCompiled : HandleToObject((void*)pTargetMethod.tokenContext); + + TypeDesc constrainedType = null; + if (targetConstraint != 0) + { + MethodIL methodIL = _compilation.GetMethodIL(MethodBeingCompiled); + constrainedType = (TypeDesc)ResolveTokenInScope(methodIL, typeOrMethodContext, targetConstraint); + } + + MethodWithToken targetMethod = new MethodWithToken( + targetMethodDesc, + HandleToModuleToken(ref pTargetMethod), + constrainedType: constrainedType, + unboxing: false, + context: typeOrMethodContext); pLookup.lookupKind.needsRuntimeLookup = false; pLookup.constLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.DelegateCtor(delegateTypeDesc, targetMethod)); @@ -1330,7 +1355,15 @@ namespace Internal.JitInterface } } - context = entityFromContext(pResolvedToken.tokenContext); + if (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_ResolvedStaticVirtualMethod) + { + context = null; + } + else + { + context = entityFromContext(pResolvedToken.tokenContext); + } + return HandleToModuleToken(ref pResolvedToken); } @@ -1845,22 +1878,21 @@ namespace Internal.JitInterface } callerModule = ((EcmaMethod)callerMethod.GetTypicalMethodDefinition()).Module; + bool isCallVirt = (flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_CALLVIRT) != 0; + bool isLdftn = (flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_LDFTN) != 0; + bool isStaticVirtual = (originalMethod.Signature.IsStatic && originalMethod.IsVirtual); // Spec says that a callvirt lookup ignores static methods. Since static methods // can't have the exact same signature as instance methods, a lookup that found // a static method would have never found an instance method. - if (originalMethod.Signature.IsStatic && (flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_CALLVIRT) != 0) + if (originalMethod.Signature.IsStatic && isCallVirt) { ThrowHelper.ThrowInvalidProgramException(ExceptionStringID.InvalidProgramCallVirtStatic, originalMethod); } exactType = type; - constrainedType = null; - if ((flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_CALLVIRT) != 0 && pConstrainedResolvedToken != null) - { - constrainedType = HandleToObject(pConstrainedResolvedToken->hClass); - } + constrainedType = (pConstrainedResolvedToken != null ? HandleToObject(pConstrainedResolvedToken->hClass) : null); bool resolvedConstraint = false; bool forceUseRuntimeLookup = false; @@ -1887,7 +1919,19 @@ namespace Internal.JitInterface originalMethod = methodOnUnderlyingType; } - MethodDesc directMethod = constrainedType.TryResolveConstraintMethodApprox(exactType, originalMethod, out forceUseRuntimeLookup); + MethodDesc directMethod; + if (isStaticVirtual) + { + directMethod = constrainedType.ResolveVariantInterfaceMethodToStaticVirtualMethodOnType(originalMethod); + if (directMethod != null && !_compilation.NodeFactory.CompilationModuleGroup.VersionsWithMethodBody(directMethod)) + { + directMethod = null; + } + } + else + { + directMethod = constrainedType.TryResolveConstraintMethodApprox(exactType, originalMethod, out forceUseRuntimeLookup); + } if (directMethod != null) { // Either @@ -1909,6 +1953,15 @@ namespace Internal.JitInterface pResult->thisTransform = CORINFO_THIS_TRANSFORM.CORINFO_NO_THIS_TRANSFORM; exactType = constrainedType; + if (isStaticVirtual) + { + pResolvedToken.tokenType = CorInfoTokenKind.CORINFO_TOKENKIND_ResolvedStaticVirtualMethod; + constrainedType = null; + } + } + else if (isStaticVirtual) + { + pResult->thisTransform = CORINFO_THIS_TRANSFORM.CORINFO_NO_THIS_TRANSFORM; } else if (constrainedType.IsValueType) { @@ -1925,16 +1978,17 @@ namespace Internal.JitInterface // targetMethod = methodAfterConstraintResolution; + bool constrainedTypeNeedsRuntimeLookup = (constrainedType != null && constrainedType.IsCanonicalSubtype(CanonicalFormKind.Any)); if (targetMethod.HasInstantiation) { pResult->contextHandle = contextFromMethod(targetMethod); - pResult->exactContextNeedsRuntimeLookup = targetMethod.IsSharedByGenericInstantiations; + pResult->exactContextNeedsRuntimeLookup = constrainedTypeNeedsRuntimeLookup || targetMethod.IsSharedByGenericInstantiations; } else { pResult->contextHandle = contextFromType(exactType); - pResult->exactContextNeedsRuntimeLookup = exactType.IsCanonicalSubtype(CanonicalFormKind.Any); + pResult->exactContextNeedsRuntimeLookup = constrainedTypeNeedsRuntimeLookup || exactType.IsCanonicalSubtype(CanonicalFormKind.Any); // Use main method as the context as long as the methods are called on the same type if (pResult->exactContextNeedsRuntimeLookup && @@ -1959,18 +2013,21 @@ namespace Internal.JitInterface bool resolvedCallVirt = false; bool callVirtCrossingVersionBubble = false; - if ((flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_LDFTN) != 0) + if (isStaticVirtual && !resolvedConstraint) + { + // Don't use direct calls for static virtual method calls unresolved at compile time + } + else if (isLdftn) { PrepareForUseAsAFunctionPointer(targetMethod); directCall = true; } - else - if (targetMethod.Signature.IsStatic) + else if (targetMethod.Signature.IsStatic) { // Static methods are always direct calls directCall = true; } - else if ((flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_CALLVIRT) == 0 || resolvedConstraint) + else if (!isCallVirt || resolvedConstraint) { directCall = true; } @@ -2029,16 +2086,41 @@ namespace Internal.JitInterface if (directCall) { + bool isVirtualBehaviorUnresolved = (isCallVirt && !resolvedCallVirt || isStaticVirtual && !resolvedConstraint); + // Direct calls to abstract methods are not allowed if (targetMethod.IsAbstract && // Compensate for always treating delegates as direct calls above - !(((flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_LDFTN) != 0) && ((flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_CALLVIRT) != 0) && !resolvedCallVirt)) + !(isLdftn && isVirtualBehaviorUnresolved)) { ThrowHelper.ThrowInvalidProgramException(ExceptionStringID.InvalidProgramCallAbstractMethod, targetMethod); } bool allowInstParam = (flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_ALLOWINSTPARAM) != 0; + // If the target method is resolved via constrained static virtual dispatch + // And it requires an instParam, we do not have the generic dictionary infrastructure + // to load the correct generic context arg via EmbedGenericHandle. + // Instead, force the call to go down the CORINFO_CALL_CODE_POINTER code path + // which should have somewhat inferior performance. This should only actually happen in the case + // of shared generic code calling a shared generic implementation method, which should be rare. + // + // An alternative design would be to add a new generic dictionary entry kind to hold the MethodDesc + // of the constrained target instead, and use that in some circumstances; however, implementation of + // that design requires refactoring variuos parts of the JIT interface as well as + // TryResolveConstraintMethodApprox. In particular we would need to be abled to embed a constrained lookup + // via EmbedGenericHandle, as well as decide in TryResolveConstraintMethodApprox if the call can be made + // via a single use of CORINFO_CALL_CODE_POINTER, or would be better done with a CORINFO_CALL + embedded + // constrained generic handle, or if there is a case where we would want to use both a CORINFO_CALL and + // embedded constrained generic handle. Given the current expected high performance use case of this feature + // which is generic numerics which will always resolve to exact valuetypes, it is not expected that + // the complexity involved would be worth the risk. Other scenarios are not expected to be as performance + // sensitive. + if (isStaticVirtual && pResult->exactContextNeedsRuntimeLookup) + { + allowInstParam = false; + } + if (!allowInstParam && canonMethod != null && canonMethod.RequiresInstArg()) { useInstantiatingStub = true; @@ -2085,11 +2167,16 @@ namespace Internal.JitInterface { pResult->kind = CORINFO_CALL_KIND.CORINFO_CALL_CODE_POINTER; - // For reference types, the constrained type does not affect method resolution - DictionaryEntryKind entryKind = (constrainedType != null && constrainedType.IsValueType + // For reference types, the constrained type does not affect instance virtual method resolution + DictionaryEntryKind entryKind = (constrainedType != null && (constrainedType.IsValueType || !isCallVirt) ? DictionaryEntryKind.ConstrainedMethodEntrySlot : DictionaryEntryKind.MethodEntrySlot); + if (isStaticVirtual && exactType.HasInstantiation) + { + useInstantiatingStub = true; + } + ComputeRuntimeLookupForSharedGenericToken(entryKind, ref pResolvedToken, pConstrainedResolvedToken, originalMethod, ref pResult->codePointerOrStubLookup); } } @@ -2111,6 +2198,33 @@ namespace Internal.JitInterface } pResult->nullInstanceCheck = resolvedCallVirt; } + else if (isStaticVirtual) + { + pResult->kind = CORINFO_CALL_KIND.CORINFO_CALL; + pResult->nullInstanceCheck = false; + + // Always use an instantiating stub for unresolved constrained SVM calls as we cannot + // always tell at compile time that a given SVM resolves to a method on a generic base + // class and not requesting the instantiating stub makes the runtime transform the + // owning type to its canonical equivalent that would need different codegen + // (supplying the instantiation argument). + if (!resolvedConstraint) + { + if (pResult->exactContextNeedsRuntimeLookup) + { + throw new RequiresRuntimeJitException("EmbedGenericHandle currently doesn't support propagation of RUNTIME_LOOKUP or pConstrainedResolvedToken from ComputeRuntimeLookupForSharedGenericToken"); + // ComputeRuntimeLookupForSharedGenericToken(DictionaryEntryKind.DispatchStubAddrSlot, ref pResolvedToken, pConstrainedResolvedToken, originalMethod, ref pResult->codePointerOrStubLookup); + // useInstantiatingStub = false; + } + else + { + throw new RequiresRuntimeJitException("CanInline currently doesn't support propagation of constrained type so that we cannot reliably tell whether a SVM call can be inlined"); + // Even if we decided to support SVMs unresolved at compile time, we'd still need to force the use of instantiating stub + // as we can't tell in advance whether the method will be runtime-resolved to a canonical representation. + // useInstantiatingStub = true; + } + } + } // All virtual calls which take method instantiations must // currently be implemented by an indirect call via a runtime-lookup // function pointer @@ -2250,13 +2364,6 @@ namespace Internal.JitInterface private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESOLVED_TOKEN* pConstrainedResolvedToken, CORINFO_METHOD_STRUCT_* callerHandle, CORINFO_CALLINFO_FLAGS flags, CORINFO_CALL_INFO* pResult) { - if ((flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_CALLVIRT) == 0 && pConstrainedResolvedToken != null) - { - // Defer constrained call / ldftn instructions used for static virtual methods - // to runtime resolution. - throw new RequiresRuntimeJitException("SVM"); - } - MethodDesc methodToCall; MethodDesc targetMethod; TypeDesc constrainedType; diff --git a/src/coreclr/vm/methodtable.cpp b/src/coreclr/vm/methodtable.cpp index e84f8b0..df47319 100644 --- a/src/coreclr/vm/methodtable.cpp +++ b/src/coreclr/vm/methodtable.cpp @@ -9242,7 +9242,7 @@ MethodTable::TryResolveConstraintMethodApprox( { _ASSERTE(!thInterfaceType.IsTypeDesc()); _ASSERTE(thInterfaceType.IsInterface()); - BOOL uniqueResolution; + BOOL uniqueResolution = TRUE; ResolveVirtualStaticMethodFlags flags = ResolveVirtualStaticMethodFlags::AllowVariantMatches | ResolveVirtualStaticMethodFlags::InstantiateResultOverFinalMethodDesc; @@ -9255,7 +9255,8 @@ MethodTable::TryResolveConstraintMethodApprox( thInterfaceType.GetMethodTable(), pInterfaceMD, flags, - &uniqueResolution); + (pfForceUseRuntimeLookup != NULL ? &uniqueResolution : NULL)); + if (result == NULL || !uniqueResolution) { _ASSERTE(pfForceUseRuntimeLookup != NULL); diff --git a/src/tests/Loader/classloader/StaticVirtualMethods/NegativeTestCases/MethodBodyOnUnrelatedType.ilproj b/src/tests/Loader/classloader/StaticVirtualMethods/NegativeTestCases/MethodBodyOnUnrelatedType.ilproj index 19680b9..397521a 100644 --- a/src/tests/Loader/classloader/StaticVirtualMethods/NegativeTestCases/MethodBodyOnUnrelatedType.ilproj +++ b/src/tests/Loader/classloader/StaticVirtualMethods/NegativeTestCases/MethodBodyOnUnrelatedType.ilproj @@ -1,6 +1,9 @@ Exe + + + false Full diff --git a/src/tests/Loader/classloader/StaticVirtualMethods/Regression/GitHub_70385.il b/src/tests/Loader/classloader/StaticVirtualMethods/Regression/GitHub_70385.il index 3116af1..12e585a 100644 --- a/src/tests/Loader/classloader/StaticVirtualMethods/Regression/GitHub_70385.il +++ b/src/tests/Loader/classloader/StaticVirtualMethods/Regression/GitHub_70385.il @@ -15,7 +15,7 @@ .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....: .ver 7:0:0:0 } -.assembly RecursiveGeneric +.assembly GitHub_70385 { .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) .custom instance void [System.Runtime]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx @@ -27,7 +27,7 @@ .hash algorithm 0x00008004 .ver 0:0:0:0 } -.module RecursiveGeneric.dll +.module GitHub_70385.dll // MVID: {10541B0F-16D6-4F9A-B0EB-E793F524F163} .imagebase 0x00400000 .file alignment 0x00000200 diff --git a/src/tests/readytorun/tests/main.cs b/src/tests/readytorun/tests/main.cs index 0431486..8201652 100644 --- a/src/tests/readytorun/tests/main.cs +++ b/src/tests/readytorun/tests/main.cs @@ -441,6 +441,19 @@ class Program Assert.AreEqual(ILInliningTest.TestDifferentIntValue(), actualMethodCallResult); } + private class CallDefaultVsExactStaticVirtual where T : IDefaultVsExactStaticVirtual + { + public static string CallMethodOnGenericType() => T.Method(); + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] + static void TestDefaultVsExactStaticVirtualMethodImplementation() + { + Assert.AreEqual(CallDefaultVsExactStaticVirtual.CallMethodOnGenericType(), "DefaultVsExactStaticVirtualMethod"); + // Naively one would expect that the following should do, however Roslyn fails to compile it claiming that the type DVESVC doesn't contain 'Method': + // Assert.AreEqual(DefaultVsExactStaticVirtualClass.Method(), "DefaultVsExactStaticVirtualMethod"); + } + static void RunAllTests() { Console.WriteLine("TestVirtualMethodCalls"); @@ -527,6 +540,10 @@ class Program Console.WriteLine("TestILBodyChange"); TestILBodyChange(); + + Console.WriteLine("TestDefaultVsExactStaticVirtualMethodImplementation"); + TestDefaultVsExactStaticVirtualMethodImplementation(); + ILInliningVersioningTest.RunAllTests(typeof(Program).Assembly); } diff --git a/src/tests/readytorun/tests/test.cs b/src/tests/readytorun/tests/test.cs index 73e4dfb..a24576f 100644 --- a/src/tests/readytorun/tests/test.cs +++ b/src/tests/readytorun/tests/test.cs @@ -438,6 +438,24 @@ static class OpenClosedDelegateExtensionTest } } +public interface IDefaultVsExactStaticVirtual +{ + static virtual string Method() => +#if V2 + "Error - IDefaultVsExactStaticVirtual.Method shouldn't be used in V2" +#else + "DefaultVsExactStaticVirtualMethod" +#endif + ; +} + +public class DefaultVsExactStaticVirtualClass : IDefaultVsExactStaticVirtual +{ +#if V2 + static string IDefaultVsExactStaticVirtual.Method() => "DefaultVsExactStaticVirtualMethod"; +#endif +} + // Test dependent versioning details public class ILInliningVersioningTest { -- 2.7.4