Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle MakeGeneric APIs in dataflow #99037

Merged
merged 6 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ public static TypeDesc ReplaceTypesInConstructionOfType(this TypeDesc type, Type
if (type.HasInstantiation)
{
TypeDesc[] newInstantiation = null;
Debug.Assert(type is InstantiatedType);
int instantiationIndex = 0;
for (; instantiationIndex < type.Instantiation.Length; instantiationIndex++)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class ReflectionMarker
public NodeFactory Factory { get; }
public FlowAnnotations Annotations { get; }
public DependencyList Dependencies { get => _dependencies; }
public List<INodeWithRuntimeDeterminedDependencies> RuntimeDeterminedDependencies { get; } = new List<INodeWithRuntimeDeterminedDependencies>();

internal enum AccessKind
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection.Metadata;
using ILCompiler.DependencyAnalysis;
using ILCompiler.DependencyAnalysisFramework;
using ILCompiler.Logging;
using ILLink.Shared;
using ILLink.Shared.TrimAnalysis;
Expand Down Expand Up @@ -121,12 +124,13 @@ protected override void Scan(MethodIL methodBody, ref InterproceduralState inter
}
}

public static DependencyList ScanAndProcessReturnValue(NodeFactory factory, FlowAnnotations annotations, Logger logger, MethodIL methodBody)
public static DependencyList ScanAndProcessReturnValue(NodeFactory factory, FlowAnnotations annotations, Logger logger, MethodIL methodBody, out List<INodeWithRuntimeDeterminedDependencies> runtimeDependencies)
{
var scanner = new ReflectionMethodBodyScanner(factory, annotations, logger, new MessageOrigin(methodBody.OwningMethod));

scanner.InterproceduralScan(methodBody);

runtimeDependencies = scanner._reflectionMarker.RuntimeDeterminedDependencies;
return scanner._reflectionMarker.Dependencies;
}

Expand Down Expand Up @@ -357,8 +361,6 @@ public static bool HandleCall(
case IntrinsicId.Type_GetConstructor:
case IntrinsicId.MethodBase_GetMethodFromHandle:
case IntrinsicId.MethodBase_get_MethodHandle:
case IntrinsicId.Type_MakeGenericType:
case IntrinsicId.MethodInfo_MakeGenericMethod:
case IntrinsicId.Expression_Call:
case IntrinsicId.Expression_New:
case IntrinsicId.Type_GetType:
Expand All @@ -371,18 +373,124 @@ public static bool HandleCall(
case IntrinsicId.AppDomain_CreateInstanceFromAndUnwrap:
case IntrinsicId.Assembly_CreateInstance:
{
bool result = handleCallAction.Invoke(calledMethod, instanceValue, argumentValues, intrinsicId, out methodReturnValue);
return handleCallAction.Invoke(calledMethod, instanceValue, argumentValues, intrinsicId, out methodReturnValue);
}

// Special case some intrinsics for AOT handling (on top of the trimming handling done in the HandleCallAction)
switch (intrinsicId)
case IntrinsicId.Type_MakeGenericType:
{
bool triggersWarning = false;

if (!instanceValue.IsEmpty() && !argumentValues[0].IsEmpty())
{
case IntrinsicId.Type_MakeGenericType:
case IntrinsicId.MethodInfo_MakeGenericMethod:
CheckAndReportRequires(diagnosticContext, calledMethod, DiagnosticUtilities.RequiresDynamicCodeAttribute);
break;
foreach (var value in instanceValue.AsEnumerable())
{
if (value is SystemTypeValue typeValue)
{
TypeDesc typeInstantiated = typeValue.RepresentedType.Type;
if (!typeInstantiated.IsGenericDefinition)
{
// Nothing to do, will fail at runtime
}
else if (TryGetMakeGenericInstantiation(callingMethodDefinition, argumentValues[0], out Instantiation inst, out bool isExact))
{
if (inst.Length == typeInstantiated.Instantiation.Length)
{
typeInstantiated = ((MetadataType)typeInstantiated).MakeInstantiatedType(inst);

if (isExact)
{
reflectionMarker.MarkType(diagnosticContext.Origin, typeInstantiated, "MakeGenericType");
}
else
{
reflectionMarker.RuntimeDeterminedDependencies.Add(new MakeGenericTypeSite(typeInstantiated));
}
}
}
else
{
triggersWarning = true;
}

}
else if (value == NullValue.Instance)
{
// Nothing to do
}
else
{
// We don't know what type the `MakeGenericType` was called on
triggersWarning = true;
}
}
}

if (triggersWarning)
{
CheckAndReportRequires(diagnosticContext, calledMethod, DiagnosticUtilities.RequiresDynamicCodeAttribute);
}

// This intrinsic is relevant to both trimming and AOT - call into trimming logic as well.
return handleCallAction.Invoke(calledMethod, instanceValue, argumentValues, intrinsicId, out methodReturnValue);
}

case IntrinsicId.MethodInfo_MakeGenericMethod:
{
bool triggersWarning = false;

if (!instanceValue.IsEmpty())
{
foreach (var methodValue in instanceValue.AsEnumerable())
{
if (methodValue is SystemReflectionMethodBaseValue methodBaseValue)
{
MethodDesc methodInstantiated = methodBaseValue.RepresentedMethod.Method;
if (!methodInstantiated.IsGenericMethodDefinition)
{
// Nothing to do, will fail at runtime
}
else if (!methodInstantiated.OwningType.IsGenericDefinition
&& TryGetMakeGenericInstantiation(callingMethodDefinition, argumentValues[0], out Instantiation inst, out bool isExact))
{
if (inst.Length == methodInstantiated.Instantiation.Length)
{
methodInstantiated = methodInstantiated.MakeInstantiatedMethod(inst);

if (isExact)
{
reflectionMarker.MarkMethod(diagnosticContext.Origin, methodInstantiated, "MakeGenericMethod");
}
else
{
reflectionMarker.RuntimeDeterminedDependencies.Add(new MakeGenericMethodSite(methodInstantiated));
}
}
}
else
{
// If the owning type is a generic definition, we can't help much.
triggersWarning = true;
}
}
else if (methodValue == NullValue.Instance)
{
// Nothing to do
}
else
{
// We don't know what method the `MakeGenericMethod` was called on
triggersWarning = true;
}
}
}

return result;
if (triggersWarning)
{
CheckAndReportRequires(diagnosticContext, calledMethod, DiagnosticUtilities.RequiresDynamicCodeAttribute);
}

// This intrinsic is relevant to both trimming and AOT - call into trimming logic as well.
return handleCallAction.Invoke(calledMethod, instanceValue, argumentValues, intrinsicId, out methodReturnValue);
}

case IntrinsicId.None:
Expand Down Expand Up @@ -686,6 +794,105 @@ void AddReturnValue(MultiValue value)
}
}

private static bool TryGetMakeGenericInstantiation(
MethodDesc contextMethod,
in MultiValue genericParametersArray,
out Instantiation inst,
out bool isExact)
{
// We support calling MakeGeneric APIs with a very concrete instantiation array.
// Only the form of `new Type[] { typeof(Foo), typeof(T), typeof(Foo<T>) }` is supported.

inst = default;
isExact = true;
Debug.Assert(contextMethod.GetTypicalMethodDefinition() == contextMethod);

var typesValue = genericParametersArray.AsSingleValue();
if (typesValue is NullValue)
{
// This will fail at runtime but no warning needed
inst = Instantiation.Empty;
return true;
}

// Is this an array we model?
if (typesValue is not ArrayValue array)
{
return false;
}

int? size = array.Size.AsConstInt();
if (size == null)
{
return false;
}

TypeDesc[]? sigInst = null;
TypeDesc[]? defInst = null;

ArrayBuilder<TypeDesc> result = default;
for (int i = 0; i < size.Value; i++)
{
// Go over each element of the array. If the value is unknown, bail.
if (!array.TryGetValueByIndex(i, out MultiValue value))
{
return false;
}

var singleValue = value.AsSingleValue();

TypeDesc? type = singleValue switch
{
SystemTypeValue systemType => systemType.RepresentedType.Type,
GenericParameterValue genericParamType => genericParamType.GenericParameter.GenericParameter,
NullableSystemTypeValue nullableSystemType => nullableSystemType.NullableType.Type,
_ => null
};

if (type is null)
{
return false;
}

// type is now some type.
// Because dataflow analysis oddly operates on method bodies instantiated over
// generic parameters (as opposed to instantiated over signature variables)
// We need to swap generic parameters (T, U,...) for signature variables (!0, !!1,...).
sbomer marked this conversation as resolved.
Show resolved Hide resolved
// We need to do this for both generic parameters of the owning type, and generic
// parameters of the owning method.
if (type.ContainsSignatureVariables(treatGenericParameterLikeSignatureVariable: true))
{
if (sigInst == null)
{
TypeDesc contextType = contextMethod.OwningType;
sigInst = new TypeDesc[contextType.Instantiation.Length + contextMethod.Instantiation.Length];
defInst = new TypeDesc[contextType.Instantiation.Length + contextMethod.Instantiation.Length];
TypeSystemContext context = type.Context;
for (int j = 0; j < contextType.Instantiation.Length; j++)
{
sigInst[j] = context.GetSignatureVariable(j, method: false);
defInst[j] = contextType.Instantiation[j];
}
for (int j = 0; j < contextMethod.Instantiation.Length; j++)
{
sigInst[j + contextType.Instantiation.Length] = context.GetSignatureVariable(j, method: true);
defInst[j + contextType.Instantiation.Length] = contextMethod.Instantiation[j];
}
}

isExact = false;

// defInst is [T, U, V], sigInst is `[!0, !!0, !!1]`.
type = type.ReplaceTypesInConstructionOfType(defInst, sigInst);
}

result.Add(type);
}

inst = new Instantiation(result.ToArray());
return true;
}

private static bool IsAotUnsafeDelegate(TypeDesc parameterType)
{
TypeSystemContext context = parameterType.Context;
Expand Down Expand Up @@ -846,5 +1053,33 @@ private static bool IsPInvokeDangerous(MethodDesc calledMethod, out bool comDang

return aotUnsafeDelegate || comDangerousMethod;
}

private sealed class MakeGenericMethodSite : INodeWithRuntimeDeterminedDependencies
{
private readonly MethodDesc _method;

public MakeGenericMethodSite(MethodDesc method) => _method = method;

public IEnumerable<DependencyNodeCore<NodeFactory>.DependencyListEntry> InstantiateDependencies(NodeFactory factory, Instantiation typeInstantiation, Instantiation methodInstantiation)
{
var list = new DependencyList();
RootingHelpers.TryGetDependenciesForReflectedMethod(ref list, factory, _method.InstantiateSignature(typeInstantiation, methodInstantiation), "MakeGenericMethod");
return list;
}
}

private sealed class MakeGenericTypeSite : INodeWithRuntimeDeterminedDependencies
{
private readonly TypeDesc _type;

public MakeGenericTypeSite(TypeDesc type) => _type = type;

public IEnumerable<DependencyNodeCore<NodeFactory>.DependencyListEntry> InstantiateDependencies(NodeFactory factory, Instantiation typeInstantiation, Instantiation methodInstantiation)
{
var list = new DependencyList();
RootingHelpers.TryGetDependenciesForReflectedType(ref list, factory, _type.InstantiateSignature(typeInstantiation, methodInstantiation), "MakeGenericType");
return list;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ namespace ILCompiler.DependencyAnalysis
public class DataflowAnalyzedMethodNode : DependencyNodeCore<NodeFactory>
{
private readonly MethodIL _methodIL;
private List<INodeWithRuntimeDeterminedDependencies> _runtimeDependencies;

public DataflowAnalyzedMethodNode(MethodIL methodIL)
{
Expand All @@ -32,26 +33,51 @@ public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFacto
var mdManager = (UsageBasedMetadataManager)factory.MetadataManager;
try
{
return Dataflow.ReflectionMethodBodyScanner.ScanAndProcessReturnValue(factory, mdManager.FlowAnnotations, mdManager.Logger, _methodIL);
return Dataflow.ReflectionMethodBodyScanner.ScanAndProcessReturnValue(factory, mdManager.FlowAnnotations, mdManager.Logger, _methodIL, out _runtimeDependencies);
}
catch (TypeSystemException)
{
// Something wrong with the input - missing references, etc.
// The method body likely won't compile either, so we don't care.
_runtimeDependencies = new List<INodeWithRuntimeDeterminedDependencies>();
return Array.Empty<DependencyListEntry>();
}
}

public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependencies(List<DependencyNodeCore<NodeFactory>> markedNodes, int firstNode, NodeFactory factory)
{
MethodDesc analyzedMethod = _methodIL.OwningMethod;

// Look for any generic specialization of this method. If any are found, specialize any dataflow dependencies.
for (int i = firstNode; i < markedNodes.Count; i++)
{
if (markedNodes[i] is not IMethodBodyNode methodBody)
continue;

MethodDesc method = methodBody.Method;
if (method.GetTypicalMethodDefinition() != analyzedMethod)
continue;

// Instantiate all runtime dependencies for the found generic specialization
foreach (var n in _runtimeDependencies)
{
foreach (var d in n.InstantiateDependencies(factory, method.OwningType.Instantiation, method.Instantiation))
{
yield return new CombinedDependencyListEntry(d.Node, null, d.Reason);
}
}
}
}

protected override string GetName(NodeFactory factory)
{
return "Dataflow analysis for " + _methodIL.OwningMethod.ToString();
}

public override bool InterestingForDynamicDependencyAnalysis => false;
public override bool HasDynamicDependencies => false;
public override bool HasDynamicDependencies => _runtimeDependencies.Count > 0;
public override bool HasConditionalStaticDependencies => false;
public override bool StaticDependenciesAreComputed => true;
public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory context) => null;
public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependencies(List<DependencyNodeCore<NodeFactory>> markedNodes, int firstNode, NodeFactory context) => null;
}
}
Loading
Loading