Exemplo n.º 1
0
        public void VisitNode(JSFunctionExpression fn)
        {
            Function = fn;
            FirstPass = GetFirstPass(Function.Method.QualifiedIdentifier);

            VisitChildren(fn);

            if (EnumeratorsToKill.Count > 0) {
                // Rerun the static analyzer since we made major changes
                InvalidateFirstPass();
                FirstPass = GetFirstPass(Function.Method.QualifiedIdentifier);

                // Scan to see if any of the enumerators we eliminated uses of are now
                //  unreferenced. If they are, eliminate them entirely.
                foreach (var variable in EnumeratorsToKill) {
                    var variableName = variable.Name;
                    var accesses = (
                        from a in FirstPass.Accesses
                        where a.Source.Name == variableName
                        select a
                    );

                    if (!accesses.Any()) {
                        var eliminator = new VariableEliminator(
                            variable, new JSNullExpression()
                        );
                        eliminator.Visit(fn);
                    }
                }
            }
        }
        public void VisitNode(JSFunctionExpression fn)
        {
            Function = fn;
            FirstPass = GetFirstPass(Function.Method.QualifiedIdentifier);

            VisitChildren(fn);
        }
Exemplo n.º 3
0
        public void VisitNode(JSFunctionExpression fn)
        {
            Function = fn;
            FirstPass = GetFirstPass(Function.Method.QualifiedIdentifier);

            VisitChildren(fn);

            if (ToDeclare.Count > 0) {
                int i = 0;

                foreach (var pd in ToDeclare) {
                    var es = new JSExpressionStatement(
                        new JSBinaryOperatorExpression(
                            JSOperator.Assignment, pd.Expression,
                            new JSDefaultValueLiteral(pd.Type),
                            pd.Type
                    ));

                    fn.Body.Statements.Insert(i++, es);
                }
            }
        }
Exemplo n.º 4
0
        public void VisitNode(JSFunctionExpression fn)
        {
            // Create a new visitor for nested function expressions
            if (Stack.OfType<JSFunctionExpression>().Skip(1).FirstOrDefault() != null) {
                var nested = new OptimizeArrayEnumerators(TypeSystem, FunctionSource);
                nested.Visit(fn);

                return;
            }

            Function = fn;
            FirstPass = FunctionSource.GetFirstPass(Function.Method.QualifiedIdentifier);

            VisitChildren(fn);

            if (EnumeratorsToKill.Count > 0) {
                // Rerun the static analyzer since we made major changes
                FunctionSource.InvalidateFirstPass(Function.Method.QualifiedIdentifier);
                FirstPass = FunctionSource.GetFirstPass(Function.Method.QualifiedIdentifier);

                // Scan to see if any of the enumerators we eliminated uses of are now
                //  unreferenced. If they are, eliminate them entirely.
                foreach (var variable in EnumeratorsToKill) {
                    var variableName = variable.Name;
                    var accesses = (
                        from a in FirstPass.Accesses
                        where a.Source.Name == variableName
                        select a
                    );

                    if (!accesses.Any()) {
                        var eliminator = new VariableEliminator(
                            variable, new JSNullExpression()
                        );
                        eliminator.Visit(fn);
                    }
                }
            }
        }
Exemplo n.º 5
0
        // Filters out invocations that are provably safe to ignore, based on proxy attributes/type info
        private FunctionAnalysis1stPass.Invocation[] FilterInvocations (FunctionAnalysis1stPass.Invocation[] invocations) {
            return invocations.Where(
                (invocation) => {
                    if (invocation.Method == null)
                        return true;

                    var secondPass = GetSecondPass(invocation.Method);
                    if (secondPass == null)
                        return true;

                    return (!secondPass.IsPure);
                }
            ).ToArray();
        }
Exemplo n.º 6
0
        public void VisitNode (JSFunctionExpression fn) {
            FirstPass = GetFirstPass(fn.Method.QualifiedIdentifier);
            if (FirstPass == null)
                throw new InvalidOperationException(String.Format(
                    "No first pass static analysis data for method '{0}'",
                    fn.Method.QualifiedIdentifier
                ));

            ExemptVariablesFromEffectivelyConstantStatus();

            foreach (var v in fn.AllVariables.Values.ToArray()) {
                if (v.IsThis || v.IsParameter)
                    continue;

                var assignments = (from a in FirstPass.Assignments where v.Identifier.Equals(a.Target) select a).ToArray();
                var reassignments = (from a in FirstPass.Assignments where v.Identifier.Equals(a.SourceVariable) select a).ToArray();
                var accesses = (from a in FirstPass.Accesses where v.Identifier.Equals(a.Source) select a).ToArray();
                var invocations = (from i in FirstPass.Invocations where v.Name == i.ThisVariable select i).ToArray();
                var unsafeInvocations = FilterInvocations(invocations);
                var isPassedByReference = FirstPass.VariablesPassedByRef.Contains(v.Name);

                if (assignments.FirstOrDefault() == null) {
                    if ((accesses.Length == 0) && (invocations.Length == 0) && (reassignments.Length == 0) && !isPassedByReference) {
                        if (TraceLevel >= 1)
                            Console.WriteLine("Eliminating {0} because it is never used.", v);

                        if (!DryRun) {
                            EliminatedVariables.Add(v);
                            EliminateVariable(fn, v, new JSEliminatedVariable(v), fn.Method.QualifiedIdentifier);

                            // We've invalidated the static analysis data so the best choice is to abort.
                            break;
                        }
                    } else {
                        if (TraceLevel >= 2)
                            Console.WriteLine("Never found an initial assignment for {0}.", v);
                    }

                    continue;
                }

                var valueType = v.GetActualType(TypeSystem);
                if (TypeUtil.IsIgnoredType(valueType))
                    continue;

                if (FirstPass.VariablesPassedByRef.Contains(v.Name)) {
                    if (TraceLevel >= 2)
                        Console.WriteLine("Cannot eliminate {0}; it is passed by reference.", v);

                    continue;
                }

                if (unsafeInvocations.Length > 1) {
                    if (TraceLevel >= 2)
                        Console.WriteLine("Cannot eliminate {0}; methods are invoked on it multiple times that are not provably safe.", v);

                    continue;
                }

                if ((from a in accesses where a.IsControlFlow select a).FirstOrDefault() != null) {
                    if (TraceLevel >= 2)
                        Console.WriteLine("Cannot eliminate {0}; it participates in control flow.", v);

                    continue;
                }

                if (assignments.Length > 1) {
                    if (TraceLevel >= 2)
                        Console.WriteLine("Cannot eliminate {0}; it is reassigned.", v);

                    continue;
                }

                var replacementAssignment = assignments.First();
                var replacement = replacementAssignment.NewValue;
                if (replacement.SelfAndChildrenRecursive.Contains(v)) {
                    if (TraceLevel >= 2)
                        Console.WriteLine("Cannot eliminate {0}; it contains a self-reference.", v);

                    continue;
                }

                if (replacement.SelfAndChildrenRecursive.OfType<JSBinaryOperatorExpression>().Any(boe => boe.Operator is JSAssignmentOperator)) {
                    if (TraceLevel >= 2)
                        Console.WriteLine("Cannot eliminate {0}; it contains an assignment.", v);

                    continue;
                }

                var copies = (from a in FirstPass.Assignments where v.Identifier.Equals(a.SourceVariable) select a).ToArray();
                if (
                    (copies.Length + accesses.Length) > 1
                ) {
                    if (CanSkipUsageVeto(replacement)) {
                        if (TraceLevel >= 5)
                            Console.WriteLine("Skipping veto of elimination for {0} because it is a literal.", v);
                    } else {
                        if (TraceLevel >= 2)
                            Console.WriteLine("Cannot eliminate {0}; it is used multiple times.", v);

                        continue;
                    }
                }

                if (!IsEffectivelyConstant(v, replacement)) {
                    if (TraceLevel >= 2)
                        Console.WriteLine("Cannot eliminate {0}; {1} is not a constant expression.", v, replacement);

                    continue;
                }

                var replacementField = JSPointerExpressionUtil.UnwrapExpression(replacement) as JSFieldAccess;
                if (replacementField == null) {
                    var replacementRef = replacement as JSReferenceExpression;
                    if (replacementRef != null)
                        replacementField = replacementRef.Referent as JSFieldAccess;
                }

                var _affectedFields = replacement.SelfAndChildrenRecursive.OfType<JSField>();
                if (replacementField != null)
                    _affectedFields = _affectedFields.Concat(new[] { replacementField.Field });

                var affectedFields = new HashSet<FieldInfo>((from jsf in _affectedFields select jsf.Field));
                _affectedFields = null;

                if ((affectedFields.Count > 0) || (replacementField != null))
                {
                    var firstAssignment = assignments.FirstOrDefault();
                    var lastAccess = accesses.LastOrDefault();

                    bool invalidatedByLaterFieldAccess = false;

                    foreach (var fieldAccess in FirstPass.FieldAccesses) {
                        // Note that we only compare the FieldInfo, not the this-reference.
                        // Otherwise, aliasing (accessing the same field through two this references) would cause us
                        //  to incorrectly eliminate a local.
                        if (!affectedFields.Contains(fieldAccess.Field.Field))
                            continue;

                        // Ignore field accesses before the replacement was initialized
                        if (fieldAccess.NodeIndex <= replacementAssignment.NodeIndex)
                            continue;

                        // If the field access comes after the last use of the temporary, we don't care
                        if ((lastAccess != null) && (fieldAccess.StatementIndex > lastAccess.StatementIndex))
                            continue;

                        // It's a read, so no impact on whether this optimization is valid
                        if (fieldAccess.IsRead)
                            continue;

                        if (TraceLevel >= 2)
                            Console.WriteLine(String.Format("Cannot eliminate {0}; {1} is potentially mutated later", v, fieldAccess.Field.Field));

                        invalidatedByLaterFieldAccess = true;
                        break;
                    }

                    if (invalidatedByLaterFieldAccess)
                        continue;

                    foreach (var invocation in FirstPass.Invocations) {
                        // If the invocation comes after (or is) the last use of the temporary, we don't care
                        if ((lastAccess != null) && (invocation.StatementIndex >= lastAccess.StatementIndex))
                            continue;

                        // Same goes for the first assignment.
                        if ((firstAssignment != null) && (invocation.StatementIndex <= firstAssignment.StatementIndex))
                            continue;

                        var invocationSecondPass = GetSecondPass(invocation.Method);
                        if (
                            (invocationSecondPass == null) ||
                            (invocationSecondPass.MutatedFields == null)
                        ) {
                            if (invocation.ThisAndVariables.Any((kvp) => kvp.Value.ToEnumerable().Contains(v.Identifier))) {
                                if (TraceLevel >= 2)
                                    Console.WriteLine(String.Format("Cannot eliminate {0}; a method call without field mutation data ({1}) is invoked between its initialization and use with it as an argument", v, invocation.Method ?? invocation.NonJSMethod));

                                invalidatedByLaterFieldAccess = true;
                            }
                        } else if (affectedFields.Any(invocationSecondPass.FieldIsMutatedRecursively)) {
                            if (TraceLevel >= 2)
                                Console.WriteLine(String.Format("Cannot eliminate {0}; a method call ({1}) potentially mutates a field it references", v, invocation.Method ?? invocation.NonJSMethod));

                            invalidatedByLaterFieldAccess = true;
                        }
                    }

                    if (invalidatedByLaterFieldAccess)
                        continue;
                }

                if (TraceLevel >= 1)
                    Console.WriteLine(String.Format("Eliminating {0} <- {1}", v, replacement));

                if (!DryRun) {
                    EliminatedVariables.Add(v);
                    EliminateVariable(fn, v, replacement, fn.Method.QualifiedIdentifier);

                    // We've invalidated the static analysis data so the best choice is to abort.
                    break;
                }
            }
        }
