blob: 63bafa717b0b4c603dcca293c53ae01f92b13b30 [file] [log] [blame]
/*
* 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.isForIn()) {
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.getException();
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));
}
}