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 codegen for collections expression of single spread of ReadOnlySpan for collection builder emit strategy #73102

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,11 @@ private BoundExpression RewriteCollectionExpressionConversion(Conversion convers
case CollectionExpressionTypeKind.CollectionBuilder:
// If the collection type is ImmutableArray<T>, then construction is optimized to use
// ImmutableCollectionsMarshal.AsImmutableArray.
// The only exception is when collection expression is just `[.. readOnlySpan]` of the same element type, in such cases
// it is more efficient to emit a direct call of `ImmutableArray.Create`
if (ConversionsBase.IsSpanOrListType(_compilation, node.Type, WellKnownType.System_Collections_Immutable_ImmutableArray_T, out var arrayElementType) &&
_compilation.GetWellKnownTypeMember(WellKnownMember.System_Runtime_InteropServices_ImmutableCollectionsMarshal__AsImmutableArray_T) is MethodSymbol asImmutableArray)
_compilation.GetWellKnownTypeMember(WellKnownMember.System_Runtime_InteropServices_ImmutableCollectionsMarshal__AsImmutableArray_T) is MethodSymbol asImmutableArray &&
!IsSingleReadOnlySpanSpread(node, arrayElementType, out _))
{
return VisitImmutableArrayCollectionExpression(node, arrayElementType, asImmutableArray);
}
Expand Down Expand Up @@ -138,6 +141,20 @@ static BoundNode unwrapListElement(BoundCollectionExpression node, BoundNode ele
}
}

private bool IsSingleReadOnlySpanSpread(BoundCollectionExpression node, TypeWithAnnotations elementType, [NotNullWhen(true)] out BoundExpression? spreadExpression)
{
spreadExpression = null;

if (node is { Elements: [BoundCollectionExpressionSpreadElement { Expression: { Type: NamedTypeSymbol { TypeArgumentsWithAnnotationsNoUseSiteDiagnostics: [var spreadElementType] } spreadType } expr }] } &&
spreadType.OriginalDefinition == (object)_compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T) &&
spreadElementType.Equals(elementType, TypeCompareKind.CLRSignatureCompareOptions))
DoctorKrolic marked this conversation as resolved.
Show resolved Hide resolved
{
spreadExpression = expr;
}

return spreadExpression is not null;
}

private BoundExpression VisitImmutableArrayCollectionExpression(BoundCollectionExpression node, TypeWithAnnotations elementType, MethodSymbol asImmutableArray)
{
var arrayCreation = VisitArrayOrSpanCollectionExpression(
Expand Down Expand Up @@ -376,7 +393,15 @@ private BoundExpression VisitCollectionBuilderCollectionExpression(BoundCollecti
Debug.Assert(spanType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.AllIgnoreOptions));

var elementType = spanType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0];
BoundExpression span = VisitArrayOrSpanCollectionExpression(node, CollectionExpressionTypeKind.ReadOnlySpan, spanType, elementType);

// If collection expression is of form `[.. anotherReadOnlySpan]`
// with `anotherReadOnlySpan` being a ReadOnlySpan of the same type as target collection type
// and that span cannot be captured in a returned ref struct
// we can directly use `anotherReadOnlySpan` as collection builder argument and skip the copying assignment.
var canSkipCopyingArgument = !constructMethod.ReturnType.IsRefLikeType || constructMethod.Parameters[0].EffectiveScope == ScopedKind.ScopedValue;
BoundExpression span = canSkipCopyingArgument && IsSingleReadOnlySpanSpread(node, elementType, out var spreadExpression)
DoctorKrolic marked this conversation as resolved.
Show resolved Hide resolved
? spreadExpression
: VisitArrayOrSpanCollectionExpression(node, CollectionExpressionTypeKind.ReadOnlySpan, spanType, elementType);

var invocation = new BoundCall(
node.Syntax,
Expand Down