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

Support for ref-readonly locals #22269

Merged
merged 4 commits into from
Sep 23, 2017
Merged
Show file tree
Hide file tree
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
20 changes: 7 additions & 13 deletions src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -773,7 +773,10 @@ protected BoundExpression BindInferredVariableInitializer(DiagnosticBag diagnost
}
else
{
valueKind = BindValueKind.RefOrOut;
valueKind = variableRefKind == RefKind.RefReadOnly
? BindValueKind.ReadonlyRef
: BindValueKind.RefOrOut;

if (initializer == null)
{
Error(diagnostics, ErrorCode.ERR_ByReferenceVariableMustBeInitialized, node);
Expand Down Expand Up @@ -836,19 +839,10 @@ protected BoundExpression BindInferredVariableInitializer(DiagnosticBag diagnost
// might own nested scope.
bool hasErrors = localSymbol.ScopeBinder.ValidateDeclarationNameConflictsInScope(localSymbol, diagnostics);

if (localSymbol.RefKind == RefKind.RefReadOnly)
{
Debug.Assert(typeSyntax.Parent is RefTypeSyntax);
var refKeyword = typeSyntax.Parent.GetFirstToken();
diagnostics.Add(ErrorCode.ERR_UnexpectedToken, refKeyword.GetLocation(), refKeyword.ToString());
}
else
var containingMethod = this.ContainingMemberOrLambda as MethodSymbol;
if (containingMethod != null && containingMethod.IsAsync && localSymbol.RefKind != RefKind.None)
{
var containingMethod = this.ContainingMemberOrLambda as MethodSymbol;
if (containingMethod != null && containingMethod.IsAsync && localSymbol.RefKind != RefKind.None)
{
Error(diagnostics, ErrorCode.ERR_BadAsyncLocalType, declarator);
}
Error(diagnostics, ErrorCode.ERR_BadAsyncLocalType, declarator);
}

EqualsValueClauseSyntax equalsClauseSyntax = declarator.Initializer;
Expand Down
43 changes: 33 additions & 10 deletions src/Compilers/CSharp/Portable/CodeGen/EmitAddress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,11 @@ private LocalDefinition EmitAddress(BoundExpression expression, AddressKind addr
break;

case BoundKind.Local:
EmitLocalAddress((BoundLocal)expression);
break;
return EmitLocalAddress((BoundLocal)expression, addressKind);

case BoundKind.Dup:
Debug.Assert(((BoundDup)expression).RefKind != RefKind.None, "taking address of a stack value?");
_builder.EmitOpCode(ILOpCode.Dup);
break;
return EmitDupAddress((BoundDup)expression, addressKind);

case BoundKind.ConditionalReceiver:
// do nothing receiver ref must be already pushed
Expand Down Expand Up @@ -213,10 +211,18 @@ private void EmitComplexConditionalReceiverAddress(BoundComplexConditionalReceiv
_builder.MarkLabel(doneLabel);
}

private void EmitLocalAddress(BoundLocal localAccess)
/// <summary>
/// May introduce a temp which it will return. (otherwise returns null)
/// </summary>
private LocalDefinition EmitLocalAddress(BoundLocal localAccess, AddressKind addressKind)
{
var local = localAccess.LocalSymbol;

if (!HasHome(localAccess, addressKind != AddressKind.ReadOnly))
{
return EmitAddressOfTempClone(localAccess);
}

if (IsStackLocal(local))
{
if (local.RefKind != RefKind.None)
Expand All @@ -234,6 +240,21 @@ private void EmitLocalAddress(BoundLocal localAccess)
{
_builder.EmitLocalAddress(GetLocal(localAccess));
}
return null;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: new line

}

/// <summary>
/// May introduce a temp which it will return. (otherwise returns null)
/// </summary>
private LocalDefinition EmitDupAddress(BoundDup dup, AddressKind addressKind)
{
if (!HasHome(dup, addressKind != AddressKind.ReadOnly))
Copy link
Member

Choose a reason for hiding this comment

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

Consider using a named argument for the second parm here. Makes it more readable.

{
return EmitAddressOfTempClone(dup);
}

_builder.EmitOpCode(ILOpCode.Dup);
return null;
}

private void EmitPseudoVariableAddress(BoundPseudoVariable expression)
Expand Down Expand Up @@ -345,9 +366,11 @@ private bool HasHome(BoundExpression expression, bool needWriteable)
((BoundParameter)expression).ParameterSymbol.RefKind != RefKind.RefReadOnly;

case BoundKind.Local:
// locals have home unless they are byval stack locals
// locals have home unless they are byval stack locals or ref-readonly
// locals in a mutating call
var local = ((BoundLocal)expression).LocalSymbol;
return !IsStackLocal(local) || local.RefKind != RefKind.None;
return !((IsStackLocal(local) && local.RefKind == RefKind.None) ||
(needWriteable && local.RefKind == RefKind.RefReadOnly));
Copy link
Member

Choose a reason for hiding this comment

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

Is needWritable protecting the local or the value?

Copy link
Member Author

Choose a reason for hiding this comment

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

The local. It's specifying that you must be able to write to the local pointed to by the address.

Copy link
Member

Choose a reason for hiding this comment

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

Then why is the last condition only checking ref readonly


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

Copy link
Member

Choose a reason for hiding this comment

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

i am wrong


In reply to: 140600043 [](ancestors = 140600043,140599487)


case BoundKind.Call:
var methodRefKind = ((BoundCall)expression).Method.RefKind;
Expand All @@ -356,9 +379,9 @@ private bool HasHome(BoundExpression expression, bool needWriteable)

case BoundKind.Dup:
//NB: Dup represents locals that do not need IL slot
// ref locals are currently always writeable, so we do not need to care about "needWriteable"
Debug.Assert(((BoundDup)expression).RefKind != RefKind.RefReadOnly);
return ((BoundDup)expression).RefKind != RefKind.None;
var dupRefKind = ((BoundDup)expression).RefKind;
return dupRefKind == RefKind.Ref ||
(!needWriteable && dupRefKind == RefKind.RefReadOnly);

case BoundKind.FieldAccess:
return HasHome((BoundFieldAccess)expression, needWriteable);
Expand Down
19 changes: 18 additions & 1 deletion src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -971,7 +971,23 @@ private static bool IsIndirectAssignment(BoundAssignmentOperator node)
{
var lhs = node.Left;

Debug.Assert(node.RefKind == RefKind.None || (lhs as BoundLocal)?.LocalSymbol.RefKind == RefKind.Ref,
bool IsAssignable(RefKind lhsKind, RefKind rhsKind)
Copy link
Contributor

Choose a reason for hiding this comment

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

IsAssignable [](start = 17, length = 12)

if this is only for asserting, I'd suggest inlining into the Assert call and simply returning false otherwise. Throwing here will not trigger the Debug window while debugging, and will route it to the VS fault handler.

{
switch (lhsKind)
{
case RefKind.None:
case RefKind.Ref:
return lhsKind == rhsKind;
case RefKind.RefReadOnly:
return rhsKind == RefKind.RefReadOnly || rhsKind == RefKind.Ref;
default:
throw ExceptionUtilities.UnexpectedValue(lhsKind);
}
}

Debug.Assert(node.RefKind == RefKind.None ||
lhs is BoundLocal local &&
IsAssignable(local.LocalSymbol.RefKind, node.RefKind),
"only ref locals can be a target of a ref assignment");

switch (lhs.Kind)
Expand Down Expand Up @@ -1034,6 +1050,7 @@ private static bool IsIndirectAssignment(BoundAssignmentOperator node)
throw ExceptionUtilities.UnexpectedValue(lhs.Kind);
}
}

private static bool IsIndirectOrInstanceFieldAssignment(BoundAssignmentOperator node)
{
var lhs = node.Left;
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/Symbols/LocalSymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ internal virtual bool IsWritable
case LocalDeclarationKind.UsingVariable:
return false;
default:
return true;
return RefKind != RefKind.RefReadOnly;
Copy link
Member

Choose a reason for hiding this comment

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

The local is still assignable. Is that not what is being tracked here?

Copy link
Member Author

Choose a reason for hiding this comment

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

So what do you expect to happen with the following code?

int x = 0;
ref readonly int xr = ref x;
xr++;

Copy link
Member

Choose a reason for hiding this comment

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

Same thing that happens if xr is a readonly static


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

Copy link
Contributor

Choose a reason for hiding this comment

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

can we explicitly state the true cases, and throw otherwise?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't understand why you would want to ever throw here. Ref variables are meaningful, ref-readonly variables are meaningful, and "non-ref" variables are meaningful.

}
}
}
Expand Down
Loading