Skip to content

Commit

Permalink
Collection expressions: use inline array for ReadOnlySpan<T> argument…
Browse files Browse the repository at this point in the history
… to builder method (#69227)
  • Loading branch information
cston authored Aug 1, 2023
1 parent 5024f3e commit 7183a8a
Show file tree
Hide file tree
Showing 11 changed files with 1,351 additions and 398 deletions.
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ private BoundCollectionExpression BindArrayOrSpanCollectionExpression(
TypeSymbol elementType,
BindingDiagnosticBag diagnostics)
{
var syntax = (CSharpSyntaxNode)node.Syntax;
var syntax = node.Syntax;

switch (collectionTypeKind)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4729,7 +4729,7 @@ private BoundExpression BindCollectionExpression(CollectionExpressionSyntax synt
{
builder.Add(bindElement(element, diagnostics));
}
return new BoundUnconvertedCollectionExpression(syntax, builder.ToImmutableAndFree(), this);
return new BoundUnconvertedCollectionExpression(syntax, builder.ToImmutableAndFree());

BoundExpression bindElement(CollectionElementSyntax syntax, BindingDiagnosticBag diagnostics)
{
Expand Down
1 change: 0 additions & 1 deletion src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1877,7 +1877,6 @@
<Field Name="Type" Type="TypeSymbol?" Override="true" Null="always"/>
<!-- Collection expression elements. -->
<Field Name="Elements" Type="ImmutableArray&lt;BoundExpression&gt;"/>
<Field Name="Binder" Type="Binder" Null="disallow"/>
</Node>

<Node Name="BoundCollectionExpression" Base="BoundExpression">
Expand Down
23 changes: 23 additions & 0 deletions src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1970,6 +1970,29 @@ internal MethodSymbol EnsureInlineArrayAsSpanExists(SyntaxNode syntaxNode, Named
diagnostics);
}

internal NamedTypeSymbol EnsureInlineArrayTypeExists(SyntaxNode syntaxNode, SyntheticBoundNodeFactory factory, int arrayLength, DiagnosticBag diagnostics)
{
Debug.Assert(Compilation.Assembly.RuntimeSupportsInlineArrayTypes);
Debug.Assert(arrayLength > 0);

string typeName = $"<>{(char)GeneratedNameKind.InlineArrayType}__InlineArray{arrayLength}";
var privateImplClass = GetPrivateImplClass(syntaxNode, diagnostics);
var typeAdapter = privateImplClass.GetSynthesizedType(typeName);

if (typeAdapter is null)
{
var attributeConstructor = (MethodSymbol)factory.SpecialMember(SpecialMember.System_Runtime_CompilerServices_InlineArrayAttribute__ctor);
Debug.Assert(attributeConstructor is { });

var typeSymbol = new SynthesizedInlineArrayTypeSymbol(SourceModule, typeName, arrayLength, attributeConstructor);
privateImplClass.TryAddSynthesizedType(typeSymbol.GetCciAdapter());
typeAdapter = privateImplClass.GetSynthesizedType(typeName)!;
}

Debug.Assert(typeAdapter.Name == typeName);
return (NamedTypeSymbol)typeAdapter.GetInternalSymbol()!;
}

internal MethodSymbol EnsureInlineArrayAsReadOnlySpanExists(SyntaxNode syntaxNode, NamedTypeSymbol spanType, NamedTypeSymbol intType, DiagnosticBag diagnostics)
{
Debug.Assert(intType.SpecialType == SpecialType.System_Int32);
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,35 @@ internal sealed partial class LocalRewriter
Debug.Assert(!_inExpressionLambda);
Debug.Assert(node.Type is { });

var collectionTypeKind = ConversionsBase.GetCollectionExpressionTypeKind(_compilation, node.Type, out var elementType);
switch (collectionTypeKind)
var previousSyntax = _factory.Syntax;
_factory.Syntax = node.Syntax;
try
{
case CollectionExpressionTypeKind.CollectionInitializer:
return VisitCollectionInitializerCollectionExpression(node, node.Type);
case CollectionExpressionTypeKind.Array:
case CollectionExpressionTypeKind.Span:
case CollectionExpressionTypeKind.ReadOnlySpan:
Debug.Assert(elementType is { });
return VisitArrayOrSpanCollectionExpression(node, node.Type, elementType);
case CollectionExpressionTypeKind.CollectionBuilder:
return VisitCollectionBuilderCollectionExpression(node);
case CollectionExpressionTypeKind.ListInterface:
return VisitListInterfaceCollectionExpression(node);
default:
throw ExceptionUtilities.UnexpectedValue(collectionTypeKind);
var collectionTypeKind = ConversionsBase.GetCollectionExpressionTypeKind(_compilation, node.Type, out var elementType);
switch (collectionTypeKind)
{
case CollectionExpressionTypeKind.CollectionInitializer:
return VisitCollectionInitializerCollectionExpression(node, node.Type);
case CollectionExpressionTypeKind.Array:
case CollectionExpressionTypeKind.Span:
case CollectionExpressionTypeKind.ReadOnlySpan:
Debug.Assert(elementType is { });
return VisitArrayOrSpanCollectionExpression(node, node.Type, TypeWithAnnotations.Create(elementType));
case CollectionExpressionTypeKind.CollectionBuilder:
return VisitCollectionBuilderCollectionExpression(node);
case CollectionExpressionTypeKind.ListInterface:
return VisitListInterfaceCollectionExpression(node);
default:
throw ExceptionUtilities.UnexpectedValue(collectionTypeKind);
}
}
finally
{
_factory.Syntax = previousSyntax;
}
}

private BoundExpression VisitArrayOrSpanCollectionExpression(BoundCollectionExpression node, TypeSymbol collectionType, TypeSymbol elementType)
private BoundExpression VisitArrayOrSpanCollectionExpression(BoundCollectionExpression node, TypeSymbol collectionType, TypeWithAnnotations elementType)
{
Debug.Assert(!_inExpressionLambda);

Expand All @@ -50,8 +59,8 @@ private BoundExpression VisitArrayOrSpanCollectionExpression(BoundCollectionExpr
Debug.Assert(collectionType.Name is "Span" or "ReadOnlySpan");
// We're constructing a Span<T> or ReadOnlySpan<T> rather than T[].
var spanType = (NamedTypeSymbol)collectionType;
Debug.Assert(elementType.Equals(spanType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type, TypeCompareKind.AllIgnoreOptions));
arrayType = ArrayTypeSymbol.CreateSZArray(_compilation.Assembly, TypeWithAnnotations.Create(elementType));
Debug.Assert(elementType.Equals(spanType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0], TypeCompareKind.AllIgnoreOptions));
arrayType = ArrayTypeSymbol.CreateSZArray(_compilation.Assembly, elementType);
spanConstructor = ((MethodSymbol)_compilation.GetWellKnownTypeMember(
collectionType.Name == "Span" ? WellKnownMember.System_Span_T__ctor_Array : WellKnownMember.System_ReadOnlySpan_T__ctor_Array)!).AsMember(spanType);
}
Expand All @@ -64,7 +73,7 @@ private BoundExpression VisitArrayOrSpanCollectionExpression(BoundCollectionExpr
// The array initializer includes at least one spread element, so we'll create an intermediate List<T> instance.
// https://github.com/dotnet/roslyn/issues/68785: Avoid intermediate List<T> if all spread elements have Length property.
// https://github.com/dotnet/roslyn/issues/68785: Emit Enumerable.TryGetNonEnumeratedCount() and avoid intermediate List<T> at runtime.
var listType = _compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_List_T).Construct(elementType);
var listType = _compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_List_T).Construct(ImmutableArray.Create(elementType));
var listToArray = ((MethodSymbol)_compilation.GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_List_T__ToArray)!).AsMember(listType);
var list = VisitCollectionInitializerCollectionExpression(node, collectionType);
array = _factory.Call(list, listToArray);
Expand Down Expand Up @@ -159,16 +168,35 @@ private BoundExpression VisitCollectionBuilderCollectionExpression(BoundCollecti
Debug.Assert(!_inExpressionLambda);
Debug.Assert(node.Type is { });

var syntax = node.Syntax;
var elements = node.Elements;
var constructMethod = node.CollectionBuilderMethod;

Debug.Assert(constructMethod is { });
Debug.Assert(constructMethod.ReturnType.Equals(node.Type, TypeCompareKind.AllIgnoreOptions));

var spanType = (NamedTypeSymbol)constructMethod.Parameters[0].Type;
Debug.Assert(spanType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.AllIgnoreOptions));

var span = VisitArrayOrSpanCollectionExpression(node, spanType, spanType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type);
return new BoundCall(
node.Syntax,
var elementType = spanType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0];
var locals = ArrayBuilder<LocalSymbol>.GetInstance();
var sideEffects = ArrayBuilder<BoundExpression>.GetInstance();
BoundExpression span;

if (elements.Length > 0
&& !elements.Any(i => i is BoundCollectionExpressionSpreadElement)
&& _compilation.Assembly.RuntimeSupportsInlineArrayTypes
&& (!constructMethod.ReturnType.IsRefLikeType || constructMethod.Parameters[0].EffectiveScope == ScopedKind.ScopedValue))
{
span = CreateAndPopulateInlineArray(syntax, elementType, elements, locals, sideEffects);
}
else
{
span = VisitArrayOrSpanCollectionExpression(node, spanType, elementType);
}

var call = new BoundCall(
syntax,
receiverOpt: null,
method: constructMethod,
arguments: ImmutableArray.Create(span),
Expand All @@ -181,6 +209,64 @@ private BoundExpression VisitCollectionBuilderCollectionExpression(BoundCollecti
defaultArguments: default,
resultKind: LookupResultKind.Viable,
type: constructMethod.ReturnType);

return new BoundSequence(
syntax,
locals.ToImmutableAndFree(),
sideEffects.ToImmutableAndFree(),
call,
call.Type);
}

private BoundExpression CreateAndPopulateInlineArray(
SyntaxNode syntax,
TypeWithAnnotations elementType,
ImmutableArray<BoundExpression> elements,
ArrayBuilder<LocalSymbol> locals,
ArrayBuilder<BoundExpression> sideEffects)
{
Debug.Assert(elements.Length > 0);
Debug.Assert(_factory.ModuleBuilderOpt is { });
Debug.Assert(_diagnostics.DiagnosticBag is { });
Debug.Assert(_compilation.Assembly.RuntimeSupportsInlineArrayTypes);

int arrayLength = elements.Length;
var inlineArrayType = _factory.ModuleBuilderOpt.EnsureInlineArrayTypeExists(syntax, _factory, arrayLength, _diagnostics.DiagnosticBag).Construct(ImmutableArray.Create(elementType));
Debug.Assert(inlineArrayType.HasInlineArrayAttribute(out int inlineArrayLength) && inlineArrayLength == arrayLength);

var intType = _factory.SpecialType(SpecialType.System_Int32);
MethodSymbol elementRef = _factory.ModuleBuilderOpt.EnsureInlineArrayElementRefExists(syntax, intType, _diagnostics.DiagnosticBag).
Construct(ImmutableArray.Create(TypeWithAnnotations.Create(inlineArrayType), elementType));

// Create an inline array and assign to a local.
// var tmp = new <>y__InlineArrayN<ElementType>();
BoundAssignmentOperator assignmentToTemp;
BoundLocal inlineArrayLocal = _factory.StoreToTemp(new BoundDefaultExpression(syntax, inlineArrayType), out assignmentToTemp, isKnownToReferToTempIfReferenceType: true);
sideEffects.Add(assignmentToTemp);
locals.Add(inlineArrayLocal.LocalSymbol);

// Populate the inline array.
// InlineArrayElementRef<<>y__InlineArrayN<ElementType>, ElementType>(ref tmp, 0) = element0;
// InlineArrayElementRef<<>y__InlineArrayN<ElementType>, ElementType>(ref tmp, 1) = element1;
// ...
for (int i = 0; i < arrayLength; i++)
{
var element = VisitExpression(elements[i]);
var call = _factory.Call(null, elementRef, inlineArrayLocal, _factory.Literal(i), useStrictArgumentRefKinds: true);
var assignment = new BoundAssignmentOperator(syntax, call, element, type: call.Type) { WasCompilerGenerated = true };
sideEffects.Add(assignment);
}

// Get a span to the inline array.
// ... InlineArrayAsReadOnlySpan<<>y__InlineArrayN<ElementType>, ElementType>(in tmp, N)
var inlineArrayAsReadOnlySpan = _factory.ModuleBuilderOpt.EnsureInlineArrayAsReadOnlySpanExists(syntax, _factory.WellKnownType(WellKnownType.System_ReadOnlySpan_T), intType, _diagnostics.DiagnosticBag).
Construct(ImmutableArray.Create(TypeWithAnnotations.Create(inlineArrayType), elementType));
return _factory.Call(
receiver: null,
inlineArrayAsReadOnlySpan,
inlineArrayLocal,
_factory.Literal(arrayLength),
useStrictArgumentRefKinds: true);
}

private BoundExpression MakeCollectionExpressionSpreadElement(BoundCollectionExpressionSpreadElement initializer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ internal enum GeneratedNameKind
DynamicCallSiteField = 'p',
AsyncIteratorPromiseOfValueOrEndBackingField = 'v',
DisposeModeField = 'w',
CombinedTokensField = 'x', // last
CombinedTokensField = 'x',
InlineArrayType = 'y', // last

// Deprecated - emitted by Dev12, but not by Roslyn.
// Don't reuse the values because the debugger might encounter them when consuming old binaries.
Expand Down
Loading

0 comments on commit 7183a8a

Please sign in to comment.