LocalVariableTypesCalculator.java revision 1655:3ac5d360070e
1/*
2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package jdk.nashorn.internal.codegen;
27
28import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
29import static jdk.nashorn.internal.ir.Expression.isAlwaysFalse;
30import static jdk.nashorn.internal.ir.Expression.isAlwaysTrue;
31
32import java.util.ArrayDeque;
33import java.util.ArrayList;
34import java.util.Collections;
35import java.util.Deque;
36import java.util.HashSet;
37import java.util.IdentityHashMap;
38import java.util.Iterator;
39import java.util.LinkedList;
40import java.util.List;
41import java.util.Map;
42import java.util.Set;
43import jdk.nashorn.internal.codegen.types.Type;
44import jdk.nashorn.internal.ir.AccessNode;
45import jdk.nashorn.internal.ir.BinaryNode;
46import jdk.nashorn.internal.ir.Block;
47import jdk.nashorn.internal.ir.BreakNode;
48import jdk.nashorn.internal.ir.BreakableNode;
49import jdk.nashorn.internal.ir.CallNode;
50import jdk.nashorn.internal.ir.CaseNode;
51import jdk.nashorn.internal.ir.CatchNode;
52import jdk.nashorn.internal.ir.ContinueNode;
53import jdk.nashorn.internal.ir.Expression;
54import jdk.nashorn.internal.ir.ExpressionStatement;
55import jdk.nashorn.internal.ir.ForNode;
56import jdk.nashorn.internal.ir.FunctionNode;
57import jdk.nashorn.internal.ir.GetSplitState;
58import jdk.nashorn.internal.ir.IdentNode;
59import jdk.nashorn.internal.ir.IfNode;
60import jdk.nashorn.internal.ir.IndexNode;
61import jdk.nashorn.internal.ir.JoinPredecessor;
62import jdk.nashorn.internal.ir.JoinPredecessorExpression;
63import jdk.nashorn.internal.ir.JumpStatement;
64import jdk.nashorn.internal.ir.JumpToInlinedFinally;
65import jdk.nashorn.internal.ir.LabelNode;
66import jdk.nashorn.internal.ir.LexicalContext;
67import jdk.nashorn.internal.ir.LexicalContextNode;
68import jdk.nashorn.internal.ir.LiteralNode;
69import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
70import jdk.nashorn.internal.ir.LocalVariableConversion;
71import jdk.nashorn.internal.ir.LoopNode;
72import jdk.nashorn.internal.ir.Node;
73import jdk.nashorn.internal.ir.ObjectNode;
74import jdk.nashorn.internal.ir.PropertyNode;
75import jdk.nashorn.internal.ir.ReturnNode;
76import jdk.nashorn.internal.ir.RuntimeNode;
77import jdk.nashorn.internal.ir.RuntimeNode.Request;
78import jdk.nashorn.internal.ir.SplitReturn;
79import jdk.nashorn.internal.ir.Statement;
80import jdk.nashorn.internal.ir.SwitchNode;
81import jdk.nashorn.internal.ir.Symbol;
82import jdk.nashorn.internal.ir.TernaryNode;
83import jdk.nashorn.internal.ir.ThrowNode;
84import jdk.nashorn.internal.ir.TryNode;
85import jdk.nashorn.internal.ir.UnaryNode;
86import jdk.nashorn.internal.ir.VarNode;
87import jdk.nashorn.internal.ir.WhileNode;
88import jdk.nashorn.internal.ir.WithNode;
89import jdk.nashorn.internal.ir.visitor.NodeVisitor;
90import jdk.nashorn.internal.ir.visitor.SimpleNodeVisitor;
91import jdk.nashorn.internal.parser.TokenType;
92
93/**
94 * Calculates types for local variables. For purposes of local variable type calculation, the only types used are
95 * Undefined, boolean, int, long, double, and Object. The calculation eagerly widens types of local variable to their
96 * widest at control flow join points.
97 * TODO: investigate a more sophisticated solution that uses use/def information to only widens the type of a local
98 * variable to its widest used type after the join point. That would eliminate some widenings of undefined variables to
99 * object, most notably those used only in loops. We need a full liveness analysis for that. Currently, we can establish
100 * per-type liveness, which eliminates most of unwanted dead widenings.
101 * NOTE: the way this class is implemented, it actually processes the AST in two passes. The first pass is top-down and
102 * implemented in {@code enterXxx} methods. This pass does not mutate the AST (except for one occurrence, noted below),
103 * as being able to find relevant labels for control flow joins is sensitive to their reference identity, and mutated
104 * label-carrying nodes will create copies of their labels. A second bottom-up pass applying the changes is implemented
105 * in the separate visitor sitting in {@link #leaveFunctionNode(FunctionNode)}. This visitor will also instantiate new
106 * instances of the calculator to be run on nested functions (when not lazy compiling).
107 *
108 */
109final class LocalVariableTypesCalculator extends SimpleNodeVisitor {
110
111    private static class JumpOrigin {
112        final JoinPredecessor node;
113        final Map<Symbol, LvarType> types;
114
115        JumpOrigin(final JoinPredecessor node, final Map<Symbol, LvarType> types) {
116            this.node = node;
117            this.types = types;
118        }
119    }
120
121    private static class JumpTarget {
122        private final List<JumpOrigin> origins = new LinkedList<>();
123        private Map<Symbol, LvarType> types = Collections.emptyMap();
124
125        void addOrigin(final JoinPredecessor originNode, final Map<Symbol, LvarType> originTypes) {
126            origins.add(new JumpOrigin(originNode, originTypes));
127            this.types = getUnionTypes(this.types, originTypes);
128        }
129    }
130    private enum LvarType {
131        UNDEFINED(Type.UNDEFINED),
132        BOOLEAN(Type.BOOLEAN),
133        INT(Type.INT),
134        DOUBLE(Type.NUMBER),
135        OBJECT(Type.OBJECT);
136
137        private final Type type;
138        private final TypeHolderExpression typeExpression;
139
140        private LvarType(final Type type) {
141            this.type = type;
142            this.typeExpression = new TypeHolderExpression(type);
143        }
144    }
145
146    /**
147     * A bogus Expression subclass that only reports its type. Used to interrogate BinaryNode and UnaryNode about their
148     * types by creating temporary copies of them and replacing their operands with instances of these. An alternative
149     * solution would be to add BinaryNode.getType(Type lhsType, Type rhsType) and UnaryNode.getType(Type exprType)
150     * methods. For the time being though, this is easier to implement and is in fact fairly clean. It does result in
151     * generation of higher number of temporary short lived nodes, though.
152     */
153    private static class TypeHolderExpression extends Expression {
154        private static final long serialVersionUID = 1L;
155
156        private final Type type;
157
158        TypeHolderExpression(final Type type) {
159            super(0L, 0, 0);
160            this.type = type;
161        }
162
163        @Override
164        public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
165            throw new AssertionError();
166        }
167
168        @Override
169        public Type getType() {
170            return type;
171        }
172
173        @Override
174        public void toString(final StringBuilder sb, final boolean printType) {
175            throw new AssertionError();
176        }
177    }
178
179    private static final Map<Type, LvarType> TO_LVAR_TYPE = new IdentityHashMap<>();
180
181    static {
182        for(final LvarType lvarType: LvarType.values()) {
183            TO_LVAR_TYPE.put(lvarType.type, lvarType);
184        }
185    }
186
187    @SuppressWarnings("unchecked")
188    private static IdentityHashMap<Symbol, LvarType> cloneMap(final Map<Symbol, LvarType> map) {
189        return (IdentityHashMap<Symbol, LvarType>)((IdentityHashMap<?,?>)map).clone();
190    }
191
192    private LocalVariableConversion createConversion(final Symbol symbol, final LvarType branchLvarType,
193            final Map<Symbol, LvarType> joinLvarTypes, final LocalVariableConversion next) {
194        final LvarType targetType = joinLvarTypes.get(symbol);
195        assert targetType != null;
196        if(targetType == branchLvarType) {
197            return next;
198        }
199        // NOTE: we could naively just use symbolIsUsed(symbol, branchLvarType) here, but that'd be wrong. While
200        // technically a conversion will read the value of the symbol with that type, but it will also write it to a new
201        // type, and that type might be dead (we can't know yet). For this reason, we don't treat conversion reads as
202        // real uses until we know their target type is live. If we didn't do this, and just did a symbolIsUsed here,
203        // we'd introduce false live variables which could nevertheless turn into dead ones in a subsequent
204        // deoptimization, causing a shift in the list of live locals that'd cause erroneous restoration of
205        // continuations (since RewriteException's byteCodeSlots carries an array and not a name-value map).
206
207        symbolIsConverted(symbol, branchLvarType, targetType);
208        return new LocalVariableConversion(symbol, branchLvarType.type, targetType.type, next);
209    }
210
211    private static Map<Symbol, LvarType> getUnionTypes(final Map<Symbol, LvarType> types1, final Map<Symbol, LvarType> types2) {
212        if(types1 == types2 || types1.isEmpty()) {
213            return types2;
214        } else if(types2.isEmpty()) {
215            return types1;
216        }
217        final Set<Symbol> commonSymbols = new HashSet<>(types1.keySet());
218        commonSymbols.retainAll(types2.keySet());
219        // We have a chance of returning an unmodified set if both sets have the same keys and one is strictly wider
220        // than the other.
221        final int commonSize = commonSymbols.size();
222        final int types1Size = types1.size();
223        final int types2Size = types2.size();
224        if(commonSize == types1Size && commonSize == types2Size) {
225            boolean matches1 = true, matches2 = true;
226            Map<Symbol, LvarType> union = null;
227            for(final Symbol symbol: commonSymbols) {
228                final LvarType type1 = types1.get(symbol);
229                final LvarType type2 = types2.get(symbol);
230                final LvarType widest = widestLvarType(type1, type2);
231                if(widest != type1 && matches1) {
232                    matches1 = false;
233                    if(!matches2) {
234                        union = cloneMap(types1);
235                    }
236                }
237                if (widest != type2 && matches2) {
238                    matches2 = false;
239                    if(!matches1) {
240                        union = cloneMap(types2);
241                    }
242                }
243                if(!(matches1 || matches2)) {
244                    assert union != null;
245                    union.put(symbol, widest);
246                }
247            }
248            return matches1 ? types1 : matches2 ? types2 : union;
249        }
250        // General case
251        final Map<Symbol, LvarType> union;
252        if(types1Size > types2Size) {
253            union = cloneMap(types1);
254            union.putAll(types2);
255        } else {
256            union = cloneMap(types2);
257            union.putAll(types1);
258        }
259        for(final Symbol symbol: commonSymbols) {
260            final LvarType type1 = types1.get(symbol);
261            final LvarType type2 = types2.get(symbol);
262            union.put(symbol, widestLvarType(type1,  type2));
263        }
264        return union;
265    }
266
267    private static void symbolIsUsed(final Symbol symbol, final LvarType type) {
268        if(type != LvarType.UNDEFINED) {
269            symbol.setHasSlotFor(type.type);
270        }
271    }
272
273    private static class SymbolConversions {
274        private static final byte I2D = 1 << 0;
275        private static final byte I2O = 1 << 1;
276        private static final byte D2O = 1 << 2;
277
278        private byte conversions;
279
280        void recordConversion(final LvarType from, final LvarType to) {
281            switch (from) {
282            case UNDEFINED:
283                return;
284            case INT:
285            case BOOLEAN:
286                switch (to) {
287                case DOUBLE:
288                    recordConversion(I2D);
289                    return;
290                case OBJECT:
291                    recordConversion(I2O);
292                    return;
293                default:
294                    illegalConversion(from, to);
295                    return;
296                }
297            case DOUBLE:
298                if(to == LvarType.OBJECT) {
299                    recordConversion(D2O);
300                }
301                return;
302            default:
303                illegalConversion(from, to);
304            }
305        }
306
307        private static void illegalConversion(final LvarType from, final LvarType to) {
308            throw new AssertionError("Invalid conversion from " + from + " to " + to);
309        }
310
311        void recordConversion(final byte convFlag) {
312            conversions = (byte)(conversions | convFlag);
313        }
314
315        boolean hasConversion(final byte convFlag) {
316            return (conversions & convFlag) != 0;
317        }
318
319        void calculateTypeLiveness(final Symbol symbol) {
320            if(symbol.hasSlotFor(Type.OBJECT)) {
321                if(hasConversion(D2O)) {
322                    symbol.setHasSlotFor(Type.NUMBER);
323                }
324                if(hasConversion(I2O)) {
325                    symbol.setHasSlotFor(Type.INT);
326                }
327            }
328            if(symbol.hasSlotFor(Type.NUMBER)) {
329                if(hasConversion(I2D)) {
330                    symbol.setHasSlotFor(Type.INT);
331                }
332            }
333        }
334    }
335
336    private void symbolIsConverted(final Symbol symbol, final LvarType from, final LvarType to) {
337        SymbolConversions conversions = symbolConversions.get(symbol);
338        if(conversions == null) {
339            conversions = new SymbolConversions();
340            symbolConversions.put(symbol, conversions);
341        }
342        conversions.recordConversion(from, to);
343    }
344
345    private static LvarType toLvarType(final Type type) {
346        assert type != null;
347        final LvarType lvarType = TO_LVAR_TYPE.get(type);
348        if(lvarType != null) {
349            return lvarType;
350        }
351        assert type.isObject() : "Unsupported primitive type: " + type;
352        return LvarType.OBJECT;
353    }
354    private static LvarType widestLvarType(final LvarType t1, final LvarType t2) {
355        if(t1 == t2) {
356            return t1;
357        }
358        // Undefined or boolean to anything always widens to object.
359        if(t1.ordinal() < LvarType.INT.ordinal() || t2.ordinal() < LvarType.INT.ordinal()) {
360            return LvarType.OBJECT;
361        }
362        // NOTE: we allow "widening" of long to double even though it can lose precision. ECMAScript doesn't have an
363        // Int64 type anyway, so this loss of precision is actually more conformant to the specification...
364        return LvarType.values()[Math.max(t1.ordinal(), t2.ordinal())];
365    }
366    private final Compiler compiler;
367    private final Map<Label, JumpTarget> jumpTargets = new IdentityHashMap<>();
368    // Local variable type mapping at the currently evaluated point. No map instance is ever modified; setLvarType() always
369    // allocates a new map. Immutability of maps allows for cheap snapshots by just keeping the reference to the current
370    // value.
371    private Map<Symbol, LvarType> localVariableTypes = new IdentityHashMap<>();
372    // Stack for evaluated expression types.
373    private final Deque<LvarType> typeStack = new ArrayDeque<>();
374
375    // Whether the current point in the AST is reachable code
376    private boolean reachable = true;
377    // Return type of the function
378    private Type returnType = Type.UNKNOWN;
379    // Synthetic return node that we must insert at the end of the function if it's end is reachable.
380    private ReturnNode syntheticReturn;
381
382    private boolean alreadyEnteredTopLevelFunction;
383
384    // LvarType and conversion information gathered during the top-down pass; applied to nodes in the bottom-up pass.
385    private final Map<JoinPredecessor, LocalVariableConversion> localVariableConversions = new IdentityHashMap<>();
386
387    private final Map<IdentNode, LvarType> identifierLvarTypes = new IdentityHashMap<>();
388    private final Map<Symbol, SymbolConversions> symbolConversions = new IdentityHashMap<>();
389
390    // Stack of open labels for starts of catch blocks, one for every currently traversed try block; for inserting
391    // control flow edges to them. Note that we currently don't insert actual control flow edges, but instead edges that
392    // help us with type calculations. This means that some operations that can result in an exception being thrown
393    // aren't considered (function calls, side effecting property getters and setters etc.), while some operations that
394    // don't result in control flow transfers do originate an edge to the catch blocks (namely, assignments to local
395    // variables).
396    private final Deque<Label> catchLabels = new ArrayDeque<>();
397
398    LocalVariableTypesCalculator(final Compiler compiler) {
399        this.compiler = compiler;
400    }
401
402    private JumpTarget createJumpTarget(final Label label) {
403        assert !jumpTargets.containsKey(label);
404        final JumpTarget jumpTarget = new JumpTarget();
405        jumpTargets.put(label, jumpTarget);
406        return jumpTarget;
407    }
408
409    private void doesNotContinueSequentially() {
410        reachable = false;
411        localVariableTypes = Collections.emptyMap();
412        assertTypeStackIsEmpty();
413    }
414
415    private boolean pushExpressionType(final Expression expr) {
416        typeStack.push(toLvarType(expr.getType()));
417        return false;
418    }
419
420    @Override
421    public boolean enterAccessNode(final AccessNode accessNode) {
422        visitExpression(accessNode.getBase());
423        return pushExpressionType(accessNode);
424    }
425
426    @Override
427    public boolean enterBinaryNode(final BinaryNode binaryNode) {
428        // NOTE: regardless of operator's lexical associativity, lhs is always evaluated first.
429        final Expression lhs = binaryNode.lhs();
430        final LvarType lhsType;
431        if (!(lhs instanceof IdentNode && binaryNode.isTokenType(TokenType.ASSIGN))) {
432            lhsType = visitExpression(lhs);
433        } else {
434            // Can't visit IdentNode on LHS of a simple assignment, as visits imply use, and this is def.
435            // The type is irrelevant, as only RHS is used to determine the type anyway.
436            lhsType = LvarType.UNDEFINED;
437        }
438
439        final boolean isLogical = binaryNode.isLogical();
440        final Label joinLabel = isLogical ? new Label("") : null;
441        if(isLogical) {
442            jumpToLabel((JoinPredecessor)lhs, joinLabel);
443        }
444
445        final Expression rhs = binaryNode.rhs();
446        final LvarType rhsType = visitExpression(rhs);
447        if(isLogical) {
448            jumpToLabel((JoinPredecessor)rhs, joinLabel);
449        }
450        joinOnLabel(joinLabel);
451
452        final LvarType type = toLvarType(binaryNode.setOperands(lhsType.typeExpression, rhsType.typeExpression).getType());
453
454        if(binaryNode.isAssignment() && lhs instanceof IdentNode) {
455            if(binaryNode.isSelfModifying()) {
456                onSelfAssignment((IdentNode)lhs, type);
457            } else {
458                onAssignment((IdentNode)lhs, type);
459            }
460        }
461        typeStack.push(type);
462        return false;
463    }
464
465    @Override
466    public boolean enterBlock(final Block block) {
467        for(final Symbol symbol: block.getSymbols()) {
468            if(symbol.isBytecodeLocal() && getLocalVariableTypeOrNull(symbol) == null) {
469                setType(symbol, LvarType.UNDEFINED);
470            }
471        }
472        return true;
473    }
474
475    @Override
476    public boolean enterBreakNode(final BreakNode breakNode) {
477        return enterJumpStatement(breakNode);
478    }
479
480    @Override
481    public boolean enterCallNode(final CallNode callNode) {
482        visitExpression(callNode.getFunction());
483        visitExpressions(callNode.getArgs());
484        final CallNode.EvalArgs evalArgs = callNode.getEvalArgs();
485        if (evalArgs != null) {
486            visitExpressions(evalArgs.getArgs());
487        }
488        return pushExpressionType(callNode);
489    }
490
491    @Override
492    public boolean enterContinueNode(final ContinueNode continueNode) {
493        return enterJumpStatement(continueNode);
494    }
495
496    private boolean enterJumpStatement(final JumpStatement jump) {
497        if(!reachable) {
498            return false;
499        }
500        assertTypeStackIsEmpty();
501        jumpToLabel(jump, jump.getTargetLabel(lc), getBreakTargetTypes(jump.getPopScopeLimit(lc)));
502        doesNotContinueSequentially();
503        return false;
504    }
505
506    @Override
507    protected boolean enterDefault(final Node node) {
508        return reachable;
509    }
510
511    private void enterDoWhileLoop(final WhileNode loopNode) {
512        assertTypeStackIsEmpty();
513        final JoinPredecessorExpression test = loopNode.getTest();
514        final Block body = loopNode.getBody();
515        final Label continueLabel = loopNode.getContinueLabel();
516        final Label breakLabel = loopNode.getBreakLabel();
517        final Map<Symbol, LvarType> beforeLoopTypes = localVariableTypes;
518        final Label repeatLabel = new Label("");
519        for(;;) {
520            jumpToLabel(loopNode, repeatLabel, beforeLoopTypes);
521            final Map<Symbol, LvarType> beforeRepeatTypes = localVariableTypes;
522            body.accept(this);
523            if(reachable) {
524                jumpToLabel(body, continueLabel);
525            }
526            joinOnLabel(continueLabel);
527            if(!reachable) {
528                break;
529            }
530            visitExpressionOnEmptyStack(test);
531            jumpToLabel(test, breakLabel);
532            if(isAlwaysFalse(test)) {
533                break;
534            }
535            jumpToLabel(test, repeatLabel);
536            joinOnLabel(repeatLabel);
537            if(localVariableTypes.equals(beforeRepeatTypes)) {
538                break;
539            }
540            resetJoinPoint(continueLabel);
541            resetJoinPoint(breakLabel);
542            resetJoinPoint(repeatLabel);
543        }
544
545        if(isAlwaysTrue(test)) {
546            doesNotContinueSequentially();
547        }
548
549        leaveBreakable(loopNode);
550    }
551
552    @Override
553    public boolean enterExpressionStatement(final ExpressionStatement expressionStatement) {
554        if (reachable) {
555            visitExpressionOnEmptyStack(expressionStatement.getExpression());
556        }
557        return false;
558    }
559
560    private void assertTypeStackIsEmpty() {
561        assert typeStack.isEmpty();
562    }
563
564    @Override
565    protected Node leaveDefault(final Node node) {
566        assert !(node instanceof Expression); // All expressions were handled
567        assert !(node instanceof Statement) || typeStack.isEmpty(); // No statements leave with a non-empty stack
568        return node;
569    }
570
571    private LvarType visitExpressionOnEmptyStack(final Expression expr) {
572        assertTypeStackIsEmpty();
573        return visitExpression(expr);
574    }
575
576    private LvarType visitExpression(final Expression expr) {
577        final int stackSize = typeStack.size();
578        expr.accept(this);
579        assert typeStack.size() == stackSize + 1;
580        return typeStack.pop();
581    }
582
583    private void visitExpressions(final List<Expression> exprs) {
584        for(final Expression expr: exprs) {
585            if (expr != null) {
586                visitExpression(expr);
587            }
588        }
589    }
590
591    @Override
592    public boolean enterForNode(final ForNode forNode) {
593        if(!reachable) {
594            return false;
595        }
596
597        final Expression init = forNode.getInit();
598        if(forNode.isForInOrOf()) {
599            final JoinPredecessorExpression iterable = forNode.getModify();
600            visitExpression(iterable);
601            enterTestFirstLoop(forNode, null, init,
602                    // If we're iterating over property names, and we can discern from the runtime environment
603                    // of the compilation that the object being iterated over must use strings for property
604                    // names (e.g., it is a native JS object or array), then we'll not bother trying to treat
605                    // the property names optimistically.
606                    !compiler.useOptimisticTypes() || (!forNode.isForEach() && compiler.hasStringPropertyIterator(iterable.getExpression())));
607        } else {
608            if(init != null) {
609                visitExpressionOnEmptyStack(init);
610            }
611            enterTestFirstLoop(forNode, forNode.getModify(), null, false);
612        }
613        assertTypeStackIsEmpty();
614        return false;
615    }
616
617    @Override
618    public boolean enterFunctionNode(final FunctionNode functionNode) {
619        if(alreadyEnteredTopLevelFunction) {
620            typeStack.push(LvarType.OBJECT);
621            return false;
622        }
623        int pos = 0;
624        if(!functionNode.isVarArg()) {
625            for (final IdentNode param : functionNode.getParameters()) {
626                final Symbol symbol = param.getSymbol();
627                // Parameter is not necessarily bytecode local as it can be scoped due to nested context use, but it
628                // must have a slot if we aren't in a function with vararg signature.
629                assert symbol.hasSlot();
630                final Type callSiteParamType = compiler.getParamType(functionNode, pos);
631                final LvarType paramType = callSiteParamType == null ? LvarType.OBJECT : toLvarType(callSiteParamType);
632                setType(symbol, paramType);
633                // Make sure parameter slot for its incoming value is not marked dead. NOTE: this is a heuristic. Right
634                // now, CodeGenerator.expandParameters() relies on the fact that every parameter's final slot width will
635                // be at least the same as incoming width, therefore even if a parameter is never read, we'll still keep
636                // its slot.
637                symbolIsUsed(symbol);
638                setIdentifierLvarType(param, paramType);
639                pos++;
640            }
641        }
642        setCompilerConstantAsObject(functionNode, CompilerConstants.THIS);
643
644        // TODO: coarse-grained. If we wanted to solve it completely precisely,
645        // we'd also need to push/pop its type when handling WithNode (so that
646        // it can go back to undefined after a 'with' block.
647        if(functionNode.hasScopeBlock() || functionNode.needsParentScope()) {
648            setCompilerConstantAsObject(functionNode, CompilerConstants.SCOPE);
649        }
650        if(functionNode.needsCallee()) {
651            setCompilerConstantAsObject(functionNode, CompilerConstants.CALLEE);
652        }
653        if(functionNode.needsArguments()) {
654            setCompilerConstantAsObject(functionNode, CompilerConstants.ARGUMENTS);
655        }
656
657        alreadyEnteredTopLevelFunction = true;
658        return true;
659    }
660
661    @Override
662    public boolean enterGetSplitState(final GetSplitState getSplitState) {
663        return pushExpressionType(getSplitState);
664    }
665
666    @Override
667    public boolean enterIdentNode(final IdentNode identNode) {
668        final Symbol symbol = identNode.getSymbol();
669        if(symbol.isBytecodeLocal()) {
670            symbolIsUsed(symbol);
671            final LvarType type = getLocalVariableType(symbol);
672            setIdentifierLvarType(identNode, type);
673            typeStack.push(type);
674        } else {
675            pushExpressionType(identNode);
676        }
677        return false;
678    }
679
680    @Override
681    public boolean enterIfNode(final IfNode ifNode) {
682        processIfNode(ifNode);
683        return false;
684    }
685
686    private void processIfNode(final IfNode ifNode) {
687        if(!reachable) {
688            return;
689        }
690
691        final Expression test = ifNode.getTest();
692        final Block pass = ifNode.getPass();
693        final Block fail = ifNode.getFail();
694
695        visitExpressionOnEmptyStack(test);
696
697        final Map<Symbol, LvarType> passLvarTypes;
698        final boolean reachableFromPass;
699        final boolean isTestAlwaysTrue = isAlwaysTrue(test);
700        if(isAlwaysFalse(test)) {
701            passLvarTypes = null;
702            reachableFromPass = false;
703        } else {
704            final Map<Symbol, LvarType> afterTestLvarTypes = localVariableTypes;
705            pass.accept(this);
706            assertTypeStackIsEmpty();
707            if (isTestAlwaysTrue) {
708                return;
709            }
710            passLvarTypes = localVariableTypes;
711            reachableFromPass = reachable;
712            localVariableTypes = afterTestLvarTypes;
713            reachable = true;
714        }
715
716        // If we get here, then we need to consider the case where pass block is not executed
717        assert !isTestAlwaysTrue;
718
719        if (fail != null) {
720            fail.accept(this);
721            assertTypeStackIsEmpty();
722        }
723
724        if(reachable) {
725            if(reachableFromPass) {
726                final Map<Symbol, LvarType> failLvarTypes = localVariableTypes;
727                localVariableTypes = getUnionTypes(passLvarTypes, failLvarTypes);
728                setConversion(pass, passLvarTypes, localVariableTypes);
729                // IfNode itself is associated with conversions that might need to be performed after the test if
730                // there's no else branch. E.g.
731                // if(x = 1, cond) { x = 1.0 } must widen "x = 1" to a double.
732                setConversion(fail != null ? fail : ifNode, failLvarTypes, localVariableTypes);
733            }
734        } else if (reachableFromPass) {
735            assert passLvarTypes != null;
736            localVariableTypes = passLvarTypes;
737            reachable = true;
738        }
739    }
740
741    @Override
742    public boolean enterIndexNode(final IndexNode indexNode) {
743        visitExpression(indexNode.getBase());
744        visitExpression(indexNode.getIndex());
745        return pushExpressionType(indexNode);
746    }
747
748    @Override
749    public boolean enterJoinPredecessorExpression(final JoinPredecessorExpression joinExpr) {
750        final Expression expr = joinExpr.getExpression();
751        if (expr != null) {
752            expr.accept(this);
753        } else {
754            typeStack.push(LvarType.UNDEFINED);
755        }
756        return false;
757    }
758
759    @Override
760    public boolean enterJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) {
761        return enterJumpStatement(jumpToInlinedFinally);
762    }
763
764    @Override
765    public boolean enterLiteralNode(final LiteralNode<?> literalNode) {
766        if (literalNode instanceof ArrayLiteralNode) {
767            final List<Expression> expressions = ((ArrayLiteralNode)literalNode).getElementExpressions();
768            if (expressions != null) {
769                visitExpressions(expressions);
770            }
771        }
772        pushExpressionType(literalNode);
773        return false;
774    }
775
776    @Override
777    public boolean enterObjectNode(final ObjectNode objectNode) {
778        for(final PropertyNode propertyNode: objectNode.getElements()) {
779            // Avoid falsely adding property keys to the control flow graph
780            final Expression value = propertyNode.getValue();
781            if (value != null) {
782                visitExpression(value);
783            }
784        }
785        return pushExpressionType(objectNode);
786    }
787
788    @Override
789    public boolean enterPropertyNode(final PropertyNode propertyNode) {
790        // Property nodes are only accessible through object literals, and we handled that case above
791        throw new AssertionError();
792    }
793
794    @Override
795    public boolean enterReturnNode(final ReturnNode returnNode) {
796        if(!reachable) {
797            return false;
798        }
799
800        final Expression returnExpr = returnNode.getExpression();
801        final Type returnExprType;
802        if(returnExpr != null) {
803            returnExprType = visitExpressionOnEmptyStack(returnExpr).type;
804        } else {
805            assertTypeStackIsEmpty();
806            returnExprType = Type.UNDEFINED;
807        }
808        returnType = Type.widestReturnType(returnType, returnExprType);
809        doesNotContinueSequentially();
810        return false;
811    }
812
813    @Override
814    public boolean enterRuntimeNode(final RuntimeNode runtimeNode) {
815        visitExpressions(runtimeNode.getArgs());
816        return pushExpressionType(runtimeNode);
817    }
818
819    @Override
820    public boolean enterSplitReturn(final SplitReturn splitReturn) {
821        doesNotContinueSequentially();
822        return false;
823    }
824
825    @Override
826    public boolean enterSwitchNode(final SwitchNode switchNode) {
827        if(!reachable) {
828            return false;
829        }
830
831        visitExpressionOnEmptyStack(switchNode.getExpression());
832
833        final List<CaseNode> cases = switchNode.getCases();
834        if(cases.isEmpty()) {
835            return false;
836        }
837
838        // Control flow is different for all-integer cases where we dispatch by switch table, and for all other cases
839        // where we do sequential comparison. Note that CaseNode objects act as join points.
840        final boolean isInteger = switchNode.isUniqueInteger();
841        final Label breakLabel = switchNode.getBreakLabel();
842        final boolean hasDefault = switchNode.getDefaultCase() != null;
843
844        boolean tagUsed = false;
845        for(final CaseNode caseNode: cases) {
846            final Expression test = caseNode.getTest();
847            if(!isInteger && test != null) {
848                visitExpressionOnEmptyStack(test);
849                if(!tagUsed) {
850                    symbolIsUsed(switchNode.getTag(), LvarType.OBJECT);
851                    tagUsed = true;
852                }
853            }
854            // CaseNode carries the conversions that need to be performed on its entry from the test.
855            // CodeGenerator ensures these are only emitted when arriving on the branch and not through a
856            // fallthrough.
857            jumpToLabel(caseNode, caseNode.getBody().getEntryLabel());
858        }
859        if(!hasDefault) {
860            // No default case means we can arrive at the break label without entering any cases. In that case
861            // SwitchNode will carry the conversions that need to be performed before it does that jump.
862            jumpToLabel(switchNode, breakLabel);
863        }
864
865        // All cases are arrived at through jumps
866        doesNotContinueSequentially();
867
868        Block previousBlock = null;
869        for(final CaseNode caseNode: cases) {
870            final Block body = caseNode.getBody();
871            final Label entryLabel = body.getEntryLabel();
872            if(previousBlock != null && reachable) {
873                jumpToLabel(previousBlock, entryLabel);
874            }
875            joinOnLabel(entryLabel);
876            assert reachable == true;
877            body.accept(this);
878            previousBlock = body;
879        }
880        if(previousBlock != null && reachable) {
881            jumpToLabel(previousBlock, breakLabel);
882        }
883        leaveBreakable(switchNode);
884        return false;
885    }
886
887    @Override
888    public boolean enterTernaryNode(final TernaryNode ternaryNode) {
889        final Expression test = ternaryNode.getTest();
890        final Expression trueExpr = ternaryNode.getTrueExpression();
891        final Expression falseExpr = ternaryNode.getFalseExpression();
892
893        visitExpression(test);
894
895        final Map<Symbol, LvarType> testExitLvarTypes = localVariableTypes;
896        final LvarType trueType;
897        if(!isAlwaysFalse(test)) {
898            trueType = visitExpression(trueExpr);
899        } else {
900            trueType = null;
901        }
902        final Map<Symbol, LvarType> trueExitLvarTypes = localVariableTypes;
903        localVariableTypes = testExitLvarTypes;
904        final LvarType falseType;
905        if(!isAlwaysTrue(test)) {
906            falseType = visitExpression(falseExpr);
907        } else {
908            falseType = null;
909        }
910        final Map<Symbol, LvarType> falseExitLvarTypes = localVariableTypes;
911        localVariableTypes = getUnionTypes(trueExitLvarTypes, falseExitLvarTypes);
912        setConversion((JoinPredecessor)trueExpr, trueExitLvarTypes, localVariableTypes);
913        setConversion((JoinPredecessor)falseExpr, falseExitLvarTypes, localVariableTypes);
914
915        typeStack.push(trueType != null ? falseType != null ? widestLvarType(trueType, falseType) : trueType : assertNotNull(falseType));
916        return false;
917    }
918
919    private static <T> T assertNotNull(final T t) {
920        assert t != null;
921        return t;
922    }
923
924    private void enterTestFirstLoop(final LoopNode loopNode, final JoinPredecessorExpression modify,
925            final Expression iteratorValues, final boolean iteratorValuesAreObject) {
926        final JoinPredecessorExpression test = loopNode.getTest();
927        if(isAlwaysFalse(test)) {
928            visitExpressionOnEmptyStack(test);
929            return;
930        }
931
932        final Label continueLabel = loopNode.getContinueLabel();
933        final Label breakLabel = loopNode.getBreakLabel();
934
935        final Label repeatLabel = modify == null ? continueLabel : new Label("");
936        final Map<Symbol, LvarType> beforeLoopTypes = localVariableTypes;
937        for(;;) {
938            jumpToLabel(loopNode, repeatLabel, beforeLoopTypes);
939            final Map<Symbol, LvarType> beforeRepeatTypes = localVariableTypes;
940            if(test != null) {
941                visitExpressionOnEmptyStack(test);
942            }
943            if(!isAlwaysTrue(test)) {
944                jumpToLabel(test, breakLabel);
945            }
946            if(iteratorValues instanceof IdentNode) {
947                final IdentNode ident = (IdentNode)iteratorValues;
948                // Receives iterator values; the optimistic type of the iterator values is tracked on the
949                // identifier, but we override optimism if it's known that the object being iterated over will
950                // never have primitive property names.
951                onAssignment(ident, iteratorValuesAreObject ? LvarType.OBJECT :
952                    toLvarType(compiler.getOptimisticType(ident)));
953            }
954            final Block body = loopNode.getBody();
955            body.accept(this);
956            if(reachable) {
957                jumpToLabel(body, continueLabel);
958            }
959            joinOnLabel(continueLabel);
960            if(!reachable) {
961                break;
962            }
963            if(modify != null) {
964                visitExpressionOnEmptyStack(modify);
965                jumpToLabel(modify, repeatLabel);
966                joinOnLabel(repeatLabel);
967            }
968            if(localVariableTypes.equals(beforeRepeatTypes)) {
969                break;
970            }
971            // Reset the join points and repeat the analysis
972            resetJoinPoint(continueLabel);
973            resetJoinPoint(breakLabel);
974            resetJoinPoint(repeatLabel);
975        }
976
977        if(isAlwaysTrue(test) && iteratorValues == null) {
978            doesNotContinueSequentially();
979        }
980
981        leaveBreakable(loopNode);
982    }
983
984    @Override
985    public boolean enterThrowNode(final ThrowNode throwNode) {
986        if(!reachable) {
987            return false;
988        }
989
990        visitExpressionOnEmptyStack(throwNode.getExpression());
991        jumpToCatchBlock(throwNode);
992        doesNotContinueSequentially();
993        return false;
994    }
995
996    @Override
997    public boolean enterTryNode(final TryNode tryNode) {
998        if(!reachable) {
999            return false;
1000        }
1001
1002        // This is the label for the join point at the entry of the catch blocks.
1003        final Label catchLabel = new Label("");
1004        catchLabels.push(catchLabel);
1005
1006        // Presume that even the start of the try block can immediately go to the catch
1007        jumpToLabel(tryNode, catchLabel);
1008
1009        final Block body = tryNode.getBody();
1010        body.accept(this);
1011        catchLabels.pop();
1012
1013        // Final exit label for the whole try/catch construct (after the try block and after all catches).
1014        final Label endLabel = new Label("");
1015
1016        boolean canExit = false;
1017        if(reachable) {
1018            jumpToLabel(body, endLabel);
1019            canExit = true;
1020        }
1021        doesNotContinueSequentially();
1022
1023        for (final Block inlinedFinally : tryNode.getInlinedFinallies()) {
1024            final Block finallyBody = TryNode.getLabelledInlinedFinallyBlock(inlinedFinally);
1025            joinOnLabel(finallyBody.getEntryLabel());
1026            // NOTE: the jump to inlined finally can end up in dead code, so it is not necessarily reachable.
1027            if (reachable) {
1028                finallyBody.accept(this);
1029                // All inlined finallies end with a jump or a return
1030                assert !reachable;
1031            }
1032        }
1033
1034        joinOnLabel(catchLabel);
1035        for(final CatchNode catchNode: tryNode.getCatches()) {
1036            final IdentNode exception = catchNode.getException();
1037            onAssignment(exception, LvarType.OBJECT);
1038            final Expression condition = catchNode.getExceptionCondition();
1039            if(condition != null) {
1040                visitExpression(condition);
1041            }
1042            final Map<Symbol, LvarType> afterConditionTypes = localVariableTypes;
1043            final Block catchBody = catchNode.getBody();
1044            // TODO: currently, we consider that the catch blocks are always reachable from the try block as currently
1045            // we lack enough analysis to prove that no statement before a break/continue/return in the try block can
1046            // throw an exception.
1047            reachable = true;
1048            catchBody.accept(this);
1049            final Symbol exceptionSymbol = exception.getSymbol();
1050            if(reachable) {
1051                localVariableTypes = cloneMap(localVariableTypes);
1052                localVariableTypes.remove(exceptionSymbol);
1053                jumpToLabel(catchBody, endLabel);
1054                canExit = true;
1055            }
1056            localVariableTypes = cloneMap(afterConditionTypes);
1057            localVariableTypes.remove(exceptionSymbol);
1058        }
1059        // NOTE: if we had one or more conditional catch blocks with no unconditional catch block following them, then
1060        // there will be an unconditional rethrow, so the join point can never be reached from the last
1061        // conditionExpression.
1062        doesNotContinueSequentially();
1063
1064        if(canExit) {
1065            joinOnLabel(endLabel);
1066        }
1067
1068        return false;
1069    }
1070
1071
1072    @Override
1073    public boolean enterUnaryNode(final UnaryNode unaryNode) {
1074        final Expression expr = unaryNode.getExpression();
1075        final LvarType unaryType = toLvarType(unaryNode.setExpression(visitExpression(expr).typeExpression).getType());
1076        if(unaryNode.isSelfModifying() && expr instanceof IdentNode) {
1077            onSelfAssignment((IdentNode)expr, unaryType);
1078        }
1079        typeStack.push(unaryType);
1080        return false;
1081    }
1082
1083    @Override
1084    public boolean enterVarNode(final VarNode varNode) {
1085        if (!reachable) {
1086            return false;
1087        }
1088        final Expression init = varNode.getInit();
1089        if(init != null) {
1090            onAssignment(varNode.getName(), visitExpression(init));
1091        }
1092        return false;
1093    }
1094
1095    @Override
1096    public boolean enterWhileNode(final WhileNode whileNode) {
1097        if(!reachable) {
1098            return false;
1099        }
1100        if(whileNode.isDoWhile()) {
1101            enterDoWhileLoop(whileNode);
1102        } else {
1103            enterTestFirstLoop(whileNode, null, null, false);
1104        }
1105        return false;
1106    }
1107
1108    @Override
1109    public boolean enterWithNode(final WithNode withNode) {
1110        if (reachable) {
1111            visitExpression(withNode.getExpression());
1112            withNode.getBody().accept(this);
1113        }
1114        return false;
1115    };
1116
1117    private Map<Symbol, LvarType> getBreakTargetTypes(final LexicalContextNode target) {
1118        // Remove symbols defined in the the blocks that are being broken out of.
1119        Map<Symbol, LvarType> types = localVariableTypes;
1120        for(final Iterator<LexicalContextNode> it = lc.getAllNodes(); it.hasNext();) {
1121            final LexicalContextNode node = it.next();
1122            if(node instanceof Block) {
1123                for(final Symbol symbol: ((Block)node).getSymbols()) {
1124                    if(localVariableTypes.containsKey(symbol)) {
1125                        if(types == localVariableTypes) {
1126                            types = cloneMap(localVariableTypes);
1127                        }
1128                        types.remove(symbol);
1129                    }
1130                }
1131            }
1132            if(node == target) {
1133                break;
1134            }
1135        }
1136        return types;
1137    }
1138
1139    /**
1140     * Returns the current type of the local variable represented by the symbol. This is the most strict of all
1141     * {@code getLocalVariableType*} methods, as it will throw an assertion if the type is null. Therefore, it is only
1142     * safe to be invoked on symbols known to be bytecode locals, and only after they have been initialized.
1143     * Regardless, it is recommended to use this method in majority of cases, as because of its strictness it is the
1144     * best suited for catching missing type calculation bugs early.
1145     * @param symbol a symbol representing a bytecode local variable.
1146     * @return the current type of the local variable represented by the symbol
1147     */
1148    private LvarType getLocalVariableType(final Symbol symbol) {
1149        final LvarType type = getLocalVariableTypeOrNull(symbol);
1150        assert type != null;
1151        return type;
1152    }
1153
1154    /**
1155     * Gets the type for a variable represented by a symbol, or null if the type is not know. This is the least strict
1156     * of all local variable type getters, and as such its use is discouraged except in initialization scenarios (where
1157     * a just-defined symbol might still be null).
1158     * @param symbol the symbol
1159     * @return the current type for the symbol, or null if the type is not known either because the symbol has not been
1160     * initialized, or because the symbol does not represent a bytecode local variable.
1161     */
1162    private LvarType getLocalVariableTypeOrNull(final Symbol symbol) {
1163        return localVariableTypes.get(symbol);
1164    }
1165
1166    private JumpTarget getOrCreateJumpTarget(final Label label) {
1167        JumpTarget jumpTarget = jumpTargets.get(label);
1168        if(jumpTarget == null) {
1169            jumpTarget = createJumpTarget(label);
1170        }
1171        return jumpTarget;
1172    }
1173
1174
1175    /**
1176     * If there's a join point associated with a label, insert the join point into the flow.
1177     * @param label the label to insert a join point for.
1178     */
1179    private void joinOnLabel(final Label label) {
1180        final JumpTarget jumpTarget = jumpTargets.remove(label);
1181        if(jumpTarget == null) {
1182            return;
1183        }
1184        assert !jumpTarget.origins.isEmpty();
1185        reachable = true;
1186        localVariableTypes = getUnionTypes(jumpTarget.types, localVariableTypes);
1187        for(final JumpOrigin jumpOrigin: jumpTarget.origins) {
1188            setConversion(jumpOrigin.node, jumpOrigin.types, localVariableTypes);
1189        }
1190    }
1191
1192    /**
1193     * If we're in a try/catch block, add an edge from the specified node to the try node's pre-catch label.
1194     */
1195    private void jumpToCatchBlock(final JoinPredecessor jumpOrigin) {
1196        final Label currentCatchLabel = catchLabels.peek();
1197        if(currentCatchLabel != null) {
1198            jumpToLabel(jumpOrigin, currentCatchLabel);
1199        }
1200    }
1201
1202    private void jumpToLabel(final JoinPredecessor jumpOrigin, final Label label) {
1203        jumpToLabel(jumpOrigin, label, localVariableTypes);
1204    }
1205
1206    private void jumpToLabel(final JoinPredecessor jumpOrigin, final Label label, final Map<Symbol, LvarType> types) {
1207        getOrCreateJumpTarget(label).addOrigin(jumpOrigin, types);
1208    }
1209
1210    @Override
1211    public Node leaveBlock(final Block block) {
1212        if(lc.isFunctionBody()) {
1213            if(reachable) {
1214                // reachable==true means we can reach the end of the function without an explicit return statement. We
1215                // need to insert a synthetic one then. This logic used to be in Lower.leaveBlock(), but Lower's
1216                // reachability analysis (through Terminal.isTerminal() flags) is not precise enough so
1217                // Lower$BlockLexicalContext.afterSetStatements will sometimes think the control flow terminates even
1218                // when it didn't. Example: function() { switch((z)) { default: {break; } throw x; } }.
1219                createSyntheticReturn(block);
1220                assert !reachable;
1221            }
1222            // We must calculate the return type here (and not in leaveFunctionNode) as it can affect the liveness of
1223            // the :return symbol and thus affect conversion type liveness calculations for it.
1224            calculateReturnType();
1225        }
1226
1227        boolean cloned = false;
1228        for(final Symbol symbol: block.getSymbols()) {
1229            // Undefine the symbol outside the block
1230            if(localVariableTypes.containsKey(symbol)) {
1231                if(!cloned) {
1232                    localVariableTypes = cloneMap(localVariableTypes);
1233                    cloned = true;
1234                }
1235                localVariableTypes.remove(symbol);
1236            }
1237
1238            if(symbol.hasSlot()) {
1239                final SymbolConversions conversions = symbolConversions.get(symbol);
1240                if(conversions != null) {
1241                    // Potentially make some currently dead types live if they're needed as a source of a type
1242                    // conversion at a join.
1243                    conversions.calculateTypeLiveness(symbol);
1244                }
1245                if(symbol.slotCount() == 0) {
1246                    // This is a local variable that is never read. It won't need a slot.
1247                    symbol.setNeedsSlot(false);
1248                }
1249            }
1250        }
1251
1252        if(reachable) {
1253            // TODO: this is totally backwards. Block should not be breakable, LabelNode should be breakable.
1254            final LabelNode labelNode = lc.getCurrentBlockLabelNode();
1255            if(labelNode != null) {
1256                jumpToLabel(labelNode, block.getBreakLabel());
1257            }
1258        }
1259        leaveBreakable(block);
1260        return block;
1261    }
1262
1263    private void calculateReturnType() {
1264        // NOTE: if return type is unknown, then the function does not explicitly return a value. Such a function under
1265        // ECMAScript rules returns Undefined, which has Type.OBJECT. We might consider an optimization in the future
1266        // where we can return void functions.
1267        if(returnType.isUnknown()) {
1268            returnType = Type.OBJECT;
1269        }
1270    }
1271
1272    private void createSyntheticReturn(final Block body) {
1273        final FunctionNode functionNode = lc.getCurrentFunction();
1274        final long token = functionNode.getToken();
1275        final int finish = functionNode.getFinish();
1276        final List<Statement> statements = body.getStatements();
1277        final int lineNumber = statements.isEmpty() ? functionNode.getLineNumber() : statements.get(statements.size() - 1).getLineNumber();
1278        final IdentNode returnExpr;
1279        if(functionNode.isProgram()) {
1280            returnExpr = new IdentNode(token, finish, RETURN.symbolName()).setSymbol(getCompilerConstantSymbol(functionNode, RETURN));
1281        } else {
1282            returnExpr = null;
1283        }
1284        syntheticReturn = new ReturnNode(lineNumber, token, finish, returnExpr);
1285        syntheticReturn.accept(this);
1286    }
1287
1288    /**
1289     * Leave a breakable node. If there's a join point associated with its break label (meaning there was at least one
1290     * break statement to the end of the node), insert the join point into the flow.
1291     * @param breakable the breakable node being left.
1292     */
1293    private void leaveBreakable(final BreakableNode breakable) {
1294        joinOnLabel(breakable.getBreakLabel());
1295        assertTypeStackIsEmpty();
1296    }
1297
1298    @Override
1299    public Node leaveFunctionNode(final FunctionNode functionNode) {
1300        // Sets the return type of the function and also performs the bottom-up pass of applying type and conversion
1301        // information to nodes as well as doing the calculation on nested functions as required.
1302        FunctionNode newFunction = functionNode;
1303        final SimpleNodeVisitor applyChangesVisitor = new SimpleNodeVisitor() {
1304            private boolean inOuterFunction = true;
1305            private final Deque<JoinPredecessor> joinPredecessors = new ArrayDeque<>();
1306
1307            @Override
1308            protected boolean enterDefault(final Node node) {
1309                if(!inOuterFunction) {
1310                    return false;
1311                }
1312                if(node instanceof JoinPredecessor) {
1313                    joinPredecessors.push((JoinPredecessor)node);
1314                }
1315                return inOuterFunction;
1316            }
1317
1318            @Override
1319            public boolean enterFunctionNode(final FunctionNode fn) {
1320                if(compiler.isOnDemandCompilation()) {
1321                    // Only calculate nested function local variable types if we're doing eager compilation
1322                    return false;
1323                }
1324                inOuterFunction = false;
1325                return true;
1326            }
1327
1328            @SuppressWarnings("fallthrough")
1329            @Override
1330            public Node leaveBinaryNode(final BinaryNode binaryNode) {
1331                if(binaryNode.isComparison()) {
1332                    final Expression lhs = binaryNode.lhs();
1333                    final Expression rhs = binaryNode.rhs();
1334
1335                    final TokenType tt = binaryNode.tokenType();
1336                    switch (tt) {
1337                    case EQ_STRICT:
1338                    case NE_STRICT:
1339                        // Specialize comparison with undefined
1340                        final Expression undefinedNode = createIsUndefined(binaryNode, lhs, rhs,
1341                                tt == TokenType.EQ_STRICT ? Request.IS_UNDEFINED : Request.IS_NOT_UNDEFINED);
1342                        if(undefinedNode != binaryNode) {
1343                            return undefinedNode;
1344                        }
1345                        // Specialize comparison of boolean with non-boolean
1346                        if (lhs.getType().isBoolean() != rhs.getType().isBoolean()) {
1347                            return new RuntimeNode(binaryNode);
1348                        }
1349                        // fallthrough
1350                    default:
1351                        if (lhs.getType().isObject() && rhs.getType().isObject()) {
1352                            return new RuntimeNode(binaryNode);
1353                        }
1354                    }
1355                } else if(binaryNode.isOptimisticUndecidedType()) {
1356                    // At this point, we can assign a static type to the optimistic binary ADD operator as now we know
1357                    // the types of its operands.
1358                    return binaryNode.decideType();
1359                }
1360                return binaryNode;
1361            }
1362
1363            @Override
1364            protected Node leaveDefault(final Node node) {
1365                if(node instanceof JoinPredecessor) {
1366                    final JoinPredecessor original = joinPredecessors.pop();
1367                    assert original.getClass() == node.getClass() : original.getClass().getName() + "!=" + node.getClass().getName();
1368                    final JoinPredecessor newNode = setLocalVariableConversion(original, (JoinPredecessor)node);
1369                    if (newNode instanceof LexicalContextNode) {
1370                        lc.replace((LexicalContextNode)node, (LexicalContextNode)newNode);
1371                    }
1372                    return (Node)newNode;
1373                }
1374                return node;
1375            }
1376
1377            @Override
1378            public Node leaveBlock(final Block block) {
1379                if(inOuterFunction && syntheticReturn != null && lc.isFunctionBody()) {
1380                    final ArrayList<Statement> stmts = new ArrayList<>(block.getStatements());
1381                    stmts.add((ReturnNode)syntheticReturn.accept(this));
1382                    return block.setStatements(lc, stmts);
1383                }
1384                return super.leaveBlock(block);
1385            }
1386
1387            @Override
1388            public Node leaveFunctionNode(final FunctionNode nestedFunctionNode) {
1389                inOuterFunction = true;
1390                final FunctionNode newNestedFunction = (FunctionNode)nestedFunctionNode.accept(
1391                        new LocalVariableTypesCalculator(compiler));
1392                lc.replace(nestedFunctionNode, newNestedFunction);
1393                return newNestedFunction;
1394            }
1395
1396            @Override
1397            public Node leaveIdentNode(final IdentNode identNode) {
1398                final IdentNode original = (IdentNode)joinPredecessors.pop();
1399                final Symbol symbol = identNode.getSymbol();
1400                if(symbol == null) {
1401                    assert identNode.isPropertyName();
1402                    return identNode;
1403                } else if(symbol.hasSlot()) {
1404                    assert !symbol.isScope() || symbol.isParam(); // Only params can be slotted and scoped.
1405                    assert original.getName().equals(identNode.getName());
1406                    final LvarType lvarType = identifierLvarTypes.remove(original);
1407                    if(lvarType != null) {
1408                        return setLocalVariableConversion(original, identNode.setType(lvarType.type));
1409                    }
1410                    // If there's no type, then the identifier must've been in unreachable code. In that case, it can't
1411                    // have assigned conversions either.
1412                    assert localVariableConversions.get(original) == null;
1413                } else {
1414                    assert identIsDeadAndHasNoLiveConversions(original);
1415                }
1416                return identNode;
1417            }
1418
1419            @Override
1420            public Node leaveLiteralNode(final LiteralNode<?> literalNode) {
1421                //for e.g. ArrayLiteralNodes the initial types may have been narrowed due to the
1422                //introduction of optimistic behavior - hence ensure that all literal nodes are
1423                //reinitialized
1424                return literalNode.initialize(lc);
1425            }
1426
1427            @Override
1428            public Node leaveRuntimeNode(final RuntimeNode runtimeNode) {
1429                final Request request = runtimeNode.getRequest();
1430                final boolean isEqStrict = request == Request.EQ_STRICT;
1431                if(isEqStrict || request == Request.NE_STRICT) {
1432                    return createIsUndefined(runtimeNode, runtimeNode.getArgs().get(0), runtimeNode.getArgs().get(1),
1433                            isEqStrict ? Request.IS_UNDEFINED : Request.IS_NOT_UNDEFINED);
1434                }
1435                return runtimeNode;
1436            }
1437
1438            @SuppressWarnings("unchecked")
1439            private <T extends JoinPredecessor> T setLocalVariableConversion(final JoinPredecessor original, final T jp) {
1440                // NOTE: can't use Map.remove() as our copy-on-write AST semantics means some nodes appear twice (in
1441                // finally blocks), so we need to be able to access conversions for them multiple times.
1442                return (T)jp.setLocalVariableConversion(lc, localVariableConversions.get(original));
1443            }
1444        };
1445
1446        newFunction = newFunction.setBody(lc, (Block)newFunction.getBody().accept(applyChangesVisitor));
1447        newFunction = newFunction.setReturnType(lc, returnType);
1448
1449
1450        newFunction = newFunction.setParameters(lc, newFunction.visitParameters(applyChangesVisitor));
1451        return newFunction;
1452    }
1453
1454    private static Expression createIsUndefined(final Expression parent, final Expression lhs, final Expression rhs, final Request request) {
1455        if (isUndefinedIdent(lhs) || isUndefinedIdent(rhs)) {
1456            return new RuntimeNode(parent, request, lhs, rhs);
1457        }
1458        return parent;
1459    }
1460
1461    private static boolean isUndefinedIdent(final Expression expr) {
1462        return expr instanceof IdentNode && "undefined".equals(((IdentNode)expr).getName());
1463    }
1464
1465    private boolean identIsDeadAndHasNoLiveConversions(final IdentNode identNode) {
1466        final LocalVariableConversion conv = localVariableConversions.get(identNode);
1467        return conv == null || !conv.isLive();
1468    }
1469
1470    private void onAssignment(final IdentNode identNode, final LvarType type) {
1471        final Symbol symbol = identNode.getSymbol();
1472        assert symbol != null : identNode.getName();
1473        if(!symbol.isBytecodeLocal()) {
1474            return;
1475        }
1476        assert type != null;
1477        final LvarType finalType;
1478        if(type == LvarType.UNDEFINED && getLocalVariableType(symbol) != LvarType.UNDEFINED) {
1479            // Explicit assignment of a known undefined local variable to a local variable that is not undefined will
1480            // materialize that undefined in the assignment target. Note that assigning known undefined to known
1481            // undefined will *not* initialize the variable, e.g. "var x; var y = x;" compiles to no-op.
1482            finalType = LvarType.OBJECT;
1483            symbol.setFlag(Symbol.HAS_OBJECT_VALUE);
1484        } else {
1485            finalType = type;
1486        }
1487        setType(symbol, finalType);
1488        // Explicit assignment of an undefined value. Make sure the variable can store an object
1489        // TODO: if we communicated the fact to codegen with a flag on the IdentNode that the value was already
1490        // undefined before the assignment, we could just ignore it. In general, we could ignore an assignment if we
1491        // know that the value assigned is the same as the current value of the variable, but we'd need constant
1492        // propagation for that.
1493        setIdentifierLvarType(identNode, finalType);
1494        // For purposes of type calculation, we consider an assignment to a local variable to be followed by
1495        // the catch nodes of the current (if any) try block. This will effectively enforce that narrower
1496        // assignments to a local variable in a try block will also have to store a widened value as well. Code
1497        // within the try block will be able to keep loading the narrower value, but after the try block only
1498        // the widest value will remain live.
1499        // Rationale for this is that if there's an use for that variable in any of the catch blocks, or
1500        // following the catch blocks, they must use the widest type.
1501        // Example:
1502        /*
1503            Originally:
1504            ===========
1505            var x;
1506            try {
1507              x = 1; <-- stores into int slot for x
1508              f(x); <-- loads the int slot for x
1509              x = 3.14 <-- stores into the double slot for x
1510              f(x); <-- loads the double slot for x
1511              x = 1; <-- stores into int slot for x
1512              f(x); <-- loads the int slot for x
1513            } finally {
1514              f(x); <-- loads the double slot for x, but can be reached by a path where x is int, so we need
1515                           to go back and ensure that double values are also always stored along with int
1516                           values.
1517            }
1518
1519            After correction:
1520            =================
1521
1522            var x;
1523            try {
1524              x = 1; <-- stores into both int and double slots for x
1525              f(x); <-- loads the int slot for x
1526              x = 3.14 <-- stores into the double slot for x
1527              f(x); <-- loads the double slot for x
1528              x = 1; <-- stores into both int and double slots for x
1529              f(x); <-- loads the int slot for x
1530            } finally {
1531              f(x); <-- loads the double slot for x
1532            }
1533         */
1534        jumpToCatchBlock(identNode);
1535    }
1536
1537    private void onSelfAssignment(final IdentNode identNode, final LvarType type) {
1538        final Symbol symbol = identNode.getSymbol();
1539        assert symbol != null : identNode.getName();
1540        if(!symbol.isBytecodeLocal()) {
1541            return;
1542        }
1543        // Self-assignment never produce either a boolean or undefined
1544        assert type != null && type != LvarType.UNDEFINED && type != LvarType.BOOLEAN;
1545        setType(symbol, type);
1546        jumpToCatchBlock(identNode);
1547    }
1548
1549    private void resetJoinPoint(final Label label) {
1550        jumpTargets.remove(label);
1551    }
1552
1553    private void setCompilerConstantAsObject(final FunctionNode functionNode, final CompilerConstants cc) {
1554        final Symbol symbol = getCompilerConstantSymbol(functionNode, cc);
1555        setType(symbol, LvarType.OBJECT);
1556        // never mark compiler constants as dead
1557        symbolIsUsed(symbol);
1558    }
1559
1560    private static Symbol getCompilerConstantSymbol(final FunctionNode functionNode, final CompilerConstants cc) {
1561        return functionNode.getBody().getExistingSymbol(cc.symbolName());
1562    }
1563
1564    private void setConversion(final JoinPredecessor node, final Map<Symbol, LvarType> branchLvarTypes, final Map<Symbol, LvarType> joinLvarTypes) {
1565        if(node == null) {
1566            return;
1567        }
1568        if(branchLvarTypes.isEmpty() || joinLvarTypes.isEmpty()) {
1569            localVariableConversions.remove(node);
1570        }
1571
1572        LocalVariableConversion conversion = null;
1573        if(node instanceof IdentNode) {
1574            // conversions on variable assignment in try block are special cases, as they only apply to the variable
1575            // being assigned and all other conversions should be ignored.
1576            final Symbol symbol = ((IdentNode)node).getSymbol();
1577            conversion = createConversion(symbol, branchLvarTypes.get(symbol), joinLvarTypes, null);
1578        } else {
1579            for(final Map.Entry<Symbol, LvarType> entry: branchLvarTypes.entrySet()) {
1580                final Symbol symbol = entry.getKey();
1581                final LvarType branchLvarType = entry.getValue();
1582                conversion = createConversion(symbol, branchLvarType, joinLvarTypes, conversion);
1583            }
1584        }
1585        if(conversion != null) {
1586            localVariableConversions.put(node, conversion);
1587        } else {
1588            localVariableConversions.remove(node);
1589        }
1590    }
1591
1592    private void setIdentifierLvarType(final IdentNode identNode, final LvarType type) {
1593        assert type != null;
1594        identifierLvarTypes.put(identNode, type);
1595    }
1596
1597    /**
1598     * Marks a local variable as having a specific type from this point onward. Invoked by stores to local variables.
1599     * @param symbol the symbol representing the variable
1600     * @param type the type
1601     */
1602    private void setType(final Symbol symbol, final LvarType type) {
1603        if(getLocalVariableTypeOrNull(symbol) == type) {
1604            return;
1605        }
1606        assert symbol.hasSlot();
1607        assert !symbol.isGlobal();
1608        localVariableTypes = localVariableTypes.isEmpty() ? new IdentityHashMap<Symbol, LvarType>() : cloneMap(localVariableTypes);
1609        localVariableTypes.put(symbol, type);
1610    }
1611
1612    /**
1613     * Set a flag in the symbol marking it as needing to be able to store a value of a particular type. Every symbol for
1614     * a local variable will be assigned between 1 and 6 local variable slots for storing all types it is known to need
1615     * to store.
1616     * @param symbol the symbol
1617     */
1618    private void symbolIsUsed(final Symbol symbol) {
1619        symbolIsUsed(symbol, getLocalVariableType(symbol));
1620    }
1621}
1622