Exemplo n.º 7
0
 private FunctionAnalysis2ndPass CreateSecondPassForOverridableMethod(MethodInfo method, FunctionAnalysis1stPass firstPass)
 {
     // FIXME: Existing code is probably wrong in terms of static analysis for virtual method calls. Welp.
     return(new FunctionAnalysis2ndPass(this, firstPass, false));
 }
Exemplo n.º 8
0
 private FunctionAnalysis2ndPass CreateSecondPassForKnownMethod(FunctionAnalysis1stPass firstPass)
 {
     return(new FunctionAnalysis2ndPass(this, firstPass, true));
 }
        public void VisitNode(JSFunctionExpression fn)
        {
            // Create a new visitor for nested function expressions
            if (Stack.OfType<JSFunctionExpression>().Skip(1).FirstOrDefault() != null) {
                bool eliminated = false;

                do {
                    var nested = new EliminateSingleUseTemporaries(TypeSystem, fn.AllVariables, FunctionSource);
                    nested.Visit(fn);
                    eliminated = nested.EliminatedVariables.Count > 0;
                } while (eliminated);

                return;
            }

            var nullList = new List<int>();
            FirstPass = FunctionSource.GetFirstPass(fn.Identifier);
            if (FirstPass == null)
                throw new InvalidOperationException();

            VisitChildren(fn);

            foreach (var v in fn.AllVariables.Values.ToArray()) {
                if (v.IsThis || v.IsParameter)
                    continue;

                var valueType = v.GetExpectedType(TypeSystem);
                if (ILBlockTranslator.IsIgnoredType(valueType))
                    continue;

                var assignments = (from a in FirstPass.Assignments where v.Equals(a.Target) select a).ToArray();
                var accesses = (from a in FirstPass.Accesses where v.Equals(a.Source) select a).ToArray();

                if (FirstPass.VariablesPassedByRef.Contains(v.Name)) {
                    if (TraceLevel >= 2)
                        Debug.WriteLine(String.Format("Cannot eliminate {0}; it is passed by reference.", v));

                    continue;
                }

                if (assignments.FirstOrDefault() == null) {
                    if (accesses.Length == 0) {
                        if (TraceLevel >= 1)
                            Debug.WriteLine(String.Format("Eliminating {0} because it is never used.", v));

                        EliminatedVariables.Add(v);
                        EliminateVariable(fn, v, new JSEliminatedVariable(v));
                    } else {
                        if (TraceLevel >= 2)
                            Debug.WriteLine(String.Format("Never found an initial assignment for {0}.", v));
                    }

                    continue;
                }

                if ((from a in accesses where a.IsControlFlow select a).FirstOrDefault() != null) {
                    if (TraceLevel >= 2)
                        Debug.WriteLine(String.Format("Cannot eliminate {0}; it participates in control flow.", v));

                    continue;
                }

                /*
                if ((from a in FirstPass.Assignments where v.Equals(a.Target) && a.IsConversion select a).FirstOrDefault() != null) {
                    if (TraceLevel >= 2)
                        Debug.WriteLine(String.Format("Cannot eliminate {0}; it undergoes type conversion.", v));

                    continue;
                }
                 */

                if (assignments.Length > 1) {
                    if (TraceLevel >= 2)
                        Debug.WriteLine(String.Format("Cannot eliminate {0}; it is reassigned.", v));

                    continue;
                }

                var copies = (from a in FirstPass.Assignments where v.Equals(a.SourceVariable) select a).ToArray();
                if ((copies.Length + accesses.Length) > 1) {
                    if (TraceLevel >= 2)
                        Debug.WriteLine(String.Format("Cannot eliminate {0}; it is used multiple times.", v));

                    continue;
                }

                var replacement = assignments.First().NewValue;
                if (replacement.AllChildrenRecursive.Contains(v)) {
                    if (TraceLevel >= 2)
                        Debug.WriteLine(String.Format("Cannot eliminate {0}; it contains a self-reference.", v));

                    continue;
                }

                if (!IsEffectivelyConstant(v, replacement)) {
                    if (TraceLevel >= 2)
                        Debug.WriteLine(String.Format("Cannot eliminate {0}; it is not a constant expression.", v));

                    continue;
                }

                if (TraceLevel >= 1)
                    Debug.WriteLine(String.Format("Eliminating {0} <- {1}", v, replacement));

                var transferDataTo = replacement as JSVariable;
                if (transferDataTo != null) {
                    foreach (var access in accesses) {
                        FirstPass.Accesses.Remove(access);
                        FirstPass.Accesses.Add(new FunctionAnalysis1stPass.Access(
                            access.StatementIndex, access.NodeIndex,
                            transferDataTo, access.IsControlFlow
                        ));
                    }

                    foreach (var assignment in assignments) {
                        FirstPass.Assignments.Remove(assignment);
                        FirstPass.Assignments.Add(new FunctionAnalysis1stPass.Assignment(
                            assignment.StatementIndex, assignment.NodeIndex,
                            transferDataTo, assignment.NewValue, assignment.Operator,
                            assignment.TargetType, assignment.SourceType

                       ));
                    }
                }

                FirstPass.Assignments.RemoveAll((a) => v.Equals(a.Target));
                FirstPass.Accesses.RemoveAll((a) => v.Equals(a.Source));

                EliminatedVariables.Add(v);

                EliminateVariable(fn, v, replacement);
            }
        }
