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

Avoid using a ref local for array element l-value #24070

Merged
merged 12 commits into from
Jan 10, 2018
Merged

Conversation

cston
Copy link
Member

@cston cston commented Jan 5, 2018

Customer scenario

Using a co-variant array element as an l-value results in ArrayTypeMismatchException.

object[] a = new string[] { "" };
a[0] += "";

Bugs this fixes

547533

Workarounds, if any

Copy array element.

var s = a[0];
s += "";
a[0] = s;

Risk

Low: Removes an optimization for array element l-values for compound assignment (C# and VB) and passing by reference (VB).

Performance impact

Low: Removes an optimization for array element l-values for compound assignment (C# and VB) and passing by reference (VB).

Is this a regression from a previous update?

Regression from Visual Studio 2013.

How was the bug found?

Customer reported.

@cston
Copy link
Member Author

cston commented Jan 5, 2018

@dotnet/roslyn-compiler, @AlekseyTs, @VSadov, please review.

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Jan 6, 2018

@cston It looks like a bunch of tests require an IL baseline change. #Closed

@@ -1,5 +1,7 @@
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

#Const DONT_USE_BYREF_LOCALS_FOR_USE_TWICE = True
Copy link
Contributor

@AlekseyTs AlekseyTs Jan 6, 2018

Choose a reason for hiding this comment

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

I believe this affects more scenarios than we would like to. We only need to change behavior of UseTwiceArrayAccess method. #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated.

@@ -507,17 +507,7 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL
break;

case BoundKind.ArrayAccess:
if (isDynamicAssignment)
Copy link
Member

@VSadov VSadov Jan 6, 2018

Choose a reason for hiding this comment

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

Just removing this seems pretty heavy.

We should do this only when element type is an unsealed reference type.
We definitely do not want to do this with struct arrays or we might degrade fairly common patterns like intArray[i]++

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

Copy link
Member

Choose a reason for hiding this comment

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

Do we have a test that generates / diffs IL to verify the intArray[i]++ case maintains the current code generation?

Copy link
Member Author

Choose a reason for hiding this comment

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

Added.

Copy link
Member

Choose a reason for hiding this comment

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

Since this fix addresses such a corner case, I think the right expectation is that most or none of the existing tests need to change.
And there should be a lot of existing tests doing intArray[i]++ or similar.

Copy link
Member

Choose a reason for hiding this comment

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

It looks like it is the case after the last commit.

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Jan 6, 2018

It looks like we need to make similar changes to WithExpressionRewriter in VB. #Closed

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Jan 6, 2018

Done with review pass (iteration 2). #Closed

@cston
Copy link
Member Author

cston commented Jan 8, 2018

WithExpressionRewriter.CaptureInAByRefTemp is not called for co-variant arrays. Added tests for With.


In reply to: 355709608 [](ancestors = 355709608)

// *tmp = *L op R;
// where T is the type of L.
//
// If L is an array access, the assignment is dynamic, the compile-time of the array is dynamic[]
Copy link
Member

Choose a reason for hiding this comment

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

This paragraph doesn't mention covariant array access. Should it?

Copy link
Member Author

Choose a reason for hiding this comment

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

Added.

@@ -603,6 +604,11 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL
return variableTemp;
}

private static bool IsInvariantArray(TypeSymbol type)
{
return (type as ArrayTypeSymbol)?.ElementType?.IsSealed == true;
Copy link
Member

Choose a reason for hiding this comment

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

What does this return for T where T : struct?

Copy link
Member

Choose a reason for hiding this comment

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

We can treat T where T : struct arrays as nonvariant.

I think IsSealed returns false for type parameters regardless of constraints, so it may need to be checked separately. Or not at all - it would be a rare and hardly testable scenario.

Copy link
Member Author

@cston cston Jan 9, 2018

Choose a reason for hiding this comment

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

Yes, IsSealed returns false for type parameters (and IsMustInherit returns False in VB), regardless of constraints. I've added tests for Structure and reference type constraints in VB but I'm not able to hit IsInvariantArray with an array of type parameters in C#. I've left the implementations of IsInvariantArray as is, returning false for type parameters.

@@ -603,6 +604,11 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL
return variableTemp;
}

private static bool IsInvariantArray(TypeSymbol type)
Copy link
Member

Choose a reason for hiding this comment

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

Nit: CLR spec uses "non-variant" term for this.

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Jan 8, 2018

@cston It looks like there are legitimate test failures. #Closed

}
static void G(object[] a, string s)
{
a[0] += s;
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps one more test where the index is a nonconstant expression like a function call - to see two temps.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added.

Copy link
Member

@VSadov VSadov left a comment

Choose a reason for hiding this comment

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

LGTM

@cston cston changed the base branch from master to dev15.6.x January 9, 2018 16:55
@cston
Copy link
Member Author

cston commented Jan 9, 2018

@dotnet-bot test windows_release_vs-integration_prtest

@cston
Copy link
Member Author

cston commented Jan 9, 2018

@AlekseyTs, @jaredpar, @agocke, all feedback has been addressed.

@@ -603,6 +604,11 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL
return variableTemp;
}

