public override BoundNode VisitBinaryOperator(BoundBinaryOperator node) { BoundSpillSequenceBuilder builder = null; var right = VisitExpression(ref builder, node.Right); BoundExpression left; if (builder == null) { left = VisitExpression(ref builder, node.Left); } else { var leftBuilder = new BoundSpillSequenceBuilder(); left = VisitExpression(ref leftBuilder, node.Left); left = Spill(leftBuilder, left); if (node.OperatorKind == BinaryOperatorKind.LogicalBoolOr || node.OperatorKind == BinaryOperatorKind.LogicalBoolAnd) { leftBuilder.AddStatement(_F.If( node.OperatorKind == BinaryOperatorKind.LogicalBoolAnd ? left : _F.Not(left), UpdateStatement(builder, _F.Assignment(left, right), substituteTemps: false))); return(UpdateExpression(leftBuilder, left)); } else { // if the right-hand-side has await, spill the left leftBuilder.Include(builder); builder = leftBuilder; } } return(UpdateExpression(builder, node.Update(node.OperatorKind, left, right, node.ConstantValue, node.MethodOpt, node.ResultKind, node.Type))); }
public override BoundNode VisitCall(BoundCall node) { BoundSpillSequenceBuilder builder = null; var arguments = this.VisitExpressionList(ref builder, node.Arguments, node.ArgumentRefKindsOpt); BoundExpression receiver = null; if (builder == null) { receiver = VisitExpression(ref builder, node.ReceiverOpt); } else if (!node.Method.IsStatic) { // spill the receiver if there were await expressions in the arguments var receiverBuilder = new BoundSpillSequenceBuilder(); receiver = node.ReceiverOpt; RefKind refKind = ReceiverSpillRefKind(receiver); receiver = Spill(receiverBuilder, VisitExpression(ref receiverBuilder, receiver), refKind: refKind); receiverBuilder.Include(builder); builder = receiverBuilder; } return(UpdateExpression(builder, node.Update(receiver, node.Method, arguments))); }
public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node) { BoundSpillSequenceBuilder builder = null; var right = VisitExpression(ref builder, node.Right); BoundExpression left; if (builder == null || node.Left.Kind == BoundKind.Local) { left = VisitExpression(ref builder, node.Left); } else { // if the right-hand-side has await, spill the left var leftBuilder = new BoundSpillSequenceBuilder(); left = VisitExpression(ref leftBuilder, node.Left); if (left.Kind != BoundKind.Local) { left = Spill(leftBuilder, left, RefKind.Ref); } leftBuilder.Include(builder); builder = leftBuilder; } return(UpdateExpression(builder, node.Update(left, right, node.RefKind, node.Type))); }
private void OnAssignment(BinaryExpression node) { BoundSpillSequenceBuilder builder = null; var right = VisitExpression(ref builder, node.Right); Expression left; if (builder == null || node.Left.Entity.EntityType == EntityType.Local) { left = VisitExpression(ref builder, node.Left); } else { // if the right-hand-side has await, spill the left var leftBuilder = new BoundSpillSequenceBuilder(); left = VisitExpression(ref leftBuilder, node.Left); if (left.Entity.EntityType == EntityType.Local) { left = Spill(leftBuilder, left, true); } leftBuilder.Include(builder); builder = leftBuilder; } node.Left = left; node.Right = right; ReplaceCurrentNode(UpdateExpression(builder, node)); }
public override void OnBinaryExpression(BinaryExpression node) { if (node.Operator == BinaryOperatorType.Assign) { OnAssignment(node); return; } if (node.Operator == BinaryOperatorType.TypeTest) { OnIsaOperator(node); return; } BoundSpillSequenceBuilder builder = null; var right = VisitExpression(ref builder, node.Right); Expression left; if (builder == null) { left = VisitExpression(ref builder, node.Left); } else { var leftBuilder = new BoundSpillSequenceBuilder(); left = VisitExpression(ref leftBuilder, node.Left); left = Spill(leftBuilder, left); if (node.Operator == BinaryOperatorType.Or || node.Operator == BinaryOperatorType.And) { var tmp = _F.DeclareTempLocal(_currentMethod, node.ExpressionType); tmp.Local["SynthesizedKind"] = AWAIT_SPILL_MARKER; leftBuilder.AddLocal(tmp); leftBuilder.AddStatement(new ExpressionStatement(_F.CreateAssignment(_F.CreateLocalReference(tmp), left))); var trueBlock = new Block(); trueBlock.Add(UpdateExpression(builder, _F.CreateAssignment(_F.CreateLocalReference(tmp), right))); leftBuilder.AddStatement( new IfStatement(left.LexicalInfo, node.Operator == BinaryOperatorType.And ? _F.CreateLocalReference(tmp) : (Expression)_F.CreateNotExpression(_F.CreateLocalReference(tmp)), trueBlock, null)); ReplaceCurrentNode(UpdateExpression(leftBuilder, _F.CreateLocalReference(tmp))); return; } // if the right-hand-side has await, spill the left leftBuilder.Include(builder); builder = leftBuilder; } node.Left = left; node.Right = right; ReplaceCurrentNode(UpdateExpression(builder, node)); }
public override void OnMethodInvocationExpression(MethodInvocationExpression node) { BoundSpillSequenceBuilder builder = null; var entity = node.Target.Entity; if (entity.EntityType == EntityType.BuiltinFunction) { if (entity == BuiltinFunction.Eval) { OnEval(node); } else if (entity == BuiltinFunction.Switch) { throw new ArgumentException("Should be unreachable: Await spiller on switch"); } else { base.OnMethodInvocationExpression(node); } return; } var method = (IMethod)entity; var refs = method.GetParameters().Select(p => p.IsByRef).ToList(); node.Arguments = VisitExpressionList(ref builder, node.Arguments, refs); if (builder == null) { node.Target = VisitExpression(ref builder, node.Target); } else if (!method.IsStatic) { // spill the receiver if there were await expressions in the arguments var targetBuilder = new BoundSpillSequenceBuilder(); var target = node.Target; var isRef = TargetSpillRefKind(target); node.Target = Spill(targetBuilder, VisitExpression(ref targetBuilder, target), isRef); targetBuilder.Include(builder); builder = targetBuilder; } ReplaceCurrentNode(UpdateExpression(builder, node)); }
public override BoundNode VisitArrayCreation(BoundArrayCreation node) { BoundSpillSequenceBuilder builder = null; var init = (BoundArrayInitialization)VisitExpression(ref builder, node.InitializerOpt); BoundExpression bound; if (builder == null) { bound = VisitExpression(ref builder, node.Size); } else { // spill bounds expressions if initializers contain await var boundsBuilder = new BoundSpillSequenceBuilder(); bound = VisitExpression(ref boundsBuilder, node.Size); boundsBuilder.Include(builder); builder = boundsBuilder; } return(UpdateExpression(builder, node.Update(bound, init, node.Type))); }
public override BoundNode VisitPointerElementAccess(BoundPointerElementAccess node) { BoundSpillSequenceBuilder builder = null; var index = VisitExpression(ref builder, node.Index); BoundExpression expression; if (builder == null) { expression = VisitExpression(ref builder, node.Expression); } else { var expressionBuilder = new BoundSpillSequenceBuilder(); expression = VisitExpression(ref expressionBuilder, node.Expression); expression = Spill(expressionBuilder, expression); expressionBuilder.Include(builder); builder = expressionBuilder; } return(UpdateExpression(builder, node.Update(expression, index, node.Checked, node.Type))); }
private BoundExpression VisitExpression(ref BoundSpillSequenceBuilder builder, BoundExpression expression) { var e = (BoundExpression)this.Visit(expression); if (e == null || e.Kind != SpillSequenceBuilderKind) { return(e); } var newBuilder = (BoundSpillSequenceBuilder)e; if (builder == null) { builder = newBuilder.Update(null); } else { builder.Include(newBuilder); } return(newBuilder.Value); }
public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node) { BoundSpillSequenceBuilder builder = null; var right = VisitExpression(ref builder, node.Right); BoundExpression left = node.Left; if (builder == null) { left = VisitExpression(ref builder, left); } else { // if the right-hand-side has await, spill the left var leftBuilder = new BoundSpillSequenceBuilder(); switch (left.Kind) { case BoundKind.Local: case BoundKind.Parameter: // locals and parameters are directly assignable, LHS is not on the stack so nothing to spill break; case BoundKind.FieldAccess: var field = (BoundFieldAccess)left; // static fields are directly assignable, LHS is not on the stack, nothing to spill if (field.FieldSymbol.IsStatic) { break; } // instance fields are directly assignable, but receiver is pushed, so need to spill that. var receiver = VisitExpression(ref leftBuilder, field.ReceiverOpt); receiver = Spill(builder, receiver, field.FieldSymbol.ContainingType.IsValueType ? RefKind.Ref : RefKind.None); left = field.Update(receiver, field.FieldSymbol, field.ConstantValueOpt, field.ResultKind, field.Type); break; case BoundKind.ArrayAccess: var arrayAccess = (BoundArrayAccess)left; // array and indices are pushed on stack so need to spill that var expression = VisitExpression(ref leftBuilder, arrayAccess.Expression); expression = Spill(builder, expression, RefKind.None); var index = this.VisitExpression(ref builder, arrayAccess.Index); left = arrayAccess.Update(expression, index, arrayAccess.Type); break; default: // must be something indirectly assignable, just visit and spill as an ordinary Ref (not a RefReadOnly!!) // // NOTE: in some cases this will result in spiller producing an error. // For example if the LHS is a ref-returning method like // // obj.RefReturning(a, b, c) = await Something(); // // the spiller would eventually have to spill the evaluation result of "refReturning" call as an ordinary Ref, // which it can't. left = Spill(leftBuilder, VisitExpression(ref leftBuilder, left), RefKind.Ref); break; } leftBuilder.Include(builder); builder = leftBuilder; } return(UpdateExpression(builder, node.Update(left, right, node.IsRef, node.Type))); }
private BoundExpression Spill( BoundSpillSequenceBuilder builder, BoundExpression expression, RefKind refKind = RefKind.None, bool sideEffectsOnly = false) { Debug.Assert(builder != null); while (true) { switch (expression.Kind) { case BoundKind.ArrayInitialization: Debug.Assert(refKind == RefKind.None); Debug.Assert(!sideEffectsOnly); var arrayInitialization = (BoundArrayInitialization)expression; var newInitializers = VisitExpressionList(ref builder, arrayInitialization.Initializers, forceSpill: true); return(arrayInitialization.Update(newInitializers)); case BoundKind.ArgListOperator: Debug.Assert(refKind == RefKind.None); Debug.Assert(!sideEffectsOnly); var argumentList = (BoundArgListOperator)expression; var newArgs = VisitExpressionList(ref builder, argumentList.Arguments, argumentList.ArgumentRefKindsOpt, forceSpill: true); return(argumentList.Update(newArgs, argumentList.ArgumentRefKindsOpt, argumentList.Type)); case SpillSequenceBuilderKind: var sequenceBuilder = (BoundSpillSequenceBuilder)expression; builder.Include(sequenceBuilder); expression = sequenceBuilder.Value; continue; case BoundKind.Sequence: // neither the side-effects nor the value of the sequence contains await // (otherwise it would be converted to a SpillSequenceBuilder). if (refKind != RefKind.None) { return(expression); } goto default; case BoundKind.ThisReference: case BoundKind.BaseReference: if (refKind != RefKind.None || expression.Type.IsReferenceType) { return(expression); } goto default; case BoundKind.Parameter: if (refKind != RefKind.None) { return(expression); } goto default; case BoundKind.Local: var local = (BoundLocal)expression; if (local.LocalSymbol.SynthesizedKind == SynthesizedLocalKind.Spill || refKind != RefKind.None) { return(local); } goto default; case BoundKind.FieldAccess: var field = (BoundFieldAccess)expression; var fieldSymbol = field.FieldSymbol; if (fieldSymbol.IsStatic) { // no need to spill static fields if used as locations or if readonly if (refKind != RefKind.None || fieldSymbol.IsLet) { return(field); } goto default; } if (refKind == RefKind.None) { goto default; } var receiver = Spill(builder, field.ReceiverOpt, fieldSymbol.ContainingType.IsValueType ? refKind : RefKind.None); return(field.Update(receiver, fieldSymbol, field.ConstantValueOpt, field.ResultKind, field.Type)); case BoundKind.Literal: 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) { builder.AddStatement(_F.ExpressionStatement(expression)); return(null); } else { BoundAssignmentOperator assignToTemp; var replacement = _F.StoreToTemp( expression, out assignToTemp, refKind: refKind, kind: SynthesizedLocalKind.Spill, syntaxOpt: _F.Syntax); builder.AddLocal(replacement.LocalSymbol); builder.AddStatement(_F.ExpressionStatement(assignToTemp)); return(replacement); } } } }
private BoundExpression Spill( BoundSpillSequenceBuilder builder, BoundExpression expression, RefKind refKind = RefKind.None, bool sideEffectsOnly = false) { Debug.Assert(builder != null); while (true) { switch (expression.Kind) { case BoundKind.ArrayInitialization: Debug.Assert(refKind == RefKind.None); Debug.Assert(!sideEffectsOnly); var arrayInitialization = (BoundArrayInitialization)expression; var newInitializers = VisitExpressionList(ref builder, arrayInitialization.Initializers, forceSpill: true); return(arrayInitialization.Update(newInitializers)); case BoundKind.ArgListOperator: Debug.Assert(refKind == RefKind.None); Debug.Assert(!sideEffectsOnly); var argumentList = (BoundArgListOperator)expression; var newArgs = VisitExpressionList(ref builder, argumentList.Arguments, argumentList.ArgumentRefKindsOpt, forceSpill: true); return(argumentList.Update(newArgs, argumentList.ArgumentRefKindsOpt, argumentList.Type)); case SpillSequenceBuilder: var sequenceBuilder = (BoundSpillSequenceBuilder)expression; builder.Include(sequenceBuilder); expression = sequenceBuilder.Value; continue; case BoundKind.Sequence: // We don't need promote short-lived variables defined by the sequence to long-lived, // since neither the side-effects nor the value of the sequence contains await // (otherwise it would be converted to a SpillSequenceBuilder). var sequence = (BoundSequence)expression; builder.AddLocals(sequence.Locals); builder.AddExpressions(sequence.SideEffects); expression = sequence.Value; continue; case BoundKind.ThisReference: case BoundKind.BaseReference: if (refKind != RefKind.None || expression.Type.IsReferenceType) { return(expression); } goto default; case BoundKind.Parameter: if (refKind != RefKind.None) { return(expression); } goto default; case BoundKind.Local: var local = (BoundLocal)expression; if (local.LocalSymbol.SynthesizedKind == SynthesizedLocalKind.AwaitSpill || refKind != RefKind.None) { return(local); } goto default; case BoundKind.FieldAccess: var field = (BoundFieldAccess)expression; if (field.FieldSymbol.IsReadOnly) { if (field.FieldSymbol.IsStatic) { return(field); } if (field.FieldSymbol.ContainingType.IsValueType) { goto default; } // save the receiver; can get the field later. var receiver = Spill(builder, field.ReceiverOpt, (refKind != RefKind.None && field.FieldSymbol.Type.IsReferenceType) ? refKind : RefKind.None, sideEffectsOnly); return(field.Update(receiver, field.FieldSymbol, field.ConstantValueOpt, field.ResultKind, field.Type)); } goto default; case BoundKind.Call: var call = (BoundCall)expression; if (refKind != RefKind.None) { Debug.Assert(call.Method.RefKind != RefKind.None); _F.Diagnostics.Add(ErrorCode.ERR_RefReturningCallAndAwait, _F.Syntax.Location, call.Method); refKind = RefKind.None; // Switch the RefKind to avoid asserting later in the pipeline } goto default; case BoundKind.Literal: 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) { builder.AddStatement(_F.ExpressionStatement(expression)); return(null); } else { BoundAssignmentOperator assignToTemp; Debug.Assert(_F.Syntax.IsKind(SyntaxKind.AwaitExpression)); var replacement = _F.StoreToTemp( expression, out assignToTemp, refKind: refKind, kind: SynthesizedLocalKind.AwaitSpill, syntaxOpt: _F.Syntax); builder.AddLocal(replacement.LocalSymbol, _F.Diagnostics); builder.AddStatement(_F.ExpressionStatement(assignToTemp)); return(replacement); } } } }
private Expression Spill( BoundSpillSequenceBuilder builder, Expression expression, bool isRef = false, bool sideEffectsOnly = false) { Debug.Assert(builder != null); while (true) { switch (expression.NodeType) { case NodeType.ListLiteralExpression: case NodeType.ArrayLiteralExpression: Debug.Assert(!isRef); Debug.Assert(!sideEffectsOnly); var arrayInitialization = (ListLiteralExpression)expression; var newInitializers = VisitExpressionList(ref builder, arrayInitialization.Items, forceSpill: true); arrayInitialization.Items = newInitializers; return(arrayInitialization); case NodeType.HashLiteralExpression: Debug.Assert(!isRef); Debug.Assert(!sideEffectsOnly); var hashInitialization = (HashLiteralExpression)expression; var newInitializerPairs = VisitExpressionPairList(ref builder, hashInitialization.Items, forceSpill: true); hashInitialization.Items = newInitializerPairs; return(hashInitialization); case SpillSequenceBuilder: var sequenceBuilder = (BoundSpillSequenceBuilder)expression; builder.Include(sequenceBuilder); expression = sequenceBuilder.Value; continue; case NodeType.SelfLiteralExpression: case NodeType.SuperLiteralExpression: if (isRef || !expression.ExpressionType.IsValueType) { return(expression); } goto default; case NodeType.ParameterDeclaration: if (isRef) { return(expression); } goto default; case NodeType.ReferenceExpression: var local = expression.Entity as InternalLocal; if (local != null) { if (local.Local["SynthesizedKind"] == AWAIT_SPILL_MARKER || isRef) { return(expression); } } goto default; case NodeType.MemberReferenceExpression: if (expression.Entity.EntityType == EntityType.Field) { var field = (IField)expression.Entity; if (field.IsInitOnly) { if (field.IsStatic) { return(expression); } if (!field.DeclaringType.IsValueType) { // save the receiver; can get the field later. var target = Spill(builder, ((MemberReferenceExpression)expression).Target, isRef && !field.Type.IsValueType, sideEffectsOnly); return(_F.CreateMemberReference(target, field)); } } } goto default; case NodeType.MethodInvocationExpression: var mie = (MethodInvocationExpression)expression; if (expression.Entity == BuiltinFunction.Eval) { builder.AddExpressions(mie.Arguments.Where(a => a != mie.Arguments.Last)); expression = mie.Arguments.Last; continue; } if (isRef) { Debug.Assert(mie.ExpressionType.IsPointer); CompilerContext.Current.Errors.Add(CompilerErrorFactory.UnsafeReturnInAsync(mie)); } goto default; case NodeType.BoolLiteralExpression: case NodeType.CharLiteralExpression: case NodeType.DoubleLiteralExpression: case NodeType.IntegerLiteralExpression: case NodeType.NullLiteralExpression: case NodeType.RELiteralExpression: case NodeType.StringLiteralExpression: case NodeType.TypeofExpression: return(expression); default: if (expression.ExpressionType == _tss.VoidType || sideEffectsOnly) { builder.AddStatement(new ExpressionStatement(expression)); return(null); } var replacement = _F.DeclareTempLocal(_currentMethod, expression.ExpressionType); var assignToTemp = _F.CreateAssignment( _F.CreateLocalReference(replacement), expression); builder.AddLocal(replacement); builder.AddStatement(new ExpressionStatement(assignToTemp)); return(_F.CreateLocalReference(replacement)); } } }