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

Use a simple temp instead of InlineArray1 #73086

Merged
merged 10 commits into from
May 8, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,7 @@ private BoundExpression VisitArrayOrSpanCollectionExpression(BoundCollectionExpr
syntax,
elementType,
elements,
asReadOnlySpan: collectionTypeKind == CollectionExpressionTypeKind.ReadOnlySpan,
_additionalLocals);
asReadOnlySpan: collectionTypeKind == CollectionExpressionTypeKind.ReadOnlySpan);
}

Debug.Assert(IsAllocatingRefStructCollectionExpression(node, collectionTypeKind, elementType.Type, _compilation));
Expand Down Expand Up @@ -243,7 +242,7 @@ private BoundExpression VisitCollectionInitializerCollectionExpression(BoundColl

// Create a temp for the collection.
BoundAssignmentOperator assignmentToTemp;
BoundLocal temp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp, isKnownToReferToTempIfReferenceType: true);
BoundLocal temp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp);
var sideEffects = ArrayBuilder<BoundExpression>.GetInstance(elements.Length + 1);
sideEffects.Add(assignmentToTemp);

Expand Down Expand Up @@ -429,16 +428,33 @@ private static bool ShouldUseInlineArray(BoundCollectionExpressionBase node, CSh
SyntaxNode syntax,
TypeWithAnnotations elementType,
ImmutableArray<BoundNode> elements,
bool asReadOnlySpan,
ArrayBuilder<LocalSymbol> locals)
bool asReadOnlySpan)
{
Debug.Assert(elements.Length > 0);
Debug.Assert(elements.All(e => e is BoundExpression));
Debug.Assert(_factory.ModuleBuilderOpt is { });
Debug.Assert(_diagnostics.DiagnosticBag is { });
Debug.Assert(_compilation.Assembly.RuntimeSupportsInlineArrayTypes);
Debug.Assert(_additionalLocals is { });

int arrayLength = elements.Length;
if (arrayLength == 1
&& _factory.WellKnownMember(asReadOnlySpan
? WellKnownMember.System_ReadOnlySpan_T__ctor_ref_readonly_T
: WellKnownMember.System_Span_T__ctor_ref_T, isOptional: true) is MethodSymbol spanRefConstructor)
{
// Special case: no need to create an InlineArray1 type. Just use a temp of the element type.
var spanType = _factory
.WellKnownType(asReadOnlySpan ? WellKnownType.System_ReadOnlySpan_T : WellKnownType.System_Span_T)
.Construct([elementType]);
var constructor = spanRefConstructor.AsMember(spanType);
var element = VisitExpression((BoundExpression)elements[0]);
var temp = _factory.StoreToTemp(element, out var assignment);
Copy link
Member

Choose a reason for hiding this comment

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

What is the re-use policies on these temps? Basically is there any chance that the temp slot allocated here will be re-used or is it considered a temp that lives for the lifetime of the current method?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think the local slot can be reused outside the containing block, but that's fine, since the span value that references the temp can't escape outside the containing block.

Copy link
Member

Choose a reason for hiding this comment

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

Hmm. Some of our temp are statement level. I agree block level temp is fine but we should be sure which this is. Had other bugs with statement temps being reused in span before

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added a test. It doesn't look like the temp is reused.

Copy link
Member

Choose a reason for hiding this comment

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

Not sure the test is sufficient here. Looking at the code it seems that it's a re-usable temp. The default kind of the temp is SynthesizedLocalKind.LoweringTemp and that is not a long lived temp. The doc mentions these cannot live across a statement boundary

    /// 1) Short-lived (temporary)
    ///    The lifespan of a temporary variable shall not cross a statement boundary (a PDB sequence point).
    ///    These variables are not tracked by EnC and don't have names. Only values less than 0 are considered
    ///    short-lived: new short-lived kinds should have a negative value.

Copy link
Contributor

Choose a reason for hiding this comment

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

What is the re-use policies on these temps?

Slots can be reused for locals that go out of scope. The scope is defined by blocks and sequences that list locals they own. If I remember correctly, sometimes scope of locals is extended by codegen, when, for example, a sequence returns a ref to a local that it owns. One might say the bound tree violates scoping rules in such cases, but for whatever reason a decision was made to handle the case instead of enforcing correctness of the tree.

That said, symbols for temps are never reused by default. One might, of course, intentionally create a bound tree that shares the same temp for different purposes.

I hope that helps with concerns that prompted the original question.

Copy link
Contributor

Choose a reason for hiding this comment

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

Regarding the short-lived/long-lived story. According to my understanding, these are mostly about PDB/ENC, and the statement boundary the comment is talking about is in terms of syntax (perhaps talking in terms of sequence point boundaries would be more accurate), not about bound statement nodes. Reuse of slots in IL is not based on that. It is based on what I said in the previous message on this thread. So as long as the local is added to the right BoundBlock/BoundSequence, it will not be reused while code for that node is emitted.

There is, however, something interesting going on with where we put inline array locals. The line locals.Add(inlineArrayLocal.LocalSymbol); below. According to comments on _additionalLocals field, the local is going to end up on the enclosing method outermost block. Hopefully that is not going to mess with EnC too much because effectively the local crosses a sequence point boundary. "Collection expressions" devs might want to take a close look at this.

Copy link
Member

Choose a reason for hiding this comment

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

Great. Thansk for the explanation!

Copy link
Member

Choose a reason for hiding this comment

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

There is, however, something interesting going on with where we put inline array locals. ...

Logged #73246 based on this comment and offline discussion.

_additionalLocals.Add(temp.LocalSymbol);
333fred marked this conversation as resolved.
Show resolved Hide resolved
var call = _factory.New(constructor, arguments: [temp], argumentRefKinds: [asReadOnlySpan ? RefKindExtensions.StrictIn : RefKind.Ref]);
return _factory.Sequence([assignment], call);
}

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

Expand All @@ -449,10 +465,10 @@ private static bool ShouldUseInlineArray(BoundCollectionExpressionBase node, CSh
// 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);
BoundLocal inlineArrayLocal = _factory.StoreToTemp(new BoundDefaultExpression(syntax, inlineArrayType), out assignmentToTemp);
var sideEffects = ArrayBuilder<BoundExpression>.GetInstance();
sideEffects.Add(assignmentToTemp);
locals.Add(inlineArrayLocal.LocalSymbol);
_additionalLocals.Add(inlineArrayLocal.LocalSymbol);

// Populate the inline array.
// InlineArrayElementRef<<>y__InlineArrayN<ElementType>, ElementType>(ref tmp, 0) = element0;
Expand Down Expand Up @@ -604,8 +620,7 @@ bool tryGetToArrayMethod(TypeSymbol spreadTypeOriginalDefinition, WellKnownType
// int index = 0;
BoundLocal indexTemp = _factory.StoreToTemp(
_factory.Literal(0),
out assignmentToTemp,
isKnownToReferToTempIfReferenceType: true);
out assignmentToTemp);
localsBuilder.Add(indexTemp);
sideEffects.Add(assignmentToTemp);

Expand All @@ -615,8 +630,7 @@ bool tryGetToArrayMethod(TypeSymbol spreadTypeOriginalDefinition, WellKnownType
ImmutableArray.Create(GetKnownLengthExpression(elements, numberIncludingLastSpread, localsBuilder)),
initializerOpt: null,
arrayType),
out assignmentToTemp,
isKnownToReferToTempIfReferenceType: true);
out assignmentToTemp);
localsBuilder.Add(arrayTemp);
sideEffects.Add(assignmentToTemp);

