using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Ariadne.Generator
{
///
/// A value held in a field while a passage is paused, because an expression needs it after the pause.
///
public class SpillTemp
{
public readonly string Name;
public readonly ITypeSymbol Type;
///
/// Holds the code the temp's value comes from, which a save library's analyzer reports a problem type on.
///
public readonly Location Where;
///
/// Holds whether some pause falls between the temp being set and last read, so its value is saved.
///
public bool Saved;
///
/// The code that builds the temp's value when the frame is made, or null to leave it unset.
///
public string Initializer;
public SpillTemp(string name, ITypeSymbol type, Location where)
{
Name = name;
Type = type;
Where = where;
}
}
public enum SpillStepKind
{
Pause,
Store,
Branch,
Jump,
Label,
ArmTest,
}
///
/// Says what a branch tests its operand for, besides truth.
///
public enum NullCheck
{
None,
IsNull,
IsNotNull,
}
///
/// One thing to do ahead of the statement itself: pause in a passage call, store an operand, or steer around the
/// steps of an operand that only runs sometimes.
///
public class SpillStep
{
public readonly SpillStepKind Kind;
///
/// Holds the passage call to pause in, or null for any other step.
///
public readonly InvocationExpressionSyntax Call;
///
/// Holds the operand a store keeps or a branch tests, or null for any other step.
///
public readonly ExpressionSyntax Value;
///
/// Holds the temp that receives the value, or null for a call that returns nothing and for any step that
/// keeps no value.
///
public readonly SpillTemp Temp;
///
/// Holds the label a branch or jump goes to, or the label a label step defines.
///
public readonly string Name;
///
/// Holds whether a branch goes when its test fails.
///
public readonly bool Negate;
public readonly NullCheck NullCheck;
///
/// Holds the switch expression arm an ArmTest checks the value against, or null for any other step.
///
public readonly SwitchExpressionArmSyntax Arm;
///
/// Holds the temps that must survive a pause in this step, which is empty for any other step.
///
public readonly List LiveTemps = new List();
private SpillStep(SpillStepKind kind, InvocationExpressionSyntax call, ExpressionSyntax value, SpillTemp temp,
string name, bool negate, NullCheck nullCheck, SwitchExpressionArmSyntax arm = null)
{
Arm = arm;
Kind = kind;
Call = call;
Value = value;
Temp = temp;
Name = name;
Negate = negate;
NullCheck = nullCheck;
}
public static SpillStep Pause(InvocationExpressionSyntax call, SpillTemp temp)
{
return new SpillStep(SpillStepKind.Pause, call, null, temp, null, false, NullCheck.None);
}
public static SpillStep Store(ExpressionSyntax value, SpillTemp temp)
{
return new SpillStep(SpillStepKind.Store, null, value, temp, null, false, NullCheck.None);
}
public static SpillStep Branch(ExpressionSyntax value, bool negate, NullCheck nullCheck, string target)
{
return new SpillStep(SpillStepKind.Branch, null, value, null, target, negate, nullCheck);
}
///
/// Makes a step that goes to unless the value matches the arm.
///
public static SpillStep ArmTest(ExpressionSyntax value, SwitchExpressionArmSyntax arm, string target)
{
return new SpillStep(SpillStepKind.ArmTest, null, value, null, target, true, NullCheck.None, arm);
}
public static SpillStep Jump(string target)
{
return new SpillStep(SpillStepKind.Jump, null, null, null, target, false, NullCheck.None);
}
public static SpillStep Label(string name)
{
return new SpillStep(SpillStepKind.Label, null, null, null, name, false, NullCheck.None);
}
}
///
/// What an expression with a pause inside it becomes: steps to run in order, then the expression with each
/// replaced operand read from its temp.
///
public class SpillSequence
{
public readonly List Steps = new List();
public readonly Dictionary Replacements = new Dictionary();
public readonly List Temps = new List();
///
/// Holds how many operators were turned into jumps, which the labels they use are numbered by.
///
public int Conditionals;
}
///
/// Lifts the passage calls out of an expression without changing the order C# evaluates it in: every operand that
/// comes before a call, and might not hold the same value after the pause, is stored first.
///
public class ExpressionSpiller
{
private class Frame
{
public ExpressionSyntax Node;
///
/// Holds the operands in the order C# evaluates them.
///
public readonly List Operands = new List();
///
/// Holds how many of the leading operands are the object a call or index is made on.
///
public int Receivers;
public int Next;
///
/// Holds the operands already evaluated.
///
public readonly List Done = new List();
///
/// Holds whether the first operand decides if the others run, and the others have a pause in them.
///
public bool Conditional;
///
/// Holds the arms of a switch expression whose arms are conditional, or null for any other frame.
///
public IReadOnlyList Arms;
///
/// Holds whether the first operand is a variable the node writes, which the node's own code goes on to
/// read in place.
///
public bool Assigns;
///
/// Holds how many operands have been completed.
///
public int Finished;
public int Number;
}
private readonly ScriptClass script;
private readonly SemanticModel model;
private readonly HashSet spine = new HashSet();
private readonly HashSet assigned = new HashSet(SymbolEqualityComparer.Default);
private readonly SpillSequence returnMe = new SpillSequence();
private readonly int firstNumber;
private readonly int firstConditional;
private ExpressionSpiller(ScriptClass script, SemanticModel model, int firstNumber, int firstConditional)
{
this.script = script;
this.model = model;
this.firstNumber = firstNumber;
this.firstConditional = firstConditional;
}
private static bool InFunction(SyntaxNode node, SyntaxNode root)
{
for (SyntaxNode current = node.Parent; current != null; current = current.Parent)
{
if (current is AnonymousFunctionExpressionSyntax || current is LocalFunctionStatementSyntax)
{
return true;
}
if (current == root)
{
return false;
}
}
return false;
}
// A local or parameter can't change while the callee runs, unless this statement itself assigns it.
private void FindAssigned(SyntaxNode statement)
{
foreach (SyntaxNode node in statement.DescendantNodesAndSelf())
{
ExpressionSyntax target = null;
var assignment = node as AssignmentExpressionSyntax;
if (assignment != null)
{
target = assignment.Left;
}
var prefix = node as PrefixUnaryExpressionSyntax;
if (prefix != null && (prefix.IsKind(SyntaxKind.PreIncrementExpression)
|| prefix.IsKind(SyntaxKind.PreDecrementExpression)))
{
target = prefix.Operand;
}
var postfix = node as PostfixUnaryExpressionSyntax;
if (postfix != null)
{
target = postfix.Operand;
}
var argument = node as ArgumentSyntax;
if (argument != null && !argument.RefKindKeyword.IsKind(SyntaxKind.None)
&& !argument.RefKindKeyword.IsKind(SyntaxKind.InKeyword))
{
target = argument.Expression;
}
if (target != null)
{
ISymbol symbol = model.GetSymbolInfo(target).Symbol;
if (symbol != null)
{
assigned.Add(symbol);
}
}
}
}
private ITypeSymbol TypeOf(ExpressionSyntax node)
{
TypeInfo info = model.GetTypeInfo(node);
ITypeSymbol type = info.Type ?? info.ConvertedType;
if (type == null
|| type.TypeKind == TypeKind.Error
|| type.SpecialType == SpecialType.System_Void
|| type.IsAnonymousType
|| type.IsRefLikeType)
{
return null;
}
return type;
}
private bool IsStable(ExpressionSyntax node)
{
while (node is ParenthesizedExpressionSyntax)
{
node = ((ParenthesizedExpressionSyntax)node).Expression;
}
if (model.GetConstantValue(node).HasValue)
{
return true;
}
if (node is LiteralExpressionSyntax
|| node is ThisExpressionSyntax
|| node is BaseExpressionSyntax
|| node is TypeOfExpressionSyntax
|| node is DefaultExpressionSyntax)
{
return true;
}
if (node is IdentifierNameSyntax
|| node is MemberAccessExpressionSyntax
|| node is PredefinedTypeSyntax)
{
ISymbol symbol = model.GetSymbolInfo(node).Symbol;
if ((symbol is ILocalSymbol || symbol is IParameterSymbol) && !assigned.Contains(symbol))
{
return true;
}
}
return IsObjectRead(node);
}
// A name, or a chain of fields and properties, that holds an object. The object is the world's own, so a
// copy kept across the pause (and saved) would be a different one. Reading it again afterward is what the
// script means.
private bool IsObjectRead(ExpressionSyntax node)
{
ITypeSymbol type = TypeOf(node);
if (type == null || type.IsValueType || type.SpecialType == SpecialType.System_String)
{
return false;
}
while (true)
{
if (node is ThisExpressionSyntax)
{
return true;
}
ISymbol symbol = model.GetSymbolInfo(node).Symbol;
if (symbol is INamespaceOrTypeSymbol)
{
return true;
}
if (assigned.Contains(symbol)
|| !(symbol is ILocalSymbol || symbol is IParameterSymbol || symbol is IFieldSymbol
|| symbol is IPropertySymbol))
{
return false;
}
var member = node as MemberAccessExpressionSyntax;
if (member == null)
{
return node is IdentifierNameSyntax;
}
node = member.Expression;
}
}
// Passing a variable by reference reads nothing, so it can be passed after the pause.
private static bool IsPlainLocation(ExpressionSyntax expression)
{
return ScriptClass.IsPlainTarget(expression);
}
// An out argument that declares a variable is made by the call itself, so only a passage call can take one.
private bool AddArguments(Frame frame, IEnumerable arguments, bool declares = false)
{
foreach (ArgumentSyntax argument in arguments)
{
if (argument.RefKindKeyword.IsKind(SyntaxKind.None)
|| argument.RefKindKeyword.IsKind(SyntaxKind.InKeyword))
{
frame.Operands.Add(argument.Expression);
}
else if (!IsPlainLocation(argument.Expression)
&& !(declares && argument.Expression is DeclarationExpressionSyntax))
{
return false;
}
}
return true;
}
private bool AddInvocationOperands(Frame frame, InvocationExpressionSyntax invocation)
{
ExpressionSyntax target = invocation.Expression;
var member = target as MemberAccessExpressionSyntax;
if (member != null && member.IsKind(SyntaxKind.SimpleMemberAccessExpression))
{
// A binding is only half of an expression, so it can't be set aside on its own.
if (member.Expression is MemberBindingExpressionSyntax
|| member.Expression is ElementBindingExpressionSyntax)
{
return false;
}
if (!(model.GetSymbolInfo(member.Expression).Symbol is INamespaceOrTypeSymbol))
{
frame.Operands.Add(member.Expression);
frame.Receivers = 1;
}
}
else if (target is MemberBindingExpressionSyntax)
{
// The object is the one before the ?. that this call hangs from.
}
else if (target is IdentifierNameSyntax || target is GenericNameSyntax)
{
if (!(model.GetSymbolInfo(target).Symbol is IMethodSymbol))
{
frame.Operands.Add(target);
frame.Receivers = 1;
}
}
else
{
return false;
}
return AddArguments(frame, invocation.ArgumentList.Arguments,
declares: script.AnyPassageCalled(invocation) != null);
}
///
/// Returns whether is the variable that a compound assignment reads, applies an
/// operator to and writes back.
///
public static bool IsCompoundTarget(SyntaxNode node)
{
var assignment = node.Parent as AssignmentExpressionSyntax;
return assignment != null
&& assignment.Left == node
&& !assignment.IsKind(SyntaxKind.SimpleAssignmentExpression);
}
// A declaration, or a tuple of names, declarations and discards. Assigning to one works nothing out first.
private static bool IsDeconstructionTarget(ExpressionSyntax target)
{
if (target is DeclarationExpressionSyntax)
{
return true;
}
var tuple = target as TupleExpressionSyntax;
if (tuple == null)
{
return false;
}
return tuple.Arguments.All(argument => ScriptClass.IsPlainTarget(argument.Expression)
|| IsDeconstructionTarget(argument.Expression));
}
// C# works out the object and index a target is reached through first, then (for a compound assignment) reads
// the old value, then the right side. A plain name has nothing to work out.
private bool DescribeAssignment(Frame frame, AssignmentExpressionSyntax assignment)
{
ExpressionSyntax left = assignment.Left;
bool simple = assignment.IsKind(SyntaxKind.SimpleAssignmentExpression);
bool coalesce = assignment.IsKind(SyntaxKind.CoalesceAssignmentExpression);
bool plain = ScriptClass.IsPlainTarget(left);
if (model.GetSymbolInfo(left).Symbol is IEventSymbol || (coalesce && !plain))
{
return false;
}
var element = left as ElementAccessExpressionSyntax;
var member = left as MemberAccessExpressionSyntax;
bool reachable = true;
if (!plain && element != null)
{
frame.Operands.Add(element.Expression);
frame.Receivers = 1;
reachable = AddArguments(frame, element.ArgumentList.Arguments);
}
else if (!plain && member != null && member.IsKind(SyntaxKind.SimpleMemberAccessExpression))
{
frame.Operands.Add(member.Expression);
frame.Receivers = 1;
}
else if (!plain && !(simple && IsDeconstructionTarget(left)))
{
return false;
}
if (!simple)
{
frame.Operands.Add(left);
}
frame.Operands.Add(assignment.Right);
if (coalesce)
{
frame.Assigns = true;
frame.Conditional = spine.Contains(assignment.Right);
}
return reachable;
}
// Fills in the operands of an expression with a passage call inside it, in evaluation order. Returns false for
// an expression this doesn't know how to take apart yet.
private bool Describe(Frame frame)
{
ExpressionSyntax node = frame.Node;
switch (node.Kind())
{
case SyntaxKind.ParenthesizedExpression:
frame.Operands.Add(((ParenthesizedExpressionSyntax)node).Expression);
return true;
case SyntaxKind.CastExpression:
frame.Operands.Add(((CastExpressionSyntax)node).Expression);
return true;
case SyntaxKind.UnaryPlusExpression:
case SyntaxKind.UnaryMinusExpression:
case SyntaxKind.BitwiseNotExpression:
case SyntaxKind.LogicalNotExpression:
frame.Operands.Add(((PrefixUnaryExpressionSyntax)node).Operand);
return true;
case SyntaxKind.AddExpression:
case SyntaxKind.SubtractExpression:
case SyntaxKind.MultiplyExpression:
case SyntaxKind.DivideExpression:
case SyntaxKind.ModuloExpression:
case SyntaxKind.LeftShiftExpression:
case SyntaxKind.RightShiftExpression:
case SyntaxKind.BitwiseOrExpression:
case SyntaxKind.BitwiseAndExpression:
case SyntaxKind.ExclusiveOrExpression:
case SyntaxKind.EqualsExpression:
case SyntaxKind.NotEqualsExpression:
case SyntaxKind.LessThanExpression:
case SyntaxKind.LessThanOrEqualExpression:
case SyntaxKind.GreaterThanExpression:
case SyntaxKind.GreaterThanOrEqualExpression:
frame.Operands.Add(((BinaryExpressionSyntax)node).Left);
frame.Operands.Add(((BinaryExpressionSyntax)node).Right);
return true;
case SyntaxKind.SimpleAssignmentExpression:
case SyntaxKind.AddAssignmentExpression:
case SyntaxKind.SubtractAssignmentExpression:
case SyntaxKind.MultiplyAssignmentExpression:
case SyntaxKind.DivideAssignmentExpression:
case SyntaxKind.ModuloAssignmentExpression:
case SyntaxKind.AndAssignmentExpression:
case SyntaxKind.ExclusiveOrAssignmentExpression:
case SyntaxKind.OrAssignmentExpression:
case SyntaxKind.LeftShiftAssignmentExpression:
case SyntaxKind.RightShiftAssignmentExpression:
case SyntaxKind.CoalesceAssignmentExpression:
return DescribeAssignment(frame, (AssignmentExpressionSyntax)node);
case SyntaxKind.IsExpression:
case SyntaxKind.AsExpression:
frame.Operands.Add(((BinaryExpressionSyntax)node).Left);
return true;
case SyntaxKind.IsPatternExpression:
frame.Operands.Add(((IsPatternExpressionSyntax)node).Expression);
return true;
case SyntaxKind.SwitchExpression:
var switchExpression = (SwitchExpressionSyntax)node;
frame.Operands.Add(switchExpression.GoverningExpression);
// A guard is checked ahead of every arm, so it can't hold a pause that only one arm would run.
if (switchExpression.Arms.Any(arm => arm.WhenClause != null && spine.Contains(arm.WhenClause)))
{
return false;
}
if (switchExpression.Arms.Any(arm => spine.Contains(arm)))
{
frame.Arms = switchExpression.Arms.ToList();
frame.Conditional = true;
frame.Operands.AddRange(switchExpression.Arms.Select(arm => arm.Expression));
}
return true;
case SyntaxKind.SimpleMemberAccessExpression:
frame.Operands.Add(((MemberAccessExpressionSyntax)node).Expression);
return true;
case SyntaxKind.ElementAccessExpression:
var element = (ElementAccessExpressionSyntax)node;
frame.Operands.Add(element.Expression);
frame.Receivers = 1;
return AddArguments(frame, element.ArgumentList.Arguments);
case SyntaxKind.InvocationExpression:
return AddInvocationOperands(frame, (InvocationExpressionSyntax)node);
case SyntaxKind.ObjectCreationExpression:
var creation = (ObjectCreationExpressionSyntax)node;
return creation.Initializer == null && creation.ArgumentList != null
&& AddArguments(frame, creation.ArgumentList.Arguments);
case SyntaxKind.InterpolatedStringExpression:
foreach (InterpolatedStringContentSyntax content in
((InterpolatedStringExpressionSyntax)node).Contents)
{
var interpolation = content as InterpolationSyntax;
if (interpolation != null)
{
frame.Operands.Add(interpolation.Expression);
}
}
return true;
case SyntaxKind.TupleExpression:
return AddArguments(frame, ((TupleExpressionSyntax)node).Arguments);
case SyntaxKind.LogicalAndExpression:
case SyntaxKind.LogicalOrExpression:
case SyntaxKind.CoalesceExpression:
var shortCircuit = (BinaryExpressionSyntax)node;
frame.Operands.Add(shortCircuit.Left);
frame.Operands.Add(shortCircuit.Right);
frame.Conditional = spine.Contains(shortCircuit.Right);
return true;
case SyntaxKind.ConditionalExpression:
var conditional = (ConditionalExpressionSyntax)node;
frame.Operands.Add(conditional.Condition);
frame.Operands.Add(conditional.WhenTrue);
frame.Operands.Add(conditional.WhenFalse);
frame.Conditional = spine.Contains(conditional.WhenTrue) || spine.Contains(conditional.WhenFalse);
return true;
case SyntaxKind.ConditionalAccessExpression:
var access = (ConditionalAccessExpressionSyntax)node;
if (access.Expression is MemberBindingExpressionSyntax
|| access.Expression is ElementBindingExpressionSyntax
|| access.Expression is ConditionalAccessExpressionSyntax)
{
return false;
}
frame.Operands.Add(access.Expression);
if (spine.Contains(access.WhenNotNull))
{
frame.Operands.Add(access.WhenNotNull);
frame.Conditional = true;
}
return true;
case SyntaxKind.ElementBindingExpression:
return AddArguments(frame, ((ElementBindingExpressionSyntax)node).ArgumentList.Arguments);
default:
return false;
}
}
private SpillTemp NewTemp(ITypeSymbol type, Location where)
{
var temp = new SpillTemp("__spill" + (firstNumber + returnMe.Temps.Count), type, where);
returnMe.Temps.Add(temp);
return temp;
}
// Stores each operand that has already been evaluated and might change meanwhile, outermost first, since
// that's the order they were evaluated in. Returns false if one can't be stored without changing what the
// expression means.
private bool SpillPending(List frames)
{
foreach (Frame frame in frames)
{
for (int i = 0; i < frame.Done.Count; ++i)
{
ExpressionSyntax operand = frame.Done[i];
if (returnMe.Replacements.ContainsKey(operand) || IsStable(operand))
{
continue;
}
ITypeSymbol type = TypeOf(operand);
// A copy of a struct would take the changes meant for the original.
if (type == null || (i < frame.Receivers && type.IsValueType))
{
return false;
}
SpillTemp temp = NewTemp(type, operand.GetLocation());
returnMe.Steps.Add(SpillStep.Store(operand, temp));
returnMe.Replacements[operand] = temp;
}
}
return true;
}
// Whatever came before an operator that only sometimes runs its later operands has to be settled first, since
// the branch will skip any steps that were left to the later operands.
private bool Begin(List ancestors, Frame frame)
{
if (!frame.Conditional)
{
return true;
}
frame.Number = firstConditional + returnMe.Conditionals++;
return SpillPending(ancestors);
}
private static string EndLabel(Frame frame)
{
return "__cond" + frame.Number + "End";
}
private static string ElseLabel(Frame frame)
{
return "__cond" + frame.Number + "Else";
}
// Keeps the operand that decides which others run, if it might not read the same after a pause.
private bool StoreDecider(ExpressionSyntax decider)
{
if (!returnMe.Replacements.ContainsKey(decider) && !IsStable(decider))
{
ITypeSymbol type = TypeOf(decider);
if (type == null)
{
return false;
}
SpillTemp temp = NewTemp(type, decider.GetLocation());
returnMe.Steps.Add(SpillStep.Store(decider, temp));
returnMe.Replacements[decider] = temp;
}
return true;
}
private static string ArmLabel(Frame frame, int arm)
{
return "__cond" + frame.Number + "Arm" + arm;
}
// Lays out the arms of a switch expression one after another: each is tested, run if it matches, and left
// by a jump to the end.
private bool CompletedArm(Frame parent, int index)
{
ExpressionSyntax governing = parent.Done[0];
if (index == 0)
{
if (!StoreDecider(governing))
{
return false;
}
returnMe.Steps.Add(SpillStep.ArmTest(governing, parent.Arms[0], ArmLabel(parent, 0)));
return true;
}
int arm = index - 1;
if (arm < parent.Arms.Count - 1)
{
returnMe.Steps.Add(SpillStep.Jump(EndLabel(parent)));
returnMe.Steps.Add(SpillStep.Label(ArmLabel(parent, arm)));
returnMe.Steps.Add(SpillStep.ArmTest(governing, parent.Arms[arm + 1], ArmLabel(parent, arm + 1)));
}
else
{
returnMe.Steps.Add(SpillStep.Label(ArmLabel(parent, arm)));
returnMe.Steps.Add(SpillStep.Label(EndLabel(parent)));
}
return true;
}
// Adds the branch that decides, after its first operand, whether an operator's other operands run.
private bool BranchAfterFirstOperand(Frame frame, ExpressionSyntax decider)
{
// The statement tests and writes the variable in place, so it is read here and not kept.
if (!frame.Assigns && !StoreDecider(decider))
{
return false;
}
switch (frame.Node.Kind())
{
case SyntaxKind.LogicalAndExpression:
returnMe.Steps.Add(SpillStep.Branch(decider, true, NullCheck.None, EndLabel(frame)));
break;
case SyntaxKind.LogicalOrExpression:
returnMe.Steps.Add(SpillStep.Branch(decider, false, NullCheck.None, EndLabel(frame)));
break;
case SyntaxKind.CoalesceExpression:
case SyntaxKind.CoalesceAssignmentExpression:
returnMe.Steps.Add(SpillStep.Branch(decider, false, NullCheck.IsNotNull, EndLabel(frame)));
break;
case SyntaxKind.ConditionalExpression:
returnMe.Steps.Add(SpillStep.Branch(decider, true, NullCheck.None, ElseLabel(frame)));
break;
default:
returnMe.Steps.Add(SpillStep.Branch(decider, false, NullCheck.IsNull, EndLabel(frame)));
break;
}
return true;
}
// Records that an operand has been evaluated, and for an operator that only sometimes runs its later operands,
// lays out the jumps around them.
private bool Completed(Frame parent, ExpressionSyntax operand)
{
parent.Done.Add(operand);
if (!parent.Conditional)
{
return true;
}
int index = parent.Finished++;
if (index > 0 || parent.Assigns)
{
// The statement runs these itself, and only on the paths that reach them.
parent.Done.RemoveAt(parent.Done.Count - 1);
}
if (parent.Arms != null)
{
return CompletedArm(parent, index);
}
bool isConditionalExpression = parent.Node.IsKind(SyntaxKind.ConditionalExpression);
if (index == 0)
{
return BranchAfterFirstOperand(parent, operand);
}
if (isConditionalExpression && index == 1)
{
returnMe.Steps.Add(SpillStep.Jump(EndLabel(parent)));
returnMe.Steps.Add(SpillStep.Label(ElseLabel(parent)));
}
else
{
returnMe.Steps.Add(SpillStep.Label(EndLabel(parent)));
}
return true;
}
private bool Walk(IReadOnlyList roots)
{
var frames = new List();
var start = new Frame();
if (roots.Count == 1)
{
start.Node = roots[0];
if (!Describe(start))
{
return false;
}
}
else
{
// Several expressions that one statement runs in order, such as a declaration's initializers.
start.Operands.AddRange(roots);
}
if (!Begin(frames, start))
{
return false;
}
frames.Add(start);
while (frames.Count > 0)
{
Frame top = frames[frames.Count - 1];
if (top.Next < top.Operands.Count)
{
ExpressionSyntax operand = top.Operands[top.Next++];
if (!spine.Contains(operand))
{
if (!Completed(top, operand))
{
return false;
}
continue;
}
var inner = new Frame { Node = operand };
if (!Describe(inner) || !Begin(frames, inner))
{
return false;
}
frames.Add(inner);
continue;
}
frames.RemoveAt(frames.Count - 1);
var call = top.Node as InvocationExpressionSyntax;
if (call != null && script.AnyPassageCalled(call) != null)
{
if (!SpillPending(frames))
{
return false;
}
IMethodSymbol passage = script.AnyPassageCalled(call);
SpillTemp temp = null;
if (!passage.ReturnsVoid)
{
// The type as constructed at the call, since the passage's own may be a type parameter's.
var target = (IMethodSymbol)script.ModelFor(call).GetSymbolInfo(call).Symbol;
temp = NewTemp(target.ReturnType, call.GetLocation());
returnMe.Replacements[call] = temp;
}
returnMe.Steps.Add(SpillStep.Pause(call, temp));
}
if (frames.Count > 0 && !Completed(frames[frames.Count - 1], top.Node))
{
return false;
}
}
return true;
}
private static bool IsInside(SyntaxNode node, SyntaxNode ancestor)
{
for (SyntaxNode current = node.Parent; current != null; current = current.Parent)
{
if (current == ancestor)
{
return true;
}
}
return false;
}
// The operand a step evaluates, which reads each temp that stands in for something inside it.
private static SyntaxNode RootOf(SpillStep step)
{
return step.Call ?? (SyntaxNode)step.Value;
}
// A temp needs saving at each pause that falls after the step that sets it and no later than the last step
// that reads it. Steps are counted in the order they run on any path, so a pause on a path the temp is never
// set on is counted too, which only saves a value that wasn't used. The pause's own arguments count as reads,
// since they're worked out again each time the callee is entered.
private void FindLiveTemps()
{
var lastRead = new Dictionary();
foreach (KeyValuePair replacement in returnMe.Replacements)
{
// The statement itself reads whatever isn't hidden inside an operand that was stored whole.
SyntaxNode nearest = null;
for (SyntaxNode current = replacement.Key.Parent; current != null && nearest == null;
current = current.Parent)
{
// A compound assignment's target is still reached through its parts, after the old value is kept.
if (returnMe.Replacements.ContainsKey(current) && !IsCompoundTarget(current))
{
nearest = current;
}
}
int last = nearest == null ? returnMe.Steps.Count : -1;
for (int i = 0; i < returnMe.Steps.Count; ++i)
{
SyntaxNode root = RootOf(returnMe.Steps[i]);
if (root == null)
{
continue;
}
bool reads = IsInside(replacement.Key, root)
&& (nearest == null || nearest == root || !IsInside(nearest, root));
if (root == replacement.Key && (returnMe.Steps[i].Kind == SpillStepKind.Branch
|| returnMe.Steps[i].Kind == SpillStepKind.ArmTest))
{
reads = true;
}
if (reads)
{
last = Math.Max(last, i);
}
}
lastRead[replacement.Value] = last;
}
for (int i = 0; i < returnMe.Steps.Count; ++i)
{
SpillStep step = returnMe.Steps[i];
if (step.Kind != SpillStepKind.Pause)
{
continue;
}
for (int earlier = 0; earlier < i; ++earlier)
{
SpillTemp temp = returnMe.Steps[earlier].Temp;
if (temp != null && lastRead[temp] >= i)
{
step.LiveTemps.Add(temp);
temp.Saved = true;
}
}
}
}
///
/// Returns the steps that take the passage calls out of , or null if it has none or has
/// something that can't be taken apart yet. is whatever contains the expression.
///
/// Where the temps' numbering starts, so a passage's temps differ in name.
/// Where the numbering of the jump labels starts.
public static SpillSequence Spill(ScriptClass script, SemanticModel model, SyntaxNode statement,
ExpressionSyntax root, int firstNumber, int firstConditional)
{
return Spill(script, model, statement, new[] { root }, firstNumber, firstConditional);
}
///
/// Returns the steps for several expressions that run one after another, in the order given.
///
public static SpillSequence Spill(ScriptClass script, SemanticModel model, SyntaxNode statement,
IReadOnlyList roots, int firstNumber, int firstConditional)
{
var spiller = new ExpressionSpiller(script, model, firstNumber, firstConditional);
var calls = new List();
foreach (ExpressionSyntax root in roots)
{
calls.AddRange(root.DescendantNodesAndSelf().OfType()
.Where(invocation => script.AnyPassageCalled(invocation) != null));
}
if (calls.Count == 0)
{
return null;
}
foreach (InvocationExpressionSyntax call in calls)
{
ExpressionSyntax root = null;
for (SyntaxNode node = call; root == null; node = node.Parent)
{
root = roots.FirstOrDefault(candidate => candidate == node);
}
if (InFunction(call, root) || (script.AnyPassageCalled(call).ReturnsVoid && call != root))
{
return null;
}
for (SyntaxNode node = call; ; node = node.Parent)
{
spiller.spine.Add(node);
if (node == root)
{
break;
}
}
}
spiller.FindAssigned(statement);
if (!spiller.Walk(roots))
{
return null;
}
spiller.FindLiveTemps();
return spiller.returnMe;
}
}
}