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?)
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
_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 =>
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);
// 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;
}
}
}
_compilation.DetectGenericCycles(_canonMethod, method);
}
- return _factory.MethodEntrypoint(method);
+ return _factory.MethodEntrypointOrTentativeMethod(method);
}
private void ImportCall(ILOpcode opcode, int token)
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
_compilation.DetectGenericCycles(methodIL.OwningMethod, method);
}
- return _compilation.NodeFactory.MethodEntrypoint(method, isUnboxingThunk);
+ return _compilation.NodeFactory.MethodEntrypointOrTentativeMethod(method, isUnboxingThunk);
}
private static bool IsTypeSpecForTypicalInstantiation(TypeDesc t)
// 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;
}
}
}