Propagate DynamicallyAccessedMemberTypes through Nullable<T> to T#2675
Propagate DynamicallyAccessedMemberTypes through Nullable<T> to T#2675jtschuster merged 37 commits intodotnet:mainfrom
Conversation
vitek-karas
left a comment
There was a problem hiding this comment.
Other than the comments (main worry there is handling of generic arguments), this is missing marking changes.
We need to add a test which validates that code like
[StructLayout(LayoutKind.Auto)] // This is important since without this linker will not trim fields, but add a test for methods as well just in case
struct TestS
{
[Kept] // <--- this is the important bit. There's no reference to Field, it should be preserved because of DAM
public int Field;
}
void Test()
{
typeof(Nullable<StructS>).RequirePublicFields(); // Applies DAM PublicFields onto the type, this should propagate to the StructS.
}Note that "marking" is not just a linker concept - in analyzer it means check all members of the "marked" type that they're not RUC (for example). So add tests which check for these warnings as well - those will be valid for both linker and analyzer.
But we do need the "Keep" tests for linker as well.
src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs
Outdated
Show resolved
Hide resolved
| record RuntimeTypeHandleValue : SingleValue | ||
| sealed record RuntimeTypeHandleValue : SingleValue | ||
| { | ||
| public RuntimeTypeHandleValue (in TypeProxy representedType) => RepresentedType = representedType; |
There was a problem hiding this comment.
Maybe add an assert that the type is not Nullable<T>
There was a problem hiding this comment.
When I added an assert I ran into cases where System.Nullable`1 wouldn't have a type argument, so it wouldn't make a NullableValue and would instantiate a SystemTypeValue or RuntimeTypeHandleValue. To me a SystemTypeValue makes sense since there is nothing extra to track in those situations, but would it make more sense to have the UnderlyingType of NullableValue's be optional?
src/ILLink.Shared/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs
Show resolved
Hide resolved
Generates NullableValue's in a few missing places, and tracks NullValue.Instance's in TypeHandle intrinsics.
vitek-karas
left a comment
There was a problem hiding this comment.
I think we need to fix the MakeGenericType as it currently creates an analysis hole which could break apps.
src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs
Outdated
Show resolved
Hide resolved
Also adds ArrayCreationOperation visitors to create ArrayValue's. Also addresses some other PR comments.
| /// </summary> | ||
| sealed record NullableRuntimeTypeWithDamHandleValue : SingleValue | ||
| { | ||
| public NullableRuntimeTypeWithDamHandleValue (in TypeProxy nullableType, in SingleValue underlyingTypeValue) |
There was a problem hiding this comment.
`UnderlyingTypeValue needs to be SingleValue here because in MethodBodyScanner, a RuntimeTypeHandleForGenericParameterValue is passed as the underlying type.
* wip * Fixes * Improve array creation - correctly initialize size and support multi-value for size
src/ILLink.RoslynAnalyzer/TrimAnalysis/SingleValueExtensions.cs
Outdated
Show resolved
Hide resolved
src/ILLink.Shared/TrimAnalysis/NullableRuntimeSystemTypeHandleValue.cs
Outdated
Show resolved
Hide resolved
| namespace ILLink.Shared.DataFlow | ||
| { | ||
| // Adds ability to deep copy a value | ||
| public interface IDeepCopyValue<TSingleValue> |
There was a problem hiding this comment.
I think ideally this would be required by every type used as a value in a lattice, but we could do that in a separate change.
There was a problem hiding this comment.
Yeah - I think it makes sense to require it everywhere - this change does require it on SingleValue. Other values - I would also prefer a different PR>
| namespace ILLink.Shared.TrimAnalysis | ||
| { | ||
| /// <summary> | ||
| /// This represents a Nullable<T> where T is a known SystemTypeValue. |
There was a problem hiding this comment.
If this is true, should we enforce at the type system level that the underlying type is a SystemTypeValue?
There was a problem hiding this comment.
I think I considered that, but Vitek had a reason to leave it as SingleValue. I can't remember exactly why, but having it different makes sure that we handle it separately from SystemTypeValue instead of accidentally matching NullableSystemTypeValue to the SystemTypeValue case.
There was a problem hiding this comment.
I think it's OK to change the underlying type to SystemTypeValue if it's always that.
The NullableSystemTypeValue must be separate from SystemTypeValue - it does act very similarly, but it adds the "this is Nullable flag" to the type.
There was a problem hiding this comment.
Now I remember we also wanted to keep SystemTypeValue sealed to make type checking faster. I think it might be best to leave it as inheriting from SingleValue.
There was a problem hiding this comment.
Sorry - what I meant was, can we make the underlyingType a SystemTypeValue (not that NullableSystemTypeValue should itself be a SystemTypeValue)? I think this is more consistent with how the underlying type of a NullableValueWithDynamicallyAccessedMembers is also a dataflow value, rather than a TypeProxy.
There was a problem hiding this comment.
Same question for RuntimeTypeHandleForNullableSystemTypeValue.
src/ILLink.Shared/TrimAnalysis/NullableRuntimeSystemTypeHandleValue.cs
Outdated
Show resolved
Hide resolved
src/ILLink.Shared/TrimAnalysis/NullableRuntimeTypeHandleWithGenericParameterValue.cs
Outdated
Show resolved
Hide resolved
src/ILLink.Shared/TrimAnalysis/NullableRuntimeTypeHandleWithGenericParameterValue.cs
Outdated
Show resolved
Hide resolved
src/ILLink.Shared/TrimAnalysis/NullableRuntimeTypeHandleWithGenericParameterValue.cs
Show resolved
Hide resolved
Adds tests to validate dataflow in multidimensional arrays, adds a temporary nullable MethodReturnValue variable to differentiate intentional Top values and unassigned return values. Moves the static MultiValueLattice field to the shared ArrayValue definition.
Adds tests to validate dataflow in multidimensional arrays, adds a temporary nullable MethodReturnValue variable to differentiate intentional Top values and unassigned return values. Moves the static MultiValueLattice field to the shared ArrayValue definition.
vitek-karas
left a comment
There was a problem hiding this comment.
Looks good. Let's wait on Sven for another opinion.
sbomer
left a comment
There was a problem hiding this comment.
A few more comments, overall LGTM. Thanks a lot!
| AddReturnValue (new NullableValueWithDynamicallyAccessedMembers (typeValue.RepresentedType, damValue)); | ||
| break; | ||
| // Don't warn on Nullable<T>, it will throw instead | ||
| case NullableSystemTypeValue: |
There was a problem hiding this comment.
nit: maybe put this and NullableValueWithDynamicallyAccessedMembers next to each other and have one fall through - since they aren't tracked for the same reason.
| namespace ILLink.Shared.TrimAnalysis | ||
| { | ||
| /// <summary> | ||
| /// This represents a Nullable<T> where T is a known SystemTypeValue. |
There was a problem hiding this comment.
Sorry - what I meant was, can we make the underlyingType a SystemTypeValue (not that NullableSystemTypeValue should itself be a SystemTypeValue)? I think this is more consistent with how the underlying type of a NullableValueWithDynamicallyAccessedMembers is also a dataflow value, rather than a TypeProxy.
| namespace ILLink.Shared.TrimAnalysis | ||
| { | ||
| /// <summary> | ||
| /// This represents a Nullable<T> where T is a known SystemTypeValue. |
There was a problem hiding this comment.
Same question for RuntimeTypeHandleForNullableSystemTypeValue.
| new GenericParameterValue (gp, _context.Annotations.FlowAnnotations.GetGenericParameterAnnotation (gp))); | ||
|
|
||
| case TypeReference underlyingType: | ||
| return new NullableSystemTypeValue (genericArgumentType, ResolveToTypeDefinition (underlyingType)!); |
There was a problem hiding this comment.
Probably uncommon, but I think it's possible for ResolveToTypeDefinition to fail - maybe this should return an UnknownValue if it resolves to null?
| case ValueWithDynamicallyAccessedMembers damValue: | ||
| AddReturnValue (new NullableValueWithDynamicallyAccessedMembers (typeValue.RepresentedType, damValue)); | ||
| break; | ||
| // Don't warn on Nullable<T>, it will throw instead |
There was a problem hiding this comment.
Same nit here, I would combine these two cases.
|
|
||
| return true; | ||
|
|
||
| void AddReturnValue (MultiValue value) |
There was a problem hiding this comment.
Thanks for making this more consistent with the shared code!
Unless I'm misunderstanding what you're talking about, it looks like #2680 tracks the overall discrepancy between the analyzer and linker for arrays. I'll make a note about multidimensional arrays though. |
…tnet/linker#2675) Add intrinsic support for Nullable.GetUnderlyingType and support for MakeGenericType with Nullables Adds ArrayCreationOperation visitors to create ArrayValue's in the analyzer, and adds start of dataflow analysis for array values. Adds tests to validate dataflow in Arrays. Co-authored-by: vitek-karas <[email protected]> Commit migrated from dotnet/linker@ed8b22a
This PR enables annotations placed on Nullable to propagate through to T in the linker. This does not work in the analyzer yet and will fail in CI until I update the analyzer to work with the changes.
This creates a new InstrinsicId value for
Nullable.GetUnderlyingTypeit creates a new structNullableSystemTypeValuethat inherits fromSystemTypeValue, and tracks theMultiValueof the wrapped type. It also createsNullableRuntimeTypeHandleValuewhich works similarly. In MethodBodyScanner, when previously aRuntimeTypeHandleValuewas created inScanLdtoken, it now will create aNullableRuntimeTypeHandleValuewhen the type of the handle isNullable`1. Then when atypeofis called on that handle, it converts aNullableRuntimeTypeHandleValueto theNullableSystemTypeValue. Finally, ifNullable.GetUnderlyingTypeis called on thatNullableSystemTypeValue, it propagates the annotations of eachValueWithDynamicallyAccessedMembersto the return value, or propagates theSystemTypeValueto the return value.I think this covers all the areas where nullables need to be handled, but I might be missing something.
Fixes #2662