提交 ad2bd29c 编写于 作者: T TomasMatousek

Preparation for hoisting more locals in Debug builds. I noticed we can use...

Preparation for hoisting more locals in Debug builds. I noticed we can use less maps while rewriting a method to a class.

     MethodToClassRewriter:
     - parameterMap could be moved up to LambdaRewriter since it's only needed when rewriting lambdas.
     - variablesCaptured is replaced by a virtual NeedsProxy method which is implemented on LambdaRewriter and StateMachineRewriter using existing maps (so we can save another HashSet).
        We can also distinguish between "captured" variables and variables we create proxy field for. The former are deduced from syntax. A variable may be captured but not lifted into a field (e.g. expression tree lambda parameter). Or a variable can be lifted to field but not captured (in Debug builds we are going to lift user defined variables that are not captured to allow their later capture during EnC).

     LambdaRewriter:
     - variablesCaptured encodes the same information as keys of captured syntax multi-dictionary.
     - declaredInsideExpressionLambda is not needed either. When visiting ET lambdas we used to add their parameters to both variableBlock and declaredInsideExpressionLambda maps. Since ET lambda parameters are never lifted to closure we can avoid adding them to variableBlock map instead of excluding them later via declaredInsideExpressionLambda lookup.

     Adds internal IReadOnlySet similar to IReadOnlyList and IReadOnlyDictionary so that we can specify an intent of not mutating a set. (changeset 1317999)
