提交 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
receiverType = receiverType.GetNullableUnderlyingType();
}
receiver = new BoundConditionalReceiver(receiver.Syntax, receiverType) { WasCompilerGenerated = true };
receiver = new BoundConditionalReceiver(receiver.Syntax, 0, receiverType) { WasCompilerGenerated = true };
return receiver;
}
......
......@@ -332,6 +332,36 @@ public override bool SuppressVirtualCalls
{
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
......
......@@ -968,9 +968,38 @@
<Field Name="AccessExpression" Type="BoundExpression"/>
</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 -->
<Node Name="BoundConditionalReceiver" Base="BoundExpression">
<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 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.
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CodeGen;
......@@ -47,6 +48,10 @@ private LocalDefinition EmitAddress(BoundExpression expression, AddressKind addr
Debug.Assert(!expression.Type.IsValueType);
break;
case BoundKind.ComplexConditionalReceiver:
EmitComplexConditionalReceiverAddress((BoundComplexConditionalReceiver)expression);
break;
case BoundKind.Parameter:
EmitParameterAddress((BoundParameter)expression);
break;
......@@ -95,6 +100,31 @@ private LocalDefinition EmitAddress(BoundExpression expression, AddressKind addr
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)
{
var local = localAccess.LocalSymbol;
......@@ -260,6 +290,14 @@ private bool HasHome(BoundExpression expression)
case BoundKind.Sequence:
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:
return false;
}
......
......@@ -26,7 +26,7 @@ private void EmitConversionExpression(BoundConversion conversion, bool used)
return;
}
if (!used && !ConversionHasSideEffects(conversion))
if (!used && !conversion.ConversionHasSideEffects())
{
EmitExpression(conversion.Operand, false); // just do expr side effects
return;
......@@ -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)
{
// 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.
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
......@@ -213,14 +214,18 @@ private void EmitExpression(BoundExpression expression, bool used)
EmitRefValueOperator((BoundRefValueOperator)expression, used);
break;
case BoundKind.ConditionalAccess:
EmitConditionalAccessExpression((BoundConditionalAccess)expression, used);
case BoundKind.LoweredConditionalAccess:
EmitLoweredConditionalAccessExpression((BoundLoweredConditionalAccess)expression, used);
break;
case BoundKind.ConditionalReceiver:
EmitConditionalReceiver((BoundConditionalReceiver)expression, used);
break;
case BoundKind.ComplexConditionalReceiver:
EmitComplexConditionalReceiver((BoundComplexConditionalReceiver)expression, used);
break;
case BoundKind.PseudoVariable:
EmitPseudoVariableValue((BoundPseudoVariable)expression, used);
break;
......@@ -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;
......@@ -253,7 +282,7 @@ private void EmitConditionalAccessExpression(BoundConditionalAccess expression,
{
// const but not default
receiverTemp = EmitReceiverRef(receiver, isAccessConstrained: !receiverType.IsReferenceType);
EmitExpression(expression.AccessExpression, used);
EmitExpression(expression.WhenNotNull, used);
if (receiverTemp != null)
{
FreeTemp(receiverTemp);
......@@ -269,7 +298,7 @@ private void EmitConditionalAccessExpression(BoundConditionalAccess expression,
// 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 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) ||
(receiver.Kind == BoundKind.Local && IsStackLocal(((BoundLocal)receiver).LocalSymbol));
......@@ -329,7 +358,16 @@ private void EmitConditionalAccessExpression(BoundConditionalAccess expression,
_builder.EmitOpCode(ILOpCode.Pop);
}
var whenNull = expression.WhenNullOpt;
if (whenNull == null)
{
EmitDefaultValue(expression.Type, used, expression.Syntax);
}
else
{
EmitExpression(whenNull, used);
}
_builder.EmitBranch(ILOpCode.Br, doneLabel);
if (nullCheckOnCopy)
......@@ -356,7 +394,7 @@ private void EmitConditionalAccessExpression(BoundConditionalAccess expression,
Debug.Assert(receiverTemp == null);
}
EmitExpression(expression.AccessExpression, used);
EmitExpression(expression.WhenNotNull, used);
_builder.MarkLabel(doneLabel);
if (temp != null)
......
......@@ -418,17 +418,18 @@ private void EmitCondBranch(BoundExpression condition, ref object dest, bool sen
// then it is regular binary expression - Or, And, Xor ...
goto default;
case BoundKind.ConditionalAccess:
case BoundKind.LoweredConditionalAccess:
{
var ca = (BoundConditionalAccess)condition;
var ca = (BoundLoweredConditionalAccess)condition;
var receiver = ca.Receiver;
var receiverType = receiver.Type;
// we need a copy if we deal with nonlocal value (to capture the value)
// or if we deal with stack local (reads are destructive)
var complexCase = !receiverType.IsReferenceType ||
LocalRewriter.IntroducingReadCanBeObservable(receiver, localsMayBeAssignedOrCaptured: false) ||
(receiver.Kind == BoundKind.Local && IsStackLocal(((BoundLocal)receiver).LocalSymbol));
LocalRewriter.CanChangeValueBetweenReads(receiver, localsMayBeAssignedOrCaptured: false) ||
(receiver.Kind == BoundKind.Local && IsStackLocal(((BoundLocal)receiver).LocalSymbol)) ||
(ca.WhenNullOpt?.IsDefaultValue() == false) ;
if (complexCase)
{
......@@ -445,7 +446,7 @@ private void EmitCondBranch(BoundExpression condition, ref object dest, bool sen
EmitCondBranch(receiver, ref fallThrough, sense: false);
EmitReceiverRef(receiver, isAccessConstrained: false);
EmitCondBranch(ca.AccessExpression, ref dest, sense: true);
EmitCondBranch(ca.WhenNotNull, ref dest, sense: true);
if (fallThrough != null)
{
......@@ -458,7 +459,7 @@ private void EmitCondBranch(BoundExpression condition, ref object dest, bool sen
// gotoif(!receiver.Access) labDest
EmitCondBranch(receiver, ref dest, sense: false);
EmitReceiverRef(receiver, isAccessConstrained: false);
condition = ca.AccessExpression;
condition = ca.WhenNotNull;
goto oneMoreTime;
}
}
......
......@@ -1210,7 +1210,7 @@ public override BoundNode VisitNullCoalescingOperator(BoundNullCoalescingOperato
return node.Update(left, right, node.LeftConversion, node.Type);
}
public override BoundNode VisitConditionalAccess(BoundConditionalAccess node)
public override BoundNode VisitLoweredConditionalAccess(BoundLoweredConditionalAccess node)
{
var origStack = _evalStack;
BoundExpression receiver = VisitCallReceiver(node.Receiver);
......@@ -1220,11 +1220,46 @@ public override BoundNode VisitConditionalAccess(BoundConditionalAccess node)
// right is evaluated with original stack
// (this is not entirely true, codegen will keep receiver on the stack, but that is irrelevant here)
_evalStack = origStack;
BoundExpression access = (BoundExpression)this.Visit(node.AccessExpression);
BoundExpression whenNotNull = (BoundExpression)this.Visit(node.WhenNotNull);
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)
......
......@@ -272,10 +272,15 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState,
// build argument
arguments[i] = F.Convert(manager.System_Object,
new BoundConditionalAccess(F.Syntax,
new BoundLoweredConditionalAccess(F.Syntax,
F.Field(F.This(), property.BackingField),
F.Call(new BoundConditionalReceiver(F.Syntax, property.BackingField.Type), manager.System_Object__ToString),
manager.System_String),
F.Call(new BoundConditionalReceiver(
F.Syntax,
id: i,
type: property.BackingField.Type), manager.System_Object__ToString),
null,
id: i,
type: manager.System_String),
ConversionKind.ImplicitReference);
}
formatString.Builder.Append(" }}");
......
......@@ -1980,11 +1980,44 @@ public override BoundNode VisitConditionalAccess(BoundConditionalAccess node)
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)
{
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)
{
var sideEffects = node.SideEffects;
......
......@@ -392,6 +392,11 @@ private BoundStatement UpdateStatement(BoundSpillSequenceBuilder builder, BoundS
case BoundKind.TypeExpression:
return expression;
case BoundKind.ConditionalReceiver:
// we will rewrite this as a part of rewriting whole LoweredConditionalAccess
// later, if needed
return expression;
default:
if (expression.Type.SpecialType == SpecialType.System_Void || sideEffectsOnly)
{
......@@ -881,6 +886,135 @@ public override BoundNode VisitNullCoalescingOperator(BoundNullCoalescingOperato
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)
{
Debug.Assert(node.InitializerExpressionOpt == null);
......
......@@ -177,7 +177,7 @@ private BoundExpression VisitExpressionImpl(BoundExpression node)
if (constantValue != null)
{
TypeSymbol type = node.Type;
if (((object)type == null || !type.IsNullableType()))
if (type?.IsNullableType() != true)
{
return MakeLiteral(node.Syntax, constantValue, type);
}
......
......@@ -74,7 +74,7 @@ private BoundExpression TryFoldWithConditionalAccess(BoundBinaryOperator node)
// reading rightAlwaysHasValue should not have sideeffects here
// 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;
accessExpression = node.Update(unliftedOperatorKind, accessExpression, rightAlwaysHasValue, null, node.MethodOpt, node.ResultKind, node.Type);
......@@ -250,7 +250,11 @@ public BoundExpression VisitBinaryOperator(BoundBinaryOperator node, BoundUnaryO
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);
......@@ -264,23 +268,17 @@ public BoundExpression VisitBinaryOperator(BoundBinaryOperator node, BoundUnaryO
return _dynamicFactory.MakeDynamicBinaryOperator(operatorKind, loweredLeft, loweredRight, isCompoundAssignment, type).ToExpression();
}
}
else if (operatorKind.IsUserDefined())
{
return LowerUserDefinedBinaryOperator(syntax, operatorKind, loweredLeft, loweredRight, type, method);
}
else if (operatorKind.IsLifted())
{
if (operatorKind.IsComparison())
if (operatorKind.IsLifted())
{
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())
{
case BinaryOperatorKind.NullableNullEqual:
......@@ -477,6 +475,53 @@ public BoundExpression VisitBinaryOperator(BoundBinaryOperator node, BoundUnaryO
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.
//Sometimes the conversion is not necessary - i.e. when we just check for 0
//This helper removes unnecessary implied conversion from ArrayLength node.
......@@ -662,14 +707,7 @@ private BoundExpression MakeTruthTestForDynamicLogicalOperator(CSharpSyntaxNode
if (operatorKind.IsLifted())
{
if (operatorKind.IsComparison())
{
return LowerLiftedUserDefinedComparisonOperator(syntax, operatorKind, loweredLeft, loweredRight, method);
}
else
{
return LowerLiftedBinaryArithmeticOperator(syntax, operatorKind, loweredLeft, loweredRight, type, method);
}
return RewriteLiftedBinaryOperator(syntax, operatorKind, loweredLeft, loweredRight, type, method);
}
// Otherwise, nothing special here.
......@@ -1701,6 +1739,23 @@ private MethodSymbol GetNullableMethod(CSharpSyntaxNode syntax, TypeSymbol nulla
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);
BoundExpression call = BoundCall.Synthesized(syntax, nullable, get_HasValue);
BoundExpression result = kind == BinaryOperatorKind.NullableNullNotEqual ?
......
......@@ -36,7 +36,7 @@ private BoundExpression VisitCompoundAssignmentOperator(BoundCompoundAssignmentO
(binaryOperator == BinaryOperatorKind.Addition || binaryOperator == BinaryOperatorKind.Subtraction);
// save RHS to a temp, we need to use it twice:
if (isPossibleEventHandlerOperation && IntroducingReadCanBeObservable(loweredRight))
if (isPossibleEventHandlerOperation && CanChangeValueBetweenReads(loweredRight))
{
BoundAssignmentOperator assignmentToTemp;
var temp = _factory.StoreToTemp(loweredRight, out assignmentToTemp);
......@@ -188,7 +188,7 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL
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 (prop.ReceiverOpt == null || prop.PropertySymbol.IsStatic || !IntroducingReadCanBeObservable(prop.ReceiverOpt))
if (prop.ReceiverOpt == null || prop.PropertySymbol.IsStatic || !CanChangeValueBetweenReads(prop.ReceiverOpt))
{
return prop;
}
......@@ -220,7 +220,7 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL
case BoundKind.DynamicMemberAccess:
{
var memberAccess = (BoundDynamicMemberAccess)originalLHS;
if (!IntroducingReadCanBeObservable(memberAccess.Receiver))
if (!CanChangeValueBetweenReads(memberAccess.Receiver))
{
return memberAccess;
}
......@@ -243,7 +243,7 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL
Debug.Assert(receiverOpt != null);
BoundExpression transformedReceiver;
if (IntroducingReadCanBeObservable(receiverOpt))
if (CanChangeValueBetweenReads(receiverOpt))
{
BoundExpression rewrittenReceiver = VisitExpression(receiverOpt);
......@@ -399,7 +399,7 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL
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 (fieldAccess.FieldSymbol.IsStatic || !IntroducingReadCanBeObservable(receiverOpt))
if (fieldAccess.FieldSymbol.IsStatic || !CanChangeValueBetweenReads(receiverOpt))
{
return fieldAccess;
}
......@@ -434,7 +434,7 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL
var indexerAccess = (BoundDynamicIndexerAccess)originalLHS;
BoundExpression loweredReceiver;
if (IntroducingReadCanBeObservable(indexerAccess.ReceiverOpt))
if (CanChangeValueBetweenReads(indexerAccess.ReceiverOpt))
{
BoundAssignmentOperator assignmentToTemp;
var temp = _factory.StoreToTemp(VisitExpression(indexerAccess.ReceiverOpt), out assignmentToTemp);
......@@ -452,7 +452,7 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL
for (int i = 0; i < arguments.Length; i++)
{
if (IntroducingReadCanBeObservable(arguments[i]))
if (CanChangeValueBetweenReads(arguments[i]))
{
BoundAssignmentOperator assignmentToTemp;
var temp = _factory.StoreToTemp(VisitExpression(arguments[i]), out assignmentToTemp, refKind: indexerAccess.ArgumentRefKindsOpt.RefKinds(i));
......@@ -555,7 +555,7 @@ private BoundExpression BoxReceiver(BoundExpression rewrittenReceiver, NamedType
var boundTempIndices = new BoundExpression[loweredIndices.Length];
for (int i = 0; i < boundTempIndices.Length; i++)
{
if (IntroducingReadCanBeObservable(loweredIndices[i]))
if (CanChangeValueBetweenReads(loweredIndices[i]))
{
BoundAssignmentOperator assignmentToTemp;
var temp = _factory.StoreToTemp(loweredIndices[i], out assignmentToTemp);
......@@ -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
/// on between accesses.
/// </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)
{
case BoundKind.ThisReference:
......@@ -592,12 +605,8 @@ internal static bool IntroducingReadCanBeObservable(BoundExpression expression,
return false;
case BoundKind.Literal:
// don't allocate a temp for simple primitive types:
var type = expression.Type;
return (object)type != null &&
!type.SpecialType.IsClrInteger() &&
!type.IsReferenceType &&
!type.IsEnumType();
return !ConstantValueIsTrivial(type);
case BoundKind.Parameter:
return localsMayBeAssignedOrCaptured || ((BoundParameter)expression).ParameterSymbol.RefKind != RefKind.None;
......@@ -609,5 +618,61 @@ internal static bool IntroducingReadCanBeObservable(BoundExpression expression,
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)
// null when currently enclosing conditional access node
// is not supposed to be lowered.
private BoundExpression _currentConditionalAccessTarget = null;
private int _currentConditionalAccessID = 0;
private enum ConditionalAccessLoweringKind
{
None,
NoCapture,
CaptureReceiverByVal,
CaptureReceiverByRef,
DuplicateCode
LoweredConditionalAccess,
Ternary,
TernaryCaptureReceiverByVal
}
// in simple cases could be left unlowered.
// IL gen can generate more compact code for unlowered conditional accesses
// IL gen can generate more compact code for certain conditional accesses
// by utilizing stack dup/pop instructions
internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, bool used, BoundExpression rewrittenWhenNull = null)
{
......@@ -40,84 +38,57 @@ internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, b
var loweredReceiver = this.VisitExpression(node.Receiver);
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;
// CONSIDER: If we knew that loweredReceiver is not a captured local
// we could pass "false" for localsMayBeAssignedOrCaptured
// otherwise not capturing receiver into a temp
// could introduce additional races into the code if receiver is captured
// into a closure and is modified between null check of the receiver
// and the actual access.
//
// 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())
// nullable and dynamic receivers are not directly supported in codegen and need to be lowered
// in particular nullable receiver implies that the condition of the
// conditional and the access receiver are actually different expressions
// (HasValue and GetValueOrDefault respectively)
var lowerToTernary = receiverType.IsNullableType() || node.AccessExpression.Type.IsDynamic();
if (!lowerToTernary)
{
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 ?
ConditionalAccessLoweringKind.DuplicateCode :
ConditionalAccessLoweringKind.CaptureReceiverByRef;
}
// NOTE: dynamic operations historically do not propagate mutations
// to the receiver if that hapens to be a value type
// 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
{
// locals do not need to be captured
loweringKind = ConditionalAccessLoweringKind.NoCapture;
loweringKind = ConditionalAccessLoweringKind.Ternary;
}
var previousConditionalAccesTarget = _currentConditionalAccessTarget;
var currentConditionalAccessID = ++this._currentConditionalAccessID;
LocalSymbol temp = null;
BoundExpression unconditionalAccess = null;
switch (loweringKind)
{
case ConditionalAccessLoweringKind.None:
_currentConditionalAccessTarget = null;
break;
case ConditionalAccessLoweringKind.LoweredConditionalAccess:
_currentConditionalAccessTarget = new BoundConditionalReceiver(
loweredReceiver.Syntax,
currentConditionalAccessID,
receiverType);
case ConditionalAccessLoweringKind.NoCapture:
_currentConditionalAccessTarget = loweredReceiver;
break;
case ConditionalAccessLoweringKind.DuplicateCode:
case ConditionalAccessLoweringKind.Ternary:
_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;
case ConditionalAccessLoweringKind.CaptureReceiverByRef:
temp = _factory.SynthesizedLocal(receiverType, refKind: RefKind.Ref);
case ConditionalAccessLoweringKind.TernaryCaptureReceiverByVal:
temp = _factory.SynthesizedLocal(receiverType);
_currentConditionalAccessTarget = _factory.Local(temp);
break;
......@@ -160,24 +131,29 @@ internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, b
BoundExpression result;
var objectType = _compilation.GetSpecialType(SpecialType.System_Object);
rewrittenWhenNull = rewrittenWhenNull ?? _factory.Default(nodeType);
switch (loweringKind)
{
case ConditionalAccessLoweringKind.None:
case ConditionalAccessLoweringKind.LoweredConditionalAccess:
Debug.Assert(!receiverType.IsValueType);
result = node.Update(loweredReceiver, loweredAccessExpression, type);
result = new BoundLoweredConditionalAccess(
node.Syntax,
loweredReceiver,
loweredAccessExpression,
rewrittenWhenNull,
currentConditionalAccessID,
type);
break;
case ConditionalAccessLoweringKind.CaptureReceiverByVal:
case ConditionalAccessLoweringKind.TernaryCaptureReceiverByVal:
// capture the receiver into a temp
loweredReceiver = _factory.Sequence(
_factory.AssignmentExpression(_factory.Local(temp), loweredReceiver),
_factory.Local(temp));
goto case ConditionalAccessLoweringKind.NoCapture;
goto case ConditionalAccessLoweringKind.Ternary;
case ConditionalAccessLoweringKind.NoCapture:
case ConditionalAccessLoweringKind.Ternary:
{
// (object)r != null ? access : default(T)
var condition = receiverType.IsNullableType() ?
......@@ -191,7 +167,7 @@ internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, b
result = RewriteConditionalOperator(node.Syntax,
condition,
consequence,
rewrittenWhenNull,
rewrittenWhenNull ?? _factory.Default(nodeType),
null,
nodeType);
......@@ -202,73 +178,6 @@ internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, b
}
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:
throw ExceptionUtilities.Unreachable;
}
......@@ -276,23 +185,13 @@ internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, b
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)
{
if (_currentConditionalAccessTarget == null)
{
return node;
}
var newtarget = _currentConditionalAccessTarget;
if (newtarget.Type.IsNullableType())
{
Debug.Assert(newtarget.Kind != BoundKind.ConditionalReceiver);
newtarget = MakeOptimizedGetValueOrDefault(node.Syntax, newtarget);
}
......
......@@ -73,7 +73,7 @@ private BoundExpression RewriteWindowsRuntimeEventAssignmentOperator(CSharpSynta
{
BoundAssignmentOperator tempAssignment = null;
BoundLocal boundTemp = null;
if (!eventSymbol.IsStatic && IntroducingReadCanBeObservable(rewrittenReceiverOpt))
if (!eventSymbol.IsStatic && CanChangeValueBetweenReads(rewrittenReceiverOpt))
{
boundTemp = _factory.StoreToTemp(rewrittenReceiverOpt, out tempAssignment);
}
......
......@@ -48,9 +48,7 @@ private BoundNode FuseNodes(BoundConditionalAccess conditionalAccess, BoundNullC
private bool TryGetOptimizableNullableConditionalAccess(BoundExpression operand, out BoundConditionalAccess conditionalAccess)
{
if (operand.Kind != BoundKind.ConditionalAccess ||
_inExpressionLambda ||
_factory.CurrentMethod.IsAsync)
if (operand.Kind != BoundKind.ConditionalAccess || _inExpressionLambda )
{
conditionalAccess = null;
return false;
......@@ -107,6 +105,38 @@ private bool TryGetOptimizableNullableConditionalAccess(BoundExpression operand,
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
//
// var temp = left;
......
......@@ -333,7 +333,7 @@ private BoundExpression MakeCollectionInitializer(BoundExpression rewrittenRecei
var pointerAccess = (BoundPointerElementAccess)assignment.Left;
var rewrittenIndex = VisitExpression(pointerAccess.Index);
if (IntroducingReadCanBeObservable(rewrittenIndex))
if (CanChangeValueBetweenReads(rewrittenIndex))
{
BoundAssignmentOperator store;
var temp = _factory.StoreToTemp(rewrittenIndex, out store);
......@@ -378,7 +378,7 @@ private BoundExpression MakeCollectionInitializer(BoundExpression rewrittenRecei
{
var arg = args[i];
if (IntroducingReadCanBeObservable(arg))
if (CanChangeValueBetweenReads(arg))
{
if (newArgs == null)
{
......
......@@ -486,6 +486,11 @@ public BoundBinaryOperator LogicalAnd(BoundExpression left, BoundExpression righ
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)
{
return Binary(BinaryOperatorKind.IntEqual, SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Boolean), left, right);
......@@ -594,6 +599,12 @@ public BoundExpression Conditional(BoundExpression condition, BoundExpression co
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)
{
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.
先完成此消息的编辑!
想要评论请 注册