mirror of
https://github.com/openjdk/jdk.git
synced 2026-02-02 06:28:23 +00:00
8042118: Separate types from symbols
Reviewed-by: hannesw, lagergren
This commit is contained in:
parent
31c5e7065a
commit
64fd9c191a
@ -281,7 +281,7 @@ public class TypeUtilities {
|
||||
}
|
||||
if(sourceType.isPrimitive()) {
|
||||
if(sourceType == void.class) {
|
||||
return true; // Void can be losslessly represented by any type
|
||||
return false; // Void can't be losslessly represented by any type
|
||||
}
|
||||
if(targetType.isPrimitive()) {
|
||||
return isProperPrimitiveLosslessSubtype(sourceType, targetType);
|
||||
|
||||
@ -83,4 +83,4 @@ isOfClassGuardAlwaysTrue=isOfClass guard for {0} in position {1} in method type
|
||||
isOfClassGuardAlwaysFalse=isOfClass guard for {0} in position {1} in method type {2} at {3} will always return false
|
||||
|
||||
isArrayGuardAlwaysTrue=isArray guard in position {0} in method type {1} at {2} will always return true
|
||||
isArrayGuardAlwaysFalse=isArray guard in position {0} in method type {1} at {2} will always return false
|
||||
isArrayGuardAlwaysFalse=isArray guard in position {0} in method type {1} at {2} will always return false
|
||||
|
||||
@ -164,7 +164,7 @@ public final class NashornScriptEngineFactory implements ScriptEngineFactory {
|
||||
* @param args arguments array passed to script engine.
|
||||
* @return newly created script engine.
|
||||
*/
|
||||
public ScriptEngine getScriptEngine(final String[] args) {
|
||||
public ScriptEngine getScriptEngine(final String... args) {
|
||||
checkConfigPermission();
|
||||
return new NashornScriptEngine(this, args, getAppClassLoader());
|
||||
}
|
||||
|
||||
@ -35,7 +35,6 @@ import java.util.Deque;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import jdk.nashorn.internal.ir.AccessNode;
|
||||
import jdk.nashorn.internal.ir.CallNode;
|
||||
import jdk.nashorn.internal.ir.Expression;
|
||||
@ -45,12 +44,12 @@ import jdk.nashorn.internal.ir.LexicalContext;
|
||||
import jdk.nashorn.internal.ir.Node;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
import jdk.nashorn.internal.objects.Global;
|
||||
import jdk.nashorn.internal.runtime.logging.DebugLogger;
|
||||
import jdk.nashorn.internal.runtime.logging.Loggable;
|
||||
import jdk.nashorn.internal.runtime.logging.Logger;
|
||||
import jdk.nashorn.internal.runtime.Context;
|
||||
import jdk.nashorn.internal.runtime.Debug;
|
||||
import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
|
||||
import jdk.nashorn.internal.runtime.logging.DebugLogger;
|
||||
import jdk.nashorn.internal.runtime.logging.Loggable;
|
||||
import jdk.nashorn.internal.runtime.logging.Logger;
|
||||
import jdk.nashorn.internal.runtime.options.Options;
|
||||
|
||||
/**
|
||||
@ -59,7 +58,6 @@ import jdk.nashorn.internal.runtime.options.Options;
|
||||
* introduces expensive args collection and boxing
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* var Class = {
|
||||
* create: function() {
|
||||
* return function() { //vararg
|
||||
@ -80,7 +78,6 @@ import jdk.nashorn.internal.runtime.options.Options;
|
||||
* }
|
||||
*
|
||||
* new Color(17, 47, 11);
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
@ -303,16 +300,9 @@ public final class ApplySpecialization implements Loggable {
|
||||
return finish();
|
||||
}
|
||||
|
||||
private static boolean isApply(final Node node) {
|
||||
if (node instanceof AccessNode) {
|
||||
return isApply(((AccessNode)node).getProperty());
|
||||
}
|
||||
return node instanceof IdentNode && "apply".equals(((IdentNode)node).getName());
|
||||
}
|
||||
|
||||
private static boolean isApply(final CallNode callNode) {
|
||||
final Expression f = callNode.getFunction();
|
||||
return f instanceof AccessNode && isApply(f);
|
||||
return f instanceof AccessNode && "apply".equals(((AccessNode)f).getProperty());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
946
nashorn/src/jdk/nashorn/internal/codegen/AssignSymbols.java
Normal file
946
nashorn/src/jdk/nashorn/internal/codegen/AssignSymbols.java
Normal file
@ -0,0 +1,946 @@
|
||||
/*
|
||||
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.nashorn.internal.codegen;
|
||||
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS_VAR;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.EXCEPTION_PREFIX;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.ITERATOR_PREFIX;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.SWITCH_TAG_PREFIX;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.THIS;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS;
|
||||
import static jdk.nashorn.internal.ir.Symbol.HAS_OBJECT_VALUE;
|
||||
import static jdk.nashorn.internal.ir.Symbol.IS_FUNCTION_SELF;
|
||||
import static jdk.nashorn.internal.ir.Symbol.IS_GLOBAL;
|
||||
import static jdk.nashorn.internal.ir.Symbol.IS_INTERNAL;
|
||||
import static jdk.nashorn.internal.ir.Symbol.IS_LET;
|
||||
import static jdk.nashorn.internal.ir.Symbol.IS_PARAM;
|
||||
import static jdk.nashorn.internal.ir.Symbol.IS_PROGRAM_LEVEL;
|
||||
import static jdk.nashorn.internal.ir.Symbol.IS_SCOPE;
|
||||
import static jdk.nashorn.internal.ir.Symbol.IS_THIS;
|
||||
import static jdk.nashorn.internal.ir.Symbol.IS_VAR;
|
||||
import static jdk.nashorn.internal.ir.Symbol.KINDMASK;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import jdk.nashorn.internal.ir.AccessNode;
|
||||
import jdk.nashorn.internal.ir.BinaryNode;
|
||||
import jdk.nashorn.internal.ir.Block;
|
||||
import jdk.nashorn.internal.ir.CatchNode;
|
||||
import jdk.nashorn.internal.ir.Expression;
|
||||
import jdk.nashorn.internal.ir.ForNode;
|
||||
import jdk.nashorn.internal.ir.FunctionNode;
|
||||
import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
|
||||
import jdk.nashorn.internal.ir.IdentNode;
|
||||
import jdk.nashorn.internal.ir.IndexNode;
|
||||
import jdk.nashorn.internal.ir.LexicalContext;
|
||||
import jdk.nashorn.internal.ir.LexicalContextNode;
|
||||
import jdk.nashorn.internal.ir.LiteralNode;
|
||||
import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
|
||||
import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode.ArrayUnit;
|
||||
import jdk.nashorn.internal.ir.Node;
|
||||
import jdk.nashorn.internal.ir.RuntimeNode;
|
||||
import jdk.nashorn.internal.ir.RuntimeNode.Request;
|
||||
import jdk.nashorn.internal.ir.SplitNode;
|
||||
import jdk.nashorn.internal.ir.Statement;
|
||||
import jdk.nashorn.internal.ir.SwitchNode;
|
||||
import jdk.nashorn.internal.ir.Symbol;
|
||||
import jdk.nashorn.internal.ir.TryNode;
|
||||
import jdk.nashorn.internal.ir.UnaryNode;
|
||||
import jdk.nashorn.internal.ir.VarNode;
|
||||
import jdk.nashorn.internal.ir.WithNode;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
import jdk.nashorn.internal.runtime.Context;
|
||||
import jdk.nashorn.internal.runtime.Property;
|
||||
import jdk.nashorn.internal.runtime.PropertyMap;
|
||||
import jdk.nashorn.internal.runtime.logging.DebugLogger;
|
||||
import jdk.nashorn.internal.runtime.logging.Loggable;
|
||||
import jdk.nashorn.internal.runtime.logging.Logger;
|
||||
|
||||
/**
|
||||
* This visitor assigns symbols to identifiers denoting variables. It does few more minor calculations that are only
|
||||
* possible after symbols have been assigned; such is the transformation of "delete" and "typeof" operators into runtime
|
||||
* nodes and counting of number of properties assigned to "this" in constructor functions. This visitor is also notable
|
||||
* for what it doesn't do, most significantly it does no type calculations as in JavaScript variables can change types
|
||||
* during runtime and as such symbols don't have types. Calculation of expression types is performed by a separate
|
||||
* visitor.
|
||||
*/
|
||||
@Logger(name="symbols")
|
||||
final class AssignSymbols extends NodeOperatorVisitor<LexicalContext> implements Loggable {
|
||||
private final DebugLogger log;
|
||||
private final boolean debug;
|
||||
|
||||
private static boolean isParamOrVar(final IdentNode identNode) {
|
||||
final Symbol symbol = identNode.getSymbol();
|
||||
return symbol.isParam() || symbol.isVar();
|
||||
}
|
||||
|
||||
private static String name(final Node node) {
|
||||
final String cn = node.getClass().getName();
|
||||
final int lastDot = cn.lastIndexOf('.');
|
||||
if (lastDot == -1) {
|
||||
return cn;
|
||||
}
|
||||
return cn.substring(lastDot + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if various symbols that were provisionally marked as needing a slot ended up unused, and marks them as not
|
||||
* needing a slot after all.
|
||||
* @param functionNode the function node
|
||||
* @return the passed in node, for easy chaining
|
||||
*/
|
||||
private static FunctionNode removeUnusedSlots(final FunctionNode functionNode) {
|
||||
if (!functionNode.needsCallee()) {
|
||||
functionNode.compilerConstant(CALLEE).setNeedsSlot(false);
|
||||
}
|
||||
if (!(functionNode.hasScopeBlock() || functionNode.needsParentScope())) {
|
||||
functionNode.compilerConstant(SCOPE).setNeedsSlot(false);
|
||||
}
|
||||
if (!functionNode.usesReturnSymbol()) {
|
||||
functionNode.compilerConstant(RETURN).setNeedsSlot(false);
|
||||
}
|
||||
// Named function expressions that end up not referencing themselves won't need a local slot for the self symbol.
|
||||
if(!functionNode.isDeclared() && !functionNode.usesSelfSymbol() && !functionNode.isAnonymous()) {
|
||||
final Symbol selfSymbol = functionNode.getBody().getExistingSymbol(functionNode.getIdent().getName());
|
||||
if(selfSymbol != null) {
|
||||
if(selfSymbol.isFunctionSelf()) {
|
||||
selfSymbol.setNeedsSlot(false);
|
||||
selfSymbol.clearFlag(Symbol.IS_VAR);
|
||||
}
|
||||
} else {
|
||||
assert functionNode.isProgram();
|
||||
}
|
||||
}
|
||||
return functionNode;
|
||||
}
|
||||
|
||||
private final Deque<Set<String>> thisProperties = new ArrayDeque<>();
|
||||
private final Map<String, Symbol> globalSymbols = new HashMap<>(); //reuse the same global symbol
|
||||
private final CompilationEnvironment env;
|
||||
|
||||
public AssignSymbols(final CompilationEnvironment env) {
|
||||
super(new LexicalContext());
|
||||
this.env = env;
|
||||
this.log = initLogger(env.getContext());
|
||||
this.debug = log.isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebugLogger getLogger() {
|
||||
return log;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebugLogger initLogger(final Context context) {
|
||||
return context.getLogger(this.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Define symbols for all variable declarations at the top of the function scope. This way we can get around
|
||||
* problems like
|
||||
*
|
||||
* while (true) {
|
||||
* break;
|
||||
* if (true) {
|
||||
* var s;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* to an arbitrary nesting depth.
|
||||
*
|
||||
* see NASHORN-73
|
||||
*
|
||||
* @param functionNode the FunctionNode we are entering
|
||||
* @param body the body of the FunctionNode we are entering
|
||||
*/
|
||||
private void acceptDeclarations(final FunctionNode functionNode, final Block body) {
|
||||
// This visitor will assign symbol to all declared variables, except function declarations (which are taken care
|
||||
// in a separate step above) and "var" declarations in for loop initializers.
|
||||
//
|
||||
body.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
|
||||
@Override
|
||||
public boolean enterFunctionNode(final FunctionNode nestedFn) {
|
||||
// Don't descend into nested functions
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveVarNode(final VarNode varNode) {
|
||||
if (varNode.isStatement()) {
|
||||
final IdentNode ident = varNode.getName();
|
||||
final Symbol symbol = defineSymbol(body, ident.getName(), IS_VAR);
|
||||
functionNode.addDeclaredSymbol(symbol);
|
||||
if (varNode.isFunctionDeclaration()) {
|
||||
symbol.setIsFunctionDeclaration();
|
||||
}
|
||||
return varNode.setName((IdentNode)ident.setSymbol(symbol));
|
||||
}
|
||||
return varNode;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private IdentNode compilerConstantIdentifier(CompilerConstants cc) {
|
||||
return (IdentNode)createImplicitIdentifier(cc.symbolName()).setSymbol(lc.getCurrentFunction().compilerConstant(cc));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ident node for an implicit identifier within the function (one not declared in the script source
|
||||
* code). These identifiers are defined with function's token and finish.
|
||||
* @param name the name of the identifier
|
||||
* @return an ident node representing the implicit identifier.
|
||||
*/
|
||||
private IdentNode createImplicitIdentifier(final String name) {
|
||||
final FunctionNode fn = lc.getCurrentFunction();
|
||||
return new IdentNode(fn.getToken(), fn.getFinish(), name);
|
||||
}
|
||||
|
||||
private Symbol createSymbol(final String name, final int flags) {
|
||||
if ((flags & Symbol.KINDMASK) == IS_GLOBAL) {
|
||||
//reuse global symbols so they can be hashed
|
||||
Symbol global = globalSymbols.get(name);
|
||||
if (global == null) {
|
||||
global = new Symbol(name, flags);
|
||||
globalSymbols.put(name, global);
|
||||
}
|
||||
return global;
|
||||
}
|
||||
return new Symbol(name, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a synthetic initializer for a variable (a var statement that doesn't occur in the source code). Typically
|
||||
* used to create assignmnent of {@code :callee} to the function name symbol in self-referential function
|
||||
* expressions as well as for assignment of {@code :arguments} to {@code arguments}.
|
||||
*
|
||||
* @param name the ident node identifying the variable to initialize
|
||||
* @param initConstant the compiler constant it is initialized to
|
||||
* @param fn the function node the assignment is for
|
||||
* @return a var node with the appropriate assignment
|
||||
*/
|
||||
private VarNode createSyntheticInitializer(final IdentNode name, final CompilerConstants initConstant, final FunctionNode fn) {
|
||||
final IdentNode init = compilerConstantIdentifier(initConstant);
|
||||
assert init.getSymbol() != null && init.getSymbol().isBytecodeLocal();
|
||||
|
||||
final VarNode synthVar = new VarNode(fn.getLineNumber(), fn.getToken(), fn.getFinish(), name, init);
|
||||
|
||||
final Symbol nameSymbol = fn.getBody().getExistingSymbol(name.getName());
|
||||
assert nameSymbol != null;
|
||||
|
||||
return (VarNode)synthVar.setName((IdentNode)name.setSymbol(nameSymbol)).accept(this);
|
||||
}
|
||||
|
||||
private FunctionNode createSyntheticInitializers(final FunctionNode functionNode) {
|
||||
final List<VarNode> syntheticInitializers = new ArrayList<>(2);
|
||||
|
||||
// Must visit the new var nodes in the context of the body. We could also just set the new statements into the
|
||||
// block and then revisit the entire block, but that seems to be too much double work.
|
||||
final Block body = functionNode.getBody();
|
||||
lc.push(body);
|
||||
try {
|
||||
if (functionNode.usesSelfSymbol()) {
|
||||
// "var fn = :callee"
|
||||
syntheticInitializers.add(createSyntheticInitializer(functionNode.getIdent(), CALLEE, functionNode));
|
||||
}
|
||||
|
||||
if (functionNode.needsArguments()) {
|
||||
// "var arguments = :arguments"
|
||||
syntheticInitializers.add(createSyntheticInitializer(createImplicitIdentifier(ARGUMENTS_VAR.symbolName()),
|
||||
ARGUMENTS, functionNode));
|
||||
}
|
||||
|
||||
if (syntheticInitializers.isEmpty()) {
|
||||
return functionNode;
|
||||
}
|
||||
|
||||
for(final ListIterator<VarNode> it = syntheticInitializers.listIterator(); it.hasNext();) {
|
||||
it.set((VarNode)it.next().accept(this));
|
||||
}
|
||||
} finally {
|
||||
lc.pop(body);
|
||||
}
|
||||
|
||||
final List<Statement> stmts = body.getStatements();
|
||||
final List<Statement> newStatements = new ArrayList<>(stmts.size() + syntheticInitializers.size());
|
||||
newStatements.addAll(syntheticInitializers);
|
||||
newStatements.addAll(stmts);
|
||||
return functionNode.setBody(lc, body.setStatements(lc, newStatements));
|
||||
}
|
||||
|
||||
private Symbol defineGlobalSymbol(final Block block, final String name) {
|
||||
return defineSymbol(block, name, IS_GLOBAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a new symbol in the given block.
|
||||
*
|
||||
* @param block the block in which to define the symbol
|
||||
* @param name name of symbol.
|
||||
* @param symbolFlags Symbol flags.
|
||||
*
|
||||
* @return Symbol for given name or null for redefinition.
|
||||
*/
|
||||
private Symbol defineSymbol(final Block block, final String name, final int symbolFlags) {
|
||||
int flags = symbolFlags;
|
||||
Symbol symbol = findSymbol(block, name); // Locate symbol.
|
||||
final boolean isGlobal = (flags & KINDMASK) == IS_GLOBAL;
|
||||
|
||||
// Global variables are implicitly always scope variables too.
|
||||
if (isGlobal) {
|
||||
flags |= IS_SCOPE;
|
||||
}
|
||||
|
||||
if (lc.getCurrentFunction().isProgram()) {
|
||||
flags |= IS_PROGRAM_LEVEL;
|
||||
}
|
||||
|
||||
final boolean isParam = (flags & KINDMASK) == IS_PARAM;
|
||||
final boolean isVar = (flags & KINDMASK) == IS_VAR;
|
||||
|
||||
final FunctionNode function = lc.getFunction(block);
|
||||
if (symbol != null) {
|
||||
// Symbol was already defined. Check if it needs to be redefined.
|
||||
if (isParam) {
|
||||
if (!isLocal(function, symbol)) {
|
||||
// Not defined in this function. Create a new definition.
|
||||
symbol = null;
|
||||
} else if (symbol.isParam()) {
|
||||
// Duplicate parameter. Null return will force an error.
|
||||
throw new AssertionError("duplicate parameter");
|
||||
}
|
||||
} else if (isVar) {
|
||||
if ((flags & IS_INTERNAL) == IS_INTERNAL || (flags & IS_LET) == IS_LET) {
|
||||
// Always create a new definition.
|
||||
symbol = null;
|
||||
} else {
|
||||
// Not defined in this function. Create a new definition.
|
||||
if (!isLocal(function, symbol) || symbol.less(IS_VAR)) {
|
||||
symbol = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (symbol == null) {
|
||||
// If not found, then create a new one.
|
||||
Block symbolBlock;
|
||||
|
||||
// Determine where to create it.
|
||||
if (isVar && ((flags & IS_INTERNAL) == IS_INTERNAL || (flags & IS_LET) == IS_LET)) {
|
||||
symbolBlock = block; //internal vars are always defined in the block closest to them
|
||||
} else if (isGlobal) {
|
||||
symbolBlock = lc.getOutermostFunction().getBody();
|
||||
} else {
|
||||
symbolBlock = lc.getFunctionBody(function);
|
||||
}
|
||||
|
||||
// Create and add to appropriate block.
|
||||
symbol = createSymbol(name, flags);
|
||||
symbolBlock.putSymbol(lc, symbol);
|
||||
|
||||
if ((flags & IS_SCOPE) == 0) {
|
||||
// Initial assumption; symbol can lose its slot later
|
||||
symbol.setNeedsSlot(true);
|
||||
}
|
||||
} else if (symbol.less(flags)) {
|
||||
symbol.setFlags(flags);
|
||||
}
|
||||
|
||||
if((isVar || isParam) && env.useOptimisticTypes() && env.isOnDemandCompilation()) {
|
||||
env.declareLocalSymbol(name);
|
||||
}
|
||||
|
||||
return symbol;
|
||||
}
|
||||
|
||||
private <T extends Node> T end(final T node) {
|
||||
return end(node, true);
|
||||
}
|
||||
|
||||
private <T extends Node> T end(final T node, final boolean printNode) {
|
||||
if (debug) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("[LEAVE ").
|
||||
append(name(node)).
|
||||
append("] ").
|
||||
append(printNode ? node.toString() : "").
|
||||
append(" in '").
|
||||
append(lc.getCurrentFunction().getName()).
|
||||
append('\'');
|
||||
|
||||
if (node instanceof IdentNode) {
|
||||
final Symbol symbol = ((IdentNode)node).getSymbol();
|
||||
if (symbol == null) {
|
||||
sb.append(" <NO SYMBOL>");
|
||||
} else {
|
||||
sb.append(" <symbol=").append(symbol).append('>');
|
||||
}
|
||||
}
|
||||
|
||||
log.unindent();
|
||||
log.info(sb);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterBlock(final Block block) {
|
||||
start(block);
|
||||
block.clearSymbols();
|
||||
|
||||
if (lc.isFunctionBody()) {
|
||||
enterFunctionBody();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterCatchNode(final CatchNode catchNode) {
|
||||
final IdentNode exception = catchNode.getException();
|
||||
final Block block = lc.getCurrentBlock();
|
||||
|
||||
start(catchNode);
|
||||
|
||||
// define block-local exception variable
|
||||
final String exname = exception.getName();
|
||||
// If the name of the exception starts with ":e", this is a synthetic catch block, likely a catch-all. Its
|
||||
// symbol is naturally internal, and should be treated as such.
|
||||
final boolean isInternal = exname.startsWith(EXCEPTION_PREFIX.symbolName());
|
||||
defineSymbol(block, exname, IS_VAR | IS_LET | (isInternal ? IS_INTERNAL : 0) | HAS_OBJECT_VALUE);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void enterFunctionBody() {
|
||||
final FunctionNode functionNode = lc.getCurrentFunction();
|
||||
final Block body = lc.getCurrentBlock();
|
||||
|
||||
initFunctionWideVariables(functionNode, body);
|
||||
|
||||
if (functionNode.isProgram()) {
|
||||
initGlobalSymbols(body);
|
||||
} else if (!functionNode.isDeclared() && !functionNode.isAnonymous()) {
|
||||
// It's neither declared nor program - it's a function expression then; assign it a self-symbol unless it's
|
||||
// anonymous.
|
||||
final String name = functionNode.getIdent().getName();
|
||||
assert name != null;
|
||||
assert body.getExistingSymbol(name) == null;
|
||||
defineSymbol(body, name, IS_VAR | IS_FUNCTION_SELF | HAS_OBJECT_VALUE);
|
||||
if(functionNode.allVarsInScope()) { // basically, has deep eval
|
||||
lc.setFlag(functionNode, FunctionNode.USES_SELF_SYMBOL);
|
||||
}
|
||||
}
|
||||
|
||||
acceptDeclarations(functionNode, body);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterFunctionNode(final FunctionNode functionNode) {
|
||||
// TODO: once we have information on symbols used by nested functions, we can stop descending into nested
|
||||
// functions with on-demand compilation, e.g. add
|
||||
// if(!thisProperties.isEmpty() && env.isOnDemandCompilation()) {
|
||||
// return false;
|
||||
// }
|
||||
start(functionNode, false);
|
||||
|
||||
thisProperties.push(new HashSet<String>());
|
||||
|
||||
//an outermost function in our lexical context that is not a program
|
||||
//is possible - it is a function being compiled lazily
|
||||
if (functionNode.isDeclared()) {
|
||||
final Iterator<Block> blocks = lc.getBlocks();
|
||||
if (blocks.hasNext()) {
|
||||
defineSymbol(blocks.next(), functionNode.getIdent().getName(), IS_VAR);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterVarNode(final VarNode varNode) {
|
||||
start(varNode);
|
||||
defineSymbol(lc.getCurrentBlock(), varNode.getName().getName(), IS_VAR | (lc.getCurrentFunction().isProgram() ? IS_SCOPE : 0));
|
||||
return true;
|
||||
}
|
||||
|
||||
private Symbol exceptionSymbol() {
|
||||
return newObjectInternal(EXCEPTION_PREFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* This has to run before fix assignment types, store any type specializations for
|
||||
* paramters, then turn then to objects for the generic version of this method
|
||||
*
|
||||
* @param functionNode functionNode
|
||||
*/
|
||||
private FunctionNode finalizeParameters(final FunctionNode functionNode) {
|
||||
final List<IdentNode> newParams = new ArrayList<>();
|
||||
final boolean isVarArg = functionNode.isVarArg();
|
||||
|
||||
final Block body = functionNode.getBody();
|
||||
for (final IdentNode param : functionNode.getParameters()) {
|
||||
final Symbol paramSymbol = body.getExistingSymbol(param.getName());
|
||||
assert paramSymbol != null;
|
||||
assert paramSymbol.isParam() : paramSymbol + " " + paramSymbol.getFlags();
|
||||
newParams.add((IdentNode)param.setSymbol(paramSymbol));
|
||||
|
||||
// parameters should not be slots for a function that uses variable arity signature
|
||||
if (isVarArg) {
|
||||
paramSymbol.setNeedsSlot(false);
|
||||
}
|
||||
}
|
||||
|
||||
return functionNode.setParameters(lc, newParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for symbol in the lexical context starting from the given block.
|
||||
* @param name Symbol name.
|
||||
* @return Found symbol or null if not found.
|
||||
*/
|
||||
private Symbol findSymbol(final Block block, final String name) {
|
||||
for (final Iterator<Block> blocks = lc.getBlocks(block); blocks.hasNext();) {
|
||||
final Symbol symbol = blocks.next().getExistingSymbol(name);
|
||||
if (symbol != null) {
|
||||
return symbol;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the current function as one using any global symbol. The function and all its parent functions will all be
|
||||
* marked as needing parent scope.
|
||||
* @see FunctionNode#needsParentScope()
|
||||
*/
|
||||
private void functionUsesGlobalSymbol() {
|
||||
for (final Iterator<FunctionNode> fns = lc.getFunctions(); fns.hasNext();) {
|
||||
lc.setFlag(fns.next(), FunctionNode.USES_ANCESTOR_SCOPE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the current function as one using a scoped symbol. The block defining the symbol will be marked as needing
|
||||
* its own scope to hold the variable. If the symbol is defined outside of the current function, it and all
|
||||
* functions up to (but not including) the function containing the defining block will be marked as needing parent
|
||||
* function scope.
|
||||
* @see FunctionNode#needsParentScope()
|
||||
*/
|
||||
private void functionUsesScopeSymbol(final Symbol symbol) {
|
||||
final String name = symbol.getName();
|
||||
for (final Iterator<LexicalContextNode> contextNodeIter = lc.getAllNodes(); contextNodeIter.hasNext(); ) {
|
||||
final LexicalContextNode node = contextNodeIter.next();
|
||||
if (node instanceof Block) {
|
||||
final Block block = (Block)node;
|
||||
if (block.getExistingSymbol(name) != null) {
|
||||
assert lc.contains(block);
|
||||
lc.setBlockNeedsScope(block);
|
||||
break;
|
||||
}
|
||||
} else if (node instanceof FunctionNode) {
|
||||
lc.setFlag(node, FunctionNode.USES_ANCESTOR_SCOPE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares that the current function is using the symbol.
|
||||
* @param symbol the symbol used by the current function.
|
||||
*/
|
||||
private void functionUsesSymbol(final Symbol symbol) {
|
||||
assert symbol != null;
|
||||
if (symbol.isScope()) {
|
||||
if (symbol.isGlobal()) {
|
||||
functionUsesGlobalSymbol();
|
||||
} else {
|
||||
functionUsesScopeSymbol(symbol);
|
||||
}
|
||||
} else {
|
||||
assert !symbol.isGlobal(); // Every global is also scope
|
||||
}
|
||||
}
|
||||
|
||||
private void initCompileConstant(final CompilerConstants cc, final Block block, final int flags) {
|
||||
defineSymbol(block, cc.symbolName(), flags).setNeedsSlot(true);
|
||||
}
|
||||
|
||||
private void initFunctionWideVariables(final FunctionNode functionNode, final Block body) {
|
||||
initCompileConstant(CALLEE, body, IS_PARAM | IS_INTERNAL | HAS_OBJECT_VALUE);
|
||||
initCompileConstant(THIS, body, IS_PARAM | IS_THIS | HAS_OBJECT_VALUE);
|
||||
|
||||
if (functionNode.isVarArg()) {
|
||||
initCompileConstant(VARARGS, body, IS_PARAM | IS_INTERNAL | HAS_OBJECT_VALUE);
|
||||
if (functionNode.needsArguments()) {
|
||||
initCompileConstant(ARGUMENTS, body, IS_VAR | IS_INTERNAL | HAS_OBJECT_VALUE);
|
||||
defineSymbol(body, ARGUMENTS_VAR.symbolName(), IS_VAR | HAS_OBJECT_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
initParameters(functionNode, body);
|
||||
initCompileConstant(SCOPE, body, IS_VAR | IS_INTERNAL | HAS_OBJECT_VALUE);
|
||||
initCompileConstant(RETURN, body, IS_VAR | IS_INTERNAL);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Move any properties from the global map into the scope of this function (which must be a program function).
|
||||
* @param block the function node body for which to init scope vars
|
||||
*/
|
||||
private void initGlobalSymbols(final Block block) {
|
||||
final PropertyMap map = Context.getGlobalMap();
|
||||
|
||||
for (final Property property : map.getProperties()) {
|
||||
final Symbol symbol = defineGlobalSymbol(block, property.getKey());
|
||||
log.info("Added global symbol from property map ", symbol);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize parameters for function node.
|
||||
* @param functionNode the function node
|
||||
*/
|
||||
private void initParameters(final FunctionNode functionNode, final Block body) {
|
||||
final boolean isVarArg = functionNode.isVarArg();
|
||||
final boolean scopeParams = functionNode.allVarsInScope() || isVarArg;
|
||||
for (final IdentNode param : functionNode.getParameters()) {
|
||||
final Symbol symbol = defineSymbol(body, param.getName(), IS_PARAM);
|
||||
if(scopeParams) {
|
||||
// NOTE: this "set is scope" is a poor substitute for clear expression of where the symbol is stored.
|
||||
// It will force creation of scopes where they would otherwise not necessarily be needed (functions
|
||||
// using arguments object and other variable arity functions). Tracked by JDK-8038942.
|
||||
symbol.setIsScope();
|
||||
assert symbol.hasSlot();
|
||||
if(isVarArg) {
|
||||
symbol.setNeedsSlot(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the symbol local to (that is, defined in) the specified function?
|
||||
* @param function the function
|
||||
* @param symbol the symbol
|
||||
* @return true if the symbol is defined in the specified function
|
||||
*/
|
||||
private boolean isLocal(final FunctionNode function, final Symbol symbol) {
|
||||
final FunctionNode definingFn = lc.getDefiningFunction(symbol);
|
||||
assert definingFn != null;
|
||||
return definingFn == function;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveASSIGN(final BinaryNode binaryNode) {
|
||||
// If we're assigning a property of the this object ("this.foo = ..."), record it.
|
||||
|
||||
final Expression lhs = binaryNode.lhs();
|
||||
if (lhs instanceof AccessNode) {
|
||||
final AccessNode accessNode = (AccessNode) lhs;
|
||||
final Expression base = accessNode.getBase();
|
||||
if (base instanceof IdentNode) {
|
||||
final Symbol symbol = ((IdentNode)base).getSymbol();
|
||||
if(symbol.isThis()) {
|
||||
thisProperties.peek().add(accessNode.getProperty());
|
||||
}
|
||||
}
|
||||
}
|
||||
return binaryNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveDELETE(final UnaryNode unaryNode) {
|
||||
final FunctionNode currentFunctionNode = lc.getCurrentFunction();
|
||||
final boolean strictMode = currentFunctionNode.isStrict();
|
||||
final Expression rhs = unaryNode.getExpression();
|
||||
final Expression strictFlagNode = (Expression)LiteralNode.newInstance(unaryNode, strictMode).accept(this);
|
||||
|
||||
Request request = Request.DELETE;
|
||||
final List<Expression> args = new ArrayList<>();
|
||||
|
||||
if (rhs instanceof IdentNode) {
|
||||
final IdentNode ident = (IdentNode)rhs;
|
||||
// If this is a declared variable or a function parameter, delete always fails (except for globals).
|
||||
final String name = ident.getName();
|
||||
final Symbol symbol = ident.getSymbol();
|
||||
final boolean failDelete = strictMode || symbol.isParam() || (symbol.isVar() && !symbol.isProgramLevel());
|
||||
|
||||
if (failDelete && symbol.isThis()) {
|
||||
return LiteralNode.newInstance(unaryNode, true).accept(this);
|
||||
}
|
||||
final Expression literalNode = (Expression)LiteralNode.newInstance(unaryNode, name).accept(this);
|
||||
|
||||
if (!failDelete) {
|
||||
args.add(compilerConstantIdentifier(SCOPE));
|
||||
}
|
||||
args.add(literalNode);
|
||||
args.add(strictFlagNode);
|
||||
|
||||
if (failDelete) {
|
||||
request = Request.FAIL_DELETE;
|
||||
}
|
||||
} else if (rhs instanceof AccessNode) {
|
||||
final Expression base = ((AccessNode)rhs).getBase();
|
||||
final String property = ((AccessNode)rhs).getProperty();
|
||||
|
||||
args.add(base);
|
||||
args.add((Expression)LiteralNode.newInstance(unaryNode, property).accept(this));
|
||||
args.add(strictFlagNode);
|
||||
|
||||
} else if (rhs instanceof IndexNode) {
|
||||
final IndexNode indexNode = (IndexNode)rhs;
|
||||
final Expression base = indexNode.getBase();
|
||||
final Expression index = indexNode.getIndex();
|
||||
|
||||
args.add(base);
|
||||
args.add(index);
|
||||
args.add(strictFlagNode);
|
||||
|
||||
} else {
|
||||
return LiteralNode.newInstance(unaryNode, true).accept(this);
|
||||
}
|
||||
return new RuntimeNode(unaryNode, request, args).accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveForNode(final ForNode forNode) {
|
||||
if (forNode.isForIn()) {
|
||||
forNode.setIterator(newObjectInternal(ITERATOR_PREFIX)); //NASHORN-73
|
||||
}
|
||||
|
||||
return end(forNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveFunctionNode(FunctionNode functionNode) {
|
||||
|
||||
return markProgramBlock(
|
||||
removeUnusedSlots(
|
||||
createSyntheticInitializers(
|
||||
finalizeParameters(
|
||||
lc.applyTopFlags(functionNode))))
|
||||
.setThisProperties(lc, thisProperties.pop().size())
|
||||
.setState(lc, CompilationState.SYMBOLS_ASSIGNED));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveIdentNode(final IdentNode identNode) {
|
||||
final String name = identNode.getName();
|
||||
|
||||
if (identNode.isPropertyName()) {
|
||||
return identNode;
|
||||
}
|
||||
|
||||
final Block block = lc.getCurrentBlock();
|
||||
|
||||
Symbol symbol = findSymbol(block, name);
|
||||
|
||||
//If an existing symbol with the name is found, use that otherwise, declare a new one
|
||||
if (symbol != null) {
|
||||
log.info("Existing symbol = ", symbol);
|
||||
if (symbol.isFunctionSelf()) {
|
||||
final FunctionNode functionNode = lc.getDefiningFunction(symbol);
|
||||
assert functionNode != null;
|
||||
assert lc.getFunctionBody(functionNode).getExistingSymbol(CALLEE.symbolName()) != null;
|
||||
lc.setFlag(functionNode, FunctionNode.USES_SELF_SYMBOL);
|
||||
}
|
||||
|
||||
// if symbol is non-local or we're in a with block, we need to put symbol in scope (if it isn't already)
|
||||
maybeForceScope(symbol);
|
||||
} else {
|
||||
log.info("No symbol exists. Declare as global: ", symbol);
|
||||
symbol = defineGlobalSymbol(block, name);
|
||||
Symbol.setSymbolIsScope(lc, symbol);
|
||||
}
|
||||
|
||||
functionUsesSymbol(symbol);
|
||||
|
||||
if (!identNode.isInitializedHere()) {
|
||||
symbol.increaseUseCount();
|
||||
}
|
||||
|
||||
return end(identNode.setSymbol(symbol));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveSwitchNode(final SwitchNode switchNode) {
|
||||
// We only need a symbol for the tag if it's not an integer switch node
|
||||
if(!switchNode.isInteger()) {
|
||||
switchNode.setTag(newObjectInternal(SWITCH_TAG_PREFIX));
|
||||
}
|
||||
return switchNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveTryNode(final TryNode tryNode) {
|
||||
tryNode.setException(exceptionSymbol());
|
||||
if (tryNode.getFinallyBody() != null) {
|
||||
tryNode.setFinallyCatchAll(exceptionSymbol());
|
||||
}
|
||||
|
||||
end(tryNode);
|
||||
|
||||
return tryNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveTYPEOF(final UnaryNode unaryNode) {
|
||||
final Expression rhs = unaryNode.getExpression();
|
||||
|
||||
final List<Expression> args = new ArrayList<>();
|
||||
if (rhs instanceof IdentNode && !isParamOrVar((IdentNode)rhs)) {
|
||||
args.add(compilerConstantIdentifier(SCOPE));
|
||||
args.add((Expression)LiteralNode.newInstance(rhs, ((IdentNode)rhs).getName()).accept(this)); //null
|
||||
} else {
|
||||
args.add(rhs);
|
||||
args.add((Expression)LiteralNode.newInstance(unaryNode).accept(this)); //null, do not reuse token of identifier rhs, it can be e.g. 'this'
|
||||
}
|
||||
|
||||
final Node runtimeNode = new RuntimeNode(unaryNode, Request.TYPEOF, args).accept(this);
|
||||
|
||||
end(unaryNode);
|
||||
|
||||
return runtimeNode;
|
||||
}
|
||||
|
||||
private FunctionNode markProgramBlock(FunctionNode functionNode) {
|
||||
if (env.isOnDemandCompilation() || !functionNode.isProgram()) {
|
||||
return functionNode;
|
||||
}
|
||||
|
||||
assert functionNode.getId() == 1;
|
||||
return functionNode.setBody(lc, functionNode.getBody().setFlag(lc, Block.IS_GLOBAL_SCOPE));
|
||||
}
|
||||
|
||||
/**
|
||||
* If the symbol isn't already a scope symbol, but it needs to be (see {@link #symbolNeedsToBeScope(Symbol)}, it is
|
||||
* promoted to a scope symbol and its block marked as needing a scope.
|
||||
* @param symbol the symbol that might be scoped
|
||||
*/
|
||||
private void maybeForceScope(final Symbol symbol) {
|
||||
if (!symbol.isScope() && symbolNeedsToBeScope(symbol)) {
|
||||
Symbol.setSymbolIsScope(lc, symbol);
|
||||
}
|
||||
}
|
||||
|
||||
private Symbol newInternal(final CompilerConstants cc, final int flags) {
|
||||
return defineSymbol(lc.getCurrentBlock(), lc.getCurrentFunction().uniqueName(cc.symbolName()), IS_VAR | IS_INTERNAL | flags); //NASHORN-73
|
||||
}
|
||||
|
||||
private Symbol newObjectInternal(final CompilerConstants cc) {
|
||||
return newInternal(cc, HAS_OBJECT_VALUE);
|
||||
}
|
||||
|
||||
private boolean start(final Node node) {
|
||||
return start(node, true);
|
||||
}
|
||||
|
||||
private boolean start(final Node node, final boolean printNode) {
|
||||
if (debug) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("[ENTER ").
|
||||
append(name(node)).
|
||||
append("] ").
|
||||
append(printNode ? node.toString() : "").
|
||||
append(" in '").
|
||||
append(lc.getCurrentFunction().getName()).
|
||||
append("'");
|
||||
log.info(sb);
|
||||
log.indent();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the symbol has to be a scope symbol. In general terms, it has to be a scope symbol if it can only
|
||||
* be reached from the current block by traversing a function node, a split node, or a with node.
|
||||
* @param symbol the symbol checked for needing to be a scope symbol
|
||||
* @return true if the symbol has to be a scope symbol.
|
||||
*/
|
||||
private boolean symbolNeedsToBeScope(final Symbol symbol) {
|
||||
if (symbol.isThis() || symbol.isInternal()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lc.getCurrentFunction().allVarsInScope()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean previousWasBlock = false;
|
||||
for (final Iterator<LexicalContextNode> it = lc.getAllNodes(); it.hasNext();) {
|
||||
final LexicalContextNode node = it.next();
|
||||
if (node instanceof FunctionNode || node instanceof SplitNode || isSplitArray(node)) {
|
||||
// We reached the function boundary or a splitting boundary without seeing a definition for the symbol.
|
||||
// It needs to be in scope.
|
||||
return true;
|
||||
} else if (node instanceof WithNode) {
|
||||
if (previousWasBlock) {
|
||||
// We reached a WithNode; the symbol must be scoped. Note that if the WithNode was not immediately
|
||||
// preceded by a block, this means we're currently processing its expression, not its body,
|
||||
// therefore it doesn't count.
|
||||
return true;
|
||||
}
|
||||
previousWasBlock = false;
|
||||
} else if (node instanceof Block) {
|
||||
if (((Block)node).getExistingSymbol(symbol.getName()) == symbol) {
|
||||
// We reached the block that defines the symbol without reaching either the function boundary, or a
|
||||
// WithNode. The symbol need not be scoped.
|
||||
return false;
|
||||
}
|
||||
previousWasBlock = true;
|
||||
} else {
|
||||
previousWasBlock = false;
|
||||
}
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
private static boolean isSplitArray(final LexicalContextNode expr) {
|
||||
if(!(expr instanceof ArrayLiteralNode)) {
|
||||
return false;
|
||||
}
|
||||
final List<ArrayUnit> units = ((ArrayLiteralNode)expr).getUnits();
|
||||
return !(units == null || units.isEmpty());
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -32,10 +32,10 @@ import static jdk.nashorn.internal.codegen.Condition.LE;
|
||||
import static jdk.nashorn.internal.codegen.Condition.LT;
|
||||
import static jdk.nashorn.internal.codegen.Condition.NE;
|
||||
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.BinaryNode;
|
||||
import jdk.nashorn.internal.ir.Expression;
|
||||
import jdk.nashorn.internal.ir.TernaryNode;
|
||||
import jdk.nashorn.internal.ir.JoinPredecessorExpression;
|
||||
import jdk.nashorn.internal.ir.LocalVariableConversion;
|
||||
import jdk.nashorn.internal.ir.UnaryNode;
|
||||
|
||||
/**
|
||||
@ -71,13 +71,7 @@ final class BranchOptimizer {
|
||||
break;
|
||||
}
|
||||
|
||||
// convert to boolean
|
||||
codegen.load(unaryNode, Type.BOOLEAN);
|
||||
if (state) {
|
||||
method.ifne(label);
|
||||
} else {
|
||||
method.ifeq(label);
|
||||
}
|
||||
loadTestAndJump(unaryNode, label, state);
|
||||
}
|
||||
|
||||
private void branchOptimizer(final BinaryNode binaryNode, final Label label, final boolean state) {
|
||||
@ -88,56 +82,56 @@ final class BranchOptimizer {
|
||||
case AND:
|
||||
if (state) {
|
||||
final Label skip = new Label("skip");
|
||||
branchOptimizer(lhs, skip, false);
|
||||
branchOptimizer(rhs, label, true);
|
||||
optimizeLogicalOperand(lhs, skip, false, false);
|
||||
optimizeLogicalOperand(rhs, label, true, true);
|
||||
method.label(skip);
|
||||
} else {
|
||||
branchOptimizer(lhs, label, false);
|
||||
branchOptimizer(rhs, label, false);
|
||||
optimizeLogicalOperand(lhs, label, false, false);
|
||||
optimizeLogicalOperand(rhs, label, false, true);
|
||||
}
|
||||
return;
|
||||
|
||||
case OR:
|
||||
if (state) {
|
||||
branchOptimizer(lhs, label, true);
|
||||
branchOptimizer(rhs, label, true);
|
||||
optimizeLogicalOperand(lhs, label, true, false);
|
||||
optimizeLogicalOperand(rhs, label, true, true);
|
||||
} else {
|
||||
final Label skip = new Label("skip");
|
||||
branchOptimizer(lhs, skip, true);
|
||||
branchOptimizer(rhs, label, false);
|
||||
optimizeLogicalOperand(lhs, skip, true, false);
|
||||
optimizeLogicalOperand(rhs, label, false, true);
|
||||
method.label(skip);
|
||||
}
|
||||
return;
|
||||
|
||||
case EQ:
|
||||
case EQ_STRICT:
|
||||
codegen.loadBinaryOperands(lhs, rhs, Type.widest(lhs.getType(), rhs.getType()));
|
||||
codegen.loadBinaryOperands(binaryNode);
|
||||
method.conditionalJump(state ? EQ : NE, true, label);
|
||||
return;
|
||||
|
||||
case NE:
|
||||
case NE_STRICT:
|
||||
codegen.loadBinaryOperands(lhs, rhs, Type.widest(lhs.getType(), rhs.getType()));
|
||||
codegen.loadBinaryOperands(binaryNode);
|
||||
method.conditionalJump(state ? NE : EQ, true, label);
|
||||
return;
|
||||
|
||||
case GE:
|
||||
codegen.loadBinaryOperands(lhs, rhs, Type.widest(lhs.getType(), rhs.getType()));
|
||||
codegen.loadBinaryOperands(binaryNode);
|
||||
method.conditionalJump(state ? GE : LT, false, label);
|
||||
return;
|
||||
|
||||
case GT:
|
||||
codegen.loadBinaryOperands(lhs, rhs, Type.widest(lhs.getType(), rhs.getType()));
|
||||
codegen.loadBinaryOperands(binaryNode);
|
||||
method.conditionalJump(state ? GT : LE, false, label);
|
||||
return;
|
||||
|
||||
case LE:
|
||||
codegen.loadBinaryOperands(lhs, rhs, Type.widest(lhs.getType(), rhs.getType()));
|
||||
codegen.loadBinaryOperands(binaryNode);
|
||||
method.conditionalJump(state ? LE : GT, true, label);
|
||||
return;
|
||||
|
||||
case LT:
|
||||
codegen.loadBinaryOperands(lhs, rhs, Type.widest(lhs.getType(), rhs.getType()));
|
||||
codegen.loadBinaryOperands(binaryNode);
|
||||
method.conditionalJump(state ? LT : GE, true, label);
|
||||
return;
|
||||
|
||||
@ -145,29 +139,40 @@ final class BranchOptimizer {
|
||||
break;
|
||||
}
|
||||
|
||||
codegen.load(binaryNode, Type.BOOLEAN);
|
||||
if (state) {
|
||||
method.ifne(label);
|
||||
} else {
|
||||
method.ifeq(label);
|
||||
}
|
||||
loadTestAndJump(binaryNode, label, state);
|
||||
}
|
||||
|
||||
private void optimizeLogicalOperand(final Expression expr, final Label label, final boolean state, final boolean isRhs) {
|
||||
final JoinPredecessorExpression jpexpr = (JoinPredecessorExpression)expr;
|
||||
if(LocalVariableConversion.hasLiveConversion(jpexpr)) {
|
||||
final Label after = new Label("after");
|
||||
branchOptimizer(jpexpr.getExpression(), after, !state);
|
||||
method.beforeJoinPoint(jpexpr);
|
||||
method._goto(label);
|
||||
method.label(after);
|
||||
if(isRhs) {
|
||||
method.beforeJoinPoint(jpexpr);
|
||||
}
|
||||
} else {
|
||||
branchOptimizer(jpexpr.getExpression(), label, state);
|
||||
}
|
||||
}
|
||||
private void branchOptimizer(final Expression node, final Label label, final boolean state) {
|
||||
if (!(node instanceof TernaryNode)) {
|
||||
|
||||
if (node instanceof BinaryNode) {
|
||||
branchOptimizer((BinaryNode)node, label, state);
|
||||
return;
|
||||
}
|
||||
|
||||
if (node instanceof UnaryNode) {
|
||||
branchOptimizer((UnaryNode)node, label, state);
|
||||
return;
|
||||
}
|
||||
if (node instanceof BinaryNode) {
|
||||
branchOptimizer((BinaryNode)node, label, state);
|
||||
return;
|
||||
}
|
||||
|
||||
codegen.load(node, Type.BOOLEAN);
|
||||
if (node instanceof UnaryNode) {
|
||||
branchOptimizer((UnaryNode)node, label, state);
|
||||
return;
|
||||
}
|
||||
|
||||
loadTestAndJump(node, label, state);
|
||||
}
|
||||
|
||||
private void loadTestAndJump(final Expression node, final Label label, final boolean state) {
|
||||
codegen.loadExpressionAsBoolean(node);
|
||||
if (state) {
|
||||
method.ifne(label);
|
||||
} else {
|
||||
|
||||
@ -489,9 +489,7 @@ public class ClassEmitter implements Emitter {
|
||||
null,
|
||||
null);
|
||||
|
||||
final MethodEmitter method = new MethodEmitter(this, mv, functionNode);
|
||||
method.setParameterTypes(signature.getParamTypes());
|
||||
return method;
|
||||
return new MethodEmitter(this, mv, functionNode);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -508,9 +506,7 @@ public class ClassEmitter implements Emitter {
|
||||
null,
|
||||
null);
|
||||
|
||||
final MethodEmitter method = new MethodEmitter(this, mv, functionNode);
|
||||
method.setParameterTypes(new FunctionSignature(functionNode).getParamTypes());
|
||||
return method;
|
||||
return new MethodEmitter(this, mv, functionNode);
|
||||
}
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -31,7 +31,6 @@ import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import jdk.nashorn.internal.IntDeque;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.Block;
|
||||
@ -39,7 +38,6 @@ import jdk.nashorn.internal.ir.FunctionNode;
|
||||
import jdk.nashorn.internal.ir.LexicalContext;
|
||||
import jdk.nashorn.internal.ir.LexicalContextNode;
|
||||
import jdk.nashorn.internal.ir.Node;
|
||||
import jdk.nashorn.internal.ir.SplitNode;
|
||||
import jdk.nashorn.internal.ir.Symbol;
|
||||
import jdk.nashorn.internal.ir.WithNode;
|
||||
|
||||
@ -89,18 +87,18 @@ final class CodeGeneratorLexicalContext extends LexicalContext {
|
||||
dynamicScopeCount++;
|
||||
}
|
||||
splitNodes.push(0);
|
||||
} else if (node instanceof SplitNode) {
|
||||
enterSplitNode();
|
||||
}
|
||||
return super.push(node);
|
||||
}
|
||||
|
||||
void enterSplitNode() {
|
||||
splitNodes.getAndIncrement();
|
||||
pushFreeSlots(methodEmitters.peek().getUsedSlotsWithLiveTemporaries());
|
||||
}
|
||||
|
||||
void exitSplitNode() {
|
||||
splitNodes.decrementAndGet();
|
||||
final int count = splitNodes.decrementAndGet();
|
||||
assert count >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -116,8 +114,6 @@ final class CodeGeneratorLexicalContext extends LexicalContext {
|
||||
}
|
||||
assert splitNodes.peek() == 0;
|
||||
splitNodes.pop();
|
||||
} else if (node instanceof SplitNode) {
|
||||
exitSplitNode();
|
||||
}
|
||||
return popped;
|
||||
}
|
||||
@ -208,60 +204,67 @@ final class CodeGeneratorLexicalContext extends LexicalContext {
|
||||
return getScopeCall(unit, symbol, valueType, valueType, null, flags);
|
||||
}
|
||||
|
||||
void onEnterBlock(final Block block) {
|
||||
pushFreeSlots(assignSlots(block, isFunctionBody() ? 0 : getUsedSlotCount()));
|
||||
}
|
||||
|
||||
void nextFreeSlot(final Block block) {
|
||||
final int nextFreeSlot = isFunctionBody() ? 0 : getUsedSlotCount();
|
||||
private void pushFreeSlots(final int freeSlots) {
|
||||
if (nextFreeSlotsSize == nextFreeSlots.length) {
|
||||
final int[] newNextFreeSlots = new int[nextFreeSlotsSize * 2];
|
||||
System.arraycopy(nextFreeSlots, 0, newNextFreeSlots, 0, nextFreeSlotsSize);
|
||||
nextFreeSlots = newNextFreeSlots;
|
||||
}
|
||||
nextFreeSlots[nextFreeSlotsSize++] = assignSlots(block, nextFreeSlot);
|
||||
nextFreeSlots[nextFreeSlotsSize++] = freeSlots;
|
||||
}
|
||||
|
||||
int getUsedSlotCount() {
|
||||
return nextFreeSlots[nextFreeSlotsSize - 1];
|
||||
}
|
||||
|
||||
void releaseBlockSlots(final boolean optimistic) {
|
||||
void releaseSlots() {
|
||||
--nextFreeSlotsSize;
|
||||
if(optimistic) {
|
||||
slotTypesDescriptors.peek().setLength(nextFreeSlots[nextFreeSlotsSize]);
|
||||
final int undefinedFromSlot = nextFreeSlotsSize == 0 ? 0 : nextFreeSlots[nextFreeSlotsSize - 1];
|
||||
if(!slotTypesDescriptors.isEmpty()) {
|
||||
slotTypesDescriptors.peek().setLength(undefinedFromSlot);
|
||||
}
|
||||
methodEmitters.peek().undefineLocalVariables(undefinedFromSlot, false);
|
||||
}
|
||||
|
||||
private int assignSlots(final Block block, final int firstSlot) {
|
||||
int nextSlot = firstSlot;
|
||||
int fromSlot = firstSlot;
|
||||
final MethodEmitter method = methodEmitters.peek();
|
||||
for (final Symbol symbol : block.getSymbols()) {
|
||||
if (symbol.hasSlot()) {
|
||||
symbol.setSlot(nextSlot);
|
||||
nextSlot += symbol.slotCount();
|
||||
symbol.setFirstSlot(fromSlot);
|
||||
final int toSlot = fromSlot + symbol.slotCount();
|
||||
method.defineBlockLocalVariable(fromSlot, toSlot);
|
||||
fromSlot = toSlot;
|
||||
}
|
||||
}
|
||||
methodEmitters.peek().ensureLocalVariableCount(nextSlot);
|
||||
return nextSlot;
|
||||
return fromSlot;
|
||||
}
|
||||
|
||||
static Type getTypeForSlotDescriptor(final char typeDesc) {
|
||||
// Recognizing both lowercase and uppercase as we're using both to signify symbol boundaries; see
|
||||
// MethodEmitter.markSymbolBoundariesInLvarTypesDescriptor().
|
||||
switch(typeDesc) {
|
||||
case 'I': {
|
||||
case 'I':
|
||||
case 'i':
|
||||
return Type.INT;
|
||||
}
|
||||
case 'J': {
|
||||
case 'J':
|
||||
case 'j':
|
||||
return Type.LONG;
|
||||
}
|
||||
case 'D': {
|
||||
case 'D':
|
||||
case 'd':
|
||||
return Type.NUMBER;
|
||||
}
|
||||
case 'A': {
|
||||
case 'A':
|
||||
case 'a':
|
||||
return Type.OBJECT;
|
||||
}
|
||||
case 'U': {
|
||||
case 'U':
|
||||
case 'u':
|
||||
return Type.UNKNOWN;
|
||||
}
|
||||
default: {
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -277,12 +280,8 @@ final class CodeGeneratorLexicalContext extends LexicalContext {
|
||||
return discard.peek();
|
||||
}
|
||||
|
||||
int quickSlot(final Symbol symbol) {
|
||||
final int quickSlot = nextFreeSlots[nextFreeSlotsSize - 1];
|
||||
nextFreeSlots[nextFreeSlotsSize - 1] = quickSlot + symbol.slotCount();
|
||||
methodEmitters.peek().ensureLocalVariableCount(quickSlot);
|
||||
return quickSlot;
|
||||
int quickSlot(final Type type) {
|
||||
return methodEmitters.peek().defineTemporaryLocalVariable(type.getSlots());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -34,7 +34,6 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.AccessNode;
|
||||
import jdk.nashorn.internal.ir.Expression;
|
||||
@ -44,11 +43,12 @@ import jdk.nashorn.internal.ir.IndexNode;
|
||||
import jdk.nashorn.internal.ir.Optimistic;
|
||||
import jdk.nashorn.internal.objects.NativeArray;
|
||||
import jdk.nashorn.internal.runtime.Context;
|
||||
import jdk.nashorn.internal.runtime.JSType;
|
||||
import jdk.nashorn.internal.runtime.FindProperty;
|
||||
import jdk.nashorn.internal.runtime.JSType;
|
||||
import jdk.nashorn.internal.runtime.Property;
|
||||
import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
|
||||
import jdk.nashorn.internal.runtime.ScriptObject;
|
||||
import jdk.nashorn.internal.runtime.ScriptRuntime;
|
||||
|
||||
/**
|
||||
* Class for managing metadata during a compilation, e.g. which phases
|
||||
@ -104,10 +104,10 @@ public final class CompilationEnvironment {
|
||||
CompilationPhase.CONSTANT_FOLDING_PHASE,
|
||||
CompilationPhase.LOWERING_PHASE,
|
||||
CompilationPhase.SPLITTING_PHASE,
|
||||
CompilationPhase.ATTRIBUTION_PHASE,
|
||||
CompilationPhase.RANGE_ANALYSIS_PHASE,
|
||||
CompilationPhase.TYPE_FINALIZATION_PHASE,
|
||||
CompilationPhase.SYMBOL_ASSIGNMENT_PHASE,
|
||||
CompilationPhase.SCOPE_DEPTH_COMPUTATION_PHASE,
|
||||
CompilationPhase.OPTIMISTIC_TYPE_ASSIGNMENT_PHASE,
|
||||
CompilationPhase.LOCAL_VARIABLE_TYPE_CALCULATION_PHASE,
|
||||
CompilationPhase.BYTECODE_GENERATION_PHASE
|
||||
};
|
||||
|
||||
@ -402,6 +402,18 @@ public final class CompilationEnvironment {
|
||||
return mostOptimisticType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the compilation environment that a symbol of a particular name is a local variables in a function. Used
|
||||
* with on-demand compilation, this will hide symbols of the same name from a parent scope and prevent them from
|
||||
* being mistakenly found by the optimistic types heuristics.
|
||||
* @param symbolName the name of the symbols to declare.
|
||||
*/
|
||||
void declareLocalSymbol(final String symbolName) {
|
||||
assert useOptimisticTypes() && isOnDemandCompilation() && runtimeScope != null;
|
||||
if(runtimeScope.findProperty(symbolName, false) == null) {
|
||||
runtimeScope.set(symbolName, ScriptRuntime.UNDEFINED, true);
|
||||
}
|
||||
}
|
||||
|
||||
private Type getEvaluatedType(final Optimistic expr) {
|
||||
if(expr instanceof IdentNode) {
|
||||
@ -412,7 +424,7 @@ public final class CompilationEnvironment {
|
||||
if(!(base instanceof ScriptObject)) {
|
||||
return null;
|
||||
}
|
||||
return getPropertyType((ScriptObject)base, accessNode.getProperty().getName());
|
||||
return getPropertyType((ScriptObject)base, accessNode.getProperty());
|
||||
} else if(expr instanceof IndexNode) {
|
||||
final IndexNode indexNode = (IndexNode)expr;
|
||||
final Object base = evaluateSafely(indexNode.getBase());
|
||||
@ -453,8 +465,12 @@ public final class CompilationEnvironment {
|
||||
}
|
||||
|
||||
// Safely evaluate the property, and return the narrowest type for the actual value (e.g. Type.INT for a boxed
|
||||
// integer).
|
||||
return Type.typeFor(JSType.unboxedFieldType(property.getObjectValue(owner, owner)));
|
||||
// integer). Continue not making guesses for undefined.
|
||||
final Object value = property.getObjectValue(owner, owner);
|
||||
if(value == ScriptRuntime.UNDEFINED) {
|
||||
return null;
|
||||
}
|
||||
return Type.typeFor(JSType.unboxedFieldType(value));
|
||||
}
|
||||
|
||||
private Object evaluateSafely(final Expression expr) {
|
||||
@ -466,7 +482,7 @@ public final class CompilationEnvironment {
|
||||
if(!(base instanceof ScriptObject)) {
|
||||
return null;
|
||||
}
|
||||
return evaluatePropertySafely((ScriptObject)base, accessNode.getProperty().getName());
|
||||
return evaluatePropertySafely((ScriptObject)base, accessNode.getProperty());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -25,34 +25,21 @@
|
||||
|
||||
package jdk.nashorn.internal.codegen;
|
||||
|
||||
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.ATTR;
|
||||
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.CONSTANT_FOLDED;
|
||||
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.FINALIZED;
|
||||
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.INITIALIZED;
|
||||
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.LOCAL_VARIABLE_TYPES_CALCULATED;
|
||||
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.LOWERED;
|
||||
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.OPTIMISTIC_TYPES_ASSIGNED;
|
||||
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.PARSED;
|
||||
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SCOPE_DEPTHS_COMPUTED;
|
||||
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SPLIT;
|
||||
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SYMBOLS_ASSIGNED;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
import jdk.nashorn.internal.codegen.types.Range;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.Expression;
|
||||
import jdk.nashorn.internal.ir.FunctionNode;
|
||||
import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
|
||||
import jdk.nashorn.internal.ir.LexicalContext;
|
||||
import jdk.nashorn.internal.ir.Node;
|
||||
import jdk.nashorn.internal.ir.ReturnNode;
|
||||
import jdk.nashorn.internal.ir.Symbol;
|
||||
import jdk.nashorn.internal.ir.TemporarySymbols;
|
||||
import jdk.nashorn.internal.ir.debug.ASTWriter;
|
||||
import jdk.nashorn.internal.ir.debug.PrintVisitor;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
import jdk.nashorn.internal.runtime.ScriptEnvironment;
|
||||
import jdk.nashorn.internal.runtime.Timing;
|
||||
|
||||
@ -140,169 +127,19 @@ enum CompilationPhase {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Attribution Assign symbols and types to all nodes.
|
||||
*/
|
||||
ATTRIBUTION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, SPLIT)) {
|
||||
SYMBOL_ASSIGNMENT_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, SPLIT)) {
|
||||
@Override
|
||||
FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
|
||||
final TemporarySymbols ts = compiler.getTemporarySymbols();
|
||||
final FunctionNode newFunctionNode =
|
||||
(FunctionNode)enterAttr(fn, ts).
|
||||
accept(new Attr(compiler.getCompilationEnvironment(), ts));
|
||||
|
||||
if (compiler.getEnv()._print_mem_usage) {
|
||||
compiler.getLogger().info("Attr temporary symbol count:", ts.getTotalSymbolCount());
|
||||
}
|
||||
|
||||
return newFunctionNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pessimistically set all lazy functions' return types to Object
|
||||
* and the function symbols to object
|
||||
* @param functionNode node where to start iterating
|
||||
*/
|
||||
private FunctionNode enterAttr(final FunctionNode functionNode, final TemporarySymbols ts) {
|
||||
return (FunctionNode)functionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
|
||||
@Override
|
||||
public Node leaveFunctionNode(final FunctionNode node) {
|
||||
return node.setReturnType(lc, Type.UNKNOWN).setSymbol(lc, null);
|
||||
}
|
||||
});
|
||||
return (FunctionNode)fn.accept(new AssignSymbols(compiler.getCompilationEnvironment()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[Type Attribution]";
|
||||
return "[Symbol Assignment]";
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 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, SPLIT, ATTR)) {
|
||||
@Override
|
||||
FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
|
||||
if (!compiler.getEnv()._range_analysis) {
|
||||
return fn;
|
||||
}
|
||||
|
||||
FunctionNode newFunctionNode = (FunctionNode)fn.accept(new RangeAnalyzer(compiler.getCompilationEnvironment()));
|
||||
final List<ReturnNode> returns = new ArrayList<>();
|
||||
|
||||
newFunctionNode = (FunctionNode)newFunctionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
|
||||
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(lc, 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) {
|
||||
if (node instanceof Expression) {
|
||||
final Expression expr = (Expression)node;
|
||||
final Symbol symbol = expr.getSymbol();
|
||||
if (symbol != null) {
|
||||
final Range range = symbol.getRange();
|
||||
final Type symbolType = symbol.getSymbolType();
|
||||
|
||||
if (!symbolType.isUnknown() && !symbolType.isNumeric()) {
|
||||
return expr;
|
||||
}
|
||||
|
||||
final Type rangeType = range.getType();
|
||||
if (!rangeType.isUnknown() && !Type.areEquivalent(symbolType, rangeType) && Type.widest(symbolType, rangeType) == symbolType) { //we can narrow range
|
||||
compiler.getCompilationEnvironment().getContext().getLogger(RangeAnalyzer.class).info("[", lc.getCurrentFunction().getName(), "] ", symbol, " can be ", range.getType(), " ", symbol.getRange());
|
||||
return expr.setSymbol(lc, 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]";
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* FinalizeTypes
|
||||
*
|
||||
* This pass finalizes the types for nodes. If Attr created wider types than
|
||||
* known during the first pass, convert nodes are inserted or access nodes
|
||||
* are specialized where scope accesses.
|
||||
*
|
||||
* Runtime nodes may be removed and primitivized or reintroduced depending
|
||||
* on information that was established in Attr.
|
||||
*
|
||||
* Contract: all variables must have slot assignments and scope assignments
|
||||
* before type finalization.
|
||||
*/
|
||||
TYPE_FINALIZATION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR, SPLIT)) {
|
||||
@Override
|
||||
FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
|
||||
final ScriptEnvironment env = compiler.getEnv();
|
||||
|
||||
final FunctionNode newFunctionNode = (FunctionNode)fn.accept(new FinalizeTypes(compiler.getCompilationEnvironment()));
|
||||
|
||||
if (env._print_lower_ast) {
|
||||
env.getErr().println(new ASTWriter(newFunctionNode));
|
||||
}
|
||||
|
||||
if (env._print_lower_parse) {
|
||||
env.getErr().println(new PrintVisitor(newFunctionNode));
|
||||
}
|
||||
|
||||
return newFunctionNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[Type Finalization]";
|
||||
}
|
||||
},
|
||||
|
||||
SCOPE_DEPTH_COMPUTATION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR, SPLIT, FINALIZED)) {
|
||||
SCOPE_DEPTH_COMPUTATION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, SPLIT, SYMBOLS_ASSIGNED)) {
|
||||
@Override
|
||||
FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
|
||||
return (FunctionNode)fn.accept(new FindScopeDepths(compiler));
|
||||
@ -314,19 +151,55 @@ enum CompilationPhase {
|
||||
}
|
||||
},
|
||||
|
||||
OPTIMISTIC_TYPE_ASSIGNMENT_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, SPLIT, SYMBOLS_ASSIGNED, SCOPE_DEPTHS_COMPUTED)) {
|
||||
@Override
|
||||
FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
|
||||
if(compiler.getCompilationEnvironment().useOptimisticTypes()) {
|
||||
return (FunctionNode)fn.accept(new OptimisticTypesCalculator(compiler.getCompilationEnvironment()));
|
||||
}
|
||||
return fn.setState(null, OPTIMISTIC_TYPES_ASSIGNED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[Optimistic Type Assignment]";
|
||||
}
|
||||
},
|
||||
|
||||
LOCAL_VARIABLE_TYPE_CALCULATION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, SPLIT, SYMBOLS_ASSIGNED, SCOPE_DEPTHS_COMPUTED, OPTIMISTIC_TYPES_ASSIGNED)) {
|
||||
@Override
|
||||
FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
|
||||
return (FunctionNode)fn.accept(new LocalVariableTypesCalculator(compiler.getCompilationEnvironment()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[Local Variable Type Calculation]";
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Bytecode generation:
|
||||
*
|
||||
* Generate the byte code class(es) resulting from the compiled FunctionNode
|
||||
*/
|
||||
BYTECODE_GENERATION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR, SPLIT, FINALIZED, SCOPE_DEPTHS_COMPUTED)) {
|
||||
BYTECODE_GENERATION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, SPLIT, SYMBOLS_ASSIGNED, SCOPE_DEPTHS_COMPUTED, OPTIMISTIC_TYPES_ASSIGNED, LOCAL_VARIABLE_TYPES_CALCULATED)) {
|
||||
@Override
|
||||
FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
|
||||
final ScriptEnvironment env = compiler.getEnv();
|
||||
|
||||
if (env._print_lower_ast) {
|
||||
env.getErr().println(new ASTWriter(fn));
|
||||
}
|
||||
|
||||
if (env._print_lower_parse) {
|
||||
env.getErr().println(new PrintVisitor(fn));
|
||||
}
|
||||
|
||||
FunctionNode newFunctionNode = fn;
|
||||
|
||||
final CodeGenerator codegen = new CodeGenerator(compiler);
|
||||
try {
|
||||
final CodeGenerator codegen = new CodeGenerator(compiler);
|
||||
newFunctionNode = (FunctionNode)newFunctionNode.accept(codegen);
|
||||
codegen.generateScopeCalls();
|
||||
} catch (final VerifyError e) {
|
||||
@ -338,6 +211,9 @@ enum CompilationPhase {
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
} catch (final Throwable e) {
|
||||
// Provide source file and line number being compiled when the assertion occurred
|
||||
throw new AssertionError("Failed generating bytecode for " + fn.getSourceName() + ":" + codegen.getLastLineNumber(), e);
|
||||
}
|
||||
|
||||
for (final CompileUnit compileUnit : compiler.getCompileUnits()) {
|
||||
|
||||
@ -58,7 +58,6 @@ import jdk.nashorn.internal.codegen.CompilationEnvironment.CompilationPhases;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.FunctionNode;
|
||||
import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
|
||||
import jdk.nashorn.internal.ir.TemporarySymbols;
|
||||
import jdk.nashorn.internal.ir.debug.ClassHistogramElement;
|
||||
import jdk.nashorn.internal.ir.debug.ObjectSizeCalculator;
|
||||
import jdk.nashorn.internal.runtime.CodeInstaller;
|
||||
@ -105,8 +104,6 @@ public final class Compiler implements Loggable {
|
||||
|
||||
private final CodeInstaller<ScriptEnvironment> installer;
|
||||
|
||||
private final TemporarySymbols temporarySymbols = new TemporarySymbols();
|
||||
|
||||
/** logger for compiler, trampolines, splits and related code generation events
|
||||
* that affect classes */
|
||||
private final DebugLogger log;
|
||||
@ -437,10 +434,6 @@ public final class Compiler implements Loggable {
|
||||
return installer;
|
||||
}
|
||||
|
||||
TemporarySymbols getTemporarySymbols() {
|
||||
return temporarySymbols;
|
||||
}
|
||||
|
||||
void addClass(final String name, final byte[] code) {
|
||||
bytecode.put(name, code);
|
||||
}
|
||||
|
||||
@ -29,8 +29,9 @@ import static jdk.nashorn.internal.lookup.Lookup.MH;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
|
||||
import java.util.Set;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.runtime.ScriptFunction;
|
||||
import jdk.nashorn.internal.runtime.ScriptObject;
|
||||
@ -42,7 +43,6 @@ import jdk.nashorn.internal.runtime.Source;
|
||||
*/
|
||||
|
||||
public enum CompilerConstants {
|
||||
|
||||
/** the __FILE__ variable */
|
||||
__FILE__,
|
||||
|
||||
@ -87,7 +87,7 @@ public enum CompilerConstants {
|
||||
* representations of ECMAScript functions. It is not assigned a slot, as its position in the method signature is
|
||||
* dependent on other factors (most notably, callee can precede it).
|
||||
*/
|
||||
THIS("this"),
|
||||
THIS("this", Object.class),
|
||||
|
||||
/** this debugger symbol */
|
||||
THIS_DEBUGGER(":this"),
|
||||
@ -120,7 +120,7 @@ public enum CompilerConstants {
|
||||
/** prefix for tag variable used for switch evaluation */
|
||||
SWITCH_TAG_PREFIX(":s"),
|
||||
|
||||
/** prefix for all exceptions */
|
||||
/** prefix for JVM exceptions */
|
||||
EXCEPTION_PREFIX(":e", Throwable.class),
|
||||
|
||||
/** prefix for quick slots generated in Store */
|
||||
@ -184,6 +184,8 @@ public enum CompilerConstants {
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<String> symbolNames;
|
||||
|
||||
/**
|
||||
* Prefix used for internal methods generated in script clases.
|
||||
*/
|
||||
@ -223,12 +225,17 @@ public enum CompilerConstants {
|
||||
* @return true if compiler constant name
|
||||
*/
|
||||
public static boolean isCompilerConstant(final String name) {
|
||||
for (final CompilerConstants cc : CompilerConstants.values()) {
|
||||
if (name.equals(cc.symbolName())) {
|
||||
return true;
|
||||
ensureSymbolNames();
|
||||
return symbolNames.contains(name);
|
||||
}
|
||||
|
||||
private static void ensureSymbolNames() {
|
||||
if(symbolNames == null) {
|
||||
symbolNames = new HashSet<>();
|
||||
for(final CompilerConstants cc: CompilerConstants.values()) {
|
||||
symbolNames.add(cc.symbolName);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1,252 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.nashorn.internal.codegen;
|
||||
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
|
||||
import jdk.nashorn.internal.ir.BinaryNode;
|
||||
import jdk.nashorn.internal.ir.Block;
|
||||
import jdk.nashorn.internal.ir.Expression;
|
||||
import jdk.nashorn.internal.ir.ExpressionStatement;
|
||||
import jdk.nashorn.internal.ir.ForNode;
|
||||
import jdk.nashorn.internal.ir.FunctionNode;
|
||||
import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
|
||||
import jdk.nashorn.internal.ir.LexicalContext;
|
||||
import jdk.nashorn.internal.ir.Node;
|
||||
import jdk.nashorn.internal.ir.RuntimeNode;
|
||||
import jdk.nashorn.internal.ir.RuntimeNode.Request;
|
||||
import jdk.nashorn.internal.ir.Symbol;
|
||||
import jdk.nashorn.internal.ir.UnaryNode;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor;
|
||||
import jdk.nashorn.internal.parser.Token;
|
||||
import jdk.nashorn.internal.parser.TokenType;
|
||||
import jdk.nashorn.internal.runtime.Context;
|
||||
import jdk.nashorn.internal.runtime.logging.DebugLogger;
|
||||
import jdk.nashorn.internal.runtime.logging.Loggable;
|
||||
import jdk.nashorn.internal.runtime.logging.Logger;
|
||||
|
||||
/**
|
||||
* Lower to more primitive operations. After lowering, an AST has symbols and
|
||||
* types. Lowering may also add specialized versions of methods to the script if
|
||||
* the optimizer is turned on.
|
||||
*
|
||||
* Any expression that requires temporary storage as part of computation will
|
||||
* also be detected here and give a temporary symbol
|
||||
*
|
||||
* For any op that we process in FinalizeTypes it is an absolute guarantee
|
||||
* that scope and slot information is correct. This enables e.g. AccessSpecialization
|
||||
* and frame optimizations
|
||||
*/
|
||||
@Logger(name="finalize")
|
||||
final class FinalizeTypes extends NodeOperatorVisitor<LexicalContext> implements Loggable {
|
||||
|
||||
private final DebugLogger log;
|
||||
|
||||
FinalizeTypes(final CompilationEnvironment env) {
|
||||
super(new LexicalContext());
|
||||
this.log = initLogger(env.getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebugLogger getLogger() {
|
||||
return log;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebugLogger initLogger(final Context context) {
|
||||
return context.getLogger(this.getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveForNode(final ForNode forNode) {
|
||||
if (forNode.isForIn()) {
|
||||
return forNode;
|
||||
}
|
||||
|
||||
final Expression init = forNode.getInit();
|
||||
final Expression test = forNode.getTest();
|
||||
final Expression modify = forNode.getModify();
|
||||
|
||||
assert test != null || forNode.hasGoto() : "forNode " + forNode + " needs goto and is missing it in " + lc.getCurrentFunction();
|
||||
|
||||
return forNode.
|
||||
setInit(lc, init == null ? null : discard(init)).
|
||||
setModify(lc, modify == null ? null : discard(modify));
|
||||
}
|
||||
|
||||
private static Node createIsUndefined(final Expression parent, final Expression lhs, final Expression rhs, final Request request) {
|
||||
if ("undefined".equals(lhs.getSymbol().getName()) || "undefined".equals(rhs.getSymbol().getName())) {
|
||||
return new RuntimeNode(parent, request, lhs, rhs);
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveEQ_STRICT(final BinaryNode binaryNode) {
|
||||
return createIsUndefined(binaryNode, binaryNode.lhs(), binaryNode.rhs(), Request.IS_UNDEFINED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveNE_STRICT(final BinaryNode binaryNode) {
|
||||
return createIsUndefined(binaryNode, binaryNode.lhs(), binaryNode.rhs(), Request.IS_NOT_UNDEFINED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveRuntimeNode(final RuntimeNode runtimeNode) {
|
||||
switch (runtimeNode.getRequest()) {
|
||||
case EQ_STRICT:
|
||||
return createIsUndefined(runtimeNode, runtimeNode.getArgs().get(0), runtimeNode.getArgs().get(1), Request.IS_UNDEFINED);
|
||||
case NE_STRICT:
|
||||
return createIsUndefined(runtimeNode, runtimeNode.getArgs().get(0), runtimeNode.getArgs().get(1), Request.IS_NOT_UNDEFINED);
|
||||
default:
|
||||
return runtimeNode;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveCOMMALEFT(final BinaryNode binaryNode) {
|
||||
assert binaryNode.getSymbol() != null;
|
||||
return binaryNode.setRHS(discard(binaryNode.rhs()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveCOMMARIGHT(final BinaryNode binaryNode) {
|
||||
assert binaryNode.getSymbol() != null;
|
||||
return binaryNode.setLHS(discard(binaryNode.lhs()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterBlock(final Block block) {
|
||||
updateSymbols(block);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveExpressionStatement(final ExpressionStatement expressionStatement) {
|
||||
return expressionStatement.setExpression(discard(expressionStatement.getExpression()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterFunctionNode(final FunctionNode functionNode) {
|
||||
// TODO: now that Splitter comes before Attr, these can probably all be moved to Attr.
|
||||
|
||||
// If the function doesn't need a callee, we ensure its CALLEE symbol doesn't get a slot. We can't do this
|
||||
// earlier, as access to scoped variables, self symbol, etc. in previous phases can all trigger the need for the
|
||||
// callee.
|
||||
if (!functionNode.needsCallee()) {
|
||||
functionNode.compilerConstant(CALLEE).setNeedsSlot(false);
|
||||
}
|
||||
// Similar reasoning applies to SCOPE symbol: if the function doesn't need either parent scope and none of its
|
||||
// blocks create a scope, we ensure it doesn't get a slot, but we can't determine whether it needs a scope
|
||||
// earlier than this phase.
|
||||
if (!(functionNode.hasScopeBlock() || functionNode.needsParentScope())) {
|
||||
functionNode.compilerConstant(SCOPE).setNeedsSlot(false);
|
||||
}
|
||||
// Also, we must wait until after Splitter to see if the function ended up needing the RETURN symbol.
|
||||
if (!functionNode.usesReturnSymbol()) {
|
||||
functionNode.compilerConstant(RETURN).setNeedsSlot(false);
|
||||
}
|
||||
// Named function expressions that end up not referencing themselves won't need a local slot for the self symbol.
|
||||
if(!functionNode.isDeclared() && !functionNode.usesSelfSymbol() && !functionNode.isAnonymous()) {
|
||||
final Symbol selfSymbol = functionNode.getBody().getExistingSymbol(functionNode.getIdent().getName());
|
||||
if(selfSymbol != null) {
|
||||
if(selfSymbol.isFunctionSelf()) {
|
||||
selfSymbol.setNeedsSlot(false);
|
||||
selfSymbol.clearFlag(Symbol.IS_VAR);
|
||||
}
|
||||
} else {
|
||||
assert functionNode.isProgram();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveFunctionNode(final FunctionNode functionNode) {
|
||||
return functionNode.setState(lc, CompilationState.FINALIZED);
|
||||
}
|
||||
|
||||
private void updateSymbolsLog(final FunctionNode functionNode, final Symbol symbol, final boolean loseSlot) {
|
||||
if (log.isEnabled()) {
|
||||
if (!symbol.isScope()) {
|
||||
log.finest("updateSymbols: ", symbol, " => scope, because all vars in ", functionNode.getName(), " are in scope");
|
||||
}
|
||||
if (loseSlot && symbol.hasSlot()) {
|
||||
log.finest("updateSymbols: ", symbol, " => no slot, because all vars in ", functionNode.getName(), " are in scope");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after a block or function node (subclass of block) is finished. Guarantees
|
||||
* that scope and slot information is correct for every symbol
|
||||
* @param block block for which to to finalize type info.
|
||||
*/
|
||||
private void updateSymbols(final Block block) {
|
||||
if (!block.needsScope()) {
|
||||
return; // nothing to do
|
||||
}
|
||||
|
||||
final FunctionNode functionNode = lc.getFunction(block);
|
||||
final boolean allVarsInScope = functionNode.allVarsInScope();
|
||||
final boolean isVarArg = functionNode.isVarArg();
|
||||
|
||||
for (final Symbol symbol : block.getSymbols()) {
|
||||
if (symbol.isInternal() || symbol.isThis() || symbol.isTemp()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (symbol.isVar()) {
|
||||
if (allVarsInScope || symbol.isScope()) {
|
||||
updateSymbolsLog(functionNode, symbol, true);
|
||||
Symbol.setSymbolIsScope(lc, symbol);
|
||||
symbol.setNeedsSlot(false);
|
||||
} else {
|
||||
assert symbol.hasSlot() : symbol + " should have a slot only, no scope";
|
||||
}
|
||||
} else if (symbol.isParam() && (allVarsInScope || isVarArg || symbol.isScope())) {
|
||||
updateSymbolsLog(functionNode, symbol, isVarArg);
|
||||
Symbol.setSymbolIsScope(lc, symbol);
|
||||
symbol.setNeedsSlot(!isVarArg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Expression discard(final Expression expr) {
|
||||
if (expr.getSymbol() != null) {
|
||||
final UnaryNode discard = new UnaryNode(Token.recast(expr.getToken(), TokenType.DISCARD), expr);
|
||||
//discard never has a symbol in the discard node - then it would be a nop
|
||||
assert !expr.isTerminal();
|
||||
return discard;
|
||||
}
|
||||
|
||||
// node has no result (symbol) so we can keep it the way it is
|
||||
return expr;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -34,15 +34,14 @@ import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import jdk.nashorn.internal.ir.Block;
|
||||
import jdk.nashorn.internal.ir.Expression;
|
||||
import jdk.nashorn.internal.ir.FunctionNode;
|
||||
import jdk.nashorn.internal.ir.WithNode;
|
||||
import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
|
||||
import jdk.nashorn.internal.ir.IdentNode;
|
||||
import jdk.nashorn.internal.ir.LexicalContext;
|
||||
import jdk.nashorn.internal.ir.Node;
|
||||
import jdk.nashorn.internal.ir.Symbol;
|
||||
import jdk.nashorn.internal.ir.WithNode;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
import jdk.nashorn.internal.runtime.Context;
|
||||
import jdk.nashorn.internal.runtime.PropertyMap;
|
||||
@ -211,7 +210,7 @@ final class FindScopeDepths extends NodeVisitor<LexicalContext> implements Logga
|
||||
|
||||
assert nestedFunctions != null;
|
||||
// Generate the object class and property map in case this function is ever used as constructor
|
||||
final int fieldCount = getPaddedFieldCount(newFunctionNode.countThisProperties());
|
||||
final int fieldCount = getPaddedFieldCount(newFunctionNode.getThisProperties());
|
||||
final String allocatorClassName = Compiler.binaryName(getClassName(fieldCount));
|
||||
final PropertyMap allocatorMap = PropertyMap.newMap(null, 0, fieldCount, 0);
|
||||
final RecompilableScriptFunctionData data = new RecompilableScriptFunctionData(
|
||||
@ -292,8 +291,8 @@ final class FindScopeDepths extends NodeVisitor<LexicalContext> implements Logga
|
||||
@Override
|
||||
public final boolean enterDefault(final Node node) {
|
||||
if (!env.isOnDemandCompilation()) {
|
||||
if (node instanceof Expression) {
|
||||
final Symbol symbol = ((Expression)node).getSymbol();
|
||||
if (node instanceof IdentNode) {
|
||||
final Symbol symbol = ((IdentNode)node).getSymbol();
|
||||
if (symbol != null && symbol.isScope()) {
|
||||
//if this is an internal symbol, skip it.
|
||||
symbols.add(symbol);
|
||||
|
||||
@ -24,6 +24,12 @@
|
||||
*/
|
||||
package jdk.nashorn.internal.codegen;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
|
||||
/**
|
||||
@ -37,23 +43,23 @@ public final class Label {
|
||||
//byte code generation evaluation type stack for consistency check
|
||||
//and correct opcode selection. one per label as a label may be a
|
||||
//join point
|
||||
static final class Stack {
|
||||
static final class Stack implements Cloneable {
|
||||
static final int NON_LOAD = -1;
|
||||
|
||||
Type[] data = new Type[8];
|
||||
int[] localLoads = new int[8];
|
||||
Type[] data;
|
||||
int[] localLoads;
|
||||
int sp;
|
||||
|
||||
Stack() {
|
||||
}
|
||||
List<Type> localVariableTypes;
|
||||
int firstTemp; // index of the first temporary local variable
|
||||
// Bitmap marking last slot belonging to a single symbol.
|
||||
BitSet symbolBoundary;
|
||||
|
||||
private Stack(final Stack original) {
|
||||
this();
|
||||
this.sp = original.sp;
|
||||
this.data = new Type[original.data.length];
|
||||
System.arraycopy(original.data, 0, data, 0, sp);
|
||||
this.localLoads = new int[original.localLoads.length];
|
||||
System.arraycopy(original.localLoads, 0, localLoads, 0, sp);
|
||||
Stack() {
|
||||
data = new Type[8];
|
||||
localLoads = new int[8];
|
||||
localVariableTypes = new ArrayList<>(8);
|
||||
symbolBoundary = new BitSet();
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
@ -64,18 +70,6 @@ public final class Label {
|
||||
return sp;
|
||||
}
|
||||
|
||||
boolean isEquivalentInTypesTo(final Stack other) {
|
||||
if (sp != other.sp) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < sp; i++) {
|
||||
if (!data[i].isEquivalentTo(other.data[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
sp = 0;
|
||||
}
|
||||
@ -123,24 +117,201 @@ public final class Label {
|
||||
}
|
||||
|
||||
/**
|
||||
* When joining branches, local loads that differ on different branches are invalidated.
|
||||
* @param other the stack from the other branch.
|
||||
* Returns the number of used local variable slots, including all live stack-store temporaries.
|
||||
* @return the number of used local variable slots, including all live stack-store temporaries.
|
||||
*/
|
||||
void mergeLocalLoads(final Stack other) {
|
||||
final int[] otherLoads = other.localLoads;
|
||||
int getUsedSlotsWithLiveTemporaries() {
|
||||
// There are at least as many as are declared by the current blocks.
|
||||
int usedSlots = firstTemp;
|
||||
// Look at every load on the stack, and bump the number of used slots up by the temporaries seen there.
|
||||
for(int i = sp; i-->0;) {
|
||||
final int slot = localLoads[i];
|
||||
if(slot != Label.Stack.NON_LOAD) {
|
||||
final int afterSlot = slot + localVariableTypes.get(slot).getSlots();
|
||||
if(afterSlot > usedSlots) {
|
||||
usedSlots = afterSlot;
|
||||
}
|
||||
}
|
||||
}
|
||||
return usedSlots;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param joinOrigin the stack from the other branch.
|
||||
*/
|
||||
void joinFrom(final Stack joinOrigin, final boolean breakTarget) {
|
||||
assert isStackCompatible(joinOrigin);
|
||||
if(breakTarget) {
|
||||
// As we're joining labels that can jump across block boundaries, the number of local variables can
|
||||
// differ, and we should always respect the one having less variables.
|
||||
firstTemp = Math.min(firstTemp, joinOrigin.firstTemp);
|
||||
} else {
|
||||
assert firstTemp == joinOrigin.firstTemp;
|
||||
}
|
||||
final int[] otherLoads = joinOrigin.localLoads;
|
||||
int firstDeadTemp = firstTemp;
|
||||
for(int i = 0; i < sp; ++i) {
|
||||
if(localLoads[i] != otherLoads[i]) {
|
||||
final int localLoad = localLoads[i];
|
||||
if(localLoad != otherLoads[i]) {
|
||||
localLoads[i] = NON_LOAD;
|
||||
} else if(localLoad >= firstDeadTemp) {
|
||||
firstDeadTemp = localLoad + localVariableTypes.get(localLoad).getSlots();
|
||||
}
|
||||
}
|
||||
// Eliminate dead temporaries
|
||||
undefineLocalVariables(firstDeadTemp, false);
|
||||
assert isVariablePartitioningEqual(joinOrigin, firstDeadTemp);
|
||||
mergeVariableTypes(joinOrigin, firstDeadTemp);
|
||||
}
|
||||
|
||||
private void mergeVariableTypes(final Stack joinOrigin, final int toSlot) {
|
||||
final ListIterator<Type> it1 = localVariableTypes.listIterator();
|
||||
final Iterator<Type> it2 = joinOrigin.localVariableTypes.iterator();
|
||||
for(int i = 0; i < toSlot; ++i) {
|
||||
final Type thisType = it1.next();
|
||||
final Type otherType = it2.next();
|
||||
if(otherType == Type.UNKNOWN) {
|
||||
// Variables that are <unknown> on the other branch will become <unknown> here too.
|
||||
it1.set(Type.UNKNOWN);
|
||||
} else if (thisType != otherType) {
|
||||
if(thisType.isObject() && otherType.isObject()) {
|
||||
// different object types are merged into Object.
|
||||
// TODO: maybe find most common superclass?
|
||||
it1.set(Type.OBJECT);
|
||||
} else {
|
||||
assert thisType == Type.UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void joinFromTry(final Stack joinOrigin) {
|
||||
// As we're joining labels that can jump across block boundaries, the number of local variables can
|
||||
// differ, and we should always respect the one having less variables.
|
||||
firstTemp = Math.min(firstTemp, joinOrigin.firstTemp);
|
||||
assert isVariablePartitioningEqual(joinOrigin, firstTemp);
|
||||
mergeVariableTypes(joinOrigin, firstTemp);
|
||||
}
|
||||
|
||||
private int getFirstDeadLocal(List<Type> types) {
|
||||
int i = types.size();
|
||||
for(final ListIterator<Type> it = types.listIterator(i);
|
||||
it.hasPrevious() && it.previous() == Type.UNKNOWN;
|
||||
--i); // no body
|
||||
|
||||
// Respect symbol boundaries; we never chop off half a symbol's storage
|
||||
while(!symbolBoundary.get(i - 1)) {
|
||||
++i;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
private boolean isStackCompatible(final Stack other) {
|
||||
if (sp != other.sp) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < sp; i++) {
|
||||
if (!data[i].isEquivalentTo(other.data[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isVariablePartitioningEqual(final Stack other, final int toSlot) {
|
||||
// No difference in the symbol boundaries before the toSlot
|
||||
final BitSet diff = other.getSymbolBoundaryCopy();
|
||||
diff.xor(symbolBoundary);
|
||||
return diff.previousSetBit(toSlot - 1) == -1;
|
||||
}
|
||||
|
||||
void markDeadLocalVariables(final int fromSlot, final int slotCount) {
|
||||
final int localCount = localVariableTypes.size();
|
||||
if(fromSlot >= localCount) {
|
||||
return;
|
||||
}
|
||||
final int toSlot = Math.min(fromSlot + slotCount, localCount);
|
||||
invalidateLocalLoadsOnStack(fromSlot, toSlot);
|
||||
for(int i = fromSlot; i < toSlot; ++i) {
|
||||
localVariableTypes.set(i, Type.UNKNOWN);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Type> getLocalVariableTypesCopy() {
|
||||
return (List<Type>)((ArrayList<Type>)localVariableTypes).clone();
|
||||
}
|
||||
|
||||
BitSet getSymbolBoundaryCopy() {
|
||||
return (BitSet)symbolBoundary.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of local variable slot types, but for those symbols that have multiple values, only the slot
|
||||
* holding the widest type is marked as live.
|
||||
* @return a list of widest local variable slot types.
|
||||
*/
|
||||
List<Type> getWidestLiveLocals(final List<Type> lvarTypes) {
|
||||
List<Type> widestLiveLocals = new ArrayList<>(lvarTypes);
|
||||
boolean keepNextValue = true;
|
||||
final int size = widestLiveLocals.size();
|
||||
for(int i = size - 1; i-- > 0;) {
|
||||
if(symbolBoundary.get(i)) {
|
||||
keepNextValue = true;
|
||||
}
|
||||
final Type t = widestLiveLocals.get(i);
|
||||
if(t != Type.UNKNOWN) {
|
||||
if(keepNextValue) {
|
||||
if(t != Type.SLOT_2) {
|
||||
keepNextValue = false;
|
||||
}
|
||||
} else {
|
||||
widestLiveLocals.set(i, Type.UNKNOWN);
|
||||
}
|
||||
}
|
||||
}
|
||||
widestLiveLocals.subList(Math.max(getFirstDeadLocal(widestLiveLocals), firstTemp), widestLiveLocals.size()).clear();
|
||||
return widestLiveLocals;
|
||||
}
|
||||
|
||||
String markSymbolBoundariesInLvarTypesDescriptor(final String lvarDescriptor) {
|
||||
final char[] chars = lvarDescriptor.toCharArray();
|
||||
int j = 0;
|
||||
for(int i = 0; i < chars.length; ++i) {
|
||||
final char c = chars[i];
|
||||
final int nextj = j + CodeGeneratorLexicalContext.getTypeForSlotDescriptor(c).getSlots();
|
||||
if(!symbolBoundary.get(nextj - 1)) {
|
||||
chars[i] = Character.toLowerCase(c);
|
||||
}
|
||||
j = nextj;
|
||||
}
|
||||
return new String(chars);
|
||||
}
|
||||
|
||||
Type pop() {
|
||||
assert sp > 0;
|
||||
return data[--sp];
|
||||
}
|
||||
|
||||
Stack copy() {
|
||||
return new Stack(this);
|
||||
@Override
|
||||
public Stack clone() {
|
||||
try {
|
||||
final Stack clone = (Stack)super.clone();
|
||||
clone.data = data.clone();
|
||||
clone.localLoads = localLoads.clone();
|
||||
clone.symbolBoundary = getSymbolBoundaryCopy();
|
||||
clone.localVariableTypes = getLocalVariableTypesCopy();
|
||||
return clone;
|
||||
} catch(final CloneNotSupportedException e) {
|
||||
throw new AssertionError("", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Stack cloneWithEmptyStack() {
|
||||
final Stack stack = clone();
|
||||
stack.sp = 0;
|
||||
return stack;
|
||||
}
|
||||
|
||||
int getTopLocalLoad() {
|
||||
@ -152,33 +323,167 @@ public final class Label {
|
||||
}
|
||||
|
||||
/**
|
||||
* If we store a value in a local slot, it invalidates any on-stack loads from that same slot, as the values
|
||||
* could have changed.
|
||||
* Performs various bookeeping when a value is stored in a local variable slot.
|
||||
* @param slot the slot written to
|
||||
* @param slotCount the size of the value, either 1 or 2 slots
|
||||
* @param onlySymbolLiveValue if true, this is the symbol's only live value, and other values of the symbol
|
||||
* should be marked dead
|
||||
* @param Type the type written to the slot
|
||||
*/
|
||||
void markLocalStore(final int slot, final int slotCount) {
|
||||
void onLocalStore(final Type type, final int slot, final boolean onlySymbolLiveValue) {
|
||||
if(onlySymbolLiveValue) {
|
||||
final int fromSlot = slot == 0 ? 0 : (symbolBoundary.previousSetBit(slot - 1) + 1);
|
||||
final int toSlot = symbolBoundary.nextSetBit(slot) + 1;
|
||||
for(int i = fromSlot; i < toSlot; ++i) {
|
||||
localVariableTypes.set(i, Type.UNKNOWN);
|
||||
}
|
||||
invalidateLocalLoadsOnStack(fromSlot, toSlot);
|
||||
} else {
|
||||
invalidateLocalLoadsOnStack(slot, slot + type.getSlots());
|
||||
}
|
||||
|
||||
localVariableTypes.set(slot, type);
|
||||
if(type.isCategory2()) {
|
||||
localVariableTypes.set(slot + 1, Type.SLOT_2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a slot range, invalidate knowledge about local loads on stack from these slots (because they're being
|
||||
* killed).
|
||||
* @param fromSlot first slot, inclusive.
|
||||
* @param toSlot last slot, exclusive.
|
||||
*/
|
||||
private void invalidateLocalLoadsOnStack(final int fromSlot, final int toSlot) {
|
||||
for(int i = 0; i < sp; ++i) {
|
||||
final int load = localLoads[i];
|
||||
if(load == slot || load == slot + slotCount - 1) {
|
||||
final int localLoad = localLoads[i];
|
||||
if(localLoad >= fromSlot && localLoad < toSlot) {
|
||||
localLoads[i] = NON_LOAD;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder builder = new StringBuilder("[");
|
||||
for (int i = 0; i < sp; i++) {
|
||||
builder.append(data[i]);
|
||||
if (i < sp - 1) {
|
||||
builder.append(", ");
|
||||
/**
|
||||
* Marks a range of slots as belonging to a defined local variable. The slots will start out with no live value
|
||||
* in them.
|
||||
* @param fromSlot first slot, inclusive.
|
||||
* @param toSlot last slot, exclusive.
|
||||
*/
|
||||
void defineBlockLocalVariable(final int fromSlot, final int toSlot) {
|
||||
defineLocalVariable(fromSlot, toSlot);
|
||||
assert firstTemp < toSlot;
|
||||
firstTemp = toSlot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a new temporary local variable and returns its allocated index.
|
||||
* @param width the required width (in slots) for the new variable.
|
||||
* @return the bytecode slot index where the newly allocated local begins.
|
||||
*/
|
||||
int defineTemporaryLocalVariable(final int width) {
|
||||
final int fromSlot = getUsedSlotsWithLiveTemporaries();
|
||||
defineLocalVariable(fromSlot, fromSlot + width);
|
||||
return fromSlot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a range of slots as belonging to a defined temporary local variable. The slots will start out with no
|
||||
* live value in them.
|
||||
* @param fromSlot first slot, inclusive.
|
||||
* @param toSlot last slot, exclusive.
|
||||
*/
|
||||
void defineTemporaryLocalVariable(final int fromSlot, final int toSlot) {
|
||||
defineLocalVariable(fromSlot, toSlot);
|
||||
}
|
||||
|
||||
private void defineLocalVariable(final int fromSlot, final int toSlot) {
|
||||
assert !hasLoadsOnStack(fromSlot, toSlot);
|
||||
assert fromSlot < toSlot;
|
||||
symbolBoundary.clear(fromSlot, toSlot - 1);
|
||||
symbolBoundary.set(toSlot - 1);
|
||||
final int lastExisting = Math.min(toSlot, localVariableTypes.size());
|
||||
for(int i = fromSlot; i < lastExisting; ++i) {
|
||||
localVariableTypes.set(i, Type.UNKNOWN);
|
||||
}
|
||||
for(int i = lastExisting; i < toSlot; ++i) {
|
||||
localVariableTypes.add(i, Type.UNKNOWN);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Undefines all local variables past the specified slot.
|
||||
* @param fromSlot the first slot to be undefined
|
||||
* @param canTruncateSymbol if false, the fromSlot must be either the first slot of a symbol, or the first slot
|
||||
* after the last symbol. If true, the fromSlot can be in the middle of the storage area for a symbol. This
|
||||
* should be used with care - it is only meant for use in optimism exception handlers.
|
||||
*/
|
||||
void undefineLocalVariables(final int fromSlot, final boolean canTruncateSymbol) {
|
||||
final int lvarCount = localVariableTypes.size();
|
||||
assert lvarCount == symbolBoundary.length();
|
||||
assert !hasLoadsOnStack(fromSlot, lvarCount);
|
||||
if(canTruncateSymbol) {
|
||||
if(fromSlot > 0) {
|
||||
symbolBoundary.set(fromSlot - 1);
|
||||
}
|
||||
} else {
|
||||
assert fromSlot == 0 || symbolBoundary.get(fromSlot - 1);
|
||||
}
|
||||
if(fromSlot < lvarCount) {
|
||||
symbolBoundary.clear(fromSlot, lvarCount);
|
||||
localVariableTypes.subList(fromSlot, lvarCount).clear();
|
||||
}
|
||||
firstTemp = Math.min(fromSlot, firstTemp);
|
||||
assert symbolBoundary.length() == localVariableTypes.size();
|
||||
assert symbolBoundary.length() == fromSlot;
|
||||
}
|
||||
|
||||
private void markAsOptimisticCatchHandler(final int liveLocalCount) {
|
||||
// Live temporaries that are no longer on stack are undefined
|
||||
undefineLocalVariables(liveLocalCount, true);
|
||||
// Temporaries are promoted
|
||||
firstTemp = liveLocalCount;
|
||||
// No trailing undefineds
|
||||
localVariableTypes.subList(firstTemp, localVariableTypes.size()).clear();
|
||||
assert symbolBoundary.length() == firstTemp;
|
||||
// Generalize all reference types to Object, and promote boolean to int
|
||||
for(final ListIterator<Type> it = localVariableTypes.listIterator(); it.hasNext();) {
|
||||
final Type type = it.next();
|
||||
if(type == Type.BOOLEAN) {
|
||||
it.set(Type.INT);
|
||||
} else if(type.isObject() && type != Type.OBJECT) {
|
||||
it.set(Type.OBJECT);
|
||||
}
|
||||
}
|
||||
return builder.append("]").toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if any loads on the stack come from the specified slot range.
|
||||
* @param fromSlot start of the range (inclusive)
|
||||
* @param toSlot end of the range (exclusive)
|
||||
* @return true if any loads on the stack come from the specified slot range.
|
||||
*/
|
||||
boolean hasLoadsOnStack(final int fromSlot, final int toSlot) {
|
||||
for(int i = 0; i < sp; ++i) {
|
||||
final int load = localLoads[i];
|
||||
if(load >= fromSlot && load < toSlot) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "stack=" + Arrays.toString(Arrays.copyOf(data, sp))
|
||||
+ ", symbolBoundaries=" + String.valueOf(symbolBoundary)
|
||||
+ ", firstTemp=" + firstTemp
|
||||
+ ", localTypes=" + String.valueOf(localVariableTypes)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
/** Next id for debugging purposes, remove if footprint becomes unmanageable */
|
||||
private static int nextId = 0;
|
||||
|
||||
/** Name of this label */
|
||||
private final String name;
|
||||
|
||||
@ -191,8 +496,10 @@ public final class Label {
|
||||
/** Id for debugging purposes, remove if footprint becomes unmanageable */
|
||||
private final int id;
|
||||
|
||||
/** Next id for debugging purposes, remove if footprint becomes unmanageable */
|
||||
private static int nextId = 0;
|
||||
/** Is this label reachable (anything ever jumped to it)? */
|
||||
private boolean reachable;
|
||||
|
||||
private boolean breakTarget;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@ -228,8 +535,57 @@ public final class Label {
|
||||
return stack;
|
||||
}
|
||||
|
||||
void setStack(final Label.Stack stack) {
|
||||
this.stack = stack;
|
||||
void joinFrom(final Label.Stack joinOrigin) {
|
||||
this.reachable = true;
|
||||
if(stack == null) {
|
||||
stack = joinOrigin.clone();
|
||||
} else {
|
||||
stack.joinFrom(joinOrigin, breakTarget);
|
||||
}
|
||||
}
|
||||
|
||||
void joinFromTry(final Label.Stack joinOrigin, final boolean isOptimismHandler) {
|
||||
this.reachable = true;
|
||||
if (stack == null) {
|
||||
if(!isOptimismHandler) {
|
||||
stack = joinOrigin.cloneWithEmptyStack();
|
||||
// Optimism handler needs temporaries to remain live, others don't.
|
||||
stack.undefineLocalVariables(stack.firstTemp, false);
|
||||
}
|
||||
} else {
|
||||
assert !isOptimismHandler;
|
||||
stack.joinFromTry(joinOrigin);
|
||||
}
|
||||
}
|
||||
|
||||
void markAsBreakTarget() {
|
||||
breakTarget = true;
|
||||
}
|
||||
|
||||
boolean isBreakTarget() {
|
||||
return breakTarget;
|
||||
}
|
||||
|
||||
void onCatch() {
|
||||
if(stack != null) {
|
||||
stack = stack.cloneWithEmptyStack();
|
||||
}
|
||||
}
|
||||
void markAsOptimisticCatchHandler(final Label.Stack currentStack, final int liveLocalCount) {
|
||||
stack = currentStack.cloneWithEmptyStack();
|
||||
stack.markAsOptimisticCatchHandler(liveLocalCount);
|
||||
}
|
||||
|
||||
void markAsOptimisticContinuationHandlerFor(final Label afterConsumeStackLabel) {
|
||||
stack = afterConsumeStackLabel.stack.cloneWithEmptyStack();
|
||||
}
|
||||
|
||||
boolean isReachable() {
|
||||
return reachable;
|
||||
}
|
||||
|
||||
boolean isAfter(final Label other) {
|
||||
return label.getOffset() > other.label.getOffset();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -28,13 +28,13 @@ package jdk.nashorn.internal.codegen;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.EVAL;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.THIS;
|
||||
import static jdk.nashorn.internal.ir.Expression.isAlwaysTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
import jdk.nashorn.internal.ir.BaseNode;
|
||||
import jdk.nashorn.internal.ir.BinaryNode;
|
||||
import jdk.nashorn.internal.ir.Block;
|
||||
@ -42,6 +42,7 @@ import jdk.nashorn.internal.ir.BlockLexicalContext;
|
||||
import jdk.nashorn.internal.ir.BlockStatement;
|
||||
import jdk.nashorn.internal.ir.BreakNode;
|
||||
import jdk.nashorn.internal.ir.CallNode;
|
||||
import jdk.nashorn.internal.ir.CaseNode;
|
||||
import jdk.nashorn.internal.ir.CatchNode;
|
||||
import jdk.nashorn.internal.ir.ContinueNode;
|
||||
import jdk.nashorn.internal.ir.EmptyNode;
|
||||
@ -58,6 +59,7 @@ import jdk.nashorn.internal.ir.LiteralNode;
|
||||
import jdk.nashorn.internal.ir.LoopNode;
|
||||
import jdk.nashorn.internal.ir.Node;
|
||||
import jdk.nashorn.internal.ir.ReturnNode;
|
||||
import jdk.nashorn.internal.ir.RuntimeNode;
|
||||
import jdk.nashorn.internal.ir.Statement;
|
||||
import jdk.nashorn.internal.ir.SwitchNode;
|
||||
import jdk.nashorn.internal.ir.Symbol;
|
||||
@ -72,6 +74,7 @@ import jdk.nashorn.internal.parser.Token;
|
||||
import jdk.nashorn.internal.parser.TokenType;
|
||||
import jdk.nashorn.internal.runtime.CodeInstaller;
|
||||
import jdk.nashorn.internal.runtime.Context;
|
||||
import jdk.nashorn.internal.runtime.JSType;
|
||||
import jdk.nashorn.internal.runtime.ScriptRuntime;
|
||||
import jdk.nashorn.internal.runtime.Source;
|
||||
import jdk.nashorn.internal.runtime.logging.DebugLogger;
|
||||
@ -156,15 +159,6 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo
|
||||
return context.getLogger(this.getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterBlock(final Block block) {
|
||||
final FunctionNode function = lc.getCurrentFunction();
|
||||
if (lc.isFunctionBody() && function.isProgram() && !function.hasDeclaredFunctions()) {
|
||||
new ExpressionStatement(function.getLineNumber(), block.getToken(), block.getFinish(), LiteralNode.newInstance(block, ScriptRuntime.UNDEFINED)).accept(this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveBlock(final Block block) {
|
||||
//now we have committed the entire statement list to the block, but we need to truncate
|
||||
@ -247,19 +241,15 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo
|
||||
public Node leaveForNode(final ForNode forNode) {
|
||||
ForNode newForNode = forNode;
|
||||
|
||||
final Node test = forNode.getTest();
|
||||
if (!forNode.isForIn() && conservativeAlwaysTrue(test)) {
|
||||
final Expression test = forNode.getTest();
|
||||
if (!forNode.isForIn() && isAlwaysTrue(test)) {
|
||||
newForNode = forNode.setTest(lc, null);
|
||||
}
|
||||
|
||||
newForNode = checkEscape(newForNode);
|
||||
if(newForNode.isForIn()) {
|
||||
// Wrap it in a block so its internally created iterator is restricted in scope
|
||||
BlockStatement b = BlockStatement.createReplacement(newForNode, Collections.singletonList((Statement)newForNode));
|
||||
if(newForNode.isTerminal()) {
|
||||
b = b.setBlock(b.getBlock().setIsTerminal(null, true));
|
||||
}
|
||||
addStatement(b);
|
||||
addStatementEnclosedInBlock(newForNode);
|
||||
} else {
|
||||
addStatement(newForNode);
|
||||
}
|
||||
@ -277,6 +267,16 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo
|
||||
return addStatement(ifNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveIN(BinaryNode binaryNode) {
|
||||
return new RuntimeNode(binaryNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveINSTANCEOF(BinaryNode binaryNode) {
|
||||
return new RuntimeNode(binaryNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveLabelNode(final LabelNode labelNode) {
|
||||
return addStatement(labelNode);
|
||||
@ -288,16 +288,35 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo
|
||||
return returnNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveCaseNode(CaseNode caseNode) {
|
||||
// Try to represent the case test as an integer
|
||||
final Node test = caseNode.getTest();
|
||||
if (test instanceof LiteralNode) {
|
||||
final LiteralNode<?> lit = (LiteralNode<?>)test;
|
||||
if (lit.isNumeric() && !(lit.getValue() instanceof Integer)) {
|
||||
if (JSType.isRepresentableAsInt(lit.getNumber())) {
|
||||
return caseNode.setTest((Expression)LiteralNode.newInstance(lit, lit.getInt32()).accept(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
return caseNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveSwitchNode(final SwitchNode switchNode) {
|
||||
return addStatement(switchNode);
|
||||
if(!switchNode.isInteger()) {
|
||||
// Wrap it in a block so its internally created tag is restricted in scope
|
||||
addStatementEnclosedInBlock(switchNode);
|
||||
} else {
|
||||
addStatement(switchNode);
|
||||
}
|
||||
return switchNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveThrowNode(final ThrowNode throwNode) {
|
||||
addStatement(throwNode); //ThrowNodes are always terminal, marked as such in constructor
|
||||
return throwNode;
|
||||
return addStatement(throwNode); //ThrowNodes are always terminal, marked as such in constructor
|
||||
}
|
||||
|
||||
private static Node ensureUniqueNamesIn(final Node node) {
|
||||
@ -333,10 +352,10 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo
|
||||
|
||||
final IdentNode exception = new IdentNode(token, finish, lc.getCurrentFunction().uniqueName(CompilerConstants.EXCEPTION_PREFIX.symbolName()));
|
||||
|
||||
final Block catchBody = new Block(token, finish, new ThrowNode(lineNumber, token, finish, new IdentNode(exception), ThrowNode.IS_SYNTHETIC_RETHROW));
|
||||
final Block catchBody = new Block(token, finish, new ThrowNode(lineNumber, token, finish, new IdentNode(exception), true));
|
||||
assert catchBody.isTerminal(); //ends with throw, so terminal
|
||||
|
||||
final CatchNode catchAllNode = new CatchNode(lineNumber, token, finish, new IdentNode(exception), null, catchBody, CatchNode.IS_SYNTHETIC_RETHROW);
|
||||
final CatchNode catchAllNode = new CatchNode(lineNumber, token, finish, new IdentNode(exception), null, catchBody, true);
|
||||
final Block catchAllBlock = new Block(token, finish, catchAllNode);
|
||||
|
||||
//catchallblock -> catchallnode (catchnode) -> exception -> throw
|
||||
@ -394,12 +413,12 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo
|
||||
|
||||
@Override
|
||||
public Node leaveBreakNode(final BreakNode breakNode) {
|
||||
return copy(breakNode, (Node)Lower.this.lc.getBreakable(breakNode.getLabel()));
|
||||
return copy(breakNode, (Node)Lower.this.lc.getBreakable(breakNode.getLabelName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveContinueNode(final ContinueNode continueNode) {
|
||||
return copy(continueNode, Lower.this.lc.getContinueTo(continueNode.getLabel()));
|
||||
return copy(continueNode, Lower.this.lc.getContinueTo(continueNode.getLabelName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -451,7 +470,7 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo
|
||||
final Block finallyBody = tryNode.getFinallyBody();
|
||||
|
||||
if (finallyBody == null) {
|
||||
return addStatement(tryNode);
|
||||
return addStatement(ensureUnconditionalCatch(tryNode));
|
||||
}
|
||||
|
||||
/*
|
||||
@ -494,7 +513,7 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo
|
||||
if (tryNode.getCatchBlocks().isEmpty()) {
|
||||
newTryNode = tryNode.setFinallyBody(null);
|
||||
} else {
|
||||
final Block outerBody = new Block(tryNode.getToken(), tryNode.getFinish(), tryNode.setFinallyBody(null));
|
||||
final Block outerBody = new Block(tryNode.getToken(), tryNode.getFinish(), ensureUnconditionalCatch(tryNode.setFinallyBody(null)));
|
||||
newTryNode = tryNode.setBody(outerBody).setCatchBlocks(null);
|
||||
}
|
||||
|
||||
@ -507,6 +526,18 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo
|
||||
return spliceFinally(newTryNode, rethrows, finallyBody);
|
||||
}
|
||||
|
||||
private TryNode ensureUnconditionalCatch(TryNode tryNode) {
|
||||
final List<CatchNode> catches = tryNode.getCatches();
|
||||
if(catches == null || catches.isEmpty() || catches.get(catches.size() - 1).getExceptionCondition() == null) {
|
||||
return tryNode;
|
||||
}
|
||||
// If the last catch block is conditional, add an unconditional rethrow block
|
||||
final List<Block> newCatchBlocks = new ArrayList<>(tryNode.getCatchBlocks());
|
||||
|
||||
newCatchBlocks.add(catchAllBlock(tryNode));
|
||||
return tryNode.setCatchBlocks(newCatchBlocks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveVarNode(final VarNode varNode) {
|
||||
addStatement(varNode);
|
||||
@ -518,12 +549,12 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo
|
||||
|
||||
@Override
|
||||
public Node leaveWhileNode(final WhileNode whileNode) {
|
||||
final Node test = whileNode.getTest();
|
||||
final Expression test = whileNode.getTest();
|
||||
final Block body = whileNode.getBody();
|
||||
|
||||
if (conservativeAlwaysTrue(test)) {
|
||||
if (isAlwaysTrue(test)) {
|
||||
//turn it into a for node without a test.
|
||||
final ForNode forNode = (ForNode)new ForNode(whileNode.getLineNumber(), whileNode.getToken(), whileNode.getFinish(), null, null, body, null, ForNode.IS_FOR).accept(this);
|
||||
final ForNode forNode = (ForNode)new ForNode(whileNode.getLineNumber(), whileNode.getToken(), whileNode.getFinish(), body, ForNode.IS_FOR).accept(this);
|
||||
lc.replace(whileNode, forNode);
|
||||
return forNode;
|
||||
}
|
||||
@ -610,10 +641,6 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo
|
||||
return callNode;
|
||||
}
|
||||
|
||||
private static boolean conservativeAlwaysTrue(final Node node) {
|
||||
return node == null || ((node instanceof LiteralNode) && Boolean.TRUE.equals(((LiteralNode<?>)node).getValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper that given a loop body makes sure that it is not terminal if it
|
||||
* has a continue that leads to the loop header or to outer loops' loop
|
||||
@ -636,7 +663,7 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo
|
||||
@Override
|
||||
public Node leaveContinueNode(final ContinueNode node) {
|
||||
// all inner loops have been popped.
|
||||
if (lex.contains(lex.getContinueTo(node.getLabel()))) {
|
||||
if (lex.contains(lex.getContinueTo(node.getLabelName()))) {
|
||||
escapes.add(node);
|
||||
}
|
||||
return node;
|
||||
@ -663,6 +690,14 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo
|
||||
return statement;
|
||||
}
|
||||
|
||||
private void addStatementEnclosedInBlock(final Statement stmt) {
|
||||
BlockStatement b = BlockStatement.createReplacement(stmt, Collections.<Statement>singletonList(stmt));
|
||||
if(stmt.isTerminal()) {
|
||||
b = b.setBlock(b.getBlock().setIsTerminal(null, true));
|
||||
}
|
||||
addStatement(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* An internal expression has a symbol that is tagged internal. Check if
|
||||
* this is such a node
|
||||
@ -671,7 +706,10 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo
|
||||
* @return true if internal, false otherwise
|
||||
*/
|
||||
private static boolean isInternalExpression(final Expression expression) {
|
||||
final Symbol symbol = expression.getSymbol();
|
||||
if (!(expression instanceof IdentNode)) {
|
||||
return false;
|
||||
}
|
||||
final Symbol symbol = ((IdentNode)expression).getSymbol();
|
||||
return symbol != null && symbol.isInternal();
|
||||
}
|
||||
|
||||
@ -684,7 +722,6 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo
|
||||
*/
|
||||
private static boolean isEvalResultAssignment(final Node expression) {
|
||||
final Node e = expression;
|
||||
assert e.tokenType() != TokenType.DISCARD; //there are no discards this early anymore
|
||||
if (e instanceof BinaryNode) {
|
||||
final Node lhs = ((BinaryNode)e).lhs();
|
||||
if (lhs instanceof IdentNode) {
|
||||
@ -693,5 +730,4 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -139,10 +139,6 @@ public class MapCreator<T> {
|
||||
flags |= Property.NOT_CONFIGURABLE;
|
||||
}
|
||||
|
||||
if (symbol.canBeUndefined()) {
|
||||
flags |= Property.CAN_BE_UNDEFINED;
|
||||
}
|
||||
|
||||
if (symbol.isFunctionDeclaration()) {
|
||||
flags |= Property.IS_FUNCTION_DECLARATION;
|
||||
}
|
||||
|
||||
@ -26,6 +26,8 @@
|
||||
package jdk.nashorn.internal.codegen;
|
||||
|
||||
import static jdk.nashorn.internal.codegen.ObjectClassGenerator.OBJECT_FIELDS_ONLY;
|
||||
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.Symbol;
|
||||
|
||||
/**
|
||||
@ -35,15 +37,17 @@ import jdk.nashorn.internal.ir.Symbol;
|
||||
class MapTuple<T> {
|
||||
final String key;
|
||||
final Symbol symbol;
|
||||
final Type type;
|
||||
final T value;
|
||||
|
||||
MapTuple(final String key, final Symbol symbol) {
|
||||
this(key, symbol, null);
|
||||
MapTuple(final String key, final Symbol symbol, final Type type) {
|
||||
this(key, symbol, type, null);
|
||||
}
|
||||
|
||||
MapTuple(final String key, final Symbol symbol, final T value) {
|
||||
MapTuple(final String key, final Symbol symbol, final Type type, final T value) {
|
||||
this.key = key;
|
||||
this.symbol = symbol;
|
||||
this.type = type;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
||||
@ -43,11 +43,11 @@ import static jdk.internal.org.objectweb.asm.Opcodes.IFNULL;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IF_ACMPEQ;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IF_ACMPNE;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IF_ICMPEQ;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IF_ICMPNE;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IF_ICMPGE;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IF_ICMPGT;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IF_ICMPLE;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IF_ICMPLT;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IF_ICMPNE;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.INSTANCEOF;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.INVOKEINTERFACE;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESPECIAL;
|
||||
@ -74,11 +74,11 @@ import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALL
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
|
||||
import java.util.Map;
|
||||
import jdk.internal.dynalink.support.NameCodec;
|
||||
import jdk.internal.org.objectweb.asm.Handle;
|
||||
import jdk.internal.org.objectweb.asm.MethodVisitor;
|
||||
@ -89,12 +89,16 @@ import jdk.nashorn.internal.codegen.types.ArrayType;
|
||||
import jdk.nashorn.internal.codegen.types.BitwiseType;
|
||||
import jdk.nashorn.internal.codegen.types.NumericType;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.BreakableNode;
|
||||
import jdk.nashorn.internal.ir.FunctionNode;
|
||||
import jdk.nashorn.internal.ir.IdentNode;
|
||||
import jdk.nashorn.internal.ir.JoinPredecessor;
|
||||
import jdk.nashorn.internal.ir.LexicalContext;
|
||||
import jdk.nashorn.internal.ir.LiteralNode;
|
||||
import jdk.nashorn.internal.ir.LocalVariableConversion;
|
||||
import jdk.nashorn.internal.ir.RuntimeNode;
|
||||
import jdk.nashorn.internal.ir.Symbol;
|
||||
import jdk.nashorn.internal.ir.TryNode;
|
||||
import jdk.nashorn.internal.objects.Global;
|
||||
import jdk.nashorn.internal.runtime.ArgumentSetter;
|
||||
import jdk.nashorn.internal.runtime.Context;
|
||||
@ -102,6 +106,7 @@ import jdk.nashorn.internal.runtime.Debug;
|
||||
import jdk.nashorn.internal.runtime.JSType;
|
||||
import jdk.nashorn.internal.runtime.RewriteException;
|
||||
import jdk.nashorn.internal.runtime.ScriptObject;
|
||||
import jdk.nashorn.internal.runtime.UnwarrantedOptimismException;
|
||||
import jdk.nashorn.internal.runtime.linker.Bootstrap;
|
||||
import jdk.nashorn.internal.runtime.logging.DebugLogger;
|
||||
import jdk.nashorn.internal.runtime.options.Options;
|
||||
@ -123,23 +128,28 @@ public class MethodEmitter implements Emitter {
|
||||
/** The ASM MethodVisitor we are plugged into */
|
||||
private final MethodVisitor method;
|
||||
|
||||
/** Current type stack for current evaluation */
|
||||
private Label.Stack stack;
|
||||
|
||||
/** Parent classEmitter representing the class of this method */
|
||||
private final ClassEmitter classEmitter;
|
||||
|
||||
/** FunctionNode representing this method, or null if none exists */
|
||||
protected FunctionNode functionNode;
|
||||
|
||||
/** Current type stack for current evaluation */
|
||||
private Label.Stack stack;
|
||||
|
||||
/** Check whether this emitter ever has a function return point */
|
||||
private boolean hasReturn;
|
||||
|
||||
private boolean preventUndefinedLoad;
|
||||
|
||||
/**
|
||||
* Map of live local variable definitions.
|
||||
*/
|
||||
private final Map<Symbol, LocalVariableDef> localVariableDefs = new IdentityHashMap<>();
|
||||
|
||||
/** The context */
|
||||
private final Context context;
|
||||
|
||||
private final List<Type> localVariableTypes = new ArrayList<>();
|
||||
|
||||
/** Threshold in chars for when string constants should be split */
|
||||
static final int LARGE_STRING_THRESHOLD = 32 * 1024;
|
||||
|
||||
@ -225,9 +235,12 @@ public class MethodEmitter implements Emitter {
|
||||
classEmitter.endMethod(this);
|
||||
}
|
||||
|
||||
void createNewStack() {
|
||||
assert stack == null;
|
||||
newStack();
|
||||
boolean isReachable() {
|
||||
return stack != null;
|
||||
}
|
||||
|
||||
private void doesNotContinueSequentially() {
|
||||
stack = null;
|
||||
}
|
||||
|
||||
private void newStack() {
|
||||
@ -273,26 +286,40 @@ public class MethodEmitter implements Emitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop a type from the existing stack, ensuring that it is numeric,
|
||||
* assert if not
|
||||
* Pop a type from the existing stack, ensuring that it is numeric. Boolean type is popped as int type.
|
||||
*
|
||||
* @return the type
|
||||
*/
|
||||
private NumericType popNumeric() {
|
||||
final Type type = popType();
|
||||
assert type.isNumeric() : type + " is not numeric";
|
||||
if(type.isBoolean()) {
|
||||
// Booleans are treated as int for purposes of arithmetic operations
|
||||
return Type.INT;
|
||||
}
|
||||
assert type.isNumeric();
|
||||
return (NumericType)type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop a type from the existing stack, ensuring that it is an integer type
|
||||
* (integer or long), assert if not
|
||||
* (integer or long). Boolean type is popped as int type.
|
||||
*
|
||||
* @return the type
|
||||
*/
|
||||
private BitwiseType popBitwise() {
|
||||
final Type type = popType();
|
||||
if(type == Type.BOOLEAN) {
|
||||
return Type.INT;
|
||||
}
|
||||
return (BitwiseType)type;
|
||||
}
|
||||
|
||||
private BitwiseType popInteger() {
|
||||
final Type type = popType();
|
||||
assert type.isInteger() || type.isLong() : type + " is not an integer or long";
|
||||
if(type == Type.BOOLEAN) {
|
||||
return Type.INT;
|
||||
}
|
||||
assert type == Type.INT;
|
||||
return (BitwiseType)type;
|
||||
}
|
||||
|
||||
@ -534,24 +561,14 @@ public class MethodEmitter implements Emitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a local variable. This is a nop if the symbol has no slot
|
||||
*
|
||||
* @param symbol symbol for the local variable
|
||||
* @param start start of scope
|
||||
* @param end end of scope
|
||||
* Initializes a bytecode method parameter
|
||||
* @param symbol the symbol for the parameter
|
||||
* @param type the type of the parameter
|
||||
* @param start the label for the start of the method
|
||||
*/
|
||||
void localVariable(final Symbol symbol, final Label start, final Label end) {
|
||||
if (!symbol.hasSlot()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String name = symbol.getName();
|
||||
|
||||
if (name.equals(THIS.symbolName())) {
|
||||
name = THIS_DEBUGGER.symbolName();
|
||||
}
|
||||
|
||||
method.visitLocalVariable(name, symbol.getSymbolType().getDescriptor(), null, start.getLabel(), end.getLabel(), symbol.getSlot());
|
||||
void initializeMethodParameter(final Symbol symbol, final Type type, final Label start) {
|
||||
assert symbol.isBytecodeLocal();
|
||||
localVariableDefs.put(symbol, new LocalVariableDef(start.getLabel(), type));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -619,8 +636,8 @@ public class MethodEmitter implements Emitter {
|
||||
*/
|
||||
MethodEmitter shr() {
|
||||
debug("shr");
|
||||
popType(Type.INT);
|
||||
pushType(popInteger().shr(method));
|
||||
popInteger();
|
||||
pushType(popBitwise().shr(method));
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -632,21 +649,21 @@ public class MethodEmitter implements Emitter {
|
||||
*/
|
||||
MethodEmitter shl() {
|
||||
debug("shl");
|
||||
popType(Type.INT);
|
||||
pushType(popInteger().shl(method));
|
||||
popInteger();
|
||||
pushType(popBitwise().shl(method));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pops two integer types from the stack, performs a bitwise arithetic shift right and pushes
|
||||
* Pops two integer types from the stack, performs a bitwise arithmetic shift right and pushes
|
||||
* the result. The shift count, the first element, must be INT.
|
||||
*
|
||||
* @return the method emitter
|
||||
*/
|
||||
MethodEmitter sar() {
|
||||
debug("sar");
|
||||
popType(Type.INT);
|
||||
pushType(popInteger().sar(method));
|
||||
popInteger();
|
||||
pushType(popBitwise().sar(method));
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -668,9 +685,12 @@ public class MethodEmitter implements Emitter {
|
||||
* @param recovery label pointing to start of catch block
|
||||
*/
|
||||
void _catch(final Label recovery) {
|
||||
// While in JVM a catch block can be reached through normal control flow, our code generator never does this,
|
||||
// so we might as well presume there's no stack on entry.
|
||||
assert stack == null;
|
||||
recovery.onCatch();
|
||||
label(recovery);
|
||||
stack.clear();
|
||||
pushType(Type.typeFor(Throwable.class));
|
||||
beginCatchBlock();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -680,13 +700,21 @@ public class MethodEmitter implements Emitter {
|
||||
* @param recoveries labels pointing to start of catch block
|
||||
*/
|
||||
void _catch(final Collection<Label> recoveries) {
|
||||
assert stack == null;
|
||||
for(final Label l: recoveries) {
|
||||
label(l);
|
||||
}
|
||||
stack.clear();
|
||||
pushType(Type.OBJECT);
|
||||
beginCatchBlock();
|
||||
}
|
||||
|
||||
private void beginCatchBlock() {
|
||||
// It can happen that the catch label wasn't marked as reachable. They are marked as reachable if there's an
|
||||
// assignment in the try block, but it's possible that there was none.
|
||||
if(!isReachable()) {
|
||||
newStack();
|
||||
}
|
||||
pushType(Type.typeFor(Throwable.class));
|
||||
}
|
||||
/**
|
||||
* Start a try/catch block.
|
||||
*
|
||||
@ -694,8 +722,12 @@ public class MethodEmitter implements Emitter {
|
||||
* @param exit end label for try
|
||||
* @param recovery start label for catch
|
||||
* @param typeDescriptor type descriptor for exception
|
||||
* @param isOptimismHandler true if this is a hander for {@code UnwarrantedOptimismException}. Normally joining on a
|
||||
* catch handler kills temporary variables, but optimism handlers are an exception, as they need to capture
|
||||
* temporaries as well, so they must remain live.
|
||||
*/
|
||||
void _try(final Label entry, final Label exit, final Label recovery, final String typeDescriptor) {
|
||||
private void _try(final Label entry, final Label exit, final Label recovery, final String typeDescriptor, final boolean isOptimismHandler) {
|
||||
recovery.joinFromTry(entry.getStack(), isOptimismHandler);
|
||||
method.visitTryCatchBlock(entry.getLabel(), exit.getLabel(), recovery.getLabel(), typeDescriptor);
|
||||
}
|
||||
|
||||
@ -708,7 +740,7 @@ public class MethodEmitter implements Emitter {
|
||||
* @param clazz exception class
|
||||
*/
|
||||
void _try(final Label entry, final Label exit, final Label recovery, final Class<?> clazz) {
|
||||
method.visitTryCatchBlock(entry.getLabel(), exit.getLabel(), recovery.getLabel(), CompilerConstants.className(clazz));
|
||||
_try(entry, exit, recovery, CompilerConstants.className(clazz), clazz == UnwarrantedOptimismException.class);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -719,9 +751,12 @@ public class MethodEmitter implements Emitter {
|
||||
* @param recovery start label for catch
|
||||
*/
|
||||
void _try(final Label entry, final Label exit, final Label recovery) {
|
||||
_try(entry, exit, recovery, (String)null);
|
||||
_try(entry, exit, recovery, (String)null, false);
|
||||
}
|
||||
|
||||
void markLabelAsOptimisticCatchHandler(final Label label, final int liveLocalCount) {
|
||||
label.markAsOptimisticCatchHandler(stack, liveLocalCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the constants array
|
||||
@ -894,23 +929,36 @@ public class MethodEmitter implements Emitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a local variable to the stack. If the symbol representing
|
||||
* the local variable doesn't have a slot, this is a NOP
|
||||
* Pushes the value of an identifier to the stack. If the identifier does not represent a local variable or a
|
||||
* parameter, this will be a no-op.
|
||||
*
|
||||
* @param symbol the symbol representing the local variable.
|
||||
* @param ident the identifier for the variable being loaded.
|
||||
*
|
||||
* @return the method emitter
|
||||
*/
|
||||
MethodEmitter load(final Symbol symbol) {
|
||||
MethodEmitter load(final IdentNode ident) {
|
||||
return load(ident.getSymbol(), ident.getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes the value of the symbol to the stack with the specified type. No type conversion is being performed, and
|
||||
* the type is only being used if the symbol addresses a local variable slot. The value of the symbol is loaded if
|
||||
* it addresses a local variable slot, or it is a parameter (in which case it can also be loaded from a vararg array
|
||||
* or the arguments object). If it is neither, the operation is a no-op.
|
||||
*
|
||||
* @param symbol the symbol addressing the value being loaded
|
||||
* @param type the presumed type of the value when it is loaded from a local variable slot
|
||||
* @return the method emitter
|
||||
*/
|
||||
MethodEmitter load(final Symbol symbol, final Type type) {
|
||||
assert symbol != null;
|
||||
if (symbol.hasSlot()) {
|
||||
final int slot = symbol.getSlot();
|
||||
debug("load symbol", symbol.getName(), " slot=", slot, "type=", symbol.getSymbolType());
|
||||
load(symbol.getSymbolType(), slot);
|
||||
final int slot = symbol.getSlot(type);
|
||||
debug("load symbol", symbol.getName(), " slot=", slot, "type=", type);
|
||||
load(type, slot);
|
||||
// _try(new Label("dummy"), new Label("dummy2"), recovery);
|
||||
// method.visitTryCatchBlock(new Label(), arg1, arg2, arg3);
|
||||
} else if (symbol.isParam()) {
|
||||
assert !symbol.isScope();
|
||||
assert functionNode.isVarArg() : "Non-vararg functions have slotted parameters";
|
||||
final int index = symbol.getFieldIndex();
|
||||
if (functionNode.needsArguments()) {
|
||||
@ -931,7 +979,7 @@ public class MethodEmitter implements Emitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a local variable to the stack, given an explicit bytecode slot
|
||||
* Push a local variable to the stack, given an explicit bytecode slot.
|
||||
* This is used e.g. for stub generation where we know where items like
|
||||
* "this" and "scope" reside.
|
||||
*
|
||||
@ -945,6 +993,8 @@ public class MethodEmitter implements Emitter {
|
||||
final Type loadType = type.load(method, slot);
|
||||
assert loadType != null;
|
||||
pushType(loadType == Type.OBJECT && isThisSlot(slot) ? Type.THIS : loadType);
|
||||
assert !preventUndefinedLoad || (slot < stack.localVariableTypes.size() && stack.localVariableTypes.get(slot) != Type.UNKNOWN)
|
||||
: "Attempted load of uninitialized slot " + slot + " (as type " + type + ")";
|
||||
stack.markLocalLoad(slot);
|
||||
return this;
|
||||
}
|
||||
@ -953,7 +1003,7 @@ public class MethodEmitter implements Emitter {
|
||||
if (functionNode == null) {
|
||||
return slot == CompilerConstants.JAVA_THIS.slot();
|
||||
}
|
||||
final int thisSlot = compilerConstant(THIS).getSlot();
|
||||
final int thisSlot = getCompilerConstantSymbol(THIS).getSlot(Type.OBJECT);
|
||||
assert !functionNode.needsCallee() || thisSlot == 1; // needsCallee -> thisSlot == 1
|
||||
assert functionNode.needsCallee() || thisSlot == 0; // !needsCallee -> thisSlot == 0
|
||||
return slot == thisSlot;
|
||||
@ -975,7 +1025,7 @@ public class MethodEmitter implements Emitter {
|
||||
return this;
|
||||
}
|
||||
|
||||
private Symbol compilerConstant(final CompilerConstants cc) {
|
||||
private Symbol getCompilerConstantSymbol(final CompilerConstants cc) {
|
||||
return functionNode.getBody().getExistingSymbol(cc.symbolName());
|
||||
}
|
||||
|
||||
@ -985,22 +1035,38 @@ public class MethodEmitter implements Emitter {
|
||||
* @return if this method has a slot allocated for the scope variable.
|
||||
*/
|
||||
boolean hasScope() {
|
||||
return compilerConstant(SCOPE).hasSlot();
|
||||
return getCompilerConstantSymbol(SCOPE).hasSlot();
|
||||
}
|
||||
|
||||
MethodEmitter loadCompilerConstant(final CompilerConstants cc) {
|
||||
final Symbol symbol = compilerConstant(cc);
|
||||
return loadCompilerConstant(cc, null);
|
||||
}
|
||||
|
||||
MethodEmitter loadCompilerConstant(final CompilerConstants cc, final Type type) {
|
||||
if (cc == SCOPE && peekType() == Type.SCOPE) {
|
||||
dup();
|
||||
return this;
|
||||
}
|
||||
return load(symbol);
|
||||
return load(getCompilerConstantSymbol(cc), type != null ? type : getCompilerConstantType(cc));
|
||||
}
|
||||
|
||||
void storeCompilerConstant(final CompilerConstants cc) {
|
||||
final Symbol symbol = compilerConstant(cc);
|
||||
storeCompilerConstant(cc, null);
|
||||
}
|
||||
|
||||
void storeCompilerConstant(final CompilerConstants cc, final Type type) {
|
||||
final Symbol symbol = getCompilerConstantSymbol(cc);
|
||||
if(!symbol.hasSlot()) {
|
||||
return;
|
||||
}
|
||||
debug("store compiler constant ", symbol);
|
||||
store(symbol);
|
||||
store(symbol, type != null ? type : getCompilerConstantType(cc));
|
||||
}
|
||||
|
||||
private static Type getCompilerConstantType(final CompilerConstants cc) {
|
||||
final Class<?> constantType = cc.type();
|
||||
assert constantType != null;
|
||||
return Type.typeFor(constantType);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1032,74 +1098,212 @@ public class MethodEmitter implements Emitter {
|
||||
|
||||
/**
|
||||
* Pop a value from the stack and store it in a local variable represented
|
||||
* by the given symbol. If the symbol has no slot, this is a NOP
|
||||
* by the given identifier. If the symbol has no slot, this is a NOP
|
||||
*
|
||||
* @param symbol symbol to store stack to
|
||||
* @param ident identifier to store stack to
|
||||
*/
|
||||
void store(final Symbol symbol) {
|
||||
void store(final IdentNode ident) {
|
||||
final Type type = ident.getType();
|
||||
final Symbol symbol = ident.getSymbol();
|
||||
if(type == Type.UNDEFINED) {
|
||||
assert peekType() == Type.UNDEFINED;
|
||||
store(symbol, Type.OBJECT);
|
||||
} else {
|
||||
store(symbol, type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a definition of a local variable with a type. Used for local variable table building.
|
||||
*/
|
||||
private static class LocalVariableDef {
|
||||
// The start label from where this definition lives.
|
||||
private final jdk.internal.org.objectweb.asm.Label label;
|
||||
// The currently live type of the local variable.
|
||||
private final Type type;
|
||||
|
||||
LocalVariableDef(final jdk.internal.org.objectweb.asm.Label label, final Type type) {
|
||||
this.label = label;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void closeLocalVariable(final Symbol symbol, final Label label) {
|
||||
final LocalVariableDef def = localVariableDefs.get(symbol);
|
||||
if(def != null) {
|
||||
endLocalValueDef(symbol, def, label.getLabel());
|
||||
}
|
||||
if(isReachable()) {
|
||||
markDeadLocalVariable(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
void markDeadLocalVariable(final Symbol symbol) {
|
||||
if(!symbol.isDead()) {
|
||||
markDeadSlots(symbol.getFirstSlot(), symbol.slotCount());
|
||||
}
|
||||
}
|
||||
|
||||
void markDeadSlots(final int firstSlot, final int slotCount) {
|
||||
stack.markDeadLocalVariables(firstSlot, slotCount);
|
||||
}
|
||||
|
||||
private void endLocalValueDef(final Symbol symbol, final LocalVariableDef def, final jdk.internal.org.objectweb.asm.Label label) {
|
||||
String name = symbol.getName();
|
||||
if (name.equals(THIS.symbolName())) {
|
||||
name = THIS_DEBUGGER.symbolName();
|
||||
}
|
||||
method.visitLocalVariable(name, def.type.getDescriptor(), null, def.label, label, symbol.getSlot(def.type));
|
||||
}
|
||||
|
||||
void store(final Symbol symbol, final Type type) {
|
||||
store(symbol, type, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop a value from the stack and store it in a variable denoted by the given symbol. The variable should be either
|
||||
* a local variable, or a function parameter (and not a scoped variable). For local variables, this method will also
|
||||
* do the bookeeping of the local variable table as well as mark values in all alternative slots for the symbol as
|
||||
* dead. In this regard it differs from {@link #storeHidden(Type, int)}.
|
||||
*
|
||||
* @param symbol the symbol to store into.
|
||||
* @param type the type to store
|
||||
* @param onlySymbolLiveValue if true, this is the sole live value for the symbol. If false, currently live values should
|
||||
* be kept live.
|
||||
*/
|
||||
void store(final Symbol symbol, final Type type, final boolean onlySymbolLiveValue) {
|
||||
assert symbol != null : "No symbol to store";
|
||||
if (symbol.hasSlot()) {
|
||||
final int slot = symbol.getSlot();
|
||||
debug("store symbol", symbol.getName(), " slot=", slot);
|
||||
store(symbol.getSymbolType(), slot);
|
||||
final boolean isLiveType = symbol.hasSlotFor(type);
|
||||
final LocalVariableDef existingDef = localVariableDefs.get(symbol);
|
||||
if(existingDef == null || existingDef.type != type) {
|
||||
final jdk.internal.org.objectweb.asm.Label here = new jdk.internal.org.objectweb.asm.Label();
|
||||
if(isLiveType) {
|
||||
final LocalVariableDef newDef = new LocalVariableDef(here, type);
|
||||
localVariableDefs.put(symbol, newDef);
|
||||
}
|
||||
method.visitLabel(here);
|
||||
if(existingDef != null) {
|
||||
endLocalValueDef(symbol, existingDef, here);
|
||||
}
|
||||
}
|
||||
if(isLiveType) {
|
||||
final int slot = symbol.getSlot(type);
|
||||
debug("store symbol", symbol.getName(), " type=", type, " slot=", slot);
|
||||
storeHidden(type, slot, onlySymbolLiveValue);
|
||||
} else {
|
||||
if(onlySymbolLiveValue) {
|
||||
markDeadLocalVariable(symbol);
|
||||
}
|
||||
debug("dead store symbol ", symbol.getName(), " type=", type);
|
||||
pop();
|
||||
}
|
||||
} else if (symbol.isParam()) {
|
||||
assert !symbol.isScope();
|
||||
assert functionNode.isVarArg() : "Non-vararg functions have slotted parameters";
|
||||
final int index = symbol.getFieldIndex();
|
||||
if (functionNode.needsArguments()) {
|
||||
convert(Type.OBJECT);
|
||||
debug("store symbol", symbol.getName(), " arguments index=", index);
|
||||
loadCompilerConstant(ARGUMENTS);
|
||||
load(index);
|
||||
ArgumentSetter.SET_ARGUMENT.invoke(this);
|
||||
} else {
|
||||
convert(Type.OBJECT);
|
||||
// varargs without arguments object - just do array store to __varargs__
|
||||
debug("store symbol", symbol.getName(), " array index=", index);
|
||||
loadCompilerConstant(VARARGS);
|
||||
load(index);
|
||||
ArgumentSetter.SET_ARRAY_ELEMENT.invoke(this);
|
||||
}
|
||||
} else {
|
||||
debug("dead store symbol ", symbol.getName(), " type=", type);
|
||||
pop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop a value from the stack and store it in a given local variable
|
||||
* slot.
|
||||
* Pop a value from the stack and store it in a local variable slot. Note that in contrast with
|
||||
* {@link #store(Symbol, Type)}, this method does not adjust the local variable table, nor marks slots for
|
||||
* alternative value types for the symbol as being dead. For that reason, this method is usually not called
|
||||
* directly. Notable exceptions are temporary internal locals (e.g. quick store, last-catch-condition, etc.) that
|
||||
* are not desired to show up in the local variable table.
|
||||
*
|
||||
* @param type the type to pop
|
||||
* @param slot the slot
|
||||
*/
|
||||
void store(final Type type, final int slot) {
|
||||
void storeHidden(final Type type, final int slot) {
|
||||
storeHidden(type, slot, true);
|
||||
}
|
||||
|
||||
void storeHidden(final Type type, final int slot, final boolean onlyLiveSymbolValue) {
|
||||
explicitStore(type, slot);
|
||||
stack.onLocalStore(type, slot, onlyLiveSymbolValue);
|
||||
}
|
||||
|
||||
void storeTemp(final Type type, final int slot) {
|
||||
explicitStore(type, slot);
|
||||
defineTemporaryLocalVariable(slot, slot + type.getSlots());
|
||||
onLocalStore(type, slot);
|
||||
}
|
||||
|
||||
void onLocalStore(final Type type, final int slot) {
|
||||
stack.onLocalStore(type, slot, true);
|
||||
}
|
||||
|
||||
private void explicitStore(final Type type, final int slot) {
|
||||
assert slot != -1;
|
||||
debug("explicit store", type, slot);
|
||||
popType(type);
|
||||
type.store(method, slot);
|
||||
// TODO: disable this when not running with optimistic types?
|
||||
final int slotCount = type.getSlots();
|
||||
ensureLocalVariableCount(slot + slotCount);
|
||||
localVariableTypes.set(slot, type);
|
||||
if(slotCount == 2) {
|
||||
localVariableTypes.set(slot + 1, Type.SLOT_2);
|
||||
}
|
||||
stack.markLocalStore(slot, slotCount);
|
||||
}
|
||||
|
||||
void ensureLocalVariableCount(final int slotCount) {
|
||||
while(localVariableTypes.size() < slotCount) {
|
||||
localVariableTypes.add(Type.UNKNOWN);
|
||||
/**
|
||||
* Marks a range of slots as belonging to a defined local variable. The slots will start out with no live value
|
||||
* in them.
|
||||
* @param fromSlot first slot, inclusive.
|
||||
* @param toSlot last slot, exclusive.
|
||||
*/
|
||||
void defineBlockLocalVariable(final int fromSlot, final int toSlot) {
|
||||
stack.defineBlockLocalVariable(fromSlot, toSlot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a range of slots as belonging to a defined temporary local variable. The slots will start out with no
|
||||
* live value in them.
|
||||
* @param fromSlot first slot, inclusive.
|
||||
* @param toSlot last slot, exclusive.
|
||||
*/
|
||||
void defineTemporaryLocalVariable(final int fromSlot, final int toSlot) {
|
||||
stack.defineTemporaryLocalVariable(fromSlot, toSlot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a new temporary local variable and returns its allocated index.
|
||||
* @param width the required width (in slots) for the new variable.
|
||||
* @return the bytecode slot index where the newly allocated local begins.
|
||||
*/
|
||||
int defineTemporaryLocalVariable(final int width) {
|
||||
return stack.defineTemporaryLocalVariable(width);
|
||||
}
|
||||
|
||||
void undefineLocalVariables(final int fromSlot, final boolean canTruncateSymbol) {
|
||||
if(isReachable()) {
|
||||
stack.undefineLocalVariables(fromSlot, canTruncateSymbol);
|
||||
}
|
||||
}
|
||||
|
||||
List<Type> getLocalVariableTypes() {
|
||||
return localVariableTypes;
|
||||
return stack.localVariableTypes;
|
||||
}
|
||||
|
||||
void setParameterTypes(final Type... paramTypes) {
|
||||
assert localVariableTypes.isEmpty();
|
||||
for(final Type type: paramTypes) {
|
||||
localVariableTypes.add(type);
|
||||
if(type.isCategory2()) {
|
||||
localVariableTypes.add(Type.SLOT_2);
|
||||
}
|
||||
}
|
||||
List<Type> getWidestLiveLocals(final List<Type> localTypes) {
|
||||
return stack.getWidestLiveLocals(localTypes);
|
||||
}
|
||||
|
||||
String markSymbolBoundariesInLvarTypesDescriptor(final String lvarDescriptor) {
|
||||
return stack.markSymbolBoundariesInLvarTypesDescriptor(lvarDescriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1122,7 +1326,7 @@ public class MethodEmitter implements Emitter {
|
||||
final Type receiver = popType(Type.OBJECT);
|
||||
assert Throwable.class.isAssignableFrom(receiver.getTypeClass()) : receiver.getTypeClass();
|
||||
method.visitInsn(ATHROW);
|
||||
stack = null;
|
||||
doesNotContinueSequentially();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1354,7 +1558,7 @@ public class MethodEmitter implements Emitter {
|
||||
debug("lookupswitch", peekType());
|
||||
adjustStackForSwitch(defaultLabel, table);
|
||||
method.visitLookupSwitchInsn(defaultLabel.getLabel(), values, getLabels(table));
|
||||
stack = null; //whoever reaches the point after us provides the stack, because we don't
|
||||
doesNotContinueSequentially();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1368,14 +1572,14 @@ public class MethodEmitter implements Emitter {
|
||||
debug("tableswitch", peekType());
|
||||
adjustStackForSwitch(defaultLabel, table);
|
||||
method.visitTableSwitchInsn(lo, hi, defaultLabel.getLabel(), getLabels(table));
|
||||
stack = null; //whoever reaches the point after us provides the stack, because we don't
|
||||
doesNotContinueSequentially();
|
||||
}
|
||||
|
||||
private void adjustStackForSwitch(final Label defaultLabel, final Label... table) {
|
||||
popType(Type.INT);
|
||||
mergeStackTo(defaultLabel);
|
||||
joinTo(defaultLabel);
|
||||
for(final Label label: table) {
|
||||
mergeStackTo(label);
|
||||
joinTo(label);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1432,7 +1636,7 @@ public class MethodEmitter implements Emitter {
|
||||
convert(type);
|
||||
}
|
||||
popType(type)._return(method);
|
||||
stack = null;
|
||||
doesNotContinueSequentially();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1449,7 +1653,7 @@ public class MethodEmitter implements Emitter {
|
||||
debug("return [void]");
|
||||
assert stack.isEmpty() : stack;
|
||||
method.visitInsn(RETURN);
|
||||
stack = null;
|
||||
doesNotContinueSequentially();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1458,8 +1662,10 @@ public class MethodEmitter implements Emitter {
|
||||
* jump target is another method
|
||||
*
|
||||
* @param label destination label
|
||||
* @param targetNode the node to which the destination label belongs (the label is normally a break or continue
|
||||
* label)
|
||||
*/
|
||||
void splitAwareGoto(final LexicalContext lc, final Label label) {
|
||||
void splitAwareGoto(final LexicalContext lc, final Label label, final BreakableNode targetNode) {
|
||||
_goto(label);
|
||||
}
|
||||
|
||||
@ -1486,7 +1692,7 @@ public class MethodEmitter implements Emitter {
|
||||
assert peekType().isInteger() || peekType().isBoolean() || peekType().isObject() : "expecting integer type or object for jump, but found " + peekType();
|
||||
popType();
|
||||
}
|
||||
mergeStackTo(label);
|
||||
joinTo(label);
|
||||
method.visitJumpInsn(opcode, label.getLabel());
|
||||
}
|
||||
|
||||
@ -1658,17 +1864,41 @@ public class MethodEmitter implements Emitter {
|
||||
void _goto(final Label label) {
|
||||
debug("goto", label);
|
||||
jump(GOTO, label, 0);
|
||||
stack = null; //whoever reaches the point after us provides the stack, because we don't
|
||||
doesNotContinueSequentially(); //whoever reaches the point after us provides the stack, because we don't
|
||||
}
|
||||
|
||||
/**
|
||||
* Examine two stacks and make sure they are of the same size and their
|
||||
* contents are equivalent to each other
|
||||
* @param s0 first stack
|
||||
* @param s1 second stack
|
||||
* Unconditional jump to the start label of a loop. It differs from ordinary {@link #_goto(Label)} in that it will
|
||||
* preserve the current label stack, as the next instruction after the goto is loop body that the loop will come
|
||||
* back to. Also used to jump at the start label of the continuation handler, as it behaves much like a loop test in
|
||||
* the sense that after it is evaluated, it also jumps backwards.
|
||||
*
|
||||
* @return true if stacks are equivalent, false otherwise
|
||||
* @param loopStart start label of a loop
|
||||
*/
|
||||
void gotoLoopStart(final Label loopStart) {
|
||||
debug("goto (loop)", loopStart);
|
||||
jump(GOTO, loopStart, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unconditional jump without any control flow and data flow testing. You should not normally use this method when
|
||||
* generating code, except if you're very sure that you know what you're doing. Normally only used for the
|
||||
* admittedly torturous control flow of continuation handler plumbing.
|
||||
* @param target the target of the jump
|
||||
*/
|
||||
void uncheckedGoto(final Label target) {
|
||||
method.visitJumpInsn(GOTO, target.getLabel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Potential transfer of control to a catch block.
|
||||
*
|
||||
* @param catchLabel destination catch label
|
||||
*/
|
||||
void canThrow(final Label catchLabel) {
|
||||
catchLabel.joinFromTry(stack, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* A join in control flow - helper function that makes sure all entry stacks
|
||||
* discovered for the join point so far are equivalent
|
||||
@ -1678,46 +1908,46 @@ public class MethodEmitter implements Emitter {
|
||||
*
|
||||
* @param label label
|
||||
*/
|
||||
private void mergeStackTo(final Label label) {
|
||||
//sometimes we can do a merge stack without having a stack - i.e. when jumping ahead to dead code
|
||||
//see NASHORN-73. So far we had been saved by the line number nodes. This should have been fixed
|
||||
//by Lower removing everything after an unconditionally executed terminating statement OR a break
|
||||
//or continue in a block. Previously code left over after breaks and continues was still there
|
||||
//and caused bytecode to be generated - which crashed on stack not being there, as the merge
|
||||
//was not in fact preceeded by a visit. Furthermore, this led to ASM putting out its NOP NOP NOP
|
||||
//ATHROW sequences instead of no code being generated at all. This should now be fixed.
|
||||
assert stack != null : label + " entered with no stack. deadcode that remains?";
|
||||
|
||||
final Label.Stack labelStack = label.getStack();
|
||||
if (labelStack == null) {
|
||||
label.setStack(stack.copy());
|
||||
return;
|
||||
}
|
||||
assert stack.isEquivalentInTypesTo(labelStack) : "stacks " + stack + " is not equivalent with " + labelStack + " at join point " + label;
|
||||
stack.mergeLocalLoads(labelStack);
|
||||
private void joinTo(final Label label) {
|
||||
assert isReachable();
|
||||
label.joinFrom(stack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new label, enter it here.
|
||||
*
|
||||
* @param label the label
|
||||
* @param label
|
||||
*/
|
||||
void label(final Label label) {
|
||||
/*
|
||||
* If stack == null, this means that we came here not through a fallthrough.
|
||||
* E.g. a label after an athrow. Then we create a new stack if one doesn't exist
|
||||
* for this location already.
|
||||
*/
|
||||
if (stack == null) {
|
||||
stack = label.getStack();
|
||||
if (stack == null) {
|
||||
newStack();
|
||||
}
|
||||
breakLabel(label, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new break target label, enter it here.
|
||||
*
|
||||
* @param label the label
|
||||
* @param liveLocals the number of live locals at this label
|
||||
*/
|
||||
void breakLabel(final Label label, final int liveLocals) {
|
||||
if (!isReachable()) {
|
||||
// If we emit a label, and the label's stack is null, it must not be reachable.
|
||||
assert (label.getStack() == null) != label.isReachable();
|
||||
} else {
|
||||
joinTo(label);
|
||||
}
|
||||
// Use label's stack as we might have no stack.
|
||||
final Label.Stack labelStack = label.getStack();
|
||||
stack = labelStack == null ? null : labelStack.clone();
|
||||
if(stack != null && label.isBreakTarget() && liveLocals != -1) {
|
||||
// This has to be done because we might not have another frame to provide us with its firstTemp if the label
|
||||
// is only reachable through a break or continue statement; also in this case, the frame can actually
|
||||
// give us a higher number of live locals, e.g. if it comes from a catch. Typical example:
|
||||
// for(;;) { try{ throw 0; } catch(e) { break; } }.
|
||||
// Since the for loop can only be exited through the break in the catch block, it'll bring with it the
|
||||
// "e" as a live local, and we need to trim it off here.
|
||||
assert stack.firstTemp >= liveLocals;
|
||||
stack.firstTemp = liveLocals;
|
||||
}
|
||||
debug_label(label);
|
||||
|
||||
mergeStackTo(label); //we have to merge our stack to whatever is in the label
|
||||
|
||||
method.visitLabel(label.getLabel());
|
||||
}
|
||||
|
||||
@ -1777,8 +2007,8 @@ public class MethodEmitter implements Emitter {
|
||||
* @return common type
|
||||
*/
|
||||
private BitwiseType get2i() {
|
||||
final BitwiseType p0 = popInteger();
|
||||
final BitwiseType p1 = popInteger();
|
||||
final BitwiseType p0 = popBitwise();
|
||||
final BitwiseType p1 = popBitwise();
|
||||
assert p0.isEquivalentTo(p1) : "expecting equivalent types on stack but got " + p0 + " and " + p1;
|
||||
return p0;
|
||||
}
|
||||
@ -1844,9 +2074,9 @@ public class MethodEmitter implements Emitter {
|
||||
*
|
||||
* @return the method emitter
|
||||
*/
|
||||
MethodEmitter rem() {
|
||||
MethodEmitter rem(final int programPoint) {
|
||||
debug("rem");
|
||||
pushType(get2n().rem(method));
|
||||
pushType(get2n().rem(method, programPoint));
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -1868,6 +2098,14 @@ public class MethodEmitter implements Emitter {
|
||||
return stack.size();
|
||||
}
|
||||
|
||||
int getFirstTemp() {
|
||||
return stack.firstTemp;
|
||||
}
|
||||
|
||||
int getUsedSlotsWithLiveTemporaries() {
|
||||
return stack.getUsedSlotsWithLiveTemporaries();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to generate a function signature based on stack contents
|
||||
* and argument count and return type
|
||||
@ -2054,7 +2292,6 @@ public class MethodEmitter implements Emitter {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
private static String getProgramPoint(final int flags) {
|
||||
if((flags & CALLSITE_OPTIMISTIC) == 0) {
|
||||
return "";
|
||||
@ -2236,6 +2473,44 @@ public class MethodEmitter implements Emitter {
|
||||
}
|
||||
}
|
||||
|
||||
void beforeJoinPoint(final JoinPredecessor joinPredecessor) {
|
||||
LocalVariableConversion next = joinPredecessor.getLocalVariableConversion();
|
||||
while(next != null) {
|
||||
final Symbol symbol = next.getSymbol();
|
||||
if(next.isLive()) {
|
||||
emitLocalVariableConversion(next, true);
|
||||
} else {
|
||||
markDeadLocalVariable(symbol);
|
||||
}
|
||||
next = next.getNext();
|
||||
}
|
||||
}
|
||||
|
||||
void beforeTry(final TryNode tryNode, final Label recovery) {
|
||||
LocalVariableConversion next = tryNode.getLocalVariableConversion();
|
||||
while(next != null) {
|
||||
if(next.isLive()) {
|
||||
final Type to = emitLocalVariableConversion(next, false);
|
||||
recovery.getStack().onLocalStore(to, next.getSymbol().getSlot(to), true);
|
||||
}
|
||||
next = next.getNext();
|
||||
}
|
||||
}
|
||||
|
||||
private Type emitLocalVariableConversion(final LocalVariableConversion conversion, final boolean onlySymbolLiveValue) {
|
||||
final Type from = conversion.getFrom();
|
||||
final Type to = conversion.getTo();
|
||||
final Symbol symbol = conversion.getSymbol();
|
||||
assert symbol.isBytecodeLocal();
|
||||
if(from == Type.UNDEFINED) {
|
||||
loadUndefined(to);
|
||||
} else {
|
||||
load(symbol, from).convert(to);
|
||||
}
|
||||
store(symbol, to, onlySymbolLiveValue);
|
||||
return to;
|
||||
}
|
||||
|
||||
/*
|
||||
* Debugging below
|
||||
*/
|
||||
@ -2334,7 +2609,7 @@ public class MethodEmitter implements Emitter {
|
||||
pad--;
|
||||
}
|
||||
|
||||
if (stack != null && !stack.isEmpty()) {
|
||||
if (isReachable() && !stack.isEmpty()) {
|
||||
sb.append("{");
|
||||
sb.append(stack.size());
|
||||
sb.append(":");
|
||||
@ -2408,8 +2683,14 @@ public class MethodEmitter implements Emitter {
|
||||
return hasReturn;
|
||||
}
|
||||
|
||||
List<Label> getExternalTargets() {
|
||||
return null;
|
||||
/**
|
||||
* Invoke to enforce assertions preventing load from a local variable slot that's known to not have been written to.
|
||||
* Used by CodeGenerator, as it strictly enforces tracking of stores. Simpler uses of MethodEmitter, e.g. those
|
||||
* for creating initializers for structure classes, array getters, etc. don't have strict tracking of stores,
|
||||
* therefore they would fail if they had this assertion turned on.
|
||||
*/
|
||||
void setPreventUndefinedLoad() {
|
||||
this.preventUndefinedLoad = true;
|
||||
}
|
||||
|
||||
private static boolean isOptimistic(final int flags) {
|
||||
|
||||
@ -38,12 +38,12 @@ import static jdk.nashorn.internal.lookup.Lookup.MH;
|
||||
import static jdk.nashorn.internal.runtime.JSType.CONVERT_OBJECT;
|
||||
import static jdk.nashorn.internal.runtime.JSType.CONVERT_OBJECT_OPTIMISTIC;
|
||||
import static jdk.nashorn.internal.runtime.JSType.GET_UNDEFINED;
|
||||
import static jdk.nashorn.internal.runtime.JSType.getAccessorTypeIndex;
|
||||
import static jdk.nashorn.internal.runtime.JSType.TYPE_UNDEFINED_INDEX;
|
||||
import static jdk.nashorn.internal.runtime.JSType.TYPE_DOUBLE_INDEX;
|
||||
import static jdk.nashorn.internal.runtime.JSType.TYPE_INT_INDEX;
|
||||
import static jdk.nashorn.internal.runtime.JSType.TYPE_LONG_INDEX;
|
||||
import static jdk.nashorn.internal.runtime.JSType.TYPE_DOUBLE_INDEX;
|
||||
import static jdk.nashorn.internal.runtime.JSType.TYPE_OBJECT_INDEX;
|
||||
import static jdk.nashorn.internal.runtime.JSType.TYPE_UNDEFINED_INDEX;
|
||||
import static jdk.nashorn.internal.runtime.JSType.getAccessorTypeIndex;
|
||||
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
@ -58,6 +58,7 @@ import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.runtime.AccessorProperty;
|
||||
import jdk.nashorn.internal.runtime.Context;
|
||||
import jdk.nashorn.internal.runtime.FunctionScope;
|
||||
import jdk.nashorn.internal.runtime.JSType;
|
||||
import jdk.nashorn.internal.runtime.PropertyMap;
|
||||
import jdk.nashorn.internal.runtime.ScriptEnvironment;
|
||||
import jdk.nashorn.internal.runtime.ScriptObject;
|
||||
@ -303,11 +304,11 @@ public final class ObjectClassGenerator implements Loggable {
|
||||
|
||||
addFields(classEmitter, fieldCount);
|
||||
|
||||
final MethodEmitter init = newInitMethod(className, classEmitter);
|
||||
final MethodEmitter init = newInitMethod(classEmitter);
|
||||
init.returnVoid();
|
||||
init.end();
|
||||
|
||||
final MethodEmitter initWithSpillArrays = newInitWithSpillArraysMethod(className, classEmitter, ScriptObject.class);
|
||||
final MethodEmitter initWithSpillArrays = newInitWithSpillArraysMethod(classEmitter, ScriptObject.class);
|
||||
initWithSpillArrays.returnVoid();
|
||||
initWithSpillArrays.end();
|
||||
|
||||
@ -332,17 +333,17 @@ public final class ObjectClassGenerator implements Loggable {
|
||||
final ClassEmitter classEmitter = newClassEmitter(className, superName);
|
||||
final List<String> initFields = addFields(classEmitter, fieldCount);
|
||||
|
||||
final MethodEmitter init = newInitScopeMethod(className, classEmitter);
|
||||
final MethodEmitter init = newInitScopeMethod(classEmitter);
|
||||
initializeToUndefined(init, className, initFields);
|
||||
init.returnVoid();
|
||||
init.end();
|
||||
|
||||
final MethodEmitter initWithSpillArrays = newInitWithSpillArraysMethod(className, classEmitter, FunctionScope.class);
|
||||
final MethodEmitter initWithSpillArrays = newInitWithSpillArraysMethod(classEmitter, FunctionScope.class);
|
||||
initializeToUndefined(initWithSpillArrays, className, initFields);
|
||||
initWithSpillArrays.returnVoid();
|
||||
initWithSpillArrays.end();
|
||||
|
||||
final MethodEmitter initWithArguments = newInitScopeWithArgumentsMethod(className, classEmitter);
|
||||
final MethodEmitter initWithArguments = newInitScopeWithArgumentsMethod(classEmitter);
|
||||
initializeToUndefined(initWithArguments, className, initFields);
|
||||
initWithArguments.returnVoid();
|
||||
initWithArguments.end();
|
||||
@ -396,7 +397,7 @@ public final class ObjectClassGenerator implements Loggable {
|
||||
*
|
||||
* @return Open method emitter.
|
||||
*/
|
||||
private static MethodEmitter newInitMethod(final String className, final ClassEmitter classEmitter) {
|
||||
private static MethodEmitter newInitMethod(final ClassEmitter classEmitter) {
|
||||
final MethodEmitter init = classEmitter.init(PropertyMap.class);
|
||||
init.begin();
|
||||
init.load(Type.OBJECT, JAVA_THIS.slot());
|
||||
@ -406,7 +407,7 @@ public final class ObjectClassGenerator implements Loggable {
|
||||
return init;
|
||||
}
|
||||
|
||||
private static MethodEmitter newInitWithSpillArraysMethod(final String className, final ClassEmitter classEmitter, final Class<?> superClass) {
|
||||
private static MethodEmitter newInitWithSpillArraysMethod(final ClassEmitter classEmitter, final Class<?> superClass) {
|
||||
final MethodEmitter init = classEmitter.init(PropertyMap.class, long[].class, Object[].class);
|
||||
init.begin();
|
||||
init.load(Type.OBJECT, JAVA_THIS.slot());
|
||||
@ -423,7 +424,7 @@ public final class ObjectClassGenerator implements Loggable {
|
||||
* @param classEmitter Open class emitter.
|
||||
* @return Open method emitter.
|
||||
*/
|
||||
private static MethodEmitter newInitScopeMethod(final String className, final ClassEmitter classEmitter) {
|
||||
private static MethodEmitter newInitScopeMethod(final ClassEmitter classEmitter) {
|
||||
final MethodEmitter init = classEmitter.init(PropertyMap.class, ScriptObject.class);
|
||||
init.begin();
|
||||
init.load(Type.OBJECT, JAVA_THIS.slot());
|
||||
@ -439,7 +440,7 @@ public final class ObjectClassGenerator implements Loggable {
|
||||
* @param classEmitter Open class emitter.
|
||||
* @return Open method emitter.
|
||||
*/
|
||||
private static MethodEmitter newInitScopeWithArgumentsMethod(final String className, final ClassEmitter classEmitter) {
|
||||
private static MethodEmitter newInitScopeWithArgumentsMethod(final ClassEmitter classEmitter) {
|
||||
final MethodEmitter init = classEmitter.init(PropertyMap.class, ScriptObject.class, ScriptObject.class);
|
||||
init.begin();
|
||||
init.load(Type.OBJECT, JAVA_THIS.slot());
|
||||
@ -523,8 +524,7 @@ public final class ObjectClassGenerator implements Loggable {
|
||||
final MethodHandle sameTypeGetter = getterForType(forType, primitiveGetter, objectGetter);
|
||||
final MethodHandle mh = MH.asType(sameTypeGetter, sameTypeGetter.type().changeReturnType(Object.class));
|
||||
try {
|
||||
@SuppressWarnings("cast")
|
||||
final Object value = (Object)mh.invokeExact(receiver);
|
||||
final Object value = mh.invokeExact(receiver);
|
||||
throw new UnwarrantedOptimismException(value, programPoint);
|
||||
} catch (final Error | RuntimeException e) {
|
||||
throw e;
|
||||
@ -648,12 +648,14 @@ public final class ObjectClassGenerator implements Loggable {
|
||||
assert primitiveGetter != null;
|
||||
final MethodType tgetterType = tgetter.type();
|
||||
switch (fti) {
|
||||
case TYPE_INT_INDEX:
|
||||
case TYPE_INT_INDEX: {
|
||||
return MH.asType(tgetter, tgetterType.changeReturnType(type));
|
||||
}
|
||||
case TYPE_LONG_INDEX:
|
||||
switch (ti) {
|
||||
case TYPE_INT_INDEX:
|
||||
//get int while an int, truncating cast of long value
|
||||
return MH.explicitCastArguments(tgetter, tgetterType.changeReturnType(int.class));
|
||||
return MH.filterReturnValue(tgetter, JSType.TO_INT32_L.methodHandle);
|
||||
case TYPE_LONG_INDEX:
|
||||
return primitiveGetter;
|
||||
default:
|
||||
@ -662,6 +664,7 @@ public final class ObjectClassGenerator implements Loggable {
|
||||
case TYPE_DOUBLE_INDEX:
|
||||
switch (ti) {
|
||||
case TYPE_INT_INDEX:
|
||||
return MH.filterReturnValue(tgetter, JSType.TO_INT32_D.methodHandle);
|
||||
case TYPE_LONG_INDEX:
|
||||
return MH.explicitCastArguments(tgetter, tgetterType.changeReturnType(type));
|
||||
case TYPE_DOUBLE_INDEX:
|
||||
|
||||
@ -128,11 +128,12 @@ public abstract class ObjectCreator<T> {
|
||||
* Technique for loading an initial value. Defined by anonymous subclasses in code gen.
|
||||
*
|
||||
* @param value Value to load.
|
||||
* @param type the type of the value to load
|
||||
*/
|
||||
protected abstract void loadValue(T value);
|
||||
protected abstract void loadValue(T value, Type type);
|
||||
|
||||
MethodEmitter loadTuple(final MethodEmitter method, final MapTuple<T> tuple, final boolean pack) {
|
||||
loadValue(tuple.value);
|
||||
loadValue(tuple.value, tuple.type);
|
||||
if (pack && tuple.isPrimitive()) {
|
||||
method.pack();
|
||||
} else {
|
||||
|
||||
@ -0,0 +1,284 @@
|
||||
/*
|
||||
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.nashorn.internal.codegen;
|
||||
|
||||
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.BitSet;
|
||||
import java.util.Deque;
|
||||
import jdk.nashorn.internal.IntDeque;
|
||||
import jdk.nashorn.internal.ir.AccessNode;
|
||||
import jdk.nashorn.internal.ir.BinaryNode;
|
||||
import jdk.nashorn.internal.ir.CallNode;
|
||||
import jdk.nashorn.internal.ir.CatchNode;
|
||||
import jdk.nashorn.internal.ir.Expression;
|
||||
import jdk.nashorn.internal.ir.ExpressionStatement;
|
||||
import jdk.nashorn.internal.ir.ForNode;
|
||||
import jdk.nashorn.internal.ir.FunctionNode;
|
||||
import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
|
||||
import jdk.nashorn.internal.ir.IdentNode;
|
||||
import jdk.nashorn.internal.ir.IfNode;
|
||||
import jdk.nashorn.internal.ir.IndexNode;
|
||||
import jdk.nashorn.internal.ir.JoinPredecessorExpression;
|
||||
import jdk.nashorn.internal.ir.LexicalContext;
|
||||
import jdk.nashorn.internal.ir.LoopNode;
|
||||
import jdk.nashorn.internal.ir.Node;
|
||||
import jdk.nashorn.internal.ir.Optimistic;
|
||||
import jdk.nashorn.internal.ir.PropertyNode;
|
||||
import jdk.nashorn.internal.ir.SplitNode;
|
||||
import jdk.nashorn.internal.ir.Symbol;
|
||||
import jdk.nashorn.internal.ir.TernaryNode;
|
||||
import jdk.nashorn.internal.ir.UnaryNode;
|
||||
import jdk.nashorn.internal.ir.VarNode;
|
||||
import jdk.nashorn.internal.ir.WhileNode;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
import jdk.nashorn.internal.parser.TokenType;
|
||||
import jdk.nashorn.internal.runtime.ScriptObject;
|
||||
|
||||
/**
|
||||
* Assigns optimistic types to expressions that can have them. This class mainly contains logic for which expressions
|
||||
* must not ever be marked as optimistic, assigning narrowest non-invalidated types to program points from the
|
||||
* compilation environment, as well as initializing optimistic types of global properties for scripts.
|
||||
*/
|
||||
final class OptimisticTypesCalculator extends NodeVisitor<LexicalContext> {
|
||||
|
||||
final CompilationEnvironment env;
|
||||
|
||||
// Per-function bit set of program points that must never be optimistic.
|
||||
final Deque<BitSet> neverOptimistic = new ArrayDeque<>();
|
||||
// Per-function depth of split nodes
|
||||
final IntDeque splitDepth = new IntDeque();
|
||||
|
||||
OptimisticTypesCalculator(final CompilationEnvironment env) {
|
||||
super(new LexicalContext());
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterAccessNode(final AccessNode accessNode) {
|
||||
tagNeverOptimistic(accessNode.getBase());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterPropertyNode(PropertyNode propertyNode) {
|
||||
if(propertyNode.getKeyName().equals(ScriptObject.PROTO_PROPERTY_NAME)) {
|
||||
tagNeverOptimistic(propertyNode.getValue());
|
||||
}
|
||||
return super.enterPropertyNode(propertyNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterBinaryNode(final BinaryNode binaryNode) {
|
||||
if(binaryNode.isAssignment()) {
|
||||
final Expression lhs = binaryNode.lhs();
|
||||
if(!binaryNode.isSelfModifying()) {
|
||||
tagNeverOptimistic(lhs);
|
||||
}
|
||||
if(lhs instanceof IdentNode) {
|
||||
final Symbol symbol = ((IdentNode)lhs).getSymbol();
|
||||
// Assignment to internal symbols is never optimistic, except for self-assignment expressions
|
||||
if(symbol.isInternal() && !binaryNode.rhs().isSelfModifying()) {
|
||||
tagNeverOptimistic(binaryNode.rhs());
|
||||
}
|
||||
}
|
||||
} else if(binaryNode.isTokenType(TokenType.INSTANCEOF)) {
|
||||
tagNeverOptimistic(binaryNode.lhs());
|
||||
tagNeverOptimistic(binaryNode.rhs());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterCallNode(final CallNode callNode) {
|
||||
tagNeverOptimistic(callNode.getFunction());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterCatchNode(final CatchNode catchNode) {
|
||||
// Condition is never optimistic (always coerced to boolean).
|
||||
tagNeverOptimistic(catchNode.getExceptionCondition());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterExpressionStatement(final ExpressionStatement expressionStatement) {
|
||||
final Expression expr = expressionStatement.getExpression();
|
||||
if(!expr.isSelfModifying()) {
|
||||
tagNeverOptimistic(expr);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterForNode(final ForNode forNode) {
|
||||
if(forNode.isForIn()) {
|
||||
// for..in has the iterable in its "modify"
|
||||
tagNeverOptimistic(forNode.getModify());
|
||||
} else {
|
||||
// Test is never optimistic (always coerced to boolean).
|
||||
tagNeverOptimisticLoopTest(forNode);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterFunctionNode(final FunctionNode functionNode) {
|
||||
if (!neverOptimistic.isEmpty() && env.isOnDemandCompilation()) {
|
||||
// This is a nested function, and we're doing on-demand compilation. In these compilations, we never descend
|
||||
// into nested functions.
|
||||
return false;
|
||||
}
|
||||
neverOptimistic.push(new BitSet());
|
||||
splitDepth.push(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterIfNode(final IfNode ifNode) {
|
||||
// Test is never optimistic (always coerced to boolean).
|
||||
tagNeverOptimistic(ifNode.getTest());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterIndexNode(final IndexNode indexNode) {
|
||||
tagNeverOptimistic(indexNode.getBase());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterTernaryNode(final TernaryNode ternaryNode) {
|
||||
// Test is never optimistic (always coerced to boolean).
|
||||
tagNeverOptimistic(ternaryNode.getTest());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterUnaryNode(final UnaryNode unaryNode) {
|
||||
if(unaryNode.isTokenType(TokenType.NOT) || unaryNode.isTokenType(TokenType.NEW)) {
|
||||
// Operand of boolean negation is never optimistic (always coerced to boolean).
|
||||
// Operand of "new" is never optimistic (always coerced to Object).
|
||||
tagNeverOptimistic(unaryNode.getExpression());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterSplitNode(SplitNode splitNode) {
|
||||
splitDepth.getAndIncrement();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveSplitNode(SplitNode splitNode) {
|
||||
final int depth = splitDepth.decrementAndGet();
|
||||
assert depth >= 0;
|
||||
return splitNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterVarNode(final VarNode varNode) {
|
||||
tagNeverOptimistic(varNode.getName());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterWhileNode(final WhileNode whileNode) {
|
||||
// Test is never optimistic (always coerced to boolean).
|
||||
tagNeverOptimisticLoopTest(whileNode);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node leaveDefault(final Node node) {
|
||||
if(node instanceof Optimistic) {
|
||||
return leaveOptimistic((Optimistic)node);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveFunctionNode(final FunctionNode functionNode) {
|
||||
neverOptimistic.pop();
|
||||
final int lastSplitDepth = splitDepth.pop();
|
||||
assert lastSplitDepth == 0;
|
||||
return functionNode.setState(lc, CompilationState.OPTIMISTIC_TYPES_ASSIGNED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveIdentNode(final IdentNode identNode) {
|
||||
if(inSplitNode()) {
|
||||
return identNode;
|
||||
}
|
||||
final Symbol symbol = identNode.getSymbol();
|
||||
if(symbol == null) {
|
||||
assert identNode.isPropertyName();
|
||||
return identNode;
|
||||
} else if(symbol.isBytecodeLocal()) {
|
||||
// Identifiers accessing bytecode local variables will never be optimistic, as type calculation phase over
|
||||
// them will always assign them statically provable types. Note that access to function parameters can still
|
||||
// be optimistic if the parameter needs to be in scope as it's used by a nested function.
|
||||
return identNode;
|
||||
} else if(symbol.isParam() && lc.getCurrentFunction().isVarArg()) {
|
||||
// Parameters in vararg methods are not optimistic; we always access them using Object getters.
|
||||
return identNode.setType(identNode.getMostPessimisticType());
|
||||
} else {
|
||||
assert symbol.isScope();
|
||||
return leaveOptimistic(identNode);
|
||||
}
|
||||
}
|
||||
|
||||
private Expression leaveOptimistic(final Optimistic opt) {
|
||||
final int pp = opt.getProgramPoint();
|
||||
if(isValid(pp) && !inSplitNode() && !neverOptimistic.peek().get(pp)) {
|
||||
return (Expression)opt.setType(env.getOptimisticType(opt));
|
||||
}
|
||||
return (Expression)opt;
|
||||
}
|
||||
|
||||
private void tagNeverOptimistic(final Expression expr) {
|
||||
if(expr instanceof Optimistic) {
|
||||
final int pp = ((Optimistic)expr).getProgramPoint();
|
||||
if(isValid(pp)) {
|
||||
neverOptimistic.peek().set(pp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void tagNeverOptimisticLoopTest(final LoopNode loopNode) {
|
||||
final JoinPredecessorExpression test = loopNode.getTest();
|
||||
if(test != null) {
|
||||
tagNeverOptimistic(test.getExpression());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean inSplitNode() {
|
||||
return splitDepth.peek() > 0;
|
||||
}
|
||||
}
|
||||
@ -27,11 +27,9 @@ package jdk.nashorn.internal.codegen;
|
||||
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.FIRST_PROGRAM_POINT;
|
||||
import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.MAX_PROGRAM_POINT_VALUE;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import jdk.nashorn.internal.IntDeque;
|
||||
import jdk.nashorn.internal.ir.AccessNode;
|
||||
import jdk.nashorn.internal.ir.BinaryNode;
|
||||
import jdk.nashorn.internal.ir.CallNode;
|
||||
@ -51,7 +49,7 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
*/
|
||||
class ProgramPoints extends NodeVisitor<LexicalContext> {
|
||||
|
||||
private final Deque<int[]> nextProgramPoint = new ArrayDeque<>();
|
||||
private final IntDeque nextProgramPoint = new IntDeque();
|
||||
private final Set<Node> noProgramPoint = new HashSet<>();
|
||||
|
||||
ProgramPoints() {
|
||||
@ -59,7 +57,7 @@ class ProgramPoints extends NodeVisitor<LexicalContext> {
|
||||
}
|
||||
|
||||
private int next() {
|
||||
final int next = nextProgramPoint.peek()[0]++;
|
||||
final int next = nextProgramPoint.getAndIncrement();
|
||||
if(next > MAX_PROGRAM_POINT_VALUE) {
|
||||
throw new AssertionError("Function has more than " + MAX_PROGRAM_POINT_VALUE + " program points");
|
||||
}
|
||||
@ -68,7 +66,7 @@ class ProgramPoints extends NodeVisitor<LexicalContext> {
|
||||
|
||||
@Override
|
||||
public boolean enterFunctionNode(final FunctionNode functionNode) {
|
||||
nextProgramPoint.push(new int[] { FIRST_PROGRAM_POINT });
|
||||
nextProgramPoint.push(FIRST_PROGRAM_POINT);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -78,11 +76,11 @@ class ProgramPoints extends NodeVisitor<LexicalContext> {
|
||||
return functionNode;
|
||||
}
|
||||
|
||||
private Optimistic setProgramPoint(final Optimistic optimistic, final int programPoint) {
|
||||
private Expression setProgramPoint(final Optimistic optimistic) {
|
||||
if (noProgramPoint.contains(optimistic)) {
|
||||
return optimistic;
|
||||
return (Expression)optimistic;
|
||||
}
|
||||
return (Optimistic)(Expression)optimistic.setProgramPoint(programPoint);
|
||||
return (Expression)(optimistic.canBeOptimistic() ? optimistic.setProgramPoint(next()) : optimistic);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -101,31 +99,34 @@ class ProgramPoints extends NodeVisitor<LexicalContext> {
|
||||
|
||||
@Override
|
||||
public Node leaveIdentNode(final IdentNode identNode) {
|
||||
return (Node)setProgramPoint(identNode, next());
|
||||
if(identNode.isPropertyName()) {
|
||||
return identNode;
|
||||
}
|
||||
return setProgramPoint(identNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveCallNode(final CallNode callNode) {
|
||||
return (Node)setProgramPoint(callNode, next());
|
||||
return setProgramPoint(callNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveAccessNode(final AccessNode accessNode) {
|
||||
return (Node)setProgramPoint(accessNode, next());
|
||||
return setProgramPoint(accessNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveIndexNode(final IndexNode indexNode) {
|
||||
return (Node)setProgramPoint(indexNode, next());
|
||||
return setProgramPoint(indexNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveBinaryNode(final BinaryNode binaryNode) {
|
||||
return (Node)setProgramPoint(binaryNode, next());
|
||||
return setProgramPoint(binaryNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveUnaryNode(final UnaryNode unaryNode) {
|
||||
return (Node)setProgramPoint(unaryNode, next());
|
||||
return setProgramPoint(unaryNode);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,491 +0,0 @@
|
||||
/*
|
||||
* 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.Expression;
|
||||
import jdk.nashorn.internal.ir.ForNode;
|
||||
import jdk.nashorn.internal.ir.IdentNode;
|
||||
import jdk.nashorn.internal.ir.LexicalContext;
|
||||
import jdk.nashorn.internal.ir.LiteralNode;
|
||||
import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
|
||||
import jdk.nashorn.internal.ir.LoopNode;
|
||||
import jdk.nashorn.internal.ir.Node;
|
||||
import jdk.nashorn.internal.ir.RuntimeNode;
|
||||
import jdk.nashorn.internal.ir.Symbol;
|
||||
import jdk.nashorn.internal.ir.UnaryNode;
|
||||
import jdk.nashorn.internal.ir.VarNode;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
import jdk.nashorn.internal.parser.TokenType;
|
||||
import jdk.nashorn.internal.runtime.Context;
|
||||
import jdk.nashorn.internal.runtime.logging.DebugLogger;
|
||||
import jdk.nashorn.internal.runtime.logging.Loggable;
|
||||
import jdk.nashorn.internal.runtime.logging.Logger;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@Logger(name="ranges")
|
||||
final class RangeAnalyzer extends NodeOperatorVisitor<LexicalContext> implements Loggable {
|
||||
private final DebugLogger log;
|
||||
|
||||
private final Range.Functionality func;
|
||||
|
||||
private final Map<LoopNode, Symbol> loopCounters = new HashMap<>();
|
||||
|
||||
RangeAnalyzer(final CompilationEnvironment env) {
|
||||
super(new LexicalContext());
|
||||
this.log = initLogger(env.getContext());
|
||||
this.func = new Range.Functionality(log);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebugLogger getLogger() {
|
||||
return log;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebugLogger initLogger(final Context context) {
|
||||
return context.getLogger(this.getClass());
|
||||
}
|
||||
|
||||
@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 Expression 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 = func.join(symbol.getRange(), range);
|
||||
|
||||
//anything assigned in the loop, not being the safe loop counter(s) invalidates its entire range
|
||||
if (lc.inLoop() && !isLoopCounter(lc.getCurrentLoop(), symbol)) {
|
||||
symbol.setRange(Range.createGenericRange());
|
||||
return symbol;
|
||||
}
|
||||
|
||||
if (!symRange.equals(symbol.getRange())) {
|
||||
log.fine("Modify range for " + dest + " " + symbol + " from " + symbol.getRange() + " to " + symRange + " (in node = " + dest + ")" );
|
||||
symbol.setRange(symRange);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveADD(final BinaryNode node) {
|
||||
setRange(node, func.add(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveSUB(final BinaryNode node) {
|
||||
setRange(node, func.sub(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveMUL(final BinaryNode node) {
|
||||
setRange(node, func.mul(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveDIV(final BinaryNode node) {
|
||||
setRange(node, func.div(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveMOD(final BinaryNode node) {
|
||||
setRange(node, func.mod(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveBIT_AND(final BinaryNode node) {
|
||||
setRange(node, func.and(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveBIT_OR(final BinaryNode node) {
|
||||
setRange(node, func.or(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveBIT_XOR(final BinaryNode node) {
|
||||
setRange(node, func.xor(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveSAR(final BinaryNode node) {
|
||||
setRange(node, func.sar(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveSHL(final BinaryNode node) {
|
||||
setRange(node, func.shl(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveSHR(final BinaryNode node) {
|
||||
setRange(node, func.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.getExpression(), range);
|
||||
setRange(node, range);
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveASSIGN_ADD(final BinaryNode node) {
|
||||
return leaveSelfModifyingAssign(node, func.add(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveASSIGN_SUB(final BinaryNode node) {
|
||||
return leaveSelfModifyingAssign(node, func.sub(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveASSIGN_MUL(final BinaryNode node) {
|
||||
return leaveSelfModifyingAssign(node, func.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, func.and(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveASSIGN_BIT_OR(final BinaryNode node) {
|
||||
return leaveSelfModifyingAssign(node, func.or(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveASSIGN_BIT_XOR(final BinaryNode node) {
|
||||
return leaveSelfModifyingAssign(node, func.xor(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveASSIGN_SAR(final BinaryNode node) {
|
||||
return leaveSelfModifyingAssign(node, func.sar(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveASSIGN_SHR(final BinaryNode node) {
|
||||
return leaveSelfModifyingAssign(node, func.shr(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveASSIGN_SHL(final BinaryNode node) {
|
||||
return leaveSelfModifyingAssign(node, func.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, func.sub(node.getExpression().getSymbol().getRange(), Range.createRange(1)));
|
||||
case INCPREFIX:
|
||||
case INCPOSTFIX:
|
||||
return leaveSelfModifyingAssign(node, func.add(node.getExpression().getSymbol().getRange(), Range.createRange(1)));
|
||||
default:
|
||||
throw new UnsupportedOperationException("" + node.tokenType());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveADD(final UnaryNode node) {
|
||||
Range range = node.getExpression().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, func.neg(node.getExpression().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);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public boolean enterLiteralNode(final LiteralNode node) {
|
||||
// ignore array literals
|
||||
return !(node instanceof ArrayLiteralNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveLiteralNode(@SuppressWarnings("rawtypes") final LiteralNode node) {
|
||||
if (node.getType().isInteger()) {
|
||||
setRange(node, Range.createRange(node.getInt32()));
|
||||
} else if (node.getType().isNumber()) {
|
||||
setRange(node, Range.createRange(node.getNumber()));
|
||||
} else if (node.getType().isLong()) {
|
||||
setRange(node, Range.createRange(node.getLong()));
|
||||
} else if (node.getType().isBoolean()) {
|
||||
setRange(node, Range.createTypeRange(Type.BOOLEAN));
|
||||
} else {
|
||||
setRange(node, Range.createGenericRange());
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterRuntimeNode(final RuntimeNode node) {
|
||||
// a runtime node that cannot be specialized is no point entering
|
||||
return node.getRequest().canSpecialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a symbol is unsafely assigned in a loop - i.e. repeteadly assigned and
|
||||
* not being identified as the loop counter. That means we don't really know anything
|
||||
* about its range.
|
||||
* @param loopNode loop node
|
||||
* @param symbol symbol
|
||||
* @return true if assigned in loop
|
||||
*/
|
||||
// TODO - this currently checks for nodes only - needs to be augmented for while nodes
|
||||
// assignment analysis is also very conservative
|
||||
private static boolean assignedInLoop(final LoopNode loopNode, final Symbol symbol) {
|
||||
final HashSet<Node> skip = new HashSet<>();
|
||||
final HashSet<Node> assignmentsInLoop = new HashSet<>();
|
||||
|
||||
loopNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
|
||||
private boolean assigns(final Node node, final Symbol s) {
|
||||
return node.isAssignment() && ((Assignment<?>)node).getAssignmentDest().getSymbol() == s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterForNode(final ForNode forNode) {
|
||||
if (forNode.getInit() != null) {
|
||||
skip.add(forNode.getInit());
|
||||
}
|
||||
if (forNode.getModify() != null) {
|
||||
skip.add(forNode.getModify());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveDefault(final Node node) {
|
||||
//if this is an assignment to symbol
|
||||
if (!skip.contains(node) && assigns(node, symbol)) {
|
||||
assignmentsInLoop.add(node);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
});
|
||||
|
||||
return !assignmentsInLoop.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for a loop counter. This is currently quite conservative, in that it only handles
|
||||
* x <= counter and x < counter.
|
||||
*
|
||||
* @param node loop node to check
|
||||
* @return
|
||||
*/
|
||||
private Symbol findLoopCounter(final LoopNode node) {
|
||||
final Expression test = node.getTest();
|
||||
|
||||
if (test != null && test.isComparison()) {
|
||||
final BinaryNode binaryNode = (BinaryNode)test;
|
||||
final Expression lhs = binaryNode.lhs();
|
||||
final Expression 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(func.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;
|
||||
}
|
||||
}
|
||||
@ -32,7 +32,7 @@ import static jdk.nashorn.internal.codegen.ObjectClassGenerator.OBJECT_FIELDS_ON
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.Expression;
|
||||
import jdk.nashorn.internal.ir.LiteralNode;
|
||||
import jdk.nashorn.internal.runtime.JSType;
|
||||
@ -193,7 +193,7 @@ public final class SpillObjectCreator extends ObjectCreator<Expression> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadValue(final Expression node) {
|
||||
codegen.load(node);
|
||||
protected void loadValue(final Expression expr, final Type type) {
|
||||
codegen.loadExpressionAsType(expr, type);
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,13 +25,13 @@
|
||||
|
||||
package jdk.nashorn.internal.codegen;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import jdk.internal.org.objectweb.asm.MethodVisitor;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.BreakableNode;
|
||||
import jdk.nashorn.internal.ir.LexicalContext;
|
||||
import jdk.nashorn.internal.ir.SplitNode;
|
||||
import jdk.nashorn.internal.runtime.Scope;
|
||||
@ -47,6 +47,13 @@ public class SplitMethodEmitter extends MethodEmitter {
|
||||
private final SplitNode splitNode;
|
||||
|
||||
private final List<Label> externalTargets = new ArrayList<>();
|
||||
/**
|
||||
* In addition to external target labels, we need to track the target breakables too as the code generator needs to
|
||||
* be able to correctly pop the scopes to the target, see {@link CodeGenerator#leaveSplitNode(SplitNode)}. Note that
|
||||
* this is only used within CodeGenerator, which doesn't mutate the AST, so keeping pointers to other nodes is not
|
||||
* incorrect.
|
||||
*/
|
||||
private final List<BreakableNode> externalTargetNodes = new ArrayList<>();
|
||||
|
||||
SplitMethodEmitter(final ClassEmitter classEmitter, final MethodVisitor mv, SplitNode splitNode) {
|
||||
super(classEmitter, mv);
|
||||
@ -54,9 +61,9 @@ public class SplitMethodEmitter extends MethodEmitter {
|
||||
}
|
||||
|
||||
@Override
|
||||
void splitAwareGoto(final LexicalContext lc, final Label label) {
|
||||
void splitAwareGoto(final LexicalContext lc, final Label label, final BreakableNode targetNode) {
|
||||
assert splitNode != null;
|
||||
final int index = findExternalTarget(lc, label);
|
||||
final int index = findExternalTarget(lc, label, targetNode);
|
||||
if (index >= 0) {
|
||||
loadCompilerConstant(SCOPE);
|
||||
checkcast(Scope.class);
|
||||
@ -66,18 +73,19 @@ public class SplitMethodEmitter extends MethodEmitter {
|
||||
_return(functionNode.getReturnType());
|
||||
return;
|
||||
}
|
||||
super.splitAwareGoto(lc, label);
|
||||
super.splitAwareGoto(lc, label, targetNode);
|
||||
}
|
||||
|
||||
private int findExternalTarget(final LexicalContext lc, final Label label) {
|
||||
private int findExternalTarget(final LexicalContext lc, final Label label, final BreakableNode targetNode) {
|
||||
final int index = externalTargets.indexOf(label);
|
||||
|
||||
if (index >= 0) {
|
||||
return index;
|
||||
}
|
||||
|
||||
if (lc.isExternalTarget(splitNode, label)) {
|
||||
if (lc.isExternalTarget(splitNode, targetNode)) {
|
||||
externalTargets.add(label);
|
||||
externalTargetNodes.add(targetNode);
|
||||
return externalTargets.size() - 1;
|
||||
}
|
||||
return -1;
|
||||
@ -93,8 +101,11 @@ public class SplitMethodEmitter extends MethodEmitter {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
final List<Label> getExternalTargets() {
|
||||
return externalTargets;
|
||||
}
|
||||
|
||||
final List<BreakableNode> getExternalTargetNodes() {
|
||||
return externalTargetNodes;
|
||||
}
|
||||
}
|
||||
|
||||
@ -306,11 +306,6 @@ final class WeighNodes extends NodeOperatorVisitor<LexicalContext> {
|
||||
return runtimeNodeWeight(unaryNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveDISCARD(final UnaryNode unaryNode) {
|
||||
return unaryNodeWeight(unaryNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveNEW(final UnaryNode unaryNode) {
|
||||
weight += NEW_WEIGHT;
|
||||
|
||||
@ -50,6 +50,9 @@
|
||||
|
||||
package jdk.nashorn.internal.codegen.types;
|
||||
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.I2D;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.I2L;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IADD;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.ICONST_0;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.ICONST_1;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.ILOAD;
|
||||
@ -57,9 +60,10 @@ import static jdk.internal.org.objectweb.asm.Opcodes.IRETURN;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.ISTORE;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup;
|
||||
import static jdk.nashorn.internal.runtime.JSType.UNDEFINED_INT;
|
||||
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
|
||||
|
||||
import jdk.internal.org.objectweb.asm.MethodVisitor;
|
||||
import jdk.nashorn.internal.codegen.CompilerConstants;
|
||||
import jdk.nashorn.internal.runtime.JSType;
|
||||
|
||||
/**
|
||||
* The boolean type class
|
||||
@ -67,6 +71,7 @@ import jdk.nashorn.internal.runtime.JSType;
|
||||
public final class BooleanType extends Type {
|
||||
|
||||
private static final CompilerConstants.Call VALUE_OF = staticCallNoLookup(Boolean.class, "valueOf", Boolean.class, boolean.class);
|
||||
private static final CompilerConstants.Call TO_STRING = staticCallNoLookup(Boolean.class, "toString", String.class, boolean.class);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@ -134,19 +139,13 @@ public final class BooleanType extends Type {
|
||||
}
|
||||
|
||||
if (to.isNumber()) {
|
||||
convert(method, OBJECT);
|
||||
invokestatic(method, JSType.TO_NUMBER);
|
||||
method.visitInsn(I2D);
|
||||
} else if (to.isLong()) {
|
||||
method.visitInsn(I2L);
|
||||
} else if (to.isInteger()) {
|
||||
return to; // do nothing.
|
||||
} else if (to.isLong()) {
|
||||
convert(method, OBJECT);
|
||||
invokestatic(method, JSType.TO_UINT32);
|
||||
} else if (to.isLong()) {
|
||||
convert(method, OBJECT);
|
||||
invokestatic(method, JSType.TO_LONG);
|
||||
//nop
|
||||
} else if (to.isString()) {
|
||||
invokestatic(method, VALUE_OF);
|
||||
invokestatic(method, JSType.TO_PRIMITIVE_TO_STRING);
|
||||
invokestatic(method, TO_STRING);
|
||||
} else if (to.isObject()) {
|
||||
invokestatic(method, VALUE_OF);
|
||||
} else {
|
||||
@ -158,6 +157,12 @@ public final class BooleanType extends Type {
|
||||
|
||||
@Override
|
||||
public Type add(final MethodVisitor method, final int programPoint) {
|
||||
throw new UnsupportedOperationException("add");
|
||||
// Adding booleans in JavaScript is perfectly valid, they add as if false=0 and true=1
|
||||
if(programPoint == INVALID_PROGRAM_POINT) {
|
||||
method.visitInsn(IADD);
|
||||
} else {
|
||||
method.visitInvokeDynamicInsn("iadd", "(II)I", MATHBOOTSTRAP, programPoint);
|
||||
}
|
||||
return INT;
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,7 +84,7 @@ interface BytecodeNumericOps {
|
||||
* @param programPoint program point id
|
||||
* @return result type
|
||||
*/
|
||||
Type rem(MethodVisitor method);
|
||||
Type rem(MethodVisitor method, int programPoint);
|
||||
|
||||
/**
|
||||
* Comparison with int return value, e.g. LCMP, DCMP.
|
||||
|
||||
@ -28,6 +28,7 @@ package jdk.nashorn.internal.codegen.types;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.BIPUSH;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.I2D;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.I2L;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IADD;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IAND;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.ICONST_0;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.ICONST_1;
|
||||
@ -37,17 +38,21 @@ import static jdk.internal.org.objectweb.asm.Opcodes.ICONST_4;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.ICONST_5;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.ICONST_M1;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.ILOAD;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IMUL;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.INEG;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IOR;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IREM;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IRETURN;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.ISHL;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.ISHR;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.ISTORE;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.ISUB;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IUSHR;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IXOR;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.SIPUSH;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup;
|
||||
import static jdk.nashorn.internal.runtime.JSType.UNDEFINED_INT;
|
||||
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
|
||||
|
||||
import jdk.internal.org.objectweb.asm.MethodVisitor;
|
||||
import jdk.nashorn.internal.codegen.CompilerConstants;
|
||||
|
||||
@ -145,7 +150,11 @@ class IntType extends BitwiseType {
|
||||
|
||||
@Override
|
||||
public Type add(final MethodVisitor method, final int programPoint) {
|
||||
method.visitInvokeDynamicInsn("iadd", "(II)I", MATHBOOTSTRAP, programPoint);
|
||||
if(programPoint == INVALID_PROGRAM_POINT) {
|
||||
method.visitInsn(IADD);
|
||||
} else {
|
||||
method.visitInvokeDynamicInsn("iadd", "(II)I", MATHBOOTSTRAP, programPoint);
|
||||
}
|
||||
return INT;
|
||||
}
|
||||
|
||||
@ -200,31 +209,49 @@ class IntType extends BitwiseType {
|
||||
|
||||
@Override
|
||||
public Type sub(final MethodVisitor method, final int programPoint) {
|
||||
method.visitInvokeDynamicInsn("isub", "(II)I", MATHBOOTSTRAP, programPoint);
|
||||
if(programPoint == INVALID_PROGRAM_POINT) {
|
||||
method.visitInsn(ISUB);
|
||||
} else {
|
||||
method.visitInvokeDynamicInsn("isub", "(II)I", MATHBOOTSTRAP, programPoint);
|
||||
}
|
||||
return INT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type mul(final MethodVisitor method, final int programPoint) {
|
||||
method.visitInvokeDynamicInsn("imul", "(II)I", MATHBOOTSTRAP, programPoint);
|
||||
if(programPoint == INVALID_PROGRAM_POINT) {
|
||||
method.visitInsn(IMUL);
|
||||
} else {
|
||||
method.visitInvokeDynamicInsn("imul", "(II)I", MATHBOOTSTRAP, programPoint);
|
||||
}
|
||||
return INT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type div(final MethodVisitor method, final int programPoint) {
|
||||
// Never perform non-optimistic integer division in JavaScript.
|
||||
assert programPoint != INVALID_PROGRAM_POINT;
|
||||
|
||||
method.visitInvokeDynamicInsn("idiv", "(II)I", MATHBOOTSTRAP, programPoint);
|
||||
return INT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type rem(final MethodVisitor method) {
|
||||
method.visitInsn(IREM);
|
||||
public Type rem(final MethodVisitor method, final int programPoint) {
|
||||
// Never perform non-optimistic integer remainder in JavaScript.
|
||||
assert programPoint != INVALID_PROGRAM_POINT;
|
||||
|
||||
method.visitInvokeDynamicInsn("irem", "(II)I", MATHBOOTSTRAP, programPoint);
|
||||
return INT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type neg(final MethodVisitor method, final int programPoint) {
|
||||
method.visitInvokeDynamicInsn("ineg", "(I)I", MATHBOOTSTRAP, programPoint);
|
||||
if(programPoint == INVALID_PROGRAM_POINT) {
|
||||
method.visitInsn(INEG);
|
||||
} else {
|
||||
method.visitInvokeDynamicInsn("ineg", "(I)I", MATHBOOTSTRAP, programPoint);
|
||||
}
|
||||
return INT;
|
||||
}
|
||||
|
||||
|
||||
@ -27,21 +27,24 @@ package jdk.nashorn.internal.codegen.types;
|
||||
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.L2D;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.L2I;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LADD;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LAND;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LCMP;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LCONST_0;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LCONST_1;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LLOAD;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LMUL;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LOR;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LREM;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LRETURN;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LSHL;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LSHR;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LSTORE;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LSUB;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LUSHR;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LXOR;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup;
|
||||
import static jdk.nashorn.internal.runtime.JSType.UNDEFINED_LONG;
|
||||
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
|
||||
|
||||
import jdk.internal.org.objectweb.asm.MethodVisitor;
|
||||
import jdk.nashorn.internal.codegen.CompilerConstants;
|
||||
@ -136,31 +139,49 @@ class LongType extends BitwiseType {
|
||||
|
||||
@Override
|
||||
public Type add(final MethodVisitor method, final int programPoint) {
|
||||
method.visitInvokeDynamicInsn("ladd", "(JJ)J", MATHBOOTSTRAP, programPoint);
|
||||
if(programPoint == INVALID_PROGRAM_POINT) {
|
||||
method.visitInsn(LADD);
|
||||
} else {
|
||||
method.visitInvokeDynamicInsn("ladd", "(JJ)J", MATHBOOTSTRAP, programPoint);
|
||||
}
|
||||
return LONG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type sub(final MethodVisitor method, final int programPoint) {
|
||||
method.visitInvokeDynamicInsn("lsub", "(JJ)J", MATHBOOTSTRAP, programPoint);
|
||||
if(programPoint == INVALID_PROGRAM_POINT) {
|
||||
method.visitInsn(LSUB);
|
||||
} else {
|
||||
method.visitInvokeDynamicInsn("lsub", "(JJ)J", MATHBOOTSTRAP, programPoint);
|
||||
}
|
||||
return LONG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type mul(final MethodVisitor method, final int programPoint) {
|
||||
method.visitInvokeDynamicInsn("lmul", "(JJ)J", MATHBOOTSTRAP, programPoint);
|
||||
if(programPoint == INVALID_PROGRAM_POINT) {
|
||||
method.visitInsn(LMUL);
|
||||
} else {
|
||||
method.visitInvokeDynamicInsn("lmul", "(JJ)J", MATHBOOTSTRAP, programPoint);
|
||||
}
|
||||
return LONG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type div(final MethodVisitor method, final int programPoint) {
|
||||
// Never perform non-optimistic integer division in JavaScript.
|
||||
assert programPoint != INVALID_PROGRAM_POINT;
|
||||
|
||||
method.visitInvokeDynamicInsn("ldiv", "(JJ)J", MATHBOOTSTRAP, programPoint);
|
||||
return LONG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type rem(final MethodVisitor method) {
|
||||
method.visitInsn(LREM);
|
||||
public Type rem(final MethodVisitor method, final int programPoint) {
|
||||
// Never perform non-optimistic integer remainder in JavaScript.
|
||||
assert programPoint != INVALID_PROGRAM_POINT;
|
||||
|
||||
method.visitInvokeDynamicInsn("lrem", "(JJ)J", MATHBOOTSTRAP, programPoint);
|
||||
return LONG;
|
||||
}
|
||||
|
||||
|
||||
@ -164,7 +164,7 @@ class NumberType extends NumericType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type rem(final MethodVisitor method) {
|
||||
public Type rem(final MethodVisitor method, final int programPoint) {
|
||||
method.visitInsn(DREM);
|
||||
return NUMBER;
|
||||
}
|
||||
|
||||
@ -1,705 +0,0 @@
|
||||
/*
|
||||
* 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.logging.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) {
|
||||
final long min = left.getMin() >>> shift;
|
||||
final 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);
|
||||
}
|
||||
}
|
||||
@ -54,7 +54,6 @@ import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import jdk.internal.org.objectweb.asm.Handle;
|
||||
import jdk.internal.org.objectweb.asm.MethodVisitor;
|
||||
import jdk.nashorn.internal.codegen.CompilerConstants.Call;
|
||||
@ -497,6 +496,37 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
|
||||
return type0.weight() > type1.weight() ? type0 : type1;
|
||||
}
|
||||
|
||||
/**
|
||||
* When doing widening for return types of a function or a ternary operator, it is not valid to widen a boolean to
|
||||
* anything other than object. Note that this wouldn't be necessary if {@code Type.widest} did not allow
|
||||
* boolean-to-number widening. Eventually, we should address it there, but it affects too many other parts of the
|
||||
* system and is sometimes legitimate (e.g. whenever a boolean value would undergo ToNumber conversion anyway).
|
||||
* @param t1 type 1
|
||||
* @param t2 type 2
|
||||
* @return wider of t1 and t2, except if one is boolean and the other is neither boolean nor unknown, in which case
|
||||
* {@code Type.OBJECT} is returned.
|
||||
*/
|
||||
public static Type widestReturnType(final Type t1, final Type t2) {
|
||||
if (t1.isUnknown()) {
|
||||
return t2;
|
||||
} else if (t2.isUnknown()) {
|
||||
return t1;
|
||||
} else if(t1.isBoolean() != t2.isBoolean() || t1.isNumeric() != t2.isNumeric()) {
|
||||
return Type.OBJECT;
|
||||
}
|
||||
return Type.widest(t1, t2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a generic version of the type. Basically, if the type {@link #isObject()}, returns {@link #OBJECT},
|
||||
* otherwise returns the type unchanged.
|
||||
* @param type the type to generify
|
||||
* @return the generified type
|
||||
*/
|
||||
public static Type generic(final Type type) {
|
||||
return type.isObject() ? Type.OBJECT : type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the narrowest or least common of two types
|
||||
*
|
||||
@ -752,17 +782,17 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
|
||||
/**
|
||||
* This is an integer type, i.e INT, INT32.
|
||||
*/
|
||||
public static final Type INT = putInCache(new IntType());
|
||||
public static final BitwiseType INT = putInCache(new IntType());
|
||||
|
||||
/**
|
||||
* This is the number singleton, used for all number types
|
||||
*/
|
||||
public static final Type NUMBER = putInCache(new NumberType());
|
||||
public static final NumericType NUMBER = putInCache(new NumberType());
|
||||
|
||||
/**
|
||||
* This is the long singleton, used for all long types
|
||||
*/
|
||||
public static final Type LONG = putInCache(new LongType());
|
||||
public static final BitwiseType LONG = putInCache(new LongType());
|
||||
|
||||
/**
|
||||
* A string singleton
|
||||
|
||||
@ -34,8 +34,8 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
*/
|
||||
@Immutable
|
||||
public final class AccessNode extends BaseNode {
|
||||
/** Property ident. */
|
||||
private final IdentNode property;
|
||||
/** Property name. */
|
||||
private final String property;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@ -45,13 +45,13 @@ public final class AccessNode extends BaseNode {
|
||||
* @param base base node
|
||||
* @param property property
|
||||
*/
|
||||
public AccessNode(final long token, final int finish, final Expression base, final IdentNode property) {
|
||||
public AccessNode(final long token, final int finish, final Expression base, final String property) {
|
||||
super(token, finish, base, false);
|
||||
this.property = property.setIsPropertyName();
|
||||
this.property = property;
|
||||
}
|
||||
|
||||
private AccessNode(final AccessNode accessNode, final Expression base, final IdentNode property, final boolean isFunction, final Type optimisticType, final boolean isOptimistic, final int id) {
|
||||
super(accessNode, base, isFunction, optimisticType, isOptimistic, id);
|
||||
private AccessNode(final AccessNode accessNode, final Expression base, final String property, final boolean isFunction, final Type type, final int id) {
|
||||
super(accessNode, base, isFunction, type, id);
|
||||
this.property = property;
|
||||
}
|
||||
|
||||
@ -63,8 +63,7 @@ public final class AccessNode extends BaseNode {
|
||||
public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
|
||||
if (visitor.enterAccessNode(this)) {
|
||||
return visitor.leaveAccessNode(
|
||||
setBase((Expression)base.accept(visitor)).
|
||||
setProperty((IdentNode)property.accept(visitor)));
|
||||
setBase((Expression)base.accept(visitor)));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@ -73,7 +72,7 @@ public final class AccessNode extends BaseNode {
|
||||
public void toString(final StringBuilder sb) {
|
||||
final boolean needsParen = tokenType().needsParens(getBase().tokenType(), true);
|
||||
|
||||
Node.optimisticType(this, sb);
|
||||
optimisticTypeToString(sb);
|
||||
|
||||
if (needsParen) {
|
||||
sb.append('(');
|
||||
@ -86,15 +85,15 @@ public final class AccessNode extends BaseNode {
|
||||
}
|
||||
sb.append('.');
|
||||
|
||||
sb.append(property.getName());
|
||||
sb.append(property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the property
|
||||
* Get the property name
|
||||
*
|
||||
* @return the property IdentNode
|
||||
* @return the property name
|
||||
*/
|
||||
public IdentNode getProperty() {
|
||||
public String getProperty() {
|
||||
return property;
|
||||
}
|
||||
|
||||
@ -102,22 +101,15 @@ public final class AccessNode extends BaseNode {
|
||||
if (this.base == base) {
|
||||
return this;
|
||||
}
|
||||
return new AccessNode(this, base, property, isFunction(), optimisticType, isOptimistic, programPoint);
|
||||
}
|
||||
|
||||
private AccessNode setProperty(final IdentNode property) {
|
||||
if (this.property == property) {
|
||||
return this;
|
||||
}
|
||||
return new AccessNode(this, base, property, isFunction(), optimisticType, isOptimistic, programPoint);
|
||||
return new AccessNode(this, base, property, isFunction(), type, programPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessNode setType(final TemporarySymbols ts, final Type optimisticType) {
|
||||
if (this.optimisticType == optimisticType) {
|
||||
public AccessNode setType(final Type type) {
|
||||
if (this.type == type) {
|
||||
return this;
|
||||
}
|
||||
return new AccessNode(this, base, property, isFunction(), optimisticType, isOptimistic, programPoint);
|
||||
return new AccessNode(this, base, property, isFunction(), type, programPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -125,7 +117,7 @@ public final class AccessNode extends BaseNode {
|
||||
if (this.programPoint == programPoint) {
|
||||
return this;
|
||||
}
|
||||
return new AccessNode(this, base, property, isFunction(), optimisticType, isOptimistic, programPoint);
|
||||
return new AccessNode(this, base, property, isFunction(), type, programPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -133,15 +125,6 @@ public final class AccessNode extends BaseNode {
|
||||
if (isFunction()) {
|
||||
return this;
|
||||
}
|
||||
return new AccessNode(this, base, property, true, optimisticType, isOptimistic, programPoint);
|
||||
return new AccessNode(this, base, property, true, type, programPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessNode setIsOptimistic(final boolean isOptimistic) {
|
||||
if (this.isOptimistic == isOptimistic) {
|
||||
return this;
|
||||
}
|
||||
return new AccessNode(this, base, property, isFunction(), optimisticType, isOptimistic, programPoint);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -27,6 +27,7 @@ package jdk.nashorn.internal.ir;
|
||||
|
||||
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
|
||||
|
||||
import java.util.function.Function;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.annotations.Immutable;
|
||||
|
||||
@ -44,11 +45,8 @@ public abstract class BaseNode extends Expression implements FunctionCall, Optim
|
||||
|
||||
private final boolean isFunction;
|
||||
|
||||
/** Callsite type for this node, if overriden optimistically or conservatively depending on coercion */
|
||||
protected final Type optimisticType;
|
||||
|
||||
/** Does this node have a callsite type, and it is optimistic rather than inferred from coercion semantics */
|
||||
protected final boolean isOptimistic;
|
||||
/** Callsite type for this node, if overridden optimistically or conservatively depending on coercion */
|
||||
protected final Type type;
|
||||
|
||||
/** Program point id */
|
||||
protected final int programPoint;
|
||||
@ -65,9 +63,8 @@ public abstract class BaseNode extends Expression implements FunctionCall, Optim
|
||||
super(token, base.getStart(), finish);
|
||||
this.base = base;
|
||||
this.isFunction = isFunction;
|
||||
this.optimisticType = null;
|
||||
this.type = null;
|
||||
this.programPoint = INVALID_PROGRAM_POINT;
|
||||
this.isOptimistic = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,16 +73,14 @@ public abstract class BaseNode extends Expression implements FunctionCall, Optim
|
||||
* @param base base
|
||||
* @param isFunction is this a function
|
||||
* @param callSiteType the callsite type for this base node, either optimistic or conservative
|
||||
* @param isOptimistic is the callsite type optimistic rather than based on statically known coercion semantics
|
||||
* @param programPoint program point id
|
||||
*/
|
||||
protected BaseNode(final BaseNode baseNode, final Expression base, final boolean isFunction, final Type callSiteType, final boolean isOptimistic, final int programPoint) {
|
||||
protected BaseNode(final BaseNode baseNode, final Expression base, final boolean isFunction, final Type callSiteType, final int programPoint) {
|
||||
super(baseNode);
|
||||
this.base = base;
|
||||
this.isFunction = isFunction;
|
||||
this.optimisticType = callSiteType;
|
||||
this.type = callSiteType;
|
||||
this.programPoint = programPoint;
|
||||
this.isOptimistic = isOptimistic;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -102,8 +97,8 @@ public abstract class BaseNode extends Expression implements FunctionCall, Optim
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Type getType() {
|
||||
return optimisticType == null ? super.getType() : optimisticType;
|
||||
public Type getType(Function<Symbol, Type> localVariableTypes) {
|
||||
return type == null ? getMostPessimisticType() : type;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -126,12 +121,6 @@ public abstract class BaseNode extends Expression implements FunctionCall, Optim
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOptimistic() {
|
||||
return isOptimistic;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mark this node as being the callee operand of a {@link CallNode}.
|
||||
* @return a base node identical to this one in all aspects except with its function flag set.
|
||||
|
||||
@ -29,7 +29,9 @@ import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.annotations.Ignore;
|
||||
import jdk.nashorn.internal.ir.annotations.Immutable;
|
||||
@ -41,6 +43,10 @@ import jdk.nashorn.internal.parser.TokenType;
|
||||
*/
|
||||
@Immutable
|
||||
public final class BinaryNode extends Expression implements Assignment<Expression>, Optimistic {
|
||||
// Placeholder for "undecided optimistic ADD type". Unfortunately, we can't decide the type of ADD during optimistic
|
||||
// type calculation as it can have local variables as its operands that will decide its ultimate type.
|
||||
private static final Type OPTIMISTIC_UNDECIDED_TYPE = Type.typeFor(new Object(){}.getClass());
|
||||
|
||||
/** Left hand side argument. */
|
||||
private final Expression lhs;
|
||||
|
||||
@ -48,14 +54,14 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
|
||||
|
||||
private final int programPoint;
|
||||
|
||||
private final boolean isOptimistic;
|
||||
|
||||
private final Type type;
|
||||
|
||||
private Type cachedType;
|
||||
private Object cachedTypeFunction;
|
||||
|
||||
@Ignore
|
||||
private static final List<TokenType> CAN_OVERFLOW =
|
||||
Collections.unmodifiableList(
|
||||
Arrays.asList(new TokenType[] {
|
||||
private static final Set<TokenType> CAN_OVERFLOW =
|
||||
Collections.unmodifiableSet(new HashSet<>(Arrays.asList(new TokenType[] {
|
||||
TokenType.ADD,
|
||||
TokenType.DIV,
|
||||
TokenType.MOD,
|
||||
@ -66,7 +72,7 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
|
||||
TokenType.ASSIGN_MOD,
|
||||
TokenType.ASSIGN_MUL,
|
||||
TokenType.ASSIGN_SUB
|
||||
}));
|
||||
})));
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@ -77,23 +83,25 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
|
||||
*/
|
||||
public BinaryNode(final long token, final Expression lhs, final Expression rhs) {
|
||||
super(token, lhs.getStart(), rhs.getFinish());
|
||||
assert !(isTokenType(TokenType.AND) || isTokenType(TokenType.OR)) || lhs instanceof JoinPredecessorExpression;
|
||||
this.lhs = lhs;
|
||||
this.rhs = rhs;
|
||||
this.programPoint = INVALID_PROGRAM_POINT;
|
||||
this.isOptimistic = false;
|
||||
this.type = null;
|
||||
}
|
||||
|
||||
private BinaryNode(final BinaryNode binaryNode, final Expression lhs, final Expression rhs, final Type type, final int programPoint, final boolean isOptimistic) {
|
||||
private BinaryNode(final BinaryNode binaryNode, final Expression lhs, final Expression rhs, final Type type, final int programPoint) {
|
||||
super(binaryNode);
|
||||
this.lhs = lhs;
|
||||
this.rhs = rhs;
|
||||
this.programPoint = programPoint;
|
||||
this.isOptimistic = isOptimistic;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Returns true if the node is a comparison operation.
|
||||
* @return true if the node is a comparison operation.
|
||||
*/
|
||||
public boolean isComparison() {
|
||||
switch (tokenType()) {
|
||||
case EQ:
|
||||
@ -110,6 +118,36 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the node is a logical operation.
|
||||
* @return true if the node is a logical operation.
|
||||
*/
|
||||
public boolean isLogical() {
|
||||
return isLogical(tokenType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the token type represents a logical operation.
|
||||
* @param tokenType the token type
|
||||
* @return true if the token type represents a logical operation.
|
||||
*/
|
||||
public static boolean isLogical(final TokenType tokenType) {
|
||||
switch (tokenType) {
|
||||
case AND:
|
||||
case OR:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Function<Symbol, Type> UNKNOWN_LOCALS = new Function<Symbol, Type>() {
|
||||
@Override
|
||||
public Type apply(Symbol t) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the widest possible type for this operation. This is used for compile time
|
||||
* static type inference
|
||||
@ -118,8 +156,47 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
|
||||
*/
|
||||
@Override
|
||||
public Type getWidestOperationType() {
|
||||
return getWidestOperationType(UNKNOWN_LOCALS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the widest possible operand type for this operation.
|
||||
*
|
||||
* @return Type
|
||||
*/
|
||||
public Type getWidestOperandType() {
|
||||
switch (tokenType()) {
|
||||
case SHR:
|
||||
case ASSIGN_SHR:
|
||||
return Type.INT;
|
||||
case INSTANCEOF:
|
||||
return Type.OBJECT;
|
||||
default:
|
||||
if (isComparison()) {
|
||||
return Type.OBJECT;
|
||||
}
|
||||
return getWidestOperationType();
|
||||
}
|
||||
}
|
||||
|
||||
private Type getWidestOperationType(final Function<Symbol, Type> localVariableTypes) {
|
||||
switch (tokenType()) {
|
||||
case ADD:
|
||||
case ASSIGN_ADD: {
|
||||
final Type lhsType = lhs.getType(localVariableTypes);
|
||||
final Type rhsType = rhs.getType(localVariableTypes);
|
||||
if(lhsType == Type.BOOLEAN && rhsType == Type.BOOLEAN) {
|
||||
return Type.INT;
|
||||
}
|
||||
final Type widestOperandType = Type.widest(lhs.getType(localVariableTypes), rhs.getType(localVariableTypes));
|
||||
if(widestOperandType == Type.INT) {
|
||||
return Type.LONG;
|
||||
} else if (widestOperandType.isNumeric()) {
|
||||
return Type.NUMBER;
|
||||
}
|
||||
return Type.OBJECT;
|
||||
}
|
||||
case SHR:
|
||||
case ASSIGN_SHR:
|
||||
return Type.LONG;
|
||||
case ASSIGN_SAR:
|
||||
@ -135,13 +212,36 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
|
||||
return Type.INT;
|
||||
case DIV:
|
||||
case MOD:
|
||||
case ASSIGN_DIV:
|
||||
case ASSIGN_MOD: {
|
||||
// Naively, one might think MOD has the same type as the widest of its operands, this is unfortunately not
|
||||
// true when denominator is zero, so even type(int % int) == double.
|
||||
return Type.NUMBER;
|
||||
}
|
||||
case MUL:
|
||||
case SUB:
|
||||
case ASSIGN_DIV:
|
||||
case ASSIGN_MOD:
|
||||
case ASSIGN_MUL:
|
||||
case ASSIGN_SUB:
|
||||
case ASSIGN_SUB: {
|
||||
final Type lhsType = lhs.getType(localVariableTypes);
|
||||
final Type rhsType = rhs.getType(localVariableTypes);
|
||||
if(lhsType == Type.BOOLEAN && rhsType == Type.BOOLEAN) {
|
||||
return Type.INT;
|
||||
}
|
||||
final Type widestOperandType = Type.widest(booleanToInt(lhsType), booleanToInt(rhsType));
|
||||
if(widestOperandType == Type.INT) {
|
||||
return Type.LONG;
|
||||
}
|
||||
return Type.NUMBER;
|
||||
}
|
||||
case VOID: {
|
||||
return Type.UNDEFINED;
|
||||
}
|
||||
case ASSIGN: {
|
||||
return rhs.getType(localVariableTypes);
|
||||
}
|
||||
case INSTANCEOF: {
|
||||
return Type.BOOLEAN;
|
||||
}
|
||||
default:
|
||||
if (isComparison()) {
|
||||
return Type.BOOLEAN;
|
||||
@ -150,8 +250,12 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
|
||||
}
|
||||
}
|
||||
|
||||
private static Type booleanToInt(Type type) {
|
||||
return type == Type.BOOLEAN ? Type.INT : type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this node is an assigment
|
||||
* Check if this node is an assignment
|
||||
*
|
||||
* @return true if this node assigns a value
|
||||
*/
|
||||
@ -203,7 +307,10 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
|
||||
@Override
|
||||
public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
|
||||
if (visitor.enterBinaryNode(this)) {
|
||||
return visitor.leaveBinaryNode(setLHS((Expression)lhs.accept(visitor)).setRHS((Expression)rhs.accept(visitor)));
|
||||
if(tokenType().isLeftAssociative()) {
|
||||
return visitor.leaveBinaryNode(setLHS((Expression)lhs.accept(visitor)).setRHS((Expression)rhs.accept(visitor)));
|
||||
}
|
||||
return visitor.leaveBinaryNode(setRHS((Expression)rhs.accept(visitor)).setLHS((Expression)lhs.accept(visitor)));
|
||||
}
|
||||
|
||||
return this;
|
||||
@ -245,6 +352,30 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAlwaysFalse() {
|
||||
switch (tokenType()) {
|
||||
case COMMALEFT:
|
||||
return lhs.isAlwaysFalse();
|
||||
case COMMARIGHT:
|
||||
return rhs.isAlwaysFalse();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAlwaysTrue() {
|
||||
switch (tokenType()) {
|
||||
case COMMALEFT:
|
||||
return lhs.isAlwaysTrue();
|
||||
case COMMARIGHT:
|
||||
return rhs.isAlwaysTrue();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toString(final StringBuilder sb) {
|
||||
final TokenType tokenType = tokenType();
|
||||
@ -281,7 +412,7 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
|
||||
}
|
||||
|
||||
if (isOptimistic()) {
|
||||
sb.append(Node.OPT_IDENTIFIER);
|
||||
sb.append(Expression.OPT_IDENTIFIER);
|
||||
}
|
||||
|
||||
sb.append(' ');
|
||||
@ -320,7 +451,7 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
|
||||
if (this.lhs == lhs) {
|
||||
return this;
|
||||
}
|
||||
return new BinaryNode(this, lhs, rhs, type, programPoint, isOptimistic);
|
||||
return new BinaryNode(this, lhs, rhs, type, programPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -332,7 +463,7 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
|
||||
if (this.rhs == rhs) {
|
||||
return this;
|
||||
}
|
||||
return new BinaryNode(this, lhs, rhs, type, programPoint, isOptimistic);
|
||||
return new BinaryNode(this, lhs, rhs, type, programPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -342,7 +473,7 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
|
||||
|
||||
@Override
|
||||
public boolean canBeOptimistic() {
|
||||
return getMostOptimisticType() != getMostPessimisticType();
|
||||
return isTokenType(TokenType.ADD) || (getMostOptimisticType() != getMostPessimisticType());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -350,22 +481,16 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
|
||||
if (this.programPoint == programPoint) {
|
||||
return this;
|
||||
}
|
||||
return new BinaryNode(this, lhs, rhs, type, programPoint, isOptimistic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BinaryNode setIsOptimistic(final boolean isOptimistic) {
|
||||
if (this.isOptimistic == isOptimistic) {
|
||||
return this;
|
||||
}
|
||||
assert isOptimistic;
|
||||
return new BinaryNode(this, lhs, rhs, type, programPoint, isOptimistic);
|
||||
return new BinaryNode(this, lhs, rhs, type, programPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getMostOptimisticType() {
|
||||
if (CAN_OVERFLOW.contains(tokenType())) {
|
||||
return Type.widest(Type.INT, Type.widest(lhs.getType(), rhs.getType()));
|
||||
final TokenType tokenType = tokenType();
|
||||
if(tokenType == TokenType.ADD || tokenType == TokenType.ASSIGN_ADD) {
|
||||
return OPTIMISTIC_UNDECIDED_TYPE;
|
||||
} else if (CAN_OVERFLOW.contains(tokenType())) {
|
||||
return Type.INT;
|
||||
}
|
||||
return getMostPessimisticType();
|
||||
}
|
||||
@ -375,21 +500,41 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
|
||||
return getWidestOperationType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOptimistic() {
|
||||
return isOptimistic;
|
||||
/**
|
||||
* Returns true if the node has the optimistic type of the node is not yet decided. Optimistic ADD nodes start out
|
||||
* as undecided until we can figure out if they're numeric or not.
|
||||
* @return true if the node has the optimistic type of the node is not yet decided.
|
||||
*/
|
||||
public boolean isOptimisticUndecidedType() {
|
||||
return type == OPTIMISTIC_UNDECIDED_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return type == null ? super.getType() : type;
|
||||
public Type getType(final Function<Symbol, Type> localVariableTypes) {
|
||||
if(localVariableTypes == cachedTypeFunction) {
|
||||
return cachedType;
|
||||
}
|
||||
cachedType = getTypeUncached(localVariableTypes);
|
||||
cachedTypeFunction = localVariableTypes;
|
||||
return cachedType;
|
||||
}
|
||||
|
||||
private Type getTypeUncached(final Function<Symbol, Type> localVariableTypes) {
|
||||
if(type == OPTIMISTIC_UNDECIDED_TYPE) {
|
||||
return Type.widest(lhs.getType(localVariableTypes), rhs.getType(localVariableTypes));
|
||||
}
|
||||
final Type widest = getWidestOperationType(localVariableTypes);
|
||||
if(type == null) {
|
||||
return widest;
|
||||
}
|
||||
return Type.narrowest(widest, Type.widest(type, Type.widest(lhs.getType(localVariableTypes), rhs.getType(localVariableTypes))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BinaryNode setType(TemporarySymbols ts, Type type) {
|
||||
public BinaryNode setType(Type type) {
|
||||
if (this.type == type) {
|
||||
return this;
|
||||
}
|
||||
return new BinaryNode(this, lhs, rhs, type, programPoint, isOptimistic);
|
||||
return new BinaryNode(this, lhs, rhs, type, programPoint);
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,8 +25,6 @@
|
||||
|
||||
package jdk.nashorn.internal.ir;
|
||||
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -36,7 +34,6 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import jdk.nashorn.internal.codegen.Label;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.annotations.Immutable;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
|
||||
@ -60,16 +57,14 @@ public class Block extends Node implements BreakableNode, Flags<Block> {
|
||||
/** Does the block/function need a new scope? */
|
||||
protected final int flags;
|
||||
|
||||
/**
|
||||
* @see JoinPredecessor
|
||||
*/
|
||||
private final LocalVariableConversion conversion;
|
||||
|
||||
/** Flag indicating that this block needs scope */
|
||||
public static final int NEEDS_SCOPE = 1 << 0;
|
||||
|
||||
/**
|
||||
* Flag indicating whether this block uses the self symbol for the function. This is used only for blocks that are
|
||||
* bodies of function nodes who refer to themselves by name. It causes Attr to insert a var [fn_name] = __callee__
|
||||
* at the start of the body
|
||||
*/
|
||||
public static final int USES_SELF_SYMBOL = 1 << 1;
|
||||
|
||||
/**
|
||||
* Is this block tagged as terminal based on its contents
|
||||
* (usually the last statement)
|
||||
@ -98,6 +93,7 @@ public class Block extends Node implements BreakableNode, Flags<Block> {
|
||||
this.breakLabel = new Label("block_break");
|
||||
final int len = statements.length;
|
||||
this.flags = len > 0 && statements[len - 1].hasTerminalFlags() ? IS_TERMINAL : 0;
|
||||
this.conversion = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -111,7 +107,7 @@ public class Block extends Node implements BreakableNode, Flags<Block> {
|
||||
this(token, finish, statements.toArray(new Statement[statements.size()]));
|
||||
}
|
||||
|
||||
private Block(final Block block, final int finish, final List<Statement> statements, final int flags, final Map<String, Symbol> symbols) {
|
||||
private Block(final Block block, final int finish, final List<Statement> statements, final int flags, final Map<String, Symbol> symbols, LocalVariableConversion conversion) {
|
||||
super(block);
|
||||
this.statements = statements;
|
||||
this.flags = flags;
|
||||
@ -119,6 +115,7 @@ public class Block extends Node implements BreakableNode, Flags<Block> {
|
||||
this.entryLabel = new Label(block.entryLabel);
|
||||
this.breakLabel = new Label(block.breakLabel);
|
||||
this.finish = finish;
|
||||
this.conversion = conversion;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -131,8 +128,8 @@ public class Block extends Node implements BreakableNode, Flags<Block> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the symbols in a block
|
||||
* TODO: make this immutable
|
||||
* Clear the symbols in the block.
|
||||
* TODO: make this immutable.
|
||||
*/
|
||||
public void clearSymbols() {
|
||||
symbols.clear();
|
||||
@ -140,7 +137,7 @@ public class Block extends Node implements BreakableNode, Flags<Block> {
|
||||
|
||||
@Override
|
||||
public Node ensureUniqueLabels(final LexicalContext lc) {
|
||||
return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags, symbols));
|
||||
return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags, symbols, conversion));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -159,7 +156,7 @@ public class Block extends Node implements BreakableNode, Flags<Block> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an iterator for all the symbols defined in this block
|
||||
* Get a copy of the list for all the symbols defined in this block
|
||||
* @return symbol iterator
|
||||
*/
|
||||
public List<Symbol> getSymbols() {
|
||||
@ -229,19 +226,6 @@ public class Block extends Node implements BreakableNode, Flags<Block> {
|
||||
return isTerminal ? setFlag(lc, IS_TERMINAL) : clearFlag(lc, IS_TERMINAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the type of the return symbol in this block if present.
|
||||
* @param returnType the new type
|
||||
* @return this block
|
||||
*/
|
||||
public Block setReturnType(final Type returnType) {
|
||||
final Symbol symbol = getExistingSymbol(RETURN.symbolName());
|
||||
if (symbol != null) {
|
||||
symbol.setTypeOverride(returnType);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFlags() {
|
||||
return flags;
|
||||
@ -265,6 +249,19 @@ public class Block extends Node implements BreakableNode, Flags<Block> {
|
||||
return breakLabel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Block setLocalVariableConversion(final LexicalContext lc, final LocalVariableConversion conversion) {
|
||||
if(this.conversion == conversion) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags, symbols, conversion));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalVariableConversion getLocalVariableConversion() {
|
||||
return conversion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of statements in this block
|
||||
*
|
||||
@ -274,6 +271,17 @@ public class Block extends Node implements BreakableNode, Flags<Block> {
|
||||
return Collections.unmodifiableList(statements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the line number of the first statement in the block.
|
||||
* @return the line number of the first statement in the block, or -1 if the block has no statements.
|
||||
*/
|
||||
public int getFirstStatementLineNumber() {
|
||||
if(statements == null || statements.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
return statements.get(0).getLineNumber();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the statement list for this block
|
||||
*
|
||||
@ -289,7 +297,7 @@ public class Block extends Node implements BreakableNode, Flags<Block> {
|
||||
if (!statements.isEmpty()) {
|
||||
lastFinish = statements.get(statements.size() - 1).getFinish();
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new Block(this, Math.max(finish, lastFinish), statements, flags, symbols));
|
||||
return Node.replaceInLexicalContext(lc, this, new Block(this, Math.max(finish, lastFinish), statements, flags, symbols, conversion));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -316,7 +324,7 @@ public class Block extends Node implements BreakableNode, Flags<Block> {
|
||||
if (this.flags == flags) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags, symbols));
|
||||
return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags, symbols, conversion));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -344,7 +352,7 @@ public class Block extends Node implements BreakableNode, Flags<Block> {
|
||||
return this;
|
||||
}
|
||||
|
||||
return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags | NEEDS_SCOPE, symbols));
|
||||
return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags | NEEDS_SCOPE, symbols, conversion));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -32,9 +32,7 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
* IR representation for {@code break} statements.
|
||||
*/
|
||||
@Immutable
|
||||
public final class BreakNode extends Statement {
|
||||
|
||||
private final IdentNode label;
|
||||
public final class BreakNode extends JumpStatement {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@ -42,22 +40,16 @@ public final class BreakNode extends Statement {
|
||||
* @param lineNumber line number
|
||||
* @param token token
|
||||
* @param finish finish
|
||||
* @param label label for break or null if none
|
||||
* @param labelName label name for break or null if none
|
||||
*/
|
||||
public BreakNode(final int lineNumber, final long token, final int finish, final IdentNode label) {
|
||||
super(lineNumber, token, finish);
|
||||
this.label = label;
|
||||
public BreakNode(final int lineNumber, final long token, final int finish, final String labelName) {
|
||||
super(lineNumber, token, finish, labelName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasGoto() {
|
||||
return true;
|
||||
private BreakNode(final BreakNode breakNode, final LocalVariableConversion conversion) {
|
||||
super(breakNode, conversion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assist in IR navigation.
|
||||
* @param visitor IR navigating visitor.
|
||||
*/
|
||||
@Override
|
||||
public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
|
||||
if (visitor.enterBreakNode(this)) {
|
||||
@ -67,21 +59,13 @@ public final class BreakNode extends Statement {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the label for this break node
|
||||
* @return label, or null if none
|
||||
*/
|
||||
public IdentNode getLabel() {
|
||||
return label;
|
||||
@Override
|
||||
JumpStatement createNewJumpStatement(final LocalVariableConversion conversion) {
|
||||
return new BreakNode(this, conversion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toString(final StringBuilder sb) {
|
||||
sb.append("break");
|
||||
|
||||
if (label != null) {
|
||||
sb.append(' ');
|
||||
label.toString(sb);
|
||||
}
|
||||
String getStatementName() {
|
||||
return "break";
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ import jdk.nashorn.internal.codegen.Label;
|
||||
* This class represents a node from which control flow can execute
|
||||
* a {@code break} statement
|
||||
*/
|
||||
public interface BreakableNode extends LexicalContextNode {
|
||||
public interface BreakableNode extends LexicalContextNode, JoinPredecessor {
|
||||
/**
|
||||
* Ensure that any labels in this breakable node are unique so
|
||||
* that new jumps won't go to old parts of the tree. Used for
|
||||
|
||||
@ -36,6 +36,8 @@ abstract class BreakableStatement extends LexicalContextStatement implements Bre
|
||||
/** break label. */
|
||||
protected final Label breakLabel;
|
||||
|
||||
final LocalVariableConversion conversion;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@ -47,16 +49,19 @@ abstract class BreakableStatement extends LexicalContextStatement implements Bre
|
||||
protected BreakableStatement(final int lineNumber, final long token, final int finish, final Label breakLabel) {
|
||||
super(lineNumber, token, finish);
|
||||
this.breakLabel = breakLabel;
|
||||
this.conversion = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor
|
||||
*
|
||||
* @param breakableNode source node
|
||||
* @param conversion the potentially new local variable conversion
|
||||
*/
|
||||
protected BreakableStatement(final BreakableStatement breakableNode) {
|
||||
protected BreakableStatement(final BreakableStatement breakableNode, final LocalVariableConversion conversion) {
|
||||
super(breakableNode);
|
||||
this.breakLabel = new Label(breakableNode.getBreakLabel());
|
||||
this.conversion = conversion;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -88,4 +93,19 @@ abstract class BreakableStatement extends LexicalContextStatement implements Bre
|
||||
public List<Label> getLabels() {
|
||||
return Collections.singletonList(breakLabel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoinPredecessor setLocalVariableConversion(final LexicalContext lc, LocalVariableConversion conversion) {
|
||||
if(this.conversion == conversion) {
|
||||
return this;
|
||||
}
|
||||
return setLocalVariableConversionChanged(lc, conversion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalVariableConversion getLocalVariableConversion() {
|
||||
return conversion;
|
||||
}
|
||||
|
||||
abstract JoinPredecessor setLocalVariableConversionChanged(LexicalContext lc, LocalVariableConversion conversion);
|
||||
}
|
||||
|
||||
@ -25,15 +25,16 @@
|
||||
|
||||
package jdk.nashorn.internal.ir;
|
||||
|
||||
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.annotations.Ignore;
|
||||
import jdk.nashorn.internal.ir.annotations.Immutable;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
|
||||
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
|
||||
|
||||
/**
|
||||
* IR representation for a function call.
|
||||
*/
|
||||
@ -47,13 +48,10 @@ public final class CallNode extends LexicalContextExpression implements Optimist
|
||||
private final List<Expression> args;
|
||||
|
||||
/** Is this a "new" operation */
|
||||
private static final int IS_NEW = 1 << 0;
|
||||
|
||||
/** Is the callsite type for this call optimistic rather than based on statically known coercion semantics */
|
||||
private static final int IS_OPTIMISTIC = 1 << 1;
|
||||
private static final int IS_NEW = 1 << 0;
|
||||
|
||||
/** Can this be a Function.call? */
|
||||
private static final int IS_APPLY_TO_CALL = 1 << 2;
|
||||
private static final int IS_APPLY_TO_CALL = 1 << 1;
|
||||
|
||||
private final int flags;
|
||||
|
||||
@ -153,13 +151,14 @@ public final class CallNode extends LexicalContextExpression implements Optimist
|
||||
* @param finish finish
|
||||
* @param function the function to call
|
||||
* @param args args to the call
|
||||
* @param isNew true if this is a constructor call with the "new" keyword
|
||||
*/
|
||||
public CallNode(final int lineNumber, final long token, final int finish, final Expression function, final List<Expression> args) {
|
||||
public CallNode(final int lineNumber, final long token, final int finish, final Expression function, final List<Expression> args, final boolean isNew) {
|
||||
super(token, finish);
|
||||
|
||||
this.function = function;
|
||||
this.args = args;
|
||||
this.flags = 0;
|
||||
this.flags = isNew ? IS_NEW : 0;
|
||||
this.evalArgs = null;
|
||||
this.lineNumber = lineNumber;
|
||||
this.programPoint = INVALID_PROGRAM_POINT;
|
||||
@ -186,12 +185,12 @@ public final class CallNode extends LexicalContextExpression implements Optimist
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return optimisticType == null ? super.getType() : optimisticType;
|
||||
public Type getType(Function<Symbol, Type> localVariableTypes) {
|
||||
return optimisticType == null ? Type.OBJECT : optimisticType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optimistic setType(final TemporarySymbols ts, final Type optimisticType) {
|
||||
public Optimistic setType(final Type optimisticType) {
|
||||
if (this.optimisticType == optimisticType) {
|
||||
return this;
|
||||
}
|
||||
@ -227,8 +226,7 @@ public final class CallNode extends LexicalContextExpression implements Optimist
|
||||
|
||||
@Override
|
||||
public void toString(final StringBuilder sb) {
|
||||
Node.optimisticType(this, sb);
|
||||
|
||||
optimisticTypeToString(sb);
|
||||
final StringBuilder fsb = new StringBuilder();
|
||||
function.toString(fsb);
|
||||
if (isApplyToCall()) {
|
||||
@ -349,14 +347,6 @@ public final class CallNode extends LexicalContextExpression implements Optimist
|
||||
return (flags & IS_NEW) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag this call as a new operation
|
||||
* @return same node or new one on state change
|
||||
*/
|
||||
public CallNode setIsNew() {
|
||||
return setFlags(flags | IS_NEW);
|
||||
}
|
||||
|
||||
private CallNode setFlags(final int flags) {
|
||||
if (this.flags == flags) {
|
||||
return this;
|
||||
@ -391,17 +381,4 @@ public final class CallNode extends LexicalContextExpression implements Optimist
|
||||
public boolean canBeOptimistic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOptimistic() {
|
||||
return (flags & IS_OPTIMISTIC) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optimistic setIsOptimistic(final boolean isOptimistic) {
|
||||
if (isOptimistic() == isOptimistic) {
|
||||
return this;
|
||||
}
|
||||
return new CallNode(this, function, args, isOptimistic ? (flags | IS_OPTIMISTIC) : (flags & ~IS_OPTIMISTIC), optimisticType, evalArgs, programPoint);
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
* Case nodes are not BreakableNodes, but the SwitchNode is
|
||||
*/
|
||||
@Immutable
|
||||
public final class CaseNode extends Node {
|
||||
public final class CaseNode extends Node implements JoinPredecessor {
|
||||
/** Test expression. */
|
||||
private final Expression test;
|
||||
|
||||
@ -44,6 +44,11 @@ public final class CaseNode extends Node {
|
||||
/** Case entry label. */
|
||||
private final Label entry;
|
||||
|
||||
/**
|
||||
* @see JoinPredecessor
|
||||
*/
|
||||
private final LocalVariableConversion conversion;
|
||||
|
||||
/**
|
||||
* Constructors
|
||||
*
|
||||
@ -58,14 +63,16 @@ public final class CaseNode extends Node {
|
||||
this.test = test;
|
||||
this.body = body;
|
||||
this.entry = new Label("entry");
|
||||
this.conversion = null;
|
||||
}
|
||||
|
||||
CaseNode(final CaseNode caseNode, final Expression test, final Block body) {
|
||||
CaseNode(final CaseNode caseNode, final Expression test, final Block body, final LocalVariableConversion conversion) {
|
||||
super(caseNode);
|
||||
|
||||
this.test = test;
|
||||
this.body = body;
|
||||
this.entry = new Label(caseNode.entry);
|
||||
this.conversion = conversion;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -133,13 +140,26 @@ public final class CaseNode extends Node {
|
||||
if (this.test == test) {
|
||||
return this;
|
||||
}
|
||||
return new CaseNode(this, test, body);
|
||||
return new CaseNode(this, test, body, conversion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoinPredecessor setLocalVariableConversion(final LexicalContext lc, final LocalVariableConversion conversion) {
|
||||
if(this.conversion == conversion) {
|
||||
return this;
|
||||
}
|
||||
return new CaseNode(this, test, body, conversion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalVariableConversion getLocalVariableConversion() {
|
||||
return conversion;
|
||||
}
|
||||
|
||||
private CaseNode setBody(final Block body) {
|
||||
if (this.body == body) {
|
||||
return this;
|
||||
}
|
||||
return new CaseNode(this, test, body);
|
||||
return new CaseNode(this, test, body, conversion);
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,10 +42,7 @@ public final class CatchNode extends Statement {
|
||||
/** Catch body. */
|
||||
private final Block body;
|
||||
|
||||
private final int flags;
|
||||
|
||||
/** Is this block a synthethic rethrow created by finally inlining? */
|
||||
public static final int IS_SYNTHETIC_RETHROW = 1;
|
||||
private final boolean isSyntheticRethrow;
|
||||
|
||||
/**
|
||||
* Constructors
|
||||
@ -56,22 +53,24 @@ public final class CatchNode extends Statement {
|
||||
* @param exception variable name of exception
|
||||
* @param exceptionCondition exception condition
|
||||
* @param body catch body
|
||||
* @param flags flags
|
||||
* @param isSyntheticRethrow true if this node is a synthetically generated rethrow node.
|
||||
*/
|
||||
public CatchNode(final int lineNumber, final long token, final int finish, final IdentNode exception, final Expression exceptionCondition, final Block body, final int flags) {
|
||||
public CatchNode(final int lineNumber, final long token, final int finish, final IdentNode exception,
|
||||
final Expression exceptionCondition, final Block body, final boolean isSyntheticRethrow) {
|
||||
super(lineNumber, token, finish);
|
||||
this.exception = exception == null ? null : exception.setIsInitializedHere();
|
||||
this.exceptionCondition = exceptionCondition;
|
||||
this.body = body;
|
||||
this.flags = flags;
|
||||
this.isSyntheticRethrow = isSyntheticRethrow;
|
||||
}
|
||||
|
||||
private CatchNode(final CatchNode catchNode, final IdentNode exception, final Expression exceptionCondition, final Block body, final int flags) {
|
||||
private CatchNode(final CatchNode catchNode, final IdentNode exception, final Expression exceptionCondition,
|
||||
final Block body, final boolean isSyntheticRethrow) {
|
||||
super(catchNode);
|
||||
this.exception = exception;
|
||||
this.exceptionCondition = exceptionCondition;
|
||||
this.body = body;
|
||||
this.flags = flags;
|
||||
this.isSyntheticRethrow = isSyntheticRethrow;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -132,7 +131,7 @@ public final class CatchNode extends Statement {
|
||||
if (this.exceptionCondition == exceptionCondition) {
|
||||
return this;
|
||||
}
|
||||
return new CatchNode(this, exception, exceptionCondition, body, flags);
|
||||
return new CatchNode(this, exception, exceptionCondition, body, isSyntheticRethrow);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -152,14 +151,14 @@ public final class CatchNode extends Statement {
|
||||
if (this.exception == exception) {
|
||||
return this;
|
||||
}
|
||||
return new CatchNode(this, exception, exceptionCondition, body, flags);
|
||||
return new CatchNode(this, exception, exceptionCondition, body, isSyntheticRethrow);
|
||||
}
|
||||
|
||||
private CatchNode setBody(final Block body) {
|
||||
if (this.body == body) {
|
||||
return this;
|
||||
}
|
||||
return new CatchNode(this, exception, exceptionCondition, body, flags);
|
||||
return new CatchNode(this, exception, exceptionCondition, body, isSyntheticRethrow);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -170,7 +169,6 @@ public final class CatchNode extends Statement {
|
||||
* @return true if a finally synthetic rethrow
|
||||
*/
|
||||
public boolean isSyntheticRethrow() {
|
||||
return (flags & IS_SYNTHETIC_RETHROW) == IS_SYNTHETIC_RETHROW;
|
||||
return isSyntheticRethrow;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -32,26 +32,21 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
* IR representation for CONTINUE statements.
|
||||
*/
|
||||
@Immutable
|
||||
public class ContinueNode extends Statement {
|
||||
|
||||
private IdentNode label;
|
||||
|
||||
public class ContinueNode extends JumpStatement {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param lineNumber line number
|
||||
* @param token token
|
||||
* @param finish finish
|
||||
* @param label label for break or null if none
|
||||
* @param labelName label name for continue or null if none
|
||||
*/
|
||||
public ContinueNode(final int lineNumber, final long token, final int finish, final IdentNode label) {
|
||||
super(lineNumber, token, finish);
|
||||
this.label = label;
|
||||
public ContinueNode(final int lineNumber, final long token, final int finish, final String labelName) {
|
||||
super(lineNumber, token, finish, labelName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasGoto() {
|
||||
return true;
|
||||
private ContinueNode(final ContinueNode continueNode, final LocalVariableConversion conversion) {
|
||||
super(continueNode, conversion);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -63,22 +58,14 @@ public class ContinueNode extends Statement {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the label for this break node
|
||||
* @return label, or null if none
|
||||
*/
|
||||
public IdentNode getLabel() {
|
||||
return label;
|
||||
@Override
|
||||
JumpStatement createNewJumpStatement(final LocalVariableConversion conversion) {
|
||||
return new ContinueNode(this, conversion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toString(final StringBuilder sb) {
|
||||
sb.append("continue");
|
||||
|
||||
if (label != null) {
|
||||
sb.append(' ');
|
||||
label.toString(sb);
|
||||
}
|
||||
String getStatementName() {
|
||||
return "continue";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
|
||||
package jdk.nashorn.internal.ir;
|
||||
|
||||
import java.util.function.Function;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
|
||||
/**
|
||||
@ -33,7 +34,12 @@ import jdk.nashorn.internal.codegen.types.Type;
|
||||
*
|
||||
*/
|
||||
public abstract class Expression extends Node {
|
||||
private Symbol symbol;
|
||||
private static final Function<Symbol, Type> UNKNOWN_LOCALS = new Function<Symbol, Type>() {
|
||||
@Override
|
||||
public Type apply(Symbol t) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
Expression(final long token, final int start, final int finish) {
|
||||
super(token, start, finish);
|
||||
@ -45,57 +51,25 @@ public abstract class Expression extends Node {
|
||||
|
||||
Expression(final Expression expr) {
|
||||
super(expr);
|
||||
this.symbol = expr.symbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Symbol the compiler has assigned to this Node. The symbol
|
||||
* is the place where it's expression value is stored after evaluation
|
||||
* Returns the type of the expression.
|
||||
*
|
||||
* @return the symbol
|
||||
* @return the type of the expression.
|
||||
*/
|
||||
public Symbol getSymbol() {
|
||||
return symbol;
|
||||
public final Type getType() {
|
||||
return getType(UNKNOWN_LOCALS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a symbol to this node. See {@link Expression#getSymbol()} for explanation
|
||||
* of what a symbol is
|
||||
*
|
||||
* @param lc lexical context
|
||||
* @param symbol the symbol
|
||||
* @return new node
|
||||
* Returns the type of the expression under the specified symbol-to-type mapping. By default delegates to
|
||||
* {@link #getType()} but expressions whose type depends on their subexpressions' types and expressions whose type
|
||||
* depends on symbol type ({@link IdentNode}) will have a special implementation.
|
||||
* @param localVariableTypes a mapping from symbols to their types, used for type calculation.
|
||||
* @return the type of the expression under the specified symbol-to-type mapping.
|
||||
*/
|
||||
public Expression setSymbol(final LexicalContext lc, final Symbol symbol) {
|
||||
if (this.symbol == symbol) {
|
||||
return this;
|
||||
}
|
||||
final Expression newExpr = (Expression)clone();
|
||||
newExpr.symbol = symbol;
|
||||
return newExpr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the expression has a type. The default behavior is to go into the symbol
|
||||
* and check the symbol type, but there may be overrides, for example in
|
||||
* getters that require a different type than the internal representation
|
||||
*
|
||||
* @return true if a type exists
|
||||
*/
|
||||
public boolean hasType() {
|
||||
return getSymbol() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the expression. Typically this is the symbol type. No types
|
||||
* are stored in the expression itself, unless it implements TypeOverride.
|
||||
*
|
||||
* @return the type of the node.
|
||||
*/
|
||||
public Type getType() {
|
||||
assert hasType() : this + " has no type";
|
||||
return symbol.getSymbolType();
|
||||
}
|
||||
public abstract Type getType(final Function<Symbol, Type> localVariableTypes);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this expression depends exclusively on state that is constant
|
||||
@ -108,4 +82,88 @@ public abstract class Expression extends Node {
|
||||
public boolean isLocal() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this a self modifying assignment?
|
||||
* @return true if self modifying, e.g. a++, or a*= 17
|
||||
*/
|
||||
public boolean isSelfModifying() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns widest operation type of this operation.
|
||||
*
|
||||
* @return the widest type for this operation
|
||||
*/
|
||||
public Type getWidestOperationType() {
|
||||
return Type.OBJECT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the type of this expression is narrower than its widest operation type (thus, it is
|
||||
* optimistically typed).
|
||||
* @return true if this expression is optimistically typed.
|
||||
*/
|
||||
public final boolean isOptimistic() {
|
||||
return getType().narrowerThan(getWidestOperationType());
|
||||
}
|
||||
|
||||
static final String OPT_IDENTIFIER = "%";
|
||||
|
||||
void optimisticTypeToString(final StringBuilder sb) {
|
||||
optimisticTypeToString(sb, isOptimistic());
|
||||
}
|
||||
|
||||
void optimisticTypeToString(final StringBuilder sb, boolean optimistic) {
|
||||
sb.append('{');
|
||||
final Type type = getType();
|
||||
final String desc = type == Type.UNDEFINED ? "U" : type.getDescriptor();
|
||||
|
||||
sb.append(desc.charAt(desc.length() - 1) == ';' ? "O" : desc);
|
||||
if(isOptimistic() && optimistic) {
|
||||
sb.append(OPT_IDENTIFIER);
|
||||
sb.append('_');
|
||||
sb.append(((Optimistic)this).getProgramPoint());
|
||||
}
|
||||
sb.append('}');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the runtime value of this expression is always false when converted to boolean as per ECMAScript
|
||||
* ToBoolean conversion. Used in control flow calculations.
|
||||
* @return true if this expression's runtime value converted to boolean is always false.
|
||||
*/
|
||||
public boolean isAlwaysFalse() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the runtime value of this expression is always true when converted to boolean as per ECMAScript
|
||||
* ToBoolean conversion. Used in control flow calculations.
|
||||
* @return true if this expression's runtime value converted to boolean is always true.
|
||||
*/
|
||||
public boolean isAlwaysTrue() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the expression is not null and {@link #isAlwaysFalse()}.
|
||||
* @param test a test expression used as a predicate of a branch or a loop.
|
||||
* @return true if the expression is not null and {@link #isAlwaysFalse()}.
|
||||
*/
|
||||
public static boolean isAlwaysFalse(Expression test) {
|
||||
return test != null && test.isAlwaysFalse();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the expression is null or {@link #isAlwaysTrue()}. Null is considered to be always true as a
|
||||
* for loop with no test is equivalent to a for loop with always-true test.
|
||||
* @param test a test expression used as a predicate of a branch or a loop.
|
||||
* @return true if the expression is null or {@link #isAlwaysFalse()}.
|
||||
*/
|
||||
public static boolean isAlwaysTrue(Expression test) {
|
||||
return test == null || test.isAlwaysTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,11 +33,12 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
*/
|
||||
@Immutable
|
||||
public final class ForNode extends LoopNode {
|
||||
/** Initialize expression. */
|
||||
/** Initialize expression for an ordinary for statement, or the LHS expression receiving iterated-over values in a
|
||||
* for-in statement. */
|
||||
private final Expression init;
|
||||
|
||||
/** Test expression. */
|
||||
private final Expression modify;
|
||||
/** Modify expression for an ordinary statement, or the source of the iterator in the for-in statement. */
|
||||
private final JoinPredecessorExpression modify;
|
||||
|
||||
/** Iterator symbol. */
|
||||
private Symbol iterator;
|
||||
@ -59,30 +60,30 @@ public final class ForNode extends LoopNode {
|
||||
* @param lineNumber line number
|
||||
* @param token token
|
||||
* @param finish finish
|
||||
* @param init initialization expression
|
||||
* @param test test
|
||||
* @param body body
|
||||
* @param modify modify
|
||||
* @param flags flags
|
||||
*/
|
||||
public ForNode(final int lineNumber, final long token, final int finish, final Expression init, final Expression test, final Block body, final Expression modify, final int flags) {
|
||||
super(lineNumber, token, finish, test, body, false);
|
||||
this.init = init;
|
||||
this.modify = modify;
|
||||
public ForNode(final int lineNumber, final long token, final int finish, final Block body, final int flags) {
|
||||
super(lineNumber, token, finish, body, false);
|
||||
this.flags = flags;
|
||||
this.init = null;
|
||||
this.modify = null;
|
||||
}
|
||||
|
||||
private ForNode(final ForNode forNode, final Expression init, final Expression test, final Block body, final Expression modify, final int flags, final boolean controlFlowEscapes) {
|
||||
super(forNode, test, body, controlFlowEscapes);
|
||||
private ForNode(final ForNode forNode, final Expression init, final JoinPredecessorExpression test,
|
||||
final Block body, final JoinPredecessorExpression modify, final int flags, final boolean controlFlowEscapes, LocalVariableConversion conversion) {
|
||||
super(forNode, test, body, controlFlowEscapes, conversion);
|
||||
this.init = init;
|
||||
this.modify = modify;
|
||||
this.flags = flags;
|
||||
this.iterator = forNode.iterator; //TODO is this acceptable? symbols are never cloned, just copied as references
|
||||
// Even if the for node gets cloned in try/finally, the symbol can be shared as only one branch of the finally
|
||||
// is executed.
|
||||
this.iterator = forNode.iterator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node ensureUniqueLabels(LexicalContext lc) {
|
||||
return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes));
|
||||
return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -90,8 +91,8 @@ public final class ForNode extends LoopNode {
|
||||
if (visitor.enterForNode(this)) {
|
||||
return visitor.leaveForNode(
|
||||
setInit(lc, init == null ? null : (Expression)init.accept(visitor)).
|
||||
setTest(lc, test == null ? null : (Expression)test.accept(visitor)).
|
||||
setModify(lc, modify == null ? null : (Expression)modify.accept(visitor)).
|
||||
setTest(lc, test == null ? null : (JoinPredecessorExpression)test.accept(visitor)).
|
||||
setModify(lc, modify == null ? null : (JoinPredecessorExpression)modify.accept(visitor)).
|
||||
setBody(lc, (Block)body.accept(visitor)));
|
||||
}
|
||||
|
||||
@ -100,7 +101,8 @@ public final class ForNode extends LoopNode {
|
||||
|
||||
@Override
|
||||
public void toString(final StringBuilder sb) {
|
||||
sb.append("for (");
|
||||
sb.append("for");
|
||||
LocalVariableConversion.toString(conversion, sb).append(' ');
|
||||
|
||||
if (isForIn()) {
|
||||
init.toString(sb);
|
||||
@ -154,7 +156,7 @@ public final class ForNode extends LoopNode {
|
||||
if (this.init == init) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes));
|
||||
return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -212,7 +214,7 @@ public final class ForNode extends LoopNode {
|
||||
* Get the modification expression for this ForNode
|
||||
* @return the modification expression
|
||||
*/
|
||||
public Expression getModify() {
|
||||
public JoinPredecessorExpression getModify() {
|
||||
return modify;
|
||||
}
|
||||
|
||||
@ -222,24 +224,19 @@ public final class ForNode extends LoopNode {
|
||||
* @param modify new modification expression
|
||||
* @return new for node if changed or existing if not
|
||||
*/
|
||||
public ForNode setModify(final LexicalContext lc, final Expression modify) {
|
||||
public ForNode setModify(final LexicalContext lc, final JoinPredecessorExpression modify) {
|
||||
if (this.modify == modify) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes));
|
||||
return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression getTest() {
|
||||
return test;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForNode setTest(final LexicalContext lc, final Expression test) {
|
||||
public ForNode setTest(final LexicalContext lc, final JoinPredecessorExpression test) {
|
||||
if (this.test == test) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes));
|
||||
return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -252,7 +249,7 @@ public final class ForNode extends LoopNode {
|
||||
if (this.body == body) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes));
|
||||
return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -260,14 +257,18 @@ public final class ForNode extends LoopNode {
|
||||
if (this.controlFlowEscapes == controlFlowEscapes) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes));
|
||||
return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion));
|
||||
}
|
||||
|
||||
private ForNode setFlags(final LexicalContext lc, final int flags) {
|
||||
if (this.flags == flags) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes));
|
||||
return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion));
|
||||
}
|
||||
|
||||
@Override
|
||||
JoinPredecessor setLocalVariableConversionChanged(final LexicalContext lc, final LocalVariableConversion conversion) {
|
||||
return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion));
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import java.util.function.Function;
|
||||
import jdk.nashorn.internal.codegen.CompileUnit;
|
||||
import jdk.nashorn.internal.codegen.Compiler;
|
||||
import jdk.nashorn.internal.codegen.CompilerConstants;
|
||||
@ -79,14 +79,16 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
CONSTANT_FOLDED,
|
||||
/** method has been lowered */
|
||||
LOWERED,
|
||||
/** method hass been attributed */
|
||||
ATTR,
|
||||
/** method has been split */
|
||||
SPLIT,
|
||||
/** method has had its types finalized */
|
||||
FINALIZED,
|
||||
/** method has had symbols assigned */
|
||||
SYMBOLS_ASSIGNED,
|
||||
/** computed scope depths for symbols */
|
||||
SCOPE_DEPTHS_COMPUTED,
|
||||
/** method has had types calculated*/
|
||||
OPTIMISTIC_TYPES_ASSIGNED,
|
||||
/** method has had types calculated*/
|
||||
LOCAL_VARIABLE_TYPES_CALCULATED,
|
||||
/** method has been emitted to bytecode */
|
||||
EMITTED
|
||||
}
|
||||
@ -132,9 +134,9 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
@Ignore
|
||||
private final EnumSet<CompilationState> compilationState;
|
||||
|
||||
/** Properties of this object assigned in this function */
|
||||
/** Number of properties of "this" object assigned in this function */
|
||||
@Ignore
|
||||
private HashSet<String> thisProperties;
|
||||
private final int thisProperties;
|
||||
|
||||
/** Function flags. */
|
||||
private final int flags;
|
||||
@ -186,14 +188,11 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
/** Does this function have nested declarations? */
|
||||
public static final int HAS_FUNCTION_DECLARATIONS = 1 << 10;
|
||||
|
||||
/** Can this function be specialized? */
|
||||
public static final int CAN_SPECIALIZE = 1 << 11;
|
||||
|
||||
/** Does this function have optimistic expressions? */
|
||||
public static final int IS_OPTIMISTIC = 1 << 12;
|
||||
/** Does this function have optimistic expressions? (If it does, it can undergo deoptimizing recompilation.) */
|
||||
public static final int IS_DEOPTIMIZABLE = 1 << 11;
|
||||
|
||||
/** Are we vararg, but do we just pass the arguments along to apply or call */
|
||||
public static final int HAS_APPLY_TO_CALL_SPECIALIZATION = 1 << 13;
|
||||
public static final int HAS_APPLY_TO_CALL_SPECIALIZATION = 1 << 12;
|
||||
|
||||
/** Does this function explicitly use the {@link CompilerConstants#RETURN} symbol? Some functions are known to
|
||||
* always use the return symbol, namely a function that is a program (as it must track its last executed expression
|
||||
@ -201,12 +200,19 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
* partitions). Other functions normally don't use the return symbol (so we optimize away its slot), except in some
|
||||
* very special cases, e.g. when containing a return statement in a finally block. These special cases set this
|
||||
* flag. */
|
||||
public static final int USES_RETURN_SYMBOL = 1 << 14;
|
||||
public static final int USES_RETURN_SYMBOL = 1 << 13;
|
||||
|
||||
/**
|
||||
* Is this function the top-level program?
|
||||
*/
|
||||
public static final int IS_PROGRAM = 1 << 15;
|
||||
public static final int IS_PROGRAM = 1 << 14;
|
||||
|
||||
/**
|
||||
* Flag indicating whether this function uses the local variable symbol for itself. Only named function expressions
|
||||
* can have this flag set if they reference themselves (e.g. "(function f() { return f })". Declared functions will
|
||||
* use the symbol in their parent scope instead when they reference themselves by name.
|
||||
*/
|
||||
public static final int USES_SELF_SYMBOL = 1 << 15;
|
||||
|
||||
/** Does this function use the "this" keyword? */
|
||||
public static final int USES_THIS = 1 << 16;
|
||||
@ -285,6 +291,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
this.sourceURL = sourceURL;
|
||||
this.compileUnit = null;
|
||||
this.body = null;
|
||||
this.thisProperties = 0;
|
||||
}
|
||||
|
||||
private FunctionNode(
|
||||
@ -297,7 +304,8 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
final CompileUnit compileUnit,
|
||||
final EnumSet<CompilationState> compilationState,
|
||||
final Block body,
|
||||
final List<IdentNode> parameters) {
|
||||
final List<IdentNode> parameters,
|
||||
final int thisProperties) {
|
||||
super(functionNode);
|
||||
this.lineNumber = functionNode.lineNumber;
|
||||
this.flags = flags;
|
||||
@ -309,6 +317,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
this.compilationState = compilationState;
|
||||
this.body = body;
|
||||
this.parameters = parameters;
|
||||
this.thisProperties = thisProperties;
|
||||
|
||||
// the fields below never change - they are final and assigned in constructor
|
||||
this.source = functionNode.source;
|
||||
@ -318,7 +327,6 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
this.declaredSymbols = functionNode.declaredSymbols;
|
||||
this.kind = functionNode.kind;
|
||||
this.firstToken = functionNode.firstToken;
|
||||
this.thisProperties = functionNode.thisProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -329,6 +337,15 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits the parameter nodes of this function. Parameters are normally not visited automatically.
|
||||
* @param visitor the visitor to apply to the nodes.
|
||||
* @return a list of parameter nodes, potentially modified from original ones by the visitor.
|
||||
*/
|
||||
public List<IdentNode> visitParameters(final NodeVisitor<? extends LexicalContext> visitor) {
|
||||
return Node.accept(visitor, IdentNode.class, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the source for this function
|
||||
* @return the source
|
||||
@ -374,7 +391,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
return this;
|
||||
}
|
||||
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, newSourceURL, name, returnType, compileUnit, compilationState, body, parameters));
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, newSourceURL, name, returnType, compileUnit, compilationState, body, parameters, thisProperties));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -385,14 +402,6 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
return lineNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can this function node be regenerated with more specific type args?
|
||||
* @return true if specialization is possible
|
||||
*/
|
||||
public boolean canSpecialize() {
|
||||
return (flags & (IS_OPTIMISTIC | CAN_SPECIALIZE)) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the compilation state of this function
|
||||
* @return the compilation state
|
||||
@ -440,7 +449,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
}
|
||||
final EnumSet<CompilationState> newState = EnumSet.copyOf(this.compilationState);
|
||||
newState.add(state);
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, newState, body, parameters));
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, newState, body, parameters, thisProperties));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -497,7 +506,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
if (this.flags == flags) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters));
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, thisProperties));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -519,11 +528,11 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the function is optimistic
|
||||
* @return true if this function is optimistic
|
||||
* Returns true if the function contains at least one optimistic operation (and thus can be deoptimized).
|
||||
* @return true if the function contains at least one optimistic operation (and thus can be deoptimized).
|
||||
*/
|
||||
public boolean isOptimistic() {
|
||||
return getFlag(IS_OPTIMISTIC);
|
||||
public boolean canBeDeoptimized() {
|
||||
return getFlag(IS_DEOPTIMIZABLE);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -636,7 +645,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
if(this.body == body) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags | (body.needsScope() ? FunctionNode.HAS_SCOPE_BLOCK : 0), sourceURL, name, returnType, compileUnit, compilationState, body, parameters));
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags | (body.needsScope() ? FunctionNode.HAS_SCOPE_BLOCK : 0), sourceURL, name, returnType, compileUnit, compilationState, body, parameters, thisProperties));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -708,22 +717,24 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a property assigned to the this object in this function.
|
||||
* @param key the property name
|
||||
* Set the number of properties assigned to the this object in this function.
|
||||
* @param lc the current lexical context.
|
||||
* @param thisProperties number of properties
|
||||
* @return a potentially modified function node
|
||||
*/
|
||||
public void addThisProperty(final String key) {
|
||||
if (thisProperties == null) {
|
||||
thisProperties = new HashSet<>();
|
||||
public FunctionNode setThisProperties(final LexicalContext lc, final int thisProperties) {
|
||||
if (this.thisProperties == thisProperties) {
|
||||
return this;
|
||||
}
|
||||
thisProperties.add(key);
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, thisProperties));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of properties assigned to the this object in this function.
|
||||
* @return number of properties
|
||||
*/
|
||||
public int countThisProperties() {
|
||||
return thisProperties == null ? 0 : thisProperties.size();
|
||||
public int getThisProperties() {
|
||||
return thisProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -761,7 +772,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
if (this.lastToken == lastToken) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters));
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, thisProperties));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -782,7 +793,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
if (this.name.equals(name)) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters));
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, thisProperties));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -812,6 +823,16 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
return Collections.unmodifiableList(parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identifier for a named parameter at the specified position in this function's parameter list.
|
||||
* @param index the parameter's position.
|
||||
* @return the identifier for the requested named parameter.
|
||||
* @throws IndexOutOfBoundsException if the index is invalid.
|
||||
*/
|
||||
public IdentNode getParameter(final int index) {
|
||||
return parameters.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the compile unit used to compile this function
|
||||
* @see Compiler
|
||||
@ -823,7 +844,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
if (this.parameters == parameters) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters));
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, thisProperties));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -849,11 +870,16 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
* @return true if this function node is a named function expression that uses the symbol for itself.
|
||||
*/
|
||||
public boolean usesSelfSymbol() {
|
||||
return body.getFlag(Block.USES_SELF_SYMBOL);
|
||||
return getFlag(USES_SELF_SYMBOL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
public Type getType(Function<Symbol, Type> localVariableTypes) {
|
||||
return FUNCTION_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getWidestOperationType() {
|
||||
return FUNCTION_TYPE;
|
||||
}
|
||||
|
||||
@ -878,10 +904,10 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
//we never bother with object types narrower than objects, that will lead to byte code verification errors
|
||||
//as for instance even if we know we are returning a string from a method, the code generator will always
|
||||
//treat it as an object, at least for now
|
||||
if (this.returnType == returnType) {
|
||||
final Type type = returnType.isObject() ? Type.OBJECT : returnType;
|
||||
if (this.returnType == type) {
|
||||
return this;
|
||||
}
|
||||
final Type type = Type.widest(this.returnType, returnType.isObject() ? Type.OBJECT : returnType);
|
||||
return Node.replaceInLexicalContext(
|
||||
lc,
|
||||
this,
|
||||
@ -894,8 +920,9 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
type,
|
||||
compileUnit,
|
||||
compilationState,
|
||||
body.setReturnType(type),
|
||||
parameters
|
||||
body,
|
||||
parameters,
|
||||
thisProperties
|
||||
));
|
||||
}
|
||||
|
||||
@ -927,7 +954,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
if (this.compileUnit == compileUnit) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters));
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, thisProperties));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -28,33 +28,40 @@ package jdk.nashorn.internal.ir;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.__DIR__;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.__FILE__;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.__LINE__;
|
||||
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
|
||||
|
||||
import java.util.function.Function;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.annotations.Immutable;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
|
||||
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
|
||||
import jdk.nashorn.internal.parser.Token;
|
||||
import jdk.nashorn.internal.parser.TokenType;
|
||||
|
||||
/**
|
||||
* IR representation for an identifier.
|
||||
*/
|
||||
@Immutable
|
||||
public final class IdentNode extends Expression implements PropertyKey, FunctionCall, Optimistic {
|
||||
public final class IdentNode extends Expression implements PropertyKey, FunctionCall, Optimistic, JoinPredecessor {
|
||||
private static final int PROPERTY_NAME = 1 << 0;
|
||||
private static final int INITIALIZED_HERE = 1 << 1;
|
||||
private static final int FUNCTION = 1 << 2;
|
||||
private static final int FUTURESTRICT_NAME = 1 << 3;
|
||||
private static final int OPTIMISTIC = 1 << 4;
|
||||
|
||||
/** Identifier. */
|
||||
private final String name;
|
||||
|
||||
/** Optimistic type */
|
||||
private final Type optimisticType;
|
||||
private final Type type;
|
||||
|
||||
private final int flags;
|
||||
|
||||
private final int programPoint;
|
||||
|
||||
private final LocalVariableConversion conversion;
|
||||
|
||||
private Symbol symbol;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@ -64,18 +71,21 @@ public final class IdentNode extends Expression implements PropertyKey, Function
|
||||
*/
|
||||
public IdentNode(final long token, final int finish, final String name) {
|
||||
super(token, finish);
|
||||
this.name = name.intern();
|
||||
this.optimisticType = null;
|
||||
this.flags = 0;
|
||||
this.programPoint = INVALID_PROGRAM_POINT;
|
||||
this.name = name.intern();
|
||||
this.type = null;
|
||||
this.flags = 0;
|
||||
this.programPoint = INVALID_PROGRAM_POINT;
|
||||
this.conversion = null;
|
||||
}
|
||||
|
||||
private IdentNode(final IdentNode identNode, final String name, final Type callSiteType, final int flags, final int programPoint) {
|
||||
private IdentNode(final IdentNode identNode, final String name, final Type type, final int flags, final int programPoint, final LocalVariableConversion conversion) {
|
||||
super(identNode);
|
||||
this.name = name;
|
||||
this.optimisticType = callSiteType;
|
||||
this.flags = flags;
|
||||
this.programPoint = programPoint;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.flags = flags;
|
||||
this.programPoint = programPoint;
|
||||
this.conversion = conversion;
|
||||
this.symbol = identNode.symbol;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -85,20 +95,33 @@ public final class IdentNode extends Expression implements PropertyKey, Function
|
||||
*/
|
||||
public IdentNode(final IdentNode identNode) {
|
||||
super(identNode);
|
||||
this.name = identNode.getName();
|
||||
this.optimisticType = null;
|
||||
this.flags = identNode.flags;
|
||||
this.programPoint = INVALID_PROGRAM_POINT;
|
||||
this.name = identNode.getName();
|
||||
this.type = identNode.type;
|
||||
this.flags = identNode.flags;
|
||||
this.conversion = identNode.conversion;
|
||||
this.programPoint = INVALID_PROGRAM_POINT;
|
||||
this.symbol = identNode.symbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an identifier for the symbol. Normally used by code generator for creating temporary storage identifiers
|
||||
* that must contain both a symbol and a type.
|
||||
* @param symbol the symbol to create a temporary identifier for.
|
||||
* @return a temporary identifier for the symbol.
|
||||
*/
|
||||
public static IdentNode createInternalIdentifier(final Symbol symbol) {
|
||||
return (IdentNode)new IdentNode(Token.toDesc(TokenType.IDENT, 0, 0), 0, symbol.getName()).setSymbol(symbol);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return optimisticType == null ? super.getType() : optimisticType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAtom() {
|
||||
return true;
|
||||
public Type getType(Function<Symbol, Type> localVariableTypes) {
|
||||
if(type != null) {
|
||||
return type;
|
||||
} else if(symbol != null && symbol.isScope()) {
|
||||
return Type.OBJECT;
|
||||
}
|
||||
final Type symbolType = localVariableTypes.apply(symbol);
|
||||
return symbolType == null ? Type.UNDEFINED : symbolType;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -117,7 +140,7 @@ public final class IdentNode extends Expression implements PropertyKey, Function
|
||||
|
||||
@Override
|
||||
public void toString(final StringBuilder sb) {
|
||||
Node.optimisticType(this, sb);
|
||||
optimisticTypeToString(sb, symbol == null || !symbol.hasSlot());
|
||||
sb.append(name);
|
||||
}
|
||||
|
||||
@ -139,6 +162,31 @@ public final class IdentNode extends Expression implements PropertyKey, Function
|
||||
return !getSymbol().isScope();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Symbol the compiler has assigned to this identifier. The symbol is a description of the storage
|
||||
* location for the identifier.
|
||||
*
|
||||
* @return the symbol
|
||||
*/
|
||||
public Symbol getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a symbol to this identifier. See {@link IdentNode#getSymbol()} for explanation of what a symbol is.
|
||||
*
|
||||
* @param symbol the symbol
|
||||
* @return new node
|
||||
*/
|
||||
public Expression setSymbol(final Symbol symbol) {
|
||||
if (this.symbol == symbol) {
|
||||
return this;
|
||||
}
|
||||
final IdentNode newIdent = (IdentNode)clone();
|
||||
newIdent.symbol = symbol;
|
||||
return newIdent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this IdentNode is a property name
|
||||
* @return true if this is a property name
|
||||
@ -155,7 +203,7 @@ public final class IdentNode extends Expression implements PropertyKey, Function
|
||||
if (isPropertyName()) {
|
||||
return this;
|
||||
}
|
||||
return new IdentNode(this, name, optimisticType, flags | PROPERTY_NAME, programPoint);
|
||||
return new IdentNode(this, name, type, flags | PROPERTY_NAME, programPoint, conversion);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -174,7 +222,7 @@ public final class IdentNode extends Expression implements PropertyKey, Function
|
||||
if (isFutureStrictName()) {
|
||||
return this;
|
||||
}
|
||||
return new IdentNode(this, name, optimisticType, flags | FUTURESTRICT_NAME, programPoint);
|
||||
return new IdentNode(this, name, type, flags | FUTURESTRICT_NAME, programPoint, conversion);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -193,7 +241,7 @@ public final class IdentNode extends Expression implements PropertyKey, Function
|
||||
if (isInitializedHere()) {
|
||||
return this;
|
||||
}
|
||||
return new IdentNode(this, name, optimisticType, flags | INITIALIZED_HERE, programPoint);
|
||||
return new IdentNode(this, name, type, flags | INITIALIZED_HERE, programPoint, conversion);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -212,11 +260,11 @@ public final class IdentNode extends Expression implements PropertyKey, Function
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentNode setType(final TemporarySymbols ts, final Type callSiteType) {
|
||||
if (this.optimisticType == callSiteType) {
|
||||
public IdentNode setType(final Type type) {
|
||||
if (this.type == type) {
|
||||
return this;
|
||||
}
|
||||
return new IdentNode(this, name, callSiteType, flags, programPoint);
|
||||
return new IdentNode(this, name, type, flags, programPoint, conversion);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -227,7 +275,7 @@ public final class IdentNode extends Expression implements PropertyKey, Function
|
||||
if (isFunction()) {
|
||||
return this;
|
||||
}
|
||||
return new IdentNode(this, name, optimisticType, flags | FUNCTION, programPoint);
|
||||
return new IdentNode(this, name, type, flags | FUNCTION, programPoint, conversion);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -240,7 +288,7 @@ public final class IdentNode extends Expression implements PropertyKey, Function
|
||||
if (this.programPoint == programPoint) {
|
||||
return this;
|
||||
}
|
||||
return new IdentNode(this, name, optimisticType, flags, programPoint);
|
||||
return new IdentNode(this, name, type, flags, programPoint, conversion);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -259,8 +307,11 @@ public final class IdentNode extends Expression implements PropertyKey, Function
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOptimistic() {
|
||||
return (flags & OPTIMISTIC) == OPTIMISTIC;
|
||||
public JoinPredecessor setLocalVariableConversion(final LexicalContext lc, final LocalVariableConversion conversion) {
|
||||
if(this.conversion == conversion) {
|
||||
return this;
|
||||
}
|
||||
return new IdentNode(this, name, type, flags, programPoint, conversion);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -274,10 +325,7 @@ public final class IdentNode extends Expression implements PropertyKey, Function
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optimistic setIsOptimistic(final boolean isOptimistic) {
|
||||
if (isOptimistic() == isOptimistic) {
|
||||
return this;
|
||||
}
|
||||
return new IdentNode(this, name, optimisticType, isOptimistic ? (flags | OPTIMISTIC) : (flags & ~OPTIMISTIC), programPoint);
|
||||
public LocalVariableConversion getLocalVariableConversion() {
|
||||
return conversion;
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
* IR representation for an IF statement.
|
||||
*/
|
||||
@Immutable
|
||||
public final class IfNode extends Statement {
|
||||
public final class IfNode extends Statement implements JoinPredecessor {
|
||||
/** Test expression. */
|
||||
private final Expression test;
|
||||
|
||||
@ -42,6 +42,12 @@ public final class IfNode extends Statement {
|
||||
/** Fail statements. */
|
||||
private final Block fail;
|
||||
|
||||
/**
|
||||
* Local variable conversions that need to be performed after test if it evaluates to false, and there's no else
|
||||
* branch.
|
||||
*/
|
||||
private final LocalVariableConversion conversion;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@ -57,13 +63,15 @@ public final class IfNode extends Statement {
|
||||
this.test = test;
|
||||
this.pass = pass;
|
||||
this.fail = fail;
|
||||
this.conversion = null;
|
||||
}
|
||||
|
||||
private IfNode(final IfNode ifNode, final Expression test, final Block pass, final Block fail) {
|
||||
private IfNode(final IfNode ifNode, final Expression test, final Block pass, final Block fail, final LocalVariableConversion conversion) {
|
||||
super(ifNode);
|
||||
this.test = test;
|
||||
this.pass = pass;
|
||||
this.fail = fail;
|
||||
this.conversion = conversion;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -102,7 +110,7 @@ public final class IfNode extends Statement {
|
||||
if (this.fail == fail) {
|
||||
return this;
|
||||
}
|
||||
return new IfNode(this, test, pass, fail);
|
||||
return new IfNode(this, test, pass, fail, conversion);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -117,7 +125,7 @@ public final class IfNode extends Statement {
|
||||
if (this.pass == pass) {
|
||||
return this;
|
||||
}
|
||||
return new IfNode(this, test, pass, fail);
|
||||
return new IfNode(this, test, pass, fail, conversion);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -137,6 +145,19 @@ public final class IfNode extends Statement {
|
||||
if (this.test == test) {
|
||||
return this;
|
||||
}
|
||||
return new IfNode(this, test, pass, fail);
|
||||
return new IfNode(this, test, pass, fail, conversion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IfNode setLocalVariableConversion(final LexicalContext lc, final LocalVariableConversion conversion) {
|
||||
if(this.conversion == conversion) {
|
||||
return this;
|
||||
}
|
||||
return new IfNode(this, test, pass, fail, conversion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalVariableConversion getLocalVariableConversion() {
|
||||
return conversion;
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,8 +49,8 @@ public final class IndexNode extends BaseNode {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
private IndexNode(final IndexNode indexNode, final Expression base, final Expression index, final boolean isFunction, final Type optimisticType, final boolean isOptimistic, final int programPoint) {
|
||||
super(indexNode, base, isFunction, optimisticType, isOptimistic, programPoint);
|
||||
private IndexNode(final IndexNode indexNode, final Expression base, final Expression index, final boolean isFunction, final Type type, final int programPoint) {
|
||||
super(indexNode, base, isFunction, type, programPoint);
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@ -72,7 +72,7 @@ public final class IndexNode extends BaseNode {
|
||||
sb.append('(');
|
||||
}
|
||||
|
||||
Node.optimisticType(this, sb);
|
||||
optimisticTypeToString(sb);
|
||||
|
||||
base.toString(sb);
|
||||
|
||||
@ -97,7 +97,7 @@ public final class IndexNode extends BaseNode {
|
||||
if (this.base == base) {
|
||||
return this;
|
||||
}
|
||||
return new IndexNode(this, base, index, isFunction(), optimisticType, isOptimistic, programPoint);
|
||||
return new IndexNode(this, base, index, isFunction(), type, programPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -109,15 +109,15 @@ public final class IndexNode extends BaseNode {
|
||||
if(this.index == index) {
|
||||
return this;
|
||||
}
|
||||
return new IndexNode(this, base, index, isFunction(), optimisticType, isOptimistic, programPoint);
|
||||
return new IndexNode(this, base, index, isFunction(), type, programPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexNode setType(final TemporarySymbols ts, final Type optimisticType) {
|
||||
if (this.optimisticType == optimisticType) {
|
||||
public IndexNode setType(final Type type) {
|
||||
if (this.type == type) {
|
||||
return this;
|
||||
}
|
||||
return new IndexNode(this, base, index, isFunction(), optimisticType, isOptimistic, programPoint);
|
||||
return new IndexNode(this, base, index, isFunction(), type, programPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -125,7 +125,7 @@ public final class IndexNode extends BaseNode {
|
||||
if (isFunction()) {
|
||||
return this;
|
||||
}
|
||||
return new IndexNode(this, base, index, true, optimisticType, isOptimistic, programPoint);
|
||||
return new IndexNode(this, base, index, true, type, programPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -133,14 +133,6 @@ public final class IndexNode extends BaseNode {
|
||||
if (this.programPoint == programPoint) {
|
||||
return this;
|
||||
}
|
||||
return new IndexNode(this, base, index, isFunction(), optimisticType, isOptimistic, programPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexNode setIsOptimistic(boolean isOptimistic) {
|
||||
if (this.isOptimistic == isOptimistic) {
|
||||
return this;
|
||||
}
|
||||
return new IndexNode(this, base, index, isFunction(), optimisticType, isOptimistic, programPoint);
|
||||
return new IndexNode(this, base, index, isFunction(), type, programPoint);
|
||||
}
|
||||
}
|
||||
|
||||
49
nashorn/src/jdk/nashorn/internal/ir/JoinPredecessor.java
Normal file
49
nashorn/src/jdk/nashorn/internal/ir/JoinPredecessor.java
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.ir;
|
||||
|
||||
/**
|
||||
* Interface implemented by AST nodes that either can occur as predecessors of a control flow join, or contain a control
|
||||
* flow join themselves. JoinPredecessor only provides a getter and setter for a {@link LocalVariableConversion}; the
|
||||
* semantics of control flow for a particular node implementing the interface are shared between
|
||||
* {@code LocalVariableTypesCalculator} that creates the conversions, and {@code CodeGenerator} that uses them.
|
||||
*/
|
||||
public interface JoinPredecessor {
|
||||
/**
|
||||
* Set the local variable conversions needed to unify their types at a control flow join point.
|
||||
* @param lc the current lexical context
|
||||
* @param conversion the conversions.
|
||||
* @return this node or a different node representing the change.
|
||||
*/
|
||||
public JoinPredecessor setLocalVariableConversion(LexicalContext lc, LocalVariableConversion conversion);
|
||||
|
||||
/**
|
||||
* Returns the local variable conversions needed to unify their types at a control flow join point.
|
||||
* @return the local variable conversions needed to unify their types at a control flow join point. Can be null.
|
||||
* Can contain {@link LocalVariableConversion#isLive() dead conversions}.
|
||||
*/
|
||||
public LocalVariableConversion getLocalVariableConversion();
|
||||
}
|
||||
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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.ir;
|
||||
|
||||
import java.util.function.Function;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
|
||||
/**
|
||||
* A wrapper for an expression that is in a position to be a join predecessor.
|
||||
*/
|
||||
public class JoinPredecessorExpression extends Expression implements JoinPredecessor {
|
||||
|
||||
private final Expression expression;
|
||||
private final LocalVariableConversion conversion;
|
||||
|
||||
/**
|
||||
* A no-arg constructor does not wrap any expression on its own, but can be used as a place to contain a local
|
||||
* variable conversion in a place where an expression can otherwise stand.
|
||||
*/
|
||||
public JoinPredecessorExpression() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* A constructor for wrapping an expression and making it a join predecessor. Typically used on true and false
|
||||
* subexpressions of the ternary node as well as on the operands of short-circuiting logical expressions {@code &&}
|
||||
* and {@code ||}.
|
||||
* @param expression the expression to wrap
|
||||
*/
|
||||
public JoinPredecessorExpression(final Expression expression) {
|
||||
this(expression, null);
|
||||
}
|
||||
|
||||
private JoinPredecessorExpression(final Expression expression, final LocalVariableConversion conversion) {
|
||||
super(expression == null ? 0L : expression.getToken(), expression == null ? 0 : expression.getStart(), expression == null ? 0 : expression.getFinish());
|
||||
this.expression = expression;
|
||||
this.conversion = conversion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoinPredecessor setLocalVariableConversion(final LexicalContext lc, final LocalVariableConversion conversion) {
|
||||
if(conversion == this.conversion) {
|
||||
return this;
|
||||
}
|
||||
return new JoinPredecessorExpression(expression, conversion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType(Function<Symbol, Type> localVariableTypes) {
|
||||
return expression.getType(localVariableTypes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAlwaysFalse() {
|
||||
return expression != null && expression.isAlwaysFalse();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAlwaysTrue() {
|
||||
return expression != null && expression.isAlwaysTrue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying expression.
|
||||
* @return the underlying expression.
|
||||
*/
|
||||
public Expression getExpression() {
|
||||
return expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the underlying expression.
|
||||
* @param expression the new underlying expression
|
||||
* @return this or modified join predecessor expression object.
|
||||
*/
|
||||
public JoinPredecessorExpression setExpression(final Expression expression) {
|
||||
if(expression == this.expression) {
|
||||
return this;
|
||||
}
|
||||
return new JoinPredecessorExpression(expression, conversion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalVariableConversion getLocalVariableConversion() {
|
||||
return conversion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node accept(NodeVisitor<? extends LexicalContext> visitor) {
|
||||
if(visitor.enterJoinPredecessorExpression(this)) {
|
||||
final Expression expr = getExpression();
|
||||
return visitor.leaveJoinPredecessorExpression(expr == null ? this : setExpression((Expression)expr.accept(visitor)));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toString(StringBuilder sb) {
|
||||
if(expression != null) {
|
||||
expression.toString(sb);
|
||||
}
|
||||
if(conversion != null) {
|
||||
conversion.toString(sb);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
99
nashorn/src/jdk/nashorn/internal/ir/JumpStatement.java
Normal file
99
nashorn/src/jdk/nashorn/internal/ir/JumpStatement.java
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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.ir;
|
||||
|
||||
/**
|
||||
* Common base class for jump statements (e.g. {@code break} and {@code continue}).
|
||||
*/
|
||||
public abstract class JumpStatement extends Statement implements JoinPredecessor {
|
||||
|
||||
private final String labelName;
|
||||
private final LocalVariableConversion conversion;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param lineNumber line number
|
||||
* @param token token
|
||||
* @param finish finish
|
||||
* @param labelName label name for break or null if none
|
||||
*/
|
||||
protected JumpStatement(final int lineNumber, final long token, final int finish, final String labelName) {
|
||||
super(lineNumber, token, finish);
|
||||
this.labelName = labelName;
|
||||
this.conversion = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor.
|
||||
* @param jumpStatement the original jump statement.
|
||||
* @param conversion a new local variable conversion.
|
||||
*/
|
||||
protected JumpStatement(final JumpStatement jumpStatement, final LocalVariableConversion conversion) {
|
||||
super(jumpStatement);
|
||||
this.labelName = jumpStatement.labelName;
|
||||
this.conversion = conversion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasGoto() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the label name for this break node
|
||||
* @return label name, or null if none
|
||||
*/
|
||||
public String getLabelName() {
|
||||
return labelName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toString(final StringBuilder sb) {
|
||||
sb.append(getStatementName());
|
||||
|
||||
if (labelName != null) {
|
||||
sb.append(' ').append(labelName);
|
||||
}
|
||||
}
|
||||
|
||||
abstract String getStatementName();
|
||||
|
||||
@Override
|
||||
public JumpStatement setLocalVariableConversion(final LexicalContext lc, final LocalVariableConversion conversion) {
|
||||
if(this.conversion == conversion) {
|
||||
return this;
|
||||
}
|
||||
return createNewJumpStatement(conversion);
|
||||
}
|
||||
|
||||
abstract JumpStatement createNewJumpStatement(LocalVariableConversion newConversion);
|
||||
|
||||
@Override
|
||||
public LocalVariableConversion getLocalVariableConversion() {
|
||||
return conversion;
|
||||
}
|
||||
}
|
||||
@ -29,36 +29,42 @@ import jdk.nashorn.internal.ir.annotations.Immutable;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
|
||||
/**
|
||||
* IR representation for a labeled statement.
|
||||
* IR representation for a labeled statement. It implements JoinPredecessor to hold conversions that need to be effected
|
||||
* when the block exits normally, but is also targeted by a break statement that might bring different local variable
|
||||
* types to the join at the break point.
|
||||
*/
|
||||
@Immutable
|
||||
public final class LabelNode extends LexicalContextStatement {
|
||||
public final class LabelNode extends LexicalContextStatement implements JoinPredecessor {
|
||||
/** Label ident. */
|
||||
private final IdentNode label;
|
||||
private final String labelName;
|
||||
|
||||
/** Statements. */
|
||||
private final Block body;
|
||||
|
||||
private final LocalVariableConversion localVariableConversion;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param lineNumber line number
|
||||
* @param token token
|
||||
* @param finish finish
|
||||
* @param label label identifier
|
||||
* @param labelName label name
|
||||
* @param body body of label node
|
||||
*/
|
||||
public LabelNode(final int lineNumber, final long token, final int finish, final IdentNode label, final Block body) {
|
||||
public LabelNode(final int lineNumber, final long token, final int finish, final String labelName, final Block body) {
|
||||
super(lineNumber, token, finish);
|
||||
|
||||
this.label = label;
|
||||
this.labelName = labelName;
|
||||
this.body = body;
|
||||
this.localVariableConversion = null;
|
||||
}
|
||||
|
||||
private LabelNode(final LabelNode labelNode, final IdentNode label, final Block body) {
|
||||
private LabelNode(final LabelNode labelNode, final String labelName, final Block body, final LocalVariableConversion localVariableConversion) {
|
||||
super(labelNode);
|
||||
this.label = label;
|
||||
this.body = body;
|
||||
this.labelName = labelName;
|
||||
this.body = body;
|
||||
this.localVariableConversion = localVariableConversion;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -69,9 +75,7 @@ public final class LabelNode extends LexicalContextStatement {
|
||||
@Override
|
||||
public Node accept(final LexicalContext lc, final NodeVisitor<? extends LexicalContext> visitor) {
|
||||
if (visitor.enterLabelNode(this)) {
|
||||
return visitor.leaveLabelNode(
|
||||
setLabel(lc, (IdentNode)label.accept(visitor)).
|
||||
setBody(lc, (Block)body.accept(visitor)));
|
||||
return visitor.leaveLabelNode(setBody(lc, (Block)body.accept(visitor)));
|
||||
}
|
||||
|
||||
return this;
|
||||
@ -79,8 +83,7 @@ public final class LabelNode extends LexicalContextStatement {
|
||||
|
||||
@Override
|
||||
public void toString(final StringBuilder sb) {
|
||||
label.toString(sb);
|
||||
sb.append(':');
|
||||
sb.append(labelName).append(':');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -101,22 +104,27 @@ public final class LabelNode extends LexicalContextStatement {
|
||||
if (this.body == body) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new LabelNode(this, label, body));
|
||||
return Node.replaceInLexicalContext(lc, this, new LabelNode(this, labelName, body, localVariableConversion));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the identifier representing the label name
|
||||
* Get the label name
|
||||
* @return the label
|
||||
*/
|
||||
public IdentNode getLabel() {
|
||||
return label;
|
||||
public String getLabelName() {
|
||||
return labelName;
|
||||
}
|
||||
|
||||
private LabelNode setLabel(final LexicalContext lc, final IdentNode label) {
|
||||
if (this.label == label) {
|
||||
@Override
|
||||
public LocalVariableConversion getLocalVariableConversion() {
|
||||
return localVariableConversion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LabelNode setLocalVariableConversion(final LexicalContext lc, final LocalVariableConversion localVariableConversion) {
|
||||
if(this.localVariableConversion == localVariableConversion) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new LabelNode(this, label, body));
|
||||
return Node.replaceInLexicalContext(lc, this, new LabelNode(this, labelName, body, localVariableConversion));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -27,7 +27,6 @@ package jdk.nashorn.internal.ir;
|
||||
import java.io.File;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import jdk.nashorn.internal.codegen.Label;
|
||||
import jdk.nashorn.internal.runtime.Debug;
|
||||
import jdk.nashorn.internal.runtime.Source;
|
||||
|
||||
@ -202,6 +201,19 @@ public class LexicalContext {
|
||||
return (T)popped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly apply flags to the topmost element on the stack. This is only valid to use from a
|
||||
* {@code NodeVisitor.leaveXxx()} method and only on the node being exited at the time. It is not mandatory to use,
|
||||
* as {@link #pop(LexicalContextNode)} will apply the flags automatically, but this method can be used to apply them
|
||||
* during the {@code leaveXxx()} method in case its logic depends on the value of the flags.
|
||||
* @param node the node to apply the flags to. Must be the topmost node on the stack.
|
||||
* @return the passed in node, or a modified node (if any flags were modified)
|
||||
*/
|
||||
public <T extends LexicalContextNode & Flags<T>> T applyTopFlags(final T node) {
|
||||
assert node == peek();
|
||||
return node.setFlag(this, flags[sp - 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the top element in the context
|
||||
* @return the node that was pushed last
|
||||
@ -269,6 +281,20 @@ public class LexicalContext {
|
||||
return iter.hasNext() ? iter.next() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the label node of the current block.
|
||||
* @return the label node of the current block, if it is labeled. Otherwise returns null.
|
||||
*/
|
||||
public LabelNode getCurrentBlockLabelNode() {
|
||||
assert stack[sp - 1] instanceof Block;
|
||||
if(sp < 2) {
|
||||
return null;
|
||||
}
|
||||
final LexicalContextNode parent = stack[sp - 2];
|
||||
return parent instanceof LabelNode ? (LabelNode)parent : null;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
public FunctionNode getProgram() {
|
||||
final Iterator<FunctionNode> iter = getFunctions();
|
||||
@ -374,9 +400,6 @@ public class LexicalContext {
|
||||
* @return block in which the symbol is defined, assert if no such block in context
|
||||
*/
|
||||
public Block getDefiningBlock(final Symbol symbol) {
|
||||
if (symbol.isTemp()) {
|
||||
return null;
|
||||
}
|
||||
final String name = symbol.getName();
|
||||
for (final Iterator<Block> it = getBlocks(); it.hasNext();) {
|
||||
final Block next = it.next();
|
||||
@ -393,9 +416,6 @@ public class LexicalContext {
|
||||
* @return function node in which this symbol is defined, assert if no such symbol exists in context
|
||||
*/
|
||||
public FunctionNode getDefiningFunction(final Symbol symbol) {
|
||||
if (symbol.isTemp()) {
|
||||
return null;
|
||||
}
|
||||
final String name = symbol.getName();
|
||||
for (final Iterator<LexicalContextNode> iter = new NodeIterator<>(LexicalContextNode.class); iter.hasNext();) {
|
||||
final LexicalContextNode next = iter.next();
|
||||
@ -438,7 +458,10 @@ public class LexicalContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of scopes until a given node.
|
||||
* Count the number of scopes until a given node. Note that this method is solely used to figure out the number of
|
||||
* scopes that need to be explicitly popped in order to perform a break or continue jump within the current bytecode
|
||||
* method. For this reason, the method returns 0 if it encounters a {@code SplitNode} between the current location
|
||||
* and the break/continue target.
|
||||
* @param until node to stop counting at. Must be within the current function
|
||||
* @return number of with scopes encountered in the context
|
||||
*/
|
||||
@ -450,6 +473,9 @@ public class LexicalContext {
|
||||
final LexicalContextNode node = iter.next();
|
||||
if (node == until) {
|
||||
break;
|
||||
} else if (node instanceof SplitNode) {
|
||||
// Don't bother popping scopes if we're going to do a return from a split method anyway.
|
||||
return 0;
|
||||
}
|
||||
assert !(node instanceof FunctionNode); // Can't go outside current function
|
||||
if (node instanceof WithNode || node instanceof Block && ((Block)node).needsScope()) {
|
||||
@ -488,12 +514,13 @@ public class LexicalContext {
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param labelName name of the 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
|
||||
*/
|
||||
public BreakableNode getBreakable(final IdentNode label) {
|
||||
if (label != null) {
|
||||
final LabelNode foundLabel = findLabel(label.getName());
|
||||
public BreakableNode getBreakable(final String labelName) {
|
||||
if (labelName != null) {
|
||||
final LabelNode foundLabel = findLabel(labelName);
|
||||
if (foundLabel != null) {
|
||||
// iterate to the nearest breakable to the foundLabel
|
||||
BreakableNode breakable = null;
|
||||
@ -513,12 +540,13 @@ public class LexicalContext {
|
||||
|
||||
/**
|
||||
* Find the continue target node corresponding to this label.
|
||||
* @param label label to search for, if null the closest loop node will be returned unconditionally, e.g. a while loop with no label
|
||||
* @param labelName label name to search for. If null the closest loop node will be returned unconditionally, e.g. a
|
||||
* while loop with no label
|
||||
* @return closest continue target node
|
||||
*/
|
||||
public LoopNode getContinueTo(final IdentNode label) {
|
||||
if (label != null) {
|
||||
final LabelNode foundLabel = findLabel(label.getName());
|
||||
public LoopNode getContinueTo(final String labelName) {
|
||||
if (labelName != null) {
|
||||
final LabelNode foundLabel = findLabel(labelName);
|
||||
if (foundLabel != null) {
|
||||
// iterate to the nearest loop to the foundLabel
|
||||
LoopNode loop = null;
|
||||
@ -540,7 +568,7 @@ public class LexicalContext {
|
||||
public LabelNode findLabel(final String name) {
|
||||
for (final Iterator<LabelNode> iter = new NodeIterator<>(LabelNode.class, getCurrentFunction()); iter.hasNext(); ) {
|
||||
final LabelNode next = iter.next();
|
||||
if (next.getLabel().getName().equals(name)) {
|
||||
if (next.getLabelName().equals(name)) {
|
||||
return next;
|
||||
}
|
||||
}
|
||||
@ -548,31 +576,21 @@ public class LexicalContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given label is a jump destination that lies outside a given
|
||||
* split node
|
||||
* Checks whether a given target is a jump destination that lies outside a given split node
|
||||
* @param splitNode the split node
|
||||
* @param label the label
|
||||
* @return true if label resides outside the split node
|
||||
* @param target the target node
|
||||
* @return true if target resides outside the split node
|
||||
*/
|
||||
public boolean isExternalTarget(final SplitNode splitNode, final Label label) {
|
||||
boolean targetFound = false;
|
||||
for (int i = sp - 1; i >= 0; i--) {
|
||||
public boolean isExternalTarget(final SplitNode splitNode, final BreakableNode target) {
|
||||
for (int i = sp; i-- > 0;) {
|
||||
final LexicalContextNode next = stack[i];
|
||||
if (next == splitNode) {
|
||||
return !targetFound;
|
||||
}
|
||||
|
||||
if (next instanceof BreakableNode) {
|
||||
for (final Label l : ((BreakableNode)next).getLabels()) {
|
||||
if (l == label) {
|
||||
targetFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (next == target) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
assert false : label + " was expected in lexical context " + LexicalContext.this + " but wasn't";
|
||||
return false;
|
||||
throw new AssertionError(target + " was expected in lexical context " + LexicalContext.this + " but wasn't");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -45,15 +45,4 @@ abstract class LexicalContextExpression extends Expression implements LexicalCon
|
||||
public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
|
||||
return Acceptor.accept(this, visitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the symbol and replace in lexical context if applicable
|
||||
* @param lc lexical context
|
||||
* @param symbol symbol
|
||||
* @return new node if symbol changed
|
||||
*/
|
||||
@Override
|
||||
public Expression setSymbol(final LexicalContext lc, final Symbol symbol) {
|
||||
return Node.replaceInLexicalContext(lc, this, (LexicalContextExpression)super.setSymbol(null, symbol));
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,6 +28,7 @@ package jdk.nashorn.internal.ir;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import jdk.nashorn.internal.codegen.CompileUnit;
|
||||
import jdk.nashorn.internal.codegen.types.ArrayType;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
@ -85,11 +86,6 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
|
||||
this.value = newValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAtom() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the literal value is null
|
||||
* @return true if literal value is null
|
||||
@ -99,7 +95,7 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
public Type getType(final Function<Symbol, Type> localVariableTypes) {
|
||||
return Type.typeFor(value.getClass());
|
||||
}
|
||||
|
||||
@ -226,7 +222,7 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
|
||||
* Get the literal node value
|
||||
* @return the value
|
||||
*/
|
||||
public T getValue() {
|
||||
public final T getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -279,6 +275,16 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
|
||||
public boolean isLocal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAlwaysFalse() {
|
||||
return !isTrue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAlwaysTrue() {
|
||||
return isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
@ -298,7 +304,7 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
public Type getType(final Function<Symbol, Type> localVariableTypes) {
|
||||
return Type.BOOLEAN;
|
||||
}
|
||||
|
||||
@ -361,7 +367,7 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
public Type getType(final Function<Symbol, Type> localVariableTypes) {
|
||||
return type;
|
||||
}
|
||||
|
||||
@ -485,7 +491,7 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
public Type getType(final Function<Symbol, Type> localVariableTypes) {
|
||||
return Type.OBJECT;
|
||||
}
|
||||
|
||||
@ -554,7 +560,7 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
public Type getType(final Function<Symbol, Type> localVariableTypes) {
|
||||
return Type.OBJECT;
|
||||
}
|
||||
|
||||
@ -567,7 +573,7 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
|
||||
/**
|
||||
* Array literal node class.
|
||||
*/
|
||||
public static final class ArrayLiteralNode extends LiteralNode<Expression[]> {
|
||||
public static final class ArrayLiteralNode extends LiteralNode<Expression[]> implements LexicalContextNode {
|
||||
|
||||
/** Array element type. */
|
||||
private Type elementType;
|
||||
@ -738,32 +744,36 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
|
||||
return array;
|
||||
}
|
||||
|
||||
private static Type getNarrowestElementType(final Expression[] value) {
|
||||
Type elementType = Type.INT;
|
||||
for (final Expression node : value) {
|
||||
if (node == null) {
|
||||
elementType = elementType.widest(Type.OBJECT); //no way to represent undefined as number
|
||||
/**
|
||||
* Returns the narrowest element type that is wide enough to represent all the expressions in the array.
|
||||
* @param elementExpressions the array of expressions
|
||||
* @return the narrowest element type that is wide enough to represent all the expressions in the array.
|
||||
*/
|
||||
private static Type getNarrowestElementType(final Expression[] elementExpressions) {
|
||||
Type widestElementType = Type.INT;
|
||||
for (final Expression element : elementExpressions) {
|
||||
if (element == null) {
|
||||
widestElementType = widestElementType.widest(Type.OBJECT); //no way to represent undefined as number
|
||||
break;
|
||||
}
|
||||
|
||||
assert node.getSymbol() != null; //don't run this on unresolved nodes or you are in trouble
|
||||
Type symbolType = node.getSymbol().getSymbolType();
|
||||
if (symbolType.isUnknown()) {
|
||||
symbolType = Type.OBJECT;
|
||||
Type elementType = element.getType();
|
||||
if (elementType.isUnknown()) {
|
||||
elementType = Type.OBJECT;
|
||||
}
|
||||
|
||||
if (symbolType.isBoolean()) {
|
||||
elementType = elementType.widest(Type.OBJECT);
|
||||
if (elementType.isBoolean()) {
|
||||
widestElementType = widestElementType.widest(Type.OBJECT);
|
||||
break;
|
||||
}
|
||||
|
||||
elementType = elementType.widest(symbolType);
|
||||
widestElementType = widestElementType.widest(elementType);
|
||||
|
||||
if (elementType.isObject()) {
|
||||
if (widestElementType.isObject()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return elementType;
|
||||
return widestElementType;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -792,7 +802,7 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
public Type getType(final Function<Symbol, Type> localVariableTypes) {
|
||||
return Type.typeFor(NativeArray.class);
|
||||
}
|
||||
|
||||
@ -866,16 +876,21 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
|
||||
|
||||
@Override
|
||||
public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
|
||||
return Acceptor.accept(this, visitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node accept(LexicalContext lc, NodeVisitor<? extends LexicalContext> visitor) {
|
||||
if (visitor.enterLiteralNode(this)) {
|
||||
final List<Expression> oldValue = Arrays.asList(value);
|
||||
final List<Expression> newValue = Node.accept(visitor, Expression.class, oldValue);
|
||||
return visitor.leaveLiteralNode(oldValue != newValue ? setValue(newValue) : this);
|
||||
return visitor.leaveLiteralNode(oldValue != newValue ? setValue(lc, newValue) : this);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private ArrayLiteralNode setValue(final List<Expression> value) {
|
||||
return new ArrayLiteralNode(this, value.toArray(new Expression[value.size()]));
|
||||
private ArrayLiteralNode setValue(final LexicalContext lc, final List<Expression> value) {
|
||||
return (ArrayLiteralNode)lc.replace(this, new ArrayLiteralNode(this, value.toArray(new Expression[value.size()])));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
174
nashorn/src/jdk/nashorn/internal/ir/LocalVariableConversion.java
Normal file
174
nashorn/src/jdk/nashorn/internal/ir/LocalVariableConversion.java
Normal file
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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.ir;
|
||||
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
|
||||
/**
|
||||
* Class describing one or more local variable conversions that needs to be performed on entry to a control flow join
|
||||
* point. Note that the class is named as a singular "Conversion" and not a plural "Conversions", but instances of the
|
||||
* class have a reference to the next conversion, so multiple conversions are always represented with a single instance
|
||||
* that is a head of a linked list of instances.
|
||||
* @see JoinPredecessor
|
||||
*/
|
||||
public final class LocalVariableConversion {
|
||||
private final Symbol symbol;
|
||||
// TODO: maybe introduce a type pair class? These will often be repeated.
|
||||
private final Type from;
|
||||
private final Type to;
|
||||
private final LocalVariableConversion next;
|
||||
|
||||
/**
|
||||
* Creates a new object representing a local variable conversion.
|
||||
* @param symbol the symbol representing the local variable whose value is being converted.
|
||||
* @param from the type value is being converted from.
|
||||
* @param to the type value is being converted to.
|
||||
* @param next next conversion at the same join point, if any (the conversion object implements a singly-linked
|
||||
* list of conversions).
|
||||
*/
|
||||
public LocalVariableConversion(final Symbol symbol, final Type from, final Type to, final LocalVariableConversion next) {
|
||||
this.symbol = symbol;
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type being converted from.
|
||||
* @return the type being converted from.
|
||||
*/
|
||||
public Type getFrom() {
|
||||
return from;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type being converted to.
|
||||
* @return the type being converted to.
|
||||
*/
|
||||
public Type getTo() {
|
||||
return to;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next conversion at the same join point, or null if this is the last one.
|
||||
* @return the next conversion at the same join point.
|
||||
*/
|
||||
public LocalVariableConversion getNext() {
|
||||
return next;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the symbol representing the local variable whose value is being converted.
|
||||
* @return the symbol representing the local variable whose value is being converted.
|
||||
*/
|
||||
public Symbol getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this conversion is live. A conversion is live if the symbol has a slot for the conversion's
|
||||
* {@link #getTo() to} type. If a conversion is dead, it can be omitted in code generator.
|
||||
* @return true if this conversion is live.
|
||||
*/
|
||||
public boolean isLive() {
|
||||
return symbol.hasSlotFor(to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this conversion {@link #isLive()}, or if any of its {@link #getNext()} conversions are live.
|
||||
* @return true if this conversion, or any conversion following it, are live.
|
||||
*/
|
||||
public boolean isAnyLive() {
|
||||
return isLive() || isAnyLive(next);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the passed join predecessor has {@link #isAnyLive()} conversion.
|
||||
* @param jp the join predecessor being examined.
|
||||
* @return true if the join predecessor conversion is not null and {@link #isAnyLive()}.
|
||||
*/
|
||||
public static boolean hasLiveConversion(final JoinPredecessor jp) {
|
||||
return isAnyLive(jp.getLocalVariableConversion());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the passed conversion is not null, and it {@link #isAnyLive()}.
|
||||
* @parameter conv the conversion being tested for liveness.
|
||||
* @return true if the conversion is not null and {@link #isAnyLive()}.
|
||||
*/
|
||||
private static boolean isAnyLive(final LocalVariableConversion conv) {
|
||||
return conv != null && conv.isAnyLive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString(new StringBuilder()).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a string representation of this conversion in the passed string builder.
|
||||
* @param sb the string builder in which to generate a string representation of this conversion.
|
||||
* @return the passed in string builder.
|
||||
*/
|
||||
public StringBuilder toString(final StringBuilder sb) {
|
||||
if(isLive()) {
|
||||
return toStringNext(sb.append('\u27e6'), true).append("\u27e7 ");
|
||||
}
|
||||
return next == null ? sb : next.toString(sb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a string representation of the passed conversion in the passed string builder.
|
||||
* @param conv the conversion to render in the string builder.
|
||||
* @param sb the string builder in which to generate a string representation of this conversion.
|
||||
* @return the passed in string builder.
|
||||
*/
|
||||
public static StringBuilder toString(final LocalVariableConversion conv, final StringBuilder sb) {
|
||||
return conv == null ? sb : conv.toString(sb);
|
||||
}
|
||||
|
||||
private StringBuilder toStringNext(final StringBuilder sb, final boolean first) {
|
||||
if(isLive()) {
|
||||
if(!first) {
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append(symbol.getName()).append(':').append(getTypeChar(from)).append('\u2192').append(getTypeChar(to));
|
||||
return next == null ? sb : next.toStringNext(sb, false);
|
||||
}
|
||||
return next == null ? sb : next.toStringNext(sb, first);
|
||||
}
|
||||
|
||||
private static char getTypeChar(final Type type) {
|
||||
if(type == Type.UNDEFINED) {
|
||||
return 'U';
|
||||
} else if(type.isObject()) {
|
||||
return 'O';
|
||||
} else if(type == Type.BOOLEAN) {
|
||||
return 'Z';
|
||||
}
|
||||
return type.getBytecodeStackType();
|
||||
}
|
||||
}
|
||||
@ -37,7 +37,7 @@ public abstract class LoopNode extends BreakableStatement {
|
||||
protected final Label continueLabel;
|
||||
|
||||
/** Loop test node, null if infinite */
|
||||
protected final Expression test;
|
||||
protected final JoinPredecessorExpression test;
|
||||
|
||||
/** Loop body */
|
||||
protected final Block body;
|
||||
@ -51,14 +51,13 @@ public abstract class LoopNode extends BreakableStatement {
|
||||
* @param lineNumber lineNumber
|
||||
* @param token token
|
||||
* @param finish finish
|
||||
* @param test test, or null if infinite loop
|
||||
* @param body loop body
|
||||
* @param controlFlowEscapes controlFlowEscapes
|
||||
*/
|
||||
protected LoopNode(final int lineNumber, final long token, final int finish, final Expression test, final Block body, final boolean controlFlowEscapes) {
|
||||
protected LoopNode(final int lineNumber, final long token, final int finish, final Block body, final boolean controlFlowEscapes) {
|
||||
super(lineNumber, token, finish, new Label("while_break"));
|
||||
this.continueLabel = new Label("while_continue");
|
||||
this.test = test;
|
||||
this.test = null;
|
||||
this.body = body;
|
||||
this.controlFlowEscapes = controlFlowEscapes;
|
||||
}
|
||||
@ -70,9 +69,11 @@ public abstract class LoopNode extends BreakableStatement {
|
||||
* @param test new test
|
||||
* @param body new body
|
||||
* @param controlFlowEscapes controlFlowEscapes
|
||||
* @param conversion the local variable conversion carried by this loop node.
|
||||
*/
|
||||
protected LoopNode(final LoopNode loopNode, final Expression test, final Block body, final boolean controlFlowEscapes) {
|
||||
super(loopNode);
|
||||
protected LoopNode(final LoopNode loopNode, final JoinPredecessorExpression test, final Block body,
|
||||
final boolean controlFlowEscapes, final LocalVariableConversion conversion) {
|
||||
super(loopNode, conversion);
|
||||
this.continueLabel = new Label(loopNode.continueLabel);
|
||||
this.test = test;
|
||||
this.body = body;
|
||||
@ -150,7 +151,9 @@ public abstract class LoopNode extends BreakableStatement {
|
||||
* Get the test for this for node
|
||||
* @return the test
|
||||
*/
|
||||
public abstract Expression getTest();
|
||||
public final JoinPredecessorExpression getTest() {
|
||||
return test;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the test for this for node
|
||||
@ -159,7 +162,7 @@ public abstract class LoopNode extends BreakableStatement {
|
||||
* @param test new test
|
||||
* @return same or new node depending on if test was changed
|
||||
*/
|
||||
public abstract LoopNode setTest(final LexicalContext lc, final Expression test);
|
||||
public abstract LoopNode setTest(final LexicalContext lc, final JoinPredecessorExpression test);
|
||||
|
||||
/**
|
||||
* Set the control flow escapes flag for this node.
|
||||
@ -170,5 +173,4 @@ public abstract class LoopNode extends BreakableStatement {
|
||||
* @return new loop node if changed otherwise the same
|
||||
*/
|
||||
public abstract LoopNode setControlFlowEscapes(final LexicalContext lc, final boolean controlFlowEscapes);
|
||||
|
||||
}
|
||||
|
||||
@ -27,7 +27,6 @@ package jdk.nashorn.internal.ir;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
import jdk.nashorn.internal.parser.Token;
|
||||
import jdk.nashorn.internal.parser.TokenType;
|
||||
@ -81,15 +80,6 @@ public abstract class Node implements Cloneable {
|
||||
this.finish = node.finish;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this an atom node - for example a literal or an identity
|
||||
*
|
||||
* @return true if atom
|
||||
*/
|
||||
public boolean isAtom() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this a loop node?
|
||||
*
|
||||
@ -109,31 +99,6 @@ public abstract class Node implements Cloneable {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this a self modifying assignment?
|
||||
* @return true if self modifying, e.g. a++, or a*= 17
|
||||
*/
|
||||
public boolean isSelfModifying() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns widest operation type of this operation.
|
||||
*
|
||||
* @return the widest type for this operation
|
||||
*/
|
||||
public Type getWidestOperationType() {
|
||||
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
|
||||
@ -282,21 +247,6 @@ public abstract class Node implements Cloneable {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag an expression as optimistic or not. This is a convenience wrapper
|
||||
* that is a no op of the expression cannot be optimistic
|
||||
* @param expr expression
|
||||
* @param isOptimistic is optimistic flag
|
||||
* @return the new expression, or same if unmodified state
|
||||
*/
|
||||
//SAM method in Java 8
|
||||
public static Expression setIsOptimistic(final Expression expr, final boolean isOptimistic) {
|
||||
if (expr instanceof Optimistic) {
|
||||
return (Expression)((Optimistic)expr).setIsOptimistic(isOptimistic);
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
//on change, we have to replace the entire list, that's we can't simple do ListIterator.set
|
||||
static <T extends Node> List<T> accept(final NodeVisitor<? extends LexicalContext> visitor, final Class<T> clazz, final List<T> list) {
|
||||
boolean changed = false;
|
||||
@ -319,21 +269,4 @@ public abstract class Node implements Cloneable {
|
||||
}
|
||||
return newNode;
|
||||
}
|
||||
|
||||
static final String OPT_IDENTIFIER = "%";
|
||||
|
||||
static void optimisticType(final Node node, final StringBuilder sb) {
|
||||
if (node instanceof Optimistic && ((Optimistic)node).isOptimistic()) {
|
||||
sb.append('{');
|
||||
final String desc = (((Expression)node).getType()).getDescriptor();
|
||||
sb.append(desc.charAt(desc.length() - 1) == ';' ? "O" : desc);
|
||||
if (node instanceof Optimistic && ((Optimistic)node).isOptimistic()) {
|
||||
sb.append(OPT_IDENTIFIER);
|
||||
sb.append('_');
|
||||
sb.append(((Optimistic)node).getProgramPoint());
|
||||
}
|
||||
sb.append('}');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -27,6 +27,8 @@ package jdk.nashorn.internal.ir;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.annotations.Immutable;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
|
||||
@ -65,6 +67,11 @@ public final class ObjectNode extends Expression {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType(final Function<Symbol, Type> localVariableTypes) {
|
||||
return Type.OBJECT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toString(final StringBuilder sb) {
|
||||
sb.append('{');
|
||||
|
||||
@ -69,13 +69,6 @@ public interface Optimistic {
|
||||
*/
|
||||
public boolean canBeOptimistic();
|
||||
|
||||
/**
|
||||
* Is this op optimistic, i.e. narrower than its narrowest statically provable
|
||||
* type
|
||||
* @return true if optimistic
|
||||
*/
|
||||
public boolean isOptimistic();
|
||||
|
||||
/**
|
||||
* Get the most optimistic type for this node. Typically we start out as
|
||||
* an int, and then at runtime we bump this up to number and then Object
|
||||
@ -92,21 +85,11 @@ public interface Optimistic {
|
||||
*/
|
||||
public Type getMostPessimisticType();
|
||||
|
||||
|
||||
/**
|
||||
* Tag this type override as optimistic rather than based on statically known
|
||||
* type coercion semantics
|
||||
* @param isOptimistic is this function optimistic
|
||||
* @return new optimistic function
|
||||
*/
|
||||
public Optimistic setIsOptimistic(final boolean isOptimistic);
|
||||
|
||||
/**
|
||||
* Set the override type
|
||||
*
|
||||
* @param ts temporary symbols
|
||||
* @param type the type
|
||||
* @return a node equivalent to this one except for the requested change.
|
||||
*/
|
||||
public Optimistic setType(final TemporarySymbols ts, final Type type);
|
||||
public Optimistic setType(final Type type);
|
||||
}
|
||||
|
||||
@ -24,8 +24,6 @@
|
||||
*/
|
||||
package jdk.nashorn.internal.ir;
|
||||
|
||||
import jdk.nashorn.internal.IntDeque;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@ -57,7 +55,6 @@ public class OptimisticLexicalContext extends LexicalContext {
|
||||
|
||||
/** Optimistic assumptions that could be made per function */
|
||||
private final Deque<List<Assumption>> optimisticAssumptions = new ArrayDeque<>();
|
||||
private final IntDeque splitNodes = new IntDeque();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@ -111,9 +108,6 @@ public class OptimisticLexicalContext extends LexicalContext {
|
||||
if (isEnabled) {
|
||||
if(node instanceof FunctionNode) {
|
||||
optimisticAssumptions.push(new ArrayList<Assumption>());
|
||||
splitNodes.push(0);
|
||||
} else if(node instanceof SplitNode) {
|
||||
splitNodes.getAndIncrement();
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,20 +120,9 @@ public class OptimisticLexicalContext extends LexicalContext {
|
||||
if (isEnabled) {
|
||||
if(node instanceof FunctionNode) {
|
||||
optimisticAssumptions.pop();
|
||||
assert splitNodes.peek() == 0;
|
||||
splitNodes.pop();
|
||||
} else if(node instanceof SplitNode) {
|
||||
splitNodes.decrementAndGet();
|
||||
}
|
||||
}
|
||||
return popped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the lexical context is inside a split node
|
||||
* @return true if we are traversing a SplitNode
|
||||
*/
|
||||
public boolean isInSplitNode() {
|
||||
return !splitNodes.isEmpty() && splitNodes.peek() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,16 +25,17 @@
|
||||
|
||||
package jdk.nashorn.internal.ir;
|
||||
|
||||
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import java.util.function.Function;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.annotations.Immutable;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
import jdk.nashorn.internal.parser.TokenType;
|
||||
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
|
||||
|
||||
/**
|
||||
* IR representation for a runtime call.
|
||||
@ -169,9 +170,14 @@ public class RuntimeNode extends Expression implements Optimistic {
|
||||
* @param node the node
|
||||
* @return request type
|
||||
*/
|
||||
public static Request requestFor(final Node node) {
|
||||
assert node.isComparison();
|
||||
public static Request requestFor(final Expression node) {
|
||||
switch (node.tokenType()) {
|
||||
case TYPEOF:
|
||||
return Request.TYPEOF;
|
||||
case IN:
|
||||
return Request.IN;
|
||||
case INSTANCEOF:
|
||||
return Request.INSTANCEOF;
|
||||
case EQ_STRICT:
|
||||
return Request.EQ_STRICT;
|
||||
case NE_STRICT:
|
||||
@ -409,13 +415,12 @@ public class RuntimeNode extends Expression implements Optimistic {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* Constructor used to replace a binary node with a runtime request.
|
||||
*
|
||||
* @param parent parent node from which to inherit source, token, finish and arguments
|
||||
* @param request the request
|
||||
*/
|
||||
public RuntimeNode(final BinaryNode parent, final Request request) {
|
||||
this(parent, request, parent.lhs(), parent.rhs());
|
||||
public RuntimeNode(final BinaryNode parent) {
|
||||
this(parent, Request.requestFor(parent), parent.lhs(), parent.rhs());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -455,7 +460,7 @@ public class RuntimeNode extends Expression implements Optimistic {
|
||||
* Return type for the ReferenceNode
|
||||
*/
|
||||
@Override
|
||||
public Type getType() {
|
||||
public Type getType(Function<Symbol, Type> localVariableTypes) {
|
||||
return request.getReturnType();
|
||||
}
|
||||
|
||||
@ -568,17 +573,7 @@ public class RuntimeNode extends Expression implements Optimistic {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOptimistic() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeNode setIsOptimistic(final boolean isOptimistic) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeNode setType(final TemporarySymbols ts, final Type type) {
|
||||
public RuntimeNode setType(final Type type) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,7 +25,10 @@
|
||||
|
||||
package jdk.nashorn.internal.ir;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import jdk.nashorn.internal.codegen.CompileUnit;
|
||||
import jdk.nashorn.internal.codegen.Label;
|
||||
import jdk.nashorn.internal.ir.annotations.Immutable;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
|
||||
@ -41,7 +44,9 @@ public class SplitNode extends LexicalContextStatement {
|
||||
private final CompileUnit compileUnit;
|
||||
|
||||
/** Body of split code. */
|
||||
private final Node body;
|
||||
private final Block body;
|
||||
|
||||
private Map<Label, JoinPredecessor> jumps;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@ -50,18 +55,19 @@ public class SplitNode extends LexicalContextStatement {
|
||||
* @param body body of split code
|
||||
* @param compileUnit compile unit to use for the body
|
||||
*/
|
||||
public SplitNode(final String name, final Node body, final CompileUnit compileUnit) {
|
||||
super(-1, body.getToken(), body.getFinish());
|
||||
public SplitNode(final String name, final Block body, final CompileUnit compileUnit) {
|
||||
super(body.getFirstStatementLineNumber(), body.getToken(), body.getFinish());
|
||||
this.name = name;
|
||||
this.body = body;
|
||||
this.compileUnit = compileUnit;
|
||||
}
|
||||
|
||||
private SplitNode(final SplitNode splitNode, final Node body) {
|
||||
private SplitNode(final SplitNode splitNode, final Block body) {
|
||||
super(splitNode);
|
||||
this.name = splitNode.name;
|
||||
this.body = body;
|
||||
this.compileUnit = splitNode.compileUnit;
|
||||
this.jumps = splitNode.jumps;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -72,7 +78,7 @@ public class SplitNode extends LexicalContextStatement {
|
||||
return body;
|
||||
}
|
||||
|
||||
private SplitNode setBody(final LexicalContext lc, final Node body) {
|
||||
private SplitNode setBody(final LexicalContext lc, final Block body) {
|
||||
if (this.body == body) {
|
||||
return this;
|
||||
}
|
||||
@ -82,7 +88,7 @@ public class SplitNode extends LexicalContextStatement {
|
||||
@Override
|
||||
public Node accept(final LexicalContext lc, final NodeVisitor<? extends LexicalContext> visitor) {
|
||||
if (visitor.enterSplitNode(this)) {
|
||||
return visitor.leaveSplitNode(setBody(lc, body.accept(visitor)));
|
||||
return visitor.leaveSplitNode(setBody(lc, (Block)body.accept(visitor)));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@ -111,4 +117,25 @@ public class SplitNode extends LexicalContextStatement {
|
||||
return compileUnit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a jump that crosses this split node's boundary (it originates within the split node, and goes to a target
|
||||
* outside of it).
|
||||
* @param jumpOrigin the join predecessor that's the origin of the jump
|
||||
* @param targetLabel the label that's the target of the jump.
|
||||
*/
|
||||
public void addJump(final JoinPredecessor jumpOrigin, final Label targetLabel) {
|
||||
if(jumps == null) {
|
||||
jumps = new HashMap<>();
|
||||
}
|
||||
jumps.put(targetLabel, jumpOrigin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the jump origin within this split node for a target.
|
||||
* @param targetLabel the target for which a jump origin is sought.
|
||||
* @return the jump origin, or null.
|
||||
*/
|
||||
public JoinPredecessor getJumpOrigin(final Label targetLabel) {
|
||||
return jumps == null ? null : jumps.get(targetLabel);
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,11 +66,12 @@ public final class SwitchNode extends BreakableStatement {
|
||||
this.defaultCaseIndex = defaultCase == null ? -1 : cases.indexOf(defaultCase);
|
||||
}
|
||||
|
||||
private SwitchNode(final SwitchNode switchNode, final Expression expression, final List<CaseNode> cases, final int defaultCase) {
|
||||
super(switchNode);
|
||||
private SwitchNode(final SwitchNode switchNode, final Expression expression, final List<CaseNode> cases,
|
||||
final int defaultCaseIndex, final LocalVariableConversion conversion) {
|
||||
super(switchNode, conversion);
|
||||
this.expression = expression;
|
||||
this.cases = cases;
|
||||
this.defaultCaseIndex = defaultCase;
|
||||
this.defaultCaseIndex = defaultCaseIndex;
|
||||
this.tag = switchNode.getTag(); //TODO are symbols inhereted as references?
|
||||
}
|
||||
|
||||
@ -78,9 +79,9 @@ public final class SwitchNode extends BreakableStatement {
|
||||
public Node ensureUniqueLabels(final LexicalContext lc) {
|
||||
final List<CaseNode> newCases = new ArrayList<>();
|
||||
for (final CaseNode caseNode : cases) {
|
||||
newCases.add(new CaseNode(caseNode, caseNode.getTest(), caseNode.getBody()));
|
||||
newCases.add(new CaseNode(caseNode, caseNode.getTest(), caseNode.getBody(), caseNode.getLocalVariableConversion()));
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, newCases, defaultCaseIndex));
|
||||
return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, newCases, defaultCaseIndex, conversion));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -138,7 +139,7 @@ public final class SwitchNode extends BreakableStatement {
|
||||
* by NodeVisitors who perform operations on every case node
|
||||
* @param lc lexical context
|
||||
* @param cases list of cases
|
||||
* @return new switcy node or same if no state was changed
|
||||
* @return new switch node or same if no state was changed
|
||||
*/
|
||||
public SwitchNode setCases(final LexicalContext lc, final List<CaseNode> cases) {
|
||||
return setCases(lc, cases, defaultCaseIndex);
|
||||
@ -148,7 +149,7 @@ public final class SwitchNode extends BreakableStatement {
|
||||
if (this.cases == cases) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex));
|
||||
return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -180,7 +181,7 @@ public final class SwitchNode extends BreakableStatement {
|
||||
if (this.expression == expression) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex));
|
||||
return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -200,5 +201,27 @@ public final class SwitchNode extends BreakableStatement {
|
||||
public void setTag(final Symbol tag) {
|
||||
this.tag = tag;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if all cases of this switch statement are 32-bit signed integer constants.
|
||||
* @return true if all cases of this switch statement are 32-bit signed integer constants.
|
||||
*/
|
||||
public boolean isInteger() {
|
||||
for (final CaseNode caseNode : cases) {
|
||||
final Expression test = caseNode.getTest();
|
||||
if (test != null && !isIntegerLiteral(test)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
JoinPredecessor setLocalVariableConversionChanged(final LexicalContext lc, final LocalVariableConversion conversion) {
|
||||
return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex, conversion));
|
||||
}
|
||||
|
||||
private static boolean isIntegerLiteral(final Expression expr) {
|
||||
return expr instanceof LiteralNode && ((LiteralNode<?>)expr).getValue() instanceof Integer;
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,58 +25,64 @@
|
||||
|
||||
package jdk.nashorn.internal.ir;
|
||||
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
|
||||
|
||||
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;
|
||||
import jdk.nashorn.internal.runtime.options.Options;
|
||||
|
||||
/**
|
||||
* Maps a name to specific data.
|
||||
* Symbol is a symbolic address for a value ("variable" if you wish). Identifiers in JavaScript source, as well as
|
||||
* certain synthetic variables created by the compiler are represented by Symbol objects. Symbols can address either
|
||||
* local variable slots in bytecode ("slotted symbol"), or properties in scope objects ("scoped symbol"). A symbol can
|
||||
* also end up being defined but then not used during symbol assignment calculations; such symbol will be neither
|
||||
* scoped, nor slotted; it represents a dead variable (it might be written to, but is never read). Finally, a symbol can
|
||||
* be both slotted and in scope. This special case can only occur with bytecode method parameters. They all come in as
|
||||
* slotted, but if they are used by a nested function (or eval) then they will be copied into the scope object, and used
|
||||
* from there onwards. Two further special cases are parameters stored in {@code NativeArguments} objects and parameters
|
||||
* stored in {@code Object[]} parameter to variable-arity functions. Those use the {@code #getFieldIndex()} property to
|
||||
* refer to their location.
|
||||
*/
|
||||
|
||||
public final class Symbol implements Comparable<Symbol> {
|
||||
/** Symbol kinds. Kind ordered by precedence. */
|
||||
public static final int IS_TEMP = 1;
|
||||
/** Is this Global */
|
||||
public static final int IS_GLOBAL = 2;
|
||||
public static final int IS_GLOBAL = 1;
|
||||
/** Is this a variable */
|
||||
public static final int IS_VAR = 3;
|
||||
public static final int IS_VAR = 2;
|
||||
/** Is this a parameter */
|
||||
public static final int IS_PARAM = 4;
|
||||
public static final int IS_PARAM = 3;
|
||||
/** Is this a constant */
|
||||
public static final int IS_CONSTANT = 5;
|
||||
public static final int IS_CONSTANT = 4;
|
||||
/** Mask for kind flags */
|
||||
public static final int KINDMASK = (1 << 3) - 1; // Kinds are represented by lower three bits
|
||||
|
||||
/** Is this scope */
|
||||
public static final int IS_SCOPE = 1 << 4;
|
||||
/** Is this symbol in scope */
|
||||
public static final int IS_SCOPE = 1 << 3;
|
||||
/** Is this a this symbol */
|
||||
public static final int IS_THIS = 1 << 5;
|
||||
/** Can this symbol ever be undefined */
|
||||
public static final int CAN_BE_UNDEFINED = 1 << 6;
|
||||
/** Is this symbol always defined? */
|
||||
public static final int IS_ALWAYS_DEFINED = 1 << 7;
|
||||
public static final int IS_THIS = 1 << 4;
|
||||
/** Is this a let */
|
||||
public static final int IS_LET = 1 << 8;
|
||||
public static final int IS_LET = 1 << 5;
|
||||
/** Is this an internal symbol, never represented explicitly in source code */
|
||||
public static final int IS_INTERNAL = 1 << 9;
|
||||
public static final int IS_INTERNAL = 1 << 6;
|
||||
/** Is this a function self-reference symbol */
|
||||
public static final int IS_FUNCTION_SELF = 1 << 10;
|
||||
/** Is this a specialized param, i.e. known type base on runtime callsite? */
|
||||
public static final int IS_SPECIALIZED_PARAM = 1 << 11;
|
||||
/** Is this symbol a shared temporary? */
|
||||
public static final int IS_SHARED = 1 << 12;
|
||||
public static final int IS_FUNCTION_SELF = 1 << 7;
|
||||
/** Is this a function declaration? */
|
||||
public static final int IS_FUNCTION_DECLARATION = 1 << 13;
|
||||
public static final int IS_FUNCTION_DECLARATION = 1 << 8;
|
||||
/** Is this a program level symbol? */
|
||||
public static final int IS_PROGRAM_LEVEL = 1 << 14;
|
||||
public static final int IS_PROGRAM_LEVEL = 1 << 9;
|
||||
/** Are this symbols' values stored in local variable slots? */
|
||||
public static final int HAS_SLOT = 1 << 10;
|
||||
/** Is this symbol known to store an int value ? */
|
||||
public static final int HAS_INT_VALUE = 1 << 11;
|
||||
/** Is this symbol known to store a long value ? */
|
||||
public static final int HAS_LONG_VALUE = 1 << 12;
|
||||
/** Is this symbol known to store a double value ? */
|
||||
public static final int HAS_DOUBLE_VALUE = 1 << 13;
|
||||
/** Is this symbol known to store an object value ? */
|
||||
public static final int HAS_OBJECT_VALUE = 1 << 14;
|
||||
|
||||
/** Null or name identifying symbol. */
|
||||
private final String name;
|
||||
@ -84,11 +90,9 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
/** Symbol flags. */
|
||||
private int flags;
|
||||
|
||||
/** Type of symbol. */
|
||||
private Type type;
|
||||
|
||||
/** Local variable slot. -1 indicates external property. */
|
||||
private int slot;
|
||||
/** First bytecode method local variable slot for storing the value(s) of this variable. -1 indicates the variable
|
||||
* is not stored in local variable slots or it is not yet known. */
|
||||
private int firstSlot = -1;
|
||||
|
||||
/** Field number in scope or property; array index in varargs when not using arguments object. */
|
||||
private int fieldIndex;
|
||||
@ -96,9 +100,6 @@ 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;
|
||||
@ -132,18 +133,15 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
*
|
||||
* @param name name of symbol
|
||||
* @param flags symbol flags
|
||||
* @param type type of this symbol
|
||||
* @param slot bytecode slot for this symbol
|
||||
*/
|
||||
protected Symbol(final String name, final int flags, final Type type, final int slot) {
|
||||
protected Symbol(final String name, final int flags, final int slot) {
|
||||
this.name = name;
|
||||
this.flags = flags;
|
||||
this.type = type;
|
||||
this.slot = slot;
|
||||
this.firstSlot = slot;
|
||||
this.fieldIndex = -1;
|
||||
this.range = Range.createUnknownRange();
|
||||
if(shouldTrace()) {
|
||||
trace("CREATE SYMBOL " + type);
|
||||
trace("CREATE SYMBOL " + name);
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,29 +152,7 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
* @param flags symbol flags
|
||||
*/
|
||||
public Symbol(final String name, final int flags) {
|
||||
this(name, flags, Type.UNKNOWN, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param name name of symbol
|
||||
* @param flags symbol flags
|
||||
* @param type type of this symbol
|
||||
*/
|
||||
public Symbol(final String name, final int flags, final Type type) {
|
||||
this(name, flags, type, -1);
|
||||
}
|
||||
|
||||
private Symbol(final Symbol base, final String name, final int flags) {
|
||||
this.flags = flags;
|
||||
this.name = name;
|
||||
|
||||
this.fieldIndex = base.fieldIndex;
|
||||
this.slot = base.slot;
|
||||
this.type = base.type;
|
||||
this.useCount = base.useCount;
|
||||
this.range = base.range;
|
||||
this(name, flags, -1);
|
||||
}
|
||||
|
||||
private static String align(final String string, final int max) {
|
||||
@ -189,17 +165,6 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type for this symbol. Normally, if there is no type override,
|
||||
* this is where any type for any node is stored. If the node has a TypeOverride,
|
||||
* it may override this, e.g. when asking for a scoped field as a double
|
||||
*
|
||||
* @return symbol type
|
||||
*/
|
||||
public final Type getSymbolType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Debugging .
|
||||
*
|
||||
@ -211,14 +176,10 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
|
||||
sb.append(align(name, 20)).
|
||||
append(": ").
|
||||
append(align(type.toString(), 10)).
|
||||
append(", ").
|
||||
append(align(slot == -1 ? "none" : "" + slot, 10));
|
||||
append(align(firstSlot == -1 ? "none" : "" + firstSlot, 10));
|
||||
|
||||
switch (flags & KINDMASK) {
|
||||
case IS_TEMP:
|
||||
sb.append(" temp");
|
||||
break;
|
||||
case IS_GLOBAL:
|
||||
sb.append(" global");
|
||||
break;
|
||||
@ -251,10 +212,6 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
sb.append(" this");
|
||||
}
|
||||
|
||||
if (!canBeUndefined()) {
|
||||
sb.append(" def'd");
|
||||
}
|
||||
|
||||
if (isProgramLevel()) {
|
||||
sb.append(" program");
|
||||
}
|
||||
@ -281,7 +238,12 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
* @return the symbol
|
||||
*/
|
||||
public Symbol setNeedsSlot(final boolean needsSlot) {
|
||||
setSlot(needsSlot ? 0 : -1);
|
||||
if(needsSlot) {
|
||||
assert !isScope();
|
||||
flags |= HAS_SLOT;
|
||||
} else {
|
||||
flags &= ~HAS_SLOT;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -291,7 +253,14 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
* @return Number of slots.
|
||||
*/
|
||||
public int slotCount() {
|
||||
return type.isCategory2() ? 2 : 1;
|
||||
return ((flags & HAS_INT_VALUE) == 0 ? 0 : 1) +
|
||||
((flags & HAS_LONG_VALUE) == 0 ? 0 : 2) +
|
||||
((flags & HAS_DOUBLE_VALUE) == 0 ? 0 : 2) +
|
||||
((flags & HAS_OBJECT_VALUE) == 0 ? 0 : 1);
|
||||
}
|
||||
|
||||
private boolean isSlotted() {
|
||||
return firstSlot != -1 && ((flags & HAS_SLOT) != 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -299,17 +268,18 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(name).
|
||||
append(' ').
|
||||
append('(').
|
||||
append(getSymbolType().getTypeClass().getSimpleName()).
|
||||
append(')');
|
||||
append(' ');
|
||||
|
||||
if (hasSlot()) {
|
||||
sb.append(' ').
|
||||
append('(').
|
||||
append("slot=").
|
||||
append(slot).
|
||||
append(')');
|
||||
append(firstSlot).append(' ');
|
||||
if((flags & HAS_INT_VALUE) != 0) { sb.append('I'); }
|
||||
if((flags & HAS_LONG_VALUE) != 0) { sb.append('J'); }
|
||||
if((flags & HAS_DOUBLE_VALUE) != 0) { sb.append('D'); }
|
||||
if((flags & HAS_OBJECT_VALUE) != 0) { sb.append('O'); }
|
||||
sb.append(')');
|
||||
}
|
||||
|
||||
if (isScope()) {
|
||||
@ -329,21 +299,31 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this symbol have an allocated bytecode slot. If not, it is scope
|
||||
* and must be loaded from memory upon access
|
||||
* Does this symbol have an allocated bytecode slot? Note that having an allocated bytecode slot doesn't necessarily
|
||||
* mean the symbol's value will be stored in it. Namely, a function parameter can have a bytecode slot, but if it is
|
||||
* in scope, then the bytecode slot will not be used. See {@link #isBytecodeLocal()}.
|
||||
*
|
||||
* @return true if this symbol has a local bytecode slot
|
||||
*/
|
||||
public boolean hasSlot() {
|
||||
return slot >= 0;
|
||||
return (flags & HAS_SLOT) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a temporary symbol
|
||||
* @return true if temporary
|
||||
* Is this symbol a local variable stored in bytecode local variable slots? This is true for a slotted variable that
|
||||
* is not in scope. (E.g. a parameter that is in scope is slotted, but it will not be a local variable).
|
||||
* @return true if this symbol is using bytecode local slots for its storage.
|
||||
*/
|
||||
public boolean isTemp() {
|
||||
return (flags & KINDMASK) == IS_TEMP;
|
||||
public boolean isBytecodeLocal() {
|
||||
return hasSlot() && !isScope();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this symbol is dead (it is a local variable that is statically proven to never be read in any type).
|
||||
* @return true if this symbol is dead
|
||||
*/
|
||||
public boolean isDead() {
|
||||
return (flags & (HAS_SLOT | IS_SCOPE)) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -354,15 +334,7 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
*/
|
||||
public boolean isScope() {
|
||||
assert (flags & KINDMASK) != IS_GLOBAL || (flags & IS_SCOPE) == IS_SCOPE : "global without scope flag";
|
||||
return (flags & IS_SCOPE) == IS_SCOPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this symbol is a temporary that is being shared across expressions.
|
||||
* @return true if this symbol is a temporary that is being shared across expressions.
|
||||
*/
|
||||
public boolean isShared() {
|
||||
return (flags & IS_SHARED) == IS_SHARED;
|
||||
return (flags & IS_SCOPE) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -370,17 +342,7 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
* @return true if a function declaration
|
||||
*/
|
||||
public boolean isFunctionDeclaration() {
|
||||
return (flags & IS_FUNCTION_DECLARATION) == IS_FUNCTION_DECLARATION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an unshared copy of a symbol. The symbol must be currently shared.
|
||||
* @param newName the name for the new symbol.
|
||||
* @return a new, unshared symbol.
|
||||
*/
|
||||
public Symbol createUnshared(final String newName) {
|
||||
assert isShared();
|
||||
return new Symbol(this, newName, flags & ~IS_SHARED);
|
||||
return (flags & IS_FUNCTION_DECLARATION) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -392,28 +354,14 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
if(shouldTrace()) {
|
||||
trace("SET IS SCOPE");
|
||||
}
|
||||
assert !isShared();
|
||||
flags |= IS_SCOPE;
|
||||
if(!isParam()) {
|
||||
flags &= ~HAS_SLOT;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this symbol as one being shared by multiple expressions. The symbol must be a temporary.
|
||||
* @return the symbol
|
||||
*/
|
||||
public Symbol setIsShared() {
|
||||
if (!isShared()) {
|
||||
assert isTemp();
|
||||
if(shouldTrace()) {
|
||||
trace("SET IS SHARED");
|
||||
}
|
||||
flags |= IS_SHARED;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mark this symbol as a function declaration.
|
||||
*/
|
||||
@ -450,77 +398,12 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
return (flags & KINDMASK) == IS_PARAM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this symbol is always defined, which overrides all canBeUndefined tags
|
||||
* @return true if always defined
|
||||
*/
|
||||
public boolean isAlwaysDefined() {
|
||||
return isParam() || (flags & IS_ALWAYS_DEFINED) == IS_ALWAYS_DEFINED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a program (script) level definition
|
||||
* @return true if program level
|
||||
*/
|
||||
public boolean isProgramLevel() {
|
||||
return (flags & IS_PROGRAM_LEVEL) == IS_PROGRAM_LEVEL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the range for this symbol
|
||||
* @return range for symbol
|
||||
*/
|
||||
public Range getRange() {
|
||||
return range;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the range for this symbol
|
||||
* @param range range
|
||||
* @return the symbol
|
||||
*/
|
||||
public Symbol setRange(final Range range) {
|
||||
this.range = range;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this symbol represents a return value with a known non-generic type.
|
||||
* @return true if specialized return value
|
||||
*/
|
||||
public boolean isNonGenericReturn() {
|
||||
return getName().equals(RETURN.symbolName()) && type != Type.OBJECT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this symbol is a function parameter of known
|
||||
* narrowest type
|
||||
* @return true if parameter
|
||||
*/
|
||||
public boolean isSpecializedParam() {
|
||||
return (flags & IS_SPECIALIZED_PARAM) == IS_SPECIALIZED_PARAM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this symbol can ever be undefined
|
||||
* @return true if can be undefined
|
||||
*/
|
||||
public boolean canBeUndefined() {
|
||||
return (flags & CAN_BE_UNDEFINED) == CAN_BE_UNDEFINED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag this symbol as potentially undefined in parts of the program
|
||||
* @return the symbol
|
||||
*/
|
||||
public Symbol setCanBeUndefined() {
|
||||
if (isAlwaysDefined()) {
|
||||
return this;
|
||||
} else if (!canBeUndefined()) {
|
||||
assert !isShared();
|
||||
flags |= CAN_BE_UNDEFINED;
|
||||
}
|
||||
return this;
|
||||
return (flags & IS_PROGRAM_LEVEL) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -553,7 +436,7 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
* @return true if let
|
||||
*/
|
||||
public boolean isLet() {
|
||||
return (flags & IS_LET) == IS_LET;
|
||||
return (flags & IS_LET) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -561,7 +444,6 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
*/
|
||||
public void setIsLet() {
|
||||
if (!isLet()) {
|
||||
assert !isShared();
|
||||
flags |= IS_LET;
|
||||
}
|
||||
}
|
||||
@ -571,7 +453,7 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
* @return true if this symbol as a function's self-referencing symbol.
|
||||
*/
|
||||
public boolean isFunctionSelf() {
|
||||
return (flags & IS_FUNCTION_SELF) == IS_FUNCTION_SELF;
|
||||
return (flags & IS_FUNCTION_SELF) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -594,7 +476,6 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
*/
|
||||
public Symbol setFieldIndex(final int fieldIndex) {
|
||||
if (this.fieldIndex != fieldIndex) {
|
||||
assert !isShared();
|
||||
this.fieldIndex = fieldIndex;
|
||||
}
|
||||
return this;
|
||||
@ -615,7 +496,6 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
*/
|
||||
public Symbol setFlags(final int flags) {
|
||||
if (this.flags != flags) {
|
||||
assert !isShared() : this;
|
||||
this.flags = flags;
|
||||
}
|
||||
return this;
|
||||
@ -628,7 +508,6 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
*/
|
||||
public Symbol setFlag(final int flag) {
|
||||
if ((this.flags & flag) == 0) {
|
||||
assert !isShared() : this;
|
||||
this.flags |= flag;
|
||||
}
|
||||
return this;
|
||||
@ -641,7 +520,6 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
*/
|
||||
public Symbol clearFlag(final int flag) {
|
||||
if ((this.flags & flag) != 0) {
|
||||
assert !isShared() : this;
|
||||
this.flags &= ~flag;
|
||||
}
|
||||
return this;
|
||||
@ -656,11 +534,73 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the byte code slot for this symbol
|
||||
* @return byte code slot, or -1 if no slot allocated/possible
|
||||
* Get the index of the first bytecode slot for this symbol
|
||||
* @return byte code slot
|
||||
*/
|
||||
public int getSlot() {
|
||||
return slot;
|
||||
public int getFirstSlot() {
|
||||
assert isSlotted();
|
||||
return firstSlot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index of the bytecode slot for this symbol for storing a value of the specified type.
|
||||
* @param type the requested type
|
||||
* @return byte code slot
|
||||
*/
|
||||
public int getSlot(final Type type) {
|
||||
assert isSlotted();
|
||||
int typeSlot = firstSlot;
|
||||
if(type.isBoolean() || type.isInteger()) {
|
||||
assert (flags & HAS_INT_VALUE) != 0;
|
||||
return typeSlot;
|
||||
}
|
||||
typeSlot += ((flags & HAS_INT_VALUE) == 0 ? 0 : 1);
|
||||
if(type.isLong()) {
|
||||
assert (flags & HAS_LONG_VALUE) != 0;
|
||||
return typeSlot;
|
||||
}
|
||||
typeSlot += ((flags & HAS_LONG_VALUE) == 0 ? 0 : 2);
|
||||
if(type.isNumber()) {
|
||||
assert (flags & HAS_DOUBLE_VALUE) != 0;
|
||||
return typeSlot;
|
||||
}
|
||||
assert type.isObject();
|
||||
assert (flags & HAS_OBJECT_VALUE) != 0 : name;
|
||||
return typeSlot + ((flags & HAS_DOUBLE_VALUE) == 0 ? 0 : 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this symbol has a local variable slot for storing a value of specific type.
|
||||
* @param type the type
|
||||
* @return true if this symbol has a local variable slot for storing a value of specific type.
|
||||
*/
|
||||
public boolean hasSlotFor(final Type type) {
|
||||
if(type.isBoolean() || type.isInteger()) {
|
||||
return (flags & HAS_INT_VALUE) != 0;
|
||||
} else if(type.isLong()) {
|
||||
return (flags & HAS_LONG_VALUE) != 0;
|
||||
} else if(type.isNumber()) {
|
||||
return (flags & HAS_DOUBLE_VALUE) != 0;
|
||||
}
|
||||
assert type.isObject();
|
||||
return (flags & HAS_OBJECT_VALUE) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks this symbol as having a local variable slot for storing a value of specific type.
|
||||
* @param type the type
|
||||
*/
|
||||
public void setHasSlotFor(final Type type) {
|
||||
if(type.isBoolean() || type.isInteger()) {
|
||||
setFlag(HAS_INT_VALUE);
|
||||
} else if(type.isLong()) {
|
||||
setFlag(HAS_LONG_VALUE);
|
||||
} else if(type.isNumber()) {
|
||||
setFlag(HAS_DOUBLE_VALUE);
|
||||
} else {
|
||||
assert type.isObject();
|
||||
setFlag(HAS_OBJECT_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -682,88 +622,16 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
|
||||
/**
|
||||
* Set the bytecode slot for this symbol
|
||||
* @param slot valid bytecode slot, or -1 if not available
|
||||
* @param firstSlot valid bytecode slot
|
||||
* @return the symbol
|
||||
*/
|
||||
public Symbol setSlot(final int slot) {
|
||||
if (slot != this.slot) {
|
||||
assert !isShared();
|
||||
public Symbol setFirstSlot(final int firstSlot) {
|
||||
assert firstSlot >= 0 && firstSlot <= 65535;
|
||||
if (firstSlot != this.firstSlot) {
|
||||
if(shouldTrace()) {
|
||||
trace("SET SLOT " + slot);
|
||||
trace("SET SLOT " + firstSlot);
|
||||
}
|
||||
this.slot = slot;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a specific subclass of Object to the symbol
|
||||
*
|
||||
* @param type the type
|
||||
* @return the symbol
|
||||
*/
|
||||
public Symbol setType(final Class<?> type) {
|
||||
assert !type.isPrimitive() && !Number.class.isAssignableFrom(type) : "Class<?> types can only be subclasses of object";
|
||||
setType(Type.typeFor(type));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a type to the symbol
|
||||
*
|
||||
* @param type the type
|
||||
* @return the symbol
|
||||
*/
|
||||
public Symbol setType(final Type type) {
|
||||
setTypeOverride(Type.widest(this.type, type));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if calling {@link #setType(Type)} on this symbol would effectively change its type.
|
||||
* @param newType the new type to test for
|
||||
* @return true if setting this symbols type to a new value would effectively change its type.
|
||||
*/
|
||||
public boolean wouldChangeType(final Type newType) {
|
||||
return Type.widest(this.type, newType) != this.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only use this if you know about an existing type
|
||||
* constraint - otherwise a type can only be
|
||||
* widened
|
||||
*
|
||||
* @param type the type
|
||||
* @return the symbol
|
||||
*/
|
||||
public Symbol setTypeOverride(final Type type) {
|
||||
final Type old = this.type;
|
||||
if (old != type) {
|
||||
assert !isShared() : this + " is a shared symbol and cannot have its type overridden to " + type;
|
||||
if(shouldTrace()) {
|
||||
trace("TYPE CHANGE: " + old + "=>" + type + " == " + type);
|
||||
}
|
||||
this.type = type;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type of the symbol to the specified type. If the type would be changed, but this symbol is a shared
|
||||
* temporary, it will instead return a different temporary symbol of the requested type from the passed temporary
|
||||
* symbols. That way, it never mutates the type of a shared temporary.
|
||||
* @param type the new type for the symbol
|
||||
* @param ts a holder of temporary symbols
|
||||
* @return either this symbol, or a different symbol if this symbol is a shared temporary and it type would have to
|
||||
* be changed.
|
||||
*/
|
||||
public Symbol setTypeOverrideShared(final Type type, final TemporarySymbols ts) {
|
||||
if (getSymbolType() != type) {
|
||||
if (isShared()) {
|
||||
assert !hasSlot();
|
||||
return ts.getTypedTemporarySymbol(type);
|
||||
}
|
||||
setTypeOverride(type);
|
||||
this.firstSlot = firstSlot;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -1,169 +0,0 @@
|
||||
/*
|
||||
* 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.ir;
|
||||
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.TEMP_PREFIX;
|
||||
import static jdk.nashorn.internal.ir.Symbol.IS_TEMP;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
|
||||
/**
|
||||
* Class that holds reusable temporary symbols by type.
|
||||
*
|
||||
*/
|
||||
public class TemporarySymbols {
|
||||
private static final String prefix = TEMP_PREFIX.symbolName() + "$";
|
||||
|
||||
private int totalSymbolCount;
|
||||
private final Map<Type, TypedTemporarySymbols> temporarySymbolsByType = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Associates a temporary symbol of a given type with a node, if the node doesn't already have any symbol.
|
||||
* @param lc the current lexical context
|
||||
* @param type the type of the temporary symbol
|
||||
* @param node the node
|
||||
* @return the node that is guaranteed to have a symbol.
|
||||
*/
|
||||
public Expression ensureSymbol(final LexicalContext lc, final Type type, final Expression node) {
|
||||
final Symbol symbol = node.getSymbol();
|
||||
if (symbol != null) {
|
||||
return node;
|
||||
}
|
||||
return node.setSymbol(lc, getTypedTemporarySymbol(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a type, returns a temporary symbol of that type.
|
||||
* @param type the required type of the symbol.
|
||||
* @return a temporary symbol of the required type.
|
||||
*/
|
||||
public Symbol getTypedTemporarySymbol(final Type type) {
|
||||
return getTypedTemporarySymbols(type).getTemporarySymbol(type);
|
||||
}
|
||||
|
||||
private TypedTemporarySymbols getTypedTemporarySymbols(final Type type) {
|
||||
TypedTemporarySymbols temporarySymbols = temporarySymbolsByType.get(type);
|
||||
if(temporarySymbols == null) {
|
||||
temporarySymbols = new TypedTemporarySymbols();
|
||||
temporarySymbolsByType.put(type, temporarySymbols);
|
||||
}
|
||||
return temporarySymbols;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called to signal to this object that all the symbols it holds can be reused now.
|
||||
*/
|
||||
public void reuse() {
|
||||
for(TypedTemporarySymbols ts: temporarySymbolsByType.values()) {
|
||||
ts.reuse();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a shared symbol, creates an unshared copy of it with a unique name.
|
||||
* @param symbol the shared symbol
|
||||
* @return the unshared, uniquely named copy of the symbol
|
||||
*/
|
||||
public Symbol createUnshared(Symbol symbol) {
|
||||
return symbol.createUnshared(getUniqueName());
|
||||
}
|
||||
|
||||
private String getUniqueName() {
|
||||
return prefix + (++totalSymbolCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of symbols this object created during its lifetime.
|
||||
* @return the total number of symbols this object created during its lifetime.
|
||||
*/
|
||||
public int getTotalSymbolCount() {
|
||||
return totalSymbolCount;
|
||||
}
|
||||
|
||||
private class TypedTemporarySymbols {
|
||||
private Symbol[] symbols = new Symbol[16];
|
||||
private int nextFreeSymbol = 0;
|
||||
private int symbolCount = 0;
|
||||
|
||||
Symbol getTemporarySymbol(final Type type) {
|
||||
while(nextFreeSymbol < symbolCount) {
|
||||
final Symbol nextSymbol = symbols[nextFreeSymbol];
|
||||
assert nextSymbol != null;
|
||||
// If it has a slot, we can't reuse it.
|
||||
if(!nextSymbol.hasSlot()) {
|
||||
final Type symbolType = nextSymbol.getSymbolType();
|
||||
if(symbolType == type) {
|
||||
assert nextSymbol.isTemp();
|
||||
assert !nextSymbol.isScope();
|
||||
// If types match, we can reuse it.
|
||||
nextSymbol.setIsShared();
|
||||
nextFreeSymbol++;
|
||||
return nextSymbol;
|
||||
}
|
||||
// If its type changed, but it doesn't have a slot then move it to its new home according to its
|
||||
// new type.
|
||||
getTypedTemporarySymbols(symbolType).addSymbol(nextSymbol);
|
||||
}
|
||||
// If we can move another symbol into its place, do that and repeat the analysis for this symbol.
|
||||
--symbolCount;
|
||||
if(symbolCount != nextFreeSymbol) {
|
||||
final Symbol lastFreeSymbol = symbols[symbolCount];
|
||||
symbols[nextFreeSymbol] = lastFreeSymbol;
|
||||
}
|
||||
symbols[symbolCount] = null;
|
||||
}
|
||||
return createNewSymbol(type);
|
||||
}
|
||||
|
||||
private Symbol createNewSymbol(final Type type) {
|
||||
ensureCapacity();
|
||||
final Symbol symbol = symbols[nextFreeSymbol] = new Symbol(getUniqueName(), IS_TEMP, type);
|
||||
nextFreeSymbol++;
|
||||
symbolCount++;
|
||||
return symbol;
|
||||
}
|
||||
|
||||
private void addSymbol(Symbol symbol) {
|
||||
ensureCapacity();
|
||||
symbols[symbolCount++] = symbol;
|
||||
}
|
||||
|
||||
void reuse() {
|
||||
nextFreeSymbol = 0;
|
||||
}
|
||||
|
||||
private void ensureCapacity() {
|
||||
if(symbolCount == symbols.length) {
|
||||
final Symbol[] newSymbols = new Symbol[symbolCount * 2];
|
||||
System.arraycopy(symbols, 0, newSymbols, 0, symbolCount);
|
||||
symbols = newSymbols;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -25,20 +25,20 @@
|
||||
|
||||
package jdk.nashorn.internal.ir;
|
||||
|
||||
import java.util.function.Function;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.annotations.Immutable;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
|
||||
/**
|
||||
* TernaryNode nodes represent three operand operations (?:).
|
||||
* TernaryNode represent the ternary operator {@code ?:}. Note that for control-flow calculation reasons its branch
|
||||
* expressions (but not its test expression) are always wrapped in instances of {@link JoinPredecessorExpression}.
|
||||
*/
|
||||
@Immutable
|
||||
public final class TernaryNode extends Expression {
|
||||
private final Expression test;
|
||||
|
||||
private final Expression trueExpr;
|
||||
|
||||
/** Third argument. */
|
||||
private final Expression falseExpr;
|
||||
private final JoinPredecessorExpression trueExpr;
|
||||
private final JoinPredecessorExpression falseExpr;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@ -48,14 +48,15 @@ public final class TernaryNode extends Expression {
|
||||
* @param trueExpr expression evaluated when test evaluates to true
|
||||
* @param falseExpr expression evaluated when test evaluates to true
|
||||
*/
|
||||
public TernaryNode(final long token, final Expression test, final Expression trueExpr, final Expression falseExpr) {
|
||||
public TernaryNode(final long token, final Expression test, final JoinPredecessorExpression trueExpr, final JoinPredecessorExpression falseExpr) {
|
||||
super(token, falseExpr.getFinish());
|
||||
this.test = test;
|
||||
this.trueExpr = trueExpr;
|
||||
this.falseExpr = falseExpr;
|
||||
}
|
||||
|
||||
private TernaryNode(final TernaryNode ternaryNode, final Expression test, final Expression trueExpr, final Expression falseExpr) {
|
||||
private TernaryNode(final TernaryNode ternaryNode, final Expression test, final JoinPredecessorExpression trueExpr,
|
||||
final JoinPredecessorExpression falseExpr) {
|
||||
super(ternaryNode);
|
||||
this.test = test;
|
||||
this.trueExpr = trueExpr;
|
||||
@ -66,9 +67,9 @@ public final class TernaryNode extends Expression {
|
||||
public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
|
||||
if (visitor.enterTernaryNode(this)) {
|
||||
final Expression newTest = (Expression)getTest().accept(visitor);
|
||||
final Expression newTrueExpr = (Expression)getTrueExpression().accept(visitor);
|
||||
final Expression newFalseExpr = (Expression)falseExpr.accept(visitor);
|
||||
return visitor.leaveTernaryNode(setTest(newTest).setTrueExpression(newTrueExpr).setFalseExpression1(newFalseExpr));
|
||||
final JoinPredecessorExpression newTrueExpr = (JoinPredecessorExpression)trueExpr.accept(visitor);
|
||||
final JoinPredecessorExpression newFalseExpr = (JoinPredecessorExpression)falseExpr.accept(visitor);
|
||||
return visitor.leaveTernaryNode(setTest(newTest).setTrueExpression(newTrueExpr).setFalseExpression(newFalseExpr));
|
||||
}
|
||||
|
||||
return this;
|
||||
@ -116,6 +117,12 @@ public final class TernaryNode extends Expression {
|
||||
&& getFalseExpression().isLocal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType(Function<Symbol, Type> localVariableTypes) {
|
||||
return Type.widestReturnType(getTrueExpression().getType(localVariableTypes), getFalseExpression().getType(localVariableTypes));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the test expression for this ternary expression, i.e. "x" in x ? y : z
|
||||
* @return the test expression
|
||||
@ -128,7 +135,7 @@ public final class TernaryNode extends Expression {
|
||||
* Get the true expression for this ternary expression, i.e. "y" in x ? y : z
|
||||
* @return the true expression
|
||||
*/
|
||||
public Expression getTrueExpression() {
|
||||
public JoinPredecessorExpression getTrueExpression() {
|
||||
return trueExpr;
|
||||
}
|
||||
|
||||
@ -136,7 +143,7 @@ public final class TernaryNode extends Expression {
|
||||
* Get the false expression for this ternary expression, i.e. "z" in x ? y : z
|
||||
* @return the false expression
|
||||
*/
|
||||
public Expression getFalseExpression() {
|
||||
public JoinPredecessorExpression getFalseExpression() {
|
||||
return falseExpr;
|
||||
}
|
||||
|
||||
@ -157,7 +164,7 @@ public final class TernaryNode extends Expression {
|
||||
* @param trueExpr new true expression
|
||||
* @return a node equivalent to this one except for the requested change.
|
||||
*/
|
||||
public TernaryNode setTrueExpression(final Expression trueExpr) {
|
||||
public TernaryNode setTrueExpression(final JoinPredecessorExpression trueExpr) {
|
||||
if (this.trueExpr == trueExpr) {
|
||||
return this;
|
||||
}
|
||||
@ -169,7 +176,7 @@ public final class TernaryNode extends Expression {
|
||||
* @param falseExpr new false expression
|
||||
* @return a node equivalent to this one except for the requested change.
|
||||
*/
|
||||
public TernaryNode setFalseExpression1(final Expression falseExpr) {
|
||||
public TernaryNode setFalseExpression(final JoinPredecessorExpression falseExpr) {
|
||||
if (this.falseExpr == falseExpr) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -32,14 +32,13 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
* IR representation for THROW statements.
|
||||
*/
|
||||
@Immutable
|
||||
public final class ThrowNode extends Statement {
|
||||
public final class ThrowNode extends Statement implements JoinPredecessor {
|
||||
/** Exception expression. */
|
||||
private final Expression expression;
|
||||
|
||||
private final int flags;
|
||||
private final LocalVariableConversion conversion;
|
||||
|
||||
/** Is this block a synthethic rethrow created by finally inlining? */
|
||||
public static final int IS_SYNTHETIC_RETHROW = 1;
|
||||
private final boolean isSyntheticRethrow;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@ -48,18 +47,21 @@ public final class ThrowNode extends Statement {
|
||||
* @param token token
|
||||
* @param finish finish
|
||||
* @param expression expression to throw
|
||||
* @param flags flags
|
||||
* @param isSyntheticRethrow true if this throw node is part of a synthetic rethrow.
|
||||
*/
|
||||
public ThrowNode(final int lineNumber, final long token, final int finish, final Expression expression, final int flags) {
|
||||
public ThrowNode(final int lineNumber, final long token, final int finish, final Expression expression, final boolean isSyntheticRethrow) {
|
||||
super(lineNumber, token, finish);
|
||||
this.expression = expression;
|
||||
this.flags = flags;
|
||||
this.isSyntheticRethrow = isSyntheticRethrow;
|
||||
this.conversion = null;
|
||||
}
|
||||
|
||||
private ThrowNode(final ThrowNode node, final Expression expression, final int flags) {
|
||||
private ThrowNode(final ThrowNode node, final Expression expression, final boolean isSyntheticRethrow,
|
||||
final LocalVariableConversion conversion) {
|
||||
super(node);
|
||||
this.expression = expression;
|
||||
this.flags = flags;
|
||||
this.isSyntheticRethrow = isSyntheticRethrow;
|
||||
this.conversion = conversion;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -87,6 +89,9 @@ public final class ThrowNode extends Statement {
|
||||
if (expression != null) {
|
||||
expression.toString(sb);
|
||||
}
|
||||
if(conversion != null) {
|
||||
conversion.toString(sb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -106,7 +111,7 @@ public final class ThrowNode extends Statement {
|
||||
if (this.expression == expression) {
|
||||
return this;
|
||||
}
|
||||
return new ThrowNode(this, expression, flags);
|
||||
return new ThrowNode(this, expression, isSyntheticRethrow, conversion);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -116,7 +121,20 @@ public final class ThrowNode extends Statement {
|
||||
* @return true if synthetic throw node
|
||||
*/
|
||||
public boolean isSyntheticRethrow() {
|
||||
return (flags & IS_SYNTHETIC_RETHROW) == IS_SYNTHETIC_RETHROW;
|
||||
return isSyntheticRethrow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoinPredecessor setLocalVariableConversion(final LexicalContext lc, final LocalVariableConversion conversion) {
|
||||
if(this.conversion == conversion) {
|
||||
return this;
|
||||
}
|
||||
return new ThrowNode(this, expression, isSyntheticRethrow, conversion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalVariableConversion getLocalVariableConversion() {
|
||||
return conversion;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -28,8 +28,6 @@ package jdk.nashorn.internal.ir;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jdk.nashorn.internal.codegen.Label;
|
||||
import jdk.nashorn.internal.ir.annotations.Immutable;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
|
||||
@ -37,7 +35,7 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
* IR representation of a TRY statement.
|
||||
*/
|
||||
@Immutable
|
||||
public final class TryNode extends Statement {
|
||||
public final class TryNode extends Statement implements JoinPredecessor {
|
||||
/** Try statements. */
|
||||
private final Block body;
|
||||
|
||||
@ -47,15 +45,14 @@ public final class TryNode extends Statement {
|
||||
/** Finally clause. */
|
||||
private final Block finallyBody;
|
||||
|
||||
/** Exit label. */
|
||||
private final Label exit;
|
||||
|
||||
/** Exception symbol. */
|
||||
private Symbol exception;
|
||||
|
||||
/** Catchall exception for finally expansion, where applicable */
|
||||
private Symbol finallyCatchAll;
|
||||
|
||||
private final LocalVariableConversion conversion;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@ -71,21 +68,22 @@ public final class TryNode extends Statement {
|
||||
this.body = body;
|
||||
this.catchBlocks = catchBlocks;
|
||||
this.finallyBody = finallyBody;
|
||||
this.exit = new Label("exit");
|
||||
this.conversion = null;
|
||||
}
|
||||
|
||||
private TryNode(final TryNode tryNode, final Block body, final List<Block> catchBlocks, final Block finallyBody) {
|
||||
private TryNode(final TryNode tryNode, final Block body, final List<Block> catchBlocks, final Block finallyBody, LocalVariableConversion conversion) {
|
||||
super(tryNode);
|
||||
this.body = body;
|
||||
this.catchBlocks = catchBlocks;
|
||||
this.finallyBody = finallyBody;
|
||||
this.exit = new Label(tryNode.exit);
|
||||
this.conversion = conversion;
|
||||
this.exception = tryNode.exception;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node ensureUniqueLabels(final LexicalContext lc) {
|
||||
//try nodes are never in lex context
|
||||
return new TryNode(this, body, catchBlocks, finallyBody);
|
||||
return new TryNode(this, body, catchBlocks, finallyBody, conversion);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -115,7 +113,6 @@ public final class TryNode extends Statement {
|
||||
setBody(newBody).
|
||||
setFinallyBody(newFinallyBody).
|
||||
setCatchBlocks(Node.accept(visitor, Block.class, catchBlocks)).
|
||||
setException(exception).
|
||||
setFinallyCatchAll(finallyCatchAll));
|
||||
}
|
||||
|
||||
@ -144,7 +141,7 @@ public final class TryNode extends Statement {
|
||||
if (this.body == body) {
|
||||
return this;
|
||||
}
|
||||
return new TryNode(this, body, catchBlocks, finallyBody);
|
||||
return new TryNode(this, body, catchBlocks, finallyBody, conversion);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -154,11 +151,15 @@ public final class TryNode extends Statement {
|
||||
public List<CatchNode> getCatches() {
|
||||
final List<CatchNode> catches = new ArrayList<>(catchBlocks.size());
|
||||
for (final Block catchBlock : catchBlocks) {
|
||||
catches.add((CatchNode)catchBlock.getStatements().get(0));
|
||||
catches.add(getCatchNodeFromBlock(catchBlock));
|
||||
}
|
||||
return Collections.unmodifiableList(catches);
|
||||
}
|
||||
|
||||
private static CatchNode getCatchNodeFromBlock(final Block catchBlock) {
|
||||
return (CatchNode)catchBlock.getStatements().get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the catch blocks for this try block
|
||||
* @return a list of blocks
|
||||
@ -176,7 +177,7 @@ public final class TryNode extends Statement {
|
||||
if (this.catchBlocks == catchBlocks) {
|
||||
return this;
|
||||
}
|
||||
return new TryNode(this, body, catchBlocks, finallyBody);
|
||||
return new TryNode(this, body, catchBlocks, finallyBody, conversion);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -186,7 +187,6 @@ public final class TryNode extends Statement {
|
||||
public Symbol getException() {
|
||||
return exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the exception symbol for this try block
|
||||
* @param exception a symbol for the compiler to store the exception in
|
||||
@ -218,14 +218,6 @@ public final class TryNode extends Statement {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exit label for this try block
|
||||
* @return exit label
|
||||
*/
|
||||
public Label getExit() {
|
||||
return exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the body of the finally clause for this try
|
||||
* @return finally body, or null if no finally
|
||||
@ -243,6 +235,19 @@ public final class TryNode extends Statement {
|
||||
if (this.finallyBody == finallyBody) {
|
||||
return this;
|
||||
}
|
||||
return new TryNode(this, body, catchBlocks, finallyBody);
|
||||
return new TryNode(this, body, catchBlocks, finallyBody, conversion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoinPredecessor setLocalVariableConversion(final LexicalContext lc, final LocalVariableConversion conversion) {
|
||||
if(this.conversion == conversion) {
|
||||
return this;
|
||||
}
|
||||
return new TryNode(this, body, catchBlocks, finallyBody, conversion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalVariableConversion getLocalVariableConversion() {
|
||||
return conversion;
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,6 +33,7 @@ import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.annotations.Ignore;
|
||||
import jdk.nashorn.internal.ir.annotations.Immutable;
|
||||
@ -50,8 +51,6 @@ public final class UnaryNode extends Expression implements Assignment<Expression
|
||||
|
||||
private final int programPoint;
|
||||
|
||||
private final boolean isOptimistic;
|
||||
|
||||
private final Type type;
|
||||
|
||||
@Ignore
|
||||
@ -88,16 +87,14 @@ public final class UnaryNode extends Expression implements Assignment<Expression
|
||||
super(token, start, finish);
|
||||
this.expression = expression;
|
||||
this.programPoint = INVALID_PROGRAM_POINT;
|
||||
this.isOptimistic = false;
|
||||
this.type = null;
|
||||
}
|
||||
|
||||
|
||||
private UnaryNode(final UnaryNode unaryNode, final Expression expression, final Type type, final int programPoint, final boolean isOptimistic) {
|
||||
private UnaryNode(final UnaryNode unaryNode, final Expression expression, final Type type, final int programPoint) {
|
||||
super(unaryNode);
|
||||
this.expression = expression;
|
||||
this.programPoint = programPoint;
|
||||
this.isOptimistic = isOptimistic;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@ -124,12 +121,40 @@ public final class UnaryNode extends Expression implements Assignment<Expression
|
||||
return isAssignment();
|
||||
}
|
||||
|
||||
private static final Function<Symbol, Type> UNKNOWN_LOCALS = new Function<Symbol, Type>() {
|
||||
@Override
|
||||
public Type apply(Symbol t) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@Override
|
||||
public Type getWidestOperationType() {
|
||||
return getWidestOperationType(UNKNOWN_LOCALS);
|
||||
}
|
||||
|
||||
private Type getWidestOperationType(final Function<Symbol, Type> localVariableTypes) {
|
||||
switch (tokenType()) {
|
||||
case ADD:
|
||||
final Type operandType = getExpression().getType(localVariableTypes);
|
||||
if(operandType == Type.BOOLEAN) {
|
||||
return Type.INT;
|
||||
} else if(operandType.isObject()) {
|
||||
return Type.NUMBER;
|
||||
}
|
||||
assert operandType.isNumeric();
|
||||
return operandType;
|
||||
case SUB:
|
||||
// This might seems overly conservative until you consider that -0 can only be represented as a double.
|
||||
return Type.NUMBER;
|
||||
case NOT:
|
||||
case DELETE:
|
||||
return Type.BOOLEAN;
|
||||
case BIT_NOT:
|
||||
return Type.INT;
|
||||
case VOID:
|
||||
return Type.UNDEFINED;
|
||||
default:
|
||||
return isAssignment() ? Type.NUMBER : Type.OBJECT;
|
||||
}
|
||||
@ -206,7 +231,7 @@ public final class UnaryNode extends Expression implements Assignment<Expression
|
||||
final boolean isPostfix = tokenType == DECPOSTFIX || tokenType == INCPOSTFIX;
|
||||
|
||||
if (isOptimistic()) {
|
||||
sb.append(Node.OPT_IDENTIFIER);
|
||||
sb.append(Expression.OPT_IDENTIFIER);
|
||||
}
|
||||
boolean rhsParen = tokenType.needsParens(getExpression().tokenType(), false);
|
||||
|
||||
@ -261,7 +286,7 @@ public final class UnaryNode extends Expression implements Assignment<Expression
|
||||
if (this.expression == expression) {
|
||||
return this;
|
||||
}
|
||||
return new UnaryNode(this, expression, type, programPoint, isOptimistic);
|
||||
return new UnaryNode(this, expression, type, programPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -274,15 +299,7 @@ public final class UnaryNode extends Expression implements Assignment<Expression
|
||||
if (this.programPoint == programPoint) {
|
||||
return this;
|
||||
}
|
||||
return new UnaryNode(this, expression, type, programPoint, isOptimistic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnaryNode setIsOptimistic(final boolean isOptimistic) {
|
||||
if (this.isOptimistic == isOptimistic) {
|
||||
return this;
|
||||
}
|
||||
return new UnaryNode(this, expression, type, programPoint, isOptimistic);
|
||||
return new UnaryNode(this, expression, type, programPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -304,22 +321,20 @@ public final class UnaryNode extends Expression implements Assignment<Expression
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOptimistic() {
|
||||
//return hasType() && canBeOptimistic() && getType().narrowerThan(getMostPessimisticType());
|
||||
return isOptimistic;
|
||||
public Type getType(Function<Symbol, Type> localVariableTypes) {
|
||||
final Type widest = getWidestOperationType(localVariableTypes);
|
||||
if(type == null) {
|
||||
return widest;
|
||||
}
|
||||
return Type.narrowest(widest, Type.widest(type, expression.getType(localVariableTypes)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return type == null ? super.getType() : type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnaryNode setType(TemporarySymbols ts, Type type) {
|
||||
public UnaryNode setType(Type type) {
|
||||
if (this.type == type) {
|
||||
return this;
|
||||
}
|
||||
return new UnaryNode(this, expression, type, programPoint, isOptimistic);
|
||||
return new UnaryNode(this, expression, type, programPoint);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -123,8 +123,9 @@ public final class VarNode extends Statement implements Assignment<IdentNode> {
|
||||
@Override
|
||||
public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
|
||||
if (visitor.enterVarNode(this)) {
|
||||
final IdentNode newName = (IdentNode)name.accept(visitor);
|
||||
// var is right associative, so visit init before name
|
||||
final Expression newInit = init == null ? null : (Expression)init.accept(visitor);
|
||||
final IdentNode newName = (IdentNode)name.accept(visitor);
|
||||
final VarNode newThis;
|
||||
if (name != newName || init != newInit) {
|
||||
newThis = new VarNode(this, newName, newInit, flags);
|
||||
|
||||
@ -47,7 +47,7 @@ public final class WhileNode extends LoopNode {
|
||||
* @param isDoWhile is this a do while loop?
|
||||
*/
|
||||
public WhileNode(final int lineNumber, final long token, final int finish, final boolean isDoWhile) {
|
||||
super(lineNumber, token, finish, null, null, false);
|
||||
super(lineNumber, token, finish, null, false);
|
||||
this.isDoWhile = isDoWhile;
|
||||
}
|
||||
|
||||
@ -58,15 +58,16 @@ public final class WhileNode extends LoopNode {
|
||||
* @param test test
|
||||
* @param body body
|
||||
* @param controlFlowEscapes control flow escapes?
|
||||
* @param conversion TODO
|
||||
*/
|
||||
protected WhileNode(final WhileNode whileNode, final Expression test, final Block body, final boolean controlFlowEscapes) {
|
||||
super(whileNode, test, body, controlFlowEscapes);
|
||||
private WhileNode(final WhileNode whileNode, final JoinPredecessorExpression test, final Block body, final boolean controlFlowEscapes, LocalVariableConversion conversion) {
|
||||
super(whileNode, test, body, controlFlowEscapes, conversion);
|
||||
this.isDoWhile = whileNode.isDoWhile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node ensureUniqueLabels(final LexicalContext lc) {
|
||||
return Node.replaceInLexicalContext(lc, this, new WhileNode(this, test, body, controlFlowEscapes));
|
||||
return Node.replaceInLexicalContext(lc, this, new WhileNode(this, test, body, controlFlowEscapes, conversion));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -80,26 +81,21 @@ public final class WhileNode extends LoopNode {
|
||||
if (isDoWhile()) {
|
||||
return visitor.leaveWhileNode(
|
||||
setBody(lc, (Block)body.accept(visitor)).
|
||||
setTest(lc, (Expression)test.accept(visitor)));
|
||||
setTest(lc, (JoinPredecessorExpression)test.accept(visitor)));
|
||||
}
|
||||
return visitor.leaveWhileNode(
|
||||
setTest(lc, (Expression)test.accept(visitor)).
|
||||
setTest(lc, (JoinPredecessorExpression)test.accept(visitor)).
|
||||
setBody(lc, (Block)body.accept(visitor)));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression getTest() {
|
||||
return test;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WhileNode setTest(final LexicalContext lc, final Expression test) {
|
||||
public WhileNode setTest(final LexicalContext lc, final JoinPredecessorExpression test) {
|
||||
if (this.test == test) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new WhileNode(this, test, body, controlFlowEscapes));
|
||||
return Node.replaceInLexicalContext(lc, this, new WhileNode(this, test, body, controlFlowEscapes, conversion));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -112,7 +108,7 @@ public final class WhileNode extends LoopNode {
|
||||
if (this.body == body) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new WhileNode(this, test, body, controlFlowEscapes));
|
||||
return Node.replaceInLexicalContext(lc, this, new WhileNode(this, test, body, controlFlowEscapes, conversion));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -120,7 +116,12 @@ public final class WhileNode extends LoopNode {
|
||||
if (this.controlFlowEscapes == controlFlowEscapes) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new WhileNode(this, test, body, controlFlowEscapes));
|
||||
return Node.replaceInLexicalContext(lc, this, new WhileNode(this, test, body, controlFlowEscapes, conversion));
|
||||
}
|
||||
|
||||
@Override
|
||||
JoinPredecessor setLocalVariableConversionChanged(final LexicalContext lc, final LocalVariableConversion conversion) {
|
||||
return Node.replaceInLexicalContext(lc, this, new WhileNode(this, test, body, controlFlowEscapes, conversion));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -36,6 +36,7 @@ import java.util.List;
|
||||
import jdk.nashorn.internal.ir.BinaryNode;
|
||||
import jdk.nashorn.internal.ir.Block;
|
||||
import jdk.nashorn.internal.ir.Expression;
|
||||
import jdk.nashorn.internal.ir.IdentNode;
|
||||
import jdk.nashorn.internal.ir.Node;
|
||||
import jdk.nashorn.internal.ir.Symbol;
|
||||
import jdk.nashorn.internal.ir.TernaryNode;
|
||||
@ -121,8 +122,8 @@ public final class ASTWriter {
|
||||
type = "ref: " + type;
|
||||
}
|
||||
final Symbol symbol;
|
||||
if (node instanceof Expression) {
|
||||
symbol = ((Expression)node).getSymbol();
|
||||
if (node instanceof IdentNode) {
|
||||
symbol = ((IdentNode)node).getSymbol();
|
||||
} else {
|
||||
symbol = null;
|
||||
}
|
||||
|
||||
@ -25,9 +25,9 @@
|
||||
|
||||
package jdk.nashorn.internal.ir.debug;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import jdk.nashorn.internal.codegen.CompilerConstants;
|
||||
import jdk.nashorn.internal.ir.AccessNode;
|
||||
import jdk.nashorn.internal.ir.BinaryNode;
|
||||
@ -39,12 +39,14 @@ import jdk.nashorn.internal.ir.CaseNode;
|
||||
import jdk.nashorn.internal.ir.CatchNode;
|
||||
import jdk.nashorn.internal.ir.ContinueNode;
|
||||
import jdk.nashorn.internal.ir.EmptyNode;
|
||||
import jdk.nashorn.internal.ir.Expression;
|
||||
import jdk.nashorn.internal.ir.ExpressionStatement;
|
||||
import jdk.nashorn.internal.ir.ForNode;
|
||||
import jdk.nashorn.internal.ir.FunctionNode;
|
||||
import jdk.nashorn.internal.ir.IdentNode;
|
||||
import jdk.nashorn.internal.ir.IfNode;
|
||||
import jdk.nashorn.internal.ir.IndexNode;
|
||||
import jdk.nashorn.internal.ir.JoinPredecessorExpression;
|
||||
import jdk.nashorn.internal.ir.LabelNode;
|
||||
import jdk.nashorn.internal.ir.LexicalContext;
|
||||
import jdk.nashorn.internal.ir.LiteralNode;
|
||||
@ -99,6 +101,17 @@ public final class JSONWriter extends NodeVisitor<LexicalContext> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterJoinPredecessorExpression(JoinPredecessorExpression joinPredecessorExpression) {
|
||||
final Expression expr = joinPredecessorExpression.getExpression();
|
||||
if(expr != null) {
|
||||
expr.accept(this);
|
||||
} else {
|
||||
nullValue();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean enterDefault(final Node node) {
|
||||
objectStart();
|
||||
@ -129,8 +142,7 @@ public final class JSONWriter extends NodeVisitor<LexicalContext> {
|
||||
accessNode.getBase().accept(this);
|
||||
comma();
|
||||
|
||||
property("property");
|
||||
accessNode.getProperty().accept(this);
|
||||
property("property", accessNode.getProperty());
|
||||
comma();
|
||||
|
||||
property("computed", false);
|
||||
@ -150,16 +162,6 @@ public final class JSONWriter extends NodeVisitor<LexicalContext> {
|
||||
return leave();
|
||||
}
|
||||
|
||||
private static boolean isLogical(final TokenType tt) {
|
||||
switch (tt) {
|
||||
case AND:
|
||||
case OR:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterBinaryNode(final BinaryNode binaryNode) {
|
||||
enterDefault(binaryNode);
|
||||
@ -167,7 +169,7 @@ public final class JSONWriter extends NodeVisitor<LexicalContext> {
|
||||
final String name;
|
||||
if (binaryNode.isAssignment()) {
|
||||
name = "AssignmentExpression";
|
||||
} else if (isLogical(binaryNode.tokenType())) {
|
||||
} else if (binaryNode.isLogical()) {
|
||||
name = "LogicalExpression";
|
||||
} else {
|
||||
name = "BinaryExpression";
|
||||
@ -196,11 +198,11 @@ public final class JSONWriter extends NodeVisitor<LexicalContext> {
|
||||
type("BreakStatement");
|
||||
comma();
|
||||
|
||||
final IdentNode label = breakNode.getLabel();
|
||||
property("label");
|
||||
if (label != null) {
|
||||
label.accept(this);
|
||||
final String label = breakNode.getLabelName();
|
||||
if(label != null) {
|
||||
property("label", label);
|
||||
} else {
|
||||
property("label");
|
||||
nullValue();
|
||||
}
|
||||
|
||||
@ -275,11 +277,11 @@ public final class JSONWriter extends NodeVisitor<LexicalContext> {
|
||||
type("ContinueStatement");
|
||||
comma();
|
||||
|
||||
final IdentNode label = continueNode.getLabel();
|
||||
property("label");
|
||||
if (label != null) {
|
||||
label.accept(this);
|
||||
final String label = continueNode.getLabelName();
|
||||
if(label != null) {
|
||||
property("label", label);
|
||||
} else {
|
||||
property("label");
|
||||
nullValue();
|
||||
}
|
||||
|
||||
@ -532,8 +534,7 @@ public final class JSONWriter extends NodeVisitor<LexicalContext> {
|
||||
type("LabeledStatement");
|
||||
comma();
|
||||
|
||||
property("label");
|
||||
labelNode.getLabel().accept(this);
|
||||
property("label", labelNode.getLabelName());
|
||||
comma();
|
||||
|
||||
property("body");
|
||||
|
||||
@ -39,7 +39,6 @@ import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import jdk.internal.org.objectweb.asm.Attribute;
|
||||
import jdk.internal.org.objectweb.asm.Handle;
|
||||
import jdk.internal.org.objectweb.asm.Label;
|
||||
@ -928,6 +927,10 @@ public final class NashornTextifier extends Printer {
|
||||
}
|
||||
} else {
|
||||
final int lastSlash = desc.lastIndexOf('/');
|
||||
final int lastBracket = desc.lastIndexOf('[');
|
||||
if(lastBracket != -1) {
|
||||
sb.append(desc, 0, lastBracket + 1);
|
||||
}
|
||||
sb.append(lastSlash == -1 ? desc : desc.substring(lastSlash + 1));
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,18 +29,25 @@ import java.util.List;
|
||||
import jdk.nashorn.internal.ir.BinaryNode;
|
||||
import jdk.nashorn.internal.ir.Block;
|
||||
import jdk.nashorn.internal.ir.BlockStatement;
|
||||
import jdk.nashorn.internal.ir.BreakNode;
|
||||
import jdk.nashorn.internal.ir.CaseNode;
|
||||
import jdk.nashorn.internal.ir.CatchNode;
|
||||
import jdk.nashorn.internal.ir.ContinueNode;
|
||||
import jdk.nashorn.internal.ir.ExpressionStatement;
|
||||
import jdk.nashorn.internal.ir.ForNode;
|
||||
import jdk.nashorn.internal.ir.FunctionNode;
|
||||
import jdk.nashorn.internal.ir.IdentNode;
|
||||
import jdk.nashorn.internal.ir.IfNode;
|
||||
import jdk.nashorn.internal.ir.JoinPredecessor;
|
||||
import jdk.nashorn.internal.ir.JoinPredecessorExpression;
|
||||
import jdk.nashorn.internal.ir.LabelNode;
|
||||
import jdk.nashorn.internal.ir.LexicalContext;
|
||||
import jdk.nashorn.internal.ir.LocalVariableConversion;
|
||||
import jdk.nashorn.internal.ir.Node;
|
||||
import jdk.nashorn.internal.ir.SplitNode;
|
||||
import jdk.nashorn.internal.ir.Statement;
|
||||
import jdk.nashorn.internal.ir.SwitchNode;
|
||||
import jdk.nashorn.internal.ir.ThrowNode;
|
||||
import jdk.nashorn.internal.ir.TryNode;
|
||||
import jdk.nashorn.internal.ir.UnaryNode;
|
||||
import jdk.nashorn.internal.ir.VarNode;
|
||||
@ -139,6 +146,27 @@ public final class PrintVisitor extends NodeVisitor<LexicalContext> {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterContinueNode(final ContinueNode node) {
|
||||
node.toString(sb);
|
||||
printLocalVariableConversion(node);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterBreakNode(final BreakNode node) {
|
||||
node.toString(sb);
|
||||
printLocalVariableConversion(node);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterThrowNode(final ThrowNode node) {
|
||||
node.toString(sb);
|
||||
printLocalVariableConversion(node);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterBlock(final Block block) {
|
||||
sb.append(' ');
|
||||
@ -190,6 +218,7 @@ public final class PrintVisitor extends NodeVisitor<LexicalContext> {
|
||||
sb.append(EOLN);
|
||||
indent();
|
||||
sb.append('}');
|
||||
printLocalVariableConversion(block);
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -210,6 +239,24 @@ public final class PrintVisitor extends NodeVisitor<LexicalContext> {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterJoinPredecessorExpression(JoinPredecessorExpression expr) {
|
||||
expr.getExpression().accept(this);
|
||||
printLocalVariableConversion(expr);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterIdentNode(IdentNode identNode) {
|
||||
identNode.toString(sb);
|
||||
printLocalVariableConversion(identNode);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void printLocalVariableConversion(final JoinPredecessor joinPredecessor) {
|
||||
LocalVariableConversion.toString(joinPredecessor.getLocalVariableConversion(), sb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterUnaryNode(final UnaryNode unaryNode) {
|
||||
unaryNode.toString(sb, new Runnable() {
|
||||
@ -252,7 +299,12 @@ public final class PrintVisitor extends NodeVisitor<LexicalContext> {
|
||||
sb.append(" else ");
|
||||
fail.accept(this);
|
||||
}
|
||||
|
||||
if(ifNode.getLocalVariableConversion() != null) {
|
||||
assert fail == null;
|
||||
sb.append(" else ");
|
||||
printLocalVariableConversion(ifNode);
|
||||
sb.append(";");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -263,7 +315,7 @@ public final class PrintVisitor extends NodeVisitor<LexicalContext> {
|
||||
indent += TABWIDTH;
|
||||
labeledNode.toString(sb);
|
||||
labeledNode.getBody().accept(this);
|
||||
|
||||
printLocalVariableConversion(labeledNode);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -296,12 +348,19 @@ public final class PrintVisitor extends NodeVisitor<LexicalContext> {
|
||||
sb.append(EOLN);
|
||||
indent();
|
||||
caseNode.toString(sb);
|
||||
printLocalVariableConversion(caseNode);
|
||||
indent += TABWIDTH;
|
||||
caseNode.getBody().accept(this);
|
||||
indent -= TABWIDTH;
|
||||
sb.append(EOLN);
|
||||
}
|
||||
|
||||
if(switchNode.getLocalVariableConversion() != null) {
|
||||
sb.append(EOLN);
|
||||
indent();
|
||||
sb.append("default: ");
|
||||
printLocalVariableConversion(switchNode);
|
||||
sb.append("{}");
|
||||
}
|
||||
sb.append(EOLN);
|
||||
indent();
|
||||
sb.append("}");
|
||||
@ -312,6 +371,7 @@ public final class PrintVisitor extends NodeVisitor<LexicalContext> {
|
||||
@Override
|
||||
public boolean enterTryNode(final TryNode tryNode) {
|
||||
tryNode.toString(sb);
|
||||
printLocalVariableConversion(tryNode);
|
||||
tryNode.getBody().accept(this);
|
||||
|
||||
final List<Block> catchBlocks = tryNode.getCatchBlocks();
|
||||
@ -336,6 +396,7 @@ public final class PrintVisitor extends NodeVisitor<LexicalContext> {
|
||||
public boolean enterVarNode(final VarNode varNode) {
|
||||
sb.append("var ");
|
||||
varNode.getName().toString(sb);
|
||||
printLocalVariableConversion(varNode.getName());
|
||||
final Node init = varNode.getInit();
|
||||
if (init != null) {
|
||||
sb.append(" = ");
|
||||
@ -347,6 +408,7 @@ public final class PrintVisitor extends NodeVisitor<LexicalContext> {
|
||||
|
||||
@Override
|
||||
public boolean enterWhileNode(final WhileNode whileNode) {
|
||||
printLocalVariableConversion(whileNode);
|
||||
if (whileNode.isDoWhile()) {
|
||||
sb.append("do");
|
||||
whileNode.getBody().accept(this);
|
||||
|
||||
@ -53,8 +53,6 @@ public class NodeOperatorVisitor<T extends LexicalContext> extends NodeVisitor<T
|
||||
return enterBIT_NOT(unaryNode);
|
||||
case DELETE:
|
||||
return enterDELETE(unaryNode);
|
||||
case DISCARD:
|
||||
return enterDISCARD(unaryNode);
|
||||
case NEW:
|
||||
return enterNEW(unaryNode);
|
||||
case NOT:
|
||||
@ -84,8 +82,6 @@ public class NodeOperatorVisitor<T extends LexicalContext> extends NodeVisitor<T
|
||||
return leaveBIT_NOT(unaryNode);
|
||||
case DELETE:
|
||||
return leaveDELETE(unaryNode);
|
||||
case DISCARD:
|
||||
return leaveDISCARD(unaryNode);
|
||||
case NEW:
|
||||
return leaveNEW(unaryNode);
|
||||
case NOT:
|
||||
@ -358,26 +354,6 @@ public class NodeOperatorVisitor<T extends LexicalContext> extends NodeVisitor<T
|
||||
return leaveDefault(unaryNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unary enter - callback for entering a discard operator
|
||||
*
|
||||
* @param unaryNode the node
|
||||
* @return true if traversal should continue and node children be traversed, false otherwise
|
||||
*/
|
||||
public boolean enterDISCARD(final UnaryNode unaryNode) {
|
||||
return enterDefault(unaryNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unary leave - callback for leaving a discard operator
|
||||
*
|
||||
* @param unaryNode the node
|
||||
* @return processed node, which will replace the original one, or the original node
|
||||
*/
|
||||
public Node leaveDISCARD(final UnaryNode unaryNode) {
|
||||
return leaveDefault(unaryNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unary enter - callback for entering a new operator
|
||||
*
|
||||
|
||||
@ -41,6 +41,7 @@ import jdk.nashorn.internal.ir.FunctionNode;
|
||||
import jdk.nashorn.internal.ir.IdentNode;
|
||||
import jdk.nashorn.internal.ir.IfNode;
|
||||
import jdk.nashorn.internal.ir.IndexNode;
|
||||
import jdk.nashorn.internal.ir.JoinPredecessorExpression;
|
||||
import jdk.nashorn.internal.ir.LabelNode;
|
||||
import jdk.nashorn.internal.ir.LexicalContext;
|
||||
import jdk.nashorn.internal.ir.LiteralNode;
|
||||
@ -688,6 +689,27 @@ public abstract class NodeVisitor<T extends LexicalContext> {
|
||||
return leaveDefault(unaryNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for entering a {@link JoinPredecessorExpression}.
|
||||
*
|
||||
* @param expr the join predecessor expression
|
||||
* @return true if traversal should continue and node children be traversed, false otherwise
|
||||
*/
|
||||
public boolean enterJoinPredecessorExpression(final JoinPredecessorExpression expr) {
|
||||
return enterDefault(expr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for leaving a {@link JoinPredecessorExpression}.
|
||||
*
|
||||
* @param expr the join predecessor expression
|
||||
* @return processed node, which will replace the original one, or the original node
|
||||
*/
|
||||
public Node leaveJoinPredecessorExpression(final JoinPredecessorExpression expr) {
|
||||
return leaveDefault(expr);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Callback for entering a VarNode
|
||||
*
|
||||
|
||||
@ -45,7 +45,6 @@ import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import jdk.internal.dynalink.linker.GuardedInvocation;
|
||||
import jdk.internal.dynalink.linker.LinkRequest;
|
||||
import jdk.nashorn.internal.codegen.ApplySpecialization;
|
||||
@ -91,18 +90,15 @@ public final class Global extends ScriptObject implements Scope {
|
||||
* like
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* public boolean setterGuard(final Object receiver) {
|
||||
* final Global global = Global.instance();
|
||||
* final ScriptObject sobj = global.getFunctionPrototype();
|
||||
* final Object apply = sobj.get("apply");
|
||||
* return apply == receiver;
|
||||
* }
|
||||
*
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Naturally, checking for builting classes like NativeFunction is cheaper,
|
||||
* Naturally, checking for builtin classes like NativeFunction is cheaper,
|
||||
* it's when you start adding property checks for said builtins you have
|
||||
* problems with guard speed.
|
||||
*/
|
||||
@ -1953,7 +1949,7 @@ public final class Global extends ScriptObject implements Scope {
|
||||
this.builtinFunction = (ScriptFunction)initConstructor("Function");
|
||||
|
||||
// create global anonymous function
|
||||
final ScriptFunction anon = ScriptFunctionImpl.newAnonymousFunction(this);
|
||||
final ScriptFunction anon = ScriptFunctionImpl.newAnonymousFunction();
|
||||
// need to copy over members of Function.prototype to anon function
|
||||
anon.addBoundProperties(getFunctionPrototype());
|
||||
|
||||
|
||||
@ -1147,7 +1147,7 @@ public final class NativeString extends ScriptObject {
|
||||
return str.substring(start, end + 1);
|
||||
}
|
||||
|
||||
private static Object newObj(final Object self, final CharSequence str) {
|
||||
private static Object newObj(final CharSequence str) {
|
||||
return new NativeString(str);
|
||||
}
|
||||
|
||||
@ -1165,7 +1165,7 @@ public final class NativeString extends ScriptObject {
|
||||
@Constructor(arity = 1)
|
||||
public static Object constructor(final boolean newObj, final Object self, final Object... args) {
|
||||
final CharSequence str = args.length > 0 ? JSType.toCharSequence(args[0]) : "";
|
||||
return newObj ? newObj(self, str) : str.toString();
|
||||
return newObj ? newObj(str) : str.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1180,7 +1180,7 @@ public final class NativeString extends ScriptObject {
|
||||
*/
|
||||
@SpecializedConstructor
|
||||
public static Object constructor(final boolean newObj, final Object self) {
|
||||
return newObj ? newObj(self, "") : "";
|
||||
return newObj ? newObj("") : "";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1197,7 +1197,7 @@ public final class NativeString extends ScriptObject {
|
||||
@SpecializedConstructor
|
||||
public static Object constructor(final boolean newObj, final Object self, final Object arg) {
|
||||
final CharSequence str = JSType.toCharSequence(arg);
|
||||
return newObj ? newObj(self, str) : str.toString();
|
||||
return newObj ? newObj(str) : str.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1214,7 +1214,7 @@ public final class NativeString extends ScriptObject {
|
||||
@SpecializedConstructor
|
||||
public static Object constructor(final boolean newObj, final Object self, final int arg) {
|
||||
final String str = JSType.toString(arg);
|
||||
return newObj ? newObj(self, str) : str;
|
||||
return newObj ? newObj(str) : str;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1231,7 +1231,7 @@ public final class NativeString extends ScriptObject {
|
||||
@SpecializedConstructor
|
||||
public static Object constructor(final boolean newObj, final Object self, final boolean arg) {
|
||||
final String str = JSType.toString(arg);
|
||||
return newObj ? newObj(self, str) : str;
|
||||
return newObj ? newObj(str) : str;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -30,7 +30,6 @@ import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import jdk.nashorn.internal.runtime.AccessorProperty;
|
||||
import jdk.nashorn.internal.runtime.GlobalFunctions;
|
||||
import jdk.nashorn.internal.runtime.Property;
|
||||
@ -176,13 +175,13 @@ public class ScriptFunctionImpl extends ScriptFunction {
|
||||
private static class AnonymousFunction extends ScriptFunctionImpl {
|
||||
private static final PropertyMap anonmap$ = PropertyMap.newMap();
|
||||
|
||||
AnonymousFunction(final Global global) {
|
||||
AnonymousFunction() {
|
||||
super("", GlobalFunctions.ANONYMOUS, anonmap$, null);
|
||||
}
|
||||
}
|
||||
|
||||
static ScriptFunctionImpl newAnonymousFunction(final Global global) {
|
||||
return new AnonymousFunction(global);
|
||||
static ScriptFunctionImpl newAnonymousFunction() {
|
||||
return new AnonymousFunction();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -61,7 +61,6 @@ import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jdk.internal.dynalink.support.NameCodec;
|
||||
import jdk.nashorn.internal.codegen.CompilerConstants;
|
||||
import jdk.nashorn.internal.codegen.Namespace;
|
||||
@ -86,6 +85,7 @@ import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
|
||||
import jdk.nashorn.internal.ir.IdentNode;
|
||||
import jdk.nashorn.internal.ir.IfNode;
|
||||
import jdk.nashorn.internal.ir.IndexNode;
|
||||
import jdk.nashorn.internal.ir.JoinPredecessorExpression;
|
||||
import jdk.nashorn.internal.ir.LabelNode;
|
||||
import jdk.nashorn.internal.ir.LexicalContext;
|
||||
import jdk.nashorn.internal.ir.LiteralNode;
|
||||
@ -471,11 +471,6 @@ loop:
|
||||
if (isStrictMode) {
|
||||
flags |= FunctionNode.IS_STRICT;
|
||||
}
|
||||
if (env._specialize_calls != null) {
|
||||
if (env._specialize_calls.contains(name)) {
|
||||
flags |= FunctionNode.CAN_SPECIALIZE;
|
||||
}
|
||||
}
|
||||
if (parentFunction == null) {
|
||||
flags |= FunctionNode.IS_PROGRAM;
|
||||
}
|
||||
@ -653,9 +648,13 @@ loop:
|
||||
}
|
||||
|
||||
// Build up node.
|
||||
if(BinaryNode.isLogical(opType)) {
|
||||
return new BinaryNode(op, new JoinPredecessorExpression(lhs), new JoinPredecessorExpression(rhs));
|
||||
}
|
||||
return new BinaryNode(op, lhs, rhs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reduce increment/decrement to simpler operations.
|
||||
* @param firstToken First token.
|
||||
@ -1198,7 +1197,7 @@ loop:
|
||||
*/
|
||||
private void forStatement() {
|
||||
// Create FOR node, capturing FOR token.
|
||||
ForNode forNode = new ForNode(line, token, Token.descPosition(token), null, null, null, null, ForNode.IS_FOR);
|
||||
ForNode forNode = new ForNode(line, token, Token.descPosition(token), null, ForNode.IS_FOR);
|
||||
|
||||
lc.push(forNode);
|
||||
|
||||
@ -1241,16 +1240,16 @@ loop:
|
||||
|
||||
expect(SEMICOLON);
|
||||
if (type != SEMICOLON) {
|
||||
forNode = forNode.setTest(lc, expression());
|
||||
forNode = forNode.setTest(lc, joinPredecessorExpression());
|
||||
}
|
||||
expect(SEMICOLON);
|
||||
if (type != RPAREN) {
|
||||
forNode = forNode.setModify(lc, expression());
|
||||
forNode = forNode.setModify(lc, joinPredecessorExpression());
|
||||
}
|
||||
break;
|
||||
|
||||
case IN:
|
||||
forNode = forNode.setIsForIn(lc);
|
||||
forNode = forNode.setIsForIn(lc).setTest(lc, new JoinPredecessorExpression());
|
||||
if (vars != null) {
|
||||
// for (var i in obj)
|
||||
if (vars.size() == 1) {
|
||||
@ -1283,7 +1282,7 @@ loop:
|
||||
next();
|
||||
|
||||
// Get the collection expression.
|
||||
forNode = forNode.setModify(lc, expression());
|
||||
forNode = forNode.setModify(lc, joinPredecessorExpression());
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -1343,7 +1342,7 @@ loop:
|
||||
try {
|
||||
expect(LPAREN);
|
||||
final int whileLine = line;
|
||||
final Expression test = expression();
|
||||
final JoinPredecessorExpression test = joinPredecessorExpression();
|
||||
expect(RPAREN);
|
||||
final Block body = getStatement();
|
||||
appendStatement(whileNode =
|
||||
@ -1381,7 +1380,7 @@ loop:
|
||||
expect(WHILE);
|
||||
expect(LPAREN);
|
||||
final int doLine = line;
|
||||
final Expression test = expression();
|
||||
final JoinPredecessorExpression test = joinPredecessorExpression();
|
||||
expect(RPAREN);
|
||||
|
||||
if (type == SEMICOLON) {
|
||||
@ -1435,8 +1434,8 @@ loop:
|
||||
break;
|
||||
}
|
||||
|
||||
final IdentNode label = labelNode == null ? null : labelNode.getLabel();
|
||||
final LoopNode targetNode = lc.getContinueTo(label);
|
||||
final String labelName = labelNode == null ? null : labelNode.getLabelName();
|
||||
final LoopNode targetNode = lc.getContinueTo(labelName);
|
||||
|
||||
if (targetNode == null) {
|
||||
throw error(AbstractParser.message("illegal.continue.stmt"), continueToken);
|
||||
@ -1445,7 +1444,7 @@ loop:
|
||||
endOfLine();
|
||||
|
||||
// Construct and add CONTINUE node.
|
||||
appendStatement(new ContinueNode(continueLine, continueToken, finish, label == null ? null : new IdentNode(label)));
|
||||
appendStatement(new ContinueNode(continueLine, continueToken, finish, labelName));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1485,8 +1484,8 @@ loop:
|
||||
|
||||
//either an explicit label - then get its node or just a "break" - get first breakable
|
||||
//targetNode is what we are breaking out from.
|
||||
final IdentNode label = labelNode == null ? null : labelNode.getLabel();
|
||||
final BreakableNode targetNode = lc.getBreakable(label);
|
||||
final String labelName = labelNode == null ? null : labelNode.getLabelName();
|
||||
final BreakableNode targetNode = lc.getBreakable(labelName);
|
||||
if (targetNode == null) {
|
||||
throw error(AbstractParser.message("illegal.break.stmt"), breakToken);
|
||||
}
|
||||
@ -1494,7 +1493,7 @@ loop:
|
||||
endOfLine();
|
||||
|
||||
// Construct and add BREAK node.
|
||||
appendStatement(new BreakNode(breakLine, breakToken, finish, label == null ? null : new IdentNode(label)));
|
||||
appendStatement(new BreakNode(breakLine, breakToken, finish, labelName));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1721,7 +1720,7 @@ loop:
|
||||
throw error(AbstractParser.message("duplicate.label", ident.getName()), labelToken);
|
||||
}
|
||||
|
||||
LabelNode labelNode = new LabelNode(line, labelToken, finish, ident, null);
|
||||
LabelNode labelNode = new LabelNode(line, labelToken, finish, ident.getName(), null);
|
||||
try {
|
||||
lc.push(labelNode);
|
||||
labelNode = labelNode.setBody(lc, getStatement());
|
||||
@ -1768,7 +1767,7 @@ loop:
|
||||
|
||||
endOfLine();
|
||||
|
||||
appendStatement(new ThrowNode(throwLine, throwToken, finish, expression, 0));
|
||||
appendStatement(new ThrowNode(throwLine, throwToken, finish, expression, false));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1831,7 +1830,7 @@ loop:
|
||||
try {
|
||||
// Get CATCH body.
|
||||
final Block catchBody = getBlock(true);
|
||||
final CatchNode catchNode = new CatchNode(catchLine, catchToken, finish, exception, ifExpression, catchBody, 0);
|
||||
final CatchNode catchNode = new CatchNode(catchLine, catchToken, finish, exception, ifExpression, catchBody, false);
|
||||
appendStatement(catchNode);
|
||||
} finally {
|
||||
catchBlock = restoreBlock(catchBlock);
|
||||
@ -1993,7 +1992,7 @@ loop:
|
||||
// Skip ending of edit string expression.
|
||||
expect(RBRACE);
|
||||
|
||||
return new CallNode(primaryLine, primaryToken, finish, execIdent, arguments);
|
||||
return new CallNode(primaryLine, primaryToken, finish, execIdent, arguments, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2343,7 +2342,7 @@ loop:
|
||||
detectSpecialFunction((IdentNode)lhs);
|
||||
}
|
||||
|
||||
lhs = new CallNode(callLine, callToken, finish, lhs, arguments);
|
||||
lhs = new CallNode(callLine, callToken, finish, lhs, arguments, false);
|
||||
}
|
||||
|
||||
loop:
|
||||
@ -2358,7 +2357,7 @@ loop:
|
||||
final List<Expression> arguments = optimizeList(argumentList());
|
||||
|
||||
// Create call node.
|
||||
lhs = new CallNode(callLine, callToken, finish, lhs, arguments);
|
||||
lhs = new CallNode(callLine, callToken, finish, lhs, arguments, false);
|
||||
|
||||
break;
|
||||
|
||||
@ -2381,7 +2380,7 @@ loop:
|
||||
final IdentNode property = getIdentifierName();
|
||||
|
||||
// Create property access node.
|
||||
lhs = new AccessNode(callToken, finish, lhs, property);
|
||||
lhs = new AccessNode(callToken, finish, lhs, property.getName());
|
||||
|
||||
break;
|
||||
|
||||
@ -2437,7 +2436,7 @@ loop:
|
||||
arguments.add(objectLiteral());
|
||||
}
|
||||
|
||||
final CallNode callNode = new CallNode(callLine, constructor.getToken(), finish, constructor, optimizeList(arguments));
|
||||
final CallNode callNode = new CallNode(callLine, constructor.getToken(), finish, constructor, optimizeList(arguments), true);
|
||||
|
||||
return new UnaryNode(newToken, callNode);
|
||||
}
|
||||
@ -2461,7 +2460,7 @@ loop:
|
||||
|
||||
switch (type) {
|
||||
case NEW:
|
||||
// Get new exppression.
|
||||
// Get new expression.
|
||||
lhs = newExpression();
|
||||
break;
|
||||
|
||||
@ -2505,7 +2504,7 @@ loop:
|
||||
final IdentNode property = getIdentifierName();
|
||||
|
||||
// Create property access node.
|
||||
lhs = new AccessNode(callToken, finish, lhs, property);
|
||||
lhs = new AccessNode(callToken, finish, lhs, property.getName());
|
||||
|
||||
break;
|
||||
|
||||
@ -2709,7 +2708,7 @@ loop:
|
||||
return ((PropertyKey)nameExpr).getPropertyName();
|
||||
} else if(nameExpr instanceof AccessNode) {
|
||||
markDefaultNameUsed();
|
||||
return ((AccessNode)nameExpr).getProperty().getName();
|
||||
return ((AccessNode)nameExpr).getProperty();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@ -2751,7 +2750,7 @@ loop:
|
||||
*/
|
||||
private List<IdentNode> formalParameterList(final TokenType endType) {
|
||||
// Prepare to gather parameters.
|
||||
final List<IdentNode> parameters = new ArrayList<>();
|
||||
final ArrayList<IdentNode> parameters = new ArrayList<>();
|
||||
// Track commas.
|
||||
boolean first = true;
|
||||
|
||||
@ -2772,6 +2771,7 @@ loop:
|
||||
parameters.add(ident);
|
||||
}
|
||||
|
||||
parameters.trimToSize();
|
||||
return parameters;
|
||||
}
|
||||
|
||||
@ -3089,6 +3089,10 @@ loop:
|
||||
return expression(unaryExpression(), COMMARIGHT.getPrecedence(), false);
|
||||
}
|
||||
|
||||
private JoinPredecessorExpression joinPredecessorExpression() {
|
||||
return new JoinPredecessorExpression(expression());
|
||||
}
|
||||
|
||||
private Expression expression(final Expression exprLhs, final int minPrecedence, final boolean noIn) {
|
||||
// Get the precedence of the next operator.
|
||||
int precedence = type.getPrecedence();
|
||||
@ -3105,15 +3109,15 @@ loop:
|
||||
|
||||
// Pass expression. Middle expression of a conditional expression can be a "in"
|
||||
// expression - even in the contexts where "in" is not permitted.
|
||||
final Expression rhs = expression(unaryExpression(), ASSIGN.getPrecedence(), false);
|
||||
final Expression trueExpr = expression(unaryExpression(), ASSIGN.getPrecedence(), false);
|
||||
|
||||
expect(COLON);
|
||||
|
||||
// Fail expression.
|
||||
final Expression third = expression(unaryExpression(), ASSIGN.getPrecedence(), noIn);
|
||||
final Expression falseExpr = expression(unaryExpression(), ASSIGN.getPrecedence(), noIn);
|
||||
|
||||
// Build up node.
|
||||
lhs = new TernaryNode(op, lhs, rhs, third);
|
||||
lhs = new TernaryNode(op, lhs, new JoinPredecessorExpression(trueExpr), new JoinPredecessorExpression(falseExpr));
|
||||
} else {
|
||||
// Skip operator.
|
||||
next();
|
||||
|
||||
@ -25,7 +25,6 @@
|
||||
|
||||
package jdk.nashorn.internal.parser;
|
||||
|
||||
import java.util.Locale;
|
||||
import static jdk.nashorn.internal.parser.TokenKind.BINARY;
|
||||
import static jdk.nashorn.internal.parser.TokenKind.BRACKET;
|
||||
import static jdk.nashorn.internal.parser.TokenKind.FUTURE;
|
||||
@ -36,6 +35,8 @@ import static jdk.nashorn.internal.parser.TokenKind.LITERAL;
|
||||
import static jdk.nashorn.internal.parser.TokenKind.SPECIAL;
|
||||
import static jdk.nashorn.internal.parser.TokenKind.UNARY;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Description of all the JavaScript tokens.
|
||||
*/
|
||||
@ -182,7 +183,6 @@ public enum TokenType {
|
||||
ARRAY (LITERAL, null),
|
||||
|
||||
COMMALEFT (IR, null),
|
||||
DISCARD (IR, null),
|
||||
DECPOSTFIX (IR, null),
|
||||
INCPOSTFIX (IR, null);
|
||||
|
||||
|
||||
@ -42,7 +42,6 @@ import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.SwitchPoint;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import jdk.nashorn.internal.codegen.ObjectClassGenerator;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.lookup.Lookup;
|
||||
@ -367,15 +366,7 @@ public class AccessorProperty extends Property {
|
||||
* Initialize the type of a property
|
||||
*/
|
||||
protected final void initializeType() {
|
||||
Class<?> initialType = null;
|
||||
if (OBJECT_FIELDS_ONLY) {
|
||||
initialType = Object.class;
|
||||
} else {
|
||||
if (!canBeUndefined()) { //todo if !canBeUndefined it means that we have an exact initialType
|
||||
initialType = int.class;
|
||||
}
|
||||
}
|
||||
setCurrentType(initialType);
|
||||
setCurrentType(OBJECT_FIELDS_ONLY ? Object.class : null);
|
||||
}
|
||||
|
||||
private static MethodHandle bindTo(final MethodHandle mh, final Object receiver) {
|
||||
|
||||
@ -37,7 +37,6 @@ import java.lang.invoke.SwitchPoint;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import jdk.nashorn.internal.codegen.types.ArrayType;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.FunctionNode;
|
||||
@ -68,7 +67,6 @@ final class CompiledFunction {
|
||||
private MethodHandle constructor;
|
||||
private OptimismInfo optimismInfo;
|
||||
private int flags; // from FunctionNode
|
||||
private boolean applyToCall;
|
||||
|
||||
CompiledFunction(final MethodHandle invoker) {
|
||||
this(invoker, null);
|
||||
@ -91,7 +89,7 @@ final class CompiledFunction {
|
||||
CompiledFunction(final MethodHandle invoker, final RecompilableScriptFunctionData functionData, final int flags) {
|
||||
this(invoker, null, functionData.getLogger());
|
||||
this.flags = flags;
|
||||
if ((flags & FunctionNode.IS_OPTIMISTIC) != 0) {
|
||||
if ((flags & FunctionNode.IS_DEOPTIMIZABLE) != 0) {
|
||||
optimismInfo = new OptimismInfo(functionData);
|
||||
} else {
|
||||
optimismInfo = null;
|
||||
@ -102,12 +100,8 @@ final class CompiledFunction {
|
||||
return flags;
|
||||
}
|
||||
|
||||
void setIsApplyToCall() {
|
||||
applyToCall = true;
|
||||
}
|
||||
|
||||
boolean isApplyToCall() {
|
||||
return applyToCall;
|
||||
return (flags & FunctionNode.HAS_APPLY_TO_CALL_SPECIALIZATION) != 0;
|
||||
}
|
||||
|
||||
boolean isVarArg() {
|
||||
@ -152,10 +146,6 @@ final class CompiledFunction {
|
||||
return constructor;
|
||||
}
|
||||
|
||||
MethodHandle getInvoker() {
|
||||
return invoker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a version of the invoker intended for a pessimistic caller (return type is Object, no caller optimistic
|
||||
* program point available).
|
||||
@ -480,10 +470,10 @@ final class CompiledFunction {
|
||||
* function has no optimistic assumptions.
|
||||
*/
|
||||
SwitchPoint getOptimisticAssumptionsSwitchPoint() {
|
||||
return isOptimistic() ? optimismInfo.optimisticAssumptions : null;
|
||||
return canBeDeoptimized() ? optimismInfo.optimisticAssumptions : null;
|
||||
}
|
||||
|
||||
boolean isOptimistic() {
|
||||
boolean canBeDeoptimized() {
|
||||
return optimismInfo != null;
|
||||
}
|
||||
|
||||
@ -492,7 +482,7 @@ final class CompiledFunction {
|
||||
|
||||
// If compiled function is not optimistic, it can't ever change its invoker/constructor, so just return them
|
||||
// directly.
|
||||
if(!isOptimistic()) {
|
||||
if(!canBeDeoptimized()) {
|
||||
return handle;
|
||||
}
|
||||
|
||||
@ -518,7 +508,7 @@ final class CompiledFunction {
|
||||
final MethodHandle relink = MethodHandles.insertArguments(RELINK_COMPOSABLE_INVOKER, 0, cs, inv, constructor);
|
||||
target = assumptions.guardWithTest(handle, MethodHandles.foldArguments(cs.dynamicInvoker(), relink));
|
||||
}
|
||||
cs.setTarget(target);
|
||||
cs.setTarget(target.asType(cs.type()));
|
||||
}
|
||||
|
||||
private MethodHandle getInvokerOrConstructor(final boolean selectCtor) {
|
||||
@ -526,12 +516,13 @@ final class CompiledFunction {
|
||||
}
|
||||
|
||||
MethodHandle createInvoker(final Class<?> callSiteReturnType, final int callerProgramPoint) {
|
||||
final boolean isOptimistic = isOptimistic();
|
||||
final boolean isOptimistic = canBeDeoptimized();
|
||||
MethodHandle handleRewriteException = isOptimistic ? createRewriteExceptionHandler() : null;
|
||||
|
||||
MethodHandle inv = invoker;
|
||||
if(isValid(callerProgramPoint)) {
|
||||
inv = OptimisticReturnFilters.filterOptimisticReturnValue(inv, callSiteReturnType, callerProgramPoint);
|
||||
inv = changeReturnType(inv, callSiteReturnType);
|
||||
if(callSiteReturnType.isPrimitive() && handleRewriteException != null) {
|
||||
// because handleRewriteException always returns Object
|
||||
handleRewriteException = OptimisticReturnFilters.filterOptimisticReturnValue(handleRewriteException,
|
||||
@ -575,32 +566,30 @@ final class CompiledFunction {
|
||||
* @return the method handle for the rest-of method, for folding composition.
|
||||
*/
|
||||
private MethodHandle handleRewriteException(final OptimismInfo oldOptimismInfo, final RewriteException re) {
|
||||
if (log.isEnabled()) {
|
||||
log.info(new RecompilationEvent(Level.INFO, re, re.getReturnValueNonDestructive()), "\tRewriteException ", re.getMessageShort());
|
||||
}
|
||||
|
||||
final MethodType type = type();
|
||||
// Compiler needs a call site type as its input, which always has a callee parameter, so we must add it if
|
||||
// this function doesn't have a callee parameter.
|
||||
final MethodType callSiteType = type.parameterType(0) == ScriptFunction.class ? type : type.insertParameterTypes(0, ScriptFunction.class);
|
||||
|
||||
final FunctionNode fn = oldOptimismInfo.recompile(callSiteType, re);
|
||||
if (log.isEnabled()) {
|
||||
log.info(new RecompilationEvent(Level.INFO, re, re.getReturnValueNonDestructive()), "\tRewriteException ", re.getMessageShort());
|
||||
}
|
||||
|
||||
// It didn't necessarily recompile, e.g. for an outer invocation of a recursive function if we already
|
||||
// recompiled a deoptimized version for an inner invocation.
|
||||
|
||||
final boolean isOptimistic;
|
||||
final boolean canBeDeoptimized;
|
||||
if (fn != null) {
|
||||
//is recompiled
|
||||
assert optimismInfo == oldOptimismInfo;
|
||||
isOptimistic = fn.isOptimistic();
|
||||
canBeDeoptimized = fn.canBeDeoptimized();
|
||||
if (log.isEnabled()) {
|
||||
log.info("Recompiled '", fn.getName(), "' (", Debug.id(this), ")", isOptimistic ? " remains optimistic." : " is no longer optimistic.");
|
||||
log.info("Recompiled '", fn.getName(), "' (", Debug.id(this), ")", canBeDeoptimized ? " can still be deoptimized." : " is completely deoptimized.");
|
||||
}
|
||||
final MethodHandle newInvoker = oldOptimismInfo.data.lookup(fn);
|
||||
invoker = newInvoker.asType(type.changeReturnType(newInvoker.type().returnType()));
|
||||
constructor = null; // Will be regenerated when needed
|
||||
// Note that we only adjust the switch point after we set the invoker/constructor. This is important.
|
||||
if (isOptimistic) {
|
||||
if (canBeDeoptimized) {
|
||||
// Otherwise, set a new switch point.
|
||||
oldOptimismInfo.newOptimisticAssumptions();
|
||||
} else {
|
||||
@ -608,13 +597,15 @@ final class CompiledFunction {
|
||||
optimismInfo = null;
|
||||
}
|
||||
} else {
|
||||
isOptimistic = isOptimistic();
|
||||
assert !isOptimistic || optimismInfo == oldOptimismInfo;
|
||||
// It didn't necessarily recompile, e.g. for an outer invocation of a recursive function if we already
|
||||
// recompiled a deoptimized version for an inner invocation.
|
||||
canBeDeoptimized = canBeDeoptimized();
|
||||
assert !canBeDeoptimized || optimismInfo == oldOptimismInfo;
|
||||
}
|
||||
|
||||
final MethodHandle restOf = changeReturnType(oldOptimismInfo.compileRestOfMethod(callSiteType, re), Object.class);
|
||||
// If rest-of is itself optimistic, we must make sure that we can repeat a deoptimization if it, too hits an exception.
|
||||
return isOptimistic ? MH.catchException(restOf, RewriteException.class, createRewriteExceptionHandler()) : restOf;
|
||||
return canBeDeoptimized ? MH.catchException(restOf, RewriteException.class, createRewriteExceptionHandler()) : restOf;
|
||||
}
|
||||
|
||||
private static class OptimismInfo {
|
||||
|
||||
@ -145,13 +145,16 @@ public enum JSType {
|
||||
/** Div exact wrapper for potentially integer division that turns into float point */
|
||||
public static final Call DIV_EXACT = staticCall(JSTYPE_LOOKUP, JSType.class, "divExact", int.class, int.class, int.class, int.class);
|
||||
|
||||
/** Mod exact wrapper for potentially integer remainders that turns into float point */
|
||||
public static final Call REM_EXACT = staticCall(JSTYPE_LOOKUP, JSType.class, "remExact", int.class, int.class, int.class, int.class);
|
||||
|
||||
/** Decrement exact wrapper for potentially overflowing integer operations */
|
||||
public static final Call DECREMENT_EXACT = staticCall(JSTYPE_LOOKUP, JSType.class, "decrementExact", int.class, int.class, int.class);
|
||||
|
||||
/** Increment exact wrapper for potentially overflowing integer operations */
|
||||
public static final Call INCREMENT_EXACT = staticCall(JSTYPE_LOOKUP, JSType.class, "incrementExact", int.class, int.class, int.class);
|
||||
|
||||
/** Negate exact exact wrapper for potentially overflowing long operations */
|
||||
/** Negate exact exact wrapper for potentially overflowing integer operations */
|
||||
public static final Call NEGATE_EXACT = staticCall(JSTYPE_LOOKUP, JSType.class, "negateExact", int.class, int.class, int.class);
|
||||
|
||||
/** Add exact wrapper for potentially overflowing long operations */
|
||||
@ -166,6 +169,9 @@ public enum JSType {
|
||||
/** Div exact wrapper for potentially integer division that turns into float point */
|
||||
public static final Call DIV_EXACT_LONG = staticCall(JSTYPE_LOOKUP, JSType.class, "divExact", long.class, long.class, long.class, int.class);
|
||||
|
||||
/** Mod exact wrapper for potentially integer remainders that turns into float point */
|
||||
public static final Call REM_EXACT_LONG = staticCall(JSTYPE_LOOKUP, JSType.class, "remExact", long.class, long.class, long.class, int.class);
|
||||
|
||||
/** Decrement exact wrapper for potentially overflowing long operations */
|
||||
public static final Call DECREMENT_EXACT_LONG = staticCall(JSTYPE_LOOKUP, JSType.class, "decrementExact", long.class, long.class, int.class);
|
||||
|
||||
@ -1417,6 +1423,24 @@ public enum JSType {
|
||||
throw new UnwarrantedOptimismException((double)x / (double)y, programPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for modExact. Throws UnwarrantedOptimismException if the modulo can't be represented as int.
|
||||
*
|
||||
* @param x first term
|
||||
* @param y second term
|
||||
* @param programPoint program point id
|
||||
* @return the result
|
||||
* @throws UnwarrantedOptimismException if the modulo can't be represented as int.
|
||||
*/
|
||||
public static int remExact(final int x, final int y, final int programPoint) throws UnwarrantedOptimismException {
|
||||
try {
|
||||
return x % y;
|
||||
} catch (final ArithmeticException e) {
|
||||
assert y == 0; // Only mod by zero anticipated
|
||||
throw new UnwarrantedOptimismException(Double.NaN, programPoint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for divExact. Throws UnwarrantedOptimismException if the result of the division can't be represented as
|
||||
* long.
|
||||
@ -1442,6 +1466,24 @@ public enum JSType {
|
||||
throw new UnwarrantedOptimismException((double)x / (double)y, programPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for modExact. Throws UnwarrantedOptimismException if the modulo can't be represented as int.
|
||||
*
|
||||
* @param x first term
|
||||
* @param y second term
|
||||
* @param programPoint program point id
|
||||
* @return the result
|
||||
* @throws UnwarrantedOptimismException if the modulo can't be represented as int.
|
||||
*/
|
||||
public static long remExact(final long x, final long y, final int programPoint) throws UnwarrantedOptimismException {
|
||||
try {
|
||||
return x % y;
|
||||
} catch (final ArithmeticException e) {
|
||||
assert y == 0L; // Only mod by zero anticipated
|
||||
throw new UnwarrantedOptimismException(Double.NaN, programPoint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for decrementExact
|
||||
*
|
||||
|
||||
@ -49,6 +49,7 @@ public final class OptimisticReturnFilters {
|
||||
private static final int BOOLEAN_TYPE_INDEX;
|
||||
private static final int CHAR_TYPE_INDEX;
|
||||
private static final int FLOAT_TYPE_INDEX;
|
||||
private static final int VOID_TYPE_INDEX;
|
||||
|
||||
static {
|
||||
final MethodHandle INT_DOUBLE = findOwnMH("ensureInt", int.class, double.class, int.class);
|
||||
@ -57,11 +58,13 @@ public final class OptimisticReturnFilters {
|
||||
findOwnMH("ensureInt", int.class, long.class, int.class),
|
||||
INT_DOUBLE,
|
||||
findOwnMH("ensureInt", int.class, Object.class, int.class),
|
||||
findOwnMH("ensureInt", int.class, int.class),
|
||||
findOwnMH("ensureInt", int.class, boolean.class, int.class),
|
||||
findOwnMH("ensureInt", int.class, char.class, int.class),
|
||||
INT_DOUBLE.asType(INT_DOUBLE.type().changeParameterType(0, float.class)),
|
||||
};
|
||||
|
||||
VOID_TYPE_INDEX = ENSURE_INT.length - 4;
|
||||
BOOLEAN_TYPE_INDEX = ENSURE_INT.length - 3;
|
||||
CHAR_TYPE_INDEX = ENSURE_INT.length - 2;
|
||||
FLOAT_TYPE_INDEX = ENSURE_INT.length - 1;
|
||||
@ -72,6 +75,7 @@ public final class OptimisticReturnFilters {
|
||||
null,
|
||||
LONG_DOUBLE,
|
||||
findOwnMH("ensureLong", long.class, Object.class, int.class),
|
||||
ENSURE_INT[VOID_TYPE_INDEX].asType(ENSURE_INT[VOID_TYPE_INDEX].type().changeReturnType(long.class)),
|
||||
ENSURE_INT[BOOLEAN_TYPE_INDEX].asType(ENSURE_INT[BOOLEAN_TYPE_INDEX].type().changeReturnType(long.class)),
|
||||
ENSURE_INT[CHAR_TYPE_INDEX].asType(ENSURE_INT[CHAR_TYPE_INDEX].type().changeReturnType(long.class)),
|
||||
LONG_DOUBLE.asType(LONG_DOUBLE.type().changeParameterType(0, float.class)),
|
||||
@ -82,6 +86,7 @@ public final class OptimisticReturnFilters {
|
||||
null,
|
||||
null,
|
||||
findOwnMH("ensureNumber", double.class, Object.class, int.class),
|
||||
ENSURE_INT[VOID_TYPE_INDEX].asType(ENSURE_INT[VOID_TYPE_INDEX].type().changeReturnType(double.class)),
|
||||
ENSURE_INT[BOOLEAN_TYPE_INDEX].asType(ENSURE_INT[BOOLEAN_TYPE_INDEX].type().changeReturnType(double.class)),
|
||||
ENSURE_INT[CHAR_TYPE_INDEX].asType(ENSURE_INT[CHAR_TYPE_INDEX].type().changeReturnType(double.class)),
|
||||
null
|
||||
@ -108,12 +113,12 @@ public final class OptimisticReturnFilters {
|
||||
}
|
||||
|
||||
final MethodHandle guard = getOptimisticTypeGuard(expectedReturnType, actualReturnType);
|
||||
return guard == null ? mh : MH.filterReturnValue(mh, MH.insertArguments(guard, 1, programPoint));
|
||||
return guard == null ? mh : MH.filterReturnValue(mh, MH.insertArguments(guard, guard.type().parameterCount() - 1, programPoint));
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a guarded invocation and a callsite descriptor, perform return value filtering
|
||||
* according to the optimistic type coercion rules, using the return value from the deccriptor
|
||||
* according to the optimistic type coercion rules, using the return value from the descriptor
|
||||
* @param inv the invocation
|
||||
* @param desc the descriptor
|
||||
* @return filtered invocation
|
||||
@ -156,6 +161,8 @@ public final class OptimisticReturnFilters {
|
||||
return accTypeIndex;
|
||||
} else if(provable == boolean.class) {
|
||||
return BOOLEAN_TYPE_INDEX;
|
||||
} else if(provable == void.class) {
|
||||
return VOID_TYPE_INDEX;
|
||||
} else if(provable == byte.class || provable == short.class) {
|
||||
return 0; // never needs a guard, as it's assignable to int
|
||||
} else if(provable == char.class) {
|
||||
@ -215,6 +222,12 @@ public final class OptimisticReturnFilters {
|
||||
throw new UnwarrantedOptimismException(arg, programPoint, Type.OBJECT);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static int ensureInt(final int programPoint) {
|
||||
// Turns a void into UNDEFINED
|
||||
throw new UnwarrantedOptimismException(ScriptRuntime.UNDEFINED, programPoint, Type.OBJECT);
|
||||
}
|
||||
|
||||
private static long ensureLong(final double arg, final int programPoint) {
|
||||
if (JSType.isRepresentableAsLong(arg) && !JSType.isNegativeZero(arg)) {
|
||||
return (long)arg;
|
||||
|
||||
@ -73,11 +73,8 @@ public abstract class Property {
|
||||
/** Is parameter accessed thru arguments? */
|
||||
public static final int HAS_ARGUMENTS = 1 << 4;
|
||||
|
||||
/** Can this property be undefined? */
|
||||
public static final int CAN_BE_UNDEFINED = 1 << 5;
|
||||
|
||||
/** Is this a function declaration property ? */
|
||||
public static final int IS_FUNCTION_DECLARATION = 1 << 6;
|
||||
public static final int IS_FUNCTION_DECLARATION = 1 << 5;
|
||||
|
||||
/**
|
||||
* Is this is a primitive field given to us by Nasgen, i.e.
|
||||
@ -85,7 +82,7 @@ public abstract class Property {
|
||||
* is narrower than object, e.g. Math.PI which is declared
|
||||
* as a double
|
||||
*/
|
||||
public static final int IS_NASGEN_PRIMITIVE = 1 << 7;
|
||||
public static final int IS_NASGEN_PRIMITIVE = 1 << 6;
|
||||
|
||||
/** Is this property bound to a receiver? This means get/set operations will be delegated to
|
||||
* a statically defined object instead of the object passed as callsite parameter. */
|
||||
@ -653,18 +650,6 @@ public abstract class Property {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this property can be primitive. This is a conservative
|
||||
* analysis result, so {@code true} might mean that it can still be
|
||||
* defined, but it will never say that a property can not be undefined
|
||||
* if it can
|
||||
*
|
||||
* @return can be undefined status
|
||||
*/
|
||||
public boolean canBeUndefined() {
|
||||
return (flags & CAN_BE_UNDEFINED) == CAN_BE_UNDEFINED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this property represents a function declaration.
|
||||
* @return whether this property is a function declaration or not.
|
||||
|
||||
@ -36,7 +36,6 @@ import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import jdk.internal.dynalink.support.NameCodec;
|
||||
import jdk.nashorn.internal.codegen.ApplySpecialization;
|
||||
import jdk.nashorn.internal.codegen.CompilationEnvironment;
|
||||
@ -50,6 +49,7 @@ import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.FunctionNode;
|
||||
import jdk.nashorn.internal.ir.LexicalContext;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
import jdk.nashorn.internal.objects.Global;
|
||||
import jdk.nashorn.internal.parser.Parser;
|
||||
import jdk.nashorn.internal.parser.Token;
|
||||
import jdk.nashorn.internal.parser.TokenType;
|
||||
@ -405,10 +405,6 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
return new ApplyToCallTransform(this, callSiteType);
|
||||
}
|
||||
|
||||
private FunctionNode compileTypeSpecialization(final MethodType actualCallSiteType, final ScriptObject runtimeScope, final FunctionNodeTransform tr) {
|
||||
return compile(actualCallSiteType, null, runtimeScope, "Type specialized compilation", tr);
|
||||
}
|
||||
|
||||
private ParamTypeMap typeMap(final MethodType fnCallSiteType, final FunctionNodeTransform tr) {
|
||||
if (isVariableArity() && !tr.wasTransformed()) {
|
||||
return null;
|
||||
@ -444,8 +440,17 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
compiler.install(fn);
|
||||
|
||||
// look up the rest of method
|
||||
final MethodHandle mh = lookupWithExplicitType(fn, MethodType.methodType(fn.getReturnType().getTypeClass(), RewriteException.class));
|
||||
return mh;
|
||||
return lookupWithExplicitType(fn, MethodType.methodType(fn.getReturnType().getTypeClass(), RewriteException.class));
|
||||
}
|
||||
|
||||
private FunctionNode compileTypeSpecialization(final MethodType actualCallSiteType, final ScriptObject runtimeScope, final FunctionNodeTransform tr) {
|
||||
// We're creating an empty script object for holding local variables. AssignSymbols will populate it with
|
||||
// explicit Undefined values for undefined local variables (see AssignSymbols#defineSymbol() and
|
||||
// CompilationEnvironment#declareLocalSymbol()).
|
||||
final ScriptObject locals = Global.newEmptyInstance();
|
||||
locals.setProto(runtimeScope);
|
||||
|
||||
return compile(actualCallSiteType, null, locals, "Type specialized compilation", tr);
|
||||
}
|
||||
|
||||
FunctionNode compile(final MethodType actualCallSiteType, final Map<Integer, Type> invalidatedProgramPoints, final ScriptObject runtimeScope, final String reason, final FunctionNodeTransform tr) {
|
||||
@ -630,7 +635,6 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
final FunctionNode fn = compileTypeSpecialization(callSiteType, runtimeScope, new ApplyToCallTransform(this, callSiteType));
|
||||
if (fn.hasOptimisticApplyToCall()) { //did the specialization work
|
||||
existingBest = addCode(fn, callSiteType);
|
||||
existingBest.setIsApplyToCall();
|
||||
}
|
||||
}
|
||||
|
||||
@ -711,14 +715,13 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
public boolean isGlobalSymbol(final FunctionNode functionNode, final String symbolName) {
|
||||
RecompilableScriptFunctionData data = getScriptFunctionData(functionNode.getId());
|
||||
assert data != null;
|
||||
final RecompilableScriptFunctionData program = getProgram();
|
||||
|
||||
while (data != program) {
|
||||
do {
|
||||
if (data.hasInternalSymbol(symbolName)) {
|
||||
return false;
|
||||
}
|
||||
data = data.getParent();
|
||||
}
|
||||
} while(data != null);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -34,8 +34,9 @@ import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodHandles.Lookup;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Arrays;
|
||||
|
||||
import jdk.nashorn.internal.codegen.CompilerConstants;
|
||||
import jdk.nashorn.internal.codegen.CompilerConstants.Call;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.lookup.MethodHandleFactory;
|
||||
@ -52,45 +53,61 @@ public class RewriteException extends Exception {
|
||||
// Runtime scope in effect at the time of the compilation. Used to evaluate types of expressions and prevent overly
|
||||
// optimistic assumptions (which will lead to unnecessary deoptimizing recompilations).
|
||||
private ScriptObject runtimeScope;
|
||||
//contents of bytecode slots
|
||||
|
||||
// Contents of bytecode slots
|
||||
private Object[] byteCodeSlots;
|
||||
|
||||
private final int[] previousContinuationEntryPoints;
|
||||
|
||||
/** Methodhandle for getting the contents of the bytecode slots in the exception */
|
||||
/** Call for getting the contents of the bytecode slots in the exception */
|
||||
public static final Call GET_BYTECODE_SLOTS = virtualCallNoLookup(RewriteException.class, "getByteCodeSlots", Object[].class);
|
||||
/** Methodhandle for getting the program point in the exception */
|
||||
/** Call for getting the program point in the exception */
|
||||
public static final Call GET_PROGRAM_POINT = virtualCallNoLookup(RewriteException.class, "getProgramPoint", int.class);
|
||||
/** Methodhandle for getting the return value for the exception */
|
||||
/** Call for getting the return value for the exception */
|
||||
public static final Call GET_RETURN_VALUE = virtualCallNoLookup(RewriteException.class, "getReturnValueDestructive", Object.class);
|
||||
/** Methodhandle for the populate array bootstrap */
|
||||
/** Call for the populate array bootstrap */
|
||||
public static final Call BOOTSTRAP = staticCallNoLookup(RewriteException.class, "populateArrayBootstrap", CallSite.class, Lookup.class, String.class, MethodType.class, int.class);
|
||||
|
||||
/** Methodhandle for populating an array with local variable state */
|
||||
/** Call for populating an array with local variable state */
|
||||
private static final Call POPULATE_ARRAY = staticCall(MethodHandles.lookup(), RewriteException.class, "populateArray", Object[].class, Object[].class, int.class, Object[].class);
|
||||
|
||||
/** Call for converting an array to a long array. */
|
||||
public static final Call TO_LONG_ARRAY = staticCallNoLookup(RewriteException.class, "toLongArray", long[].class, Object.class, RewriteException.class);
|
||||
/** Call for converting an array to a double array. */
|
||||
public static final Call TO_DOUBLE_ARRAY = staticCallNoLookup(RewriteException.class, "toDoubleArray", double[].class, Object.class, RewriteException.class);
|
||||
/** Call for converting an array to an object array. */
|
||||
public static final Call TO_OBJECT_ARRAY = staticCallNoLookup(RewriteException.class, "toObjectArray", Object[].class, Object.class, RewriteException.class);
|
||||
/** Call for converting an object to null if it can't be represented as an instance of a class. */
|
||||
public static final Call INSTANCE_OR_NULL = staticCallNoLookup(RewriteException.class, "instanceOrNull", Object.class, Object.class, Class.class);
|
||||
/** Call for asserting the length of an array. */
|
||||
public static final Call ASSERT_ARRAY_LENGTH = staticCallNoLookup(RewriteException.class, "assertArrayLength", void.class, Object[].class, int.class);
|
||||
|
||||
/**
|
||||
* Constructor for a rewrite exception thrown from an optimistic function.
|
||||
* @param e the {@link UnwarrantedOptimismException} that triggered this exception.
|
||||
* @param byteCodeSlots contents of local variable slots at the time of rewrite at the program point
|
||||
* @param byteCodeSymbolNames byte code symbol names
|
||||
* @param runtimeScope the runtime scope used for known type information when recompiling
|
||||
* @param byteCodeSymbolNames the names of the variables in the {@code byteCodeSlots} parameter. The array might
|
||||
* have less elements, and some elements might be unnamed (the name can be null). The information is provided in an
|
||||
* effort to assist evaluation of expressions for their types by the compiler doing the deoptimizing recompilation,
|
||||
* and can thus be incomplete - the more complete it is, the more expressions can be evaluated by the compiler, and
|
||||
* the more unnecessary deoptimizing compilations can be avoided.
|
||||
*/
|
||||
public RewriteException(
|
||||
final UnwarrantedOptimismException e,
|
||||
final Object[] byteCodeSlots,
|
||||
final String[] byteCodeSymbolNames,
|
||||
final ScriptObject runtimeScope) {
|
||||
this(e, byteCodeSlots, byteCodeSymbolNames, runtimeScope, null);
|
||||
final String[] byteCodeSymbolNames) {
|
||||
this(e, byteCodeSlots, byteCodeSymbolNames, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for a rewrite exception thrown from a rest-of method.
|
||||
* @param e the {@link UnwarrantedOptimismException} that triggered this exception.
|
||||
* @param byteCodeSymbolNames byte code symbol names
|
||||
* @param byteCodeSlots contents of local variable slots at the time of rewrite at the program point
|
||||
* @param runtimeScope the runtime scope used for known type information when recompiling
|
||||
* @param byteCodeSymbolNames the names of the variables in the {@code byteCodeSlots} parameter. The array might
|
||||
* have less elements, and some elements might be unnamed (the name can be null). The information is provided in an
|
||||
* effort to assist evaluation of expressions for their types by the compiler doing the deoptimizing recompilation,
|
||||
* and can thus be incomplete - the more complete it is, the more expressions can be evaluated by the compiler, and
|
||||
* the more unnecessary deoptimizing compilations can be avoided.
|
||||
* @param previousContinuationEntryPoints an array of continuation entry points that were already executed during
|
||||
* one logical invocation of the function (a rest-of triggering a rest-of triggering a...)
|
||||
*/
|
||||
@ -98,11 +115,10 @@ public class RewriteException extends Exception {
|
||||
final UnwarrantedOptimismException e,
|
||||
final Object[] byteCodeSlots,
|
||||
final String[] byteCodeSymbolNames,
|
||||
final ScriptObject runtimeScope,
|
||||
final int[] previousContinuationEntryPoints) {
|
||||
super("", e, false, Context.DEBUG);
|
||||
this.byteCodeSlots = byteCodeSlots;
|
||||
this.runtimeScope = mergeSlotsWithScope(byteCodeSlots, byteCodeSymbolNames, runtimeScope);
|
||||
this.runtimeScope = mergeSlotsWithScope(byteCodeSlots, byteCodeSymbolNames);
|
||||
this.previousContinuationEntryPoints = previousContinuationEntryPoints;
|
||||
}
|
||||
|
||||
@ -122,14 +138,18 @@ public class RewriteException extends Exception {
|
||||
return new ConstantCallSite(mh);
|
||||
}
|
||||
|
||||
private static ScriptObject mergeSlotsWithScope(final Object[] byteCodeSlots, final String[] byteCodeSymbolNames,
|
||||
final ScriptObject runtimeScope) {
|
||||
private static ScriptObject mergeSlotsWithScope(final Object[] byteCodeSlots, final String[] byteCodeSymbolNames) {
|
||||
final ScriptObject locals = Global.newEmptyInstance();
|
||||
final int l = Math.min(byteCodeSlots.length, byteCodeSymbolNames.length);
|
||||
ScriptObject runtimeScope = null;
|
||||
final String scopeName = CompilerConstants.SCOPE.symbolName();
|
||||
for(int i = 0; i < l; ++i) {
|
||||
final String name = byteCodeSymbolNames[i];
|
||||
final Object value = byteCodeSlots[i];
|
||||
if(name != null) {
|
||||
if(scopeName.equals(name)) {
|
||||
assert runtimeScope == null;
|
||||
runtimeScope = (ScriptObject)value;
|
||||
} else if(name != null) {
|
||||
locals.set(name, value, true);
|
||||
}
|
||||
}
|
||||
@ -150,6 +170,114 @@ public class RewriteException extends Exception {
|
||||
return arrayToBePopluated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Continuation handler calls this method when a local variable carried over into the continuation is expected to be
|
||||
* a long array in the continued method. Normally, it will also be a long array in the original (interrupted by
|
||||
* deoptimization) method, but it can actually be an int array that underwent widening in the new code version.
|
||||
* @param obj the object that has to be converted into a long array
|
||||
* @param e the exception being processed
|
||||
* @return a long array
|
||||
*/
|
||||
public static long[] toLongArray(final Object obj, final RewriteException e) {
|
||||
if(obj instanceof long[]) {
|
||||
return (long[])obj;
|
||||
}
|
||||
|
||||
assert obj instanceof int[];
|
||||
|
||||
final int[] in = (int[])obj;
|
||||
final long[] out = new long[in.length];
|
||||
for(int i = 0; i < in.length; ++i) {
|
||||
out[i] = in[i];
|
||||
}
|
||||
return e.replaceByteCodeValue(in, out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Continuation handler calls this method when a local variable carried over into the continuation is expected to be
|
||||
* a double array in the continued method. Normally, it will also be a double array in the original (interrupted by
|
||||
* deoptimization) method, but it can actually be an int or long array that underwent widening in the new code version.
|
||||
* @param obj the object that has to be converted into a double array
|
||||
* @param e the exception being processed
|
||||
* @return a double array
|
||||
*/
|
||||
public static double[] toDoubleArray(final Object obj, final RewriteException e) {
|
||||
if(obj instanceof double[]) {
|
||||
return (double[])obj;
|
||||
}
|
||||
|
||||
assert obj instanceof int[] || obj instanceof long[];
|
||||
|
||||
final int l = Array.getLength(obj);
|
||||
final double[] out = new double[l];
|
||||
for(int i = 0; i < l; ++i) {
|
||||
out[i] = Array.getDouble(obj, i);
|
||||
}
|
||||
return e.replaceByteCodeValue(obj, out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Continuation handler calls this method when a local variable carried over into the continuation is expected to be
|
||||
* an Object array in the continued method. Normally, it will also be an Object array in the original (interrupted by
|
||||
* deoptimization) method, but it can actually be an int, long, or double array that underwent widening in the new
|
||||
* code version.
|
||||
* @param obj the object that has to be converted into an Object array
|
||||
* @param e the exception being processed
|
||||
* @return an Object array
|
||||
*/
|
||||
public static Object[] toObjectArray(final Object obj, final RewriteException e) {
|
||||
if(obj instanceof Object[]) {
|
||||
return (Object[])obj;
|
||||
}
|
||||
|
||||
assert obj instanceof int[] || obj instanceof long[] || obj instanceof double[] : obj.getClass().getName();
|
||||
|
||||
final int l = Array.getLength(obj);
|
||||
final Object[] out = new Object[l];
|
||||
for(int i = 0; i < l; ++i) {
|
||||
out[i] = Array.get(obj, i);
|
||||
}
|
||||
return e.replaceByteCodeValue(obj, out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Continuation handler calls this method when a local variable carried over into the continuation is expected to
|
||||
* have a certain type, but the value can have a different type coming from the deoptimized method as it was a dead
|
||||
* store. If we had precise liveness analysis, we wouldn't need this.
|
||||
* @param obj the object inspected for being of a particular type
|
||||
* @param clazz the type the object must belong to
|
||||
* @return the object if it belongs to the type, or null otherwise
|
||||
*/
|
||||
public static Object instanceOrNull(final Object obj, final Class<?> clazz) {
|
||||
return clazz.isInstance(obj) ? obj : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the length of an array. Invoked from continuation handler only when running with assertions enabled.
|
||||
* The array can, in fact, have more elements than asserted, but they must all have Undefined as their value. The
|
||||
* method does not test for the array having less elements than asserted, as those would already have caused an
|
||||
* {@code ArrayIndexOutOfBoundsException} to be thrown as the continuation handler attempts to access the missing
|
||||
* elements.
|
||||
* @param arr the array
|
||||
* @param length the asserted length
|
||||
*/
|
||||
public static void assertArrayLength(final Object[] arr, final int length) {
|
||||
for(int i = arr.length; i-- > length;) {
|
||||
if(arr[i] != ScriptRuntime.UNDEFINED) {
|
||||
throw new AssertionError(String.format("Expected array length %d, but it is %d", length, i + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T replaceByteCodeValue(final Object in, final T out) {
|
||||
for(int i = 0; i < byteCodeSlots.length; ++i) {
|
||||
if(byteCodeSlots[i] == in) {
|
||||
byteCodeSlots[i] = out;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private UnwarrantedOptimismException getUOE() {
|
||||
return (UnwarrantedOptimismException)getCause();
|
||||
}
|
||||
@ -218,6 +346,8 @@ public class RewriteException extends Exception {
|
||||
String str = returnValue.toString();
|
||||
if (returnValue instanceof String) {
|
||||
str = '\'' + str + '\'';
|
||||
} else if (returnValue instanceof Double) {
|
||||
str = str + 'd';
|
||||
} else if (returnValue instanceof Long) {
|
||||
str = str + 'l';
|
||||
}
|
||||
|
||||
@ -27,14 +27,11 @@ package jdk.nashorn.internal.runtime;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import jdk.nashorn.internal.codegen.Namespace;
|
||||
import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
|
||||
import jdk.nashorn.internal.runtime.options.KeyValueOption;
|
||||
@ -184,9 +181,6 @@ public final class ScriptEnvironment {
|
||||
/** is this environment in scripting mode? */
|
||||
public final boolean _scripting;
|
||||
|
||||
/** is the JIT allowed to specializ calls based on callsite types? */
|
||||
public final Set<String> _specialize_calls;
|
||||
|
||||
/** is this environment in strict mode? */
|
||||
public final boolean _strict;
|
||||
|
||||
@ -254,17 +248,6 @@ public final class ScriptEnvironment {
|
||||
_version = options.getBoolean("version");
|
||||
_verify_code = options.getBoolean("verify.code");
|
||||
|
||||
final String specialize = options.getString("specialize.calls");
|
||||
if (specialize == null) {
|
||||
_specialize_calls = null;
|
||||
} else {
|
||||
_specialize_calls = new HashSet<>();
|
||||
final StringTokenizer st = new StringTokenizer(specialize, ",");
|
||||
while (st.hasMoreElements()) {
|
||||
_specialize_calls.add(st.nextToken());
|
||||
}
|
||||
}
|
||||
|
||||
String dir = null;
|
||||
String func = null;
|
||||
final String pc = options.getString("print.code");
|
||||
@ -323,18 +306,6 @@ public final class ScriptEnvironment {
|
||||
this._loggers = lo == null ? new HashMap<String, LoggerInfo>() : lo.getLoggers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Can we specialize a particular method name?
|
||||
* @param functionName method name
|
||||
* @return true if we are allowed to generate versions of this method
|
||||
*/
|
||||
public boolean canSpecialize(final String functionName) {
|
||||
if (_specialize_calls == null) {
|
||||
return false;
|
||||
}
|
||||
return _specialize_calls.isEmpty() || _specialize_calls.contains(functionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the output stream for this environment
|
||||
* @return output print writer
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
|
||||
package jdk.nashorn.internal.runtime;
|
||||
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCall;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup;
|
||||
import static jdk.nashorn.internal.codegen.ObjectClassGenerator.OBJECT_FIELDS_ONLY;
|
||||
@ -62,7 +63,6 @@ import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import jdk.internal.dynalink.CallSiteDescriptor;
|
||||
import jdk.internal.dynalink.linker.GuardedInvocation;
|
||||
import jdk.internal.dynalink.linker.LinkRequest;
|
||||
@ -185,7 +185,7 @@ public abstract class ScriptObject implements PropertyAccess {
|
||||
public static final Call GET_PROTO_DEPTH = virtualCallNoLookup(ScriptObject.class, "getProto", ScriptObject.class, int.class);
|
||||
|
||||
/** Method handle for setting the proto of a ScriptObject */
|
||||
public static final Call SET_PROTO = virtualCallNoLookup(ScriptObject.class, "setInitialProto", void.class, ScriptObject.class);
|
||||
public static final Call SET_GLOBAL_OBJECT_PROTO = staticCallNoLookup(ScriptObject.class, "setGlobalObjectProto", void.class, ScriptObject.class);
|
||||
|
||||
/** Method handle for setting the proto of a ScriptObject after checking argument */
|
||||
public static final Call SET_PROTO_CHECK = virtualCallNoLookup(ScriptObject.class, "setProtoCheck", void.class, Object.class);
|
||||
@ -1261,6 +1261,14 @@ public abstract class ScriptObject implements PropertyAccess {
|
||||
this.proto = initialProto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked from generated bytecode to initialize the prototype of object literals to the global Object prototype.
|
||||
* @param obj the object literal that needs to have its prototype initialized to the global Object prototype.
|
||||
*/
|
||||
public static void setGlobalObjectProto(final ScriptObject obj) {
|
||||
obj.setInitialProto(Global.objectPrototype());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the __proto__ of an object with checks.
|
||||
* @param newProto Prototype to set.
|
||||
|
||||
@ -39,7 +39,7 @@ public final class UnwarrantedOptimismException extends RuntimeException {
|
||||
public static final int INVALID_PROGRAM_POINT = -1;
|
||||
|
||||
/** The value for the first ordinary program point */
|
||||
public static final int FIRST_PROGRAM_POINT = 0;
|
||||
public static final int FIRST_PROGRAM_POINT = 1;
|
||||
|
||||
private Object returnValue;
|
||||
private final int programPoint;
|
||||
@ -86,6 +86,8 @@ public final class UnwarrantedOptimismException extends RuntimeException {
|
||||
*/
|
||||
public UnwarrantedOptimismException(final Object returnValue, final int programPoint, final Type returnType) {
|
||||
super("", null, false, Context.DEBUG);
|
||||
assert returnType != Type.OBJECT || returnValue == null || !Type.typeFor(returnValue.getClass()).isNumeric();
|
||||
assert returnType != Type.INT;
|
||||
this.returnValue = returnValue;
|
||||
this.programPoint = programPoint;
|
||||
this.returnType = returnType;
|
||||
|
||||
@ -33,7 +33,6 @@ import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodHandles.Lookup;
|
||||
import java.lang.invoke.MethodType;
|
||||
|
||||
import jdk.internal.dynalink.CallSiteDescriptor;
|
||||
import jdk.internal.dynalink.DynamicLinker;
|
||||
import jdk.internal.dynalink.DynamicLinkerFactory;
|
||||
@ -197,6 +196,9 @@ public final class Bootstrap {
|
||||
case "idiv":
|
||||
mh = JSType.DIV_EXACT.methodHandle();
|
||||
break;
|
||||
case "irem":
|
||||
mh = JSType.REM_EXACT.methodHandle();
|
||||
break;
|
||||
case "ineg":
|
||||
mh = JSType.NEGATE_EXACT.methodHandle();
|
||||
break;
|
||||
@ -212,6 +214,9 @@ public final class Bootstrap {
|
||||
case "ldiv":
|
||||
mh = JSType.DIV_EXACT_LONG.methodHandle();
|
||||
break;
|
||||
case "lrem":
|
||||
mh = JSType.REM_EXACT_LONG.methodHandle();
|
||||
break;
|
||||
case "lneg":
|
||||
mh = JSType.NEGATE_EXACT_LONG.methodHandle();
|
||||
break;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user