|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Roslyn.Utilities;
using Microsoft.CodeAnalysis.CodeGen;
using Microsoft.CodeAnalysis.Emit;
namespace Microsoft.CodeAnalysis.CSharp
{
internal sealed partial class IteratorMethodToStateMachineRewriter : MethodToStateMachineRewriter
{
/// <summary>
/// The field of the generated iterator class that underlies the Current property.
/// </summary>
private readonly FieldSymbol _current;
/// <summary>
/// Tells us if a particular try contains yield returns
/// </summary>
private YieldsInTryAnalysis? _yieldsInTryAnalysis;
/// <summary>
/// When this is more that 0, returns are emitted as "methodValue = value; goto exitLabel;"
/// </summary>
private int _tryNestingLevel;
private LabelSymbol? _exitLabel;
private LocalSymbol? _methodValue;
/// <summary>
/// The current iterator finally frame in the tree of finally frames.
/// By default there is a root finally frame.
/// Root frame does not have a handler, but may contain nested frames.
/// </summary>
private IteratorFinallyFrame _currentFinallyFrame = new IteratorFinallyFrame();
/// <summary>
/// Finally state of the next Finally frame if such created.
/// Finally state is a negative decreasing number starting with -3. (-2 is used for something else).
/// Root frame has finally state -1.
///
/// The Finally state is the state that we are in when "between states".
/// Regular states are positive and are the only states that can be resumed to.
/// The purpose of distinct finally states is to have enough information about
/// which finally handlers must run when we need to finalize iterator after a fault.
/// </summary>
private StateMachineState _nextFinalizeState;
internal IteratorMethodToStateMachineRewriter(
SyntheticBoundNodeFactory F,
MethodSymbol originalMethod,
FieldSymbol state,
FieldSymbol current,
FieldSymbol? instanceIdField,
IReadOnlySet<Symbol> hoistedVariables,
IReadOnlyDictionary<Symbol, CapturedSymbolReplacement> nonReusableLocalProxies,
SynthesizedLocalOrdinalsDispenser synthesizedLocalOrdinals,
ArrayBuilder<StateMachineStateDebugInfo> stateMachineStateDebugInfoBuilder,
VariableSlotAllocator slotAllocatorOpt,
int nextFreeHoistedLocalSlot,
BindingDiagnosticBag diagnostics)
: base(F, originalMethod, state, instanceIdField, hoistedVariables, nonReusableLocalProxies, synthesizedLocalOrdinals, stateMachineStateDebugInfoBuilder, slotAllocatorOpt, nextFreeHoistedLocalSlot, diagnostics)
{
_current = current;
_nextFinalizeState = slotAllocatorOpt?.GetFirstUnusedStateMachineState(increasing: false) ?? StateMachineState.FirstIteratorFinalizeState;
}
#nullable disable
protected sealed override HotReloadExceptionCode EncMissingStateErrorCode
=> HotReloadExceptionCode.CannotResumeSuspendedIteratorMethod;
protected override StateMachineState FirstIncreasingResumableState
=> StateMachineState.FirstResumableIteratorState;
internal void GenerateMoveNextAndDispose(BoundStatement body, SynthesizedImplementationMethod moveNextMethod, SynthesizedImplementationMethod disposeMethod)
{
// scan body for yielding try blocks
_yieldsInTryAnalysis = new YieldsInTryAnalysis(body);
if (_yieldsInTryAnalysis.ContainsYieldsInTrys())
{
// adjust for the method Try/Fault block that we will put around the body.
_tryNestingLevel++;
}
///////////////////////////////////
// Generate the body for MoveNext()
///////////////////////////////////
F.CurrentFunction = moveNextMethod;
AddState(StateMachineState.InitialIteratorState, out GeneratedLabelSymbol initialLabel);
var newBody = (BoundStatement)Visit(body);
// switch(cachedState) {
// case 0: goto state_0;
// case 1: goto state_1;
// //etc
// default: return false;
// }
// state_0:
// state = -1;
// [optional: cachedThis = capturedThis;]
// [[rewritten body]]
newBody = F.Block((object)cachedThis == null ?
ImmutableArray.Create(cachedState) :
ImmutableArray.Create(cachedState, cachedThis),
F.HiddenSequencePoint(),
F.Assignment(F.Local(cachedState), F.Field(F.This(), stateField)),
CacheThisIfNeeded(),
Dispatch(isOutermost: true),
GenerateReturn(finished: true),
F.Label(initialLabel),
F.Assignment(F.Field(F.This(), stateField), F.Literal(StateMachineState.NotStartedOrRunningState)),
newBody);
//
// C# spec requires that iterators trap all exceptions and self-dispose eagerly.
//
// 10.14.4.1 The MoveNext method
// . . .
// When an exception is thrown and propagated out of the iterator block:
// o Appropriate finally blocks in the iterator body will have been executed by the exception propagation.
// o The state of the enumerator object is changed to after.
// o The exception propagation continues to the caller of the MoveNext method.
// . . .
//
if (_yieldsInTryAnalysis.ContainsYieldsInTrys())
{
// try
// {
// body;
// }
// fault
// {
// this.Dispose();
// }
var faultBlock = F.Block(F.ExpressionStatement(F.Call(F.This(), disposeMethod)));
newBody = F.Fault((BoundBlock)newBody, faultBlock);
}
newBody = F.Instrument(F.SequencePoint(body.Syntax, HandleReturn(newBody)), instrumentation);
F.CloseMethod(newBody);
///////////////////////////////////
// Generate the body for Dispose().
///////////////////////////////////
F.CurrentFunction = disposeMethod;
var rootFrame = _currentFinallyFrame;
if (rootFrame.knownStates == null)
{
// nothing to finalize
F.CloseMethod(F.Return());
}
else
{
var stateLocal = F.SynthesizedLocal(stateField.Type);
var state = F.Local(stateLocal);
var disposeBody = F.Block(
ImmutableArray.Create<LocalSymbol>(stateLocal),
F.Assignment(F.Local(stateLocal), F.Field(F.This(), stateField)),
EmitFinallyFrame(rootFrame, state),
F.Return());
F.CloseMethod(disposeBody);
}
}
private BoundBlock HandleReturn(BoundStatement newBody)
{
if (_exitLabel is null)
{
// body;
// return false;
return F.Block(
newBody,
F.Return(F.Literal(false)));
}
else
{
// body;
// methodValue = false;
// exitLabel:
// return methodValue;
return F.Block(
ImmutableArray.Create<LocalSymbol>(_methodValue),
newBody,
F.Assignment(this.F.Local(_methodValue), this.F.Literal(true)),
F.Label(_exitLabel),
F.Return(this.F.Local(_methodValue)));
}
}
/// <summary>
/// Produces a Try/Finally if frame has a handler (otherwise a regular block).
/// Handler goes into the Finally.
/// If there are nested frames, they are emitted into the try block.
/// This way the handler for the current frame is guaranteed to run even if
/// nested handlers throw exceptions.
///
/// {
/// switch(state)
/// {
/// case state1:
/// case state2:
/// case state3:
/// case state4:
/// try
/// {
/// switch(state)
/// {
/// case state3:
/// case state4:
/// try
/// {
/// ... more nested state dispatches if any ....
/// }
/// finally
/// {
/// // handler for a try where state3 and state4 can be observed
/// handler_3_4()
/// }
/// break;
/// }
/// }
/// finally
/// {
/// // handler for a try where state1 and state2 can be observed
/// handler_1_2()
/// }
/// break;
///
/// case state5:
/// ... another dispatch of nested states to their finally blocks ...
/// break;
/// }
/// }
///
/// </summary>
private BoundStatement EmitFinallyFrame(IteratorFinallyFrame frame, BoundLocal state)
{
BoundStatement body = null;
if (frame.knownStates != null)
{
var breakLabel = F.GenerateLabel("break");
var sections = from ft in frame.knownStates
group ft.Key by ft.Value into g
select F.SwitchSection(
g.SelectAsArray(state => (int)state),
EmitFinallyFrame(g.Key, state),
F.Goto(breakLabel));
body = F.Block(
F.Switch(state, sections.ToImmutableArray()),
F.Label(breakLabel));
}
if (!frame.IsRoot())
{
var tryBlock = body != null ? F.Block(body) : F.Block();
body = F.Try(
tryBlock,
ImmutableArray<BoundCatchBlock>.Empty,
F.Block(F.ExpressionStatement(F.Call(F.This(), frame.handler))));
}
Debug.Assert(body != null, "we should have either sub-dispatch or a handler");
return body;
}
protected override BoundStatement GenerateReturn(bool finished)
{
BoundLiteral result = this.F.Literal(!finished);
if (_tryNestingLevel == 0)
{
return F.Return(result);
}
else
{
if ((object)_exitLabel == null)
{
_exitLabel = this.F.GenerateLabel("exitLabel");
_methodValue = F.SynthesizedLocal(result.Type);
}
var gotoExit = F.Goto(_exitLabel);
if (finished)
{
// since we are finished, we need to treat this as a potential Leave
gotoExit = (BoundGotoStatement)VisitGotoStatement(gotoExit);
}
return this.F.Block(
F.Assignment(this.F.Local(_methodValue), result),
gotoExit);
}
}
#region Visitors
public override BoundNode VisitYieldBreakStatement(BoundYieldBreakStatement node)
{
return GenerateReturn(finished: true);
}
public override BoundNode VisitYieldReturnStatement(BoundYieldReturnStatement node)
{
// yield return expression;
// is translated to
// this.current = expression;
// this.state = <next_state>;
// return true;
// <next_state_label>: ;
// <hidden sequence point>
// this.state = finalizeState;
AddResumableState(node.Syntax, awaitId: default, out StateMachineState stateNumber, out GeneratedLabelSymbol resumeLabel);
_currentFinallyFrame.AddState(stateNumber);
var rewrittenExpression = (BoundExpression)Visit(node.Expression);
return F.Block(
F.Assignment(F.Field(F.This(), _current), rewrittenExpression),
F.Assignment(F.Field(F.This(), stateField), F.Literal(stateNumber)),
GenerateReturn(finished: false),
F.Label(resumeLabel),
F.HiddenSequencePoint(),
F.Assignment(F.Field(F.This(), stateField), F.Literal(_currentFinallyFrame.finalizeState)));
}
public override BoundNode VisitGotoStatement(BoundGotoStatement node)
{
BoundExpression caseExpressionOpt = (BoundExpression)this.Visit(node.CaseExpressionOpt);
BoundLabel labelExpressionOpt = (BoundLabel)this.Visit(node.LabelExpressionOpt);
var proxyLabel = _currentFinallyFrame.ProxyLabelIfNeeded(node.Label);
Debug.Assert(node.Label == proxyLabel || !(F.CurrentFunction is IteratorFinallyMethodSymbol), "should not be proxying branches in finally");
return node.Update(proxyLabel, caseExpressionOpt, labelExpressionOpt);
}
public override BoundNode VisitConditionalGoto(BoundConditionalGoto node)
{
Debug.Assert(node.Label == _currentFinallyFrame.ProxyLabelIfNeeded(node.Label), "conditional leave?");
return base.VisitConditionalGoto(node);
}
public override BoundNode VisitTryStatement(BoundTryStatement node)
{
// if node contains no yields, do regular rewrite in the current frame.
if (!ContainsYields(node))
{
_tryNestingLevel++;
var result = node.Update(
(BoundBlock)Visit(node.TryBlock),
VisitList(node.CatchBlocks),
(BoundBlock)Visit(node.FinallyBlockOpt),
node.FinallyLabelOpt,
node.PreferFaultHandler);
_tryNestingLevel--;
return result;
}
Debug.Assert(node.CatchBlocks.IsEmpty, "try with yields must have no catches");
Debug.Assert(node.FinallyBlockOpt != null, "try with yields must have finally");
// rewrite TryBlock in a new frame.
var frame = PushFrame(node);
_tryNestingLevel++;
var rewrittenBody = (BoundStatement)this.Visit(node.TryBlock);
Debug.Assert(!frame.IsRoot());
Debug.Assert(frame.parent.knownStates.ContainsValue(frame), "parent must be aware about states in the child frame");
var finallyMethod = frame.handler;
var origMethod = F.CurrentFunction;
// rewrite finally block into a Finally method.
F.CurrentFunction = finallyMethod;
var rewrittenHandler = (BoundStatement)this.Visit(node.FinallyBlockOpt);
_tryNestingLevel--;
PopFrame();
// {
// this.state = parentFinalizeState;
// body;
// return;
// }
Debug.Assert(frame.parent.finalizeState == _currentFinallyFrame.finalizeState);
rewrittenHandler = F.Block((object)this.cachedThis != null ?
ImmutableArray.Create(this.cachedThis) :
ImmutableArray<LocalSymbol>.Empty,
F.Assignment(F.Field(F.This(), stateField), F.Literal(frame.parent.finalizeState)),
CacheThisIfNeeded(),
rewrittenHandler,
F.Return()
);
F.CloseMethod(rewrittenHandler);
F.CurrentFunction = origMethod;
var bodyStatements = ArrayBuilder<BoundStatement>.GetInstance();
// add a call to the handler after the try body.
//
// {
// this.state = finalizeState;
// body;
// this.Finally(); // will reset the state to the finally state of the parent.
// }
bodyStatements.Add(F.Assignment(F.Field(F.This(), stateField), F.Literal(frame.finalizeState)));
bodyStatements.Add(rewrittenBody);
bodyStatements.Add(F.ExpressionStatement(F.Call(F.This(), finallyMethod)));
// handle proxy labels if have any
if (frame.proxyLabels != null)
{
var dropThrough = F.GenerateLabel("dropThrough");
bodyStatements.Add(F.Goto(dropThrough));
var parent = frame.parent;
foreach (var p in frame.proxyLabels)
{
var proxy = p.Value;
var destination = p.Key;
// branch lands here
bodyStatements.Add(F.Label(proxy));
// finalize current state, proceed to destination.
bodyStatements.Add(F.ExpressionStatement(F.Call(F.This(), finallyMethod)));
// let the parent forward the branch appropriately
var parentProxy = parent.ProxyLabelIfNeeded(destination);
bodyStatements.Add(F.Goto(parentProxy));
}
bodyStatements.Add(F.Label(dropThrough));
}
return F.Block(bodyStatements.ToImmutableAndFree());
}
private IteratorFinallyFrame PushFrame(BoundTryStatement statement)
{
var syntax = statement.Syntax;
if (slotAllocator?.TryGetPreviousStateMachineState(syntax, awaitId: default, out var finalizeState) != true)
{
finalizeState = _nextFinalizeState--;
}
AddStateDebugInfo(syntax, awaitId: default, finalizeState);
var finallyMethod = MakeSynthesizedFinally(finalizeState);
var newFrame = new IteratorFinallyFrame(_currentFinallyFrame, finalizeState, finallyMethod, _yieldsInTryAnalysis.Labels(statement));
newFrame.AddState(finalizeState);
_currentFinallyFrame = newFrame;
return newFrame;
}
private void PopFrame()
{
var result = _currentFinallyFrame;
_currentFinallyFrame = result.parent;
}
private bool ContainsYields(BoundTryStatement statement)
{
return _yieldsInTryAnalysis.ContainsYields(statement);
}
private IteratorFinallyMethodSymbol MakeSynthesizedFinally(StateMachineState finalizeState)
{
var stateMachineType = (IteratorStateMachine)F.CurrentType;
var finallyMethod = new IteratorFinallyMethodSymbol(stateMachineType, GeneratedNames.MakeIteratorFinallyMethodName(finalizeState));
F.ModuleBuilderOpt.AddSynthesizedDefinition(stateMachineType, finallyMethod.GetCciAdapter());
return finallyMethod;
}
#endregion
}
}
|