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

Optimize single spread collection expression for List<T> #74769

Merged
merged 21 commits into from
Sep 3, 2024
Merged
Changes from 1 commit
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 @@ -166,40 +166,48 @@ private bool TryRewriteSingleElementSpreadToList(BoundCollectionExpression node,
return false;
}

if (!ShouldUseAddRangeOrToListMethod(singleSpread.Expression.Type, singleSpread.EnumeratorInfoOpt?.GetEnumeratorInfo.Method, listElementType))
{
return false;
}

if (TryGetWellKnownTypeMember(node.Syntax, WellKnownMember.System_Linq_Enumerable__ToList, out MethodSymbol? toListGeneric, isOptional: true))
{
var toListOfElementType = toListGeneric.Construct([listElementType]);
result = _factory.Call(receiver: null, toListOfElementType, singleSpread.Expression);
return true;
}

return false;
}

private bool ShouldUseAddRangeOrToListMethod(TypeSymbol? spreadType, MethodSymbol? getEnumeratorMethod, TypeWithAnnotations elementType)
{
Debug.Assert(spreadType is not null);
DoctorKrolic marked this conversation as resolved.
Show resolved Hide resolved

var iEnumerableOfTType = _compilation.GetSpecialType(SpecialType.System_Collections_Generic_IEnumerable_T);
var iEnumerableOfElementType = iEnumerableOfTType.Construct([listElementType]);
var iEnumerableOfElementType = iEnumerableOfTType.Construct([elementType]);

var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;

var singleSpreadExpression = singleSpread.Expression;
var conversion = _compilation.Conversions.ClassifyImplicitConversionFromExpression(singleSpreadExpression, iEnumerableOfElementType, ref discardedUseSiteInfo);
var conversion = _compilation.Conversions.ClassifyImplicitConversionFromType(spreadType, iEnumerableOfElementType, ref discardedUseSiteInfo);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we maybe use the actual use site info for thisand report it if we take the optimzal code path an the end? Unline the ICollection<T> check, the resulting conversion is kinda used in the final codegn, although implicitly

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the original code for the AddRange case was reporting the use-site diagnostic, but I think it's fine to drop it. The code we generate does not actually use this type in any way, it's just a hint for us to decide if AddRange/ToList is likely to be efficient.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var conversion = _compilation.Conversions.ClassifyImplicitConversionFromType(spreadType, iEnumerableOfElementType, ref discardedUseSiteInfo);

Let's use the code from tryOptimizeSpreadElement which checks for ICollection<T>, then checks the conversion to addRangeMethod.Parameters[0].Type rather than the conversion to IEnumerable<T> directly. But perhaps we should use ClassifyImplicitConversionFromType() rather than ClassifyConversionFromType() for the parameter conversion check. Also, the comment in the ICollection<T> check from tryOptimizeSpreadElement would be good to keep. Obviously, the references to AddRange should be updated though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, the ICollection<T> check in tryOptimizeSpreadElement used spreadElement.EnumeratorInfo.CollectionType and spreadElement.EnumeratorInfo.ElementType. With this change, we are using spreadElement.Expression.Type and the element type from the target type. Is there a difference between those pairs of types?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a difference between those pairs of types?

I didn't implement logic in the binding layer, that fills in EnumeratorInfoOpt, but from the sanity point of view they should be the same. I preffer avoiding EnumeratorInfoOpt since it can be null. Or maybe we can assume, that at the point of lowering we always have it and drop additional null checks?

if (conversion.Kind is not (ConversionKind.Identity or ConversionKind.ImplicitReference))
{
return false;
}

if (singleSpread.EnumeratorInfoOpt?.GetEnumeratorInfo.Method.ReturnType.IsValueType == true)
if (getEnumeratorMethod?.ReturnType.IsValueType == true)
{
var iCollectionOfTType = _compilation.GetSpecialType(SpecialType.System_Collections_Generic_ICollection_T);
var iCollectionOfElementType = iCollectionOfTType.Construct([listElementType]);
var iCollectionOfElementType = iCollectionOfTType.Construct([elementType]);

var singleSpreadType = singleSpreadExpression.Type;
Debug.Assert(singleSpreadType is not null);

if (!singleSpreadType.ImplementsInterface(iCollectionOfElementType, ref discardedUseSiteInfo))
if (!spreadType.ImplementsInterface(iCollectionOfElementType, ref discardedUseSiteInfo))
DoctorKrolic marked this conversation as resolved.
Show resolved Hide resolved
{
return false;
}
}

if (TryGetWellKnownTypeMember(node.Syntax, WellKnownMember.System_Linq_Enumerable__ToList, out MethodSymbol? toListGeneric, isOptional: true))
{
var toListOfElementType = toListGeneric.Construct([listElementType]);
result = _factory.Call(receiver: null, toListOfElementType, singleSpreadExpression);
return true;
}

return false;
return true;
DoctorKrolic marked this conversation as resolved.
Show resolved Hide resolved
}

private static bool CanOptimizeSingleSpreadAsCollectionBuilderArgument(BoundCollectionExpression node, [NotNullWhen(true)] out BoundExpression? spreadExpression)
Expand Down Expand Up @@ -1118,34 +1126,13 @@ private BoundExpression CreateAndPopulateList(BoundCollectionExpression node, Ty
if (addRangeMethod is null)
return false;

if (spreadElement.EnumeratorInfoOpt is { } enumeratorInfo)
{
var iCollectionOfTType = _compilation.GetSpecialType(SpecialType.System_Collections_Generic_ICollection_T);
var iCollectionOfElementType = iCollectionOfTType.Construct(enumeratorInfo.ElementType);
var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;

// If collection has a struct enumerator but doesn't implement ICollection<T>
// then manual `foreach` is always more efficient then using `AddRange` method
if (enumeratorInfo.GetEnumeratorInfo.Method.ReturnType.IsValueType &&
!enumeratorInfo.CollectionType.ImplementsInterface(iCollectionOfElementType, ref discardedUseSiteInfo))
{
return false;
}
}

var type = rewrittenSpreadOperand.Type!;

var useSiteInfo = GetNewCompoundUseSiteInfo();
var conversion = _compilation.Conversions.ClassifyConversionFromType(type, addRangeMethod.Parameters[0].Type, isChecked: false, ref useSiteInfo);
_diagnostics.Add(rewrittenSpreadOperand.Syntax, useSiteInfo);
if (conversion.IsIdentity || (conversion.IsImplicit && conversion.IsReference))
if (!ShouldUseAddRangeOrToListMethod(rewrittenSpreadOperand.Type, spreadElement.EnumeratorInfoOpt?.GetEnumeratorInfo.Method, elementType))
{
conversion.MarkUnderlyingConversionsCheckedRecursive();
sideEffects.Add(_factory.Call(listTemp, addRangeMethod, rewrittenSpreadOperand));
return true;
return false;
}

return false;
sideEffects.Add(_factory.Call(listTemp, addRangeMethod, rewrittenSpreadOperand));
return true;
});
}

Expand Down
Loading