private static bool IsInvariantArray(TypeSymbol type)
{
return (type as ArrayTypeSymbol)?.ElementType?.IsSealed == true;
Copy link
Contributor

@AlekseyTs AlekseyTs Jan 9, 2018

Choose a reason for hiding this comment

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

? [](start = 57, length = 1)

It feels like the second conditional access is redundant. #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated.

@@ -208,7 +208,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic

Debug.Assert(node.IsLValue)

#If DONT_USE_BYREF_LOCALS_FOR_USE_TWICE Then
Copy link
Contributor

@AlekseyTs AlekseyTs Jan 9, 2018

Choose a reason for hiding this comment

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

It is not clear why is this conditional compilation got removed from this method. I would expect it to be adjusted to ensure that UseTwiceLValue is still not called when DONT_USE_BYREF_LOCALS_FOR_USE_TWICE is defined. #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

Added #If.

End Function

Private Shared Function IsInvariantArray(type As TypeSymbol) As Boolean
Dim value = TryCast(type, ArrayTypeSymbol)?.ElementType?.IsNotInheritable
Copy link
Contributor

@AlekseyTs AlekseyTs Jan 9, 2018

Choose a reason for hiding this comment

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

? [](start = 67, length = 1)

The second conditional access feels redundant. #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated.


Private Shared Function IsInvariantArray(type As TypeSymbol) As Boolean
Dim value = TryCast(type, ArrayTypeSymbol)?.ElementType?.IsNotInheritable
Return value.HasValue AndAlso value.GetValueOrDefault()
Copy link
Contributor

@AlekseyTs AlekseyTs Jan 9, 2018

Choose a reason for hiding this comment

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

value.HasValue AndAlso [](start = 19, length = 22)

The value.HasValue check is redundant. #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

Removed.

Copy link
Member

@agocke agocke left a comment

Choose a reason for hiding this comment

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

LGTM

End Sub

<Fact>
Public Sub ArrayElementWithBlock_Invariant()
Copy link
Contributor

@AlekseyTs AlekseyTs Jan 9, 2018

Choose a reason for hiding this comment

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

ArrayElementWithBlock_Invariant [](start = 19, length = 31)

I think that "With Block" tests would be more interesting if they were accessing the implicit receiver more than once. #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated.

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Jan 9, 2018

Done with review pass (iteration 10) #Closed

Copy link
Contributor

@AlekseyTs AlekseyTs left a comment

Choose a reason for hiding this comment

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

LGTM (iteration 12)

@cston
Copy link
Member Author

cston commented Jan 9, 2018

@dotnet-bot test windows_release_vs-integration_prtest

@cston
Copy link
Member Author

cston commented Jan 9, 2018

@MeiChin-Tsai for approval.

@MeiChin-Tsai
Copy link

approved.

@cston cston merged commit 2f6c1c5 into dotnet:dev15.6.x Jan 10, 2018
@cston cston deleted the 547533 branch January 10, 2018 16:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants