Example #1
0
 public override BoundNode VisitFixedStatement(BoundFixedStatement node)
 {
     AddAll(node.Locals);
     base.VisitFixedStatement(node);
     RemoveAll(node.Locals);
     return(null);
 }
Example #2
0
        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()));
            }
        }