8006069: Range analysis first iteration, runtime specializations
Reviewed-by: jlaskey, sundar
--- a/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java Fri May 17 16:44:22 2013 -0300
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java Mon May 20 16:38:38 2013 +0200
@@ -11,13 +11,21 @@
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
import java.util.EnumSet;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
+
+import jdk.nashorn.internal.codegen.types.Range;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.Block;
import jdk.nashorn.internal.ir.CallNode;
import jdk.nashorn.internal.ir.FunctionNode;
+import jdk.nashorn.internal.ir.ReturnNode;
+import jdk.nashorn.internal.ir.Symbol;
import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
import jdk.nashorn.internal.ir.LexicalContext;
import jdk.nashorn.internal.ir.Node;
@@ -174,7 +182,7 @@
FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
final TemporarySymbols ts = compiler.getTemporarySymbols();
final FunctionNode newFunctionNode = (FunctionNode)enterAttr(fn, ts).accept(new Attr(ts));
- if(compiler.getEnv()._print_mem_usage) {
+ if (compiler.getEnv()._print_mem_usage) {
Compiler.LOG.info("Attr temporary symbol count: " + ts.getTotalSymbolCount());
}
return newFunctionNode;
@@ -208,6 +216,89 @@
},
/*
+ * Range analysis
+ * Conservatively prove that certain variables can be narrower than
+ * the most generic number type
+ */
+ RANGE_ANALYSIS_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR)) {
+ @Override
+ FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
+ if (!compiler.getEnv()._range_analysis) {
+ return fn;
+ }
+
+ FunctionNode newFunctionNode = (FunctionNode)fn.accept(new RangeAnalyzer());
+ final List<ReturnNode> returns = new ArrayList<>();
+
+ newFunctionNode = (FunctionNode)newFunctionNode.accept(new NodeVisitor() {
+ private final Deque<ArrayList<ReturnNode>> returnStack = new ArrayDeque<>();
+
+ @Override
+ public boolean enterFunctionNode(final FunctionNode functionNode) {
+ returnStack.push(new ArrayList<ReturnNode>());
+ return true;
+ }
+
+ @Override
+ public Node leaveFunctionNode(final FunctionNode functionNode) {
+ Type returnType = Type.UNKNOWN;
+ for (final ReturnNode ret : returnStack.pop()) {
+ if (ret.getExpression() == null) {
+ returnType = Type.OBJECT;
+ break;
+ }
+ returnType = Type.widest(returnType, ret.getExpression().getType());
+ }
+ return functionNode.setReturnType(getLexicalContext(), returnType);
+ }
+
+ @Override
+ public Node leaveReturnNode(final ReturnNode returnNode) {
+ final ReturnNode result = (ReturnNode)leaveDefault(returnNode);
+ returns.add(result);
+ return result;
+ }
+
+ @Override
+ public Node leaveDefault(final Node node) {
+ final Symbol symbol = node.getSymbol();
+ if (symbol != null) {
+ final Range range = symbol.getRange();
+ final Type symbolType = symbol.getSymbolType();
+ if (!symbolType.isNumeric()) {
+ return node;
+ }
+ final Type rangeType = range.getType();
+ if (!Type.areEquivalent(symbolType, rangeType) && Type.widest(symbolType, rangeType) == symbolType) { //we can narrow range
+ RangeAnalyzer.LOG.info("[", getLexicalContext().getCurrentFunction().getName(), "] ", symbol, " can be ", range.getType(), " ", symbol.getRange());
+ return node.setSymbol(getLexicalContext(), symbol.setTypeOverrideShared(range.getType(), compiler.getTemporarySymbols()));
+ }
+ }
+ return node;
+ }
+ });
+
+ Type returnType = Type.UNKNOWN;
+ for (final ReturnNode node : returns) {
+ if (node.getExpression() != null) {
+ returnType = Type.widest(returnType, node.getExpression().getType());
+ } else {
+ returnType = Type.OBJECT;
+ break;
+ }
+ }
+
+ return newFunctionNode.setReturnType(null, returnType);
+ }
+
+ @Override
+ public String toString() {
+ return "[Range Analysis]";
+ }
+ },
+
+
+ /*
* Splitter Split the AST into several compile units based on a size
* heuristic Splitter needs attributed AST for weight calculations (e.g. is
* a + b a ScriptRuntime.ADD with call overhead or a dadd with much less).
@@ -218,7 +309,6 @@
FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
final CompileUnit outermostCompileUnit = compiler.addCompileUnit(compiler.firstCompileUnitName());
-// assert fn.isProgram() ;
final FunctionNode newFunctionNode = new Splitter(compiler, fn, outermostCompileUnit).split(fn);
assert newFunctionNode.getCompileUnit() == outermostCompileUnit : "fn.compileUnit (" + newFunctionNode.getCompileUnit() + ") != " + outermostCompileUnit;
--- a/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java Fri May 17 16:44:22 2013 -0300
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java Mon May 20 16:38:38 2013 +0200
@@ -99,7 +99,7 @@
private boolean strict;
- private CodeInstaller<ScriptEnvironment> installer;
+ private final CodeInstaller<ScriptEnvironment> installer;
private final TemporarySymbols temporarySymbols = new TemporarySymbols();
@@ -219,6 +219,7 @@
CompilationPhase.CONSTANT_FOLDING_PHASE,
CompilationPhase.LOWERING_PHASE,
CompilationPhase.ATTRIBUTION_PHASE,
+ CompilationPhase.RANGE_ANALYSIS_PHASE,
CompilationPhase.SPLITTING_PHASE,
CompilationPhase.TYPE_FINALIZATION_PHASE,
CompilationPhase.BYTECODE_GENERATION_PHASE);
@@ -384,6 +385,8 @@
if (info) {
final StringBuilder sb = new StringBuilder();
sb.append("Compile job for '").
+ append(newFunctionNode.getSource()).
+ append(':').
append(newFunctionNode.getName()).
append("' finished");
@@ -487,7 +490,7 @@
}
if (sb != null) {
- LOG.info(sb);
+ LOG.fine(sb);
}
return rootClass;
--- a/nashorn/src/jdk/nashorn/internal/codegen/CompilerConstants.java Fri May 17 16:44:22 2013 -0300
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CompilerConstants.java Mon May 20 16:38:38 2013 +0200
@@ -262,7 +262,7 @@
* @return the internal descriptor for this type
*/
public static String typeDescriptor(final Class<?> clazz) {
- return Type.getDescriptor(clazz);
+ return Type.typeFor(clazz).getDescriptor();
}
/**
--- a/nashorn/src/jdk/nashorn/internal/codegen/MethodEmitter.java Fri May 17 16:44:22 2013 -0300
+++ b/nashorn/src/jdk/nashorn/internal/codegen/MethodEmitter.java Mon May 20 16:38:38 2013 +0200
@@ -2081,7 +2081,9 @@
* @param args debug information to print
*/
private void debug(final Object... args) {
- debug(30, args);
+ if (DEBUG) {
+ debug(30, args);
+ }
}
/**
@@ -2091,7 +2093,9 @@
* @param args debug information to print
*/
private void debug_label(final Object... args) {
- debug(26, args);
+ if (DEBUG) {
+ debug(22, args);
+ }
}
private void debug(final int padConstant, final Object... args) {
@@ -2164,7 +2168,6 @@
new Throwable().printStackTrace(LOG.getOutputStream());
}
}
-
}
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/src/jdk/nashorn/internal/codegen/RangeAnalyzer.java Mon May 20 16:38:38 2013 +0200
@@ -0,0 +1,474 @@
+/*
+ * 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.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 {
+ 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() {
+ }
+
+ @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 (getLexicalContext().inLoop() && !isLoopCounter(getLexicalContext().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() {
+ 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;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/src/jdk/nashorn/internal/codegen/types/Range.java Mon May 20 16:38:38 2013 +0200
@@ -0,0 +1,705 @@
+/*
+ * 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.types;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import jdk.nashorn.internal.runtime.DebugLogger;
+import jdk.nashorn.internal.runtime.JSType;
+
+/**
+ * Represents the value range of a symbol.
+ */
+public abstract class Range {
+
+ private static final Range GENERIC_RANGE = new Range() {
+ @Override
+ public Type getType() {
+ return Type.OBJECT;
+ }
+ };
+
+ private static final Range NUMBER_RANGE = new Range() {
+ @Override
+ public Type getType() {
+ return Type.NUMBER;
+ }
+ };
+
+ private static final Range UNKNOWN_RANGE = new Range() {
+ @Override
+ public Type getType() {
+ return Type.UNKNOWN;
+ }
+
+ @Override
+ public boolean isUnknown() {
+ return true;
+ }
+ };
+
+ private static class IntegerRange extends Range {
+ private final long min;
+ private final long max;
+ private final Type type;
+
+ private IntegerRange(final long min, final long max) {
+ assert min <= max;
+ this.min = min;
+ this.max = max;
+ this.type = typeFromRange(min, max);
+ }
+
+ private static Type typeFromRange(final long from, final long to) {
+ if (from >= Integer.MIN_VALUE && to <= Integer.MAX_VALUE) {
+ return Type.INT;
+ }
+ return Type.LONG;
+ }
+
+ @Override
+ public Type getType() {
+ return type;
+ }
+
+ public long getMin() {
+ return min;
+ }
+
+ public long getMax() {
+ return max;
+ }
+
+ @Override
+ public boolean isIntegerConst() {
+ return getMin() == getMax();
+ }
+
+ private long getBitMask() {
+ if (min == max) {
+ return min;
+ }
+
+ if (min < 0) {
+ return ~0L;
+ }
+
+ long mask = 1;
+ while (mask < max) {
+ mask = (mask << 1) | 1;
+ }
+ return mask;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj instanceof IntegerRange) {
+ final IntegerRange other = (IntegerRange)obj;
+ return this.type == other.type && this.min == other.min && this.max == other.max;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Long.hashCode(min) ^ Long.hashCode(max);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "[" + min +", " + max + "]";
+ }
+ }
+
+ /**
+ * Get narrowest type for this range
+ * @return type
+ */
+ public abstract Type getType();
+
+ /**
+ * Is this range unknown
+ * @return true if unknown
+ */
+ public boolean isUnknown() {
+ return false;
+ }
+
+ /**
+ * Check if an integer is enough to span this range
+ * @return true if integer is enough
+ */
+ public boolean isIntegerType() {
+ return this instanceof IntegerRange;
+ }
+
+ /**
+ * Check if an integer is enough to span this range
+ * @return true if integer is enough
+ */
+ public boolean isIntegerConst() {
+ return false;
+ }
+
+ /**
+ * Create an unknown range - this is most likely a singleton object
+ * and it represents "we have no known range information"
+ * @return the range
+ */
+ public static Range createUnknownRange() {
+ return UNKNOWN_RANGE;
+ }
+
+ /**
+ * Create a constant range: [value, value]
+ * @param value value
+ * @return the range
+ */
+ public static Range createRange(final int value) {
+ return createIntegerRange(value, value);
+ }
+
+ /**
+ * Create a constant range: [value, value]
+ * @param value value
+ * @return the range
+ */
+ public static Range createRange(final long value) {
+ return createIntegerRange(value, value);
+ }
+
+ /**
+ * Create a constant range: [value, value]
+ * @param value value
+ * @return the range
+ */
+ public static Range createRange(final double value) {
+ if (isRepresentableAsLong(value)) {
+ return createIntegerRange((long) value, (long) value);
+ }
+ return createNumberRange();
+ }
+
+ /**
+ * Create a constant range: [value, value]
+ * @param value value
+ * @return the range
+ */
+ public static Range createRange(final Object value) {
+ if (value instanceof Integer) {
+ return createRange((int)value);
+ } else if (value instanceof Long) {
+ return createRange((long)value);
+ } else if (value instanceof Double) {
+ return createRange((double)value);
+ }
+
+ return createGenericRange();
+ }
+
+ /**
+ * Create a generic range - object symbol that carries no range
+ * information
+ * @return the range
+ */
+ public static Range createGenericRange() {
+ return GENERIC_RANGE;
+ }
+
+ /**
+ * Create a number range - number symbol that carries no range
+ * information
+ * @return the range
+ */
+ public static Range createNumberRange() {
+ return NUMBER_RANGE;
+ }
+
+ /**
+ * Create an integer range [min, max]
+ * @param min minimum value, inclusive
+ * @param max maximum value, inclusive
+ * @return the range
+ */
+ public static IntegerRange createIntegerRange(final long min, final long max) {
+ return new IntegerRange(min, max);
+ }
+
+ /**
+ * Create an integer range of maximum type width for the given type
+ * @param type the type
+ * @return the range
+ */
+ public static IntegerRange createIntegerRange(final Type type) {
+ assert type.isNumeric() && !type.isNumber();
+ final long min;
+ final long max;
+ if (type.isInteger()) {
+ min = Integer.MIN_VALUE;
+ max = Integer.MAX_VALUE;
+ } else if (type.isLong()) {
+ min = Long.MIN_VALUE;
+ max = Long.MAX_VALUE;
+ } else {
+ throw new AssertionError(); //type incompatible with integer range
+ }
+ return new IntegerRange(min, max);
+ }
+
+ /**
+ * Create an range of maximum type width for the given type
+ * @param type the type
+ * @return the range
+ */
+ public static Range createTypeRange(final Type type) {
+ if (type.isNumber()) {
+ return createNumberRange();
+ } else if (type.isNumeric()) {
+ return createIntegerRange(type);
+ } else {
+ return createGenericRange();
+ }
+ }
+
+ // check that add doesn't overflow
+ private static boolean checkAdd(final long a, final long b) {
+ final long result = a + b;
+ return ((a ^ result) & (b ^ result)) >= 0;
+ }
+
+ // check that sub doesn't overflow
+ private static boolean checkSub(final long a, final long b) {
+ final long result = a - b;
+ return ((a ^ result) & (b ^ result)) >= 0;
+ }
+
+ private static boolean checkMul(final long a, final long b) {
+ // TODO correct overflow check
+ return a >= Integer.MIN_VALUE && a <= Integer.MAX_VALUE && b >= Integer.MIN_VALUE && b <= Integer.MAX_VALUE;
+ }
+
+ /**
+ * The range functionality class responsible for merging ranges and drawing
+ * range conclusions from operations executed
+ */
+ public static class Functionality {
+ /** logger */
+ protected final DebugLogger log;
+
+ /**
+ * Constructor
+ * @param log logger
+ */
+ public Functionality(final DebugLogger log) {
+ this.log = log;
+ }
+
+ /**
+ * Join two ranges
+ * @param a first range
+ * @param b second range
+ * @return the joined range
+ */
+ public Range join(final Range a, final Range b) {
+ if (a.equals(b)) {
+ return a;
+ }
+
+ Type joinedType = a.getType();
+ if (a.getType() != b.getType()) {
+ if (a.isUnknown()) {
+ return b;
+ }
+ if (b.isUnknown()) {
+ return a;
+ }
+
+ joinedType = Type.widest(a.getType(), b.getType());
+ }
+
+ if (joinedType.isInteger() || joinedType.isLong()) {
+ return createIntegerRange(
+ Math.min(((IntegerRange) a).getMin(), ((IntegerRange) b).getMin()),
+ Math.max(((IntegerRange) a).getMax(), ((IntegerRange) b).getMax()));
+ }
+
+ return createTypeRange(joinedType);
+ }
+
+ /**
+ * Add operation
+ * @param a range of first symbol to be added
+ * @param b range of second symbol to be added
+ * @return resulting range representing the value range after add
+ */
+ public Range add(final Range a, final Range b) {
+ if (a.isIntegerType() && b.isIntegerType()) {
+ final IntegerRange lhs = (IntegerRange)a;
+ final IntegerRange rhs = (IntegerRange)b;
+ if (checkAdd(lhs.getMin(), rhs.getMin()) && checkAdd(lhs.getMax(), rhs.getMax())) {
+ return createIntegerRange(lhs.getMin() + rhs.getMin(), lhs.getMax() + rhs.getMax());
+ }
+ }
+
+ if (a.getType().isNumeric() && b.getType().isNumeric()) {
+ return createNumberRange();
+ }
+
+ return createGenericRange();
+ }
+
+ /**
+ * Sub operation
+ * @param a range of first symbol to be subtracted
+ * @param b range of second symbol to be subtracted
+ * @return resulting range representing the value range after subtraction
+ */
+ public Range sub(final Range a, final Range b) {
+ if (a.isIntegerType() && b.isIntegerType()) {
+ final IntegerRange lhs = (IntegerRange)a;
+ final IntegerRange rhs = (IntegerRange)b;
+ if (checkSub(lhs.getMin(), rhs.getMax()) && checkSub(lhs.getMax(), rhs.getMin())) {
+ return createIntegerRange(lhs.getMin() - rhs.getMax(), lhs.getMax() - rhs.getMin());
+ }
+ }
+
+ if (a.getType().isNumeric() && b.getType().isNumeric()) {
+ return createNumberRange();
+ }
+
+ return createGenericRange();
+ }
+
+ /**
+ * Mul operation
+ * @param a range of first symbol to be multiplied
+ * @param b range of second symbol to be multiplied
+ * @return resulting range representing the value range after multiplication
+ */
+ public Range mul(final Range a, final Range b) {
+ if (a.isIntegerType() && b.isIntegerType()) {
+ final IntegerRange lhs = (IntegerRange)a;
+ final IntegerRange rhs = (IntegerRange)b;
+
+ //ensure that nothing ever overflows or underflows
+ if (checkMul(lhs.getMin(), rhs.getMin()) &&
+ checkMul(lhs.getMax(), rhs.getMax()) &&
+ checkMul(lhs.getMin(), rhs.getMax()) &&
+ checkMul(lhs.getMax(), rhs.getMin())) {
+
+ final List<Long> results =
+ Arrays.asList(
+ lhs.getMin() * rhs.getMin(),
+ lhs.getMin() * rhs.getMax(),
+ lhs.getMax() * rhs.getMin(),
+ lhs.getMax() * rhs.getMax());
+ return createIntegerRange(Collections.min(results), Collections.max(results));
+ }
+ }
+
+ if (a.getType().isNumeric() && b.getType().isNumeric()) {
+ return createNumberRange();
+ }
+
+ return createGenericRange();
+ }
+
+ /**
+ * Neg operation
+ * @param a range of value symbol to be negated
+ * @return resulting range representing the value range after neg
+ */
+ public Range neg(final Range a) {
+ if (a.isIntegerType()) {
+ final IntegerRange rhs = (IntegerRange)a;
+ if (rhs.getMin() != Long.MIN_VALUE && rhs.getMax() != Long.MIN_VALUE) {
+ return createIntegerRange(-rhs.getMax(), -rhs.getMin());
+ }
+ }
+
+ if (a.getType().isNumeric()) {
+ return createNumberRange();
+ }
+
+ return createGenericRange();
+ }
+
+ /**
+ * Bitwise and operation
+ * @param a range of first symbol to be and:ed
+ * @param b range of second symbol to be and:ed
+ * @return resulting range representing the value range after and
+ */
+ public Range and(final Range a, final Range b) {
+ if (a.isIntegerType() && b.isIntegerType()) {
+ final int resultMask = (int) (((IntegerRange)a).getBitMask() & ((IntegerRange)b).getBitMask());
+ if (resultMask >= 0) {
+ return createIntegerRange(0, resultMask);
+ }
+ } else if (a.isUnknown() && b.isIntegerType()) {
+ final long operandMask = ((IntegerRange)b).getBitMask();
+ if (operandMask >= 0) {
+ return createIntegerRange(0, operandMask);
+ }
+ } else if (a.isIntegerType() && b.isUnknown()) {
+ final long operandMask = ((IntegerRange)a).getBitMask();
+ if (operandMask >= 0) {
+ return createIntegerRange(0, operandMask);
+ }
+ }
+
+ return createTypeRange(Type.INT);
+ }
+
+ /**
+ * Bitwise or operation
+ * @param a range of first symbol to be or:ed
+ * @param b range of second symbol to be or:ed
+ * @return resulting range representing the value range after or
+ */
+ public Range or(final Range a, final Range b) {
+ if (a.isIntegerType() && b.isIntegerType()) {
+ final int resultMask = (int)(((IntegerRange)a).getBitMask() | ((IntegerRange)b).getBitMask());
+ if (resultMask >= 0) {
+ return createIntegerRange(0, resultMask);
+ }
+ }
+
+ return createTypeRange(Type.INT);
+ }
+
+ /**
+ * Bitwise xor operation
+ * @param a range of first symbol to be xor:ed
+ * @param b range of second symbol to be xor:ed
+ * @return resulting range representing the value range after and
+ */
+ public Range xor(final Range a, final Range b) {
+ if (a.isIntegerConst() && b.isIntegerConst()) {
+ return createRange(((IntegerRange)a).getMin() ^ ((IntegerRange)b).getMin());
+ }
+
+ if (a.isIntegerType() && b.isIntegerType()) {
+ final int resultMask = (int)(((IntegerRange)a).getBitMask() | ((IntegerRange)b).getBitMask());
+ if (resultMask >= 0) {
+ return createIntegerRange(0, createIntegerRange(0, resultMask).getBitMask());
+ }
+ }
+ return createTypeRange(Type.INT);
+ }
+
+ /**
+ * Bitwise shl operation
+ * @param a range of first symbol to be shl:ed
+ * @param b range of second symbol to be shl:ed
+ * @return resulting range representing the value range after shl
+ */
+ public Range shl(final Range a, final Range b) {
+ if (b.isIntegerType() && b.isIntegerConst()) {
+ final IntegerRange left = (IntegerRange)(a.isIntegerType() ? a : createTypeRange(Type.INT));
+ final int shift = (int)((IntegerRange) b).getMin() & 0x1f;
+ final int min = (int)left.getMin() << shift;
+ final int max = (int)left.getMax() << shift;
+ if (min >> shift == left.getMin() && max >> shift == left.getMax()) {
+ return createIntegerRange(min, max);
+ }
+ }
+
+ return createTypeRange(Type.INT);
+ }
+
+ /**
+ * Bitwise shr operation
+ * @param a range of first symbol to be shr:ed
+ * @param b range of second symbol to be shr:ed
+ * @return resulting range representing the value range after shr
+ */
+ public Range shr(final Range a, final Range b) {
+ if (b.isIntegerType() && b.isIntegerConst()) {
+ final long shift = ((IntegerRange) b).getMin() & 0x1f;
+ final IntegerRange left = (IntegerRange)(a.isIntegerType() ? a : createTypeRange(Type.INT));
+ if (left.getMin() >= 0) {
+ long min = left.getMin() >>> shift;
+ long max = left.getMax() >>> shift;
+ return createIntegerRange(min, max);
+ } else if (shift >= 1) {
+ return createIntegerRange(0, JSType.MAX_UINT >>> shift);
+ }
+ }
+
+ return createTypeRange(Type.INT);
+ }
+
+ /**
+ * Bitwise sar operation
+ * @param a range of first symbol to be sar:ed
+ * @param b range of second symbol to be sar:ed
+ * @return resulting range representing the value range after sar
+ */
+ public Range sar(final Range a, final Range b) {
+ if (b.isIntegerType() && b.isIntegerConst()) {
+ final IntegerRange left = (IntegerRange)(a.isIntegerType() ? a : createTypeRange(Type.INT));
+ final long shift = ((IntegerRange) b).getMin() & 0x1f;
+ final long min = left.getMin() >> shift;
+ final long max = left.getMax() >> shift;
+ return createIntegerRange(min, max);
+ }
+
+ return createTypeRange(Type.INT);
+ }
+
+ /**
+ * Modulo operation
+ * @param a range of first symbol to the mod operation
+ * @param b range of second symbol to be mod operation
+ * @return resulting range representing the value range after mod
+ */
+ public Range mod(final Range a, final Range b) {
+ if (a.isIntegerType() && b.isIntegerType()) {
+ final IntegerRange rhs = (IntegerRange) b;
+ if (rhs.getMin() > 0 || rhs.getMax() < 0) { // divisor range must not include 0
+ final long absmax = Math.max(Math.abs(rhs.getMin()), Math.abs(rhs.getMax())) - 1;
+ return createIntegerRange(rhs.getMin() > 0 ? 0 : -absmax, rhs.getMax() < 0 ? 0 : +absmax);
+ }
+ }
+ return createTypeRange(Type.NUMBER);
+ }
+
+ /**
+ * Division operation
+ * @param a range of first symbol to the division
+ * @param b range of second symbol to be division
+ * @return resulting range representing the value range after division
+ */
+ public Range div(final Range a, final Range b) {
+ // TODO
+ return createTypeRange(Type.NUMBER);
+ }
+ }
+
+ /**
+ * Simple trace functionality that will log range creation
+ */
+ public static class TraceFunctionality extends Functionality {
+ TraceFunctionality(final DebugLogger log) {
+ super(log);
+ }
+
+ private Range trace(final Range result, final String operation, final Range... operands) {
+ log.fine("range::" + operation + Arrays.toString(operands) + " => " + result);
+ return result;
+ }
+
+ @Override
+ public Range join(final Range a, final Range b) {
+ final Range result = super.join(a, b);
+ if (!a.equals(b)) {
+ trace(result, "join", a, b);
+ }
+ return result;
+ }
+
+ @Override
+ public Range add(final Range a, final Range b) {
+ return trace(super.add(a, b), "add", a, b);
+ }
+
+ @Override
+ public Range sub(final Range a, final Range b) {
+ return trace(super.sub(a, b), "sub", a, b);
+ }
+
+ @Override
+ public Range mul(final Range a, final Range b) {
+ return trace(super.mul(a, b), "mul", a, b);
+ }
+
+ @Override
+ public Range neg(final Range a) {
+ return trace(super.neg(a), "neg", a);
+ }
+
+ @Override
+ public Range and(final Range a, final Range b) {
+ return trace(super.and(a, b), "and", a, b);
+ }
+
+ @Override
+ public Range or(final Range a, final Range b) {
+ return trace(super.or(a, b), "or", a, b);
+ }
+
+ @Override
+ public Range xor(final Range a, final Range b) {
+ return trace(super.xor(a, b), "xor", a, b);
+ }
+
+ @Override
+ public Range shl(final Range a, final Range b) {
+ return trace(super.shl(a, b), "shl", a, b);
+ }
+
+ @Override
+ public Range shr(final Range a, final Range b) {
+ return trace(super.shr(a, b), "shr", a, b);
+ }
+
+ @Override
+ public Range sar(final Range a, final Range b) {
+ return trace(super.sar(a, b), "sar", a, b);
+ }
+
+ @Override
+ public Range mod(final Range a, final Range b) {
+ return trace(super.mod(a, b), "mod", a, b);
+ }
+
+ @Override
+ public Range div(final Range a, final Range b) {
+ return trace(super.div(a, b), "div", a, b);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(getType());
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean isRepresentableAsInt(final double number) {
+ return (int)number == number && !isNegativeZero(number);
+ }
+
+ private static boolean isRepresentableAsLong(final double number) {
+ return (long)number == number && !isNegativeZero(number);
+ }
+
+ private static boolean isNegativeZero(final double number) {
+ return Double.doubleToLongBits(number) == Double.doubleToLongBits(-0.0);
+ }
+}
--- a/nashorn/src/jdk/nashorn/internal/codegen/types/Type.java Fri May 17 16:44:22 2013 -0300
+++ b/nashorn/src/jdk/nashorn/internal/codegen/types/Type.java Mon May 20 16:38:38 2013 +0200
@@ -106,23 +106,13 @@
Type(final String name, final Class<?> clazz, final int weight, final int slots) {
this.name = name;
this.clazz = clazz;
- this.descriptor = Type.getDescriptor(clazz);
+ this.descriptor = jdk.internal.org.objectweb.asm.Type.getDescriptor(clazz);
this.weight = weight;
assert weight >= MIN_WEIGHT && weight <= MAX_WEIGHT : "illegal type weight: " + weight;
this.slots = slots;
}
/**
- * Return an internal descriptor for a type
- *
- * @param type the type
- * @return descriptor string
- */
- public static String getDescriptor(final Class<?> type) {
- return jdk.internal.org.objectweb.asm.Type.getDescriptor(type);
- }
-
- /**
* Get the weight of this type - use this e.g. for sorting method descriptors
* @return the weight
*/
--- a/nashorn/src/jdk/nashorn/internal/ir/BinaryNode.java Fri May 17 16:44:22 2013 -0300
+++ b/nashorn/src/jdk/nashorn/internal/ir/BinaryNode.java Mon May 20 16:38:38 2013 +0200
@@ -59,6 +59,23 @@
this.rhs = rhs;
}
+ @Override
+ public boolean isComparison() {
+ switch (tokenType()) {
+ case EQ:
+ case EQ_STRICT:
+ case NE:
+ case NE_STRICT:
+ case LE:
+ case LT:
+ case GE:
+ case GT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
/**
* Return the widest possible type for this operation. This is used for compile time
* static type inference
--- a/nashorn/src/jdk/nashorn/internal/ir/FunctionNode.java Fri May 17 16:44:22 2013 -0300
+++ b/nashorn/src/jdk/nashorn/internal/ir/FunctionNode.java Mon May 20 16:38:38 2013 +0200
@@ -340,7 +340,7 @@
* @return true if specialization is possible
*/
public boolean canSpecialize() {
- return getFlag(CAN_SPECIALIZE);
+ return snapshot != null && getFlag(CAN_SPECIALIZE);
}
/**
--- a/nashorn/src/jdk/nashorn/internal/ir/LexicalContext.java Fri May 17 16:44:22 2013 -0300
+++ b/nashorn/src/jdk/nashorn/internal/ir/LexicalContext.java Mon May 20 16:38:38 2013 +0200
@@ -440,6 +440,23 @@
}
/**
+ * Check whether the lexical context is currently inside a loop
+ * @return true if inside a loop
+ */
+ public boolean inLoop() {
+ return getCurrentLoop() != null;
+ }
+
+ /**
+ * Returns the loop header of the current loop, or null if not inside a loop
+ * @return loop header
+ */
+ public LoopNode getCurrentLoop() {
+ final Iterator<LoopNode> iter = new NodeIterator<>(LoopNode.class, getCurrentFunction());
+ return iter.hasNext() ? iter.next() : null;
+ }
+
+ /**
* Find the breakable node corresponding to this label.
* @param label label to search for, if null the closest breakable node will be returned unconditionally, e.g. a while loop with no label
* @return closest breakable node
@@ -461,8 +478,7 @@
}
private LoopNode getContinueTo() {
- final Iterator<LoopNode> iter = new NodeIterator<>(LoopNode.class, getCurrentFunction());
- return iter.hasNext() ? iter.next() : null;
+ return getCurrentLoop();
}
/**
--- a/nashorn/src/jdk/nashorn/internal/ir/Node.java Fri May 17 16:44:22 2013 -0300
+++ b/nashorn/src/jdk/nashorn/internal/ir/Node.java Mon May 20 16:38:38 2013 +0200
@@ -153,6 +153,14 @@
}
/**
+ * Returns true if this node represents a comparison operator
+ * @return true if comparison
+ */
+ public boolean isComparison() {
+ return false;
+ }
+
+ /**
* For reference copies - ensure that labels in the copy node are unique
* using an appropriate copy constructor
* @param lc lexical context
--- a/nashorn/src/jdk/nashorn/internal/ir/Symbol.java Fri May 17 16:44:22 2013 -0300
+++ b/nashorn/src/jdk/nashorn/internal/ir/Symbol.java Mon May 20 16:38:38 2013 +0200
@@ -29,6 +29,8 @@
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
+
+import jdk.nashorn.internal.codegen.types.Range;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.runtime.Context;
import jdk.nashorn.internal.runtime.Debug;
@@ -89,6 +91,9 @@
/** Number of times this symbol is used in code */
private int useCount;
+ /** Range for symbol */
+ private Range range;
+
/** Debugging option - dump info and stack trace when symbols with given names are manipulated */
private static final Set<String> TRACE_SYMBOLS;
private static final Set<String> TRACE_SYMBOLS_STACKTRACE;
@@ -131,6 +136,7 @@
this.type = type;
this.slot = slot;
this.fieldIndex = -1;
+ this.range = Range.createUnknownRange();
trace("CREATE SYMBOL");
}
@@ -157,12 +163,13 @@
private Symbol(final Symbol base, final String name, final int flags) {
this.flags = flags;
- this.name = name;
+ this.name = name;
this.fieldIndex = base.fieldIndex;
- this.slot = base.slot;
- this.type = base.type;
- this.useCount = base.useCount;
+ this.slot = base.slot;
+ this.type = base.type;
+ this.useCount = base.useCount;
+ this.range = base.range;
}
private static String align(final String string, final int max) {
@@ -276,7 +283,7 @@
@Override
public String toString() {
- final StringBuilder sb = new StringBuilder();
+ final StringBuilder sb = new StringBuilder();
sb.append(name).
append(' ').
@@ -410,6 +417,22 @@
}
/**
+ * Get the range for this symbol
+ * @return range for symbol
+ */
+ public Range getRange() {
+ return range;
+ }
+
+ /**
+ * Set the range for this symbol
+ * @param range range
+ */
+ public void setRange(final Range range) {
+ this.range = range;
+ }
+
+ /**
* Check if this symbol is a function parameter of known
* narrowest type
* @return true if parameter
--- a/nashorn/src/jdk/nashorn/internal/runtime/CompiledFunction.java Fri May 17 16:44:22 2013 -0300
+++ b/nashorn/src/jdk/nashorn/internal/runtime/CompiledFunction.java Mon May 20 16:38:38 2013 +0200
@@ -35,21 +35,27 @@
*/
final class CompiledFunction implements Comparable<CompiledFunction> {
+ /** The method type may be more specific than the invoker, if. e.g.
+ * the invoker is guarded, and a guard with a generic object only
+ * fallback, while the target is more specific, we still need the
+ * more specific type for sorting */
+ private final MethodType type;
private final MethodHandle invoker;
private MethodHandle constructor;
- CompiledFunction(final MethodHandle invoker) {
- this(invoker, null);
+ CompiledFunction(final MethodType type, final MethodHandle invoker) {
+ this(type, invoker, null);
}
- CompiledFunction(final MethodHandle invoker, final MethodHandle constructor) {
- this.invoker = invoker;
- this.constructor = constructor; //isConstructor
+ CompiledFunction(final MethodType type, final MethodHandle invoker, final MethodHandle constructor) {
+ this.type = type;
+ this.invoker = invoker;
+ this.constructor = constructor;
}
@Override
public String toString() {
- return "<invoker=" + invoker + " ctor=" + constructor + ">";
+ return "<callSiteType= " + type + " invoker=" + invoker + " ctor=" + constructor + ">";
}
MethodHandle getInvoker() {
@@ -69,7 +75,7 @@
}
MethodType type() {
- return invoker.type();
+ return type;
}
@Override
@@ -103,8 +109,8 @@
return weight() > o.weight();
}
- boolean moreGenericThan(final MethodType type) {
- return weight() > weight(type);
+ boolean moreGenericThan(final MethodType mt) {
+ return weight() > weight(mt);
}
/**
@@ -112,15 +118,15 @@
* It is compatible if the types are narrower than the invocation type so that
* a semantically equivalent linkage can be performed.
*
- * @param typesc
+ * @param mt type to check against
* @return
*/
- boolean typeCompatible(final MethodType type) {
- final Class<?>[] wantedParams = type.parameterArray();
+ boolean typeCompatible(final MethodType mt) {
+ final Class<?>[] wantedParams = mt.parameterArray();
final Class<?>[] existingParams = type().parameterArray();
//if we are not examining a varargs type, the number of parameters must be the same
- if (wantedParams.length != existingParams.length && !isVarArgsType(type)) {
+ if (wantedParams.length != existingParams.length && !isVarArgsType(mt)) {
return false;
}
--- a/nashorn/src/jdk/nashorn/internal/runtime/FinalScriptFunctionData.java Fri May 17 16:44:22 2013 -0300
+++ b/nashorn/src/jdk/nashorn/internal/runtime/FinalScriptFunctionData.java Mon May 20 16:38:38 2013 +0200
@@ -78,9 +78,9 @@
//only nasgen constructors: (boolean, self, args) are subject to binding a boolean newObj. isConstructor
//is too conservative a check. However, isConstructor(mh) always implies isConstructor param
assert isConstructor();
- code.add(new CompiledFunction(MH.insertArguments(mh, 0, false), composeConstructor(MH.insertArguments(mh, 0, true), needsCallee))); //make sure callee state can be determined when we reach constructor
+ code.add(new CompiledFunction(mh.type(), MH.insertArguments(mh, 0, false), composeConstructor(MH.insertArguments(mh, 0, true), needsCallee))); //make sure callee state can be determined when we reach constructor
} else {
- code.add(new CompiledFunction(mh));
+ code.add(new CompiledFunction(mh.type(), mh));
}
}
--- a/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Fri May 17 16:44:22 2013 -0300
+++ b/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Mon May 20 16:38:38 2013 +0200
@@ -30,6 +30,8 @@
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.LinkedList;
import jdk.nashorn.internal.codegen.Compiler;
@@ -49,9 +51,16 @@
*/
public final class RecompilableScriptFunctionData extends ScriptFunctionData {
+ /** FunctionNode with the code for this ScriptFunction */
private FunctionNode functionNode;
- private final PropertyMap allocatorMap;
+
+ /** Allocator map from makeMap() */
+ private final PropertyMap allocatorMap;
+
+ /** Code installer used for all further recompilation/specialization of this ScriptFunction */
private final CodeInstaller<ScriptEnvironment> installer;
+
+ /** Name of class where allocator function resides */
private final String allocatorClassName;
/** lazily generated allocator */
@@ -60,6 +69,23 @@
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
/**
+ * Used for specialization based on runtime arguments. Whenever we specialize on
+ * callsite parameter types at runtime, we need to use a parameter type guard to
+ * ensure that the specialized version of the script function continues to be
+ * applicable for a particular callsite *
+ */
+ private static final MethodHandle PARAM_TYPE_GUARD = findOwnMH("paramTypeGuard", boolean.class, Type[].class, Object[].class);
+
+ /**
+ * It is usually a good gamble whever we detect a runtime callsite with a double
+ * (or java.lang.Number instance) to specialize the parameter to an integer, if the
+ * parameter in question can be represented as one. The double typically only exists
+ * because the compiler doesn't know any better than "a number type" and conservatively
+ * picks doubles when it can't prove that an integer addition wouldn't overflow
+ */
+ private static final MethodHandle ENSURE_INT = findOwnMH("ensureInt", int.class, Object.class);
+
+ /**
* Constructor - public as scripts use it
*
* @param functionNode functionNode that represents this function code
@@ -141,14 +167,6 @@
return; // nothing to do, we have code, at least some.
}
- // check if function node is lazy, need to compile it.
- // note that currently function cloning is not working completely, which
- // means that the compiler will mutate the function node it has been given
- // once it has been compiled, it cannot be recompiled. This means that
- // lazy compilation works (not compiled yet) but e.g. specializations won't
- // until the copy-on-write changes for IR are in, making cloning meaningless.
- // therefore, currently method specialization is disabled. TODO
-
if (functionNode.isLazy()) {
Compiler.LOG.info("Trampoline hit: need to do lazy compilation of '", functionNode.getName(), "'");
final Compiler compiler = new Compiler(installer);
@@ -156,38 +174,55 @@
assert !functionNode.isLazy();
compiler.install(functionNode);
- // we don't need to update any flags - varArgs and needsCallee are instrincic
- // in the function world we need to get a destination node from the compile instead
- // and replace it with our function node. TODO
+ /*
+ * We don't need to update any flags - varArgs and needsCallee are instrincic
+ * in the function world we need to get a destination node from the compile instead
+ * and replace it with our function node. TODO
+ */
}
- // we can't get here unless we have bytecode, either from eager compilation or from
- // running a lazy compile on the lines above
+ /*
+ * We can't get to this program point unless we have bytecode, either from
+ * eager compilation or from running a lazy compile on the lines above
+ */
assert functionNode.hasState(CompilationState.EMITTED) : functionNode.getName() + " " + functionNode.getState() + " " + Debug.id(functionNode);
// code exists - look it up and add it into the automatically sorted invoker list
- addCode(functionNode, null, null);
+ addCode(functionNode);
}
- private MethodHandle addCode(final FunctionNode fn, final MethodHandle guard, final MethodHandle fallback) {
- final MethodHandle target =
+ private MethodHandle addCode(final FunctionNode fn) {
+ return addCode(fn, null, null, null);
+ }
+
+ private MethodHandle addCode(final FunctionNode fn, final MethodType runtimeType, final MethodHandle guard, final MethodHandle fallback) {
+ final MethodType targetType = new FunctionSignature(fn).getMethodType();
+ MethodHandle target =
MH.findStatic(
LOOKUP,
fn.getCompileUnit().getCode(),
fn.getName(),
- new FunctionSignature(fn).
- getMethodType());
- MethodHandle mh = target;
- if (guard != null) {
- try {
- mh = MH.guardWithTest(MH.asCollector(guard, Object[].class, target.type().parameterCount()), MH.asType(target, fallback.type()), fallback);
- } catch (Throwable e) {
- e.printStackTrace();
+ targetType);
+
+ /*
+ * For any integer argument. a double that is representable as an integer is OK.
+ * otherwise the guard would have failed. in that case introduce a filter that
+ * casts the double to an integer, which we know will preserve all precision.
+ */
+ for (int i = 0; i < targetType.parameterCount(); i++) {
+ if (targetType.parameterType(i) == int.class) {
+ //representable as int
+ target = MH.filterArguments(target, i, ENSURE_INT);
}
}
- final CompiledFunction cf = new CompiledFunction(mh);
+ MethodHandle mh = target;
+ if (guard != null) {
+ mh = MH.guardWithTest(MH.asCollector(guard, Object[].class, target.type().parameterCount()), MH.asType(target, fallback.type()), fallback);
+ }
+
+ final CompiledFunction cf = new CompiledFunction(runtimeType == null ? targetType : runtimeType, mh);
code.add(cf);
return cf.getInvoker();
@@ -212,69 +247,162 @@
return Type.OBJECT;
}
- @SuppressWarnings("unused")
- private static boolean paramTypeGuard(final Type[] compileTimeTypes, final Type[] runtimeTypes, Object... args) {
- //System.err.println("Param type guard " + Arrays.asList(args));
+ private static boolean canCoerce(final Object arg, final Type type) {
+ Type argType = runtimeType(arg);
+ if (Type.widest(argType, type) == type || arg == ScriptRuntime.UNDEFINED) {
+ return true;
+ }
+ System.err.println(arg + " does not fit in "+ argType + " " + type + " " + arg.getClass());
+ new Throwable().printStackTrace();
return false;
}
- private static final MethodHandle PARAM_TYPE_GUARD = findOwnMH("paramTypeGuard", boolean.class, Type[].class, Type[].class, Object[].class);
+ @SuppressWarnings("unused")
+ private static boolean paramTypeGuard(final Type[] paramTypes, final Object... args) {
+ final int length = args.length;
+ assert args.length >= paramTypes.length;
+
+ //i==start, skip the this, callee params etc
+ int start = args.length - paramTypes.length;
+ for (int i = start; i < args.length; i++) {
+ final Object arg = args[i];
+ if (!canCoerce(arg, paramTypes[i - start])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @SuppressWarnings("unused")
+ private static int ensureInt(final Object arg) {
+ if (arg instanceof Number) {
+ return ((Number)arg).intValue();
+ } else if (arg instanceof Undefined) {
+ return 0;
+ }
+ throw new AssertionError(arg);
+ }
+
+ /**
+ * Given the runtime callsite args, compute a method type that is equivalent to what
+ * was passed - this is typically a lot more specific that what the compiler has been
+ * able to deduce
+ * @param callSiteType callsite type for the compiled callsite target
+ * @param args runtime arguments to the compiled callsite target
+ * @return adjusted method type, narrowed as to conform to runtime callsite type instead
+ */
+ private static MethodType runtimeType(final MethodType callSiteType, final Object[] args) {
+ if (args == null) {
+ //for example bound, or otherwise runtime arguments to callsite unavailable, then
+ //do not change the type
+ return callSiteType;
+ }
+ final Class<?>[] paramTypes = new Class<?>[callSiteType.parameterCount()];
+ final int start = args.length - callSiteType.parameterCount();
+ for (int i = start; i < args.length; i++) {
+ paramTypes[i - start] = runtimeType(args[i]).getTypeClass();
+ }
+ return MH.type(callSiteType.returnType(), paramTypes);
+ }
+
+ private static ArrayList<Type> runtimeType(final MethodType mt) {
+ final ArrayList<Type> type = new ArrayList<>();
+ for (int i = 0; i < mt.parameterCount(); i++) {
+ type.add(Type.typeFor(mt.parameterType(i)));
+ }
+ return type;
+ }
@Override
MethodHandle getBestInvoker(final MethodType callSiteType, final Object[] args) {
- final MethodHandle mh = super.getBestInvoker(callSiteType, args);
+ final MethodType runtimeType = runtimeType(callSiteType, args);
+ assert runtimeType.parameterCount() == callSiteType.parameterCount();
+
+ final MethodHandle mh = super.getBestInvoker(runtimeType, args);
- if (!functionNode.canSpecialize() || !code.isLessSpecificThan(callSiteType)) {
+ /*
+ * Not all functions can be specialized, for example, if we deemed memory
+ * footprint too large to store a parse snapshot, or if it is meaningless
+ * to do so, such as e.g. for runScript
+ */
+ if (!functionNode.canSpecialize()) {
return mh;
}
- final FunctionNode snapshot = functionNode.getSnapshot();
- if (snapshot == null) {
+ /*
+ * Check if best invoker is equally specific or more specific than runtime
+ * type. In that case, we don't need further specialization, but can use
+ * whatever we have already. We know that it will match callSiteType, or it
+ * would not have been returned from getBestInvoker
+ */
+ if (!code.isLessSpecificThan(runtimeType)) {
return mh;
}
int i;
+ final FunctionNode snapshot = functionNode.getSnapshot();
+ assert snapshot != null;
- //classes known at runtime
- final LinkedList<Type> runtimeArgs = new LinkedList<>();
- for (i = args.length - 1; i >= args.length - snapshot.getParameters().size(); i--) {
- runtimeArgs.addLast(runtimeType(args[i]));
- }
-
- //classes known at compile time
+ /*
+ * Create a list of the arg types that the compiler knows about
+ * typically, the runtime args are a lot more specific, and we should aggressively
+ * try to use those whenever possible
+ * We WILL try to make an aggressive guess as possible, and add guards if needed.
+ * For example, if the compiler can deduce that we have a number type, but the runtime
+ * passes and int, we might still want to keep it an int, and the gamble to
+ * check that whatever is passed is int representable usually pays off
+ * If the compiler only knows that a parameter is an "Object", it is still worth
+ * it to try to specialize it by looking at the runtime arg.
+ */
final LinkedList<Type> compileTimeArgs = new LinkedList<>();
for (i = callSiteType.parameterCount() - 1; i >= 0 && compileTimeArgs.size() < snapshot.getParameters().size(); i--) {
- compileTimeArgs.addLast(Type.typeFor(callSiteType.parameterType(i)));
+ compileTimeArgs.addFirst(Type.typeFor(callSiteType.parameterType(i)));
}
- //the classes known at compile time are a safe to generate as primitives without parameter guards
- //the classes known at runtime are safe to generate as primitives IFF there are parameter guards
+ /*
+ * The classes known at compile time are a safe to generate as primitives without parameter guards
+ * But the classes known at runtime (if more specific than compile time types) are safe to generate as primitives
+ * IFF there are parameter guards
+ */
MethodHandle guard = null;
+ final ArrayList<Type> runtimeParamTypes = runtimeType(runtimeType);
+ while (runtimeParamTypes.size() > functionNode.getParameters().size()) {
+ runtimeParamTypes.remove(0);
+ }
for (i = 0; i < compileTimeArgs.size(); i++) {
- final Type runtimeType = runtimeArgs.get(i);
- final Type compileType = compileTimeArgs.get(i);
+ final Type rparam = Type.typeFor(runtimeType.parameterType(i));
+ final Type cparam = compileTimeArgs.get(i);
- if (compileType.isObject() && !runtimeType.isObject()) {
+ if (cparam.isObject() && !rparam.isObject()) {
+ //check that the runtime object is still coercible to the runtime type, because compiler can't prove it's always primitive
if (guard == null) {
- guard = PARAM_TYPE_GUARD;
- guard = MH.insertArguments(guard, 0, compileTimeArgs.toArray(new Type[compileTimeArgs.size()]), runtimeArgs.toArray(new Type[runtimeArgs.size()]));
+ guard = MH.insertArguments(PARAM_TYPE_GUARD, 0, (Object)runtimeParamTypes.toArray(new Type[runtimeParamTypes.size()]));
}
}
}
- //System.err.println("Specialized " + name + " " + runtimeArgs + " known=" + compileTimeArgs);
+ Compiler.LOG.info("Callsite specialized ", name, " runtimeType=", runtimeType, " parameters=", snapshot.getParameters(), " args=", Arrays.asList(args));
assert snapshot != null;
assert snapshot != functionNode;
final Compiler compiler = new Compiler(installer);
- final FunctionNode compiledSnapshot = compiler.compile(snapshot.setHints(null, new Compiler.Hints(compileTimeArgs.toArray(new Type[compileTimeArgs.size()]))));
+
+ final FunctionNode compiledSnapshot = compiler.compile(
+ snapshot.setHints(
+ null,
+ new Compiler.Hints(runtimeParamTypes.toArray(new Type[runtimeParamTypes.size()]))));
+ /*
+ * No matter how narrow your types were, they can never be narrower than Attr during recompile made them. I.e. you
+ * can put an int into the function here, if you see it as a runtime type, but if the function uses a multiplication
+ * on it, it will still need to be a double. At least until we have overflow checks. Similarly, if an int is
+ * passed but it is used as a string, it makes no sense to make the parameter narrower than Object. At least until
+ * the "different types for one symbol in difference places" work is done
+ */
compiler.install(compiledSnapshot);
- final MethodHandle nmh = addCode(compiledSnapshot, guard, mh);
-
- return nmh;
+ return addCode(compiledSnapshot, runtimeType, guard, mh);
}
private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
--- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptEnvironment.java Fri May 17 16:44:22 2013 -0300
+++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptEnvironment.java Mon May 20 16:38:38 2013 +0200
@@ -54,7 +54,7 @@
private final Namespace namespace;
/** Current Options object. */
- private Options options;
+ private final Options options;
/** Always allow functions as statements */
public final boolean _anon_functions;
@@ -155,6 +155,9 @@
/** print symbols and their contents for the script */
public final boolean _print_symbols;
+ /** range analysis for known types */
+ public final boolean _range_analysis;
+
/** is this environment in scripting mode? */
public final boolean _scripting;
@@ -219,6 +222,7 @@
_print_parse = options.getBoolean("print.parse");
_print_lower_parse = options.getBoolean("print.lower.parse");
_print_symbols = options.getBoolean("print.symbols");
+ _range_analysis = options.getBoolean("range.analysis");
_scripting = options.getBoolean("scripting");
_strict = options.getBoolean("strict");
_version = options.getBoolean("version");
--- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java Fri May 17 16:44:22 2013 -0300
+++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java Mon May 20 16:38:38 2013 +0200
@@ -91,12 +91,13 @@
CompiledFunction bind(final CompiledFunction originalInv, final ScriptFunction fn, final Object self, final Object[] args) {
final MethodHandle boundInvoker = bindInvokeHandle(originalInv.getInvoker(), fn, self, args);
+ //TODO the boundinvoker.type() could actually be more specific here
if (isConstructor()) {
ensureConstructor(originalInv);
- return new CompiledFunction(boundInvoker, bindConstructHandle(originalInv.getConstructor(), fn, args));
+ return new CompiledFunction(boundInvoker.type(), boundInvoker, bindConstructHandle(originalInv.getConstructor(), fn, args));
}
- return new CompiledFunction(boundInvoker);
+ return new CompiledFunction(boundInvoker.type(), boundInvoker);
}
/**
--- a/nashorn/src/jdk/nashorn/internal/runtime/resources/Options.properties Fri May 17 16:44:22 2013 -0300
+++ b/nashorn/src/jdk/nashorn/internal/runtime/resources/Options.properties Mon May 20 16:38:38 2013 +0200
@@ -277,6 +277,12 @@
desc="Print the symbol table." \
}
+nashorn.option.range.analysis = { \
+ name="--range-analysis", \
+ is_undocumented=true, \
+ desc="Do range analysis using known compile time types, and try to narrow number types" \
+}
+
nashorn.option.D = { \
name="-D", \
desc="-Dname=value. Set a system property. This option can be repeated.", \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/ranges_disabled.js Mon May 20 16:38:38 2013 +0200
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ *
+ * 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.
+ */
+
+/**
+ * range analysis test. check that computation return values are correct
+ * both with and without range analysis
+ *
+ * @test
+ * @run
+ */
+
+load(__DIR__ + "ranges_payload.js");
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/ranges_disabled.js.EXPECTED Mon May 20 16:38:38 2013 +0200
@@ -0,0 +1,4 @@
+289
+11094405
+4294967293
+-4722
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/ranges_enabled.js Mon May 20 16:38:38 2013 +0200
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ *
+ * 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.
+ */
+
+/**
+ * range analysis test. check that computation return values are correct
+ * both with and without range analysis
+ *
+ * @test
+ * @option --range-analysis
+ * @run
+ */
+
+load(__DIR__ + "ranges_payload.js");
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/ranges_enabled.js.EXPECTED Mon May 20 16:38:38 2013 +0200
@@ -0,0 +1,4 @@
+289
+11094405
+4294967293
+-4722
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/ranges_payload.js Mon May 20 16:38:38 2013 +0200
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ *
+ * 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.
+ */
+
+/**
+ * range analysis test. check that computation return values are correct
+ * both with and without range analysis
+ *
+ * @subtest
+ */
+
+function f(c) {
+ var v = c & 0xffff;
+ var w = v & 0xfff;
+ var x = v * w;
+ return x;
+}
+
+function g() {
+ var sum = 0;
+ for (var x = 0; x < 4711; x++) {
+ sum += x;
+ }
+ return sum;
+}
+
+function g2() {
+ var sum = 0;
+ //make sure we overflow
+ var displacement = 0x7ffffffe;
+ for (var x = displacement; x < (displacement + 2); x++) {
+ sum += x;
+ }
+ return sum;
+}
+
+//mostly provide code coverage for all the range operations
+function h() {
+ var sum = 0;
+ sum += 4711;
+ sum &= 0xffff;
+ sum /= 2;
+ sum *= 2;
+ sum -= 4;
+ sum |= 2;
+ sum ^= 17;
+ sum = sum % 10000;
+ sum = -sum;
+ return sum
+}
+
+print(f(17));
+print(g());
+print(g2());
+print(h());