Generate fewer TentativeInstanceMethod nodes (#80414)
authorMichal Strehovský <MichalStrehovsky@users.noreply.github.com>
Tue, 10 Jan 2023 07:48:18 +0000 (16:48 +0900)
committerGitHub <noreply@github.com>
Tue, 10 Jan 2023 07:48:18 +0000 (16:48 +0900)
In order for the compiler to be able to eliminate bodies of instance methods on types that were never allocated, the compiler has a concept of "tentative instance methods".

E.g.

```csharp
Foo f = null;
f?.DoSomething();

class Foo
{
    public void DoSomething() { expensive stuff }
}
```

When compiling `Main()` instead of generating code that refers to the entrypoint of `DoSomething` (that we would then have to compile), we generate it as a reference to a "tentative method". This tentative method will either turn into a throw helper, or the real instance method. This depends on whether the owning type of the method was seen as allocated - the tentative method conditionally depends on the real body if the type was allocated.

Before this PR, tentative methods were dispensed from `MethodEntrypoint` method on `NodeFactory`. I.e. anyone asking for a method entrypoint could potentially get a tentative entrypoint. But this also means that places that refer to method bodies and already know the owning type is allocated still introduce an unnecessary tentative entrypoint node. They should refer to the entrypoint directly. This pull request does that. One now needs to explicitly ask for a tentative entrypoint - `MethodEntrypoint`. We actually only have two places that really need to do it.

This is pretty much a zero diff change - we only change bookkeeping within the compiler.

I saw one difference in WebApi - references to finalizers from unconstructed MethodTables now bring the real method. We can fix that multiple ways. I couldn't decide the best one, so I left it out for now (should we ask for a tentative entrypoint? should we fill it out as zero? should we say unconstructed types don't have finalizers and skip it?)

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ILScanNodeFactory.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MultiFileCompilationModuleGroup.cs
src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs
src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/RyuJitNodeFactory.cs
src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs
src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/TestInfraMultiFileCompilationModuleGroup.cs

index c17647d070a4825ef5d52c95f238b408f62fc10a..90e29d20bcc2d4c91302c425657fcf00d3f003ea 100644 (file)
@@ -42,10 +42,6 @@ namespace ILCompiler.DependencyAnalysis
 
             if (CompilationModuleGroup.ContainsMethodBody(method, false))
             {
-                // We might be able to optimize the method body away if the owning type was never seen as allocated.
-                if (method.NotCallableWithoutOwningEEType() && CompilationModuleGroup.AllowInstanceMethodOptimization(method))
-                    return new TentativeInstanceMethodNode(new ScannedMethodNode(method));
-
                 return new ScannedMethodNode(method);
             }
             else
index be70251c991d67dd28b30d85888ccaf7fd3cd0d8..df4367fde9701565c7dd372c670ad7b315ae8d38 100644 (file)
@@ -255,6 +255,11 @@ namespace ILCompiler.DependencyAnalysis
 
             _methodEntrypoints = new MethodEntrypointHashtable(this);
 
+            _tentativeMethods = new NodeCache<MethodDesc, TentativeInstanceMethodNode>(method =>
+            {
+                return new TentativeInstanceMethodNode((IMethodBodyNode)MethodEntrypoint(method));
+            });
+
             _unboxingStubs = new NodeCache<MethodDesc, IMethodNode>(CreateUnboxingStubNode);
 
             _methodAssociatedData = new NodeCache<IMethodNode, MethodAssociatedDataNode>(methodNode =>
@@ -839,6 +844,19 @@ namespace ILCompiler.DependencyAnalysis
             return _methodEntrypoints.GetOrCreateValue(method);
         }
 
