提交 a8d7f098 编写于 作者: V VSadov

Eliminated "DuplicateCode" mode of lowering

上级 b8e47524
......@@ -979,7 +979,18 @@
<Node Name="BoundConditionalReceiver" Base="BoundExpression">
<Field Name="Type" Type="TypeSymbol" Override="true" Null="disallow"/>
</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">
<!-- SPEC: A method group is a set of overloaded methods resulting from a member lookup.
SPEC: A method group may have an associated instance expression and
......
// 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:
EmitComplexConditionalReceiver((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 EmitComplexConditionalReceiver(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;
}
......
// 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;
......@@ -221,6 +222,10 @@ private void EmitExpression(BoundExpression expression, bool used)
EmitConditionalReceiver((BoundConditionalReceiver)expression, used);
break;
case BoundKind.ComplexConditionalReceiver:
EmitComplexConditionalReceiver((BoundComplexConditionalReceiver)expression, used);
break;
case BoundKind.PseudoVariable:
EmitPseudoVariableValue((BoundPseudoVariable)expression, used);
break;
......@@ -234,6 +239,30 @@ private void EmitExpression(BoundExpression 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;
......
......@@ -1235,6 +1235,29 @@ public override BoundNode VisitLoweredConditionalAccess(BoundLoweredConditionalA
return node.Update(receiver, whenNotNull, whenNull, 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)
{
// checked(-x) is emitted as "0 - x"
......
......@@ -2004,6 +2004,20 @@ 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,127 @@ 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 whenNull = VisitExpression(ref whenNullBuilder, node.WhenNullOpt);
if (whenNotNullBuilder == null && whenNullBuilder == null)
{
return UpdateExpression(receiverBuilder, node.Update(receiver, whenNotNull, whenNull, 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);
receiverBuilder.AddStatement(
_F.If(condition,
wnenNotNullStatement,
UpdateStatement(whenNullBuilder, _F.ExpressionStatement(whenNull), substituteTemps: false)));
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);
receiverBuilder.AddLocal(tmp, _F.Diagnostics);
receiverBuilder.AddStatement(
_F.If(condition,
wnenNotNullStatement,
UpdateStatement(whenNullBuilder, _F.Assignment(_F.Local(tmp), whenNull), substituteTemps: false)));
return receiverBuilder.Update(_F.Local(tmp));
}
}
private class ConditionalReceiverReplacer: BoundTreeRewriter
{
private readonly BoundExpression _receiver;
#if DEBUG
// we must replace exatly one node
private int _replaced;
#endif
private ConditionalReceiverReplacer(BoundExpression receiver)
{
this._receiver = receiver;
}
public static BoundStatement Replace(BoundNode node, BoundExpression receiver)
{
var replacer = new ConditionalReceiverReplacer(receiver);
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 DEBUG
_replaced++;
#endif
return _receiver;
}
}
public override BoundNode VisitObjectCreationExpression(BoundObjectCreationExpression node)
{
Debug.Assert(node.InitializerExpressionOpt == null);
......
......@@ -23,11 +23,9 @@ public override BoundNode VisitConditionalAccess(BoundConditionalAccess node)
private enum ConditionalAccessLoweringKind
{
None,
NoCapture,
CaptureReceiverByVal,
CaptureReceiverByRef,
DuplicateCode
LoweredConditionalAccess,
Ternary,
TernaryCaptureReceiverByVal
}
// IL gen can generate more compact code for certain conditional accesses
......@@ -39,52 +37,28 @@ 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() &&
(receiverType.IsReferenceType || receiverType.IsTypeParameter() && needTemp))
var lowerToTernary = receiverType.IsNullableType() || node.AccessExpression.Type.IsDynamic();
if (!lowerToTernary)
{
// trivial cases can be handled more efficiently in IL gen
loweringKind = ConditionalAccessLoweringKind.None;
// trivial cases are directly supported in IL gen
loweringKind = ConditionalAccessLoweringKind.LoweredConditionalAccess;
}
else if (needTemp)
// 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 (IntroducingReadCanBeObservable(loweredReceiver, !receiverType.IsNullableType()))
{
if (receiverType.IsReferenceType || receiverType.IsNullableType())
{
loweringKind = ConditionalAccessLoweringKind.CaptureReceiverByVal;
}
else
{
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;
}
......@@ -94,32 +68,19 @@ internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, b
switch (loweringKind)
{
case ConditionalAccessLoweringKind.None:
case ConditionalAccessLoweringKind.LoweredConditionalAccess:
_currentConditionalAccessTarget = null;
break;
case ConditionalAccessLoweringKind.NoCapture:
case ConditionalAccessLoweringKind.Ternary:
_currentConditionalAccessTarget = loweredReceiver;
break;
case ConditionalAccessLoweringKind.DuplicateCode:
_currentConditionalAccessTarget = loweredReceiver;
unconditionalAccess = used ?
this.VisitExpression(node.AccessExpression) :
this.VisitUnusedExpression(node.AccessExpression);
goto case ConditionalAccessLoweringKind.CaptureReceiverByVal;
case ConditionalAccessLoweringKind.CaptureReceiverByVal:
case ConditionalAccessLoweringKind.TernaryCaptureReceiverByVal:
temp = _factory.SynthesizedLocal(receiverType);
_currentConditionalAccessTarget = _factory.Local(temp);
break;
case ConditionalAccessLoweringKind.CaptureReceiverByRef:
temp = _factory.SynthesizedLocal(receiverType, refKind: RefKind.Ref);
_currentConditionalAccessTarget = _factory.Local(temp);
break;
default:
throw ExceptionUtilities.UnexpectedValue(loweringKind);
}
......@@ -163,7 +124,7 @@ internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, b
switch (loweringKind)
{
case ConditionalAccessLoweringKind.None:
case ConditionalAccessLoweringKind.LoweredConditionalAccess:
Debug.Assert(!receiverType.IsValueType);
result = new BoundLoweredConditionalAccess(
node.Syntax,
......@@ -173,15 +134,15 @@ internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, b
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() ?
......@@ -206,73 +167,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;
}
......@@ -280,13 +174,6 @@ 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)
......
......@@ -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));
......
......@@ -2834,6 +2834,87 @@ static async Task<int> Val()
False");
}
[Fact]
public void ConditionalMemberAccessUnConstrainedAsyncValExt()
{
var source = @"
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using DE;
namespace DE
{
public static class IDispExt
{
public static void DisposeExt(this Program.IDisposable1 d, int i)
{
d.Dispose(i);
}
}
}
public class Program
{
public interface IDisposable1
{
int Dispose(int i);
}
class C1 : IDisposable1
{
private bool disposed;
public int Dispose(int i)
{
System.Console.WriteLine(disposed);
disposed = true;
return 1;
}
}
struct S1 : IDisposable1
{
private bool disposed;
public int Dispose(int i)
{
System.Console.WriteLine(disposed);
disposed = true;
return 1;
}
}
static void Main(string[] args)
{
C1 c = new C1();
Test(c, c).Wait();
S1 s = new S1();
Test(s, s).Wait();
}
static async Task<int> Val()
{
await Task.Yield();
return 0;
}
static async Task<int> Test<T>(T x, T y) where T : IDisposable1
{
x?.DisposeExt(await Val());
y?.DisposeExt(await Val());
return 1;
}
}
";
var c = CreateCompilationWithMscorlib45(source, new[] { SystemRef_v4_0_30319_17929, SystemCoreRef_v4_0_30319_17929, CSharpRef }, TestOptions.ReleaseExe);
var comp = CompileAndVerify(c, expectedOutput: @"False
True
False
False");
}
[Fact]
public void ConditionalMemberAccessUnConstrainedAsyncNested()
{
......@@ -2850,7 +2931,7 @@ interface IDisposable1
}
class C1 : IDisposable1
{
{
private bool disposed;
public IDisposable1 Dispose(int i)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册