public override BoundNode VisitFixedStatement(BoundFixedStatement node) { AddAll(node.Locals); base.VisitFixedStatement(node); RemoveAll(node.Locals); return(null); }
public override BoundNode VisitFixedStatement(BoundFixedStatement node) { int numFixedLocals = node.Locals.Length; var localBuilder = ArrayBuilder <LocalSymbol> .GetInstance(numFixedLocals); localBuilder.AddRange(node.Locals); var statementBuilder = ArrayBuilder <BoundStatement> .GetInstance(numFixedLocals + 1 + 1); //+1 for body, +1 for hidden seq point var cleanup = new BoundStatement[numFixedLocals]; ImmutableArray <BoundLocalDeclaration> localDecls = node.Declarations.LocalDeclarations; Debug.Assert(localDecls.Length == numFixedLocals); for (int i = 0; i < numFixedLocals; i++) { BoundLocalDeclaration localDecl = localDecls[i]; LocalSymbol temp; LocalSymbol localToClear; statementBuilder.Add(InitializeFixedStatementLocal(localDecl, factory, out temp, out localToClear)); if (!ReferenceEquals(temp, null)) { localBuilder.Add(temp); } // NOTE: Dev10 nulls out the locals in declaration order (as opposed to "popping" them in reverse order). cleanup[i] = factory.Assignment(factory.Local(localToClear), factory.Null(localToClear.Type)); } BoundStatement rewrittenBody = VisitStatement(node.Body); statementBuilder.Add(rewrittenBody); statementBuilder.Add(factory.HiddenSequencePoint()); Debug.Assert(statementBuilder.Count == numFixedLocals + 1 + 1); // In principle, the cleanup code (i.e. nulling out the pinned variables) is always // in a finally block. However, we can optimize finally away (keeping the cleanup // code) in cases where both of the following are true: // 1) there are no branches out of the fixed statement; and // 2) the fixed statement is not in a try block (syntactic or synthesized). if (IsInTryBlock(node) || HasGotoOut(rewrittenBody)) { return(factory.Block( localBuilder.ToImmutableAndFree(), new BoundTryStatement( factory.Syntax, factory.Block(statementBuilder.ToImmutableAndFree()), ImmutableArray <BoundCatchBlock> .Empty, factory.Block(cleanup)))); } else { statementBuilder.AddRange(cleanup); return(factory.Block(localBuilder.ToImmutableAndFree(), statementBuilder.ToImmutableAndFree())); } }
public override BoundNode VisitFixedStatement(BoundFixedStatement node) { int numFixedLocals = node.Locals.Length; var localBuilder = ArrayBuilder<LocalSymbol>.GetInstance(numFixedLocals); localBuilder.AddRange(node.Locals); var statementBuilder = ArrayBuilder<BoundStatement>.GetInstance(numFixedLocals + 1 + 1); //+1 for body, +1 for hidden seq point var cleanup = new BoundStatement[numFixedLocals]; ImmutableArray<BoundLocalDeclaration> localDecls = node.Declarations.LocalDeclarations; Debug.Assert(localDecls.Length == numFixedLocals); for (int i = 0; i < numFixedLocals; i++) { BoundLocalDeclaration localDecl = localDecls[i]; LocalSymbol temp; LocalSymbol localToClear; statementBuilder.Add(InitializeFixedStatementLocal(localDecl, factory, out temp, out localToClear)); if (!ReferenceEquals(temp, null)) { localBuilder.Add(temp); } // NOTE: Dev10 nulls out the locals in declaration order (as opposed to "popping" them in reverse order). cleanup[i] = factory.Assignment(factory.Local(localToClear), factory.Null(localToClear.Type)); } BoundStatement rewrittenBody = VisitStatement(node.Body); statementBuilder.Add(rewrittenBody); statementBuilder.Add(factory.HiddenSequencePoint()); Debug.Assert(statementBuilder.Count == numFixedLocals + 1 + 1); // In principle, the cleanup code (i.e. nulling out the pinned variables) is always // in a finally block. However, we can optimize finally away (keeping the cleanup // code) in cases where both of the following are true: // 1) there are no branches out of the fixed statement; and // 2) the fixed statement is not in a try block (syntactic or synthesized). if (IsInTryBlock(node) || HasGotoOut(rewrittenBody)) { return factory.Block( localBuilder.ToImmutableAndFree(), new BoundTryStatement( factory.Syntax, factory.Block(statementBuilder.ToImmutableAndFree()), ImmutableArray<BoundCatchBlock>.Empty, factory.Block(cleanup))); } else { statementBuilder.AddRange(cleanup); return factory.Block(localBuilder.ToImmutableAndFree(), statementBuilder.ToImmutableAndFree()); } }
public override BoundNode VisitFixedStatement(BoundFixedStatement node) { ImmutableArray <BoundLocalDeclaration> localDecls = node.Declarations.LocalDeclarations; int numFixedLocals = localDecls.Length; var localBuilder = ArrayBuilder <LocalSymbol> .GetInstance(node.Locals.Length); localBuilder.AddRange(node.Locals); var statementBuilder = ArrayBuilder <BoundStatement> .GetInstance(numFixedLocals + 1 + 1); //+1 for body, +1 for hidden seq point var cleanup = new BoundStatement[numFixedLocals]; for (int i = 0; i < numFixedLocals; i++) { BoundLocalDeclaration localDecl = localDecls[i]; LocalSymbol pinnedTemp; statementBuilder.Add( InitializeFixedStatementLocal(localDecl, _factory, out pinnedTemp) ); localBuilder.Add(pinnedTemp); // NOTE: Dev10 nulls out the locals in declaration order (as opposed to "popping" them in reverse order). if (pinnedTemp.RefKind == RefKind.None) { // temp = null; cleanup[i] = _factory.Assignment( _factory.Local(pinnedTemp), _factory.Null(pinnedTemp.Type) ); } else { Debug.Assert(!pinnedTemp.Type.IsManagedTypeNoUseSiteDiagnostics); // temp = ref *default(T*); cleanup[i] = _factory.Assignment( _factory.Local(pinnedTemp), new BoundPointerIndirectionOperator( _factory.Syntax, _factory.Default(new PointerTypeSymbol(pinnedTemp.TypeWithAnnotations)), pinnedTemp.Type ), isRef: true ); } } BoundStatement?rewrittenBody = VisitStatement(node.Body); Debug.Assert(rewrittenBody is { });
/// <summary> /// Basically, what we need to know is, if an exception occurred within the fixed statement, would /// additional code in the current method be executed before its stack frame was popped? /// </summary> private static bool IsInTryBlock(BoundFixedStatement boundFixed) { CSharpSyntaxNode node = boundFixed.Syntax.Parent; while (node != null) { switch (node.Kind) { case SyntaxKind.TryStatement: // NOTE: if we started in the catch or finally of this try statement, // we will have bypassed this node. return(true); case SyntaxKind.UsingStatement: // ACASEY: In treating using statements as try-finally's, we're following // Dev11. The practical explanation for Dev11's behavior is that using // statements have already been lowered by the time the check is performed. // A more thoughtful explanation is that user code could run between the // raising of an exception and the unwinding of the stack (via Dispose()) // and that user code would likely appreciate the reduced memory pressure // of having the fixed local unpinned. // NOTE: As in Dev11, we're not emitting a try-finally if the fixed // statement is nested within a lock statement. Practically, dev11 // probably lowers locks after fixed statement, and so, does not see // the try-finally. More thoughtfully, no user code will run in the // finally statement, so it's not necessary. // BREAK: Takes into account whether an outer fixed statement will be // lowered into a try-finally block and responds accordingly. This is // unnecessary since nothing will ever be allocated in the finally // block of a lowered fixed statement, so memory pressure is not an // issue. Note that only nested fixed statements where the outer (but // not the inner) fixed statement has an unmatched goto, but is not // contained in a try-finally, will be affected. e.g. // fixed (...) { // fixed (...) { } // goto L1: ; // } return(true); case SyntaxKind.ForEachStatement: // We're being conservative here - there's actually only // a try block if the enumerator is disposable, but we // can't tell that from the syntax. Dev11 checks in the // lowered tree, so it is more precise. return(true); case SyntaxKind.SimpleLambdaExpression: case SyntaxKind.ParenthesizedLambdaExpression: case SyntaxKind.AnonymousMethodExpression: // Stop looking. return(false); case SyntaxKind.CatchClause: // If we're in the catch of a try-catch-finally, then // we're still in the scope of the try-finally handler. if (((TryStatementSyntax)node.Parent).Finally != null) { return(true); } goto case SyntaxKind.FinallyClause; case SyntaxKind.FinallyClause: // Skip past the enclosing try to avoid a false positive. node = node.Parent; Debug.Assert(node.Kind == SyntaxKind.TryStatement); node = node.Parent; break; default: if (node is MemberDeclarationSyntax) { // Stop looking. return(false); } node = node.Parent; break; } } return(false); }
/// <summary> /// Basically, what we need to know is, if an exception occurred within the fixed statement, would /// additional code in the current method be executed before its stack frame was popped? /// </summary> private static bool IsInTryBlock(BoundFixedStatement boundFixed) { CSharpSyntaxNode node = boundFixed.Syntax.Parent; while (node != null) { switch (node.Kind) { case SyntaxKind.TryStatement: // NOTE: if we started in the catch or finally of this try statement, // we will have bypassed this node. return true; case SyntaxKind.UsingStatement: // ACASEY: In treating using statements as try-finally's, we're following // Dev11. The practical explanation for Dev11's behavior is that using // statements have already been lowered by the time the check is performed. // A more thoughtful explanation is that user code could run between the // raising of an exception and the unwinding of the stack (via Dispose()) // and that user code would likely appreciate the reduced memory pressure // of having the fixed local unpinned. // NOTE: As in Dev11, we're not emitting a try-finally if the fixed // statement is nested within a lock statement. Practically, dev11 // probably lowers locks after fixed statement, and so, does not see // the try-finally. More thoughtfully, no user code will run in the // finally statement, so it's not necessary. // BREAK: Takes into account whether an outer fixed statement will be // lowered into a try-finally block and responds accordingly. This is // unnecessary since nothing will ever be allocated in the finally // block of a lowered fixed statement, so memory pressure is not an // issue. Note that only nested fixed statements where the outer (but // not the inner) fixed statement has an unmatched goto, but is not // contained in a try-finally, will be affected. e.g. // fixed (...) { // fixed (...) { } // goto L1: ; // } return true; case SyntaxKind.ForEachStatement: // We're being conservative here - there's actually only // a try block if the enumerator is disposable, but we // can't tell that from the syntax. Dev11 checks in the // lowered tree, so it is more precise. return true; case SyntaxKind.SimpleLambdaExpression: case SyntaxKind.ParenthesizedLambdaExpression: case SyntaxKind.AnonymousMethodExpression: // Stop looking. return false; case SyntaxKind.CatchClause: // If we're in the catch of a try-catch-finally, then // we're still in the scope of the try-finally handler. if (((TryStatementSyntax)node.Parent).Finally != null) { return true; } goto case SyntaxKind.FinallyClause; case SyntaxKind.FinallyClause: // Skip past the enclosing try to avoid a false positive. node = node.Parent; Debug.Assert(node.Kind == SyntaxKind.TryStatement); node = node.Parent; break; default: if (node is MemberDeclarationSyntax) { // Stop looking. return false; } node = node.Parent; break; } } return false; }
public override BoundNode VisitFixedStatement(BoundFixedStatement node) { ImmutableArray <BoundLocalDeclaration> localDecls = node.Declarations.LocalDeclarations; int numFixedLocals = localDecls.Length; var localBuilder = ArrayBuilder <LocalSymbol> .GetInstance(node.Locals.Length); localBuilder.AddRange(node.Locals); var statementBuilder = ArrayBuilder <BoundStatement> .GetInstance(numFixedLocals + 1 + 1); //+1 for body, +1 for hidden seq point var cleanup = new BoundStatement[numFixedLocals]; for (int i = 0; i < numFixedLocals; i++) { BoundLocalDeclaration localDecl = localDecls[i]; LocalSymbol pinnedTemp; statementBuilder.Add(InitializeFixedStatementLocal(localDecl, _factory, out pinnedTemp)); localBuilder.Add(pinnedTemp); // NOTE: Dev10 nulls out the locals in declaration order (as opposed to "popping" them in reverse order). if (pinnedTemp.RefKind == RefKind.None) { // temp = null; cleanup[i] = _factory.Assignment(_factory.Local(pinnedTemp), _factory.Null(pinnedTemp.Type)); } else { Debug.Assert(!pinnedTemp.Type.IsManagedType); // temp = ref *default(T*); cleanup[i] = _factory.Assignment(_factory.Local(pinnedTemp), new BoundPointerIndirectionOperator( _factory.Syntax, _factory.Default(new PointerTypeSymbol(pinnedTemp.TypeWithAnnotations)), pinnedTemp.Type), isRef: true); } } BoundStatement rewrittenBody = VisitStatement(node.Body); statementBuilder.Add(rewrittenBody); statementBuilder.Add(_factory.HiddenSequencePoint()); Debug.Assert(statementBuilder.Count == numFixedLocals + 1 + 1); // In principle, the cleanup code (i.e. nulling out the pinned variables) is always // in a finally block. However, we can optimize finally away (keeping the cleanup // code) in cases where both of the following are true: // 1) there are no branches out of the fixed statement; and // 2) the fixed statement is not in a try block (syntactic or synthesized). if (IsInTryBlock(node) || HasGotoOut(rewrittenBody)) { return(_factory.Block( localBuilder.ToImmutableAndFree(), new BoundTryStatement( _factory.Syntax, _factory.Block(statementBuilder.ToImmutableAndFree()), ImmutableArray <BoundCatchBlock> .Empty, _factory.Block(cleanup)))); } else { statementBuilder.AddRange(cleanup); return(_factory.Block(localBuilder.ToImmutableAndFree(), statementBuilder.ToImmutableAndFree())); } }