提交 899aaca8 编写于 作者: V VSadov

Merge pull request #1935 from VSadov/elvisOpt

Improved code generation for ?. in some scenarios (in particular async)
...@@ -6437,7 +6437,7 @@ private BoundExpression GetReceiverForConditionalBinding(ExpressionSyntax bindin ...@@ -6437,7 +6437,7 @@ private BoundExpression GetReceiverForConditionalBinding(ExpressionSyntax bindin
receiverType = receiverType.GetNullableUnderlyingType(); receiverType = receiverType.GetNullableUnderlyingType();
} }
receiver = new BoundConditionalReceiver(receiver.Syntax, receiverType) { WasCompilerGenerated = true }; receiver = new BoundConditionalReceiver(receiver.Syntax, 0, receiverType) { WasCompilerGenerated = true };
return receiver; return receiver;
} }
......
...@@ -332,6 +332,36 @@ public override bool SuppressVirtualCalls ...@@ -332,6 +332,36 @@ public override bool SuppressVirtualCalls
{ {
get { return this.IsBaseConversion; } get { return this.IsBaseConversion; }
} }
/// <summary>
/// Returns true when conversion itself (not the operand) may have sideeffects
/// A typical sideeffect of a conversion is an exception when conversion is unsuccessful.
/// </summary>
/// <returns></returns>
internal bool ConversionHasSideEffects()
{
// only some intrinsic conversions are side effect free
// the only side effect of an intrinsic conversion is a throw when we fail to convert.
// and some intrinsic conversion always succeed
switch (this.ConversionKind)
{
case ConversionKind.Identity:
// NOTE: even explicit float/double identity conversion does not have side
// effects since it does not throw
case ConversionKind.ImplicitNumeric:
case ConversionKind.ImplicitEnumeration:
// implicit ref cast does not throw ...
case ConversionKind.ImplicitReference:
case ConversionKind.Boxing:
return false;
// unchecked numeric conversion does not throw
case ConversionKind.ExplicitNumeric:
return this.Checked;
}
return true;
}
} }
internal partial class BoundObjectCreationExpression internal partial class BoundObjectCreationExpression
......
...@@ -968,9 +968,38 @@ ...@@ -968,9 +968,38 @@
<Field Name="AccessExpression" Type="BoundExpression"/> <Field Name="AccessExpression" Type="BoundExpression"/>
</Node> </Node>
<Node Name="BoundLoweredConditionalAccess" Base="BoundExpression">
<Field Name="Type" Type="TypeSymbol" Override="true" Null="disallow"/>
<Field Name="Receiver" Type="BoundExpression"/>
<Field Name="WhenNotNull" Type="BoundExpression"/>
<Field Name="WhenNullOpt" Type="BoundExpression" Null="allow"/>
<!--
Async rewriter needs to replace receivers with their spilled values
and for that it needs to match receivers and the containing conditional
expressions.
To be able to do that, during lowering, we will assign
BoundLoweredConditionalAccess and corresponding BoundConditionalReceiver
matching Id that are integers unique for the containing method body.
-->
<Field Name="Id" Type="int"/>
</Node>
<!-- represents the receiver of a conditional access expression --> <!-- represents the receiver of a conditional access expression -->
<Node Name="BoundConditionalReceiver" Base="BoundExpression"> <Node Name="BoundConditionalReceiver" Base="BoundExpression">
<Field Name="Type" Type="TypeSymbol" Override="true" Null="disallow"/> <Field Name="Type" Type="TypeSymbol" Override="true" Null="disallow"/>
<!-- See the comment in BoundLoweredConditionalAccess -->
<Field Name="Id" Type="int"/>
</Node>
<!-- This node represents a complex receiver for a conditional access.
At runtime, when its type is a value type, ValueTypeReceiver should be used as a receiver.
Otherwise, ReferenceTypeReceiver should be used.
This kind of receiver is created only by Async rewriter.
-->
<Node Name="BoundComplexConditionalReceiver" Base="BoundExpression" HasValidate="true">
<Field Name="Type" Type="TypeSymbol" Override="true" Null="disallow"/>
<Field Name="ValueTypeReceiver" Type="BoundExpression" Null="disallow"/>
<Field Name="ReferenceTypeReceiver" Type="BoundExpression" Null="disallow"/>
</Node> </Node>
<Node Name="BoundMethodGroup" Base="BoundMethodOrPropertyGroup"> <Node Name="BoundMethodGroup" Base="BoundMethodOrPropertyGroup">
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.CodeGen;
...@@ -47,6 +48,10 @@ private LocalDefinition EmitAddress(BoundExpression expression, AddressKind addr ...@@ -47,6 +48,10 @@ private LocalDefinition EmitAddress(BoundExpression expression, AddressKind addr
Debug.Assert(!expression.Type.IsValueType); Debug.Assert(!expression.Type.IsValueType);
break; break;
case BoundKind.ComplexConditionalReceiver:
EmitComplexConditionalReceiverAddress((BoundComplexConditionalReceiver)expression);
break;
case BoundKind.Parameter: case BoundKind.Parameter:
EmitParameterAddress((BoundParameter)expression); EmitParameterAddress((BoundParameter)expression);
break; break;
...@@ -95,6 +100,31 @@ private LocalDefinition EmitAddress(BoundExpression expression, AddressKind addr ...@@ -95,6 +100,31 @@ private LocalDefinition EmitAddress(BoundExpression expression, AddressKind addr
return null; return null;
} }
private void EmitComplexConditionalReceiverAddress(BoundComplexConditionalReceiver expression)
{
Debug.Assert(!expression.Type.IsReferenceType);
Debug.Assert(!expression.Type.IsValueType);
var receiverType = expression.Type;
var whenValueTypeLabel = new Object();
var doneLabel = new Object();
EmitInitObj(receiverType, true, expression.Syntax);
EmitBox(receiverType, expression.Syntax);
_builder.EmitBranch(ILOpCode.Brtrue, whenValueTypeLabel);
var receiverTemp = EmitAddress(expression.ReferenceTypeReceiver, addressKind: AddressKind.ReadOnly);
Debug.Assert(receiverTemp == null);
_builder.EmitBranch(ILOpCode.Br, doneLabel);
_builder.AdjustStack(-1);
_builder.MarkLabel(whenValueTypeLabel);
EmitReceiverRef(expression.ValueTypeReceiver, isAccessConstrained: true);
_builder.MarkLabel(doneLabel);
}
private void EmitLocalAddress(BoundLocal localAccess) private void EmitLocalAddress(BoundLocal localAccess)
{ {
var local = localAccess.LocalSymbol; var local = localAccess.LocalSymbol;
...@@ -260,6 +290,14 @@ private bool HasHome(BoundExpression expression) ...@@ -260,6 +290,14 @@ private bool HasHome(BoundExpression expression)
case BoundKind.Sequence: case BoundKind.Sequence:
return HasHome(((BoundSequence)expression).Value); return HasHome(((BoundSequence)expression).Value);
case BoundKind.ComplexConditionalReceiver:
Debug.Assert(HasHome(((BoundComplexConditionalReceiver)expression).ValueTypeReceiver));
Debug.Assert(HasHome(((BoundComplexConditionalReceiver)expression).ReferenceTypeReceiver));
goto case BoundKind.ConditionalReceiver;
case BoundKind.ConditionalReceiver:
return true;
default: default:
return false; return false;
} }
......
...@@ -26,7 +26,7 @@ private void EmitConversionExpression(BoundConversion conversion, bool used) ...@@ -26,7 +26,7 @@ private void EmitConversionExpression(BoundConversion conversion, bool used)
return; return;
} }
if (!used && !ConversionHasSideEffects(conversion)) if (!used && !conversion.ConversionHasSideEffects())
{ {
EmitExpression(conversion.Operand, false); // just do expr side effects EmitExpression(conversion.Operand, false); // just do expr side effects
return; return;
...@@ -115,30 +115,6 @@ private void EmitConversion(BoundConversion conversion) ...@@ -115,30 +115,6 @@ private void EmitConversion(BoundConversion conversion)
} }
} }
private static bool ConversionHasSideEffects(BoundConversion conversion)
{
// only some intrinsic conversions are side effect free the only side effect of an
// intrinsic conversion is a throw when we fail to convert.
switch (conversion.ConversionKind)
{
case ConversionKind.Identity:
// NOTE: even explicit float/double identity conversion does not have side
// effects since it does not throw
case ConversionKind.ImplicitNumeric:
case ConversionKind.ImplicitEnumeration:
// implicit ref cast does not throw ...
case ConversionKind.ImplicitReference:
case ConversionKind.Boxing:
return false;
// unchecked numeric conversion does not throw
case ConversionKind.ExplicitNumeric:
return conversion.Checked;
}
return true;
}
private void EmitIdentityConversion(BoundConversion conversion) private void EmitIdentityConversion(BoundConversion conversion)
{ {
// An _explicit_ identity conversion from double to double or float to float on // An _explicit_ identity conversion from double to double or float to float on
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
...@@ -213,14 +214,18 @@ private void EmitExpression(BoundExpression expression, bool used) ...@@ -213,14 +214,18 @@ private void EmitExpression(BoundExpression expression, bool used)
EmitRefValueOperator((BoundRefValueOperator)expression, used); EmitRefValueOperator((BoundRefValueOperator)expression, used);
break; break;
case BoundKind.ConditionalAccess: case BoundKind.LoweredConditionalAccess:
EmitConditionalAccessExpression((BoundConditionalAccess)expression, used); EmitLoweredConditionalAccessExpression((BoundLoweredConditionalAccess)expression, used);
break; break;
case BoundKind.ConditionalReceiver: case BoundKind.ConditionalReceiver:
EmitConditionalReceiver((BoundConditionalReceiver)expression, used); EmitConditionalReceiver((BoundConditionalReceiver)expression, used);
break; break;
case BoundKind.ComplexConditionalReceiver:
EmitComplexConditionalReceiver((BoundComplexConditionalReceiver)expression, used);
break;
case BoundKind.PseudoVariable: case BoundKind.PseudoVariable:
EmitPseudoVariableValue((BoundPseudoVariable)expression, used); EmitPseudoVariableValue((BoundPseudoVariable)expression, used);
break; break;
...@@ -234,7 +239,31 @@ private void EmitExpression(BoundExpression expression, bool used) ...@@ -234,7 +239,31 @@ private void EmitExpression(BoundExpression expression, bool used)
} }
} }
private void EmitConditionalAccessExpression(BoundConditionalAccess expression, bool used) private void EmitComplexConditionalReceiver(BoundComplexConditionalReceiver expression, bool used)
{
Debug.Assert(!expression.Type.IsReferenceType);
Debug.Assert(!expression.Type.IsValueType);
var receiverType = expression.Type;
var whenValueTypeLabel = new object();
var doneLabel = new object();
EmitInitObj(receiverType, true, expression.Syntax);
EmitBox(receiverType, expression.Syntax);
_builder.EmitBranch(ILOpCode.Brtrue, whenValueTypeLabel);
EmitExpression(expression.ReferenceTypeReceiver, used);
_builder.EmitBranch(ILOpCode.Br, doneLabel);
_builder.AdjustStack(-1);
_builder.MarkLabel(whenValueTypeLabel);
EmitExpression(expression.ValueTypeReceiver, used);
_builder.MarkLabel(doneLabel);
}
private void EmitLoweredConditionalAccessExpression(BoundLoweredConditionalAccess expression, bool used)
{ {
var receiver = expression.Receiver; var receiver = expression.Receiver;
...@@ -253,7 +282,7 @@ private void EmitConditionalAccessExpression(BoundConditionalAccess expression, ...@@ -253,7 +282,7 @@ private void EmitConditionalAccessExpression(BoundConditionalAccess expression,
{ {
// const but not default // const but not default
receiverTemp = EmitReceiverRef(receiver, isAccessConstrained: !receiverType.IsReferenceType); receiverTemp = EmitReceiverRef(receiver, isAccessConstrained: !receiverType.IsReferenceType);
EmitExpression(expression.AccessExpression, used); EmitExpression(expression.WhenNotNull, used);
if (receiverTemp != null) if (receiverTemp != null)
{ {
FreeTemp(receiverTemp); FreeTemp(receiverTemp);
...@@ -269,7 +298,7 @@ private void EmitConditionalAccessExpression(BoundConditionalAccess expression, ...@@ -269,7 +298,7 @@ private void EmitConditionalAccessExpression(BoundConditionalAccess expression,
// we need a copy if we deal with nonlocal value (to capture the value) // we need a copy if we deal with nonlocal value (to capture the value)
// or if we have a ref-constrained T (to do box just once) // or if we have a ref-constrained T (to do box just once)
// or if we deal with stack local (reads are destructive) // or if we deal with stack local (reads are destructive)
var nullCheckOnCopy = LocalRewriter.IntroducingReadCanBeObservable(receiver, localsMayBeAssignedOrCaptured: false) || var nullCheckOnCopy = LocalRewriter.CanChangeValueBetweenReads(receiver, localsMayBeAssignedOrCaptured: false) ||
(receiverType.IsReferenceType && receiverType.TypeKind == TypeKind.TypeParameter) || (receiverType.IsReferenceType && receiverType.TypeKind == TypeKind.TypeParameter) ||
(receiver.Kind == BoundKind.Local && IsStackLocal(((BoundLocal)receiver).LocalSymbol)); (receiver.Kind == BoundKind.Local && IsStackLocal(((BoundLocal)receiver).LocalSymbol));
...@@ -329,7 +358,16 @@ private void EmitConditionalAccessExpression(BoundConditionalAccess expression, ...@@ -329,7 +358,16 @@ private void EmitConditionalAccessExpression(BoundConditionalAccess expression,
_builder.EmitOpCode(ILOpCode.Pop); _builder.EmitOpCode(ILOpCode.Pop);
} }
var whenNull = expression.WhenNullOpt;
if (whenNull == null)
{
EmitDefaultValue(expression.Type, used, expression.Syntax); EmitDefaultValue(expression.Type, used, expression.Syntax);
}
else
{
EmitExpression(whenNull, used);
}
_builder.EmitBranch(ILOpCode.Br, doneLabel); _builder.EmitBranch(ILOpCode.Br, doneLabel);
if (nullCheckOnCopy) if (nullCheckOnCopy)
...@@ -356,7 +394,7 @@ private void EmitConditionalAccessExpression(BoundConditionalAccess expression, ...@@ -356,7 +394,7 @@ private void EmitConditionalAccessExpression(BoundConditionalAccess expression,
Debug.Assert(receiverTemp == null); Debug.Assert(receiverTemp == null);
} }
EmitExpression(expression.AccessExpression, used); EmitExpression(expression.WhenNotNull, used);
_builder.MarkLabel(doneLabel); _builder.MarkLabel(doneLabel);
if (temp != null) if (temp != null)
......
...@@ -418,17 +418,18 @@ private void EmitCondBranch(BoundExpression condition, ref object dest, bool sen ...@@ -418,17 +418,18 @@ private void EmitCondBranch(BoundExpression condition, ref object dest, bool sen
// then it is regular binary expression - Or, And, Xor ... // then it is regular binary expression - Or, And, Xor ...
goto default; goto default;
case BoundKind.ConditionalAccess: case BoundKind.LoweredConditionalAccess:
{ {
var ca = (BoundConditionalAccess)condition; var ca = (BoundLoweredConditionalAccess)condition;
var receiver = ca.Receiver; var receiver = ca.Receiver;
var receiverType = receiver.Type; var receiverType = receiver.Type;
// we need a copy if we deal with nonlocal value (to capture the value) // we need a copy if we deal with nonlocal value (to capture the value)
// or if we deal with stack local (reads are destructive) // or if we deal with stack local (reads are destructive)
var complexCase = !receiverType.IsReferenceType || var complexCase = !receiverType.IsReferenceType ||
LocalRewriter.IntroducingReadCanBeObservable(receiver, localsMayBeAssignedOrCaptured: false) || LocalRewriter.CanChangeValueBetweenReads(receiver, localsMayBeAssignedOrCaptured: false) ||
(receiver.Kind == BoundKind.Local && IsStackLocal(((BoundLocal)receiver).LocalSymbol)); (receiver.Kind == BoundKind.Local && IsStackLocal(((BoundLocal)receiver).LocalSymbol)) ||
(ca.WhenNullOpt?.IsDefaultValue() == false) ;
if (complexCase) if (complexCase)
{ {
...@@ -445,7 +446,7 @@ private void EmitCondBranch(BoundExpression condition, ref object dest, bool sen ...@@ -445,7 +446,7 @@ private void EmitCondBranch(BoundExpression condition, ref object dest, bool sen
EmitCondBranch(receiver, ref fallThrough, sense: false); EmitCondBranch(receiver, ref fallThrough, sense: false);
EmitReceiverRef(receiver, isAccessConstrained: false); EmitReceiverRef(receiver, isAccessConstrained: false);
EmitCondBranch(ca.AccessExpression, ref dest, sense: true); EmitCondBranch(ca.WhenNotNull, ref dest, sense: true);
if (fallThrough != null) if (fallThrough != null)
{ {
...@@ -458,7 +459,7 @@ private void EmitCondBranch(BoundExpression condition, ref object dest, bool sen ...@@ -458,7 +459,7 @@ private void EmitCondBranch(BoundExpression condition, ref object dest, bool sen
// gotoif(!receiver.Access) labDest // gotoif(!receiver.Access) labDest
EmitCondBranch(receiver, ref dest, sense: false); EmitCondBranch(receiver, ref dest, sense: false);
EmitReceiverRef(receiver, isAccessConstrained: false); EmitReceiverRef(receiver, isAccessConstrained: false);
condition = ca.AccessExpression; condition = ca.WhenNotNull;
goto oneMoreTime; goto oneMoreTime;
} }
} }
......
...@@ -1210,7 +1210,7 @@ public override BoundNode VisitNullCoalescingOperator(BoundNullCoalescingOperato ...@@ -1210,7 +1210,7 @@ public override BoundNode VisitNullCoalescingOperator(BoundNullCoalescingOperato
return node.Update(left, right, node.LeftConversion, node.Type); return node.Update(left, right, node.LeftConversion, node.Type);
} }
public override BoundNode VisitConditionalAccess(BoundConditionalAccess node) public override BoundNode VisitLoweredConditionalAccess(BoundLoweredConditionalAccess node)
{ {
var origStack = _evalStack; var origStack = _evalStack;
BoundExpression receiver = VisitCallReceiver(node.Receiver); BoundExpression receiver = VisitCallReceiver(node.Receiver);
...@@ -1220,11 +1220,46 @@ public override BoundNode VisitConditionalAccess(BoundConditionalAccess node) ...@@ -1220,11 +1220,46 @@ public override BoundNode VisitConditionalAccess(BoundConditionalAccess node)
// right is evaluated with original stack // right is evaluated with original stack
// (this is not entirely true, codegen will keep receiver on the stack, but that is irrelevant here) // (this is not entirely true, codegen will keep receiver on the stack, but that is irrelevant here)
_evalStack = origStack; _evalStack = origStack;
BoundExpression access = (BoundExpression)this.Visit(node.AccessExpression); BoundExpression whenNotNull = (BoundExpression)this.Visit(node.WhenNotNull);
EnsureStackState(cookie); // implicit label here EnsureStackState(cookie); // implicit label here
return node.Update(receiver, access, node.Type); var whenNull = node.WhenNullOpt;
if (whenNull != null)
{
_evalStack = origStack; // whennull is evaluated with original stack
whenNull = (BoundExpression)this.Visit(whenNull);
EnsureStackState(cookie); // implicit label here
}
else
{
_counter += 1;
}
return node.Update(receiver, whenNotNull, whenNull, node.Id, node.Type);
}
public override BoundNode VisitComplexConditionalReceiver(BoundComplexConditionalReceiver node)
{
EnsureOnlyEvalStack();
var origStack = this._evalStack;
this._evalStack += 1;
var cookie = GetStackStateCookie(); // implicit goto here
this._evalStack = origStack; // consequence is evaluated with original stack
var valueTypeReceiver = (BoundExpression)this.Visit(node.ValueTypeReceiver);
EnsureStackState(cookie); // implicit label here
this._evalStack = origStack; // alternative is evaluated with original stack
var referenceTypeReceiver = (BoundExpression)this.Visit(node.ReferenceTypeReceiver);
EnsureStackState(cookie); // implicit label here
return node.Update(valueTypeReceiver, referenceTypeReceiver, node.Type);
} }
public override BoundNode VisitUnaryOperator(BoundUnaryOperator node) public override BoundNode VisitUnaryOperator(BoundUnaryOperator node)
......
...@@ -272,10 +272,15 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, ...@@ -272,10 +272,15 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState,
// build argument // build argument
arguments[i] = F.Convert(manager.System_Object, arguments[i] = F.Convert(manager.System_Object,
new BoundConditionalAccess(F.Syntax, new BoundLoweredConditionalAccess(F.Syntax,
F.Field(F.This(), property.BackingField), F.Field(F.This(), property.BackingField),
F.Call(new BoundConditionalReceiver(F.Syntax, property.BackingField.Type), manager.System_Object__ToString), F.Call(new BoundConditionalReceiver(
manager.System_String), F.Syntax,
id: i,
type: property.BackingField.Type), manager.System_Object__ToString),
null,
id: i,
type: manager.System_String),
ConversionKind.ImplicitReference); ConversionKind.ImplicitReference);
} }
formatString.Builder.Append(" }}"); formatString.Builder.Append(" }}");
......
...@@ -1980,11 +1980,44 @@ public override BoundNode VisitConditionalAccess(BoundConditionalAccess node) ...@@ -1980,11 +1980,44 @@ public override BoundNode VisitConditionalAccess(BoundConditionalAccess node)
return null; return null;
} }
public override BoundNode VisitLoweredConditionalAccess(BoundLoweredConditionalAccess node)
{
VisitRvalue(node.Receiver);
var savedState = this.State.Clone();
VisitRvalue(node.WhenNotNull);
IntersectWith(ref this.State, ref savedState);
if (node.WhenNullOpt != null)
{
savedState = this.State.Clone();
VisitRvalue(node.WhenNullOpt);
IntersectWith(ref this.State, ref savedState);
}
return null;
}
public override BoundNode VisitConditionalReceiver(BoundConditionalReceiver node) public override BoundNode VisitConditionalReceiver(BoundConditionalReceiver node)
{ {
return null; return null;
} }
public override BoundNode VisitComplexConditionalReceiver(BoundComplexConditionalReceiver node)
{
var savedState = this.State.Clone();
VisitRvalue(node.ValueTypeReceiver);
IntersectWith(ref this.State, ref savedState);
savedState = this.State.Clone();
VisitRvalue(node.ReferenceTypeReceiver);
IntersectWith(ref this.State, ref savedState);
return null;
}
public override BoundNode VisitSequence(BoundSequence node) public override BoundNode VisitSequence(BoundSequence node)
{ {
var sideEffects = node.SideEffects; var sideEffects = node.SideEffects;
......
...@@ -392,6 +392,11 @@ private BoundStatement UpdateStatement(BoundSpillSequenceBuilder builder, BoundS ...@@ -392,6 +392,11 @@ private BoundStatement UpdateStatement(BoundSpillSequenceBuilder builder, BoundS
case BoundKind.TypeExpression: case BoundKind.TypeExpression:
return expression; return expression;
case BoundKind.ConditionalReceiver:
// we will rewrite this as a part of rewriting whole LoweredConditionalAccess
// later, if needed
return expression;
default: default:
if (expression.Type.SpecialType == SpecialType.System_Void || sideEffectsOnly) if (expression.Type.SpecialType == SpecialType.System_Void || sideEffectsOnly)
{ {
...@@ -881,6 +886,135 @@ public override BoundNode VisitNullCoalescingOperator(BoundNullCoalescingOperato ...@@ -881,6 +886,135 @@ public override BoundNode VisitNullCoalescingOperator(BoundNullCoalescingOperato
return UpdateExpression(builder, node.Update(left, right, node.LeftConversion, node.Type)); return UpdateExpression(builder, node.Update(left, right, node.LeftConversion, node.Type));
} }
public override BoundNode VisitLoweredConditionalAccess(BoundLoweredConditionalAccess node)
{
var receiverRefKind = ReceiverSpillRefKind(node.Receiver);
BoundSpillSequenceBuilder receiverBuilder = null;
var receiver = VisitExpression(ref receiverBuilder, node.Receiver);
BoundSpillSequenceBuilder whenNotNullBuilder = null;
var whenNotNull = VisitExpression(ref whenNotNullBuilder, node.WhenNotNull);
BoundSpillSequenceBuilder whenNullBuilder = null;
var whenNullOpt = VisitExpression(ref whenNullBuilder, node.WhenNullOpt);
if (whenNotNullBuilder == null && whenNullBuilder == null)
{
return UpdateExpression(receiverBuilder, node.Update(receiver, whenNotNull, whenNullOpt, node.Id, node.Type));
}
if (receiverBuilder == null) receiverBuilder = new BoundSpillSequenceBuilder();
if (whenNotNullBuilder == null) whenNotNullBuilder = new BoundSpillSequenceBuilder();
if (whenNullBuilder == null) whenNullBuilder = new BoundSpillSequenceBuilder();
BoundExpression condition;
if (receiver.Type.IsReferenceType || receiverRefKind == RefKind.None)
{
// spill to a clone
receiver = Spill(receiverBuilder, receiver, RefKind.None);
condition = _F.ObjectNotEqual(
_F.Convert(_F.SpecialType(SpecialType.System_Object), receiver),
_F.Null(_F.SpecialType(SpecialType.System_Object)));
}
else
{
receiver = Spill(receiverBuilder, receiver, RefKind.Ref);
var clone = _F.SynthesizedLocal(receiver.Type, _F.Syntax, refKind: RefKind.None, kind: SynthesizedLocalKind.AwaitSpill);
receiverBuilder.AddLocal(clone, _F.Diagnostics);
// (object)default(T) != null
var isNotClass = _F.ObjectNotEqual(
_F.Convert(_F.SpecialType(SpecialType.System_Object), _F.Default(receiver.Type)),
_F.Null(_F.SpecialType(SpecialType.System_Object)));
// isNotCalss || {clone = receiver; (object)clone != null}
condition = _F.LogicalOr(
isNotClass,
_F.Sequence(
_F.AssignmentExpression(_F.Local(clone), receiver),
_F.ObjectNotEqual(
_F.Convert(_F.SpecialType(SpecialType.System_Object), _F.Local(clone)),
_F.Null(_F.SpecialType(SpecialType.System_Object))))
);
receiver = _F.ComplexConditionalReceiver(receiver, _F.Local(clone));
}
if (node.Type.SpecialType == SpecialType.System_Void)
{
var wnenNotNullStatement = UpdateStatement(whenNotNullBuilder, _F.ExpressionStatement(whenNotNull), substituteTemps: false);
wnenNotNullStatement = ConditionalReceiverReplacer.Replace(wnenNotNullStatement, receiver, node.Id);
Debug.Assert(whenNullOpt == null || !LocalRewriter.ReadIsSideeffecting(whenNullOpt));
receiverBuilder.AddStatement(_F.If(condition, wnenNotNullStatement));
return receiverBuilder.Update(_F.Default(node.Type));
}
else
{
Debug.Assert(_F.Syntax.IsKind(SyntaxKind.AwaitExpression));
var tmp = _F.SynthesizedLocal(node.Type, kind: SynthesizedLocalKind.AwaitSpill, syntax: _F.Syntax);
var wnenNotNullStatement = UpdateStatement(whenNotNullBuilder, _F.Assignment(_F.Local(tmp), whenNotNull), substituteTemps: false);
wnenNotNullStatement = ConditionalReceiverReplacer.Replace(wnenNotNullStatement, receiver, node.Id);
whenNullOpt = whenNullOpt ?? _F.Default(node.Type);
receiverBuilder.AddLocal(tmp, _F.Diagnostics);
receiverBuilder.AddStatement(
_F.If(condition,
wnenNotNullStatement,
UpdateStatement(whenNullBuilder, _F.Assignment(_F.Local(tmp), whenNullOpt), substituteTemps: false)));
return receiverBuilder.Update(_F.Local(tmp));
}
}
private class ConditionalReceiverReplacer: BoundTreeRewriter
{
private readonly BoundExpression _receiver;
private readonly int _receiverID;
#if DEBUG
// we must replace exatly one node
private int _replaced;
#endif
private ConditionalReceiverReplacer(BoundExpression receiver, int receiverID)
{
this._receiver = receiver;
this._receiverID = receiverID;
}
public static BoundStatement Replace(BoundNode node, BoundExpression receiver, int receiverID)
{
var replacer = new ConditionalReceiverReplacer(receiver, receiverID);
var result = (BoundStatement)replacer.Visit(node);
#if DEBUG
Debug.Assert(replacer._replaced == 1, "should have replaced exactly one node");
#endif
return result;
}
public override BoundNode VisitConditionalReceiver(BoundConditionalReceiver node)
{
if (node.Id == this._receiverID)
{
#if DEBUG
_replaced++;
#endif
return _receiver;
}
return node;
}
}
public override BoundNode VisitObjectCreationExpression(BoundObjectCreationExpression node) public override BoundNode VisitObjectCreationExpression(BoundObjectCreationExpression node)
{ {
Debug.Assert(node.InitializerExpressionOpt == null); Debug.Assert(node.InitializerExpressionOpt == null);
......
...@@ -177,7 +177,7 @@ private BoundExpression VisitExpressionImpl(BoundExpression node) ...@@ -177,7 +177,7 @@ private BoundExpression VisitExpressionImpl(BoundExpression node)
if (constantValue != null) if (constantValue != null)
{ {
TypeSymbol type = node.Type; TypeSymbol type = node.Type;
if (((object)type == null || !type.IsNullableType())) if (type?.IsNullableType() != true)
{ {
return MakeLiteral(node.Syntax, constantValue, type); return MakeLiteral(node.Syntax, constantValue, type);
} }
......
...@@ -74,7 +74,7 @@ private BoundExpression TryFoldWithConditionalAccess(BoundBinaryOperator node) ...@@ -74,7 +74,7 @@ private BoundExpression TryFoldWithConditionalAccess(BoundBinaryOperator node)
// reading rightAlwaysHasValue should not have sideeffects here // reading rightAlwaysHasValue should not have sideeffects here
// otherwise we would need to read it even when we knew that LHS is null // otherwise we would need to read it even when we knew that LHS is null
if (rightAlwaysHasValue != null && !IntroducingReadCanBeObservable(rightAlwaysHasValue)) if (rightAlwaysHasValue != null && !ReadIsSideeffecting(rightAlwaysHasValue))
{ {
BoundExpression accessExpression = conditionalAccess.AccessExpression; BoundExpression accessExpression = conditionalAccess.AccessExpression;
accessExpression = node.Update(unliftedOperatorKind, accessExpression, rightAlwaysHasValue, null, node.MethodOpt, node.ResultKind, node.Type); accessExpression = node.Update(unliftedOperatorKind, accessExpression, rightAlwaysHasValue, null, node.MethodOpt, node.ResultKind, node.Type);
...@@ -250,7 +250,11 @@ public BoundExpression VisitBinaryOperator(BoundBinaryOperator node, BoundUnaryO ...@@ -250,7 +250,11 @@ public BoundExpression VisitBinaryOperator(BoundBinaryOperator node, BoundUnaryO
return RewriteDelegateOperation(syntax, operatorKind, loweredLeft, loweredRight, type, SpecialMember.System_Delegate__op_Inequality); return RewriteDelegateOperation(syntax, operatorKind, loweredLeft, loweredRight, type, SpecialMember.System_Delegate__op_Inequality);
} }
} }
else if (operatorKind.IsDynamic()) else
// try to lower the expression.
{
if (operatorKind.IsDynamic())
{ {
Debug.Assert(!isPointerElementAccess); Debug.Assert(!isPointerElementAccess);
...@@ -264,23 +268,17 @@ public BoundExpression VisitBinaryOperator(BoundBinaryOperator node, BoundUnaryO ...@@ -264,23 +268,17 @@ public BoundExpression VisitBinaryOperator(BoundBinaryOperator node, BoundUnaryO
return _dynamicFactory.MakeDynamicBinaryOperator(operatorKind, loweredLeft, loweredRight, isCompoundAssignment, type).ToExpression(); return _dynamicFactory.MakeDynamicBinaryOperator(operatorKind, loweredLeft, loweredRight, isCompoundAssignment, type).ToExpression();
} }
} }
else if (operatorKind.IsUserDefined())
{ if (operatorKind.IsLifted())
return LowerUserDefinedBinaryOperator(syntax, operatorKind, loweredLeft, loweredRight, type, method);
}
else if (operatorKind.IsLifted())
{
if (operatorKind.IsComparison())
{ {
return LowerLiftedBuiltInComparisonOperator(syntax, operatorKind, loweredLeft, loweredRight); return RewriteLiftedBinaryOperator(syntax, operatorKind, loweredLeft, loweredRight, type, method);
} }
else
if (operatorKind.IsUserDefined())
{ {
return LowerLiftedBinaryArithmeticOperator(syntax, operatorKind, loweredLeft, loweredRight, type, method); return LowerUserDefinedBinaryOperator(syntax, operatorKind, loweredLeft, loweredRight, type, method);
}
} }
else
{
switch (operatorKind.OperatorWithLogical() | operatorKind.OperandTypes()) switch (operatorKind.OperatorWithLogical() | operatorKind.OperandTypes())
{ {
case BinaryOperatorKind.NullableNullEqual: case BinaryOperatorKind.NullableNullEqual:
...@@ -477,6 +475,53 @@ public BoundExpression VisitBinaryOperator(BoundBinaryOperator node, BoundUnaryO ...@@ -477,6 +475,53 @@ public BoundExpression VisitBinaryOperator(BoundBinaryOperator node, BoundUnaryO
new BoundBinaryOperator(syntax, operatorKind, loweredLeft, loweredRight, null, null, LookupResultKind.Viable, type); new BoundBinaryOperator(syntax, operatorKind, loweredLeft, loweredRight, null, null, LookupResultKind.Viable, type);
} }
private BoundExpression RewriteLiftedBinaryOperator(CSharpSyntaxNode syntax, BinaryOperatorKind operatorKind, BoundExpression loweredLeft, BoundExpression loweredRight, TypeSymbol type, MethodSymbol method)
{
var conditionalLeft = loweredLeft as BoundLoweredConditionalAccess;
// NOTE: we could in theory handle sideeffecting loweredRight here too
// by including it as a part of whenNull, but there is a concern
// that it can lead to code duplication
var optimize = conditionalLeft != null &&
!ReadIsSideeffecting(loweredRight) &&
(conditionalLeft.WhenNullOpt == null || conditionalLeft.WhenNullOpt.IsDefaultValue());
if (optimize)
{
loweredLeft = conditionalLeft.WhenNotNull;
}
var result = operatorKind.IsComparison() ?
operatorKind.IsUserDefined() ?
LowerLiftedUserDefinedComparisonOperator(syntax, operatorKind, loweredLeft, loweredRight, method) :
LowerLiftedBuiltInComparisonOperator(syntax, operatorKind, loweredLeft, loweredRight) :
LowerLiftedBinaryArithmeticOperator(syntax, operatorKind, loweredLeft, loweredRight, type, method);
if (optimize)
{
BoundExpression whenNullOpt = null;
// for all operators null-in means null-out
// except for the Equal/NotEqual since null == null ==> true
if (operatorKind.Operator() == BinaryOperatorKind.NotEqual ||
operatorKind.Operator() == BinaryOperatorKind.Equal)
{
whenNullOpt = RewriteLiftedBinaryOperator(syntax, operatorKind, _factory.Default(loweredLeft.Type), loweredRight, type, method);
}
result = conditionalLeft.Update(
conditionalLeft.Receiver,
whenNotNull: result,
whenNullOpt: whenNullOpt,
id: conditionalLeft.Id,
type: result.Type
);
}
return result;
}
//array length produces native uint, so the node typically implies a conversion to int32/int64. //array length produces native uint, so the node typically implies a conversion to int32/int64.
//Sometimes the conversion is not necessary - i.e. when we just check for 0 //Sometimes the conversion is not necessary - i.e. when we just check for 0
//This helper removes unnecessary implied conversion from ArrayLength node. //This helper removes unnecessary implied conversion from ArrayLength node.
...@@ -662,14 +707,7 @@ private BoundExpression MakeTruthTestForDynamicLogicalOperator(CSharpSyntaxNode ...@@ -662,14 +707,7 @@ private BoundExpression MakeTruthTestForDynamicLogicalOperator(CSharpSyntaxNode
if (operatorKind.IsLifted()) if (operatorKind.IsLifted())
{ {
if (operatorKind.IsComparison()) return RewriteLiftedBinaryOperator(syntax, operatorKind, loweredLeft, loweredRight, type, method);
{
return LowerLiftedUserDefinedComparisonOperator(syntax, operatorKind, loweredLeft, loweredRight, method);
}
else
{
return LowerLiftedBinaryArithmeticOperator(syntax, operatorKind, loweredLeft, loweredRight, type, method);
}
} }
// Otherwise, nothing special here. // Otherwise, nothing special here.
...@@ -1701,6 +1739,23 @@ private MethodSymbol GetNullableMethod(CSharpSyntaxNode syntax, TypeSymbol nulla ...@@ -1701,6 +1739,23 @@ private MethodSymbol GetNullableMethod(CSharpSyntaxNode syntax, TypeSymbol nulla
type: returnType); type: returnType);
} }
// arr?.Length == null
var conditionalAccess = nullable as BoundLoweredConditionalAccess;
if (conditionalAccess != null &&
(conditionalAccess.WhenNullOpt == null || conditionalAccess.WhenNullOpt.IsDefaultValue()))
{
BoundExpression whenNotNull = RewriteNullableNullEquality(
syntax,
kind,
conditionalAccess.WhenNotNull,
_factory.Null(conditionalAccess.WhenNotNull.Type),
returnType);
var whenNull = kind == BinaryOperatorKind.NullableNullEqual ? MakeBooleanConstant(syntax, true) : null;
return conditionalAccess.Update(conditionalAccess.Receiver, whenNotNull, whenNull, conditionalAccess.Id, whenNotNull.Type);
}
MethodSymbol get_HasValue = GetNullableMethod(syntax, nullable.Type, SpecialMember.System_Nullable_T_get_HasValue); MethodSymbol get_HasValue = GetNullableMethod(syntax, nullable.Type, SpecialMember.System_Nullable_T_get_HasValue);
BoundExpression call = BoundCall.Synthesized(syntax, nullable, get_HasValue); BoundExpression call = BoundCall.Synthesized(syntax, nullable, get_HasValue);
BoundExpression result = kind == BinaryOperatorKind.NullableNullNotEqual ? BoundExpression result = kind == BinaryOperatorKind.NullableNullNotEqual ?
......
...@@ -36,7 +36,7 @@ private BoundExpression VisitCompoundAssignmentOperator(BoundCompoundAssignmentO ...@@ -36,7 +36,7 @@ private BoundExpression VisitCompoundAssignmentOperator(BoundCompoundAssignmentO
(binaryOperator == BinaryOperatorKind.Addition || binaryOperator == BinaryOperatorKind.Subtraction); (binaryOperator == BinaryOperatorKind.Addition || binaryOperator == BinaryOperatorKind.Subtraction);
// save RHS to a temp, we need to use it twice: // save RHS to a temp, we need to use it twice:
if (isPossibleEventHandlerOperation && IntroducingReadCanBeObservable(loweredRight)) if (isPossibleEventHandlerOperation && CanChangeValueBetweenReads(loweredRight))
{ {
BoundAssignmentOperator assignmentToTemp; BoundAssignmentOperator assignmentToTemp;
var temp = _factory.StoreToTemp(loweredRight, out assignmentToTemp); var temp = _factory.StoreToTemp(loweredRight, out assignmentToTemp);
...@@ -188,7 +188,7 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL ...@@ -188,7 +188,7 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL
var prop = (BoundPropertyAccess)originalLHS; var prop = (BoundPropertyAccess)originalLHS;
// If the property is static or if the receiver is of kind "Base" or "this", then we can just generate prop = prop + value // If the property is static or if the receiver is of kind "Base" or "this", then we can just generate prop = prop + value
if (prop.ReceiverOpt == null || prop.PropertySymbol.IsStatic || !IntroducingReadCanBeObservable(prop.ReceiverOpt)) if (prop.ReceiverOpt == null || prop.PropertySymbol.IsStatic || !CanChangeValueBetweenReads(prop.ReceiverOpt))
{ {
return prop; return prop;
} }
...@@ -220,7 +220,7 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL ...@@ -220,7 +220,7 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL
case BoundKind.DynamicMemberAccess: case BoundKind.DynamicMemberAccess:
{ {
var memberAccess = (BoundDynamicMemberAccess)originalLHS; var memberAccess = (BoundDynamicMemberAccess)originalLHS;
if (!IntroducingReadCanBeObservable(memberAccess.Receiver)) if (!CanChangeValueBetweenReads(memberAccess.Receiver))
{ {
return memberAccess; return memberAccess;
} }
...@@ -243,7 +243,7 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL ...@@ -243,7 +243,7 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL
Debug.Assert(receiverOpt != null); Debug.Assert(receiverOpt != null);
BoundExpression transformedReceiver; BoundExpression transformedReceiver;
if (IntroducingReadCanBeObservable(receiverOpt)) if (CanChangeValueBetweenReads(receiverOpt))
{ {
BoundExpression rewrittenReceiver = VisitExpression(receiverOpt); BoundExpression rewrittenReceiver = VisitExpression(receiverOpt);
...@@ -399,7 +399,7 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL ...@@ -399,7 +399,7 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL
BoundExpression receiverOpt = fieldAccess.ReceiverOpt; BoundExpression receiverOpt = fieldAccess.ReceiverOpt;
//If the receiver is static or is the receiver is of kind "Base" or "this", then we can just generate field = field + value //If the receiver is static or is the receiver is of kind "Base" or "this", then we can just generate field = field + value
if (fieldAccess.FieldSymbol.IsStatic || !IntroducingReadCanBeObservable(receiverOpt)) if (fieldAccess.FieldSymbol.IsStatic || !CanChangeValueBetweenReads(receiverOpt))
{ {
return fieldAccess; return fieldAccess;
} }
...@@ -434,7 +434,7 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL ...@@ -434,7 +434,7 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL
var indexerAccess = (BoundDynamicIndexerAccess)originalLHS; var indexerAccess = (BoundDynamicIndexerAccess)originalLHS;
BoundExpression loweredReceiver; BoundExpression loweredReceiver;
if (IntroducingReadCanBeObservable(indexerAccess.ReceiverOpt)) if (CanChangeValueBetweenReads(indexerAccess.ReceiverOpt))
{ {
BoundAssignmentOperator assignmentToTemp; BoundAssignmentOperator assignmentToTemp;
var temp = _factory.StoreToTemp(VisitExpression(indexerAccess.ReceiverOpt), out assignmentToTemp); var temp = _factory.StoreToTemp(VisitExpression(indexerAccess.ReceiverOpt), out assignmentToTemp);
...@@ -452,7 +452,7 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL ...@@ -452,7 +452,7 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL
for (int i = 0; i < arguments.Length; i++) for (int i = 0; i < arguments.Length; i++)
{ {
if (IntroducingReadCanBeObservable(arguments[i])) if (CanChangeValueBetweenReads(arguments[i]))
{ {
BoundAssignmentOperator assignmentToTemp; BoundAssignmentOperator assignmentToTemp;
var temp = _factory.StoreToTemp(VisitExpression(arguments[i]), out assignmentToTemp, refKind: indexerAccess.ArgumentRefKindsOpt.RefKinds(i)); var temp = _factory.StoreToTemp(VisitExpression(arguments[i]), out assignmentToTemp, refKind: indexerAccess.ArgumentRefKindsOpt.RefKinds(i));
...@@ -555,7 +555,7 @@ private BoundExpression BoxReceiver(BoundExpression rewrittenReceiver, NamedType ...@@ -555,7 +555,7 @@ private BoundExpression BoxReceiver(BoundExpression rewrittenReceiver, NamedType
var boundTempIndices = new BoundExpression[loweredIndices.Length]; var boundTempIndices = new BoundExpression[loweredIndices.Length];
for (int i = 0; i < boundTempIndices.Length; i++) for (int i = 0; i < boundTempIndices.Length; i++)
{ {
if (IntroducingReadCanBeObservable(loweredIndices[i])) if (CanChangeValueBetweenReads(loweredIndices[i]))
{ {
BoundAssignmentOperator assignmentToTemp; BoundAssignmentOperator assignmentToTemp;
var temp = _factory.StoreToTemp(loweredIndices[i], out assignmentToTemp); var temp = _factory.StoreToTemp(loweredIndices[i], out assignmentToTemp);
...@@ -583,8 +583,21 @@ private BoundExpression BoxReceiver(BoundExpression rewrittenReceiver, NamedType ...@@ -583,8 +583,21 @@ private BoundExpression BoxReceiver(BoundExpression rewrittenReceiver, NamedType
/// even though l is a local, we must access it via a temp since "foo(ref l)" may change it /// even though l is a local, we must access it via a temp since "foo(ref l)" may change it
/// on between accesses. /// on between accesses.
/// </summary> /// </summary>
internal static bool IntroducingReadCanBeObservable(BoundExpression expression, bool localsMayBeAssignedOrCaptured = true) internal static bool CanChangeValueBetweenReads(
BoundExpression expression,
bool localsMayBeAssignedOrCaptured = true)
{ {
if (expression.IsDefaultValue())
{
return false;
}
if (expression.ConstantValue != null)
{
var type = expression.Type;
return !ConstantValueIsTrivial(type);
}
switch (expression.Kind) switch (expression.Kind)
{ {
case BoundKind.ThisReference: case BoundKind.ThisReference:
...@@ -592,12 +605,8 @@ internal static bool IntroducingReadCanBeObservable(BoundExpression expression, ...@@ -592,12 +605,8 @@ internal static bool IntroducingReadCanBeObservable(BoundExpression expression,
return false; return false;
case BoundKind.Literal: case BoundKind.Literal:
// don't allocate a temp for simple primitive types:
var type = expression.Type; var type = expression.Type;
return (object)type != null && return !ConstantValueIsTrivial(type);
!type.SpecialType.IsClrInteger() &&
!type.IsReferenceType &&
!type.IsEnumType();
case BoundKind.Parameter: case BoundKind.Parameter:
return localsMayBeAssignedOrCaptured || ((BoundParameter)expression).ParameterSymbol.RefKind != RefKind.None; return localsMayBeAssignedOrCaptured || ((BoundParameter)expression).ParameterSymbol.RefKind != RefKind.None;
...@@ -609,5 +618,61 @@ internal static bool IntroducingReadCanBeObservable(BoundExpression expression, ...@@ -609,5 +618,61 @@ internal static bool IntroducingReadCanBeObservable(BoundExpression expression,
return true; return true;
} }
} }
// a simple check for common nonsideeffecting expressions
internal static bool ReadIsSideeffecting(
BoundExpression expression)
{
if (expression.ConstantValue != null)
{
return false;
}
if (expression.IsDefaultValue())
{
return false;
}
switch (expression.Kind)
{
case BoundKind.ThisReference:
case BoundKind.BaseReference:
case BoundKind.Literal:
case BoundKind.Parameter:
case BoundKind.Local:
case BoundKind.Lambda:
return false;
case BoundKind.Conversion:
var conv = (BoundConversion)expression;
return conv.ConversionHasSideEffects() ||
ReadIsSideeffecting(conv.Operand);
case BoundKind.ObjectCreationExpression:
// common production of lowered conversions to nullable
// new S?(arg)
if (expression.Type.IsNullableType())
{
var objCreation = (BoundObjectCreationExpression)expression;
return objCreation.Arguments.Length == 1 && ReadIsSideeffecting(objCreation.Arguments[0]);
}
return false;
default:
return true;
}
}
// nontrivial literals do not change between reads
// but may require re-constructing, so it is better
// to treat them as potentially changing.
private static bool ConstantValueIsTrivial(TypeSymbol type)
{
return (object)type == null ||
type.SpecialType.IsClrInteger() ||
type.IsReferenceType ||
type.IsEnumType();
}
} }
} }
...@@ -20,18 +20,16 @@ public override BoundNode VisitConditionalAccess(BoundConditionalAccess node) ...@@ -20,18 +20,16 @@ public override BoundNode VisitConditionalAccess(BoundConditionalAccess node)
// null when currently enclosing conditional access node // null when currently enclosing conditional access node
// is not supposed to be lowered. // is not supposed to be lowered.
private BoundExpression _currentConditionalAccessTarget = null; private BoundExpression _currentConditionalAccessTarget = null;
private int _currentConditionalAccessID = 0;
private enum ConditionalAccessLoweringKind private enum ConditionalAccessLoweringKind
{ {
None, LoweredConditionalAccess,
NoCapture, Ternary,
CaptureReceiverByVal, TernaryCaptureReceiverByVal
CaptureReceiverByRef,
DuplicateCode
} }
// in simple cases could be left unlowered. // IL gen can generate more compact code for certain conditional accesses
// IL gen can generate more compact code for unlowered conditional accesses
// by utilizing stack dup/pop instructions // by utilizing stack dup/pop instructions
internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, bool used, BoundExpression rewrittenWhenNull = null) internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, bool used, BoundExpression rewrittenWhenNull = null)
{ {
...@@ -40,84 +38,57 @@ internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, b ...@@ -40,84 +38,57 @@ internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, b
var loweredReceiver = this.VisitExpression(node.Receiver); var loweredReceiver = this.VisitExpression(node.Receiver);
var receiverType = loweredReceiver.Type; var receiverType = loweredReceiver.Type;
//TODO: if AccessExpression does not contain awaits, the node could be left unlowered (saves a temp),
// but there seem to be no way of knowing that without walking AccessExpression.
// For now we will just check that we are in an async method, but it would be nice
// to have something more precise.
var isAsync = _factory.CurrentMethod.IsAsync;
ConditionalAccessLoweringKind loweringKind; ConditionalAccessLoweringKind loweringKind;
// CONSIDER: If we knew that loweredReceiver is not a captured local // nullable and dynamic receivers are not directly supported in codegen and need to be lowered
// we could pass "false" for localsMayBeAssignedOrCaptured // in particular nullable receiver implies that the condition of the
// otherwise not capturing receiver into a temp // conditional and the access receiver are actually different expressions
// could introduce additional races into the code if receiver is captured // (HasValue and GetValueOrDefault respectively)
// into a closure and is modified between null check of the receiver var lowerToTernary = receiverType.IsNullableType() || node.AccessExpression.Type.IsDynamic();
// and the actual access.
// if (!lowerToTernary)
// Nullable is special since we are not going to read any part of it twice
// we will read "HasValue" and then, conditionally will read "ValueOrDefault"
// that is no different than just reading both values unconditionally.
// As a result in the case of nullable, not reading captured local through a temp
// does not introduce any additional races so it is irrelevant whether
// the local is captured or not.
var localsMayBeAssignedOrCaptured = !receiverType.IsNullableType();
var needTemp = IntroducingReadCanBeObservable(loweredReceiver, localsMayBeAssignedOrCaptured);
if (!isAsync && !node.AccessExpression.Type.IsDynamic() && rewrittenWhenNull == null &&
(receiverType.IsReferenceType || receiverType.IsTypeParameter() && needTemp))
{
// trivial cases can be handled more efficiently in IL gen
loweringKind = ConditionalAccessLoweringKind.None;
}
else if (needTemp)
{
if (receiverType.IsReferenceType || receiverType.IsNullableType())
{ {
loweringKind = ConditionalAccessLoweringKind.CaptureReceiverByVal; // trivial cases are directly supported in IL gen
loweringKind = ConditionalAccessLoweringKind.LoweredConditionalAccess;
} }
else // Nullable is special since we are not going to read any part of it twice
// we will read "HasValue" and then, conditionally will read "ValueOrDefault"
else if (CanChangeValueBetweenReads(loweredReceiver, !receiverType.IsNullableType()))
{ {
loweringKind = isAsync ? // NOTE: dynamic operations historically do not propagate mutations
ConditionalAccessLoweringKind.DuplicateCode : // to the receiver if that hapens to be a value type
ConditionalAccessLoweringKind.CaptureReceiverByRef; // so we can capture receiver by value in dynamic case regardless of
} // the type of receiver
// Nullable receivers are immutable so should be captured by value as well.
loweringKind = ConditionalAccessLoweringKind.TernaryCaptureReceiverByVal;
} }
else else
{ {
// locals do not need to be captured loweringKind = ConditionalAccessLoweringKind.Ternary;
loweringKind = ConditionalAccessLoweringKind.NoCapture;
} }
var previousConditionalAccesTarget = _currentConditionalAccessTarget; var previousConditionalAccesTarget = _currentConditionalAccessTarget;
var currentConditionalAccessID = ++this._currentConditionalAccessID;
LocalSymbol temp = null; LocalSymbol temp = null;
BoundExpression unconditionalAccess = null; BoundExpression unconditionalAccess = null;
switch (loweringKind) switch (loweringKind)
{ {
case ConditionalAccessLoweringKind.None: case ConditionalAccessLoweringKind.LoweredConditionalAccess:
_currentConditionalAccessTarget = null; _currentConditionalAccessTarget = new BoundConditionalReceiver(
break; loweredReceiver.Syntax,
currentConditionalAccessID,
receiverType);
case ConditionalAccessLoweringKind.NoCapture:
_currentConditionalAccessTarget = loweredReceiver;
break; break;
case ConditionalAccessLoweringKind.DuplicateCode: case ConditionalAccessLoweringKind.Ternary:
_currentConditionalAccessTarget = loweredReceiver; _currentConditionalAccessTarget = loweredReceiver;
unconditionalAccess = used ?
this.VisitExpression(node.AccessExpression) :
this.VisitUnusedExpression(node.AccessExpression);
goto case ConditionalAccessLoweringKind.CaptureReceiverByVal;
case ConditionalAccessLoweringKind.CaptureReceiverByVal:
temp = _factory.SynthesizedLocal(receiverType);
_currentConditionalAccessTarget = _factory.Local(temp);
break; break;
case ConditionalAccessLoweringKind.CaptureReceiverByRef: case ConditionalAccessLoweringKind.TernaryCaptureReceiverByVal:
temp = _factory.SynthesizedLocal(receiverType, refKind: RefKind.Ref); temp = _factory.SynthesizedLocal(receiverType);
_currentConditionalAccessTarget = _factory.Local(temp); _currentConditionalAccessTarget = _factory.Local(temp);
break; break;
...@@ -160,24 +131,29 @@ internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, b ...@@ -160,24 +131,29 @@ internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, b
BoundExpression result; BoundExpression result;
var objectType = _compilation.GetSpecialType(SpecialType.System_Object); var objectType = _compilation.GetSpecialType(SpecialType.System_Object);
rewrittenWhenNull = rewrittenWhenNull ?? _factory.Default(nodeType);
switch (loweringKind) switch (loweringKind)
{ {
case ConditionalAccessLoweringKind.None: case ConditionalAccessLoweringKind.LoweredConditionalAccess:
Debug.Assert(!receiverType.IsValueType); Debug.Assert(!receiverType.IsValueType);
result = node.Update(loweredReceiver, loweredAccessExpression, type); result = new BoundLoweredConditionalAccess(
node.Syntax,
loweredReceiver,
loweredAccessExpression,
rewrittenWhenNull,
currentConditionalAccessID,
type);
break; break;
case ConditionalAccessLoweringKind.CaptureReceiverByVal: case ConditionalAccessLoweringKind.TernaryCaptureReceiverByVal:
// capture the receiver into a temp // capture the receiver into a temp
loweredReceiver = _factory.Sequence( loweredReceiver = _factory.Sequence(
_factory.AssignmentExpression(_factory.Local(temp), loweredReceiver), _factory.AssignmentExpression(_factory.Local(temp), loweredReceiver),
_factory.Local(temp)); _factory.Local(temp));
goto case ConditionalAccessLoweringKind.NoCapture; goto case ConditionalAccessLoweringKind.Ternary;
case ConditionalAccessLoweringKind.NoCapture: case ConditionalAccessLoweringKind.Ternary:
{ {
// (object)r != null ? access : default(T) // (object)r != null ? access : default(T)
var condition = receiverType.IsNullableType() ? var condition = receiverType.IsNullableType() ?
...@@ -191,7 +167,7 @@ internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, b ...@@ -191,7 +167,7 @@ internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, b
result = RewriteConditionalOperator(node.Syntax, result = RewriteConditionalOperator(node.Syntax,
condition, condition,
consequence, consequence,
rewrittenWhenNull, rewrittenWhenNull ?? _factory.Default(nodeType),
null, null,
nodeType); nodeType);
...@@ -202,73 +178,6 @@ internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, b ...@@ -202,73 +178,6 @@ internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, b
} }
break; break;
case ConditionalAccessLoweringKind.CaptureReceiverByRef:
// {ref T r; T v;
// r = ref receiver;
// (isClass && { v = r; r = ref v; v == null } ) ?
// null;
// r.Foo()}
{
var v = _factory.SynthesizedLocal(receiverType);
BoundExpression captureRef = _factory.AssignmentExpression(_factory.Local(temp), loweredReceiver, refKind: RefKind.Ref);
BoundExpression isNull = _factory.LogicalAnd(
IsClass(receiverType, objectType),
_factory.Sequence(
_factory.AssignmentExpression(_factory.Local(v), _factory.Local(temp)),
_factory.AssignmentExpression(_factory.Local(temp), _factory.Local(v), RefKind.Ref),
_factory.ObjectEqual(_factory.Convert(objectType, _factory.Local(v)), _factory.Null(objectType)))
);
result = RewriteConditionalOperator(node.Syntax,
isNull,
rewrittenWhenNull,
loweredAccessExpression,
null,
nodeType);
result = _factory.Sequence(
ImmutableArray.Create(temp, v),
captureRef,
result
);
}
break;
case ConditionalAccessLoweringKind.DuplicateCode:
{
Debug.Assert(!receiverType.IsNullableType());
// if we have a class, do regular conditional access via a val temp
loweredReceiver = _factory.AssignmentExpression(_factory.Local(temp), loweredReceiver);
BoundExpression ifClass = RewriteConditionalOperator(node.Syntax,
_factory.ObjectNotEqual(
_factory.Convert(objectType, loweredReceiver),
_factory.Null(objectType)),
loweredAccessExpression,
rewrittenWhenNull,
null,
nodeType);
if (temp != null)
{
ifClass = _factory.Sequence(temp, ifClass);
}
// if we have a struct, then just access unconditionally
BoundExpression ifStruct = unconditionalAccess;
// (object)(default(T)) != null ? ifStruct: ifClass
result = RewriteConditionalOperator(node.Syntax,
IsClass(receiverType, objectType),
ifClass,
ifStruct,
null,
nodeType);
}
break;
default: default:
throw ExceptionUtilities.Unreachable; throw ExceptionUtilities.Unreachable;
} }
...@@ -276,23 +185,13 @@ internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, b ...@@ -276,23 +185,13 @@ internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, b
return result; return result;
} }
private BoundBinaryOperator IsClass(TypeSymbol receiverType, NamedTypeSymbol objectType)
{
return _factory.ObjectEqual(
_factory.Convert(objectType, _factory.Default(receiverType)),
_factory.Null(objectType));
}
public override BoundNode VisitConditionalReceiver(BoundConditionalReceiver node) public override BoundNode VisitConditionalReceiver(BoundConditionalReceiver node)
{ {
if (_currentConditionalAccessTarget == null)
{
return node;
}
var newtarget = _currentConditionalAccessTarget; var newtarget = _currentConditionalAccessTarget;
if (newtarget.Type.IsNullableType()) if (newtarget.Type.IsNullableType())
{ {
Debug.Assert(newtarget.Kind != BoundKind.ConditionalReceiver);
newtarget = MakeOptimizedGetValueOrDefault(node.Syntax, newtarget); newtarget = MakeOptimizedGetValueOrDefault(node.Syntax, newtarget);
} }
......
...@@ -73,7 +73,7 @@ private BoundExpression RewriteWindowsRuntimeEventAssignmentOperator(CSharpSynta ...@@ -73,7 +73,7 @@ private BoundExpression RewriteWindowsRuntimeEventAssignmentOperator(CSharpSynta
{ {
BoundAssignmentOperator tempAssignment = null; BoundAssignmentOperator tempAssignment = null;
BoundLocal boundTemp = null; BoundLocal boundTemp = null;
if (!eventSymbol.IsStatic && IntroducingReadCanBeObservable(rewrittenReceiverOpt)) if (!eventSymbol.IsStatic && CanChangeValueBetweenReads(rewrittenReceiverOpt))
{ {
boundTemp = _factory.StoreToTemp(rewrittenReceiverOpt, out tempAssignment); boundTemp = _factory.StoreToTemp(rewrittenReceiverOpt, out tempAssignment);
} }
......
...@@ -48,9 +48,7 @@ private BoundNode FuseNodes(BoundConditionalAccess conditionalAccess, BoundNullC ...@@ -48,9 +48,7 @@ private BoundNode FuseNodes(BoundConditionalAccess conditionalAccess, BoundNullC
private bool TryGetOptimizableNullableConditionalAccess(BoundExpression operand, out BoundConditionalAccess conditionalAccess) private bool TryGetOptimizableNullableConditionalAccess(BoundExpression operand, out BoundConditionalAccess conditionalAccess)
{ {
if (operand.Kind != BoundKind.ConditionalAccess || if (operand.Kind != BoundKind.ConditionalAccess || _inExpressionLambda )
_inExpressionLambda ||
_factory.CurrentMethod.IsAsync)
{ {
conditionalAccess = null; conditionalAccess = null;
return false; return false;
...@@ -107,6 +105,38 @@ private bool TryGetOptimizableNullableConditionalAccess(BoundExpression operand, ...@@ -107,6 +105,38 @@ private bool TryGetOptimizableNullableConditionalAccess(BoundExpression operand,
return new BoundNullCoalescingOperator(syntax, rewrittenLeft, rewrittenRight, Conversion.Identity, rewrittenResultType); return new BoundNullCoalescingOperator(syntax, rewrittenLeft, rewrittenRight, Conversion.Identity, rewrittenResultType);
} }
if (leftConversion.IsIdentity || leftConversion.Kind == ConversionKind.ExplicitNullable)
{
var conditionalAccess = rewrittenLeft as BoundLoweredConditionalAccess;
if (conditionalAccess != null &&
(conditionalAccess.WhenNullOpt == null || NullableNeverHasValue(conditionalAccess.WhenNullOpt)))
{
var notNullAccess = NullableAlwaysHasValue(conditionalAccess.WhenNotNull);
if (notNullAccess != null)
{
var whenNullOpt = rewrittenRight;
if (whenNullOpt.Type.IsNullableType())
{
notNullAccess = conditionalAccess.WhenNotNull;
}
if (whenNullOpt.IsDefaultValue() && whenNullOpt.Type.SpecialType != SpecialType.System_Decimal)
{
whenNullOpt = null;
}
return conditionalAccess.Update(
conditionalAccess.Receiver,
whenNotNull: notNullAccess,
whenNullOpt: whenNullOpt,
id: conditionalAccess.Id,
type: rewrittenResultType
);
}
}
}
// We lower left ?? right to // We lower left ?? right to
// //
// var temp = left; // var temp = left;
......
...@@ -333,7 +333,7 @@ private BoundExpression MakeCollectionInitializer(BoundExpression rewrittenRecei ...@@ -333,7 +333,7 @@ private BoundExpression MakeCollectionInitializer(BoundExpression rewrittenRecei
var pointerAccess = (BoundPointerElementAccess)assignment.Left; var pointerAccess = (BoundPointerElementAccess)assignment.Left;
var rewrittenIndex = VisitExpression(pointerAccess.Index); var rewrittenIndex = VisitExpression(pointerAccess.Index);
if (IntroducingReadCanBeObservable(rewrittenIndex)) if (CanChangeValueBetweenReads(rewrittenIndex))
{ {
BoundAssignmentOperator store; BoundAssignmentOperator store;
var temp = _factory.StoreToTemp(rewrittenIndex, out store); var temp = _factory.StoreToTemp(rewrittenIndex, out store);
...@@ -378,7 +378,7 @@ private BoundExpression MakeCollectionInitializer(BoundExpression rewrittenRecei ...@@ -378,7 +378,7 @@ private BoundExpression MakeCollectionInitializer(BoundExpression rewrittenRecei
{ {
var arg = args[i]; var arg = args[i];
if (IntroducingReadCanBeObservable(arg)) if (CanChangeValueBetweenReads(arg))
{ {
if (newArgs == null) if (newArgs == null)
{ {
......
...@@ -486,6 +486,11 @@ public BoundBinaryOperator LogicalAnd(BoundExpression left, BoundExpression righ ...@@ -486,6 +486,11 @@ public BoundBinaryOperator LogicalAnd(BoundExpression left, BoundExpression righ
return Binary(BinaryOperatorKind.LogicalBoolAnd, SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Boolean), left, right); return Binary(BinaryOperatorKind.LogicalBoolAnd, SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Boolean), left, right);
} }
public BoundBinaryOperator LogicalOr(BoundExpression left, BoundExpression right)
{
return Binary(BinaryOperatorKind.LogicalBoolOr, SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Boolean), left, right);
}
public BoundBinaryOperator IntEqual(BoundExpression left, BoundExpression right) public BoundBinaryOperator IntEqual(BoundExpression left, BoundExpression right)
{ {
return Binary(BinaryOperatorKind.IntEqual, SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Boolean), left, right); return Binary(BinaryOperatorKind.IntEqual, SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Boolean), left, right);
...@@ -594,6 +599,12 @@ public BoundExpression Conditional(BoundExpression condition, BoundExpression co ...@@ -594,6 +599,12 @@ public BoundExpression Conditional(BoundExpression condition, BoundExpression co
return new BoundConditionalOperator(Syntax, condition, consequence, alternative, default(ConstantValue), type) { WasCompilerGenerated = true }; return new BoundConditionalOperator(Syntax, condition, consequence, alternative, default(ConstantValue), type) { WasCompilerGenerated = true };
} }
public BoundExpression ComplexConditionalReceiver(BoundExpression valueTypeReceiver, BoundExpression referenceTypeReceiver)
{
Debug.Assert(valueTypeReceiver.Type == referenceTypeReceiver.Type);
return new BoundComplexConditionalReceiver(Syntax, valueTypeReceiver, referenceTypeReceiver, valueTypeReceiver.Type) { WasCompilerGenerated = true };
}
public BoundExpression Coalesce(BoundExpression left, BoundExpression right) public BoundExpression Coalesce(BoundExpression left, BoundExpression right)
{ {
Debug.Assert(left.Type.Equals(right.Type, ignoreCustomModifiers: true)); Debug.Assert(left.Type.Equals(right.Type, ignoreCustomModifiers: true));
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册