Skip to content

Commit

Permalink
Add support for ref-readonly locals
Browse files Browse the repository at this point in the history
Ref-readonly locals are mostly identical to ref-readonly parameters. The
most important difference is that when possibly mutating methods are
called on ref-readonly locals of struct type, a proper temporary is
created before calling the method.
  • Loading branch information
agocke committed Sep 22, 2017
1 parent 633f8b2 commit cb6f875
Show file tree
Hide file tree
Showing 7 changed files with 640 additions and 34 deletions.
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;
}

/// <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))
{
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));

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)
{
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;
}
}
}
Expand Down
Loading

0 comments on commit cb6f875

Please sign in to comment.