blob: ab61d37481862f331c04dcc47715718b7bc6c3ad [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 java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import jdk.nashorn.internal.codegen.types.Range;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.Assignment;
import jdk.nashorn.internal.ir.BinaryNode;
import jdk.nashorn.internal.ir.ForNode;
import jdk.nashorn.internal.ir.IdentNode;
import jdk.nashorn.internal.ir.LexicalContext;
import jdk.nashorn.internal.ir.LiteralNode;
import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
import jdk.nashorn.internal.ir.LoopNode;
import jdk.nashorn.internal.ir.Node;
import jdk.nashorn.internal.ir.RuntimeNode;
import jdk.nashorn.internal.ir.Symbol;
import jdk.nashorn.internal.ir.UnaryNode;
import jdk.nashorn.internal.ir.VarNode;
import jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
import jdk.nashorn.internal.parser.TokenType;
import jdk.nashorn.internal.runtime.DebugLogger;
/**
* Range analysis and narrowing of type where it can be proven
* that there is no spillover, e.g.
*
* function func(c) {
* var v = c & 0xfff;
* var w = c & 0xeee;
* var x = v * w;
* return x;
* }
*
* Proves that the multiplication never exceeds 24 bits and can thus be an int
*/
final class RangeAnalyzer extends NodeOperatorVisitor<LexicalContext> {
static final DebugLogger LOG = new DebugLogger("ranges");
private static final Range.Functionality RANGE = new Range.Functionality(LOG);
private final Map<LoopNode, Symbol> loopCounters = new HashMap<>();
RangeAnalyzer() {
super(new LexicalContext());
}
@Override
public boolean enterForNode(final ForNode forNode) {
//conservatively attempt to identify the loop counter. Null means that it wasn't
//properly identified and that no optimizations can be made with it - its range is
//simply unknown in that case, if it is assigned in the loop
final Symbol counter = findLoopCounter(forNode);
LOG.fine("Entering forNode " + forNode + " counter = " + counter);
if (counter != null && !assignedInLoop(forNode, counter)) {
loopCounters.put(forNode, counter);
}
return true;
}
//destination visited
private Symbol setRange(final Node dest, final Range range) {
if (range.isUnknown()) {
return null;
}
final Symbol symbol = dest.getSymbol();
assert symbol != null : dest + " " + dest.getClass() + " has no symbol";
assert symbol.getRange() != null : symbol + " has no range";
final Range symRange = RANGE.join(symbol.getRange(), range);
//anything assigned in the loop, not being the safe loop counter(s) invalidates its entire range
if (lc.inLoop() && !isLoopCounter(lc.getCurrentLoop(), symbol)) {
symbol.setRange(Range.createGenericRange());
return symbol;
}
if (!symRange.equals(symbol.getRange())) {
LOG.fine("Modify range for " + dest + " " + symbol + " from " + symbol.getRange() + " to " + symRange + " (in node = " + dest + ")" );
symbol.setRange(symRange);
}
return null;
}
@Override
public Node leaveADD(final BinaryNode node) {
setRange(node, RANGE.add(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
return node;
}
@Override
public Node leaveSUB(final BinaryNode node) {
setRange(node, RANGE.sub(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
return node;
}
@Override
public Node leaveMUL(final BinaryNode node) {
setRange(node, RANGE.mul(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
return node;
}
@Override
public Node leaveDIV(final BinaryNode node) {
setRange(node, RANGE.div(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
return node;
}
@Override
public Node leaveMOD(final BinaryNode node) {
setRange(node, RANGE.mod(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
return node;
}
@Override
public Node leaveBIT_AND(final BinaryNode node) {
setRange(node, RANGE.and(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
return node;
}
@Override
public Node leaveBIT_OR(final BinaryNode node) {
setRange(node, RANGE.or(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
return node;
}
@Override
public Node leaveBIT_XOR(final BinaryNode node) {
setRange(node, RANGE.xor(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
return node;
}
@Override
public Node leaveSAR(final BinaryNode node) {
setRange(node, RANGE.sar(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
return node;
}
@Override
public Node leaveSHL(final BinaryNode node) {
setRange(node, RANGE.shl(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
return node;
}
@Override
public Node leaveSHR(final BinaryNode node) {
setRange(node, RANGE.shr(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
return node;
}
private Node leaveCmp(final BinaryNode node) {
setRange(node, Range.createTypeRange(Type.BOOLEAN));
return node;
}
@Override
public Node leaveEQ(final BinaryNode node) {
return leaveCmp(node);
}
@Override
public Node leaveEQ_STRICT(final BinaryNode node) {
return leaveCmp(node);
}
@Override
public Node leaveNE(final BinaryNode node) {
return leaveCmp(node);
}
@Override
public Node leaveNE_STRICT(final BinaryNode node) {
return leaveCmp(node);
}
@Override
public Node leaveLT(final BinaryNode node) {
return leaveCmp(node);
}
@Override
public Node leaveLE(final BinaryNode node) {
return leaveCmp(node);
}
@Override
public Node leaveGT(final BinaryNode node) {
return leaveCmp(node);
}
@Override
public Node leaveGE(final BinaryNode node) {
return leaveCmp(node);
}
@Override
public Node leaveASSIGN(final BinaryNode node) {
Range range = node.rhs().getSymbol().getRange();
if (range.isUnknown()) {
range = Range.createGenericRange();
}
setRange(node.lhs(), range);
setRange(node, range);
return node;
}
private Node leaveSelfModifyingAssign(final BinaryNode node, final Range range) {
setRange(node.lhs(), range);
setRange(node, range);
return node;
}
private Node leaveSelfModifyingAssign(final UnaryNode node, final Range range) {
setRange(node.rhs(), range);
setRange(node, range);
return node;
}
@Override
public Node leaveASSIGN_ADD(final BinaryNode node) {
return leaveSelfModifyingAssign(node, RANGE.add(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
}
@Override
public Node leaveASSIGN_SUB(final BinaryNode node) {
return leaveSelfModifyingAssign(node, RANGE.sub(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
}
@Override
public Node leaveASSIGN_MUL(final BinaryNode node) {
return leaveSelfModifyingAssign(node, RANGE.mul(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
}
@Override
public Node leaveASSIGN_DIV(final BinaryNode node) {
return leaveSelfModifyingAssign(node, Range.createTypeRange(Type.NUMBER));
}
@Override
public Node leaveASSIGN_MOD(final BinaryNode node) {
return leaveSelfModifyingAssign(node, Range.createTypeRange(Type.NUMBER));
}
@Override
public Node leaveASSIGN_BIT_AND(final BinaryNode node) {
return leaveSelfModifyingAssign(node, RANGE.and(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
}
@Override
public Node leaveASSIGN_BIT_OR(final BinaryNode node) {
return leaveSelfModifyingAssign(node, RANGE.or(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
}
@Override
public Node leaveASSIGN_BIT_XOR(final BinaryNode node) {
return leaveSelfModifyingAssign(node, RANGE.xor(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
}
@Override
public Node leaveASSIGN_SAR(final BinaryNode node) {
return leaveSelfModifyingAssign(node, RANGE.sar(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
}
@Override
public Node leaveASSIGN_SHR(final BinaryNode node) {
return leaveSelfModifyingAssign(node, RANGE.shr(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
}
@Override
public Node leaveASSIGN_SHL(final BinaryNode node) {
return leaveSelfModifyingAssign(node, RANGE.shl(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
}
@Override
public Node leaveDECINC(final UnaryNode node) {
switch (node.tokenType()) {
case DECPREFIX:
case DECPOSTFIX:
return leaveSelfModifyingAssign(node, RANGE.sub(node.rhs().getSymbol().getRange(), Range.createRange(1)));
case INCPREFIX:
case INCPOSTFIX:
return leaveSelfModifyingAssign(node, RANGE.add(node.rhs().getSymbol().getRange(), Range.createRange(1)));
default:
assert false;
return node;
}
}
@Override
public Node leaveADD(final UnaryNode node) {
Range range = node.rhs().getSymbol().getRange();
if (!range.getType().isNumeric()) {
range = Range.createTypeRange(Type.NUMBER);
}
setRange(node, range);
return node;
}
@Override
public Node leaveBIT_NOT(final UnaryNode node) {
setRange(node, Range.createTypeRange(Type.INT));
return node;
}
@Override
public Node leaveNOT(final UnaryNode node) {
setRange(node, Range.createTypeRange(Type.BOOLEAN));
return node;
}
@Override
public Node leaveSUB(final UnaryNode node) {
setRange(node, RANGE.neg(node.rhs().getSymbol().getRange()));
return node;
}
@Override
public Node leaveVarNode(final VarNode node) {
if (node.isAssignment()) {
Range range = node.getInit().getSymbol().getRange();
range = range.isUnknown() ? Range.createGenericRange() : range;
setRange(node.getName(), range);
setRange(node, range);
}
return node;
}
@SuppressWarnings("rawtypes")
@Override
public boolean enterLiteralNode(final LiteralNode node) {
// ignore array literals
return !(node instanceof ArrayLiteralNode);
}
@Override
public Node leaveLiteralNode(@SuppressWarnings("rawtypes") final LiteralNode node) {
if (node.getType().isInteger()) {
setRange(node, Range.createRange(node.getInt32()));
} else if (node.getType().isNumber()) {
setRange(node, Range.createRange(node.getNumber()));
} else if (node.getType().isLong()) {
setRange(node, Range.createRange(node.getLong()));
} else if (node.getType().isBoolean()) {
setRange(node, Range.createTypeRange(Type.BOOLEAN));
} else {
setRange(node, Range.createGenericRange());
}
return node;
}
@Override
public boolean enterRuntimeNode(final RuntimeNode node) {
// a runtime node that cannot be specialized is no point entering
return node.getRequest().canSpecialize();
}
/**
* Check whether a symbol is unsafely assigned in a loop - i.e. repeteadly assigned and
* not being identified as the loop counter. That means we don't really know anything
* about its range.
* @param loopNode loop node
* @param symbol symbol
* @return true if assigned in loop
*/
// TODO - this currently checks for nodes only - needs to be augmented for while nodes
// assignment analysis is also very conservative
private static boolean assignedInLoop(final LoopNode loopNode, final Symbol symbol) {
final HashSet<Node> skip = new HashSet<>();
final HashSet<Node> assignmentsInLoop = new HashSet<>();
loopNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
private boolean assigns(final Node node, final Symbol s) {
return node.isAssignment() && ((Assignment<?>)node).getAssignmentDest().getSymbol() == s;
}
@Override
public boolean enterForNode(final ForNode forNode) {
if (forNode.getInit() != null) {
skip.add(forNode.getInit());
}
if (forNode.getModify() != null) {
skip.add(forNode.getModify());
}
return true;
}
@Override
public Node leaveDefault(final Node node) {
//if this is an assignment to symbol
if (!skip.contains(node) && assigns(node, symbol)) {
assignmentsInLoop.add(node);
}
return node;
}
});
return !assignmentsInLoop.isEmpty();
}
/**
* Check for a loop counter. This is currently quite conservative, in that it only handles
* x <= counter and x < counter.
*
* @param node loop node to check
* @return
*/
private static Symbol findLoopCounter(final LoopNode node) {
final Node test = node.getTest();
if (test != null && test.isComparison()) {
final BinaryNode binaryNode = (BinaryNode)test;
final Node lhs = binaryNode.lhs();
final Node rhs = binaryNode.rhs();
//detect ident cmp int_literal
if (lhs instanceof IdentNode && rhs instanceof LiteralNode && ((LiteralNode<?>)rhs).getType().isInteger()) {
final Symbol symbol = lhs.getSymbol();
final int margin = ((LiteralNode<?>)rhs).getInt32();
final TokenType op = test.tokenType();
switch (op) {
case LT:
case LE:
symbol.setRange(RANGE.join(symbol.getRange(), Range.createRange(op == TokenType.LT ? margin - 1 : margin)));
return symbol;
case GT:
case GE:
//setRange(lhs, Range.createRange(op == TokenType.GT ? margin + 1 : margin));
//return symbol;
default:
break;
}
}
}
return null;
}
private boolean isLoopCounter(final LoopNode loopNode, final Symbol symbol) {
//this only works if loop nodes aren't replaced by other ones during this transform, but they are not
return loopCounters.get(loopNode) == symbol;
}
}