Expand Down Expand Up @@ -903,7 +917,7 @@ private BoundExpression CreateAndPopulateList(BoundCollectionExpression node, Ty
// If we use optimizations, we know the length of the resulting list, and we store it in a temp so we can pass it to List.ctor(int32) and to CollectionsMarshal.SetCount

// int knownLengthTemp = N + s1.Length + ...;
knownLengthTemp = _factory.StoreToTemp(knownLengthExpression, out assignmentToTemp, isKnownToReferToTempIfReferenceType: true);
knownLengthTemp = _factory.StoreToTemp(knownLengthExpression, out assignmentToTemp);
localsBuilder.Add(knownLengthTemp);
sideEffects.Add(assignmentToTemp);

Expand All @@ -924,7 +938,7 @@ private BoundExpression CreateAndPopulateList(BoundCollectionExpression node, Ty
}

// Create a temp for the list.
BoundLocal listTemp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp, isKnownToReferToTempIfReferenceType: true);
BoundLocal listTemp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp);
localsBuilder.Add(listTemp);
sideEffects.Add(assignmentToTemp);

Expand All @@ -940,7 +954,7 @@ private BoundExpression CreateAndPopulateList(BoundCollectionExpression node, Ty
sideEffects.Add(_factory.Call(receiver: null, setCount, listTemp, knownLengthTemp));

// var span = CollectionsMarshal.AsSpan<ElementType(list);
BoundLocal spanTemp = _factory.StoreToTemp(_factory.Call(receiver: null, asSpan, listTemp), out assignmentToTemp, isKnownToReferToTempIfReferenceType: true);
BoundLocal spanTemp = _factory.StoreToTemp(_factory.Call(receiver: null, asSpan, listTemp), out assignmentToTemp);
localsBuilder.Add(spanTemp);
sideEffects.Add(assignmentToTemp);

Expand All @@ -950,8 +964,7 @@ private BoundExpression CreateAndPopulateList(BoundCollectionExpression node, Ty
// int index = 0;
BoundLocal indexTemp = _factory.StoreToTemp(
_factory.Literal(0),
out assignmentToTemp,
isKnownToReferToTempIfReferenceType: true);
out assignmentToTemp);
localsBuilder.Add(indexTemp);
sideEffects.Add(assignmentToTemp);

Expand Down Expand Up @@ -1061,7 +1074,7 @@ private BoundExpression RewriteCollectionExpressionElementExpression(BoundNode e
{
var rewrittenExpression = RewriteCollectionExpressionElementExpression(elements[i]);
BoundAssignmentOperator assignmentToTemp;
BoundLocal temp = _factory.StoreToTemp(rewrittenExpression, out assignmentToTemp, isKnownToReferToTempIfReferenceType: true);
BoundLocal temp = _factory.StoreToTemp(rewrittenExpression, out assignmentToTemp);
locals.Add(temp);
sideEffects.Add(assignmentToTemp);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,21 @@ public BoundObjectCreationExpression New(NamedTypeSymbol type, ImmutableArray<Bo
public BoundObjectCreationExpression New(MethodSymbol ctor, ImmutableArray<BoundExpression> args)
=> new BoundObjectCreationExpression(Syntax, ctor, args) { WasCompilerGenerated = true };

public BoundObjectCreationExpression New(MethodSymbol constructor, ImmutableArray<BoundExpression> arguments, ImmutableArray<RefKind> argumentRefKinds)
=> new BoundObjectCreationExpression(
Syntax,
constructor,
arguments,
argumentNamesOpt: default,
argumentRefKinds,
expanded: false,
argsToParamsOpt: default,
defaultArguments: default,
constantValueOpt: null,
initializerExpressionOpt: null,
constructor.ContainingType)
{ WasCompilerGenerated = true };

public BoundObjectCreationExpression New(WellKnownMember wm, ImmutableArray<BoundExpression> args)
{
var ctor = WellKnownMethod(wm);
Expand Down