上级 6e6163c9
......@@ -58,7 +58,7 @@ internal sealed class AsyncMethodToStateMachineRewriter : MethodToStateMachineRe
SyntheticBoundNodeFactory F,
FieldSymbol state,
FieldSymbol builder,
HashSet<Symbol> variablesCaptured,
IReadOnlySet<Symbol> variablesCaptured,
IReadOnlyDictionary<Symbol, CapturedSymbolReplacement> nonReusableLocalProxies,
DiagnosticBag diagnostics)
: base(F, method, state, variablesCaptured, nonReusableLocalProxies, diagnostics, useFinalizerBookkeeping: false)
......
......@@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp
{
......@@ -52,7 +53,7 @@ internal sealed partial class IteratorMethodToStateMachineRewriter : MethodToSta
MethodSymbol originalMethod,
FieldSymbol state,
FieldSymbol current,
HashSet<Symbol> variablesCaptured,
IReadOnlySet<Symbol> variablesCaptured,
IReadOnlyDictionary<Symbol, CapturedSymbolReplacement> initialProxies,
DiagnosticBag diagnostics)
: base(F, originalMethod, state, variablesCaptured, initialProxies, diagnostics, useFinalizerBookkeeping: false)
......
......@@ -21,7 +21,7 @@ internal sealed class Analysis : BoundTreeWalker
private readonly MethodSymbol topLevelMethod;
private MethodSymbol currentParent;
private BoundNode currentBlock;
private BoundNode currentScope;
// Some syntactic forms have an "implicit" receiver. When we encounter them, we set this to the
// syntax. That way, in case we need to report an error about the receiver, we can use this
......@@ -39,36 +39,26 @@ internal sealed class Analysis : BoundTreeWalker
public bool SeenLambda { get; private set; }
/// <summary>
/// For each statement that defines variables, identifies the nearest enclosing statement that defines variables.
/// For each scope that defines variables, identifies the nearest enclosing scope that defines variables.
/// </summary>
public readonly Dictionary<BoundNode, BoundNode> blockParent = new Dictionary<BoundNode, BoundNode>();
public readonly Dictionary<BoundNode, BoundNode> scopeParent = new Dictionary<BoundNode, BoundNode>();
/// <summary>
/// For each captured variable, identifies the statement in which it will be moved to a frame class. This is
/// normally the block where the variable is introduced, but method parameters are moved
/// For each captured variable, identifies the scope in which it will be moved to a frame class. This is
/// normally the node where the variable is introduced, but method parameters are moved
/// to a frame class within the body of the method.
/// </summary>
public readonly Dictionary<Symbol, BoundNode> variableBlock = new Dictionary<Symbol, BoundNode>();
/// <summary>
/// The set of captured variables seen in the method body.
/// </summary>
public readonly HashSet<Symbol> variablesCaptured = new HashSet<Symbol>();
public readonly Dictionary<Symbol, BoundNode> variableScope = new Dictionary<Symbol, BoundNode>();
/// <summary>
/// The syntax nodes associated with each captured variable.
/// </summary>
public readonly MultiDictionary<Symbol, CSharpSyntaxNode> capturedSyntax = new MultiDictionary<Symbol, CSharpSyntaxNode>();
/// <summary>
/// The set of variables that were declared anywhere inside an expression lambda.
/// </summary>
public readonly HashSet<Symbol> declaredInsideExpressionLambda = new HashSet<Symbol>();
public readonly MultiDictionary<Symbol, CSharpSyntaxNode> capturedVariables = new MultiDictionary<Symbol, CSharpSyntaxNode>();
/// <summary>
/// For each lambda in the code, the set of variables that it captures.
/// </summary>
public readonly MultiDictionary<LambdaSymbol, Symbol> captures = new MultiDictionary<LambdaSymbol, Symbol>();
public readonly MultiDictionary<LambdaSymbol, Symbol> capturedVariablesByLambda = new MultiDictionary<LambdaSymbol, Symbol>();
/// <summary>
/// Blocks that are positioned between a block declaring some lifted variables
......@@ -97,6 +87,8 @@ internal sealed class Analysis : BoundTreeWalker
private Analysis(MethodSymbol method)
{
Debug.Assert((object)method != null);
this.currentParent = this.topLevelMethod = method;
}
......@@ -109,16 +101,15 @@ public static Analysis Analyze(BoundNode node, MethodSymbol method)
private void Analyze(BoundNode node)
{
currentBlock = FindNodeToAnalyze(node);
currentScope = FindNodeToAnalyze(node);
Debug.Assert(!inExpressionLambda);
Debug.Assert((object)topLevelMethod != null);
if ((object)topLevelMethod != null)
foreach (ParameterSymbol parameter in topLevelMethod.Parameters)
{
foreach (ParameterSymbol parameter in topLevelMethod.Parameters)
{
// parameters are counted as if they are inside the block
variableBlock[parameter] = currentBlock;
if (inExpressionLambda) declaredInsideExpressionLambda.Add(parameter);
}
// parameters are counted as if they are inside the block
variableScope[parameter] = currentScope;
}
Visit(node);
......@@ -161,7 +152,7 @@ internal void ComputeLambdaScopesAndFrameCaptures()
lambdaScopes = new Dictionary<LambdaSymbol, BoundNode>(ReferenceEqualityComparer.Instance);
needsParentFrame = new HashSet<BoundNode>();
foreach (var lambda in captures.Keys)
foreach (var lambda in capturedVariablesByLambda.Keys)
{
// get innermost and outermost scopes from which a lambda captures
......@@ -171,12 +162,12 @@ internal void ComputeLambdaScopesAndFrameCaptures()
int outermostScopeDepth = int.MaxValue;
BoundNode outermostScope = null;
foreach (var v in captures[lambda])
foreach (var variables in capturedVariablesByLambda[lambda])
{
BoundNode curBlock = null;
int curBlockDepth;
if (!variableBlock.TryGetValue(v, out curBlock))
if (!variableScope.TryGetValue(variables, out curBlock))
{
// this is something that is not defined in a block, like "Me"
// Since it is defined outside of the method, the depth is -1
......@@ -217,7 +208,7 @@ internal void ComputeLambdaScopesAndFrameCaptures()
while (innermostScope != outermostScope)
{
needsParentFrame.Add(innermostScope);
blockParent.TryGetValue(innermostScope, out innermostScope);
scopeParent.TryGetValue(innermostScope, out innermostScope);
}
}
}
......@@ -234,7 +225,7 @@ private int BlockDepth(BoundNode node)
while (node != null)
{
result = result + 1;
if (!blockParent.TryGetValue(node, out node))
if (!scopeParent.TryGetValue(node, out node))
{
break;
}
......@@ -260,18 +251,20 @@ public override BoundNode VisitCatchBlock(BoundCatchBlock node)
private BoundNode PushBlock(BoundNode node, ImmutableArray<LocalSymbol> locals)
{
var previousBlock = currentBlock;
currentBlock = node;
if (currentBlock != previousBlock) // not top-level node of the method
// blocks are not allowed in expression lambda
Debug.Assert(!inExpressionLambda);
var previousBlock = currentScope;
currentScope = node;
if (currentScope != previousBlock) // not top-level node of the method
{
// (Except for the top-level block) record the parent-child block structure
blockParent[currentBlock] = previousBlock;
scopeParent[currentScope] = previousBlock;
}
foreach (var local in locals)
{
variableBlock[local] = currentBlock;
if (inExpressionLambda) declaredInsideExpressionLambda.Add(local);
variableScope[local] = currentScope;
}
return previousBlock;
......@@ -279,7 +272,7 @@ private BoundNode PushBlock(BoundNode node, ImmutableArray<LocalSymbol> locals)
private void PopBlock(BoundNode previousBlock)
{
currentBlock = previousBlock;
currentScope = previousBlock;
}
public override BoundNode VisitSwitchStatement(BoundSwitchStatement node)
......@@ -336,30 +329,31 @@ public override BoundNode VisitLambda(BoundLambda node)
Debug.Assert((object)node.Symbol != null);
SeenLambda = true;
var oldParent = currentParent;
var oldBlock = currentBlock;
var oldBlock = currentScope;
currentParent = node.Symbol;
currentBlock = node.Body;
blockParent[currentBlock] = oldBlock;
currentScope = node.Body;
scopeParent[currentScope] = oldBlock;
var wasInExpressionLambda = inExpressionLambda;
inExpressionLambda = inExpressionLambda || node.Type.IsExpressionTree();
// for the purpose of constructing frames parameters are scoped as if they are inside the lambda block
foreach (var parameter in node.Symbol.Parameters)
if (!inExpressionLambda)
{
variableBlock[parameter] = currentBlock;
if (inExpressionLambda) declaredInsideExpressionLambda.Add(parameter);
}
// for the purpose of constructing frames parameters are scoped as if they are inside the lambda block
foreach (var parameter in node.Symbol.Parameters)
{
variableScope[parameter] = currentScope;
}
foreach (var local in node.Body.Locals)
{
variableBlock[local] = currentBlock;
if (inExpressionLambda) declaredInsideExpressionLambda.Add(local);
foreach (var local in node.Body.Locals)
{
variableScope[local] = currentScope;
}
}
var result = base.VisitBlock(node.Body);
inExpressionLambda = wasInExpressionLambda;
currentParent = oldParent;
currentBlock = oldBlock;
currentScope = oldBlock;
return result;
}
......@@ -375,13 +369,12 @@ private void ReferenceVariable(CSharpSyntaxNode syntax, Symbol symbol)
LambdaSymbol lambda = currentParent as LambdaSymbol;
if ((object)lambda != null && symbol.ContainingSymbol != lambda)
{
variablesCaptured.Add(symbol);
capturedSyntax.Add(symbol, syntax);
capturedVariables.Add(symbol, syntax);
// mark the variable as captured in each enclosing lambda up to the variable's point of declaration.
for (; (object)lambda != null && symbol.ContainingSymbol != lambda; lambda = lambda.ContainingSymbol as LambdaSymbol)
{
captures.Add(lambda, symbol);
capturedVariablesByLambda.Add(lambda, symbol);
}
}
}
......
......@@ -52,11 +52,14 @@ namespace Microsoft.CodeAnalysis.CSharp
/// the returned bound node. For example, the caller will typically perform iterator method and
/// asynchronous method transformations, and emit IL instructions into an assembly.
/// </summary>
partial class LambdaRewriter : MethodToClassRewriter
sealed partial class LambdaRewriter : MethodToClassRewriter
{
private readonly Analysis analysis;
private readonly MethodSymbol topLevelMethod;
// A mapping from every lambda parameter to its corresponding method's parameter.
private readonly Dictionary<ParameterSymbol, ParameterSymbol> parameterMap = new Dictionary<ParameterSymbol, ParameterSymbol>();
// for each block with lifted (captured) variables, the corresponding frame type
private readonly Dictionary<BoundNode, LambdaFrame> frames = new Dictionary<BoundNode, LambdaFrame>();
......@@ -114,7 +117,7 @@ partial class LambdaRewriter : MethodToClassRewriter
TypeCompilationState compilationState,
DiagnosticBag diagnostics,
bool assignLocals)
: base(compilationState, analysis.variablesCaptured, diagnostics)
: base(compilationState, diagnostics)
{
Debug.Assert(analysis != null);
Debug.Assert(thisType != null);
......@@ -134,6 +137,12 @@ partial class LambdaRewriter : MethodToClassRewriter
this.synthesizedFieldNameIdDispenser = 1;
}
protected override bool NeedsProxy(Symbol localOrParameter)
{
Debug.Assert(localOrParameter is LocalSymbol || localOrParameter is ParameterSymbol);
return analysis.capturedVariables.ContainsKey(localOrParameter);
}
/// <summary>
/// Rewrite the given node to eliminate lambda expressions. Also returned are the method symbols and their
/// bound bodies for the extracted lambda bodies. These would typically be emitted by the caller such as
......@@ -214,7 +223,6 @@ protected override NamedTypeSymbol ContainingType
/// Check that the top-level node is well-defined, in the sense that all
/// locals that are used are defined in some enclosing scope.
/// </summary>
/// <param name="node"></param>
static partial void CheckLocalsDefined(BoundNode node);
/// <summary>
......@@ -224,11 +232,10 @@ private void MakeFrames()
{
NamedTypeSymbol containingType = this.ContainingType;
foreach (Symbol captured in analysis.variablesCaptured)
foreach (Symbol captured in analysis.capturedVariables.Keys)
{
BoundNode node;
if (!analysis.variableBlock.TryGetValue(captured, out node) ||
analysis.declaredInsideExpressionLambda.Contains(captured))
if (!analysis.variableScope.TryGetValue(captured, out node))
{
continue;
}
......@@ -254,7 +261,7 @@ private void MakeFrames()
if (hoistedField.Type.IsRestrictedType())
{
foreach (CSharpSyntaxNode syntax in analysis.capturedSyntax[captured])
foreach (CSharpSyntaxNode syntax in analysis.capturedVariables[captured])
{
// CS4013: Instance of type '{0}' cannot be used inside an anonymous function, query expression, iterator block or async method
this.Diagnostics.Add(ErrorCode.ERR_SpecialByRefInLambda, syntax.Location, hoistedField.Type);
......@@ -387,12 +394,10 @@ private T IntroduceFrame<T>(BoundNode node, LambdaFrame frame, Func<ArrayBuilder
// Capture any parameters of this block. This would typically occur
// at the top level of a method or lambda with captured parameters.
// TODO: speed up the following by computing it in analysis.
foreach (var variable in analysis.variablesCaptured)
foreach (var variable in analysis.capturedVariables.Keys)
{
BoundNode varNode;
if (!analysis.variableBlock.TryGetValue(variable, out varNode) ||
varNode != node ||
analysis.declaredInsideExpressionLambda.Contains(variable))
if (!analysis.variableScope.TryGetValue(variable, out varNode) || varNode != node)
{
continue;
}
......@@ -474,6 +479,17 @@ private void InitVariableProxy(CSharpSyntaxNode syntax, Symbol symbol, LocalSymb
#region Visit Methods
protected override BoundNode VisitUnhoistedParameter(BoundParameter node)
{
ParameterSymbol replacementParameter;
if (this.parameterMap.TryGetValue(node.ParameterSymbol, out replacementParameter))
{
return new BoundParameter(node.Syntax, replacementParameter, replacementParameter.Type, node.HasErrors);
}
return base.VisitUnhoistedParameter(node);
}
public override BoundNode VisitThisReference(BoundThisReference node)
{
// "topLevelMethod.ThisParameter == null" can occur in a delegate creation expression because the method group
......@@ -530,7 +546,7 @@ public override BoundNode VisitCall(BoundCall node)
private BoundSequence RewriteSequence(BoundSequence node, ArrayBuilder<BoundExpression> prologue, ArrayBuilder<LocalSymbol> newLocals)
{
AddLocals(node.Locals, newLocals);
RewriteLocals(node.Locals, newLocals);
foreach (var expr in node.SideEffects)
{
......@@ -559,9 +575,9 @@ public override BoundNode VisitBlock(BoundBlock node)
}
}
protected BoundBlock RewriteBlock(BoundBlock node, ArrayBuilder<BoundExpression> prologue, ArrayBuilder<LocalSymbol> newLocals)
private BoundBlock RewriteBlock(BoundBlock node, ArrayBuilder<BoundExpression> prologue, ArrayBuilder<LocalSymbol> newLocals)
{
AddLocals(node.Locals, newLocals);
RewriteLocals(node.Locals, newLocals);
var newStatements = ArrayBuilder<BoundStatement>.GetInstance();
......@@ -604,7 +620,7 @@ public override BoundNode VisitCatchBlock(BoundCatchBlock node)
private BoundNode RewriteCatch(BoundCatchBlock node, ArrayBuilder<BoundExpression> prologue, ArrayBuilder<LocalSymbol> newLocals)
{
AddLocals(node.Locals, newLocals);
RewriteLocals(node.Locals, newLocals);
var rewrittenCatchLocals = newLocals.ToImmutableAndFree();
// If exception variable got lifted, IntroduceFrame will give us frame init prologue.
......@@ -784,21 +800,22 @@ private BoundNode RewriteLambdaConversion(BoundLambda node)
}
// Move the body of the lambda to a freshly generated synthetic method on its frame.
bool lambdaIsStatic = analysis.captures[node.Symbol].IsEmpty();
bool lambdaIsStatic = analysis.capturedVariablesByLambda[node.Symbol].IsEmpty();
var synthesizedMethod = new SynthesizedLambdaMethod(translatedLambdaContainer, topLevelMethod, node, lambdaIsStatic, CompilationState);
if (CompilationState.Emitting)
{
CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(translatedLambdaContainer, synthesizedMethod);
}
foreach(var parameter in node.Symbol.Parameters)
foreach (var parameter in node.Symbol.Parameters)
{
var ordinal = parameter.Ordinal;
if (lambdaIsStatic)
{
// adjust for a dummy "this" parameter at the beginning of synthesized method signature
ordinal += 1;
ordinal++;
}
parameterMap.Add(parameter, synthesizedMethod.Parameters[ordinal]);
}
......@@ -887,7 +904,7 @@ private BoundNode RewriteLambdaConversion(BoundLambda node)
// NOTE: We require "lambdaScope != null".
// We do not want to introduce a field into an actual user's class (not a synthetic frame).
var shouldCacheInLoop = lambdaScope != null &&
lambdaScope != analysis.blockParent[node.Body] &&
lambdaScope != analysis.scopeParent[node.Body] &&
InLoopOrLambda(node.Syntax, lambdaScope.Syntax);
if (shouldCacheForStaticMethod || shouldCacheInLoop)
......
......@@ -13,10 +13,6 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols
/// </summary>
internal abstract partial class MethodToClassRewriter : BoundTreeRewriter
{
// A mapping from every lambda parameter to its corresponding method's parameter. This map remains
// empty if we are performing something other than a lambda transformation (e.g. async or iterator).
protected readonly Dictionary<ParameterSymbol, ParameterSymbol> parameterMap = new Dictionary<ParameterSymbol, ParameterSymbol>();
// For each captured variable, information about its replacement. May be populated lazily (that is, not all
// upfront) by subclasses. Specifically, the async rewriter produces captured symbols for temps, including
// ref locals, lazily.
......@@ -27,11 +23,6 @@ internal abstract partial class MethodToClassRewriter : BoundTreeRewriter
// though its containing method is not correct because the code is moved into another method)
protected readonly Dictionary<LocalSymbol, LocalSymbol> localMap = new Dictionary<LocalSymbol, LocalSymbol>();
/// <summary>
/// The set of captured variables seen in the method body.
/// </summary>
private readonly HashSet<Symbol> variablesCaptured = new HashSet<Symbol>();
// A mapping for types in the original method to types in its replacement. This is mainly necessary
// when the original method was generic, as type parameters in the original method are mapping into
// type parameters of the resulting class.
......@@ -50,23 +41,18 @@ internal abstract partial class MethodToClassRewriter : BoundTreeRewriter
protected readonly DiagnosticBag Diagnostics;
protected MethodToClassRewriter(TypeCompilationState compilationState, HashSet<Symbol> variablesCaptured, DiagnosticBag diagnostics)
protected MethodToClassRewriter(TypeCompilationState compilationState, DiagnosticBag diagnostics)
{
Debug.Assert(compilationState != null);
Debug.Assert(diagnostics != null);
this.CompilationState = compilationState;
this.Diagnostics = diagnostics;
this.variablesCaptured = variablesCaptured;
}
protected bool IsCaptured(Symbol localOrParameter)
{
Debug.Assert(localOrParameter is LocalSymbol || localOrParameter is ParameterSymbol);
return variablesCaptured.Contains(localOrParameter);
}
protected abstract bool NeedsProxy(Symbol localOrParameter);
protected void AddLocals(ImmutableArray<LocalSymbol> locals, ArrayBuilder<LocalSymbol> newLocals)
protected void RewriteLocals(ImmutableArray<LocalSymbol> locals, ArrayBuilder<LocalSymbol> newLocals)
{
foreach (var local in locals)
{
......@@ -80,7 +66,7 @@ protected void AddLocals(ImmutableArray<LocalSymbol> locals, ArrayBuilder<LocalS
private bool TryRewriteLocal(LocalSymbol local, out LocalSymbol newLocal)
{
if (IsCaptured(local))
if (NeedsProxy(local))
{
// no longer a local symbol
newLocal = null;
......@@ -106,11 +92,11 @@ private bool TryRewriteLocal(LocalSymbol local, out LocalSymbol newLocal)
return true;
}
ImmutableArray<LocalSymbol> RewriteLocalList(ImmutableArray<LocalSymbol> locals)
private ImmutableArray<LocalSymbol> RewriteLocals(ImmutableArray<LocalSymbol> locals)
{
if (locals.IsEmpty) return locals;
var newLocals = ArrayBuilder<LocalSymbol>.GetInstance();
AddLocals(locals, newLocals);
RewriteLocals(locals, newLocals);
return newLocals.ToImmutableAndFree();
}
......@@ -120,7 +106,7 @@ public override BoundNode VisitCatchBlock(BoundCatchBlock node)
{
// Yield/await aren't supported in catch block atm, but we need to rewrite the type
// of the variables owned by the catch block. Note that one of these variables might be a closure frame reference.
var newLocals = RewriteLocalList(node.Locals);
var newLocals = RewriteLocals(node.Locals);
return node.Update(
newLocals,
......@@ -135,14 +121,14 @@ public override BoundNode VisitCatchBlock(BoundCatchBlock node)
public override BoundNode VisitBlock(BoundBlock node)
{
var newLocals = RewriteLocalList(node.Locals);
var newLocals = RewriteLocals(node.Locals);
var newStatements = VisitList(node.Statements);
return node.Update(newLocals, newStatements);
}
public override BoundNode VisitSequence(BoundSequence node)
{
var newLocals = RewriteLocalList(node.Locals);
var newLocals = RewriteLocals(node.Locals);
var newSideEffects = VisitList<BoundExpression>(node.SideEffects);
var newValue = (BoundExpression)this.Visit(node.Value);
var newType = this.VisitType(node.Type);
......@@ -151,8 +137,8 @@ public override BoundNode VisitSequence(BoundSequence node)
public override BoundNode VisitSwitchStatement(BoundSwitchStatement node)
{
var newOuterLocals = RewriteLocalList(node.OuterLocals);
var newInnerLocals = RewriteLocalList(node.InnerLocals);
var newOuterLocals = RewriteLocals(node.OuterLocals);
var newInnerLocals = RewriteLocals(node.InnerLocals);
BoundExpression boundExpression = (BoundExpression)this.Visit(node.BoundExpression);
ImmutableArray<BoundSwitchSection> switchSections = (ImmutableArray<BoundSwitchSection>)this.VisitList(node.SwitchSections);
return node.Update(newOuterLocals, boundExpression, node.ConstantTargetOpt, newInnerLocals, switchSections, node.BreakLabel, node.StringEquality);
......@@ -160,8 +146,8 @@ public override BoundNode VisitSwitchStatement(BoundSwitchStatement node)
public override BoundNode VisitForStatement(BoundForStatement node)
{
var newOuterLocals = RewriteLocalList(node.OuterLocals);
var newInnerLocals = RewriteLocalList(node.InnerLocals);
var newOuterLocals = RewriteLocals(node.OuterLocals);
var newInnerLocals = RewriteLocals(node.InnerLocals);
BoundStatement initializer = (BoundStatement)this.Visit(node.Initializer);
BoundExpression condition = (BoundExpression)this.Visit(node.Condition);
BoundStatement increment = (BoundStatement)this.Visit(node.Increment);
......@@ -171,7 +157,7 @@ public override BoundNode VisitForStatement(BoundForStatement node)
public override BoundNode VisitUsingStatement(BoundUsingStatement node)
{
var newLocals = RewriteLocalList(node.Locals);
var newLocals = RewriteLocals(node.Locals);
BoundMultipleLocalDeclarations declarationsOpt = (BoundMultipleLocalDeclarations)this.Visit(node.DeclarationsOpt);
BoundExpression expressionOpt = (BoundExpression)this.Visit(node.ExpressionOpt);
BoundStatement body = (BoundStatement)this.Visit(node.Body);
......@@ -289,32 +275,52 @@ private MethodSymbol GetOrCreateBaseFunctionWrapper(MethodSymbol methodBeingWrap
return wrapper;
}
public override BoundNode VisitParameter(BoundParameter node)
private bool TryReplaceWithProxy(Symbol parameterOrLocal, CSharpSyntaxNode syntax, out BoundNode replacement)
{
CapturedSymbolReplacement proxy;
if (proxies.TryGetValue(node.ParameterSymbol, out proxy))
if (proxies.TryGetValue(parameterOrLocal, out proxy))
{
return proxy.Replacement(node.Syntax, frameType => FramePointer(node.Syntax, frameType));
replacement = proxy.Replacement(syntax, frameType => FramePointer(syntax, frameType));
return true;
}
ParameterSymbol replacementParameter;
if (this.parameterMap.TryGetValue(node.ParameterSymbol, out replacementParameter))
replacement = null;
return false;
}
public sealed override BoundNode VisitParameter(BoundParameter node)
{
BoundNode replacement;
if (TryReplaceWithProxy(node.ParameterSymbol, node.Syntax, out replacement))
{
return new BoundParameter(node.Syntax, replacementParameter, replacementParameter.Type, node.HasErrors);
return replacement;
}
// Non-captured and expression tree lambda parameters don't have a proxy.
return VisitUnhoistedParameter(node);
}
protected virtual BoundNode VisitUnhoistedParameter(BoundParameter node)
{
return base.VisitParameter(node);
}
public override BoundNode VisitLocal(BoundLocal node)
public sealed override BoundNode VisitLocal(BoundLocal node)
{
CapturedSymbolReplacement proxy;
if (proxies.TryGetValue(node.LocalSymbol, out proxy))
BoundNode replacement;
if (TryReplaceWithProxy(node.LocalSymbol, node.Syntax, out replacement))
{
return proxy.Replacement(node.Syntax, frameType => FramePointer(node.Syntax, frameType));
return replacement;
}
Debug.Assert(!IsCaptured(node.LocalSymbol));
// if a local needs a proxy it should have been allocated by its declaration node.
Debug.Assert(!NeedsProxy(node.LocalSymbol));
return VisitUnhoistedLocal(node);
}
private BoundNode VisitUnhoistedLocal(BoundLocal node)
{
LocalSymbol replacementLocal;
if (this.localMap.TryGetValue(node.LocalSymbol, out replacementLocal))
{
......@@ -346,15 +352,15 @@ public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node)
if (leftLocal.LocalSymbol.RefKind != RefKind.None &&
node.RefKind != RefKind.None &&
IsCaptured(leftLocal.LocalSymbol))
NeedsProxy(leftLocal.LocalSymbol))
{
Debug.Assert(!proxies.ContainsKey(leftLocal.LocalSymbol)); // we need to create the proxy at this time
Debug.Assert(!proxies.ContainsKey(leftLocal.LocalSymbol));
Debug.Assert(!IsStackAlloc(originalRight));
//spilling ref local variables
throw ExceptionUtilities.Unreachable;
}
if (IsCaptured(leftLocal.LocalSymbol) && !proxies.ContainsKey(leftLocal.LocalSymbol))
if (NeedsProxy(leftLocal.LocalSymbol) && !proxies.ContainsKey(leftLocal.LocalSymbol))
{
Debug.Assert(leftLocal.LocalSymbol.DeclarationKind == LocalDeclarationKind.None);
// spilling temp variables
......
......@@ -16,18 +16,18 @@ internal abstract class MethodToStateMachineRewriter : MethodToClassRewriter
SyntheticBoundNodeFactory F,
MethodSymbol originalMethod,
FieldSymbol state,
HashSet<Symbol> variablesCaptured,
IReadOnlySet<Symbol> variablesCaptured,
IReadOnlyDictionary<Symbol, CapturedSymbolReplacement> nonReusableLocalProxies,
DiagnosticBag diagnostics,
bool useFinalizerBookkeeping)
: base(F.CompilationState, variablesCaptured, diagnostics)
: base(F.CompilationState, diagnostics)
{
Debug.Assert(F != null);
Debug.Assert(originalMethod != null);
Debug.Assert(state != null);
Debug.Assert(variablesCaptured != null);
Debug.Assert(nonReusableLocalProxies != null);
Debug.Assert(diagnostics != null);
Debug.Assert(variablesCaptured != null);
this.F = F;
this.stateField = state;
......@@ -35,6 +35,7 @@ internal abstract class MethodToStateMachineRewriter : MethodToClassRewriter
this.useFinalizerBookkeeping = useFinalizerBookkeeping;
this.hasFinalizerState = useFinalizerBookkeeping;
this.originalMethod = originalMethod;
this.variablesCaptured = variablesCaptured;
foreach (var proxy in nonReusableLocalProxies)
{
......@@ -42,6 +43,7 @@ internal abstract class MethodToStateMachineRewriter : MethodToClassRewriter
}
}
/// <summary>
/// True if we need to generate the code to do the bookkeeping so we can "finalize" the state machine
/// by executing code from its current state through the enclosing finally blocks. This is true for
......@@ -117,6 +119,19 @@ internal abstract class MethodToStateMachineRewriter : MethodToClassRewriter
/// </summary>
private EmptyStructTypeCache emptyStructTypeCache = new EmptyStructTypeCache(null);
/// <summary>
/// The set of captured variables seen in the method body.
/// It's the minimal set of variables that have to be hoisted since their def-use arc crosses await/yield.
/// Other variables may be hoisted to improve debugging experience.
/// </summary>
private readonly IReadOnlySet<Symbol> variablesCaptured;
protected override bool NeedsProxy(Symbol localOrParameter)
{
Debug.Assert(localOrParameter.Kind == SymbolKind.Local || localOrParameter.Kind == SymbolKind.Parameter);
return variablesCaptured.Contains(localOrParameter);
}
protected override TypeMap TypeMap
{
get { return ((SynthesizedContainer)F.CurrentClass).TypeMap; }
......@@ -185,7 +200,7 @@ public override BoundNode VisitSequence(BoundSequence node)
// statement level by the AwaitLiftingRewriter.
foreach (var local in node.Locals)
{
Debug.Assert(!IsCaptured(local) || proxies.ContainsKey(local));
Debug.Assert(!NeedsProxy(local) || proxies.ContainsKey(local));
}
return base.VisitSequence(node);
......@@ -208,7 +223,7 @@ private BoundStatement PossibleIteratorScope(ImmutableArray<LocalSymbol> locals,
var hoistedUserDefinedLocals = ArrayBuilder<SynthesizedFieldSymbolBase>.GetInstance();
foreach (var local in locals)
{
if (!IsCaptured(local))
if (!NeedsProxy(local))
{
continue;
}
......@@ -503,14 +518,13 @@ public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node)
return base.VisitAssignmentOperator(node);
}
var left = (BoundLocal)node.Left;
var local = left.LocalSymbol;
if (!IsCaptured(local))
var leftLocal = ((BoundLocal)node.Left).LocalSymbol;
if (!NeedsProxy(leftLocal))
{
return base.VisitAssignmentOperator(node);
}
if (proxies.ContainsKey(local))
if (proxies.ContainsKey(leftLocal))
{
Debug.Assert(node.RefKind == RefKind.None);
return base.VisitAssignmentOperator(node);
......@@ -521,12 +535,12 @@ public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node)
// Here we handle ref temps. By-ref synthesized variables are the target of a ref assignment operator before
// being used in any other way.
Debug.Assert(local.SynthesizedLocalKind == SynthesizedLocalKind.AwaitSpill);
Debug.Assert(leftLocal.SynthesizedLocalKind == SynthesizedLocalKind.AwaitSpill);
Debug.Assert(node.RefKind != RefKind.None);
// We have an assignment to a variable that has not yet been assigned a proxy.
// So we assign the proxy before translating the assignment.
return HoistRefInitialization(local, node);
return HoistRefInitialization(leftLocal, node);
}
/// <summary>
......
// Copyright (c) Microsoft Open Technologies, Inc. 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.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
......@@ -19,7 +20,7 @@ internal abstract class StateMachineRewriter
protected readonly SynthesizedContainer stateMachineClass;
protected FieldSymbol stateField;
protected IReadOnlyDictionary<Symbol, CapturedSymbolReplacement> nonReusableLocalProxies;
protected HashSet<Symbol> variablesCaptured;
protected IReadOnlySet<Symbol> variablesCaptured;
protected Dictionary<Symbol, CapturedSymbolReplacement> initialParameters;
protected StateMachineRewriter(
......@@ -89,11 +90,9 @@ protected BoundStatement Rewrite()
}
// fields for the captured variables of the method
var captured = IteratorAndAsyncCaptureWalker.Analyze(compilationState.ModuleBuilderOpt.Compilation, method, body);
this.variablesCaptured = new HashSet<Symbol>(captured.Keys);
this.nonReusableLocalProxies = CreateNonReusableLocalProxies(captured);
var variablesCaptured = IteratorAndAsyncCaptureWalker.Analyze(compilationState.ModuleBuilderOpt.Compilation, method, body);
this.nonReusableLocalProxies = CreateNonReusableLocalProxies(variablesCaptured);
this.variablesCaptured = variablesCaptured;
GenerateMethodImplementations();
......@@ -101,14 +100,14 @@ protected BoundStatement Rewrite()
return ReplaceOriginalMethod();
}
private IReadOnlyDictionary<Symbol, CapturedSymbolReplacement> CreateNonReusableLocalProxies(MultiDictionary<Symbol, CSharpSyntaxNode> captured)
private IReadOnlyDictionary<Symbol, CapturedSymbolReplacement> CreateNonReusableLocalProxies(MultiDictionary<Symbol, CSharpSyntaxNode> variablesCaptured)
{
var proxies = new Dictionary<Symbol, CapturedSymbolReplacement>();
var typeMap = stateMachineClass.TypeMap;
var orderedCaptured =
from local in captured.Keys
from local in variablesCaptured.Keys
orderby local.Name, (local.Locations.Length == 0) ? 0 : local.Locations[0].SourceSpan.Start
select local;
......@@ -122,7 +121,7 @@ protected BoundStatement Rewrite()
{
// create proxies for user-defined variables and for lambda closures:
Debug.Assert(local.RefKind == RefKind.None);
proxies.Add(local, MakeNonReusableLocalProxy(typeMap, captured, local));
proxies.Add(local, MakeNonReusableLocalProxy(typeMap, variablesCaptured, local));
}
}
else
......
......@@ -122,7 +122,7 @@
<Compile Include="CodeGen\CodeGenConversionTests.cs" />
<Compile Include="CodeGen\CodeGenDynamicTests.cs" />
<Compile Include="CodeGen\CodeGenExplicitImplementationTests.cs" />
<Compile Include="CodeGen\CodeGenExprLambda.cs" />
<Compile Include="CodeGen\CodeGenExprLambdaTests.cs" />
<Compile Include="CodeGen\CodeGenFieldInitTests.cs" />
<Compile Include="CodeGen\CodeGenForEachTests.cs" />
<Compile Include="CodeGen\CodeGenImplicitImplementationTests.cs" />
......
......@@ -15,23 +15,23 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen
{
public class CodeGenAsyncLocalsTests : EmitMetadataTestBase
{
private CSharpCompilation CreateCompilation(string source, IEnumerable<MetadataReference> references = null, CSharpCompilationOptions compOptions = null)
private CSharpCompilation CreateCompilation(string source, IEnumerable<MetadataReference> references = null, CSharpCompilationOptions options = null)
{
SynchronizationContext.SetSynchronizationContext(null);
compOptions = compOptions ?? TestOptions.ReleaseExe;
options = options ?? TestOptions.ReleaseExe;
IEnumerable<MetadataReference> asyncRefs = new[] { SystemRef_v4_0_30319_17929, SystemCoreRef_v4_0_30319_17929, CSharpRef };
references = (references != null) ? references.Concat(asyncRefs) : asyncRefs;
return CreateCompilationWithMscorlib45(source, options: compOptions, references: references);
return CreateCompilationWithMscorlib45(source, options: options, references: references);
}
private CompilationVerifier CompileAndVerify(string source, string expectedOutput, IEnumerable<MetadataReference> references = null, EmitOptions emitOptions = EmitOptions.All, CSharpCompilationOptions compOptions = null)
private CompilationVerifier CompileAndVerify(string source, string expectedOutput, IEnumerable<MetadataReference> references = null, EmitOptions emitOptions = EmitOptions.All, CSharpCompilationOptions options = null)
{
SynchronizationContext.SetSynchronizationContext(null);
var compilation = this.CreateCompilation(source, references: references, compOptions: compOptions);
var compilation = this.CreateCompilation(source, references: references, options: options);
return base.CompileAndVerify(compilation, expectedOutput: expectedOutput, emitOptions: emitOptions);
}
......@@ -45,28 +45,6 @@ where pair.line2.Contains("ldfld") || pair.line2.Contains("stfld")
select pair.line1.Trim() + Environment.NewLine + pair.line2.Trim());
}
[Fact]
public void Unhoisted_Used_Param()
{
var source = @"
using System.Collections.Generic;
struct Test
{
public static IEnumerable<int> F(int x)
{
x = 2;
yield return 1;
}
public static void Main()
{
F(1);
}
}";
CompileAndVerify(source, "");
}
[Fact]
public void AsyncWithLocals()
{
......@@ -440,7 +418,7 @@ class Test<U>
foreach (var x in GetEnum<U>()) await F(5);
}
}";
var c = CompileAndVerify(source, expectedOutput: null, compOptions: TestOptions.ReleaseDll);
var c = CompileAndVerify(source, expectedOutput: null, options: TestOptions.ReleaseDll);
var actual = GetFieldLoadsAndStores(c, "Test<U>.<M>d__1<S, T>.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext");
......@@ -598,7 +576,7 @@ public static async void M()
foreach (var x in GetObjectEnum()) await F(2);
}
}";
var c = CompileAndVerify(source, expectedOutput: null, compOptions: TestOptions.ReleaseDll);
var c = CompileAndVerify(source, expectedOutput: null, options: TestOptions.ReleaseDll);
var actual = GetFieldLoadsAndStores(c, "Test.<M>d__1.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext");
......
......@@ -1163,8 +1163,16 @@ public static IEnumerator<int> M(IEnumerable<int> items)
yield return x;
}
}";
CompileAndVerify(source).
VerifyIL("Program.M", @"
var c = CompileAndVerify(source, options: TestOptions.ReleaseDll.WithMetadataImportOptions(MetadataImportOptions.All), symbolValidator: module =>
{
AssertEx.Equal(new[]
{
"<>1__state",
"<>2__current"
}, module.GetFieldNames("Program.<M>d__0"));
});
c.VerifyIL("Program.M", @"
{
// Code size 7 (0x7)
.maxstack 1
......@@ -1173,6 +1181,77 @@ .maxstack 1
IL_0006: ret
}");
}
[Fact]
public void HoistedParameters_Enumerable()
{
var source = @"
using System.Collections.Generic;
struct Test
{
public static IEnumerable<int> F(int x, int y, int z)
{
x = z;
yield return 1;
y = 1;
}
public static void Main()
{
F(1, 2, 3);
}
}";
var c = CompileAndVerify(source, options: TestOptions.ReleaseDll.WithMetadataImportOptions(MetadataImportOptions.All), symbolValidator: module =>
{
// consider: we don't really need to hoist "x" and "z", we could store the values of "<>3__x" and "<>3__z" to locals at the beginning of MoveNext.
AssertEx.Equal(new[]
{
"<>1__state",
"<>2__current",
"<>l__initialThreadId",
"x",
"<>3__x",
"y",
"<>3__y",
"z",
"<>3__z",
}, module.GetFieldNames("Test.<F>d__0"));
});
}
[Fact]
public void HoistedParameters_Enumerator()
{
var source = @"
using System.Collections.Generic;
struct Test
{
public static IEnumerator<int> F(int x, int y, int z)
{
x = z;
yield return 1;
y = 1;
}
public static void Main()
{
F(1, 2, 3);
}
}";
var c = CompileAndVerify(source, options: TestOptions.ReleaseDll.WithMetadataImportOptions(MetadataImportOptions.All), symbolValidator: module =>
{
AssertEx.Equal(new[]
{
"<>1__state",
"<>2__current",
"x",
"y",
"z",
}, module.GetFieldNames("Test.<F>d__0"));
});
}
[Fact]
public void IteratorForEach()
......
......@@ -103,6 +103,7 @@
<Compile Include="Collections\IdentifierCollection.cs" />
<Compile Include="Collections\ImmutableArrayExtensions.cs" />
<Compile Include="Collections\ImmutableMemoryStream.cs" />
<Compile Include="InternalUtilities\IReadOnlySet.cs" />
<Compile Include="Collections\KeyedStack.cs" />
<Compile Include="Collections\OrderPreservingMultiDictionary.cs" />
<Compile Include="Collections\PooledDictionary.cs" />
......
// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Roslyn.Utilities
{
internal interface IReadOnlySet<T>
{
int Count { get; }
bool Contains(T item);
}
}
// Copyright (c) Microsoft Open Technologies, Inc. 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.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
......@@ -7,7 +8,7 @@
namespace Roslyn.Utilities
{
// Note that this is not threadsafe for concurrent reading and writing.
internal sealed class MultiDictionary<K, V>
internal sealed class MultiDictionary<K, V> : IReadOnlySet<K>
{
// store either a single V or an ImmutableHashSet<V>
private readonly Dictionary<K, object> dictionary;
......@@ -123,5 +124,15 @@ internal void Clear()
{
this.dictionary.Clear();
}
int IReadOnlySet<K>.Count
{
get { return KeyCount; }
}
bool IReadOnlySet<K>.Contains(K item)
{
return ContainsKey(item);
}
}
}
\ No newline at end of file
......@@ -206,6 +206,12 @@ public static NamespaceSymbol GetNamespace(this NamespaceSymbol symbol, string n
return (NamespaceSymbol)symbol.GetMembers(name).Single();
}
public static string[] GetFieldNames(this ModuleSymbol module, string qualifiedTypeName)
{
var type = (NamedTypeSymbol)module.GlobalNamespace.GetMember(qualifiedTypeName);
return type.GetMembers().OfType<FieldSymbol>().Select(f => f.Name).ToArray();
}
public static IEnumerable<CSharpAttributeData> GetAttributes(this Symbol @this, NamedTypeSymbol c)
{
return @this.GetAttributes().Where(a => a.AttributeClass == c);
......
......@@ -90,6 +90,9 @@
<Compile Include="..\..\Compilers\Core\Portable\InternalUtilities\MultiDictionary.cs">
<Link>InternalUtilities\MultiDictionary.cs</Link>
</Compile>
<Compile Include="..\..\Compilers\Core\Portable\InternalUtilities\IReadOnlySet.cs">
<Link>InternalUtilities\IReadOnlySet.cs</Link>
</Compile>
<Compile Include="..\..\Compilers\Core\Desktop\PathKind.cs">
<Link>InternalUtilities\PathKind.cs</Link>
</Compile>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册