提交 a67c183b 编写于 作者: L lagergren

8006069: Range analysis first iteration, runtime specializations

Reviewed-by: jlaskey, sundar
上级 7e694f2c
......@@ -11,13 +11,21 @@ import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SPLIT;
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 @@ enum CompilationPhase {
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;
......@@ -207,6 +215,89 @@ enum CompilationPhase {
}
},
/*
* 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
......@@ -218,7 +309,6 @@ enum CompilationPhase {
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;
......
......@@ -99,7 +99,7 @@ public final class Compiler {
private boolean strict;
private CodeInstaller<ScriptEnvironment> installer;
private final CodeInstaller<ScriptEnvironment> installer;
private final TemporarySymbols temporarySymbols = new TemporarySymbols();
......@@ -219,6 +219,7 @@ public final class Compiler {
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 @@ public final class Compiler {
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 @@ public final class Compiler {
}
if (sb != null) {
LOG.info(sb);
LOG.fine(sb);
}
return rootClass;
......
......@@ -262,7 +262,7 @@ public enum CompilerConstants {
* @return the internal descriptor for this type
*/
public static String typeDescriptor(final Class<?> clazz) {
return Type.getDescriptor(clazz);
return Type.typeFor(clazz).getDescriptor();
}
/**
......
......@@ -2081,8 +2081,10 @@ public class MethodEmitter implements Emitter {
* @param args debug information to print
*/
private void debug(final Object... args) {
if (DEBUG) {
debug(30, args);
}
}
/**
* Debug function that outputs generated bytecode and stack contents
......@@ -2091,7 +2093,9 @@ public class MethodEmitter implements Emitter {
* @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 @@ public class MethodEmitter implements Emitter {
new Throwable().printStackTrace(LOG.getOutputStream());
}
}
}
}
......
/*
* 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;
}
}
......@@ -106,22 +106,12 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
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
......
......@@ -59,6 +59,23 @@ public final class BinaryNode extends Node implements Assignment<Node> {
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
......
......@@ -340,7 +340,7 @@ public final class FunctionNode extends LexicalContextNode implements Flags<Func
* @return true if specialization is possible
*/
public boolean canSpecialize() {
return getFlag(CAN_SPECIALIZE);
return snapshot != null && getFlag(CAN_SPECIALIZE);
}
/**
......
......@@ -439,6 +439,23 @@ public class LexicalContext {
return null;
}
/**
* 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
......@@ -461,8 +478,7 @@ public class LexicalContext {
}
private LoopNode getContinueTo() {
final Iterator<LoopNode> iter = new NodeIterator<>(LoopNode.class, getCurrentFunction());
return iter.hasNext() ? iter.next() : null;
return getCurrentLoop();
}
/**
......
......@@ -152,6 +152,14 @@ public abstract class Node implements Cloneable {
return Type.OBJECT;
}
/**
* 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
......
......@@ -29,6 +29,8 @@ import java.io.PrintWriter;
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 @@ public final class Symbol implements Comparable<Symbol> {
/** 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 @@ public final class Symbol implements Comparable<Symbol> {
this.type = type;
this.slot = slot;
this.fieldIndex = -1;
this.range = Range.createUnknownRange();
trace("CREATE SYMBOL");
}
......@@ -163,6 +169,7 @@ public final class Symbol implements Comparable<Symbol> {
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) {
......@@ -409,6 +416,22 @@ public final class Symbol implements Comparable<Symbol> {
return (flags & KINDMASK) == IS_PARAM;
}
/**
* 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
......
......@@ -35,21 +35,27 @@ import jdk.nashorn.internal.codegen.types.Type;
*/
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) {
CompiledFunction(final MethodType type, final MethodHandle invoker, final MethodHandle constructor) {
this.type = type;
this.invoker = invoker;
this.constructor = constructor; //isConstructor
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 @@ final class CompiledFunction implements Comparable<CompiledFunction> {
}
MethodType type() {
return invoker.type();
return type;
}
@Override
......@@ -103,8 +109,8 @@ final class CompiledFunction implements Comparable<CompiledFunction> {
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 @@ final class CompiledFunction implements Comparable<CompiledFunction> {
* 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;
}
......
......@@ -78,9 +78,9 @@ public final class FinalScriptFunctionData extends ScriptFunctionData {
//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));
}
}
......
......@@ -30,6 +30,8 @@ import static jdk.nashorn.internal.lookup.Lookup.MH;
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 @@ import jdk.nashorn.internal.parser.TokenType;
*/
public final class RecompilableScriptFunctionData extends ScriptFunctionData {
/** FunctionNode with the code for this ScriptFunction */
private FunctionNode functionNode;
/** 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 */
......@@ -59,6 +68,23 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData {
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
*
......@@ -141,14 +167,6 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData {
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 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData {
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) {
return addCode(fn, null, null, null);
}
private MethodHandle addCode(final FunctionNode fn, final MethodHandle guard, final MethodHandle fallback) {
final MethodHandle target =
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());
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);
}
}
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();
}
}
final CompiledFunction cf = new CompiledFunction(mh);
final CompiledFunction cf = new CompiledFunction(runtimeType == null ? targetType : runtimeType, mh);
code.add(cf);
return cf.getInvoker();
......@@ -212,69 +247,162 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData {
return Type.OBJECT;
}
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;
}
@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 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;
}
private static final MethodHandle PARAM_TYPE_GUARD = findOwnMH("paramTypeGuard", boolean.class, Type[].class, Type[].class, Object[].class);
@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();
if (!functionNode.canSpecialize() || !code.isLessSpecificThan(callSiteType)) {
final MethodHandle mh = super.getBestInvoker(runtimeType, args);
/*
* 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) {
......
......@@ -54,7 +54,7 @@ public final class ScriptEnvironment {
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 @@ public final class ScriptEnvironment {
/** 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 @@ public final class ScriptEnvironment {
_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");
......
......@@ -91,12 +91,13 @@ public abstract class ScriptFunctionData {
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);
}
/**
......
......@@ -277,6 +277,12 @@ nashorn.option.print.symbols = { \
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.", \
......
/*
* 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");
/*
* 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");
/*
* 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());
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册