/* * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.nashorn.internal.codegen; import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN; import static jdk.nashorn.internal.ir.Expression.isAlwaysFalse; import static jdk.nashorn.internal.ir.Expression.isAlwaysTrue; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.AccessNode; import jdk.nashorn.internal.ir.BinaryNode; import jdk.nashorn.internal.ir.Block; import jdk.nashorn.internal.ir.BreakNode; import jdk.nashorn.internal.ir.BreakableNode; import jdk.nashorn.internal.ir.CallNode; import jdk.nashorn.internal.ir.CaseNode; import jdk.nashorn.internal.ir.CatchNode; import jdk.nashorn.internal.ir.ContinueNode; import jdk.nashorn.internal.ir.Expression; import jdk.nashorn.internal.ir.ExpressionStatement; import jdk.nashorn.internal.ir.ForNode; import jdk.nashorn.internal.ir.FunctionNode; import jdk.nashorn.internal.ir.GetSplitState; import jdk.nashorn.internal.ir.IdentNode; import jdk.nashorn.internal.ir.IfNode; import jdk.nashorn.internal.ir.IndexNode; import jdk.nashorn.internal.ir.JoinPredecessor; import jdk.nashorn.internal.ir.JoinPredecessorExpression; import jdk.nashorn.internal.ir.JumpStatement; import jdk.nashorn.internal.ir.JumpToInlinedFinally; import jdk.nashorn.internal.ir.LabelNode; import jdk.nashorn.internal.ir.LexicalContext; import jdk.nashorn.internal.ir.LexicalContextNode; import jdk.nashorn.internal.ir.LiteralNode; import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode; import jdk.nashorn.internal.ir.LocalVariableConversion; import jdk.nashorn.internal.ir.LoopNode; import jdk.nashorn.internal.ir.Node; import jdk.nashorn.internal.ir.ObjectNode; import jdk.nashorn.internal.ir.PropertyNode; import jdk.nashorn.internal.ir.ReturnNode; import jdk.nashorn.internal.ir.RuntimeNode; import jdk.nashorn.internal.ir.RuntimeNode.Request; import jdk.nashorn.internal.ir.SplitReturn; import jdk.nashorn.internal.ir.Statement; import jdk.nashorn.internal.ir.SwitchNode; import jdk.nashorn.internal.ir.Symbol; import jdk.nashorn.internal.ir.TernaryNode; import jdk.nashorn.internal.ir.ThrowNode; import jdk.nashorn.internal.ir.TryNode; import jdk.nashorn.internal.ir.UnaryNode; import jdk.nashorn.internal.ir.VarNode; import jdk.nashorn.internal.ir.WhileNode; import jdk.nashorn.internal.ir.WithNode; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.ir.visitor.SimpleNodeVisitor; import jdk.nashorn.internal.parser.TokenType; /** * Calculates types for local variables. For purposes of local variable type calculation, the only types used are * Undefined, boolean, int, long, double, and Object. The calculation eagerly widens types of local variable to their * widest at control flow join points. * TODO: investigate a more sophisticated solution that uses use/def information to only widens the type of a local * variable to its widest used type after the join point. That would eliminate some widenings of undefined variables to * object, most notably those used only in loops. We need a full liveness analysis for that. Currently, we can establish * per-type liveness, which eliminates most of unwanted dead widenings. * NOTE: the way this class is implemented, it actually processes the AST in two passes. The first pass is top-down and * implemented in {@code enterXxx} methods. This pass does not mutate the AST (except for one occurrence, noted below), * as being able to find relevant labels for control flow joins is sensitive to their reference identity, and mutated * label-carrying nodes will create copies of their labels. A second bottom-up pass applying the changes is implemented * in the separate visitor sitting in {@link #leaveFunctionNode(FunctionNode)}. This visitor will also instantiate new * instances of the calculator to be run on nested functions (when not lazy compiling). * */ final class LocalVariableTypesCalculator extends SimpleNodeVisitor { private static class JumpOrigin { final JoinPredecessor node; final Map<Symbol, LvarType> types; JumpOrigin(final JoinPredecessor node, final Map<Symbol, LvarType> types) { this.node = node; this.types = types; } } private static class JumpTarget { private final List<JumpOrigin> origins = new LinkedList<>(); private Map<Symbol, LvarType> types = Collections.emptyMap(); void addOrigin(final JoinPredecessor originNode, final Map<Symbol, LvarType> originTypes, final LocalVariableTypesCalculator calc) { origins.add(new JumpOrigin(originNode, originTypes)); this.types = calc.getUnionTypes(this.types, originTypes); } } private enum LvarType { UNDEFINED(Type.UNDEFINED), BOOLEAN(Type.BOOLEAN), INT(Type.INT), DOUBLE(Type.NUMBER), OBJECT(Type.OBJECT); private final Type type; private final TypeHolderExpression typeExpression; private LvarType(final Type type) { this.type = type; this.typeExpression = new TypeHolderExpression(type); } } /** * A bogus Expression subclass that only reports its type. Used to interrogate BinaryNode and UnaryNode about their * types by creating temporary copies of them and replacing their operands with instances of these. An alternative * solution would be to add BinaryNode.getType(Type lhsType, Type rhsType) and UnaryNode.getType(Type exprType) * methods. For the time being though, this is easier to implement and is in fact fairly clean. It does result in * generation of higher number of temporary short lived nodes, though. */ private static class TypeHolderExpression extends Expression { private static final long serialVersionUID = 1L; private final Type type; TypeHolderExpression(final Type type) { super(0L, 0, 0); this.type = type; } @Override public Node accept(final NodeVisitor<? extends LexicalContext> visitor) { throw new AssertionError(); } @Override public Type getType() { return type; } @Override public void toString(final StringBuilder sb, final boolean printType) { throw new AssertionError(); } } private static final Map<Type, LvarType> TO_LVAR_TYPE = new IdentityHashMap<>(); static { for(final LvarType lvarType: LvarType.values()) { TO_LVAR_TYPE.put(lvarType.type, lvarType); } } @SuppressWarnings("unchecked") private static HashMap<Symbol, LvarType> cloneMap(final Map<Symbol, LvarType> map) { return (HashMap<Symbol, LvarType>)((HashMap<?,?>)map).clone(); } private LocalVariableConversion createConversion(final Symbol symbol, final LvarType branchLvarType, final Map<Symbol, LvarType> joinLvarTypes, final LocalVariableConversion next) { if (invalidatedSymbols.contains(symbol)) { return next; } final LvarType targetType = joinLvarTypes.get(symbol); assert targetType != null; if(targetType == branchLvarType) { return next; } // NOTE: we could naively just use symbolIsUsed(symbol, branchLvarType) here, but that'd be wrong. While // technically a conversion will read the value of the symbol with that type, but it will also write it to a new // type, and that type might be dead (we can't know yet). For this reason, we don't treat conversion reads as // real uses until we know their target type is live. If we didn't do this, and just did a symbolIsUsed here, // we'd introduce false live variables which could nevertheless turn into dead ones in a subsequent // deoptimization, causing a shift in the list of live locals that'd cause erroneous restoration of // continuations (since RewriteException's byteCodeSlots carries an array and not a name-value map). symbolIsConverted(symbol, branchLvarType, targetType); return new LocalVariableConversion(symbol, branchLvarType.type, targetType.type, next); } private Map<Symbol, LvarType> getUnionTypes(final Map<Symbol, LvarType> types1, final Map<Symbol, LvarType> types2) { if(types1 == types2 || types1.isEmpty()) { return types2; } else if(types2.isEmpty()) { return types1; } final Set<Symbol> commonSymbols = new HashSet<>(types1.keySet()); commonSymbols.retainAll(types2.keySet()); // We have a chance of returning an unmodified set if both sets have the same keys and one is strictly wider // than the other. final int commonSize = commonSymbols.size(); final int types1Size = types1.size(); final int types2Size = types2.size(); if(commonSize == types1Size && commonSize == types2Size) { boolean matches1 = true, matches2 = true; Map<Symbol, LvarType> union = null; for(final Symbol symbol: commonSymbols) { final LvarType type1 = types1.get(symbol); final LvarType type2 = types2.get(symbol); final LvarType widest = widestLvarType(type1, type2); if(widest != type1 && matches1) { matches1 = false; if(!matches2) { union = cloneMap(types1); } } if (widest != type2 && matches2) { matches2 = false; if(!matches1) { union = cloneMap(types2); } } if(!(matches1 || matches2)) { assert union != null; union.put(symbol, widest); } } return matches1 ? types1 : matches2 ? types2 : union; } // General case final Map<Symbol, LvarType> union; if(types1Size > types2Size) { union = cloneMap(types1); union.putAll(types2); } else { union = cloneMap(types2); union.putAll(types1); } for(final Symbol symbol: commonSymbols) { final LvarType type1 = types1.get(symbol); final LvarType type2 = types2.get(symbol); union.put(symbol, widestLvarType(type1, type2)); } // If the two sets of symbols differ, there's a good chance that some of // symbols only appearing in one of the sets are lexically invalidated, // so we remove them from further consideration. // This is not strictly necessary, just a working set size optimization. union.keySet().removeAll(invalidatedSymbols); return union; } private static void symbolIsUsed(final Symbol symbol, final LvarType type) { if(type != LvarType.UNDEFINED) { symbol.setHasSlotFor(type.type); } } private static class SymbolConversions { private static final byte I2D = 1 << 0; private static final byte I2O = 1 << 1; private static final byte D2O = 1 << 2; private byte conversions; void recordConversion(final LvarType from, final LvarType to) { switch (from) { case UNDEFINED: return; case INT: case BOOLEAN: switch (to) { case DOUBLE: recordConversion(I2D); return; case OBJECT: recordConversion(I2O); return; default: illegalConversion(from, to); return; } case DOUBLE: if(to == LvarType.OBJECT) { recordConversion(D2O); } return; default: illegalConversion(from, to); } } private static void illegalConversion(final LvarType from, final LvarType to) { throw new AssertionError("Invalid conversion from " + from + " to " + to); } void recordConversion(final byte convFlag) { conversions = (byte)(conversions | convFlag); } boolean hasConversion(final byte convFlag) { return (conversions & convFlag) != 0; } void calculateTypeLiveness(final Symbol symbol) { if(symbol.hasSlotFor(Type.OBJECT)) { if(hasConversion(D2O)) { symbol.setHasSlotFor(Type.NUMBER); } if(hasConversion(I2O)) { symbol.setHasSlotFor(Type.INT); } } if(symbol.hasSlotFor(Type.NUMBER)) { if(hasConversion(I2D)) { symbol.setHasSlotFor(Type.INT); } } } } private void symbolIsConverted(final Symbol symbol, final LvarType from, final LvarType to) { SymbolConversions conversions = symbolConversions.get(symbol); if(conversions == null) { conversions = new SymbolConversions(); symbolConversions.put(symbol, conversions); } conversions.recordConversion(from, to); } private static LvarType toLvarType(final Type type) { assert type != null; final LvarType lvarType = TO_LVAR_TYPE.get(type); if(lvarType != null) { return lvarType; } assert type.isObject() : "Unsupported primitive type: " + type; return LvarType.OBJECT; } private static LvarType widestLvarType(final LvarType t1, final LvarType t2) { if(t1 == t2) { return t1; } // Undefined or boolean to anything always widens to object. if(t1.ordinal() < LvarType.INT.ordinal() || t2.ordinal() < LvarType.INT.ordinal()) { return LvarType.OBJECT; } return LvarType.values()[Math.max(t1.ordinal(), t2.ordinal())]; } private final Compiler compiler; private final Map<Label, JumpTarget> jumpTargets = new IdentityHashMap<>(); // Local variable type mapping at the currently evaluated point. No map instance is ever modified; setLvarType() always // allocates a new map. Immutability of maps allows for cheap snapshots by just keeping the reference to the current // value. private Map<Symbol, LvarType> localVariableTypes = Collections.emptyMap(); // Set of symbols whose lexical scope has already ended. private final Set<Symbol> invalidatedSymbols = new HashSet<>(); // Stack for evaluated expression types. private final Deque<LvarType> typeStack = new ArrayDeque<>(); // Whether the current point in the AST is reachable code private boolean reachable = true; // Return type of the function private Type returnType = Type.UNKNOWN; // Synthetic return node that we must insert at the end of the function if it's end is reachable. private ReturnNode syntheticReturn; private boolean alreadyEnteredTopLevelFunction; // LvarType and conversion information gathered during the top-down pass; applied to nodes in the bottom-up pass. private final Map<JoinPredecessor, LocalVariableConversion> localVariableConversions = new IdentityHashMap<>(); private final Map<IdentNode, LvarType> identifierLvarTypes = new IdentityHashMap<>(); private final Map<Symbol, SymbolConversions> symbolConversions = new IdentityHashMap<>(); // Stack of open labels for starts of catch blocks, one for every currently traversed try block; for inserting // control flow edges to them. Note that we currently don't insert actual control flow edges, but instead edges that // help us with type calculations. This means that some operations that can result in an exception being thrown // aren't considered (function calls, side effecting property getters and setters etc.), while some operations that // don't result in control flow transfers do originate an edge to the catch blocks (namely, assignments to local // variables). private final Deque<Label> catchLabels = new ArrayDeque<>(); LocalVariableTypesCalculator(final Compiler compiler) { this.compiler = compiler; } private JumpTarget createJumpTarget(final Label label) { assert !jumpTargets.containsKey(label); final JumpTarget jumpTarget = new JumpTarget(); jumpTargets.put(label, jumpTarget); return jumpTarget; } private void doesNotContinueSequentially() { reachable = false; localVariableTypes = Collections.emptyMap(); assertTypeStackIsEmpty(); } private boolean pushExpressionType(final Expression expr) { typeStack.push(toLvarType(expr.getType())); return false; } @Override public boolean enterAccessNode(final AccessNode accessNode) { visitExpression(accessNode.getBase()); return pushExpressionType(accessNode); } @Override public boolean enterBinaryNode(final BinaryNode binaryNode) { // NOTE: regardless of operator's lexical associativity, lhs is always evaluated first. final Expression lhs = binaryNode.lhs(); final LvarType lhsType; if (!(lhs instanceof IdentNode && binaryNode.isTokenType(TokenType.ASSIGN))) { lhsType = visitExpression(lhs); } else { // Can't visit IdentNode on LHS of a simple assignment, as visits imply use, and this is def. // The type is irrelevant, as only RHS is used to determine the type anyway. lhsType = LvarType.UNDEFINED; } final boolean isLogical = binaryNode.isLogical(); final Label joinLabel = isLogical ? new Label("") : null; if(isLogical) { jumpToLabel((JoinPredecessor)lhs, joinLabel); } final Expression rhs = binaryNode.rhs(); final LvarType rhsType = visitExpression(rhs); if(isLogical) { jumpToLabel((JoinPredecessor)rhs, joinLabel); } joinOnLabel(joinLabel); final LvarType type = toLvarType(binaryNode.setOperands(lhsType.typeExpression, rhsType.typeExpression).getType()); if(binaryNode.isAssignment() && lhs instanceof IdentNode) { if(binaryNode.isSelfModifying()) { onSelfAssignment((IdentNode)lhs, type); } else { onAssignment((IdentNode)lhs, type); } } typeStack.push(type); return false; } @Override public boolean enterBlock(final Block block) { boolean cloned = false; for(final Symbol symbol: block.getSymbols()) { if(symbol.isBytecodeLocal()) { if (getLocalVariableTypeOrNull(symbol) == null) { if (!cloned) { cloneOrNewLocalVariableTypes(); cloned = true; } localVariableTypes.put(symbol, LvarType.UNDEFINED); } // In case we're repeating analysis of a lexical scope (e.g. it's in a loop), // make sure all symbols lexically scoped by the block become valid again. invalidatedSymbols.remove(symbol); } } return true; } @Override public boolean enterBreakNode(final BreakNode breakNode) { return enterJumpStatement(breakNode); } @Override public boolean enterCallNode(final CallNode callNode) { visitExpression(callNode.getFunction()); visitExpressions(callNode.getArgs()); final CallNode.EvalArgs evalArgs = callNode.getEvalArgs(); if (evalArgs != null) { visitExpressions(evalArgs.getArgs()); } return pushExpressionType(callNode); } @Override public boolean enterContinueNode(final ContinueNode continueNode) { return enterJumpStatement(continueNode); } private boolean enterJumpStatement(final JumpStatement jump) { if(!reachable) { return false; } assertTypeStackIsEmpty(); jumpToLabel(jump, jump.getTargetLabel(lc), getBreakTargetTypes(jump.getPopScopeLimit(lc))); doesNotContinueSequentially(); return false; } @Override protected boolean enterDefault(final Node node) { return reachable; } private void enterDoWhileLoop(final WhileNode loopNode) { assertTypeStackIsEmpty(); final JoinPredecessorExpression test = loopNode.getTest(); final Block body = loopNode.getBody(); final Label continueLabel = loopNode.getContinueLabel(); final Label breakLabel = loopNode.getBreakLabel(); final Map<Symbol, LvarType> beforeLoopTypes = localVariableTypes; final Label repeatLabel = new Label(""); for(;;) { jumpToLabel(loopNode, repeatLabel, beforeLoopTypes); final Map<Symbol, LvarType> beforeRepeatTypes = localVariableTypes; body.accept(this); if(reachable) { jumpToLabel(body, continueLabel); } joinOnLabel(continueLabel); if(!reachable) { break; } visitExpressionOnEmptyStack(test); jumpToLabel(test, breakLabel); if(isAlwaysFalse(test)) { break; } jumpToLabel(test, repeatLabel); joinOnLabel(repeatLabel); if(localVariableTypes.equals(beforeRepeatTypes)) { break; } resetJoinPoint(continueLabel); resetJoinPoint(breakLabel); resetJoinPoint(repeatLabel); } if(isAlwaysTrue(test)) { doesNotContinueSequentially(); } leaveBreakable(loopNode); } @Override public boolean enterExpressionStatement(final ExpressionStatement expressionStatement) { if (reachable) { visitExpressionOnEmptyStack(expressionStatement.getExpression()); } return false; } private void assertTypeStackIsEmpty() { assert typeStack.isEmpty(); } @Override protected Node leaveDefault(final Node node) { assert !(node instanceof Expression); // All expressions were handled assert !(node instanceof Statement) || typeStack.isEmpty(); // No statements leave with a non-empty stack return node; } private LvarType visitExpressionOnEmptyStack(final Expression expr) { assertTypeStackIsEmpty(); return visitExpression(expr); } private LvarType visitExpression(final Expression expr) { final int stackSize = typeStack.size(); expr.accept(this); assert typeStack.size() == stackSize + 1; return typeStack.pop(); } private void visitExpressions(final List<Expression> exprs) { for(final Expression expr: exprs) { if (expr != null) { visitExpression(expr); } } } @Override public boolean enterForNode(final ForNode forNode) { if(!reachable) { return false; } final Expression init = forNode.getInit(); if(forNode.isForInOrOf()) { final JoinPredecessorExpression iterable = forNode.getModify(); visitExpression(iterable); enterTestFirstLoop(forNode, null, init, // If we're iterating over property names, and we can discern from the runtime environment // of the compilation that the object being iterated over must use strings for property // names (e.g., it is a native JS object or array), then we'll not bother trying to treat // the property names optimistically. !compiler.useOptimisticTypes() || (!forNode.isForEach() && compiler.hasStringPropertyIterator(iterable.getExpression()))); } else { if(init != null) { visitExpressionOnEmptyStack(init); } enterTestFirstLoop(forNode, forNode.getModify(), null, false); } assertTypeStackIsEmpty(); return false; } @Override public boolean enterFunctionNode(final FunctionNode functionNode) { if(alreadyEnteredTopLevelFunction) { typeStack.push(LvarType.OBJECT); return false; } int pos = 0; if(!functionNode.isVarArg()) { for (final IdentNode param : functionNode.getParameters()) { final Symbol symbol = param.getSymbol(); // Parameter is not necessarily bytecode local as it can be scoped due to nested context use, but it // must have a slot if we aren't in a function with vararg signature. assert symbol.hasSlot(); final Type callSiteParamType = compiler.getParamType(functionNode, pos); final LvarType paramType = callSiteParamType == null ? LvarType.OBJECT : toLvarType(callSiteParamType); setType(symbol, paramType); // Make sure parameter slot for its incoming value is not marked dead. NOTE: this is a heuristic. Right // now, CodeGenerator.expandParameters() relies on the fact that every parameter's final slot width will // be at least the same as incoming width, therefore even if a parameter is never read, we'll still keep // its slot. symbolIsUsed(symbol); setIdentifierLvarType(param, paramType); pos++; } } setCompilerConstantAsObject(functionNode, CompilerConstants.THIS); // TODO: coarse-grained. If we wanted to solve it completely precisely, // we'd also need to push/pop its type when handling WithNode (so that // it can go back to undefined after a 'with' block. if(functionNode.hasScopeBlock() || functionNode.needsParentScope()) { setCompilerConstantAsObject(functionNode, CompilerConstants.SCOPE); } if(functionNode.needsCallee()) { setCompilerConstantAsObject(functionNode, CompilerConstants.CALLEE); } if(functionNode.needsArguments()) { setCompilerConstantAsObject(functionNode, CompilerConstants.ARGUMENTS); } alreadyEnteredTopLevelFunction = true; return true; } @Override public boolean enterGetSplitState(final GetSplitState getSplitState) { return pushExpressionType(getSplitState); } @Override public boolean enterIdentNode(final IdentNode identNode) { final Symbol symbol = identNode.getSymbol(); if(symbol.isBytecodeLocal()) { symbolIsUsed(symbol); final LvarType type = getLocalVariableType(symbol); setIdentifierLvarType(identNode, type); typeStack.push(type); } else { pushExpressionType(identNode); } return false; } @Override public boolean enterIfNode(final IfNode ifNode) { processIfNode(ifNode); return false; } private void processIfNode(final IfNode ifNode) { if(!reachable) { return; } final Expression test = ifNode.getTest(); final Block pass = ifNode.getPass(); final Block fail = ifNode.getFail(); visitExpressionOnEmptyStack(test); final Map<Symbol, LvarType> passLvarTypes; final boolean reachableFromPass; final boolean isTestAlwaysTrue = isAlwaysTrue(test); if(isAlwaysFalse(test)) { passLvarTypes = null; reachableFromPass = false; } else { final Map<Symbol, LvarType> afterTestLvarTypes = localVariableTypes; pass.accept(this); assertTypeStackIsEmpty(); if (isTestAlwaysTrue) { return; } passLvarTypes = localVariableTypes; reachableFromPass = reachable; localVariableTypes = afterTestLvarTypes; reachable = true; } // If we get here, then we need to consider the case where pass block is not executed assert !isTestAlwaysTrue; if (fail != null) { fail.accept(this); assertTypeStackIsEmpty(); } if(reachable) { if(reachableFromPass) { final Map<Symbol, LvarType> failLvarTypes = localVariableTypes; localVariableTypes = getUnionTypes(passLvarTypes, failLvarTypes); setConversion(pass, passLvarTypes, localVariableTypes); // IfNode itself is associated with conversions that might need to be performed after the test if // there's no else branch. E.g. // if(x = 1, cond) { x = 1.0 } must widen "x = 1" to a double. setConversion(fail != null ? fail : ifNode, failLvarTypes, localVariableTypes); } } else if (reachableFromPass) { assert passLvarTypes != null; localVariableTypes = passLvarTypes; reachable = true; } } @Override public boolean enterIndexNode(final IndexNode indexNode) { visitExpression(indexNode.getBase()); visitExpression(indexNode.getIndex()); return pushExpressionType(indexNode); } @Override public boolean enterJoinPredecessorExpression(final JoinPredecessorExpression joinExpr) { final Expression expr = joinExpr.getExpression(); if (expr != null) { expr.accept(this); } else { typeStack.push(LvarType.UNDEFINED); } return false; } @Override public boolean enterJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) { return enterJumpStatement(jumpToInlinedFinally); } @Override public boolean enterLiteralNode(final LiteralNode<?> literalNode) { if (literalNode instanceof ArrayLiteralNode) { final List<Expression> expressions = ((ArrayLiteralNode)literalNode).getElementExpressions(); if (expressions != null) { visitExpressions(expressions); } } pushExpressionType(literalNode); return false; } @Override public boolean enterObjectNode(final ObjectNode objectNode) { for(final PropertyNode propertyNode: objectNode.getElements()) { // Avoid falsely adding property keys to the control flow graph final Expression value = propertyNode.getValue(); if (value != null) { visitExpression(value); } } return pushExpressionType(objectNode); } @Override public boolean enterPropertyNode(final PropertyNode propertyNode) { // Property nodes are only accessible through object literals, and we handled that case above throw new AssertionError(); } @Override public boolean enterReturnNode(final ReturnNode returnNode) { if(!reachable) { return false; } final Expression returnExpr = returnNode.getExpression(); final Type returnExprType; if(returnExpr != null) { returnExprType = visitExpressionOnEmptyStack(returnExpr).type; } else { assertTypeStackIsEmpty(); returnExprType = Type.UNDEFINED; } returnType = Type.widestReturnType(returnType, returnExprType); doesNotContinueSequentially(); return false; } @Override public boolean enterRuntimeNode(final RuntimeNode runtimeNode) { visitExpressions(runtimeNode.getArgs()); return pushExpressionType(runtimeNode); } @Override public boolean enterSplitReturn(final SplitReturn splitReturn) { doesNotContinueSequentially(); return false; } @Override public boolean enterSwitchNode(final SwitchNode switchNode) { if(!reachable) { return false; } visitExpressionOnEmptyStack(switchNode.getExpression()); final List<CaseNode> cases = switchNode.getCases(); if(cases.isEmpty()) { return false; } // Control flow is different for all-integer cases where we dispatch by switch table, and for all other cases // where we do sequential comparison. Note that CaseNode objects act as join points. final boolean isInteger = switchNode.isUniqueInteger(); final Label breakLabel = switchNode.getBreakLabel(); final boolean hasDefault = switchNode.getDefaultCase() != null; boolean tagUsed = false; for(final CaseNode caseNode: cases) { final Expression test = caseNode.getTest(); if(!isInteger && test != null) { visitExpressionOnEmptyStack(test); if(!tagUsed) { symbolIsUsed(switchNode.getTag(), LvarType.OBJECT); tagUsed = true; } } // CaseNode carries the conversions that need to be performed on its entry from the test. // CodeGenerator ensures these are only emitted when arriving on the branch and not through a // fallthrough. jumpToLabel(caseNode, caseNode.getBody().getEntryLabel()); } if(!hasDefault) { // No default case means we can arrive at the break label without entering any cases. In that case // SwitchNode will carry the conversions that need to be performed before it does that jump. jumpToLabel(switchNode, breakLabel); } // All cases are arrived at through jumps doesNotContinueSequentially(); Block previousBlock = null; for(final CaseNode caseNode: cases) { final Block body = caseNode.getBody(); final Label entryLabel = body.getEntryLabel(); if(previousBlock != null && reachable) { jumpToLabel(previousBlock, entryLabel); } joinOnLabel(entryLabel); assert reachable == true; body.accept(this); previousBlock = body; } if(previousBlock != null && reachable) { jumpToLabel(previousBlock, breakLabel); } leaveBreakable(switchNode); return false; } @Override public boolean enterTernaryNode(final TernaryNode ternaryNode) { final Expression test = ternaryNode.getTest(); final Expression trueExpr = ternaryNode.getTrueExpression(); final Expression falseExpr = ternaryNode.getFalseExpression(); visitExpression(test); final Map<Symbol, LvarType> testExitLvarTypes = localVariableTypes; final LvarType trueType; if(!isAlwaysFalse(test)) { trueType = visitExpression(trueExpr); } else { trueType = null; } final Map<Symbol, LvarType> trueExitLvarTypes = localVariableTypes; localVariableTypes = testExitLvarTypes; final LvarType falseType; if(!isAlwaysTrue(test)) { falseType = visitExpression(falseExpr); } else { falseType = null; } final Map<Symbol, LvarType> falseExitLvarTypes = localVariableTypes; localVariableTypes = getUnionTypes(trueExitLvarTypes, falseExitLvarTypes); setConversion((JoinPredecessor)trueExpr, trueExitLvarTypes, localVariableTypes); setConversion((JoinPredecessor)falseExpr, falseExitLvarTypes, localVariableTypes); typeStack.push(trueType != null ? falseType != null ? widestLvarType(trueType, falseType) : trueType : assertNotNull(falseType)); return false; } private static <T> T assertNotNull(final T t) { assert t != null; return t; } private void enterTestFirstLoop(final LoopNode loopNode, final JoinPredecessorExpression modify, final Expression iteratorValues, final boolean iteratorValuesAreObject) { final JoinPredecessorExpression test = loopNode.getTest(); if(isAlwaysFalse(test)) { visitExpressionOnEmptyStack(test); return; } final Label continueLabel = loopNode.getContinueLabel(); final Label breakLabel = loopNode.getBreakLabel(); final Label repeatLabel = modify == null ? continueLabel : new Label(""); final Map<Symbol, LvarType> beforeLoopTypes = localVariableTypes; for(;;) { jumpToLabel(loopNode, repeatLabel, beforeLoopTypes); final Map<Symbol, LvarType> beforeRepeatTypes = localVariableTypes; if(test != null) { visitExpressionOnEmptyStack(test); } if(!isAlwaysTrue(test)) { jumpToLabel(test, breakLabel); } if(iteratorValues instanceof IdentNode) { final IdentNode ident = (IdentNode)iteratorValues; // Receives iterator values; the optimistic type of the iterator values is tracked on the // identifier, but we override optimism if it's known that the object being iterated over will // never have primitive property names. onAssignment(ident, iteratorValuesAreObject ? LvarType.OBJECT : toLvarType(compiler.getOptimisticType(ident))); } final Block body = loopNode.getBody(); body.accept(this); if(reachable) { jumpToLabel(body, continueLabel); } joinOnLabel(continueLabel); if(!reachable) { break; } if(modify != null) { visitExpressionOnEmptyStack(modify); jumpToLabel(modify, repeatLabel); joinOnLabel(repeatLabel); } if(localVariableTypes.equals(beforeRepeatTypes)) { break; } // Reset the join points and repeat the analysis resetJoinPoint(continueLabel); resetJoinPoint(breakLabel); resetJoinPoint(repeatLabel); } if(isAlwaysTrue(test) && iteratorValues == null) { doesNotContinueSequentially(); } leaveBreakable(loopNode); } @Override public boolean enterThrowNode(final ThrowNode throwNode) { if(!reachable) { return false; } visitExpressionOnEmptyStack(throwNode.getExpression()); jumpToCatchBlock(throwNode); doesNotContinueSequentially(); return false; } @Override public boolean enterTryNode(final TryNode tryNode) { if(!reachable) { return false; } // This is the label for the join point at the entry of the catch blocks. final Label catchLabel = new Label(""); catchLabels.push(catchLabel); // Presume that even the start of the try block can immediately go to the catch jumpToLabel(tryNode, catchLabel); final Block body = tryNode.getBody(); body.accept(this); catchLabels.pop(); // Final exit label for the whole try/catch construct (after the try block and after all catches). final Label endLabel = new Label(""); boolean canExit = false; if(reachable) { jumpToLabel(body, endLabel); canExit = true; } doesNotContinueSequentially(); for (final Block inlinedFinally : tryNode.getInlinedFinallies()) { final Block finallyBody = TryNode.getLabelledInlinedFinallyBlock(inlinedFinally); joinOnLabel(finallyBody.getEntryLabel()); // NOTE: the jump to inlined finally can end up in dead code, so it is not necessarily reachable. if (reachable) { finallyBody.accept(this); // All inlined finallies end with a jump or a return assert !reachable; } } joinOnLabel(catchLabel); for(final CatchNode catchNode: tryNode.getCatches()) { final IdentNode exception = catchNode.getExceptionIdentifier(); onAssignment(exception, LvarType.OBJECT); final Expression condition = catchNode.getExceptionCondition(); if(condition != null) { visitExpression(condition); } final Map<Symbol, LvarType> afterConditionTypes = localVariableTypes; final Block catchBody = catchNode.getBody(); // TODO: currently, we consider that the catch blocks are always reachable from the try block as currently // we lack enough analysis to prove that no statement before a break/continue/return in the try block can // throw an exception. reachable = true; catchBody.accept(this); if(reachable) { jumpToLabel(catchBody, endLabel); canExit = true; } localVariableTypes = afterConditionTypes; } // NOTE: if we had one or more conditional catch blocks with no unconditional catch block following them, then // there will be an unconditional rethrow, so the join point can never be reached from the last // conditionExpression. doesNotContinueSequentially(); if(canExit) { joinOnLabel(endLabel); } return false; } @Override public boolean enterUnaryNode(final UnaryNode unaryNode) { final Expression expr = unaryNode.getExpression(); final LvarType unaryType = toLvarType(unaryNode.setExpression(visitExpression(expr).typeExpression).getType()); if(unaryNode.isSelfModifying() && expr instanceof IdentNode) { onSelfAssignment((IdentNode)expr, unaryType); } typeStack.push(unaryType); return false; } @Override public boolean enterVarNode(final VarNode varNode) { if (!reachable) { return false; } final Expression init = varNode.getInit(); if(init != null) { onAssignment(varNode.getName(), visitExpression(init)); } return false; } @Override public boolean enterWhileNode(final WhileNode whileNode) { if(!reachable) { return false; } if(whileNode.isDoWhile()) { enterDoWhileLoop(whileNode); } else { enterTestFirstLoop(whileNode, null, null, false); } return false; } @Override public boolean enterWithNode(final WithNode withNode) { if (reachable) { visitExpression(withNode.getExpression()); withNode.getBody().accept(this); } return false; }; private Map<Symbol, LvarType> getBreakTargetTypes(final LexicalContextNode target) { // Remove symbols defined in the the blocks that are being broken out of. Map<Symbol, LvarType> types = localVariableTypes; for(final Iterator<LexicalContextNode> it = lc.getAllNodes(); it.hasNext();) { final LexicalContextNode node = it.next(); if(node instanceof Block) { for(final Symbol symbol: ((Block)node).getSymbols()) { if(localVariableTypes.containsKey(symbol)) { if(types == localVariableTypes) { types = cloneMap(localVariableTypes); } types.remove(symbol); } } } if(node == target) { break; } } return types; } /** * Returns the current type of the local variable represented by the symbol. This is the most strict of all * {@code getLocalVariableType*} methods, as it will throw an assertion if the type is null. Therefore, it is only * safe to be invoked on symbols known to be bytecode locals, and only after they have been initialized. * Regardless, it is recommended to use this method in majority of cases, as because of its strictness it is the * best suited for catching missing type calculation bugs early. * @param symbol a symbol representing a bytecode local variable. * @return the current type of the local variable represented by the symbol */ private LvarType getLocalVariableType(final Symbol symbol) { final LvarType type = getLocalVariableTypeOrNull(symbol); assert type != null; return type; } /** * Gets the type for a variable represented by a symbol, or null if the type is not know. This is the least strict * of all local variable type getters, and as such its use is discouraged except in initialization scenarios (where * a just-defined symbol might still be null). * @param symbol the symbol * @return the current type for the symbol, or null if the type is not known either because the symbol has not been * initialized, or because the symbol does not represent a bytecode local variable. */ private LvarType getLocalVariableTypeOrNull(final Symbol symbol) { return localVariableTypes.get(symbol); } private JumpTarget getOrCreateJumpTarget(final Label label) { JumpTarget jumpTarget = jumpTargets.get(label); if(jumpTarget == null) { jumpTarget = createJumpTarget(label); } return jumpTarget; } /** * If there's a join point associated with a label, insert the join point into the flow. * @param label the label to insert a join point for. */ private void joinOnLabel(final Label label) { final JumpTarget jumpTarget = jumpTargets.remove(label); if(jumpTarget == null) { return; } assert !jumpTarget.origins.isEmpty(); reachable = true; localVariableTypes = getUnionTypes(jumpTarget.types, localVariableTypes); for(final JumpOrigin jumpOrigin: jumpTarget.origins) { setConversion(jumpOrigin.node, jumpOrigin.types, localVariableTypes); } } /** * If we're in a try/catch block, add an edge from the specified node to the try node's pre-catch label. */ private void jumpToCatchBlock(final JoinPredecessor jumpOrigin) { final Label currentCatchLabel = catchLabels.peek(); if(currentCatchLabel != null) { jumpToLabel(jumpOrigin, currentCatchLabel); } } private void jumpToLabel(final JoinPredecessor jumpOrigin, final Label label) { jumpToLabel(jumpOrigin, label, localVariableTypes); } private void jumpToLabel(final JoinPredecessor jumpOrigin, final Label label, final Map<Symbol, LvarType> types) { getOrCreateJumpTarget(label).addOrigin(jumpOrigin, types, this); } @Override public Node leaveBlock(final Block block) { if(lc.isFunctionBody()) { if(reachable) { // reachable==true means we can reach the end of the function without an explicit return statement. We // need to insert a synthetic one then. This logic used to be in Lower.leaveBlock(), but Lower's // reachability analysis (through Terminal.isTerminal() flags) is not precise enough so // Lower$BlockLexicalContext.afterSetStatements will sometimes think the control flow terminates even // when it didn't. Example: function() { switch((z)) { default: {break; } throw x; } }. createSyntheticReturn(block); assert !reachable; } // We must calculate the return type here (and not in leaveFunctionNode) as it can affect the liveness of // the :return symbol and thus affect conversion type liveness calculations for it. calculateReturnType(); } boolean cloned = false; for(final Symbol symbol: block.getSymbols()) { if(symbol.hasSlot()) { // Invalidate the symbol when its defining block ends if (symbol.isBytecodeLocal()) { if(localVariableTypes.containsKey(symbol)) { if(!cloned) { localVariableTypes = cloneMap(localVariableTypes); cloned = true; } } invalidateSymbol(symbol); } final SymbolConversions conversions = symbolConversions.get(symbol); if(conversions != null) { // Potentially make some currently dead types live if they're needed as a source of a type // conversion at a join. conversions.calculateTypeLiveness(symbol); } if(symbol.slotCount() == 0) { // This is a local variable that is never read. It won't need a slot. symbol.setNeedsSlot(false); } } } if(reachable) { // TODO: this is totally backwards. Block should not be breakable, LabelNode should be breakable. final LabelNode labelNode = lc.getCurrentBlockLabelNode(); if(labelNode != null) { jumpToLabel(labelNode, block.getBreakLabel()); } } leaveBreakable(block); return block; } private void calculateReturnType() { // NOTE: if return type is unknown, then the function does not explicitly return a value. Such a function under // ECMAScript rules returns Undefined, which has Type.OBJECT. We might consider an optimization in the future // where we can return void functions. if(returnType.isUnknown()) { returnType = Type.OBJECT; } } private void createSyntheticReturn(final Block body) { final FunctionNode functionNode = lc.getCurrentFunction(); final long token = functionNode.getToken(); final int finish = functionNode.getFinish(); final List<Statement> statements = body.getStatements(); final int lineNumber = statements.isEmpty() ? functionNode.getLineNumber() : statements.get(statements.size() - 1).getLineNumber(); final IdentNode returnExpr; if(functionNode.isProgram()) { returnExpr = new IdentNode(token, finish, RETURN.symbolName()).setSymbol(getCompilerConstantSymbol(functionNode, RETURN)); } else { returnExpr = null; } syntheticReturn = new ReturnNode(lineNumber, token, finish, returnExpr); syntheticReturn.accept(this); } /** * Leave a breakable node. If there's a join point associated with its break label (meaning there was at least one * break statement to the end of the node), insert the join point into the flow. * @param breakable the breakable node being left. */ private void leaveBreakable(final BreakableNode breakable) { joinOnLabel(breakable.getBreakLabel()); assertTypeStackIsEmpty(); } @Override public Node leaveFunctionNode(final FunctionNode functionNode) { // Sets the return type of the function and also performs the bottom-up pass of applying type and conversion // information to nodes as well as doing the calculation on nested functions as required. FunctionNode newFunction = functionNode; final SimpleNodeVisitor applyChangesVisitor = new SimpleNodeVisitor() { private boolean inOuterFunction = true; private final Deque<JoinPredecessor> joinPredecessors = new ArrayDeque<>(); @Override protected boolean enterDefault(final Node node) { if(!inOuterFunction) { return false; } if(node instanceof JoinPredecessor) { joinPredecessors.push((JoinPredecessor)node); } return inOuterFunction; } @Override public boolean enterFunctionNode(final FunctionNode fn) { if(compiler.isOnDemandCompilation()) { // Only calculate nested function local variable types if we're doing eager compilation return false; } inOuterFunction = false; return true; } @SuppressWarnings("fallthrough") @Override public Node leaveBinaryNode(final BinaryNode binaryNode) { if(binaryNode.isComparison()) { final Expression lhs = binaryNode.lhs(); final Expression rhs = binaryNode.rhs(); final TokenType tt = binaryNode.tokenType(); switch (tt) { case EQ_STRICT: case NE_STRICT: // Specialize comparison with undefined final Expression undefinedNode = createIsUndefined(binaryNode, lhs, rhs, tt == TokenType.EQ_STRICT ? Request.IS_UNDEFINED : Request.IS_NOT_UNDEFINED); if(undefinedNode != binaryNode) { return undefinedNode; } // Specialize comparison of boolean with non-boolean if (lhs.getType().isBoolean() != rhs.getType().isBoolean()) { return new RuntimeNode(binaryNode); } // fallthrough default: if (lhs.getType().isObject() && rhs.getType().isObject()) { return new RuntimeNode(binaryNode); } } } else if(binaryNode.isOptimisticUndecidedType()) { // At this point, we can assign a static type to the optimistic binary ADD operator as now we know // the types of its operands. return binaryNode.decideType(); } return binaryNode; } @Override protected Node leaveDefault(final Node node) { if(node instanceof JoinPredecessor) { final JoinPredecessor original = joinPredecessors.pop(); assert original.getClass() == node.getClass() : original.getClass().getName() + "!=" + node.getClass().getName(); final JoinPredecessor newNode = setLocalVariableConversion(original, (JoinPredecessor)node); if (newNode instanceof LexicalContextNode) { lc.replace((LexicalContextNode)node, (LexicalContextNode)newNode); } return (Node)newNode; } return node; } @Override public Node leaveBlock(final Block block) { if(inOuterFunction && syntheticReturn != null && lc.isFunctionBody()) { final ArrayList<Statement> stmts = new ArrayList<>(block.getStatements()); stmts.add((ReturnNode)syntheticReturn.accept(this)); return block.setStatements(lc, stmts); } return super.leaveBlock(block); } @Override public Node leaveFunctionNode(final FunctionNode nestedFunctionNode) { inOuterFunction = true; final FunctionNode newNestedFunction = (FunctionNode)nestedFunctionNode.accept( new LocalVariableTypesCalculator(compiler)); lc.replace(nestedFunctionNode, newNestedFunction); return newNestedFunction; } @Override public Node leaveIdentNode(final IdentNode identNode) { final IdentNode original = (IdentNode)joinPredecessors.pop(); final Symbol symbol = identNode.getSymbol(); if(symbol == null) { assert identNode.isPropertyName(); return identNode; } else if(symbol.hasSlot()) { assert !symbol.isScope() || symbol.isParam(); // Only params can be slotted and scoped. assert original.getName().equals(identNode.getName()); final LvarType lvarType = identifierLvarTypes.remove(original); if(lvarType != null) { return setLocalVariableConversion(original, identNode.setType(lvarType.type)); } // If there's no type, then the identifier must've been in unreachable code. In that case, it can't // have assigned conversions either. assert localVariableConversions.get(original) == null; } else { assert identIsDeadAndHasNoLiveConversions(original); } return identNode; } @Override public Node leaveLiteralNode(final LiteralNode<?> literalNode) { //for e.g. ArrayLiteralNodes the initial types may have been narrowed due to the //introduction of optimistic behavior - hence ensure that all literal nodes are //reinitialized return literalNode.initialize(lc); } @Override public Node leaveRuntimeNode(final RuntimeNode runtimeNode) { final Request request = runtimeNode.getRequest(); final boolean isEqStrict = request == Request.EQ_STRICT; if(isEqStrict || request == Request.NE_STRICT) { return createIsUndefined(runtimeNode, runtimeNode.getArgs().get(0), runtimeNode.getArgs().get(1), isEqStrict ? Request.IS_UNDEFINED : Request.IS_NOT_UNDEFINED); } return runtimeNode; } @SuppressWarnings("unchecked") private <T extends JoinPredecessor> T setLocalVariableConversion(final JoinPredecessor original, final T jp) { // NOTE: can't use Map.remove() as our copy-on-write AST semantics means some nodes appear twice (in // finally blocks), so we need to be able to access conversions for them multiple times. return (T)jp.setLocalVariableConversion(lc, localVariableConversions.get(original)); } }; newFunction = newFunction.setBody(lc, (Block)newFunction.getBody().accept(applyChangesVisitor)); newFunction = newFunction.setReturnType(lc, returnType); newFunction = newFunction.setParameters(lc, newFunction.visitParameters(applyChangesVisitor)); return newFunction; } private static Expression createIsUndefined(final Expression parent, final Expression lhs, final Expression rhs, final Request request) { if (isUndefinedIdent(lhs) || isUndefinedIdent(rhs)) { return new RuntimeNode(parent, request, lhs, rhs); } return parent; } private static boolean isUndefinedIdent(final Expression expr) { return expr instanceof IdentNode && "undefined".equals(((IdentNode)expr).getName()); } private boolean identIsDeadAndHasNoLiveConversions(final IdentNode identNode) { final LocalVariableConversion conv = localVariableConversions.get(identNode); return conv == null || !conv.isLive(); } private void onAssignment(final IdentNode identNode, final LvarType type) { final Symbol symbol = identNode.getSymbol(); assert symbol != null : identNode.getName(); if(!symbol.isBytecodeLocal()) { return; } assert type != null; final LvarType finalType; if(type == LvarType.UNDEFINED && getLocalVariableType(symbol) != LvarType.UNDEFINED) { // Explicit assignment of a known undefined local variable to a local variable that is not undefined will // materialize that undefined in the assignment target. Note that assigning known undefined to known // undefined will *not* initialize the variable, e.g. "var x; var y = x;" compiles to no-op. finalType = LvarType.OBJECT; symbol.setFlag(Symbol.HAS_OBJECT_VALUE); } else { finalType = type; } setType(symbol, finalType); // Explicit assignment of an undefined value. Make sure the variable can store an object // TODO: if we communicated the fact to codegen with a flag on the IdentNode that the value was already // undefined before the assignment, we could just ignore it. In general, we could ignore an assignment if we // know that the value assigned is the same as the current value of the variable, but we'd need constant // propagation for that. setIdentifierLvarType(identNode, finalType); // For purposes of type calculation, we consider an assignment to a local variable to be followed by // the catch nodes of the current (if any) try block. This will effectively enforce that narrower // assignments to a local variable in a try block will also have to store a widened value as well. Code // within the try block will be able to keep loading the narrower value, but after the try block only // the widest value will remain live. // Rationale for this is that if there's an use for that variable in any of the catch blocks, or // following the catch blocks, they must use the widest type. // Example: /* Originally: =========== var x; try { x = 1; <-- stores into int slot for x f(x); <-- loads the int slot for x x = 3.14 <-- stores into the double slot for x f(x); <-- loads the double slot for x x = 1; <-- stores into int slot for x f(x); <-- loads the int slot for x } finally { f(x); <-- loads the double slot for x, but can be reached by a path where x is int, so we need to go back and ensure that double values are also always stored along with int values. } After correction: ================= var x; try { x = 1; <-- stores into both int and double slots for x f(x); <-- loads the int slot for x x = 3.14 <-- stores into the double slot for x f(x); <-- loads the double slot for x x = 1; <-- stores into both int and double slots for x f(x); <-- loads the int slot for x } finally { f(x); <-- loads the double slot for x } */ jumpToCatchBlock(identNode); } private void onSelfAssignment(final IdentNode identNode, final LvarType type) { final Symbol symbol = identNode.getSymbol(); assert symbol != null : identNode.getName(); if(!symbol.isBytecodeLocal()) { return; } // Self-assignment never produce either a boolean or undefined assert type != null && type != LvarType.UNDEFINED && type != LvarType.BOOLEAN; setType(symbol, type); jumpToCatchBlock(identNode); } private void resetJoinPoint(final Label label) { jumpTargets.remove(label); } private void setCompilerConstantAsObject(final FunctionNode functionNode, final CompilerConstants cc) { final Symbol symbol = getCompilerConstantSymbol(functionNode, cc); setType(symbol, LvarType.OBJECT); // never mark compiler constants as dead symbolIsUsed(symbol); } private static Symbol getCompilerConstantSymbol(final FunctionNode functionNode, final CompilerConstants cc) { return functionNode.getBody().getExistingSymbol(cc.symbolName()); } private void setConversion(final JoinPredecessor node, final Map<Symbol, LvarType> branchLvarTypes, final Map<Symbol, LvarType> joinLvarTypes) { if(node == null) { return; } if(branchLvarTypes.isEmpty() || joinLvarTypes.isEmpty()) { localVariableConversions.remove(node); } LocalVariableConversion conversion = null; if(node instanceof IdentNode) { // conversions on variable assignment in try block are special cases, as they only apply to the variable // being assigned and all other conversions should be ignored. final Symbol symbol = ((IdentNode)node).getSymbol(); conversion = createConversion(symbol, branchLvarTypes.get(symbol), joinLvarTypes, null); } else { for(final Map.Entry<Symbol, LvarType> entry: branchLvarTypes.entrySet()) { final Symbol symbol = entry.getKey(); final LvarType branchLvarType = entry.getValue(); conversion = createConversion(symbol, branchLvarType, joinLvarTypes, conversion); } } if(conversion != null) { localVariableConversions.put(node, conversion); } else { localVariableConversions.remove(node); } } private void setIdentifierLvarType(final IdentNode identNode, final LvarType type) { assert type != null; identifierLvarTypes.put(identNode, type); } /** * Marks a local variable as having a specific type from this point onward. Invoked by stores to local variables. * @param symbol the symbol representing the variable * @param type the type */ private void setType(final Symbol symbol, final LvarType type) { if(getLocalVariableTypeOrNull(symbol) == type) { return; } assert symbol.hasSlot(); assert !symbol.isGlobal(); cloneOrNewLocalVariableTypes(); localVariableTypes.put(symbol, type); } private void cloneOrNewLocalVariableTypes() { localVariableTypes = localVariableTypes.isEmpty() ? new HashMap<Symbol, LvarType>() : cloneMap(localVariableTypes); } private void invalidateSymbol(final Symbol symbol) { localVariableTypes.remove(symbol); invalidatedSymbols.add(symbol); } /** * Set a flag in the symbol marking it as needing to be able to store a value of a particular type. Every symbol for * a local variable will be assigned between 1 and 6 local variable slots for storing all types it is known to need * to store. * @param symbol the symbol */ private void symbolIsUsed(final Symbol symbol) { symbolIsUsed(symbol, getLocalVariableType(symbol)); } }