Exemplo n.º 10
0
        public void VisitNode(JSFunctionExpression fn)
        {
            var nullList = new List<int>();
            FirstPass = GetFirstPass(fn.Method.QualifiedIdentifier);
            if (FirstPass == null)
                throw new InvalidOperationException(String.Format(
                    "No first pass static analysis data for method '{0}'",
                    fn.Method.QualifiedIdentifier
                ));

            VisitChildren(fn);

            foreach (var v in fn.AllVariables.Values.ToArray()) {
                if (v.IsThis || v.IsParameter)
                    continue;

                var valueType = v.GetActualType(TypeSystem);
                if (TypeUtil.IsIgnoredType(valueType))
                    continue;

                var assignments = (from a in FirstPass.Assignments where v.Equals(a.Target) select a).ToArray();
                var reassignments = (from a in FirstPass.Assignments where v.Equals(a.SourceVariable) select a).ToArray();
                var accesses = (from a in FirstPass.Accesses where v.Equals(a.Source) select a).ToArray();
                var invocations = (from i in FirstPass.Invocations where v.Name == i.ThisVariable select i).ToArray();

                if (FirstPass.VariablesPassedByRef.Contains(v.Name)) {
                    if (TraceLevel >= 2)
                        Debug.WriteLine(String.Format("Cannot eliminate {0}; it is passed by reference.", v));

                    continue;
                }

                if (assignments.FirstOrDefault() == null) {
                    if ((accesses.Length == 0) && (invocations.Length == 0) && (reassignments.Length == 0)) {
                        if (TraceLevel >= 1)
                            Debug.WriteLine(String.Format("Eliminating {0} because it is never used.", v));

                        EliminatedVariables.Add(v);
                        EliminateVariable(fn, v, new JSEliminatedVariable(v), fn.Method.QualifiedIdentifier);

                        // We've invalidated the static analysis data so the best choice is to abort.
                        break;
                    } else {
                        if (TraceLevel >= 2)
                            Debug.WriteLine(String.Format("Never found an initial assignment for {0}.", v));
                    }

                    continue;
                }

                if (invocations.Length > 0) {
                    if (TraceLevel >= 2)
                        Debug.WriteLine(String.Format("Cannot eliminate {0}; methods are invoked on it.", v));

                    continue;
                }

                if ((from a in accesses where a.IsControlFlow select a).FirstOrDefault() != null) {
                    if (TraceLevel >= 2)
                        Debug.WriteLine(String.Format("Cannot eliminate {0}; it participates in control flow.", v));

                    continue;
                }

                if (assignments.Length > 1) {
                    if (TraceLevel >= 2)
                        Debug.WriteLine(String.Format("Cannot eliminate {0}; it is reassigned.", v));

                    continue;
                }

                var copies = (from a in FirstPass.Assignments where v.Equals(a.SourceVariable) select a).ToArray();
                if ((copies.Length + accesses.Length) > 1) {
                    if (TraceLevel >= 2)
                        Debug.WriteLine(String.Format("Cannot eliminate {0}; it is used multiple times.", v));

                    continue;
                }

                var replacement = assignments.First().NewValue;
                if (replacement.SelfAndChildrenRecursive.Contains(v)) {
                    if (TraceLevel >= 2)
                        Debug.WriteLine(String.Format("Cannot eliminate {0}; it contains a self-reference.", v));

                    continue;
                }

                if (!IsEffectivelyConstant(v, replacement)) {
                    if (TraceLevel >= 2)
                        Debug.WriteLine(String.Format("Cannot eliminate {0}; it is not a constant expression.", v));

                    continue;
                }

                if (TraceLevel >= 1)
                    Debug.WriteLine(String.Format("Eliminating {0} <- {1}", v, replacement));

                EliminatedVariables.Add(v);
                EliminateVariable(fn, v, replacement, fn.Method.QualifiedIdentifier);

                // We've invalidated the static analysis data so the best choice is to abort.
                break;
            }
        }