+        private NodeCache<MethodDesc, TentativeInstanceMethodNode> _tentativeMethods;
+        public IMethodNode MethodEntrypointOrTentativeMethod(MethodDesc method, bool unboxingStub = false)
+        {
+            // We might be able to optimize the method body away if the owning type was never seen as allocated.
+            if (method.NotCallableWithoutOwningEEType() && CompilationModuleGroup.AllowInstanceMethodOptimization(method))
+            {
+                Debug.Assert(!unboxingStub);
+                return _tentativeMethods.GetOrAdd(method);
+            }
+
+            return MethodEntrypoint(method, unboxingStub);
+        }
+
         public MethodAssociatedDataNode MethodAssociatedData(IMethodNode methodNode)
         {
             return _methodAssociatedData.GetOrAdd(methodNode);
index 05888aebfef83ed2ffadf15020bb80d49c317d76..80b32320c38894640d6e7754aad3b8d5d70f09f7 100644 (file)
@@ -119,9 +119,12 @@ namespace ILCompiler
             // Both the instance methods and the owning type are homed in a single compilation group
             // so if we're able to generate the body, we would also generate the owning type here
             // and nowhere else.
-            Debug.Assert(ContainsMethodBody(method, unboxingStub: false));
-            TypeDesc owningType = method.OwningType;
-            return owningType.IsDefType && !owningType.HasInstantiation && !method.HasInstantiation;
+            if (ContainsMethodBody(method, unboxingStub: false))
+            {
+                TypeDesc owningType = method.OwningType;
+                return owningType.IsDefType && !owningType.HasInstantiation && !method.HasInstantiation;
+            }
+            return false;
         }
     }
 }
index d1946c3f5bfb18701e2e19436e57b47b0673a015..1c5c06d2c3cb8347fb8e04096aad94686c54c8b7 100644 (file)
@@ -247,7 +247,7 @@ namespace Internal.IL
                 _compilation.DetectGenericCycles(_canonMethod, method);
             }
 
-            return _factory.MethodEntrypoint(method);
+            return _factory.MethodEntrypointOrTentativeMethod(method);
         }
 
         private void ImportCall(ILOpcode opcode, int token)
index 45060d7ffa313e1a5812ffc61987a065b2e633f3..a51508005f53624b6a63a4c1d68f2dfbd4b7dac5 100644 (file)
@@ -43,10 +43,6 @@ namespace ILCompiler.DependencyAnalysis
 
             if (CompilationModuleGroup.ContainsMethodBody(method, false))
             {
-                // We might be able to optimize the method body away if the owning type was never seen as allocated.
-                if (method.NotCallableWithoutOwningEEType() && CompilationModuleGroup.AllowInstanceMethodOptimization(method))
-                    return new TentativeInstanceMethodNode(new MethodCodeNode(method));
-
                 return new MethodCodeNode(method);
             }
             else
index 5cda6a6ce538624e1300ba6aae81e93668a2840f..895d9cc38d74466a03262ac4dd620174a4084d51 100644 (file)
@@ -1139,7 +1139,7 @@ namespace Internal.JitInterface
                 _compilation.DetectGenericCycles(methodIL.OwningMethod, method);
             }
 
-            return _compilation.NodeFactory.MethodEntrypoint(method, isUnboxingThunk);
+            return _compilation.NodeFactory.MethodEntrypointOrTentativeMethod(method, isUnboxingThunk);
         }
 
         private static bool IsTypeSpecForTypicalInstantiation(TypeDesc t)
index e774cf7c50beba6483dba1ad2c0e254de0cd2e21..94d34de0ba588690acffe7a0d33791d01daabc78 100644 (file)
@@ -41,9 +41,11 @@ namespace Mono.Linker.Tests.TestCasesRunner
                        // Both the instance methods and the owning type are homed in a single compilation group
                        // so if we're able to generate the body, we would also generate the owning type here
                        // and nowhere else.
-                       Debug.Assert (ContainsMethodBody (method, unboxingStub: false));
-                       TypeDesc owningType = method.OwningType;
-                       return owningType.IsDefType && !owningType.HasInstantiation && !method.HasInstantiation;
+                       if (ContainsMethodBody (method, unboxingStub: false)) {
+                               TypeDesc owningType = method.OwningType;
+                               return owningType.IsDefType && !owningType.HasInstantiation && !method.HasInstantiation;
+                       }
+                       return false;
                }
        }
 }