Exemplo n.º 11
0
 private FunctionAnalysis2ndPass CreateSecondPassForOverridableMethod (MethodInfo method, FunctionAnalysis1stPass firstPass) {
     // FIXME: Existing code is probably wrong in terms of static analysis for virtual method calls. Welp.
     return new FunctionAnalysis2ndPass(this, firstPass, false);
 }
Exemplo n.º 12
0
 private FunctionAnalysis2ndPass CreateSecondPassForKnownMethod (FunctionAnalysis1stPass firstPass) {
     return new FunctionAnalysis2ndPass(this, firstPass, true);
 }
Exemplo n.º 13
0
        public void VisitNode (JSFunctionExpression fn) {
            Function = fn;
            FirstPass = GetFirstPass(Function.Method.QualifiedIdentifier);

            VisitChildren(fn);

            if (ToDeclare.Count > 0) {
                int i = 0;

                var vds = new JSVariableDeclarationStatement();

                foreach (var pd in ToDeclare) {
                    vds.Declarations.Add(
                        new JSBinaryOperatorExpression(
                            JSOperator.Assignment, pd.Expression,
                            pd.DefaultValue ?? new JSDefaultValueLiteral(pd.Type),
                            pd.Type
                        )
                    );
                }

                fn.Body.Statements.Insert(i++, vds);

                InvalidateFirstPass();
            }
        }
        public void VisitNode(JSFunctionExpression fn)
        {
            var nullList = new List<int>();
            FirstPass = GetFirstPass(fn.Method.QualifiedIdentifier);
            if (FirstPass == null)
                throw new InvalidOperationException(String.Format(
                    "No first pass static analysis data for method '{0}'",
                    fn.Method.QualifiedIdentifier
                ));

            VisitChildren(fn);

            foreach (var v in fn.AllVariables.Values.ToArray()) {
                if (v.IsThis || v.IsParameter)
                    continue;

                var valueType = v.GetActualType(TypeSystem);
                if (TypeUtil.IsIgnoredType(valueType))
                    continue;

                var assignments = (from a in FirstPass.Assignments where v.Equals(a.Target) select a).ToArray();
                var reassignments = (from a in FirstPass.Assignments where v.Equals(a.SourceVariable) select a).ToArray();
                var accesses = (from a in FirstPass.Accesses where v.Equals(a.Source) select a).ToArray();
                var invocations = (from i in FirstPass.Invocations where v.Name == i.ThisVariable select i).ToArray();

                if (FirstPass.VariablesPassedByRef.Contains(v.Name)) {
                    if (TraceLevel >= 2)
                        Debug.WriteLine(String.Format("Cannot eliminate {0}; it is passed by reference.", v));

                    continue;
                }

                if (assignments.FirstOrDefault() == null) {
                    if ((accesses.Length == 0) && (invocations.Length == 0) && (reassignments.Length == 0)) {
                        if (TraceLevel >= 1)
                            Debug.WriteLine(String.Format("Eliminating {0} because it is never used.", v));

                        if (!DryRun) {
                            EliminatedVariables.Add(v);
                            EliminateVariable(fn, v, new JSEliminatedVariable(v), fn.Method.QualifiedIdentifier);

                            // We've invalidated the static analysis data so the best choice is to abort.
                            break;
                        }
                    } else {
                        if (TraceLevel >= 2)
                            Debug.WriteLine(String.Format("Never found an initial assignment for {0}.", v));
                    }

                    continue;
                }

                if (invocations.Length > 1) {
                    if (TraceLevel >= 2)
                        Debug.WriteLine(String.Format("Cannot eliminate {0}; methods are invoked on it multiple times.", v));

                    continue;
                }

                if ((from a in accesses where a.IsControlFlow select a).FirstOrDefault() != null) {
                    if (TraceLevel >= 2)
                        Debug.WriteLine(String.Format("Cannot eliminate {0}; it participates in control flow.", v));

                    continue;
                }

                if (assignments.Length > 1) {
                    if (TraceLevel >= 2)
                        Debug.WriteLine(String.Format("Cannot eliminate {0}; it is reassigned.", v));

                    continue;
                }

                var copies = (from a in FirstPass.Assignments where v.Equals(a.SourceVariable) select a).ToArray();
                if ((copies.Length + accesses.Length) > 1) {
                    if (TraceLevel >= 2)
                        Debug.WriteLine(String.Format("Cannot eliminate {0}; it is used multiple times.", v));

                    continue;
                }

                var replacementAssignment = assignments.First();
                var replacement = replacementAssignment.NewValue;
                if (replacement.SelfAndChildrenRecursive.Contains(v)) {
                    if (TraceLevel >= 2)
                        Debug.WriteLine(String.Format("Cannot eliminate {0}; it contains a self-reference.", v));

                    continue;
                }

                if (!IsEffectivelyConstant(v, replacement)) {
                    if (TraceLevel >= 2)
                        Debug.WriteLine(String.Format("Cannot eliminate {0}; {1} is not a constant expression.", v, replacement));

                    continue;
                }

                var replacementField = JSPointerExpressionUtil.UnwrapExpression(replacement) as JSFieldAccess;
                if (replacementField == null) {
                    var replacementRef = replacement as JSReferenceExpression;
                    if (replacementRef != null)
                        replacementField = replacementRef.Referent as JSFieldAccess;
                }

                if (replacementField != null) {
                    var lastAccess = accesses.LastOrDefault();

                    var affectedFields = replacement.SelfAndChildrenRecursive.OfType<JSField>().ToArray();
                    bool invalidatedByLaterFieldAccess = false;
                    foreach (var field in affectedFields) {
                        foreach (var fieldAccess in FirstPass.FieldAccesses) {
                            // Different field. Note that we only compare the FieldInfo, not the this-reference.
                            // Otherwise, aliasing (accessing the same field through two this references) would cause us
                            //  to incorrectly eliminate a local.
                            if (fieldAccess.Field.Field != replacementField.Field.Field)
                                continue;

                            // Ignore field accesses before the replacement was initialized
                            if (fieldAccess.NodeIndex <= replacementAssignment.NodeIndex)
                                continue;

                            // If the field access comes after the last use of the temporary, we don't care
                            if ((lastAccess != null) && (fieldAccess.StatementIndex > lastAccess.StatementIndex))
                                continue;

                            // It's a read, so no impact on whether this optimization is valid
                            if (fieldAccess.IsRead)
                                continue;

                            if (TraceLevel >= 2)
                                Debug.WriteLine(String.Format("Cannot eliminate {0}; {1} is potentially mutated later", v, replacementField.Field));

                            invalidatedByLaterFieldAccess = true;
                            break;
                        }

                        if (invalidatedByLaterFieldAccess)
                            break;
                    }

                    if (invalidatedByLaterFieldAccess)
                        continue;
                }

                if (TraceLevel >= 1)
                    Debug.WriteLine(String.Format("Eliminating {0} <- {1}", v, replacement));

                if (!DryRun) {
                    EliminatedVariables.Add(v);
                    EliminateVariable(fn, v, replacement, fn.Method.QualifiedIdentifier);

                    // We've invalidated the static analysis data so the best choice is to abort.
                    break;
                }
            }
        }