--- a/nashorn/src/jdk/internal/dynalink/support/TypeUtilities.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/internal/dynalink/support/TypeUtilities.java Tue May 13 11:30:40 2014 +0200
@@ -281,7 +281,7 @@
}
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);
--- a/nashorn/src/jdk/internal/dynalink/support/messages.properties Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/internal/dynalink/support/messages.properties Tue May 13 11:30:40 2014 +0200
@@ -83,4 +83,4 @@
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
\ No newline at end of file
+isArrayGuardAlwaysFalse=isArray guard in position {0} in method type {1} at {2} will always return false
--- a/nashorn/src/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java Tue May 13 11:30:40 2014 +0200
@@ -164,7 +164,7 @@
* @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());
}
--- a/nashorn/src/jdk/nashorn/internal/codegen/ApplySpecialization.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/ApplySpecialization.java Tue May 13 11:30:40 2014 +0200
@@ -35,7 +35,6 @@
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.Node;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
import jdk.nashorn.internal.objects.Global;
+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.Context;
-import jdk.nashorn.internal.runtime.Debug;
-import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
import jdk.nashorn.internal.runtime.options.Options;
/**
@@ -59,7 +58,6 @@
* introduces expensive args collection and boxing
*
* <pre>
- * {@code
* var Class = {
* create: function() {
* return function() { //vararg
@@ -80,7 +78,6 @@
* }
*
* new Color(17, 47, 11);
- * }
* </pre>
*/
@@ -303,16 +300,9 @@
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());
}
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/src/jdk/nashorn/internal/codegen/AssignSymbols.java Tue May 13 11:30:40 2014 +0200
@@ -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());
+ }
+}
--- a/nashorn/src/jdk/nashorn/internal/codegen/Attr.java Mon May 05 14:17:20 2014 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,2300 +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.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.LITERAL_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.IS_ALWAYS_DEFINED;
-import static jdk.nashorn.internal.ir.Symbol.IS_CONSTANT;
-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.Map;
-import java.util.Set;
-
-import jdk.nashorn.internal.codegen.types.Type;
-import jdk.nashorn.internal.ir.AccessNode;
-import jdk.nashorn.internal.ir.BinaryNode;
-import jdk.nashorn.internal.ir.Block;
-import jdk.nashorn.internal.ir.CallNode;
-import jdk.nashorn.internal.ir.CaseNode;
-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.FunctionCall;
-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.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.Node;
-import jdk.nashorn.internal.ir.ObjectNode;
-import jdk.nashorn.internal.ir.Optimistic;
-import jdk.nashorn.internal.ir.OptimisticLexicalContext;
-import jdk.nashorn.internal.ir.ReturnNode;
-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.TemporarySymbols;
-import jdk.nashorn.internal.ir.TernaryNode;
-import jdk.nashorn.internal.ir.TryNode;
-import jdk.nashorn.internal.ir.UnaryNode;
-import jdk.nashorn.internal.ir.VarNode;
-import jdk.nashorn.internal.ir.WhileNode;
-import jdk.nashorn.internal.ir.WithNode;
-import jdk.nashorn.internal.ir.visitor.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.Debug;
-import jdk.nashorn.internal.runtime.JSType;
-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 is the attribution pass of the code generator. Attr takes Lowered IR,
- * that is, IR where control flow has been computed and high level to low level
- * substitions for operations have been performed.
- *
- * After Attr, every symbol will have a conservative correct type.
- *
- * Any expression that requires temporary storage as part of computation will
- * also be detected here and give a temporary symbol
- *
- * Types can be narrowed after Attr by Access Specialization in FinalizeTypes,
- * but in general, this is where the main symbol type information is
- * computed.
- */
-@Logger(name="Attr")
-final class Attr extends NodeOperatorVisitor<OptimisticLexicalContext> implements Loggable {
-
- private final CompilationEnvironment env;
-
- /**
- * Local definitions in current block (to discriminate from function
- * declarations always defined in the function scope. This is for
- * "can be undefined" analysis.
- */
- private final Deque<Set<String>> localDefs;
-
- /**
- * Local definitions in current block to guard against cases like
- * NASHORN-467 when things can be undefined as they are used before
- * their local var definition. *sigh* JavaScript...
- */
- private final Deque<Set<String>> localUses;
-
- private final Set<Long> optimistic = new HashSet<>();
- private final Set<Long> neverOptimistic = new HashSet<>();
- private final Map<String, Symbol> globalSymbols = new HashMap<>(); //reuse the same global symbol
-
- private int catchNestingLevel;
-
- private final DebugLogger log;
- private final boolean debug;
-
- private final TemporarySymbols temporarySymbols;
-
- /**
- * Constructor.
- */
- Attr(final CompilationEnvironment env, final TemporarySymbols temporarySymbols) {
- super(new OptimisticLexicalContext(env.useOptimisticTypes()));
- this.env = env;
- this.temporarySymbols = temporarySymbols;
- this.localDefs = new ArrayDeque<>();
- this.localUses = new ArrayDeque<>();
- 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());
- }
-
- @Override
- protected boolean enterDefault(final Node node) {
- return start(node);
- }
-
- @Override
- protected Node leaveDefault(final Node node) {
- return end(node);
- }
-
- @Override
- public boolean enterAccessNode(final AccessNode accessNode) {
- tagNeverOptimistic(accessNode.getBase());
- tagNeverOptimistic(accessNode.getProperty());
- return true;
- }
-
- @Override
- public Node leaveAccessNode(final AccessNode accessNode) {
- return end(ensureSymbolTypeOverride(accessNode, Type.OBJECT));
- }
-
- private void initFunctionWideVariables(final FunctionNode functionNode, final Block body) {
- initCompileConstant(CALLEE, body, IS_PARAM | IS_INTERNAL);
- initCompileConstant(THIS, body, IS_PARAM | IS_THIS, Type.OBJECT);
-
- if (functionNode.isVarArg()) {
- initCompileConstant(VARARGS, body, IS_PARAM | IS_INTERNAL);
- if (functionNode.needsArguments()) {
- initCompileConstant(ARGUMENTS, body, IS_VAR | IS_INTERNAL | IS_ALWAYS_DEFINED);
- final String argumentsName = ARGUMENTS_VAR.symbolName();
- newType(defineSymbol(body, argumentsName, IS_VAR | IS_ALWAYS_DEFINED), Type.typeFor(ARGUMENTS_VAR.type()));
- addLocalDef(argumentsName);
- }
- }
-
- initParameters(functionNode, body);
- initCompileConstant(SCOPE, body, IS_VAR | IS_INTERNAL | IS_ALWAYS_DEFINED);
- initCompileConstant(RETURN, body, IS_VAR | IS_INTERNAL | IS_ALWAYS_DEFINED, Type.OBJECT);
- }
-
-
- /**
- * This pushes all declarations (except for non-statements, i.e. for
- * node temporaries) to 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.
- //
- // It also handles the case that a variable can be undefined, e.g
- // if (cond) {
- // x = x.y;
- // }
- // var x = 17;
- //
- // by making sure that no identifier has been found earlier in the body than the
- // declaration - if such is the case the identifier is flagged as caBeUndefined to
- // be safe if it turns into a local var. Otherwise corrupt bytecode results
-
- body.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
- private final Set<String> uses = new HashSet<>();
- private final Set<String> canBeUndefined = new HashSet<>();
-
- @Override
- public boolean enterFunctionNode(final FunctionNode nestedFn) {
- return false;
- }
-
- @Override
- public Node leaveIdentNode(final IdentNode identNode) {
- uses.add(identNode.getName());
- return identNode;
- }
-
- @Override
- public boolean enterVarNode(final VarNode varNode) {
- final Expression init = varNode.getInit();
- if (init != null) {
- tagOptimistic(init);
- }
-
- final String name = varNode.getName().getName();
- //if this is used before the var node, the var node symbol needs to be tagged as can be undefined
- if (uses.contains(name)) {
- canBeUndefined.add(name);
- }
-
- // all uses of the declared varnode inside the var node are potentially undefined
- // however this is a bit conservative as e.g. var x = 17; var x = 1 + x; does work
- if (!varNode.isFunctionDeclaration() && varNode.getInit() != null) {
- varNode.getInit().accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
- @Override
- public boolean enterIdentNode(final IdentNode identNode) {
- if (name.equals(identNode.getName())) {
- canBeUndefined.add(name);
- }
- return false;
- }
- });
- }
-
- return true;
- }
-
- @Override
- public Node leaveVarNode(final VarNode varNode) {
- // any declared symbols that aren't visited need to be typed as well, hence the list
- if (varNode.isStatement()) {
- final IdentNode ident = varNode.getName();
- final Symbol symbol = defineSymbol(body, ident.getName(), IS_VAR);
- if (canBeUndefined.contains(ident.getName()) && varNode.getInit() == null) {
- symbol.setType(Type.OBJECT);
- symbol.setCanBeUndefined();
- }
- functionNode.addDeclaredSymbol(symbol);
- if (varNode.isFunctionDeclaration()) {
- newType(symbol, FunctionNode.FUNCTION_TYPE);
- symbol.setIsFunctionDeclaration();
- }
- return varNode.setName((IdentNode)ident.setSymbol(lc, symbol));
- }
-
- return varNode;
- }
- });
- }
-
- private void enterFunctionBody() {
-
- final FunctionNode functionNode = lc.getCurrentFunction();
- final Block body = lc.getCurrentBlock();
-
- initFunctionWideVariables(functionNode, body);
-
- if (functionNode.isProgram()) {
- initFromPropertyMap(body);
- } else if (!functionNode.isDeclared()) {
- // It's neither declared nor program - it's a function expression then; assign it a self-symbol.
- assert functionNode.getSymbol() == null;
-
- final boolean anonymous = functionNode.isAnonymous();
- final String name = anonymous ? null : functionNode.getIdent().getName();
- if (!(anonymous || body.getExistingSymbol(name) != null)) {
- assert !anonymous && name != null;
- newType(defineSymbol(body, name, IS_VAR | IS_FUNCTION_SELF), Type.OBJECT);
- if(functionNode.allVarsInScope()) { // basically, has deep eval
- lc.setFlag(body, Block.USES_SELF_SYMBOL);
- }
- }
- }
-
- acceptDeclarations(functionNode, body);
- }
-
- @Override
- public boolean enterBlock(final Block block) {
- start(block);
- //ensure that we don't use information from a previous compile. This is very ugly TODO
- //the symbols in the block should really be stateless
- block.clearSymbols();
-
- if (lc.isFunctionBody()) {
- enterFunctionBody();
- }
- pushLocalsBlock();
-
- return true;
- }
-
- @Override
- public Node leaveBlock(final Block block) {
- popLocals();
- return end(block);
- }
-
- private boolean useOptimisticTypes() {
- return env.useOptimisticTypes() &&
- // an inner function in on-demand compilation is not compiled, so no need to evaluate expressions in it
- (!env.isOnDemandCompilation() || lc.getOutermostFunction() == lc.getCurrentFunction()) &&
- // No optimistic compilation within split nodes for now
- !lc.isInSplitNode();
- }
-
- @Override
- public boolean enterCallNode(final CallNode callNode) {
- for (final Expression arg : callNode.getArgs()) {
- tagOptimistic(arg);
- }
- tagNeverOptimistic(callNode.getFunction());
- return true;
- }
-
- @Override
- public Node leaveCallNode(final CallNode callNode) {
- for (final Expression arg : callNode.getArgs()) {
- inferParameter(arg, arg.getType());
- }
- return end(ensureSymbolTypeOverride(callNode, Type.OBJECT));
- }
-
- @Override
- public boolean enterCatchNode(final CatchNode catchNode) {
- final IdentNode exception = catchNode.getException();
- final Block block = lc.getCurrentBlock();
-
- start(catchNode);
- catchNestingLevel++;
-
- // 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());
- final Symbol def = defineSymbol(block, exname, IS_VAR | IS_LET | IS_ALWAYS_DEFINED | (isInternal ? IS_INTERNAL : 0));
- // Normally, we can catch anything, not just ECMAExceptions, hence Type.OBJECT. However, for catches with
- // internal symbol, we can be sure the caught type is a Throwable.
- newType(def, isInternal ? Type.typeFor(EXCEPTION_PREFIX.type()) : Type.OBJECT);
-
- addLocalDef(exname);
-
- return true;
- }
-
- @Override
- public Node leaveCatchNode(final CatchNode catchNode) {
- final IdentNode exception = catchNode.getException();
- final Block block = lc.getCurrentBlock();
- final Symbol symbol = findSymbol(block, exception.getName());
-
- catchNestingLevel--;
-
- assert symbol != null;
- return end(catchNode.setException((IdentNode)exception.setSymbol(lc, symbol)));
- }
-
- /**
- * Declare the definition of a new 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;
-
- if (isGlobal) {
- flags |= IS_SCOPE;
- }
-
- if (lc.getCurrentFunction().isProgram()) {
- flags |= IS_PROGRAM_LEVEL;
- }
-
- final FunctionNode function = lc.getFunction(block);
- if (symbol != null) {
- // Symbol was already defined. Check if it needs to be redefined.
- if ((flags & KINDMASK) == IS_PARAM) {
- 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.
- assert false : "duplicate parameter";
- return null;
- }
- } else if ((flags & KINDMASK) == IS_VAR) {
- 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 ((flags & Symbol.KINDMASK) == IS_VAR && ((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 & Symbol.KINDMASK) != IS_GLOBAL) {
- symbol.setNeedsSlot(true);
- }
- } else if (symbol.less(flags)) {
- symbol.setFlags(flags);
- }
-
- return symbol;
- }
-
- 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);
- }
-
- @Override
- public boolean enterExpressionStatement(final ExpressionStatement expressionStatement) {
- final Expression expr = expressionStatement.getExpression();
- if (!expr.isSelfModifying()) { //self modifying ops like i++ need the optimistic type for their own operation, not just the return value, as there is no difference. gah.
- tagNeverOptimistic(expr);
- }
- return true;
- }
-
- @Override
- public boolean enterFunctionNode(final FunctionNode functionNode) {
- start(functionNode, false);
-
- //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);
- }
- }
-
- pushLocalsFunction();
-
- return true;
- }
-
- @Override
- public Node leaveFunctionNode(final FunctionNode functionNode) {
- FunctionNode newFunctionNode = functionNode;
-
- final Block body = newFunctionNode.getBody();
-
- //look for this function in the parent block
- if (functionNode.isDeclared()) {
- final Iterator<Block> blocks = lc.getBlocks();
- if (blocks.hasNext()) {
- newFunctionNode = (FunctionNode)newFunctionNode.setSymbol(lc, findSymbol(blocks.next(), functionNode.getIdent().getName()));
- }
- } else if (!functionNode.isProgram()) {
- final boolean anonymous = functionNode.isAnonymous();
- final String name = anonymous ? null : functionNode.getIdent().getName();
- if (anonymous || body.getExistingSymbol(name) != null) {
- newFunctionNode = (FunctionNode)ensureSymbol(newFunctionNode, FunctionNode.FUNCTION_TYPE);
- } else {
- assert name != null;
- final Symbol self = body.getExistingSymbol(name);
- assert self != null && self.isFunctionSelf();
- newFunctionNode = (FunctionNode)newFunctionNode.setSymbol(lc, body.getExistingSymbol(name));
- }
- }
-
- newFunctionNode = finalizeParameters(newFunctionNode);
- newFunctionNode = finalizeTypes(newFunctionNode);
- for (final Symbol symbol : newFunctionNode.getDeclaredSymbols()) {
- if (symbol.getSymbolType().isUnknown()) {
- symbol.setType(Type.OBJECT);
- symbol.setCanBeUndefined();
- }
- }
-
- List<VarNode> syntheticInitializers = null;
-
- if (newFunctionNode.usesSelfSymbol()) {
- syntheticInitializers = new ArrayList<>(2);
- log.info("Accepting self symbol init for ", newFunctionNode.getName());
- // "var fn = :callee"
- syntheticInitializers.add(createSyntheticInitializer(newFunctionNode.getIdent(), CALLEE, newFunctionNode));
- }
-
- if (newFunctionNode.needsArguments()) {
- if (syntheticInitializers == null) {
- syntheticInitializers = new ArrayList<>(1);
- }
- // "var arguments = :arguments"
- syntheticInitializers.add(createSyntheticInitializer(createImplicitIdentifier(ARGUMENTS_VAR.symbolName()),
- ARGUMENTS, newFunctionNode));
- }
-
- if (syntheticInitializers != null) {
- final List<Statement> stmts = newFunctionNode.getBody().getStatements();
- final List<Statement> newStatements = new ArrayList<>(stmts.size() + syntheticInitializers.size());
- newStatements.addAll(syntheticInitializers);
- newStatements.addAll(stmts);
- newFunctionNode = newFunctionNode.setBody(lc, newFunctionNode.getBody().setStatements(lc, newStatements));
- }
-
- final int optimisticFlag = lc.hasOptimisticAssumptions() ? FunctionNode.IS_OPTIMISTIC : 0;
-
- newFunctionNode = newFunctionNode.setState(lc, CompilationState.ATTR).setFlag(lc, optimisticFlag);
- popLocalsFunction();
-
- if (!env.isOnDemandCompilation() && newFunctionNode.isProgram()) {
- newFunctionNode = newFunctionNode.setBody(lc, newFunctionNode.getBody().setFlag(lc, Block.IS_GLOBAL_SCOPE));
- assert newFunctionNode.getId() == 1;
- }
-
- return end(newFunctionNode, false);
- }
-
- /**
- * 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 = compilerConstant(initConstant);
- assert init.getSymbol() != null && init.getSymbol().hasSlot();
-
- 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 synthVar.setName((IdentNode)name.setSymbol(lc, nameSymbol));
- }
-
- @Override
- public Node leaveIdentNode(final IdentNode identNode) {
- final String name = identNode.getName();
-
- if (identNode.isPropertyName()) {
- // assign a pseudo symbol to property name
- final Symbol pseudoSymbol = pseudoSymbol(name);
- log.info("IdentNode is property name -> assigning pseudo symbol ", pseudoSymbol);
- log.unindent();
- return end(identNode.setSymbol(lc, pseudoSymbol));
- }
-
- 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.getBody(), Block.USES_SELF_SYMBOL);
- newType(symbol, FunctionNode.FUNCTION_TYPE);
- } else if (!(identNode.isInitializedHere() || symbol.isAlwaysDefined())) {
- /*
- * See NASHORN-448, JDK-8016235
- *
- * Here is a use outside the local def scope
- * the inCatch check is a conservative approach to handle things that might have only been
- * defined in the try block, but with variable declarations, which due to JavaScript rules
- * have to be lifted up into the function scope outside the try block anyway, but as the
- * flow can fault at almost any place in the try block and get us to the catch block, all we
- * know is that we have a declaration, not a definition. This can be made better and less
- * conservative once we superimpose a CFG onto the AST.
- */
- if (!isLocalDef(name) || inCatch()) {
- newType(symbol, Type.OBJECT);
- symbol.setCanBeUndefined();
- }
- }
-
- // 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 undefined: ", symbol);
- symbol = defineGlobalSymbol(block, name);
- // we have never seen this before, it can be undefined
- newType(symbol, Type.OBJECT); // TODO unknown -we have explicit casts anyway?
- symbol.setCanBeUndefined();
- Symbol.setSymbolIsScope(lc, symbol);
- }
-
- setBlockScope(name, symbol);
-
- if (!identNode.isInitializedHere()) {
- symbol.increaseUseCount();
- }
- addLocalUse(name);
- IdentNode node = (IdentNode)identNode.setSymbol(lc, symbol);
- if (isTaggedOptimistic(identNode) && symbol.isScope()) {
- node = ensureSymbolTypeOverride(node, symbol.getSymbolType());
- }
-
- return end(node);
- }
-
- private Symbol defineGlobalSymbol(final Block block, final String name) {
- return defineSymbol(block, name, IS_GLOBAL);
- }
-
- private boolean inCatch() {
- return catchNestingLevel > 0;
- }
-
- /**
- * If the symbol isn't already a scope symbol, and it is either not local to the current function, or it is being
- * referenced from within a with block, we force it to be a scope symbol.
- * @param symbol the symbol that might be scoped
- */
- private void maybeForceScope(final Symbol symbol) {
- if (!symbol.isScope() && symbolNeedsToBeScope(symbol)) {
- Symbol.setSymbolIsScope(lc, 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) {
- // 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 void setBlockScope(final String name, final Symbol symbol) {
- assert symbol != null;
- if (symbol.isGlobal()) {
- setUsesGlobalSymbol();
- return;
- }
-
- if (symbol.isScope()) {
- Block scopeBlock = null;
- for (final Iterator<LexicalContextNode> contextNodeIter = lc.getAllNodes(); contextNodeIter.hasNext(); ) {
- final LexicalContextNode node = contextNodeIter.next();
- if (node instanceof Block) {
- if (((Block)node).getExistingSymbol(name) != null) {
- scopeBlock = (Block)node;
- break;
- }
- } else if (node instanceof FunctionNode) {
- lc.setFlag(node, FunctionNode.USES_ANCESTOR_SCOPE);
- }
- }
-
- if (scopeBlock != null) {
- assert lc.contains(scopeBlock);
- lc.setBlockNeedsScope(scopeBlock);
- }
- }
- }
-
- /**
- * 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 #needsParentScope()
- */
- private void setUsesGlobalSymbol() {
- for (final Iterator<FunctionNode> fns = lc.getFunctions(); fns.hasNext();) {
- lc.setFlag(fns.next(), FunctionNode.USES_ANCESTOR_SCOPE);
- }
- }
-
- /**
- * 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) {
- // Search up block chain to locate symbol.
-
- for (final Iterator<Block> blocks = lc.getBlocks(block); blocks.hasNext();) {
- // Find name.
- final Symbol symbol = blocks.next().getExistingSymbol(name);
- // If found then we are good.
- if (symbol != null) {
- return symbol;
- }
- }
- return null;
- }
-
- @Override
- public boolean enterIndexNode(final IndexNode indexNode) {
- tagNeverOptimistic(indexNode.getBase());
- return true;
- }
-
- @Override
- public Node leaveIndexNode(final IndexNode indexNode) {
- // return end(ensureSymbolOptimistic(Type.OBJECT, indexNode));
- return end(ensureSymbolTypeOverride(indexNode, Type.OBJECT));
- }
-
- @SuppressWarnings("rawtypes")
- @Override
- public Node leaveLiteralNode(final LiteralNode literalNode) {
- assert !literalNode.isTokenType(TokenType.THIS) : "tokentype for " + literalNode + " is this"; //guard against old dead code case. literal nodes should never inherit tokens
- assert literalNode instanceof ArrayLiteralNode || !(literalNode.getValue() instanceof Node) : "literals with Node values not supported";
- final Symbol symbol = new Symbol(lc.getCurrentFunction().uniqueName(LITERAL_PREFIX.symbolName()), IS_CONSTANT, literalNode.getType());
- if (literalNode instanceof ArrayLiteralNode) {
- ((ArrayLiteralNode)literalNode).analyze();
- }
- return end(literalNode.setSymbol(lc, symbol));
- }
-
- @Override
- public boolean enterObjectNode(final ObjectNode objectNode) {
- return start(objectNode);
- }
-
- @Override
- public Node leaveObjectNode(final ObjectNode objectNode) {
- return end(ensureSymbol(objectNode, Type.OBJECT));
- }
-
- @Override
- public Node leaveSwitchNode(final SwitchNode switchNode) {
- Type type = Type.UNKNOWN;
-
- final List<CaseNode> newCases = new ArrayList<>();
- for (final CaseNode caseNode : switchNode.getCases()) {
- final Node test = caseNode.getTest();
-
- CaseNode newCaseNode = caseNode;
- if (test != null) {
- if (test instanceof LiteralNode) {
- //go down to integers if we can
- final LiteralNode<?> lit = (LiteralNode<?>)test;
- if (lit.isNumeric() && !(lit.getValue() instanceof Integer)) {
- if (JSType.isRepresentableAsInt(lit.getNumber())) {
- newCaseNode = caseNode.setTest((Expression)LiteralNode.newInstance(lit, lit.getInt32()).accept(this));
- }
- }
- } else {
- // the "all integer" case that CodeGenerator optimizes for currently assumes literals only
- type = Type.OBJECT;
- }
-
- final Type newCaseType = newCaseNode.getTest().getType();
- if (newCaseType.isBoolean()) {
- type = Type.OBJECT; //booleans and integers aren't assignment compatible
- } else {
- type = Type.widest(type, newCaseType);
- }
- }
-
- newCases.add(newCaseNode);
- }
-
- //only optimize for all integers
- if (!type.isInteger()) {
- type = Type.OBJECT;
- }
-
- switchNode.setTag(newInternal(lc.getCurrentFunction().uniqueName(SWITCH_TAG_PREFIX.symbolName()), type));
-
- return end(switchNode.setCases(lc, newCases));
- }
-
- @Override
- public Node leaveTryNode(final TryNode tryNode) {
- tryNode.setException(exceptionSymbol());
-
- if (tryNode.getFinallyBody() != null) {
- tryNode.setFinallyCatchAll(exceptionSymbol());
- }
-
- end(tryNode);
-
- return tryNode;
- }
-
- @Override
- public boolean enterVarNode(final VarNode varNode) {
- start(varNode);
-
- final IdentNode ident = varNode.getName();
- final String name = ident.getName();
-
- final Symbol symbol = defineSymbol(lc.getCurrentBlock(), name, IS_VAR);
- assert symbol != null;
-
- // NASHORN-467 - use before definition of vars - conservative
- //function each(iterator, context) {
- // for (var i = 0, length = this.length >>> 0; i < length; i++) { if (i in this) iterator.call(context, this[i], i, this); }
- //
- if (isLocalUse(ident.getName()) && varNode.getInit() == null) {
- newType(symbol, Type.OBJECT);
- symbol.setCanBeUndefined();
- }
-
- return true;
- }
-
- @Override
- public Node leaveVarNode(final VarNode varNode) {
- final Expression init = varNode.getInit();
- final IdentNode ident = varNode.getName();
- final String name = ident.getName();
-
- final Symbol symbol = findSymbol(lc.getCurrentBlock(), name);
- assert ident.getSymbol() == symbol;
-
- if (init == null) {
- // var x; with no init will be treated like a use of x by
- // leaveIdentNode unless we remove the name from the localdef list.
- removeLocalDef(name);
- return end(varNode);
- }
-
- addLocalDef(name);
-
- assert symbol != null;
-
- final IdentNode newIdent = (IdentNode)ident.setSymbol(lc, symbol);
-
- final VarNode newVarNode = varNode.setName(newIdent);
-
- final boolean isScript = lc.getDefiningFunction(symbol).isProgram(); //see NASHORN-56
- if ((init.getType().isNumeric() || init.getType().isBoolean()) && !isScript) {
- // Forbid integers as local vars for now as we have no way to treat them as undefined
- newType(symbol, init.getType());
- } else {
- newType(symbol, Type.OBJECT);
- }
-
- assert newVarNode.getName().hasType() : newVarNode + " has no type";
-
- return end(newVarNode);
- }
-
- @Override
- public boolean enterNOT(final UnaryNode unaryNode) {
- tagNeverOptimistic(unaryNode.getExpression());
- return true;
- }
-
- public boolean enterUnaryArithmetic(final UnaryNode unaryNode) {
- tagOptimistic(unaryNode.getExpression());
- return true;
- }
-
- private UnaryNode leaveUnaryArithmetic(final UnaryNode unaryNode) {
- return end(coerce(unaryNode, unaryNode.getMostPessimisticType(), unaryNode.getExpression().getType()));
- }
-
- @Override
- public boolean enterADD(final UnaryNode unaryNode) {
- return enterUnaryArithmetic(unaryNode);
- }
-
- @Override
- public Node leaveADD(final UnaryNode unaryNode) {
- return leaveUnaryArithmetic(unaryNode);
- }
-
- @Override
- public Node leaveBIT_NOT(final UnaryNode unaryNode) {
- return end(coerce(unaryNode, Type.INT));
- }
-
- @Override
- public boolean enterDECINC(final UnaryNode unaryNode) {
- return enterUnaryArithmetic(unaryNode);
- }
-
- @Override
- public Node leaveDECINC(final UnaryNode unaryNode) {
- // @see assignOffset
- final Type pessimisticType = unaryNode.getMostPessimisticType();
- final UnaryNode newUnaryNode = ensureSymbolTypeOverride(unaryNode, pessimisticType, unaryNode.getExpression().getType());
- newType(newUnaryNode.getExpression().getSymbol(), newUnaryNode.getType());
- return end(newUnaryNode);
- }
-
- @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) {
- // If this is a declared variable or a function parameter, delete always fails (except for globals).
- final String name = ((IdentNode)rhs).getName();
-
- final boolean isParam = rhs.getSymbol().isParam();
- final boolean isVar = rhs.getSymbol().isVar();
- final boolean isNonProgramVar = isVar && !rhs.getSymbol().isProgramLevel();
- final boolean failDelete = strictMode || isParam || isNonProgramVar;
-
- if (failDelete && rhs.getSymbol().isThis()) {
- return LiteralNode.newInstance(unaryNode, true).accept(this);
- }
- final Expression literalNode = (Expression)LiteralNode.newInstance(unaryNode, name).accept(this);
-
- if (!failDelete) {
- args.add(compilerConstant(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 IdentNode property = ((AccessNode)rhs).getProperty();
-
- args.add(base);
- args.add((Expression)LiteralNode.newInstance(unaryNode, property.getName()).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);
- }
-
- final RuntimeNode runtimeNode = new RuntimeNode(unaryNode, request, args);
- assert runtimeNode.getSymbol() == unaryNode.getSymbol(); //unary parent constructor should do this
-
- return leaveRuntimeNode(runtimeNode);
- }
-
- @Override
- public Node leaveNEW(final UnaryNode unaryNode) {
- return end(coerce(unaryNode.setExpression(((CallNode)unaryNode.getExpression()).setIsNew()), Type.OBJECT));
- }
-
- @Override
- public Node leaveNOT(final UnaryNode unaryNode) {
- return end(coerce(unaryNode, Type.BOOLEAN));
- }
-
- private IdentNode compilerConstant(final CompilerConstants cc) {
- return (IdentNode)createImplicitIdentifier(cc.symbolName()).setSymbol(lc, 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);
- }
-
- @Override
- public Node leaveTYPEOF(final UnaryNode unaryNode) {
- final Expression rhs = unaryNode.getExpression();
-
- final List<Expression> args = new ArrayList<>();
- if (rhs instanceof IdentNode && !rhs.getSymbol().isParam() && !rhs.getSymbol().isVar()) {
- args.add(compilerConstant(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'
- }
-
- RuntimeNode runtimeNode = new RuntimeNode(unaryNode, Request.TYPEOF, args);
- assert runtimeNode.getSymbol() == unaryNode.getSymbol();
-
- runtimeNode = (RuntimeNode)leaveRuntimeNode(runtimeNode);
-
- end(unaryNode);
-
- return runtimeNode;
- }
-
- @Override
- public Node leaveRuntimeNode(final RuntimeNode runtimeNode) {
- return end(ensureSymbol(runtimeNode, runtimeNode.getRequest().getReturnType()));
- }
-
- @Override
- public boolean enterSUB(final UnaryNode unaryNode) {
- return enterUnaryArithmetic(unaryNode);
- }
-
- @Override
- public Node leaveSUB(final UnaryNode unaryNode) {
- return leaveUnaryArithmetic(unaryNode);
- }
-
- @Override
- public Node leaveVOID(final UnaryNode unaryNode) {
- return end(ensureSymbol(unaryNode, Type.OBJECT));
- }
-
- @Override
- public boolean enterADD(final BinaryNode binaryNode) {
- tagOptimistic(binaryNode.lhs());
- tagOptimistic(binaryNode.rhs());
- return true;
- }
-
- /**
- * Add is a special binary, as it works not only on arithmetic, but for
- * strings etc as well.
- */
- @Override
- public Node leaveADD(final BinaryNode binaryNode) {
- final Expression lhs = binaryNode.lhs();
- final Expression rhs = binaryNode.rhs();
-
- //an add is at least as wide as the current arithmetic type, possibly wider in the case of objects
- //which will be corrected in the post pass if unknown at this stage
-
- Type argumentsType = Type.widest(lhs.getType(), rhs.getType());
- if (argumentsType.getTypeClass() == String.class) {
- assert binaryNode.isTokenType(TokenType.ADD);
- argumentsType = Type.OBJECT;
- }
- final Type pessimisticType = Type.widest(Type.NUMBER, argumentsType);
-
- return end(ensureSymbolTypeOverride(binaryNode, pessimisticType, argumentsType));
- }
-
- @Override
- public Node leaveAND(final BinaryNode binaryNode) {
- return end(ensureSymbol(binaryNode, Type.OBJECT));
- }
-
- /**
- * This is a helper called before an assignment.
- * @param binaryNode assignment node
- */
- private boolean enterAssignmentNode(final BinaryNode binaryNode) {
- start(binaryNode);
- final Expression lhs = binaryNode.lhs();
- if (lhs instanceof IdentNode) {
- if (CompilerConstants.isCompilerConstant(((IdentNode)lhs).getName())) {
- tagNeverOptimistic(binaryNode.rhs());
- }
- }
- tagOptimistic(binaryNode.rhs());
-
- return true;
- }
-
- /**
- * This assign helper is called after an assignment, when all children of
- * the assign has been processed. It fixes the types and recursively makes
- * sure that everyhing has slots that should have them in the chain.
- *
- * @param binaryNode assignment node
- */
- private Node leaveAssignmentNode(final BinaryNode binaryNode) {
- final Expression lhs = binaryNode.lhs();
- final Expression rhs = binaryNode.rhs();
- final Type type;
-
- if (lhs instanceof IdentNode) {
- final Block block = lc.getCurrentBlock();
- final IdentNode ident = (IdentNode)lhs;
- final String name = ident.getName();
-
- final Symbol symbol = findSymbol(block, name);
-
- if (symbol == null) {
- defineGlobalSymbol(block, name);
- } else {
- maybeForceScope(symbol);
- }
-
- addLocalDef(name);
- }
-
- if (rhs.getType().isNumeric()) {
- type = Type.widest(lhs.getType(), rhs.getType());
- } else {
- type = Type.OBJECT; //force lhs to be an object if not numeric assignment, e.g. strings too.
- }
-
- newType(lhs.getSymbol(), type);
- return end(ensureSymbol(binaryNode, type));
- }
-
- private boolean isLocal(final FunctionNode function, final Symbol symbol) {
- final FunctionNode definingFn = lc.getDefiningFunction(symbol);
- // Temp symbols are not assigned to a block, so their defining fn is null; those can be assumed local
- return definingFn == null || definingFn == function;
- }
-
- @Override
- public boolean enterASSIGN(final BinaryNode binaryNode) {
- // left hand side of an ordinary assignment need never be optimistic (it's written only, not read).
- tagNeverOptimistic(binaryNode.lhs());
- return enterAssignmentNode(binaryNode);
- }
-
- @Override
- public Node leaveASSIGN(final BinaryNode binaryNode) {
- return leaveAssignmentNode(binaryNode);
- }
-
- @Override
- public boolean enterASSIGN_ADD(final BinaryNode binaryNode) {
- return enterAssignmentNode(binaryNode);
- }
-
- @Override
- public Node leaveASSIGN_ADD(final BinaryNode binaryNode) {
- final Expression lhs = binaryNode.lhs();
- final Expression rhs = binaryNode.rhs();
- final Type widest = Type.widest(lhs.getType(), rhs.getType());
- //Type.NUMBER if we can't prove that the add doesn't overflow. todo
-
- return leaveSelfModifyingAssignmentNode(binaryNode, widest.isNumeric() ? Type.NUMBER : Type.OBJECT);
- }
-
- @Override
- public boolean enterASSIGN_BIT_AND(final BinaryNode binaryNode) {
- return enterAssignmentNode(binaryNode);
- }
-
- @Override
- public Node leaveASSIGN_BIT_AND(final BinaryNode binaryNode) {
- return leaveSelfModifyingAssignmentNode(binaryNode);
- }
-
- @Override
- public boolean enterASSIGN_BIT_OR(final BinaryNode binaryNode) {
- return enterAssignmentNode(binaryNode);
- }
-
- @Override
- public Node leaveASSIGN_BIT_OR(final BinaryNode binaryNode) {
- return leaveSelfModifyingAssignmentNode(binaryNode);
- }
-
- @Override
- public boolean enterASSIGN_BIT_XOR(final BinaryNode binaryNode) {
- return enterAssignmentNode(binaryNode);
- }
-
- @Override
- public Node leaveASSIGN_BIT_XOR(final BinaryNode binaryNode) {
- return leaveSelfModifyingAssignmentNode(binaryNode);
- }
-
- @Override
- public boolean enterASSIGN_DIV(final BinaryNode binaryNode) {
- return enterAssignmentNode(binaryNode);
- }
-
- @Override
- public Node leaveASSIGN_DIV(final BinaryNode binaryNode) {
- return leaveSelfModifyingAssignmentNode(binaryNode);
- }
-
- @Override
- public boolean enterASSIGN_MOD(final BinaryNode binaryNode) {
- return enterAssignmentNode(binaryNode);
- }
-
- @Override
- public Node leaveASSIGN_MOD(final BinaryNode binaryNode) {
- return leaveSelfModifyingAssignmentNode(binaryNode);
- }
-
- @Override
- public boolean enterASSIGN_MUL(final BinaryNode binaryNode) {
- return enterAssignmentNode(binaryNode);
- }
-
- @Override
- public Node leaveASSIGN_MUL(final BinaryNode binaryNode) {
- return leaveSelfModifyingAssignmentNode(binaryNode);
- }
-
- @Override
- public boolean enterASSIGN_SAR(final BinaryNode binaryNode) {
- return enterAssignmentNode(binaryNode);
- }
-
- @Override
- public Node leaveASSIGN_SAR(final BinaryNode binaryNode) {
- return leaveSelfModifyingAssignmentNode(binaryNode);
- }
-
- @Override
- public boolean enterASSIGN_SHL(final BinaryNode binaryNode) {
- return enterAssignmentNode(binaryNode);
- }
-
- @Override
- public Node leaveASSIGN_SHL(final BinaryNode binaryNode) {
- return leaveSelfModifyingAssignmentNode(binaryNode);
- }
-
- @Override
- public boolean enterASSIGN_SHR(final BinaryNode binaryNode) {
- return enterAssignmentNode(binaryNode);
- }
-
- @Override
- public Node leaveASSIGN_SHR(final BinaryNode binaryNode) {
- return leaveSelfModifyingAssignmentNode(binaryNode);
- }
-
- @Override
- public boolean enterASSIGN_SUB(final BinaryNode binaryNode) {
- return enterAssignmentNode(binaryNode);
- }
-
- @Override
- public Node leaveASSIGN_SUB(final BinaryNode binaryNode) {
- return leaveSelfModifyingAssignmentNode(binaryNode);
- }
-
- @Override
- public boolean enterBIT_AND(final BinaryNode binaryNode) {
- return enterBitwiseOperator(binaryNode);
- }
-
- @Override
- public Node leaveBIT_AND(final BinaryNode binaryNode) {
- return leaveBitwiseOperator(binaryNode);
- }
-
- @Override
- public boolean enterBIT_OR(final BinaryNode binaryNode) {
- return enterBitwiseOperator(binaryNode);
- }
-
- @Override
- public Node leaveBIT_OR(final BinaryNode binaryNode) {
- return leaveBitwiseOperator(binaryNode);
- }
-
- @Override
- public boolean enterBIT_XOR(final BinaryNode binaryNode) {
- return enterBitwiseOperator(binaryNode);
- }
-
- @Override
- public Node leaveBIT_XOR(final BinaryNode binaryNode) {
- return leaveBitwiseOperator(binaryNode);
- }
-
- public boolean enterBitwiseOperator(final BinaryNode binaryNode) {
- start(binaryNode);
- tagOptimistic(binaryNode.lhs());
- tagOptimistic(binaryNode.rhs());
- return true;
- }
-
- private Node leaveBitwiseOperator(final BinaryNode binaryNode) {
- return end(coerce(binaryNode, Type.INT));
- }
-
- @Override
- public Node leaveCOMMARIGHT(final BinaryNode binaryNode) {
-// return end(ensureSymbol(binaryNode, binaryNode.rhs().getType()));
- return leaveComma(binaryNode, binaryNode.rhs());
- }
-
- @Override
- public Node leaveCOMMALEFT(final BinaryNode binaryNode) {
- return leaveComma(binaryNode, binaryNode.lhs());
- }
-
- private Node leaveComma(final BinaryNode commaNode, final Expression effectiveExpr) {
- Type type = effectiveExpr.getType();
- if (type.isUnknown()) { //TODO more optimistic
- type = Type.OBJECT;
- }
- return end(ensureSymbol(commaNode, type));
- }
-
- @Override
- public Node leaveDIV(final BinaryNode binaryNode) {
- return leaveBinaryArithmetic(binaryNode);
- }
-
- private BinaryNode leaveCmp(final BinaryNode binaryNode) {
- //infect untyped comp with opportunistic type from other
- final Expression lhs = binaryNode.lhs();
- final Expression rhs = binaryNode.rhs();
- final Type type = Type.narrowest(lhs.getType(), rhs.getType(), Type.INT);
- inferParameter(lhs, type);
- inferParameter(rhs, type);
- final Type widest = Type.widest(lhs.getType(), rhs.getType());
- ensureSymbol(lhs, widest);
- ensureSymbol(rhs, widest);
- return (BinaryNode)end(ensureSymbol(binaryNode, Type.BOOLEAN));
- }
-
- private boolean enterBinaryArithmetic(final BinaryNode binaryNode) {
- tagOptimistic(binaryNode.lhs());
- tagOptimistic(binaryNode.rhs());
- return true;
- }
-
- //leave a binary node and inherit the widest type of lhs , rhs
- private Node leaveBinaryArithmetic(final BinaryNode binaryNode) {
- return end(coerce(binaryNode, binaryNode.getMostPessimisticType(), Type.widest(binaryNode.lhs().getType(), binaryNode.rhs().getType())));
- }
-
- @Override
- public boolean enterEQ(final BinaryNode binaryNode) {
- return enterBinaryArithmetic(binaryNode);
- }
-
- @Override
- public Node leaveEQ(final BinaryNode binaryNode) {
- return leaveCmp(binaryNode);
- }
-
- @Override
- public boolean enterEQ_STRICT(final BinaryNode binaryNode) {
- return enterBinaryArithmetic(binaryNode);
- }
-
- @Override
- public Node leaveEQ_STRICT(final BinaryNode binaryNode) {
- return leaveCmp(binaryNode);
- }
-
- @Override
- public boolean enterGE(final BinaryNode binaryNode) {
- return enterBinaryArithmetic(binaryNode);
- }
-
- @Override
- public Node leaveGE(final BinaryNode binaryNode) {
- return leaveCmp(binaryNode);
- }
-
- @Override
- public boolean enterGT(final BinaryNode binaryNode) {
- return enterBinaryArithmetic(binaryNode);
- }
-
- @Override
- public Node leaveGT(final BinaryNode binaryNode) {
- return leaveCmp(binaryNode);
- }
-
- @Override
- public Node leaveIN(final BinaryNode binaryNode) {
- return leaveBinaryRuntimeOperator(binaryNode, Request.IN);
- }
-
- @Override
- public Node leaveINSTANCEOF(final BinaryNode binaryNode) {
- return leaveBinaryRuntimeOperator(binaryNode, Request.INSTANCEOF);
- }
-
- private Node leaveBinaryRuntimeOperator(final BinaryNode binaryNode, final Request request) {
- try {
- // Don't do a full RuntimeNode.accept, as we don't want to double-visit the binary node operands
- return leaveRuntimeNode(new RuntimeNode(binaryNode, request));
- } finally {
- end(binaryNode);
- }
- }
-
- @Override
- public boolean enterLE(final BinaryNode binaryNode) {
- return enterBinaryArithmetic(binaryNode);
- }
-
- @Override
- public Node leaveLE(final BinaryNode binaryNode) {
- return leaveCmp(binaryNode);
- }
-
- @Override
- public boolean enterLT(final BinaryNode binaryNode) {
- return enterBinaryArithmetic(binaryNode);
- }
-
- @Override
- public Node leaveLT(final BinaryNode binaryNode) {
- return leaveCmp(binaryNode);
- }
-
- @Override
- public Node leaveMOD(final BinaryNode binaryNode) {
- return leaveBinaryArithmetic(binaryNode);
- }
-
- @Override
- public boolean enterMUL(final BinaryNode binaryNode) {
- return enterBinaryArithmetic(binaryNode);
- }
-
- @Override
- public Node leaveMUL(final BinaryNode binaryNode) {
- return leaveBinaryArithmetic(binaryNode);
- }
-
- @Override
- public boolean enterNE(final BinaryNode binaryNode) {
- return enterBinaryArithmetic(binaryNode);
- }
-
- @Override
- public Node leaveNE(final BinaryNode binaryNode) {
- return leaveCmp(binaryNode);
- }
-
- @Override
- public boolean enterNE_STRICT(final BinaryNode binaryNode) {
- return enterBinaryArithmetic(binaryNode);
- }
-
- @Override
- public Node leaveNE_STRICT(final BinaryNode binaryNode) {
- return leaveCmp(binaryNode);
- }
-
- @Override
- public Node leaveOR(final BinaryNode binaryNode) {
- return end(ensureSymbol(binaryNode, Type.OBJECT));
- }
-
- @Override
- public boolean enterSAR(final BinaryNode binaryNode) {
- return enterBitwiseOperator(binaryNode);
- }
-
- @Override
- public Node leaveSAR(final BinaryNode binaryNode) {
- return leaveBitwiseOperator(binaryNode);
- }
-
- @Override
- public boolean enterSHL(final BinaryNode binaryNode) {
- return enterBitwiseOperator(binaryNode);
- }
-
- @Override
- public Node leaveSHL(final BinaryNode binaryNode) {
- return leaveBitwiseOperator(binaryNode);
- }
-
- @Override
- public boolean enterSHR(final BinaryNode binaryNode) {
- return enterBitwiseOperator(binaryNode);
- }
-
- @Override
- public Node leaveSHR(final BinaryNode binaryNode) {
- return end(coerce(binaryNode, Type.LONG));
- }
-
- @Override
- public boolean enterSUB(final BinaryNode binaryNode) {
- return enterBinaryArithmetic(binaryNode);
- }
-
- @Override
- public Node leaveSUB(final BinaryNode binaryNode) {
- return leaveBinaryArithmetic(binaryNode);
- }
-
- @Override
- public boolean enterForNode(final ForNode forNode) {
- tagNeverOptimistic(forNode.getTest());
- return true;
- }
-
- @Override
- public Node leaveForNode(final ForNode forNode) {
- if (forNode.isForIn()) {
- forNode.setIterator(newInternal(lc.getCurrentFunction().uniqueName(ITERATOR_PREFIX.symbolName()), Type.typeFor(ITERATOR_PREFIX.type()))); //NASHORN-73
- /*
- * Iterators return objects, so we need to widen the scope of the
- * init variable if it, for example, has been assigned double type
- * see NASHORN-50
- */
- newType(forNode.getInit().getSymbol(), Type.OBJECT);
- }
-
- return end(forNode);
- }
-
- @Override
- public boolean enterTernaryNode(final TernaryNode ternaryNode) {
- tagNeverOptimistic(ternaryNode.getTest());
- return true;
- }
-
- @Override
- public Node leaveTernaryNode(final TernaryNode ternaryNode) {
- final Type trueType = ternaryNode.getTrueExpression().getType();
- final Type falseType = ternaryNode.getFalseExpression().getType();
- final Type type;
- if (trueType.isUnknown() || falseType.isUnknown()) {
- type = Type.UNKNOWN;
- } else {
- type = widestReturnType(trueType, falseType);
- }
- return end(ensureSymbol(ternaryNode, type));
- }
-
- /**
- * 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.
- */
- private 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);
- }
-
- private void initCompileConstant(final CompilerConstants cc, final Block block, final int flags) {
- // Must not call this method for constants with no explicit types; use the one with (..., Type) signature instead.
- assert cc.type() != null;
- initCompileConstant(cc, block, flags, Type.typeFor(cc.type()));
- }
-
- private void initCompileConstant(final CompilerConstants cc, final Block block, final int flags, final Type type) {
- defineSymbol(block, cc.symbolName(), flags).
- setTypeOverride(type).
- setNeedsSlot(true);
- }
-
- /**
- * Initialize parameters for function node. This may require specializing
- * types if a specialization profile is known
- *
- * @param functionNode the function node
- */
- private void initParameters(final FunctionNode functionNode, final Block body) {
- final boolean isOptimistic = env.useOptimisticTypes();
- int pos = 0;
- for (final IdentNode param : functionNode.getParameters()) {
- addLocalDef(param.getName());
-
- final Symbol paramSymbol = defineSymbol(body, param.getName(), IS_PARAM);
- assert paramSymbol != null;
-
- final Type callSiteParamType = env.getParamType(functionNode, pos);
- if (callSiteParamType != null) {
- log.info("Callsite type override for parameter " + pos + " " + paramSymbol + " => " + callSiteParamType);
- newType(paramSymbol, callSiteParamType);
- } else {
- // When we're using optimistic compilation, we'll generate specialized versions of the functions anyway
- // based on their input type, so if we're doing a compilation without parameter types explicitly
- // specified in the compilation environment, just pre-initialize them all to Object. Note that this is
- // not merely an optimization; it has correctness implications as Type.UNKNOWN is narrower than all
- // other types, which when combined with optimistic typing can cause invalid coercions to be introduced
- // in the generated code. E.g. "var b = { x: 0 }; (function (i) { this.x += i }).apply(b, [1.1])" would
- // erroneously allow coercion of "i" to int when "this.x" is an optimistic-int and "i" starts out
- // with Type.UNKNOWN.
- newType(paramSymbol, isOptimistic ? Type.OBJECT : Type.UNKNOWN);
- }
- log.info("Initialized param ", pos, "=", paramSymbol);
- pos++;
- }
-
- }
-
- /**
- * 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 boolean pessimistic = !useOptimisticTypes();
-
- for (final IdentNode param : functionNode.getParameters()) {
- final Symbol paramSymbol = functionNode.getBody().getExistingSymbol(param.getName());
- assert paramSymbol != null;
- assert paramSymbol.isParam() : paramSymbol + " " + paramSymbol.getFlags();
- newParams.add((IdentNode)param.setSymbol(lc, paramSymbol));
-
- assert paramSymbol != null;
- final Type type = paramSymbol.getSymbolType();
-
- // all param types are initialized to unknown
- // first we check if we do have a type (inferred during generation)
- // and it's not an object. In that case we make an optimistic
- // assumption
- if (!type.isUnknown() && !type.isObject()) {
- //optimistically inferred
- lc.logOptimisticAssumption(paramSymbol, type);
- }
-
- //known runtime types are hardcoded already in initParameters so avoid any
- //overly optimistic assumptions, e.g. a double parameter known from
- //RecompilableScriptFunctionData is with us all the way
- if (type.isUnknown()) {
- newType(paramSymbol, Type.OBJECT);
- }
-
- // if we are pessimistic, we are always an object
- if (pessimistic) {
- newType(paramSymbol, Type.OBJECT);
- }
-
- // parameters should not be slots for a function that uses variable arity signature
- if (isVarArg) {
- paramSymbol.setNeedsSlot(false);
- newType(paramSymbol, Type.OBJECT);
- }
- }
-
- final FunctionNode newFunctionNode = functionNode;
-
- return newFunctionNode.setParameters(lc, newParams);
- }
-
- /**
- * Move any properties from a global map into the scope of this method
- * @param block the function node body for which to init scope vars
- */
- private void initFromPropertyMap(final Block block) {
- // For a script, add scope symbols as defined in the property map
-
- final PropertyMap map = Context.getGlobalMap();
-
- for (final Property property : map.getProperties()) {
- final String key = property.getKey();
- final Symbol symbol = defineGlobalSymbol(block, key);
- newType(symbol, Type.OBJECT);
- log.info("Added global symbol from property map ", symbol);
- }
- }
-
- private static Symbol pseudoSymbol(final String name) {
- return new Symbol(name, 0, Type.OBJECT);
- }
-
- private Symbol exceptionSymbol() {
- return newInternal(lc.getCurrentFunction().uniqueName(EXCEPTION_PREFIX.symbolName()), Type.typeFor(EXCEPTION_PREFIX.type()));
- }
-
-
- /**
- * If types have changed, we can have failed to update vars. For example
- *
- * var x = 17; //x is int
- * x = "apa"; //x is object. This will be converted fine
- *
- * @param functionNode
- */
- private FunctionNode finalizeTypes(final FunctionNode functionNode) {
- final Set<Node> changed = new HashSet<>();
- final Deque<Type> returnTypes = new ArrayDeque<>();
-
- FunctionNode currentFunctionNode = functionNode;
- int fixedPointIterations = 0;
- do {
- fixedPointIterations++;
- assert fixedPointIterations < 0x100 : "too many fixed point iterations for " + functionNode.getName() + " -> most likely infinite loop";
- changed.clear();
-
- final FunctionNode newFunctionNode = (FunctionNode)currentFunctionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
-
- private Expression widen(final Expression node, final Type to) {
- if (node instanceof LiteralNode) {
- return node;
- }
- final Type from = node.getType();
- if (!Type.areEquivalent(from, to) && Type.widest(from, to) == to) {
- if (log.isEnabled()) {
- log.fine("Had to post pass widen '", node, "' ", Debug.id(node), " from ", node.getType(), " to ", to);
- }
- Symbol symbol = node.getSymbol();
- if (symbol.isShared() && symbol.wouldChangeType(to)) {
- symbol = temporarySymbols.getTypedTemporarySymbol(to);
- }
- newType(symbol, to);
- final Expression newNode = node.setSymbol(lc, symbol);
- if (node != newNode) {
- changed.add(newNode);
- }
- return newNode;
- }
- return node;
- }
-
- @Override
- public boolean enterFunctionNode(final FunctionNode node) {
- returnTypes.push(Type.UNKNOWN);
- return true;
- }
-
- @Override
- public Node leaveFunctionNode(final FunctionNode node) {
- Type returnType = returnTypes.pop();
- if (returnType.isUnknown()) {
- returnType = Type.OBJECT;
- }
- return node.setReturnType(lc, returnType);
- }
-
- @Override
- public Node leaveReturnNode(final ReturnNode returnNode) {
- Type returnType = returnTypes.pop();
- if (returnNode.hasExpression()) {
- returnType = widestReturnType(returnType, returnNode.getExpression().getType()); //getSymbol().getSymbolType());
- } else {
- returnType = Type.OBJECT; //undefined
- }
-
- returnTypes.push(returnType);
-
- return returnNode;
- }
-
- //
- // Eg.
- //
- // var d = 17;
- // var e;
- // e = d; //initially typed as int for node type, should retype as double
- // e = object;
- //
- // var d = 17;
- // var e;
- // e -= d; //initially type number, should number remain with a final conversion supplied by Store. ugly, but the computation result of the sub is numeric
- // e = object;
- //
- @SuppressWarnings("fallthrough")
- @Override
- public Node leaveBinaryNode(final BinaryNode binaryNode) {
- Type widest = Type.widest(binaryNode.lhs().getType(), binaryNode.rhs().getType());
- BinaryNode newBinaryNode = binaryNode;
-
- if (isAdd(binaryNode)) {
- if(widest.getTypeClass() == String.class) {
- // Erase "String" to "Object" as we have trouble with optimistically typed operands that
- // would be typed "String" in the code generator as they are always loaded using the type
- // of the operation.
- widest = Type.OBJECT;
- }
- newBinaryNode = (BinaryNode)widen(newBinaryNode, widest);
- if (newBinaryNode.getType().isObject() && !isAddString(newBinaryNode)) {
- return new RuntimeNode(newBinaryNode, Request.ADD);
- }
- } else if (binaryNode.isComparison()) {
- final Expression lhs = newBinaryNode.lhs();
- final Expression rhs = newBinaryNode.rhs();
-
- Type cmpWidest = Type.widest(lhs.getType(), rhs.getType());
-
- boolean newRuntimeNode = false, finalized = false;
- switch (newBinaryNode.tokenType()) {
- case EQ_STRICT:
- case NE_STRICT:
- if (lhs.getType().isBoolean() != rhs.getType().isBoolean()) {
- newRuntimeNode = true;
- cmpWidest = Type.OBJECT;
- finalized = true;
- }
- //fallthru
- default:
- if (newRuntimeNode || cmpWidest.isObject()) {
- return new RuntimeNode(newBinaryNode, Request.requestFor(binaryNode)).setIsFinal(finalized);
- }
- break;
- }
-
- return newBinaryNode;
- } else {
- if (!binaryNode.isAssignment() || binaryNode.isSelfModifying()) {
- return newBinaryNode;
- }
- checkThisAssignment(binaryNode);
- newBinaryNode = newBinaryNode.setLHS(widen(newBinaryNode.lhs(), widest));
- newBinaryNode = (BinaryNode)widen(newBinaryNode, widest);
- }
-
- return newBinaryNode;
-
- }
-
- @Override
- public Node leaveTernaryNode(final TernaryNode ternaryNode) {
- return widen(ternaryNode, Type.widest(ternaryNode.getTrueExpression().getType(), ternaryNode.getFalseExpression().getType()));
- }
-
- private boolean isAdd(final Node node) {
- return node.isTokenType(TokenType.ADD);
- }
-
- /**
- * Determine if the outcome of + operator is a string.
- *
- * @param node Node to test.
- * @return true if a string result.
- */
- private boolean isAddString(final Node node) {
- if (node instanceof BinaryNode && isAdd(node)) {
- final BinaryNode binaryNode = (BinaryNode)node;
- final Node lhs = binaryNode.lhs();
- final Node rhs = binaryNode.rhs();
-
- return isAddString(lhs) || isAddString(rhs);
- }
-
- return node instanceof LiteralNode<?> && ((LiteralNode<?>)node).isString();
- }
-
- private void checkThisAssignment(final BinaryNode binaryNode) {
- if (binaryNode.isAssignment()) {
- if (binaryNode.lhs() instanceof AccessNode) {
- final AccessNode accessNode = (AccessNode) binaryNode.lhs();
-
- if (accessNode.getBase().getSymbol().isThis()) {
- lc.getCurrentFunction().addThisProperty(accessNode.getProperty().getName());
- }
- }
- }
- }
- });
- lc.replace(currentFunctionNode, newFunctionNode);
- currentFunctionNode = newFunctionNode;
- } while (!changed.isEmpty());
-
- return currentFunctionNode;
- }
-
- private Node leaveSelfModifyingAssignmentNode(final BinaryNode binaryNode) {
- return leaveSelfModifyingAssignmentNode(binaryNode, binaryNode.getWidestOperationType());
- }
-
- private Node leaveSelfModifyingAssignmentNode(final BinaryNode binaryNode, final Type pessimisticType) {
- //e.g. for -=, Number, no wider, destType (binaryNode.getWidestOperationType()) is the coerce type
- final Expression lhs = binaryNode.lhs();
- final BinaryNode newBinaryNode = ensureSymbolTypeOverride(binaryNode, pessimisticType, Type.widest(lhs.getType(), binaryNode.rhs().getType()));
- newType(lhs.getSymbol(), newBinaryNode.getType()); //may not narrow if dest is already wider than destType
- return end(newBinaryNode);
- }
-
- private Expression ensureSymbol(final Expression expr, final Type type) {
- log.info("New TEMPORARY added to ", lc.getCurrentFunction().getName(), " type=", type);
- return temporarySymbols.ensureSymbol(lc, type, expr);
- }
-
- @Override
- public boolean enterReturnNode(final ReturnNode returnNode) {
- tagOptimistic(returnNode.getExpression());
- return true;
- }
-
- @Override
- public boolean enterIfNode(final IfNode ifNode) {
- tagNeverOptimistic(ifNode.getTest());
- return true;
- }
-
- @Override
- public boolean enterWhileNode(final WhileNode whileNode) {
- tagNeverOptimistic(whileNode.getTest());
- return true;
- }
-
- /**
- * Used to signal that children should be optimistic. Otherwise every identnode
- * in the entire program would basically start out as being guessed as an int
- * and warmup would take an ENORMOUS time. This is also used while we get all
- * the logic up and running, as we currently can't afford to debug every potential
- * situtation that has to do with unwarranted optimism. We currently only tag
- * type overrides, all other nodes are nops in this function
- *
- * @param expr an expression that is to be tagged as optimistic.
- */
- private long tag(final Optimistic expr) {
- return (long)lc.getCurrentFunction().getId() << 32 | expr.getProgramPoint();
- }
-
- /**
- * This is used to guarantee that there are no optimistic setters, something that
- * doesn't make sense in our current model, where only optimistic getters can exist.
- * If we set something, we use the callSiteType. We might want to use dual fields
- * though and incorporate this later for the option of putting something wider than
- * is currently in the field causing an UnwarrantedOptimismException.
- *
- * @param expr expression to be tagged as never optimistic
- */
- private void tagNeverOptimistic(final Expression expr) {
- if (expr instanceof Optimistic) {
- log.info("Tagging TypeOverride node '" + expr + "' never optimistic");
- neverOptimistic.add(tag((Optimistic)expr));
- }
- }
-
- private void tagOptimistic(final Expression expr) {
- if (expr instanceof Optimistic) {
- log.info("Tagging TypeOverride node '" + expr + "' as optimistic");
- optimistic.add(tag((Optimistic)expr));
- }
- }
-
- private boolean isTaggedNeverOptimistic(final Optimistic expr) {
- return neverOptimistic.contains(tag(expr));
- }
-
- private boolean isTaggedOptimistic(final Optimistic expr) {
- return optimistic.contains(tag(expr));
- }
-
- private Type getOptimisticType(final Optimistic expr) {
- return useOptimisticTypes() ? env.getOptimisticType(expr) : expr.getMostPessimisticType();
- }
-
- /**
- * This is the base function for typing a TypeOverride as optimistic. For any expression that
- * can also be a type override (call, ident node (scope load), access node, index node) we use
- * the override type to communicate optimism.
- *
- * @param pessimisticType conservative always guaranteed to work for this operation
- * @param to node to set type for
- */
- private <T extends Expression & Optimistic> T ensureSymbolTypeOverride(final T node, final Type pessimisticType) {
- return ensureSymbolTypeOverride(node, pessimisticType, null);
- }
-
- @SuppressWarnings("unchecked")
- private <T extends Expression & Optimistic> T ensureSymbolTypeOverride(final T node, final Type pessimisticType, final Type argumentsType) {
- // check what the most optimistic type for this node should be
- // if we are running with optimistic types, this starts out as e.g. int, and based on previous
- // failed assumptions it can be wider, for example double if we have failed this assumption
- // in a previous run
- final boolean isNeverOptimistic = isTaggedNeverOptimistic(node);
-
- // avoid optimistic type evaluation if the node is never optimistic
- Type optimisticType = isNeverOptimistic ? node.getMostPessimisticType() : getOptimisticType(node);
-
- if (argumentsType != null) {
- optimisticType = Type.widest(optimisticType, argumentsType);
- }
-
- // the symbol of the expression is the pessimistic one, i.e. IndexNodes are always Object for consistency
- // with the type system.
- T expr = (T)ensureSymbol(node, pessimisticType);
-
- if (optimisticType.isObject()) {
- return expr;
- }
-
- if (isNeverOptimistic) {
- return expr;
- }
-
- if(!(node instanceof FunctionCall && ((FunctionCall)node).isFunction())) {
- // in the case that we have an optimistic type, set the type override (setType is inherited from TypeOverride)
- // but maintain the symbol type set above. Also flag the function as optimistic. Don't do this for any
- // expressions that are used as the callee of a function call.
- if (optimisticType.narrowerThan(pessimisticType)) {
- expr = (T)expr.setType(temporarySymbols, optimisticType);
- expr = (T)Node.setIsOptimistic(expr, true);
- if (optimisticType.isPrimitive()) {
- final Symbol symbol = expr.getSymbol();
- if (symbol.isShared()) {
- expr = (T)expr.setSymbol(lc, symbol.createUnshared(symbol.getName()));
- }
- }
- log.fine(expr, " turned optimistic with type=", optimisticType);
- assert ((Optimistic)expr).isOptimistic();
- }
- }
- return expr;
- }
-
-
- private Symbol newInternal(final String name, final Type type) {
- return defineSymbol(lc.getCurrentBlock(), name, IS_VAR | IS_INTERNAL).setType(type); //NASHORN-73
- }
-
- private void newType(final Symbol symbol, final Type type) {
- final Type oldType = symbol.getSymbolType();
- symbol.setType(type);
-
- if (symbol.getSymbolType() != oldType) {
- log.info("New TYPE ", type, " for ", symbol," (was ", oldType, ")");
- }
-
- if (symbol.isParam()) {
- symbol.setType(type);
- log.info("Param type change ", symbol);
- }
- }
-
- private void pushLocalsFunction() {
- localDefs.push(new HashSet<String>());
- localUses.push(new HashSet<String>());
- }
-
- private void pushLocalsBlock() {
- localDefs.push(new HashSet<>(localDefs.peek()));
- localUses.push(new HashSet<>(localUses.peek()));
- }
-
- private void popLocals() {
- localDefs.pop();
- localUses.pop();
- }
-
- private void popLocalsFunction() {
- popLocals();
- }
-
- private boolean isLocalDef(final String name) {
- return localDefs.peek().contains(name);
- }
-
- private void addLocalDef(final String name) {
- log.info("Adding local def of symbol: '", name, "'");
- localDefs.peek().add(name);
- }
-
- private void removeLocalDef(final String name) {
- log.info("Removing local def of symbol: '", name, "'");
- localDefs.peek().remove(name);
- }
-
- private boolean isLocalUse(final String name) {
- return localUses.peek().contains(name);
- }
-
- private void addLocalUse(final String name) {
- log.info("Adding local use of symbol: '", name, "'");
- localUses.peek().add(name);
- }
-
- private void inferParameter(final Expression node, final Type type) {
- final Symbol symbol = node.getSymbol();
- if (useOptimisticTypes() && symbol.isParam()) {
- final Type symbolType = symbol.getSymbolType();
- if(symbolType.isBoolean() && !(type.isBoolean() || type == Type.OBJECT)) {
- // boolean parameters can only legally be widened to Object
- return;
- }
- if (symbolType != type) {
- log.info("Infer parameter type " + symbol + " ==> " + type + " " + lc.getCurrentFunction().getSource().getName() + " " + lc.getCurrentFunction().getName());
- }
- symbol.setType(type); //will be overwritten by object later if pessimistic anyway
- lc.logOptimisticAssumption(symbol, type);
- }
- }
-
- private BinaryNode coerce(final BinaryNode binaryNode, final Type pessimisticType) {
- return coerce(binaryNode, pessimisticType, null);
- }
-
- private BinaryNode coerce(final BinaryNode binaryNode, final Type pessimisticType, final Type argumentsType) {
- final BinaryNode newNode = ensureSymbolTypeOverride(binaryNode, pessimisticType, argumentsType);
- inferParameter(binaryNode.lhs(), newNode.getType());
- inferParameter(binaryNode.rhs(), newNode.getType());
- return newNode;
- }
-
- private UnaryNode coerce(final UnaryNode unaryNode, final Type pessimisticType) {
- return coerce(unaryNode, pessimisticType, null);
- }
-
- private UnaryNode coerce(final UnaryNode unaryNode, final Type pessimisticType, final Type argumentType) {
- UnaryNode newNode = ensureSymbolTypeOverride(unaryNode, pessimisticType, argumentType);
- if (newNode.isOptimistic()) {
- if (unaryNode.getExpression() instanceof Optimistic) {
- newNode = newNode.setExpression(Node.setIsOptimistic(unaryNode.getExpression(), true));
- }
- }
- inferParameter(unaryNode.getExpression(), newNode.getType());
- return newNode;
- }
-
- 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);
- }
-
- 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;
- }
-
- 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(node instanceof Statement) {
- // If we're done with a statement, all temporaries can be reused.
- temporarySymbols.reuse();
- }
- 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 Expression) {
- final Symbol symbol = ((Expression)node).getSymbol();
- if (symbol == null) {
- sb.append(" <NO SYMBOL>");
- } else {
- sb.append(" <symbol=").append(symbol).append('>');
- }
- }
-
- log.unindent();
- log.info(sb);
- }
-
- return node;
- }
-}
--- a/nashorn/src/jdk/nashorn/internal/codegen/BranchOptimizer.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/BranchOptimizer.java Tue May 13 11:30:40 2014 +0200
@@ -32,10 +32,10 @@
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 @@
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 @@
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 @@
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 {
--- a/nashorn/src/jdk/nashorn/internal/codegen/ClassEmitter.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/ClassEmitter.java Tue May 13 11:30:40 2014 +0200
@@ -489,9 +489,7 @@
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 @@
null,
null);
- final MethodEmitter method = new MethodEmitter(this, mv, functionNode);
- method.setParameterTypes(new FunctionSignature(functionNode).getParamTypes());
- return method;
+ return new MethodEmitter(this, mv, functionNode);
}
--- a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java Tue May 13 11:30:40 2014 +0200
@@ -47,8 +47,8 @@
import static jdk.nashorn.internal.codegen.CompilerConstants.typeDescriptor;
import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup;
import static jdk.nashorn.internal.codegen.ObjectClassGenerator.OBJECT_FIELDS_ONLY;
+import static jdk.nashorn.internal.ir.Symbol.HAS_SLOT;
import static jdk.nashorn.internal.ir.Symbol.IS_INTERNAL;
-import static jdk.nashorn.internal.ir.Symbol.IS_TEMP;
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid;
import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_APPLY_TO_CALL;
@@ -62,6 +62,7 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
@@ -72,14 +73,12 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import java.util.RandomAccess;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Supplier;
-
+import jdk.nashorn.internal.IntDeque;
import jdk.nashorn.internal.codegen.ClassEmitter.Flag;
import jdk.nashorn.internal.codegen.CompilerConstants.Call;
-import jdk.nashorn.internal.codegen.RuntimeCallSite.SpecializedRuntimeNode;
import jdk.nashorn.internal.codegen.types.ArrayType;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.AccessNode;
@@ -102,11 +101,16 @@
import jdk.nashorn.internal.ir.IdentNode;
import jdk.nashorn.internal.ir.IfNode;
import jdk.nashorn.internal.ir.IndexNode;
+import jdk.nashorn.internal.ir.JoinPredecessor;
+import jdk.nashorn.internal.ir.JoinPredecessorExpression;
+import jdk.nashorn.internal.ir.LabelNode;
import jdk.nashorn.internal.ir.LexicalContext;
import jdk.nashorn.internal.ir.LexicalContextNode;
import jdk.nashorn.internal.ir.LiteralNode;
import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode.ArrayUnit;
+import jdk.nashorn.internal.ir.LiteralNode.PrimitiveLiteralNode;
+import jdk.nashorn.internal.ir.LocalVariableConversion;
import jdk.nashorn.internal.ir.LoopNode;
import jdk.nashorn.internal.ir.Node;
import jdk.nashorn.internal.ir.ObjectNode;
@@ -184,9 +188,9 @@
private static final Type SCRIPTFUNCTION_IMPL_TYPE = Type.typeFor(ScriptFunction.class);
private static final Call INIT_REWRITE_EXCEPTION = CompilerConstants.specialCallNoLookup(RewriteException.class,
- "<init>", void.class, UnwarrantedOptimismException.class, Object[].class, String[].class, ScriptObject.class);
+ "<init>", void.class, UnwarrantedOptimismException.class, Object[].class, String[].class);
private static final Call INIT_REWRITE_EXCEPTION_REST_OF = CompilerConstants.specialCallNoLookup(RewriteException.class,
- "<init>", void.class, UnwarrantedOptimismException.class, Object[].class, String[].class, ScriptObject.class, int[].class);
+ "<init>", void.class, UnwarrantedOptimismException.class, Object[].class, String[].class, int[].class);
private static final Call ENSURE_INT = CompilerConstants.staticCallNoLookup(OptimisticReturnFilters.class,
"ensureInt", int.class, Object.class, int.class);
@@ -195,6 +199,13 @@
private static final Call ENSURE_NUMBER = CompilerConstants.staticCallNoLookup(OptimisticReturnFilters.class,
"ensureNumber", double.class, Object.class, int.class);
+ private static final Class<?> ITERATOR_CLASS = Iterator.class;
+ static {
+ assert ITERATOR_CLASS == CompilerConstants.ITERATOR_PREFIX.type();
+ }
+ private static final Type ITERATOR_TYPE = Type.typeFor(ITERATOR_CLASS);
+ private static final Type EXCEPTION_TYPE = Type.typeFor(CompilerConstants.EXCEPTION_PREFIX.type());
+
/** Constant data & installation. The only reason the compiler keeps this is because it is assigned
* by reflection in class installation */
private final Compiler compiler;
@@ -223,6 +234,11 @@
/** From what size should we use spill instead of fields for JavaScript objects? */
private static final int OBJECT_SPILL_THRESHOLD = Options.getIntProperty("nashorn.spill.threshold", 256);
+ private static boolean assertsEnabled = false;
+ static {
+ assert assertsEnabled = true; // Intentional side effect
+ }
+
private final Set<String> emittedMethods = new HashSet<>();
// Function Id -> ContinuationInfo. Used by compilation of rest-of function only.
@@ -232,6 +248,11 @@
private final Set<Integer> initializedFunctionIds = new HashSet<>();
+ private static final Label METHOD_BOUNDARY = new Label("");
+ private final Deque<Label> catchLabels = new ArrayDeque<>();
+ // Number of live locals on entry to (and thus also break from) labeled blocks.
+ private final IntDeque labeledBlockBreakLiveLocals = new IntDeque();
+
/**
* Constructor.
*
@@ -265,109 +286,44 @@
}
/**
- * For an optimistic call site, we need to tag the callsite optimistic and
- * encode the program point of the callsite into it
- *
- * @param node node that can be optimistic
- * @return
- */
- private int getCallSiteFlagsOptimistic(final Optimistic node) {
- int flags = getCallSiteFlags();
- if (node.isOptimistic()) {
- flags |= CALLSITE_OPTIMISTIC;
- flags |= node.getProgramPoint() << CALLSITE_PROGRAM_POINT_SHIFT; //encode program point in high bits
- }
- return flags;
- }
-
- private static boolean isOptimistic(final int flags) {
- return (flags & CALLSITE_OPTIMISTIC) != 0;
- }
-
- /**
* Load an identity node
*
* @param identNode an identity node to load
* @return the method generator used
*/
- private MethodEmitter loadIdent(final IdentNode identNode, final Type type) {
+ private MethodEmitter loadIdent(final IdentNode identNode, final TypeBounds resultBounds) {
final Symbol symbol = identNode.getSymbol();
if (!symbol.isScope()) {
+ final Type type = identNode.getType();
+ if(type == Type.UNDEFINED) {
+ return method.loadUndefined(Type.OBJECT);
+ }
+
assert symbol.hasSlot() || symbol.isParam();
- return method.load(symbol).convert(type);
- }
-
- // If this is either __FILE__, __DIR__, or __LINE__ then load the property initially as Object as we'd convert
- // it anyway for replaceLocationPropertyPlaceholder.
- final boolean isCompileTimePropertyName = identNode.isCompileTimePropertyName();
+ return method.load(identNode);
+ }
assert identNode.getSymbol().isScope() : identNode + " is not in scope!";
- final int flags = CALLSITE_SCOPE | getCallSiteFlagsOptimistic(identNode);
+ final int flags = CALLSITE_SCOPE | getCallSiteFlags();
if (isFastScope(symbol)) {
// Only generate shared scope getter for fast-scope symbols so we know we can dial in correct scope.
if (symbol.getUseCount() > SharedScopeCall.FAST_SCOPE_GET_THRESHOLD && !isOptimisticOrRestOf()) {
method.loadCompilerConstant(SCOPE);
- loadSharedScopeVar(type, symbol, flags);
+ // As shared scope vars are only used in non-optimistic compilation, we switch from using TypeBounds to
+ // just a single definitive type, resultBounds.widest.
+ loadSharedScopeVar(resultBounds.widest, symbol, flags);
} else {
- loadFastScopeVar(identNode, type, flags, isCompileTimePropertyName);
+ new LoadFastScopeVar(identNode, resultBounds, flags).emit();
}
} else {
//slow scope load, we have no proto depth
- new OptimisticOperation() {
- @Override
- void loadStack() {
- method.loadCompilerConstant(SCOPE);
- }
- @Override
- void consumeStack() {
- dynamicGet(method, identNode, isCompileTimePropertyName ? Type.OBJECT : type, identNode.getName(), flags, identNode.isFunction());
- if(isCompileTimePropertyName) {
- replaceCompileTimeProperty(identNode, type);
- }
- }
- }.emit(identNode, type);
+ new LoadScopeVar(identNode, resultBounds, flags).emit();
}
return method;
}
- private void replaceCompileTimeProperty(final IdentNode identNode, final Type type) {
- final String name = identNode.getSymbol().getName();
- if (CompilerConstants.__FILE__.name().equals(name)) {
- replaceCompileTimeProperty(identNode, type, getCurrentSource().getName());
- } else if (CompilerConstants.__DIR__.name().equals(name)) {
- replaceCompileTimeProperty(identNode, type, getCurrentSource().getBase());
- } else if (CompilerConstants.__LINE__.name().equals(name)) {
- replaceCompileTimeProperty(identNode, type, getCurrentSource().getLine(identNode.position()));
- }
- }
-
- /**
- * When an ident with name __FILE__, __DIR__, or __LINE__ is loaded, we'll try to look it up as any other
- * identifier. However, if it gets all the way up to the Global object, it will send back a special value that
- * represents a placeholder for these compile-time location properties. This method will generate code that loads
- * the value of the compile-time location property and then invokes a method in Global that will replace the
- * placeholder with the value. Effectively, if the symbol for these properties is defined anywhere in the lexical
- * scope, they take precedence, but if they aren't, then they resolve to the compile-time location property.
- * @param identNode the ident node
- * @param type the desired return type for the ident node
- * @param propertyValue the actual value of the property
- */
- private void replaceCompileTimeProperty(final IdentNode identNode, final Type type, final Object propertyValue) {
- assert method.peekType().isObject();
- if(propertyValue instanceof String) {
- method.load((String)propertyValue);
- } else if(propertyValue instanceof Integer) {
- method.load(((Integer)propertyValue).intValue());
- method.convert(Type.OBJECT);
- } else {
- throw new AssertionError();
- }
- globalReplaceLocationPropertyPlaceholder();
- convertOptimisticReturnValue(identNode, type);
- }
-
private boolean isOptimisticOrRestOf() {
return useOptimisticTypes() || compiler.getCompilationEnvironment().isCompileRestOf();
}
@@ -433,21 +389,47 @@
return lc.getScopeGet(unit, symbol, valueType, flags | CALLSITE_FAST_SCOPE).generateInvoke(method);
}
- private MethodEmitter loadFastScopeVar(final IdentNode identNode, final Type type, final int flags, final boolean isCompileTimePropertyName) {
- return new OptimisticOperation() {
- @Override
- void loadStack() {
- method.loadCompilerConstant(SCOPE);
- loadFastScopeProto(identNode.getSymbol(), false);
- }
- @Override
- void consumeStack() {
- dynamicGet(method, identNode, isCompileTimePropertyName ? Type.OBJECT : type, identNode.getSymbol().getName(), flags | CALLSITE_FAST_SCOPE, identNode.isFunction());
- if (isCompileTimePropertyName) {
- replaceCompileTimeProperty(identNode, type);
- }
- }
- }.emit(identNode, type);
+ private class LoadScopeVar extends OptimisticOperation {
+ final IdentNode identNode;
+ private final int flags;
+
+ LoadScopeVar(final IdentNode identNode, final TypeBounds resultBounds, final int flags) {
+ super(identNode, resultBounds);
+ this.identNode = identNode;
+ this.flags = flags;
+ }
+
+ @Override
+ void loadStack() {
+ method.loadCompilerConstant(SCOPE);
+ getProto();
+ }
+
+ void getProto() {
+ }
+
+ @Override
+ void consumeStack() {
+ // If this is either __FILE__, __DIR__, or __LINE__ then load the property initially as Object as we'd convert
+ // it anyway for replaceLocationPropertyPlaceholder.
+ if(identNode.isCompileTimePropertyName()) {
+ method.dynamicGet(Type.OBJECT, identNode.getSymbol().getName(), flags, identNode.isFunction());
+ replaceCompileTimeProperty();
+ } else {
+ dynamicGet(identNode.getSymbol().getName(), flags, identNode.isFunction());
+ }
+ }
+ }
+
+ private class LoadFastScopeVar extends LoadScopeVar {
+ LoadFastScopeVar(final IdentNode identNode, final TypeBounds resultBounds, final int flags) {
+ super(identNode, resultBounds, flags | CALLSITE_FAST_SCOPE);
+ }
+
+ @Override
+ void getProto() {
+ loadFastScopeProto(identNode.getSymbol(), false);
+ }
}
private MethodEmitter storeFastScopeVar(final Symbol symbol, final int flags) {
@@ -457,7 +439,7 @@
}
private int getScopeProtoDepth(final Block startingBlock, final Symbol symbol) {
- //walk up the chain from startingblock and when we bump into the current function boundary, add the external
+ //walk up the chain from starting block and when we bump into the current function boundary, add the external
//information.
final FunctionNode fn = lc.getCurrentFunction();
final int fnId = fn.getId();
@@ -495,15 +477,22 @@
}
/**
- * Generate code that loads this node to the stack. This method is only
- * public to be accessible from the maps sub package. Do not call externally
+ * Generate code that loads this node to the stack, not constraining its type
*
- * @param node node to load
+ * @param expr node to load
*
* @return the method emitter used
*/
- MethodEmitter load(final Expression node) {
- return load(node, node.hasType() ? node.getType() : null);
+ private MethodEmitter loadExpressionUnbounded(final Expression expr) {
+ return loadExpression(expr, TypeBounds.UNBOUNDED);
+ }
+
+ private MethodEmitter loadExpressionAsObject(final Expression expr) {
+ return loadExpression(expr, TypeBounds.OBJECT);
+ }
+
+ MethodEmitter loadExpressionAsBoolean(final Expression expr) {
+ return loadExpression(expr, TypeBounds.BOOLEAN);
}
// Test whether conversion from source to target involves a call of ES 9.1 ToPrimitive
@@ -513,11 +502,11 @@
return source.isJSPrimitive() || !target.isJSPrimitive() || target.isBoolean();
}
- MethodEmitter loadBinaryOperands(final Expression lhs, final Expression rhs, final Type type) {
- return loadBinaryOperands(lhs, rhs, type, false);
+ MethodEmitter loadBinaryOperands(final BinaryNode binaryNode) {
+ return loadBinaryOperands(binaryNode.lhs(), binaryNode.rhs(), TypeBounds.UNBOUNDED.notWiderThan(binaryNode.getWidestOperandType()), false);
}
- private MethodEmitter loadBinaryOperands(final Expression lhs, final Expression rhs, final Type type, final boolean baseAlreadyOnStack) {
+ private MethodEmitter loadBinaryOperands(final Expression lhs, final Expression rhs, final TypeBounds explicitOperandBounds, final boolean baseAlreadyOnStack) {
// ECMAScript 5.1 specification (sections 11.5-11.11 and 11.13) prescribes that when evaluating a binary
// expression "LEFT op RIGHT", the order of operations must be: LOAD LEFT, LOAD RIGHT, CONVERT LEFT, CONVERT
// RIGHT, EXECUTE OP. Unfortunately, doing it in this order defeats potential optimizations that arise when we
@@ -528,38 +517,130 @@
// a primitive value, or RIGHT is an expression that loads without side effects, then we can do the
// reordering and collapse LOAD/CONVERT into a single operation; otherwise we need to do the more costly
// separate operations to preserve specification semantics.
- if (noToPrimitiveConversion(lhs.getType(), type) || rhs.isLocal()) {
+
+ // Operands' load type should not be narrower than the narrowest of the individual operand types, nor narrower
+ // than the lower explicit bound, but it should also not be wider than
+ final Type narrowestOperandType = Type.narrowest(Type.widest(lhs.getType(), rhs.getType()), explicitOperandBounds.widest);
+ final TypeBounds operandBounds = explicitOperandBounds.notNarrowerThan(narrowestOperandType);
+ if (noToPrimitiveConversion(lhs.getType(), explicitOperandBounds.widest) || rhs.isLocal()) {
// Can reorder. Combine load and convert into single operations.
- load(lhs, type, baseAlreadyOnStack);
- load(rhs, type, false);
+ loadExpression(lhs, operandBounds, baseAlreadyOnStack);
+ loadExpression(rhs, operandBounds, false);
} else {
// Can't reorder. Load and convert separately.
- load(lhs, lhs.getType(), baseAlreadyOnStack);
- load(rhs, rhs.getType(), false);
- method.swap().convert(type).swap().convert(type);
- }
+ final TypeBounds safeConvertBounds = TypeBounds.UNBOUNDED.notNarrowerThan(narrowestOperandType);
+ loadExpression(lhs, safeConvertBounds, baseAlreadyOnStack);
+ loadExpression(rhs, safeConvertBounds, false);
+ method.swap().convert(operandBounds.within(method.peekType())).swap().convert(operandBounds.within(method.peekType()));
+ }
+ assert Type.generic(method.peekType()) == operandBounds.narrowest;
+ assert Type.generic(method.peekType(1)) == operandBounds.narrowest;
return method;
}
- MethodEmitter loadBinaryOperands(final BinaryNode node) {
- return loadBinaryOperands(node.lhs(), node.rhs(), node.getType(), false);
- }
-
- MethodEmitter load(final Expression node, final Type type) {
- return load(node, type, false);
+ private static final class TypeBounds {
+ final Type narrowest;
+ final Type widest;
+
+ static final TypeBounds UNBOUNDED = new TypeBounds(Type.UNKNOWN, Type.OBJECT);
+ static final TypeBounds INT = exact(Type.INT);
+ static final TypeBounds NUMBER = exact(Type.NUMBER);
+ static final TypeBounds OBJECT = exact(Type.OBJECT);
+ static final TypeBounds BOOLEAN = exact(Type.BOOLEAN);
+
+ static TypeBounds exact(final Type type) {
+ return new TypeBounds(type, type);
+ }
+
+ TypeBounds(final Type narrowest, final Type widest) {
+ assert widest != null && widest != Type.UNDEFINED && widest != Type.UNKNOWN : widest;
+ assert narrowest != null && narrowest != Type.UNDEFINED : narrowest;
+ assert !narrowest.widerThan(widest) : narrowest + " wider than " + widest;
+ assert !widest.narrowerThan(narrowest);
+ this.narrowest = Type.generic(narrowest);
+ this.widest = Type.generic(widest);
+ }
+
+ TypeBounds notNarrowerThan(final Type type) {
+ return maybeNew(Type.narrowest(Type.widest(narrowest, type), widest), widest);
+ }
+
+ TypeBounds notWiderThan(final Type type) {
+ return maybeNew(Type.narrowest(narrowest, type), Type.narrowest(widest, type));
+ }
+
+ boolean canBeNarrowerThan(final Type type) {
+ return narrowest.narrowerThan(type);
+ }
+
+ TypeBounds maybeNew(final Type newNarrowest, final Type newWidest) {
+ if(newNarrowest == narrowest && newWidest == widest) {
+ return this;
+ }
+ return new TypeBounds(newNarrowest, newWidest);
+ }
+
+ TypeBounds booleanToInt() {
+ return maybeNew(booleanToInt(narrowest), booleanToInt(widest));
+ }
+
+ TypeBounds objectToNumber() {
+ return maybeNew(objectToNumber(narrowest), objectToNumber(widest));
+ }
+
+ private static Type booleanToInt(Type t) {
+ return t == Type.BOOLEAN ? Type.INT : t;
+ }
+
+ private static Type objectToNumber(Type t) {
+ return t.isObject() ? Type.NUMBER : t;
+ }
+
+ Type within(final Type type) {
+ if(type.narrowerThan(narrowest)) {
+ return narrowest;
+ }
+ if(type.widerThan(widest)) {
+ return widest;
+ }
+ return type;
+ }
+
+ @Override
+ public String toString() {
+ return "[" + narrowest + ", " + widest + "]";
+ }
}
- private MethodEmitter load(final Expression node, final Type type, final boolean baseAlreadyOnStack) {
- final Symbol symbol = node.getSymbol();
-
- // If we lack symbols, we just generate what we see.
- if (symbol == null || type == null) {
- node.accept(this);
- return method;
- }
-
- assert !type.isUnknown();
+ MethodEmitter loadExpressionAsType(final Expression expr, final Type type) {
+ if(type == Type.BOOLEAN) {
+ return loadExpressionAsBoolean(expr);
+ } else if(type == Type.UNDEFINED) {
+ assert expr.getType() == Type.UNDEFINED;
+ return loadExpressionAsObject(expr);
+ }
+ // having no upper bound preserves semantics of optimistic operations in the expression (by not having them
+ // converted early) and then applies explicit conversion afterwards.
+ return loadExpression(expr, TypeBounds.UNBOUNDED.notNarrowerThan(type)).convert(type);
+ }
+
+ private MethodEmitter loadExpression(final Expression expr, final TypeBounds resultBounds) {
+ return loadExpression(expr, resultBounds, false);
+ }
+
+ /**
+ * Emits code for evaluating an expression and leaving its value on top of the stack, narrowing or widening it if
+ * necessary.
+ * @param expr the expression to load
+ * @param resultBounds the incoming type bounds. The value on the top of the stack is guaranteed to not be of narrower
+ * type than the narrowest bound, or wider type than the widest bound after it is loaded.
+ * @param baseAlreadyOnStack true if the base of an access or index node is already on the stack. Used to avoid
+ * double evaluation of bases in self-assignment expressions to access and index nodes. {@code Type.OBJECT} is used
+ * to indicate the widest possible type.
+ * @return the method emitter
+ */
+ private MethodEmitter loadExpression(final Expression expr, final TypeBounds resultBounds, final boolean baseAlreadyOnStack) {
/*
* The load may be of type IdentNode, e.g. "x", AccessNode, e.g. "x.y"
@@ -568,48 +649,49 @@
*/
final CodeGenerator codegen = this;
- node.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
+ final Node currentDiscard = codegen.lc.getCurrentDiscard();
+ expr.accept(new NodeOperatorVisitor<LexicalContext>(new LexicalContext()) {
@Override
public boolean enterIdentNode(final IdentNode identNode) {
- loadIdent(identNode, type);
+ loadIdent(identNode, resultBounds);
return false;
}
@Override
public boolean enterAccessNode(final AccessNode accessNode) {
- new OptimisticOperation() {
+ new OptimisticOperation(accessNode, resultBounds) {
@Override
void loadStack() {
if (!baseAlreadyOnStack) {
- load(accessNode.getBase(), Type.OBJECT);
+ loadExpressionAsObject(accessNode.getBase());
}
assert method.peekType().isObject();
}
@Override
void consumeStack() {
- final int flags = getCallSiteFlagsOptimistic(accessNode);
- dynamicGet(method, accessNode, type, accessNode.getProperty().getName(), flags, accessNode.isFunction());
+ final int flags = getCallSiteFlags();
+ dynamicGet(accessNode.getProperty(), flags, accessNode.isFunction());
}
- }.emit(accessNode, baseAlreadyOnStack ? 1 : 0);
+ }.emit(baseAlreadyOnStack ? 1 : 0);
return false;
}
@Override
public boolean enterIndexNode(final IndexNode indexNode) {
- new OptimisticOperation() {
+ new OptimisticOperation(indexNode, resultBounds) {
@Override
void loadStack() {
if (!baseAlreadyOnStack) {
- load(indexNode.getBase(), Type.OBJECT);
- load(indexNode.getIndex());
+ loadExpressionAsObject(indexNode.getBase());
+ loadExpressionUnbounded(indexNode.getIndex());
}
}
@Override
void consumeStack() {
- final int flags = getCallSiteFlagsOptimistic(indexNode);
- dynamicGetIndex(method, indexNode, type, flags, indexNode.isFunction());
+ final int flags = getCallSiteFlags();
+ dynamicGetIndex(flags, indexNode.isFunction());
}
- }.emit(indexNode, baseAlreadyOnStack ? 2 : 0);
+ }.emit(baseAlreadyOnStack ? 2 : 0);
return false;
}
@@ -624,122 +706,337 @@
// is the last element in the compilation pipeline, the AST it produces is not used externally. So, we
// re-push the original functionNode.
lc.push(functionNode);
- method.convert(type);
+ return false;
+ }
+
+ @Override
+ public boolean enterASSIGN(final BinaryNode binaryNode) {
+ loadASSIGN(binaryNode);
+ return false;
+ }
+
+ @Override
+ public boolean enterASSIGN_ADD(final BinaryNode binaryNode) {
+ loadASSIGN_ADD(binaryNode);
+ return false;
+ }
+
+ @Override
+ public boolean enterASSIGN_BIT_AND(final BinaryNode binaryNode) {
+ loadASSIGN_BIT_AND(binaryNode);
+ return false;
+ }
+
+ @Override
+ public boolean enterASSIGN_BIT_OR(final BinaryNode binaryNode) {
+ loadASSIGN_BIT_OR(binaryNode);
+ return false;
+ }
+
+ @Override
+ public boolean enterASSIGN_BIT_XOR(final BinaryNode binaryNode) {
+ loadASSIGN_BIT_XOR(binaryNode);
+ return false;
+ }
+
+ @Override
+ public boolean enterASSIGN_DIV(final BinaryNode binaryNode) {
+ loadASSIGN_DIV(binaryNode);
+ return false;
+ }
+
+ @Override
+ public boolean enterASSIGN_MOD(final BinaryNode binaryNode) {
+ loadASSIGN_MOD(binaryNode);
+ return false;
+ }
+
+ @Override
+ public boolean enterASSIGN_MUL(final BinaryNode binaryNode) {
+ loadASSIGN_MUL(binaryNode);
+ return false;
+ }
+
+ @Override
+ public boolean enterASSIGN_SAR(final BinaryNode binaryNode) {
+ loadASSIGN_SAR(binaryNode);
+ return false;
+ }
+
+ @Override
+ public boolean enterASSIGN_SHL(final BinaryNode binaryNode) {
+ loadASSIGN_SHL(binaryNode);
+ return false;
+ }
+
+ @Override
+ public boolean enterASSIGN_SHR(final BinaryNode binaryNode) {
+ loadASSIGN_SHR(binaryNode);
+ return false;
+ }
+
+ @Override
+ public boolean enterASSIGN_SUB(final BinaryNode binaryNode) {
+ loadASSIGN_SUB(binaryNode);
return false;
}
@Override
public boolean enterCallNode(final CallNode callNode) {
- return codegen.enterCallNode(callNode, type);
+ return loadCallNode(callNode, resultBounds);
}
@Override
public boolean enterLiteralNode(final LiteralNode<?> literalNode) {
- return codegen.enterLiteralNode(literalNode, type);
+ loadLiteral(literalNode, resultBounds);
+ return false;
+ }
+
+ @Override
+ public boolean enterTernaryNode(final TernaryNode ternaryNode) {
+ loadTernaryNode(ternaryNode, resultBounds);
+ return false;
+ }
+
+ @Override
+ public boolean enterADD(final BinaryNode binaryNode) {
+ loadADD(binaryNode, resultBounds);
+ return false;
+ }
+
+ @Override
+ public boolean enterSUB(UnaryNode unaryNode) {
+ loadSUB(unaryNode, resultBounds);
+ return false;
+ }
+
+ @Override
+ public boolean enterSUB(final BinaryNode binaryNode) {
+ loadSUB(binaryNode, resultBounds);
+ return false;
+ }
+
+ @Override
+ public boolean enterMUL(final BinaryNode binaryNode) {
+ loadMUL(binaryNode, resultBounds);
+ return false;
+ }
+
+ @Override
+ public boolean enterDIV(final BinaryNode binaryNode) {
+ loadDIV(binaryNode, resultBounds);
+ return false;
+ }
+
+ @Override
+ public boolean enterMOD(final BinaryNode binaryNode) {
+ loadMOD(binaryNode, resultBounds);
+ return false;
+ }
+
+ @Override
+ public boolean enterSAR(final BinaryNode binaryNode) {
+ loadSAR(binaryNode);
+ return false;
+ }
+
+ @Override
+ public boolean enterSHL(final BinaryNode binaryNode) {
+ loadSHL(binaryNode);
+ return false;
+ }
+
+ @Override
+ public boolean enterSHR(final BinaryNode binaryNode) {
+ loadSHR(binaryNode);
+ return false;
+ }
+
+ @Override
+ public boolean enterCOMMALEFT(final BinaryNode binaryNode) {
+ loadCOMMALEFT(binaryNode, resultBounds);
+ return false;
+ }
+
+ @Override
+ public boolean enterCOMMARIGHT(final BinaryNode binaryNode) {
+ loadCOMMARIGHT(binaryNode, resultBounds);
+ return false;
+ }
+
+ @Override
+ public boolean enterAND(final BinaryNode binaryNode) {
+ loadAND_OR(binaryNode, resultBounds, true);
+ return false;
+ }
+
+ @Override
+ public boolean enterOR(final BinaryNode binaryNode) {
+ loadAND_OR(binaryNode, resultBounds, false);
+ return false;
+ }
+
+ @Override
+ public boolean enterNOT(UnaryNode unaryNode) {
+ loadNOT(unaryNode);
+ return false;
+ }
+
+ @Override
+ public boolean enterADD(UnaryNode unaryNode) {
+ loadADD(unaryNode, resultBounds);
+ return false;
+ }
+
+ @Override
+ public boolean enterBIT_NOT(UnaryNode unaryNode) {
+ loadBIT_NOT(unaryNode);
+ return false;
+ }
+
+ @Override
+ public boolean enterBIT_AND(final BinaryNode binaryNode) {
+ loadBIT_AND(binaryNode);
+ return false;
+ }
+
+ @Override
+ public boolean enterBIT_OR(final BinaryNode binaryNode) {
+ loadBIT_OR(binaryNode);
+ return false;
+ }
+
+ @Override
+ public boolean enterBIT_XOR(final BinaryNode binaryNode) {
+ loadBIT_XOR(binaryNode);
+ return false;
+ }
+
+ @Override
+ public boolean enterVOID(UnaryNode unaryNode) {
+ loadVOID(unaryNode, resultBounds);
+ return false;
+ }
+
+ @Override
+ public boolean enterEQ(final BinaryNode binaryNode) {
+ loadCmp(binaryNode, Condition.EQ);
+ return false;
+ }
+
+ @Override
+ public boolean enterEQ_STRICT(final BinaryNode binaryNode) {
+ loadCmp(binaryNode, Condition.EQ);
+ return false;
+ }
+
+ @Override
+ public boolean enterGE(final BinaryNode binaryNode) {
+ loadCmp(binaryNode, Condition.GE);
+ return false;
+ }
+
+ @Override
+ public boolean enterGT(final BinaryNode binaryNode) {
+ loadCmp(binaryNode, Condition.GT);
+ return false;
+ }
+
+ @Override
+ public boolean enterLE(final BinaryNode binaryNode) {
+ loadCmp(binaryNode, Condition.LE);
+ return false;
+ }
+
+ @Override
+ public boolean enterLT(final BinaryNode binaryNode) {
+ loadCmp(binaryNode, Condition.LT);
+ return false;
+ }
+
+ @Override
+ public boolean enterNE(final BinaryNode binaryNode) {
+ loadCmp(binaryNode, Condition.NE);
+ return false;
+ }
+
+ @Override
+ public boolean enterNE_STRICT(final BinaryNode binaryNode) {
+ loadCmp(binaryNode, Condition.NE);
+ return false;
+ }
+
+ @Override
+ public boolean enterObjectNode(final ObjectNode objectNode) {
+ loadObjectNode(objectNode);
+ return false;
+ }
+
+ @Override
+ public boolean enterRuntimeNode(final RuntimeNode runtimeNode) {
+ loadRuntimeNode(runtimeNode);
+ return false;
+ }
+
+ @Override
+ public boolean enterNEW(final UnaryNode unaryNode) {
+ loadNEW(unaryNode);
+ return false;
+ }
+
+ @Override
+ public boolean enterDECINC(final UnaryNode unaryNode) {
+ loadDECINC(unaryNode);
+ return false;
+ }
+
+ @Override
+ public boolean enterJoinPredecessorExpression(final JoinPredecessorExpression joinExpr) {
+ loadExpression(joinExpr.getExpression(), resultBounds);
+ return false;
}
@Override
public boolean enterDefault(final Node otherNode) {
- final Node currentDiscard = codegen.lc.getCurrentDiscard();
- otherNode.accept(codegen); // generate code for whatever we are looking at.
- if(currentDiscard != otherNode) {
- method.load(symbol); // load the final symbol to the stack (or nop if no slot, then result is already there)
- assert method.peekType() != null;
- method.convert(type);
- }
- return false;
+ // Must have handled all expressions that can legally be encountered.
+ throw new AssertionError(otherNode.getClass().getName());
}
});
-
+ if(currentDiscard != expr) {
+ coerceStackTop(resultBounds);
+ }
return method;
}
- @Override
- public boolean enterAccessNode(final AccessNode accessNode) {
- load(accessNode);
- return false;
+ private MethodEmitter coerceStackTop(final TypeBounds typeBounds) {
+ return method.convert(typeBounds.within(method.peekType()));
}
/**
- * Initialize a specific set of vars to undefined. This has to be done at
- * the start of each method for local variables that aren't passed as
- * parameters.
- *
- * @param symbols list of symbols.
- */
- private void initSymbols(final Iterable<Symbol> symbols) {
- final LinkedList<Symbol> numbers = new LinkedList<>();
- final LinkedList<Symbol> objects = new LinkedList<>();
- final boolean useOptimistic = useOptimisticTypes();
-
- for (final Symbol symbol : symbols) {
- /*
- * The following symbols are guaranteed to be defined and thus safe
- * from having undefined written to them: parameters internals this
- *
- * Otherwise we must, unless we perform control/escape analysis,
- * assign them undefined.
- */
- final boolean isInternal = symbol.isParam() || symbol.isInternal() || symbol.isThis();
-
- if (symbol.hasSlot()) {
- final Type type = symbol.getSymbolType();
- if (symbol.canBeUndefined() && !isInternal) {
- if (type.isNumber()) {
- numbers.add(symbol);
- } else if (type.isObject()) {
- objects.add(symbol);
- } else {
- throw new AssertionError("no potentially undefined narrower local vars than doubles are allowed: " + symbol + " in " + lc.getCurrentFunction());
- }
- } else if(useOptimistic && !symbol.isAlwaysDefined()) {
- method.loadForcedInitializer(type);
- method.store(symbol);
- }
- }
- }
-
- initSymbols(numbers, Type.NUMBER);
- initSymbols(objects, Type.OBJECT);
- }
-
- private void initSymbols(final LinkedList<Symbol> symbols, final Type type) {
- final Iterator<Symbol> it = symbols.iterator();
- if(it.hasNext()) {
- method.loadUndefined(type);
- boolean hasNext;
- do {
- final Symbol symbol = it.next();
- hasNext = it.hasNext();
- if(hasNext) {
- method.dup();
- }
- method.store(symbol);
- } while(hasNext);
- }
- }
-
- /**
- * Create symbol debug information.
+ * Closes any still open entries for this block's local variables in the bytecode local variable table.
*
* @param block block containing symbols.
*/
- private void symbolInfo(final Block block) {
+ private void closeBlockVariables(final Block block) {
for (final Symbol symbol : block.getSymbols()) {
- if (symbol.hasSlot()) {
- method.localVariable(symbol, block.getEntryLabel(), block.getBreakLabel());
+ if (symbol.isBytecodeLocal()) {
+ method.closeLocalVariable(symbol, block.getBreakLabel());
}
}
}
@Override
public boolean enterBlock(final Block block) {
+ method.label(block.getEntryLabel());
+ if(!method.isReachable()) {
+ return false;
+ }
if(lc.isFunctionBody() && emittedMethods.contains(lc.getCurrentFunction().getName())) {
return false;
}
- method.label(block.getEntryLabel());
initLocals(block);
+ assert lc.getUsedSlotCount() == method.getFirstTemp();
return true;
}
@@ -749,53 +1046,86 @@
@Override
public Node leaveBlock(final Block block) {
-
popBlockScope(block);
- lc.releaseBlockSlots(useOptimisticTypes());
-
- symbolInfo(block);
+ method.beforeJoinPoint(block);
+
+ closeBlockVariables(block);
+ lc.releaseSlots();
+ assert !method.isReachable() || lc.getUsedSlotCount() == method.getFirstTemp();
+
return block;
}
private void popBlockScope(final Block block) {
+ final Label breakLabel = block.getBreakLabel();
+
if(!block.needsScope() || lc.isFunctionBody()) {
- method.label(block.getBreakLabel());
+ emitBlockBreakLabel(breakLabel);
return;
}
- final Label entry = scopeEntryLabels.pop();
- final Label afterCatchLabel;
+ final Label beginTryLabel = scopeEntryLabels.pop();
final Label recoveryLabel = new Label("block_popscope_catch");
-
- /* pop scope a la try-finally */
- if(block.isTerminal()) {
- // Block is terminal; there's no normal-flow path for popping the scope. Label current position as the end
- // of the try block, and mark after-catch to be the block's break label.
- final Label endTryLabel = new Label("block_popscope_end_try");
- method._try(entry, endTryLabel, recoveryLabel);
- method.label(endTryLabel);
- afterCatchLabel = block.getBreakLabel();
+ emitBlockBreakLabel(breakLabel);
+ final boolean bodyCanThrow = breakLabel.isAfter(beginTryLabel);
+ if(bodyCanThrow) {
+ method._try(beginTryLabel, breakLabel, recoveryLabel);
+ }
+
+ Label afterCatchLabel = null;
+
+ if(method.isReachable()) {
+ popScope();
+ if(bodyCanThrow) {
+ afterCatchLabel = new Label("block_after_catch");
+ method._goto(afterCatchLabel);
+ }
+ }
+
+ if(bodyCanThrow) {
+ assert !method.isReachable();
+ method._catch(recoveryLabel);
+ popScopeException();
+ method.athrow();
+ }
+ if(afterCatchLabel != null) {
+ method.label(afterCatchLabel);
+ }
+ }
+
+ private void emitBlockBreakLabel(final Label breakLabel) {
+ // TODO: this is totally backwards. Block should not be breakable, LabelNode should be breakable.
+ final LabelNode labelNode = lc.getCurrentBlockLabelNode();
+ if(labelNode != null) {
+ // Only have conversions if we're reachable
+ assert labelNode.getLocalVariableConversion() == null || method.isReachable();
+ method.beforeJoinPoint(labelNode);
+ method.breakLabel(breakLabel, labeledBlockBreakLiveLocals.pop());
} else {
- // Block is non-terminal; Label current position as the block's break label (as it'll need to execute the
- // scope popping when it gets here) and as the end of the try block. Mark after-catch with a new label.
- final Label endTryLabel = block.getBreakLabel();
- method._try(entry, endTryLabel, recoveryLabel);
- method.label(endTryLabel);
- popScope();
- afterCatchLabel = new Label("block_after_catch");
- method._goto(afterCatchLabel);
- }
-
- method._catch(recoveryLabel);
- popScope();
- method.athrow();
- method.label(afterCatchLabel);
+ method.label(breakLabel);
+ }
}
private void popScope() {
popScopes(1);
}
+ /**
+ * Pop scope as part of an exception handler. Similar to {@code popScope()} but also takes care of adjusting the
+ * number of scopes that needs to be popped in case a rest-of continuation handler encounters an exception while
+ * performing a ToPrimitive conversion.
+ */
+ private void popScopeException() {
+ popScope();
+ final ContinuationInfo ci = getContinuationInfo();
+ if(ci != null) {
+ final Label catchLabel = ci.catchLabel;
+ if(catchLabel != METHOD_BOUNDARY && catchLabel == catchLabels.peek()) {
+ ++ci.exceptionScopePops;
+ }
+ }
+ }
+
private void popScopesUntil(final LexicalContextNode until) {
popScopes(lc.getScopeNestingLevelTo(until));
}
@@ -815,61 +1145,37 @@
@Override
public boolean enterBreakNode(final BreakNode breakNode) {
+ if(!method.isReachable()) {
+ return false;
+ }
enterStatement(breakNode);
- final BreakableNode breakFrom = lc.getBreakable(breakNode.getLabel());
+ method.beforeJoinPoint(breakNode);
+ final BreakableNode breakFrom = lc.getBreakable(breakNode.getLabelName());
popScopesUntil(breakFrom);
- method.splitAwareGoto(lc, breakFrom.getBreakLabel());
+ final Label breakLabel = breakFrom.getBreakLabel();
+ breakLabel.markAsBreakTarget();
+ method.splitAwareGoto(lc, breakLabel, breakFrom);
return false;
}
private int loadArgs(final List<Expression> args) {
- return loadArgs(args, args.size());
- }
-
- private int loadArgs(final List<Expression> args, final int argCount) {
- return loadArgs(args, null, false, argCount);
- }
-
- private int loadArgs(final List<Expression> args, final String signature, final boolean isVarArg, final int argCount) {
+ final int argCount = args.size();
// arg have already been converted to objects here.
- if (isVarArg || argCount > LinkerCallSite.ARGLIMIT) {
+ if (argCount > LinkerCallSite.ARGLIMIT) {
loadArgsArray(args);
return 1;
}
- // pad with undefined if size is too short. argCount is the real number of args
- int n = 0;
- final Type[] params = signature == null ? null : Type.getMethodArguments(signature);
for (final Expression arg : args) {
assert arg != null;
- if (n >= argCount) {
- load(arg);
- method.pop(); // we had to load the arg for its side effects
- } else if (params != null) {
- load(arg, params[n]);
- } else {
- load(arg);
- }
- n++;
- }
-
- while (n < argCount) {
- method.loadUndefined(Type.OBJECT);
- n++;
- }
-
+ loadExpressionUnbounded(arg);
+ }
return argCount;
}
-
- @Override
- public boolean enterCallNode(final CallNode callNode) {
- return enterCallNode(callNode, callNode.getType());
- }
-
- private boolean enterCallNode(final CallNode callNode, final Type callNodeType) {
+ private boolean loadCallNode(final CallNode callNode, final TypeBounds resultBounds) {
lineNumber(callNode.getLineNumber());
final List<Expression> args = callNode.getArgs();
@@ -883,7 +1189,7 @@
final Symbol symbol = identNode.getSymbol();
final boolean isFastScope = isFastScope(symbol);
final int scopeCallFlags = flags | (isFastScope ? CALLSITE_FAST_SCOPE : 0);
- new OptimisticOperation() {
+ new OptimisticOperation(callNode, resultBounds) {
@Override
void loadStack() {
method.loadCompilerConstant(SCOPE);
@@ -897,40 +1203,48 @@
@Override
void consumeStack() {
final Type[] paramTypes = method.getTypesFromStack(args.size());
- final SharedScopeCall scopeCall = codegenLexicalContext.getScopeCall(unit, symbol, identNode.getType(), callNodeType, paramTypes, scopeCallFlags);
+ // We have trouble finding e.g. in Type.typeFor(asm.Type) because it can't see the Context class
+ // loader, so we need to weaken reference signatures to Object.
+ for(int i = 0; i < paramTypes.length; ++i) {
+ paramTypes[i] = Type.generic(paramTypes[i]);
+ }
+ // As shared scope calls are only used in non-optimistic compilation, we switch from using
+ // TypeBounds to just a single definitive type, resultBounds.widest.
+ final SharedScopeCall scopeCall = codegenLexicalContext.getScopeCall(unit, symbol,
+ identNode.getType(), resultBounds.widest, paramTypes, scopeCallFlags);
scopeCall.generateInvoke(method);
}
- }.emit(callNode);
+ }.emit();
return method;
}
- private void scopeCall(final IdentNode node, final int flags) {
- new OptimisticOperation() {
+ private void scopeCall(final IdentNode ident, final int flags) {
+ new OptimisticOperation(callNode, resultBounds) {
int argsCount;
@Override
void loadStack() {
- load(node, Type.OBJECT); // foo() makes no sense if foo == 3
+ loadExpressionAsObject(ident); // foo() makes no sense if foo == 3
// ScriptFunction will see CALLSITE_SCOPE and will bind scope accordingly.
method.loadUndefined(Type.OBJECT); //the 'this'
argsCount = loadArgs(args);
}
@Override
void consumeStack() {
- dynamicCall(method, callNode, callNodeType, 2 + argsCount, flags);
+ dynamicCall(2 + argsCount, flags);
}
- }.emit(callNode);
- }
-
- private void evalCall(final IdentNode node, final int flags) {
+ }.emit();
+ }
+
+ private void evalCall(final IdentNode ident, final int flags) {
final Label invoke_direct_eval = new Label("invoke_direct_eval");
final Label is_not_eval = new Label("is_not_eval");
final Label eval_done = new Label("eval_done");
- new OptimisticOperation() {
+ new OptimisticOperation(callNode, resultBounds) {
int argsCount;
@Override
void loadStack() {
- load(node, Type.OBJECT); // Type.OBJECT as foo() makes no sense if foo == 3
+ loadExpressionAsObject(ident); // Type.OBJECT as foo() makes no sense if foo == 3
method.dup();
globalIsEval();
method.ifeq(is_not_eval);
@@ -941,15 +1255,15 @@
method.loadCompilerConstant(SCOPE);
final CallNode.EvalArgs evalArgs = callNode.getEvalArgs();
// load evaluated code
- load(evalArgs.getCode(), Type.OBJECT);
+ loadExpressionAsObject(evalArgs.getCode());
// load second and subsequent args for side-effect
final List<Expression> callArgs = callNode.getArgs();
final int numArgs = callArgs.size();
for (int i = 1; i < numArgs; i++) {
- load(callArgs.get(i)).pop();
+ loadExpressionUnbounded(callArgs.get(i)).pop();
}
// special/extra 'eval' arguments
- load(evalArgs.getThis());
+ loadExpressionUnbounded(evalArgs.getThis());
method.load(evalArgs.getLocation());
method.load(evalArgs.getStrictMode());
method.convert(Type.OBJECT);
@@ -965,16 +1279,16 @@
@Override
void consumeStack() {
// Ordinary call
- dynamicCall(method, callNode, callNodeType, 2 + argsCount, flags);
+ dynamicCall(2 + argsCount, flags);
method._goto(eval_done);
method.label(invoke_direct_eval);
// direct call to Global.directEval
globalDirectEval();
- convertOptimisticReturnValue(callNode, callNodeType);
- method.convert(callNodeType);
+ convertOptimisticReturnValue();
+ coerceStackTop(resultBounds);
}
- }.emit(callNode);
+ }.emit();
method.label(eval_done);
}
@@ -984,7 +1298,7 @@
final Symbol symbol = node.getSymbol();
if (symbol.isScope()) {
- final int flags = getCallSiteFlagsOptimistic(callNode) | CALLSITE_SCOPE;
+ final int flags = getCallSiteFlags() | CALLSITE_SCOPE;
final int useCount = symbol.getUseCount();
// Threshold for generating shared scope callsite is lower for fast scope symbols because we know
@@ -1000,7 +1314,7 @@
} else {
sharedScopeCall(node, flags);
}
- assert method.peekType().equals(callNodeType) : method.peekType() + "!=" + callNode.getType();
+ assert method.peekType().equals(resultBounds.within(callNode.getType())) : method.peekType() + " != " + resultBounds + "(" + callNode.getType() + ")";
} else {
enterDefault(node);
}
@@ -1015,32 +1329,33 @@
//call nodes have program points.
- new OptimisticOperation() {
+ final int flags = getCallSiteFlags() | (callNode.isApplyToCall() ? CALLSITE_APPLY_TO_CALL : 0);
+
+ new OptimisticOperation(callNode, resultBounds) {
int argCount;
@Override
void loadStack() {
- load(node.getBase(), Type.OBJECT);
+ loadExpressionAsObject(node.getBase());
method.dup();
// NOTE: not using a nested OptimisticOperation on this dynamicGet, as we expect to get back
// a callable object. Nobody in their right mind would optimistically type this call site.
assert !node.isOptimistic();
- final int flags = getCallSiteFlags() | (callNode.isApplyToCall() ? CALLSITE_APPLY_TO_CALL : 0);
- method.dynamicGet(node.getType(), node.getProperty().getName(), flags, true);
+ method.dynamicGet(node.getType(), node.getProperty(), flags, true);
method.swap();
argCount = loadArgs(args);
}
@Override
void consumeStack() {
- dynamicCall(method, callNode, callNodeType, 2 + argCount, getCallSiteFlagsOptimistic(callNode) | (callNode.isApplyToCall() ? CALLSITE_APPLY_TO_CALL : 0));
+ dynamicCall(2 + argCount, flags);
}
- }.emit(callNode);
+ }.emit();
return false;
}
@Override
public boolean enterFunctionNode(final FunctionNode origCallee) {
- new OptimisticOperation() {
+ new OptimisticOperation(callNode, resultBounds) {
FunctionNode callee;
int argsCount;
@Override
@@ -1056,28 +1371,27 @@
@Override
void consumeStack() {
- final int flags = getCallSiteFlagsOptimistic(callNode);
+ final int flags = getCallSiteFlags();
//assert callNodeType.equals(callee.getReturnType()) : callNodeType + " != " + callee.getReturnType();
- dynamicCall(method, callNode, callNodeType, 2 + argsCount, flags);
+ dynamicCall(2 + argsCount, flags);
}
- }.emit(callNode);
- method.convert(callNodeType);
+ }.emit();
return false;
}
@Override
public boolean enterIndexNode(final IndexNode node) {
- new OptimisticOperation() {
+ new OptimisticOperation(callNode, resultBounds) {
int argsCount;
@Override
void loadStack() {
- load(node.getBase(), Type.OBJECT);
+ loadExpressionAsObject(node.getBase());
method.dup();
final Type indexType = node.getIndex().getType();
if (indexType.isObject() || indexType.isBoolean()) {
- load(node.getIndex(), Type.OBJECT); //TODO
+ loadExpressionAsObject(node.getIndex()); //TODO boolean
} else {
- load(node.getIndex());
+ loadExpressionUnbounded(node.getIndex());
}
// NOTE: not using a nested OptimisticOperation on this dynamicGetIndex, as we expect to get
// back a callable object. Nobody in their right mind would optimistically type this call site.
@@ -1088,153 +1402,68 @@
}
@Override
void consumeStack() {
- final int flags = getCallSiteFlagsOptimistic(callNode);
- dynamicCall(method, callNode, callNodeType, 2 + argsCount, flags);
+ final int flags = getCallSiteFlags();
+ dynamicCall(2 + argsCount, flags);
}
- }.emit(callNode);
+ }.emit();
return false;
}
@Override
protected boolean enterDefault(final Node node) {
- new OptimisticOperation() {
+ new OptimisticOperation(callNode, resultBounds) {
int argsCount;
@Override
void loadStack() {
// Load up function.
- load(function, Type.OBJECT); //TODO, e.g. booleans can be used as functions
+ loadExpressionAsObject(function); //TODO, e.g. booleans can be used as functions
method.loadUndefined(Type.OBJECT); // ScriptFunction will figure out the correct this when it sees CALLSITE_SCOPE
argsCount = loadArgs(args);
}
@Override
void consumeStack() {
- final int flags = getCallSiteFlagsOptimistic(callNode) | CALLSITE_SCOPE;
- dynamicCall(method, callNode, callNodeType, 2 + argsCount, flags);
+ final int flags = getCallSiteFlags() | CALLSITE_SCOPE;
+ dynamicCall(2 + argsCount, flags);
}
- }.emit(callNode);
+ }.emit();
return false;
}
});
- method.store(callNode.getSymbol());
-
return false;
}
- private void convertOptimisticReturnValue(final Optimistic expr, final Type desiredType) {
- if (expr.isOptimistic()) {
- final Type optimisticType = getOptimisticCoercedType(desiredType, (Expression)expr);
- if(!optimisticType.isObject()) {
- method.load(expr.getProgramPoint());
- if(optimisticType.isInteger()) {
- method.invoke(ENSURE_INT);
- } else if(optimisticType.isLong()) {
- method.invoke(ENSURE_LONG);
- } else if(optimisticType.isNumber()) {
- method.invoke(ENSURE_NUMBER);
- } else {
- throw new AssertionError(optimisticType);
- }
- }
- }
- method.convert(desiredType);
- }
-
- /**
- * Emits the correct dynamic getter code. Normally just delegates to method emitter, except when the target
- * expression is optimistic, and the desired type is narrower than the optimistic type. In that case, it'll emit a
- * dynamic getter with its original optimistic type, and explicitly insert a narrowing conversion. This way we can
- * preserve the optimism of the values even if they're subsequently immediately coerced into a narrower type. This
- * is beneficial because in this case we can still presume that since the original getter was optimistic, the
- * conversion has no side effects.
- * @param method the method emitter
- * @param expr the expression that is being loaded through the getter
- * @param desiredType the desired type for the loaded expression (coercible from its original type)
- * @param name the name of the property being get
- * @param flags call site flags
- * @param isMethod whether we're preferrably retrieving a function
- * @return the passed in method emitter
- */
- private static MethodEmitter dynamicGet(final MethodEmitter method, final Expression expr, final Type desiredType, final String name, final int flags, final boolean isMethod) {
- final int finalFlags = maybeRemoveOptimisticFlags(desiredType, flags);
- if(isOptimistic(finalFlags)) {
- return method.dynamicGet(getOptimisticCoercedType(desiredType, expr), name, finalFlags, isMethod).convert(desiredType);
- }
- return method.dynamicGet(desiredType, name, finalFlags, isMethod);
- }
-
- private static MethodEmitter dynamicGetIndex(final MethodEmitter method, final Expression expr, final Type desiredType, final int flags, final boolean isMethod) {
- final int finalFlags = maybeRemoveOptimisticFlags(desiredType, flags);
- if(isOptimistic(finalFlags)) {
- return method.dynamicGetIndex(getOptimisticCoercedType(desiredType, expr), finalFlags, isMethod).convert(desiredType);
- }
- return method.dynamicGetIndex(desiredType, finalFlags, isMethod);
- }
-
- private static MethodEmitter dynamicCall(final MethodEmitter method, final Expression expr, final Type desiredType, final int argCount, final int flags) {
- final int finalFlags = maybeRemoveOptimisticFlags(desiredType, flags);
- if (isOptimistic(finalFlags)) {
- return method.dynamicCall(getOptimisticCoercedType(desiredType, expr), argCount, finalFlags).convert(desiredType);
- }
- return method.dynamicCall(desiredType, argCount, finalFlags);
- }
-
- /**
- * Given an optimistic expression and a desired coercing type, returns the type that should be used as the return
- * type of the dynamic invocation that is emitted as the code for the expression load. If the coercing type is
- * either boolean or narrower than the expression's optimistic type, then the optimistic type is returned, otherwise
- * the coercing type. Note that if you use this method to determine the return type of the code for the expression,
- * you will need to add an explicit {@link MethodEmitter#convert(Type)} after it to make sure that any further
- * coercing is done into the final type in case the returned type here was the optimistic type. Effectively, this
- * method allows for moving the coercion into the optimistic type when it won't adversely affect the optimistic
- * evaluation semantics, and for preserving the optimistic type and doing a separate coercion when it would affect
- * it.
- * @param coercingType the type into which the expression will ultimately be coerced
- * @param optimisticExpr the optimistic expression that will be coerced after evaluation.
- * @return
- */
- private static Type getOptimisticCoercedType(final Type coercingType, final Expression optimisticExpr) {
- assert optimisticExpr instanceof Optimistic && ((Optimistic)optimisticExpr).isOptimistic();
- final Type optimisticType = optimisticExpr.getType();
- if(coercingType.isBoolean() || coercingType.narrowerThan(optimisticType)) {
- return optimisticType;
- }
- return coercingType;
- }
-
- /**
- * If given an object type, ensures that the flags have their optimism removed (object return valued expressions are
- * never optimistic).
- * @param type the return value type
- * @param flags original flags
- * @return either the original flags, or flags with optimism stripped, if the return value type is object
- */
- private static int maybeRemoveOptimisticFlags(final Type type, final int flags) {
- return type.isObject() ? nonOptimisticFlags(flags) : flags;
- }
-
/**
* Returns the flags with optimistic flag and program point removed.
* @param flags the flags that need optimism stripped from them.
* @return flags without optimism
*/
- static int nonOptimisticFlags(final int flags) {
- return flags & ~(CALLSITE_OPTIMISTIC | (-1 << CALLSITE_PROGRAM_POINT_SHIFT));
+ static int nonOptimisticFlags(int flags) {
+ return flags & ~(CALLSITE_OPTIMISTIC | -1 << CALLSITE_PROGRAM_POINT_SHIFT);
}
@Override
public boolean enterContinueNode(final ContinueNode continueNode) {
+ if(!method.isReachable()) {
+ return false;
+ }
enterStatement(continueNode);
-
- final LoopNode continueTo = lc.getContinueTo(continueNode.getLabel());
+ method.beforeJoinPoint(continueNode);
+
+ final LoopNode continueTo = lc.getContinueTo(continueNode.getLabelName());
popScopesUntil(continueTo);
- method.splitAwareGoto(lc, continueTo.getContinueLabel());
+ final Label continueLabel = continueTo.getContinueLabel();
+ continueLabel.markAsBreakTarget();
+ method.splitAwareGoto(lc, continueLabel, continueTo);
return false;
}
@Override
public boolean enterEmptyNode(final EmptyNode emptyNode) {
+ if(!method.isReachable()) {
+ return false;
+ }
enterStatement(emptyNode);
return false;
@@ -1242,17 +1471,22 @@
@Override
public boolean enterExpressionStatement(final ExpressionStatement expressionStatement) {
+ if(!method.isReachable()) {
+ return false;
+ }
enterStatement(expressionStatement);
- final Expression expr = expressionStatement.getExpression();
- assert expr.isTokenType(TokenType.DISCARD);
- expr.accept(this);
+ loadAndDiscard(expressionStatement.getExpression());
+ assert method.getStackSize() == 0;
return false;
}
@Override
public boolean enterBlockStatement(final BlockStatement blockStatement) {
+ if(!method.isReachable()) {
+ return false;
+ }
enterStatement(blockStatement);
blockStatement.getBlock().accept(this);
@@ -1262,86 +1496,70 @@
@Override
public boolean enterForNode(final ForNode forNode) {
+ if(!method.isReachable()) {
+ return false;
+ }
enterStatement(forNode);
-
if (forNode.isForIn()) {
enterForIn(forNode);
} else {
- enterFor(forNode);
+ final Expression init = forNode.getInit();
+ if (init != null) {
+ loadAndDiscard(init);
+ }
+ enterForOrWhile(forNode, forNode.getModify());
}
return false;
}
- private void enterFor(final ForNode forNode) {
- final Expression init = forNode.getInit();
- final Expression test = forNode.getTest();
- final Block body = forNode.getBody();
- final Expression modify = forNode.getModify();
-
- if (init != null) {
- init.accept(this);
- }
-
- final Label loopLabel = new Label("loop");
- final Label testLabel = new Label("test");
-
- method._goto(testLabel);
- method.label(loopLabel);
- body.accept(this);
- method.label(forNode.getContinueLabel());
-
- lineNumber(forNode);
-
- if (!body.isTerminal() && modify != null) {
- load(modify);
- }
-
- method.label(testLabel);
- if (test != null) {
- new BranchOptimizer(this, method).execute(test, loopLabel, true);
+ private void enterForIn(final ForNode forNode) {
+ loadExpression(forNode.getModify(), TypeBounds.OBJECT);
+ method.invoke(forNode.isForEach() ? ScriptRuntime.TO_VALUE_ITERATOR : ScriptRuntime.TO_PROPERTY_ITERATOR);
+ final Symbol iterSymbol = forNode.getIterator();
+ final int iterSlot = iterSymbol.getSlot(Type.OBJECT);
+ method.store(iterSymbol, ITERATOR_TYPE);
+
+ method.beforeJoinPoint(forNode);
+
+ final Label continueLabel = forNode.getContinueLabel();
+ final Label breakLabel = forNode.getBreakLabel();
+
+ method.label(continueLabel);
+ method.load(ITERATOR_TYPE, iterSlot);
+ method.invoke(interfaceCallNoLookup(ITERATOR_CLASS, "hasNext", boolean.class));
+ final JoinPredecessorExpression test = forNode.getTest();
+ final Block body = forNode.getBody();
+ if(LocalVariableConversion.hasLiveConversion(test)) {
+ final Label afterConversion = new Label("for_in_after_test_conv");
+ method.ifne(afterConversion);
+ method.beforeJoinPoint(test);
+ method._goto(breakLabel);
+ method.label(afterConversion);
} else {
- method._goto(loopLabel);
- }
-
- method.label(forNode.getBreakLabel());
- }
-
- private void enterForIn(final ForNode forNode) {
- final Block body = forNode.getBody();
- final Expression modify = forNode.getModify();
-
- final Symbol iter = forNode.getIterator();
- final Label loopLabel = new Label("loop");
-
- final Expression init = forNode.getInit();
-
- load(modify, Type.OBJECT);
- method.invoke(forNode.isForEach() ? ScriptRuntime.TO_VALUE_ITERATOR : ScriptRuntime.TO_PROPERTY_ITERATOR);
- method.store(iter);
- method._goto(forNode.getContinueLabel());
- method.label(loopLabel);
-
- new Store<Expression>(init) {
+ method.ifeq(breakLabel);
+ }
+
+ new Store<Expression>(forNode.getInit()) {
@Override
protected void storeNonDiscard() {
- //empty
+ // This expression is neither part of a discard, nor needs to be left on the stack after it was
+ // stored, so we override storeNonDiscard to be a no-op.
}
@Override
protected void evaluate() {
- method.load(iter);
- method.invoke(interfaceCallNoLookup(Iterator.class, "next", Object.class));
+ method.load(ITERATOR_TYPE, iterSlot);
+ // TODO: optimistic for-in iteration
+ method.invoke(interfaceCallNoLookup(ITERATOR_CLASS, "next", Object.class));
}
}.store();
-
body.accept(this);
- method.label(forNode.getContinueLabel());
- method.load(iter);
- method.invoke(interfaceCallNoLookup(Iterator.class, "hasNext", boolean.class));
- method.ifne(loopLabel);
- method.label(forNode.getBreakLabel());
+ if(method.isReachable()) {
+ method._goto(continueLabel);
+ }
+ method.label(breakLabel);
}
/**
@@ -1350,11 +1568,15 @@
* @param block block with local vars.
*/
private void initLocals(final Block block) {
- lc.nextFreeSlot(block);
+ lc.onEnterBlock(block);
final boolean isFunctionBody = lc.isFunctionBody();
final FunctionNode function = lc.getCurrentFunction();
if (isFunctionBody) {
+ initializeMethodParameters(function);
+ if(!function.isVarArg()) {
+ expandParameterSlots(function);
+ }
if (method.hasScope()) {
if (function.needsParentScope()) {
method.loadCompilerConstant(CALLEE);
@@ -1368,15 +1590,6 @@
if (function.needsArguments()) {
initArguments(function);
}
- final Symbol returnSymbol = block.getExistingSymbol(RETURN.symbolName());
- if(returnSymbol.hasSlot() && useOptimisticTypes() &&
- // NOTE: a program that has no declared functions will assign ":return = UNDEFINED" first thing as it
- // starts to run, so we don't have to force initialize :return (see Lower.enterBlock()).
- !(function.isProgram() && !function.hasDeclaredFunctions()))
- {
- method.loadForcedInitializer(returnSymbol.getSymbolType());
- method.store(returnSymbol);
- }
}
/*
@@ -1391,19 +1604,11 @@
// TODO for LET we can do better: if *block* does not contain any eval/with, we don't need its vars in scope.
- final List<Symbol> localsToInitialize = new ArrayList<>();
final boolean hasArguments = function.needsArguments();
final List<MapTuple<Symbol>> tuples = new ArrayList<>();
-
+ final Iterator<IdentNode> paramIter = function.getParameters().iterator();
for (final Symbol symbol : block.getSymbols()) {
- if (symbol.isInternal() && !symbol.isThis()) {
- if (symbol.hasSlot()) {
- localsToInitialize.add(symbol);
- }
- continue;
- }
-
- if (symbol.isThis() || symbol.isTemp()) {
+ if (symbol.isInternal() || symbol.isThis()) {
continue;
}
@@ -1412,42 +1617,54 @@
if (varsInScope || symbol.isScope()) {
assert symbol.isScope() : "scope for " + symbol + " should have been set in Lower already " + function.getName();
assert !symbol.hasSlot() : "slot for " + symbol + " should have been removed in Lower already" + function.getName();
- tuples.add(new MapTuple<Symbol>(symbol.getName(), symbol) {
- //this tuple will not be put fielded, as it has no value, just a symbol
- @Override
- public boolean isPrimitive() {
- return symbol.getSymbolType().isPrimitive();
- }
- });
+
+ //this tuple will not be put fielded, as it has no value, just a symbol
+ tuples.add(new MapTuple<Symbol>(symbol.getName(), symbol, null));
} else {
- assert symbol.hasSlot() : symbol + " should have a slot only, no scope";
- localsToInitialize.add(symbol);
+ assert symbol.hasSlot() || symbol.slotCount() == 0 : symbol + " should have a slot only, no scope";
}
} else if (symbol.isParam() && (varsInScope || hasArguments || symbol.isScope())) {
- assert symbol.isScope() : "scope for " + symbol + " should have been set in Lower already " + function.getName() + " varsInScope="+varsInScope+" hasArguments="+hasArguments+" symbol.isScope()=" + symbol.isScope();
+ assert symbol.isScope() : "scope for " + symbol + " should have been set in AssignSymbols already " + function.getName() + " varsInScope="+varsInScope+" hasArguments="+hasArguments+" symbol.isScope()=" + symbol.isScope();
assert !(hasArguments && symbol.hasSlot()) : "slot for " + symbol + " should have been removed in Lower already " + function.getName();
- tuples.add(new MapTuple<Symbol>(symbol.getName(), symbol, hasArguments ? null : symbol) {
+ final Type paramType;
+ final Symbol paramSymbol;
+ if(hasArguments) {
+ assert !symbol.hasSlot() : "slot for " + symbol + " should have been removed in Lower already ";
+ paramSymbol = null;
+ paramType = null;
+ } else {
+ paramSymbol = symbol;
+ // NOTE: We're relying on the fact here that Block.symbols is a LinkedHashMap, hence it will
+ // return symbols in the order they were defined, and parameters are defined in the same order
+ // they appear in the function. That's why we can have a single pass over the parameter list
+ // with an iterator, always just scanning forward for the next parameter that matches the symbol
+ // name.
+ for(;;) {
+ final IdentNode nextParam = paramIter.next();
+ if(nextParam.getName().equals(symbol.getName())) {
+ paramType = nextParam.getType();
+ break;
+ }
+ }
+ }
+ tuples.add(new MapTuple<Symbol>(symbol.getName(), symbol, paramType, paramSymbol) {
//this symbol will be put fielded, we can't initialize it as undefined with a known type
@Override
public Class<?> getValueType() {
- return OBJECT_FIELDS_ONLY || value == null || value.getSymbolType().isBoolean() ? Object.class : value.getSymbolType().getTypeClass();
- //return OBJECT_FIELDS_ONLY ? Object.class : symbol.getSymbolType().getTypeClass();
+ return OBJECT_FIELDS_ONLY || value == null || paramType.isBoolean() ? Object.class : paramType.getTypeClass();
}
});
}
}
- // we may have locals that need to be initialized
- initSymbols(localsToInitialize);
-
/*
* Create a new object based on the symbols and values, generate
* bootstrap code for object
*/
new FieldObjectCreator<Symbol>(this, tuples, true, hasArguments) {
@Override
- protected void loadValue(final Symbol value) {
- method.load(value);
+ protected void loadValue(final Symbol value, final Type type) {
+ method.load(value, type);
}
}.makeObject(method);
// program function: merge scope into global
@@ -1456,31 +1673,100 @@
}
method.storeCompilerConstant(SCOPE);
- if (!isFunctionBody) {
+ if(!isFunctionBody) {
// Function body doesn't need a try/catch to restore scope, as it'd be a dead store anyway. Allowing it
// actually causes issues with UnwarrantedOptimismException handlers as ASM will sort this handler to
// the top of the exception handler table, so it'll be triggered instead of the UOE handlers.
- final Label scopeEntryLabel = new Label("");
+ final Label scopeEntryLabel = new Label("scope_entry");
scopeEntryLabels.push(scopeEntryLabel);
method.label(scopeEntryLabel);
}
- } else {
+ } else if (isFunctionBody && function.isVarArg()) {
// Since we don't have a scope, parameters didn't get assigned array indices by the FieldObjectCreator, so
// we need to assign them separately here.
int nextParam = 0;
- if (isFunctionBody && function.isVarArg()) {
- for (final IdentNode param : function.getParameters()) {
- param.getSymbol().setFieldIndex(nextParam++);
- }
- }
-
- initSymbols(block.getSymbols());
+ for (final IdentNode param : function.getParameters()) {
+ param.getSymbol().setFieldIndex(nextParam++);
+ }
}
// Debugging: print symbols? @see --print-symbols flag
printSymbols(block, (isFunctionBody ? "Function " : "Block in ") + (function.getIdent() == null ? "<anonymous>" : function.getIdent().getName()));
}
+ /**
+ * Incoming method parameters are always declared on method entry; declare them in the local variable table.
+ * @param function function for which code is being generated.
+ */
+ private void initializeMethodParameters(final FunctionNode function) {
+ final Label functionStart = new Label("fn_start");
+ method.label(functionStart);
+ int nextSlot = 0;
+ if(function.needsCallee()) {
+ initializeInternalFunctionParameter(CALLEE, function, functionStart, nextSlot++);
+ }
+ initializeInternalFunctionParameter(THIS, function, functionStart, nextSlot++);
+ if(function.isVarArg()) {
+ initializeInternalFunctionParameter(VARARGS, function, functionStart, nextSlot++);
+ } else {
+ for(final IdentNode param: function.getParameters()) {
+ final Symbol symbol = param.getSymbol();
+ if(symbol.isBytecodeLocal()) {
+ method.initializeMethodParameter(symbol, param.getType(), functionStart);
+ }
+ }
+ }
+ }
+
+ private void initializeInternalFunctionParameter(CompilerConstants cc, final FunctionNode fn, final Label functionStart, final int slot) {
+ final Symbol symbol = initializeInternalFunctionOrSplitParameter(cc, fn, functionStart, slot);
+ // Internal function params (:callee, this, and :varargs) are never expanded to multiple slots
+ assert symbol.getFirstSlot() == slot;
+ }
+
+ private Symbol initializeInternalFunctionOrSplitParameter(CompilerConstants cc, final FunctionNode fn, final Label functionStart, final int slot) {
+ final Symbol symbol = fn.getBody().getExistingSymbol(cc.symbolName());
+ final Type type = Type.typeFor(cc.type());
+ method.initializeMethodParameter(symbol, type, functionStart);
+ method.onLocalStore(type, slot);
+ return symbol;
+ }
+
+ /**
+ * Parameters come into the method packed into local variable slots next to each other. Nashorn on the other hand
+ * can use 1-6 slots for a local variable depending on all the types it needs to store. When this method is invoked,
+ * the symbols are already allocated such wider slots, but the values are still in tightly packed incoming slots,
+ * and we need to spread them into their new locations.
+ * @param function the function for which parameter-spreading code needs to be emitted
+ */
+ private void expandParameterSlots(FunctionNode function) {
+ final List<IdentNode> parameters = function.getParameters();
+ // Calculate the total number of incoming parameter slots
+ int currentIncomingSlot = function.needsCallee() ? 2 : 1;
+ for(final IdentNode parameter: parameters) {
+ currentIncomingSlot += parameter.getType().getSlots();
+ }
+ // Starting from last parameter going backwards, move the parameter values into their new slots.
+ for(int i = parameters.size(); i-- > 0;) {
+ final IdentNode parameter = parameters.get(i);
+ final Type parameterType = parameter.getType();
+ final int typeWidth = parameterType.getSlots();
+ currentIncomingSlot -= typeWidth;
+ final Symbol symbol = parameter.getSymbol();
+ final int slotCount = symbol.slotCount();
+ assert slotCount > 0;
+ // Scoped parameters must not hold more than one value
+ assert symbol.isBytecodeLocal() || slotCount == typeWidth;
+
+ // Mark it as having its value stored into it by the method invocation.
+ method.onLocalStore(parameterType, currentIncomingSlot);
+ if(currentIncomingSlot != symbol.getSlot(parameterType)) {
+ method.load(parameterType, currentIncomingSlot);
+ method.store(symbol, parameterType);
+ }
+ }
+ }
+
private void initArguments(final FunctionNode function) {
method.loadCompilerConstant(VARARGS);
if (function.needsCallee()) {
@@ -1535,26 +1821,38 @@
final CompilationEnvironment compEnv = compiler.getCompilationEnvironment();
final boolean isRestOf = compEnv.isCompileRestOf();
final ClassEmitter classEmitter = unit.getClassEmitter();
- method = lc.pushMethodEmitter(isRestOf ? classEmitter.restOfMethod(functionNode) : classEmitter.method(functionNode));
+ pushMethodEmitter(isRestOf ? classEmitter.restOfMethod(functionNode) : classEmitter.method(functionNode));
+ method.setPreventUndefinedLoad();
if(useOptimisticTypes()) {
lc.pushUnwarrantedOptimismHandlers();
}
// new method - reset last line number
lastLineNumber = -1;
- // Mark end for variable tables.
+
method.begin();
if (isRestOf) {
final ContinuationInfo ci = new ContinuationInfo();
fnIdToContinuationInfo.put(fnId, ci);
- method._goto(ci.getHandlerLabel());
+ method.gotoLoopStart(ci.getHandlerLabel());
}
}
return true;
}
+ private void pushMethodEmitter(final MethodEmitter newMethod) {
+ method = lc.pushMethodEmitter(newMethod);
+ catchLabels.push(METHOD_BOUNDARY);
+ }
+
+ private void popMethodEmitter() {
+ method = lc.popMethodEmitter(method);
+ assert catchLabels.peek() == METHOD_BOUNDARY;
+ catchLabels.pop();
+ }
+
@Override
public Node leaveFunctionNode(final FunctionNode functionNode) {
try {
@@ -1564,19 +1862,18 @@
generateContinuationHandler();
method.end(); // wrap up this method
unit = lc.popCompileUnit(functionNode.getCompileUnit());
- method = lc.popMethodEmitter(method);
+ popMethodEmitter();
log.info("=== END ", functionNode.getName());
} else {
markOptimistic = false;
}
FunctionNode newFunctionNode = functionNode.setState(lc, CompilationState.EMITTED);
- if (markOptimistic) {
- newFunctionNode = newFunctionNode.setFlag(lc, FunctionNode.IS_OPTIMISTIC);
+ if(markOptimistic) {
+ newFunctionNode = newFunctionNode.setFlag(lc, FunctionNode.IS_DEOPTIMIZABLE);
}
newFunctionObject(newFunctionNode, true);
-
return newFunctionNode;
} catch (final Throwable t) {
Context.printStackTrace(t);
@@ -1587,51 +1884,43 @@
}
@Override
- public boolean enterIdentNode(final IdentNode identNode) {
- return false;
- }
-
- @Override
public boolean enterIfNode(final IfNode ifNode) {
+ if(!method.isReachable()) {
+ return false;
+ }
enterStatement(ifNode);
final Expression test = ifNode.getTest();
final Block pass = ifNode.getPass();
final Block fail = ifNode.getFail();
+ final boolean hasFailConversion = LocalVariableConversion.hasLiveConversion(ifNode);
final Label failLabel = new Label("if_fail");
- final Label afterLabel = fail == null ? failLabel : new Label("if_done");
-
- new BranchOptimizer(this, method).execute(test, failLabel, false);
-
- boolean passTerminal = false;
- boolean failTerminal = false;
+ final Label afterLabel = (fail == null && !hasFailConversion) ? null : new Label("if_done");
+
+ emitBranch(test, failLabel, false);
pass.accept(this);
- if (!pass.hasTerminalFlags()) {
+ if(method.isReachable() && afterLabel != null) {
method._goto(afterLabel); //don't fallthru to fail block
- } else {
- passTerminal = pass.isTerminal();
- }
+ }
+ method.label(failLabel);
if (fail != null) {
- method.label(failLabel);
fail.accept(this);
- failTerminal = fail.isTerminal();
- }
-
- //if if terminates, put the after label there
- if (!passTerminal || !failTerminal) {
+ } else if(hasFailConversion) {
+ method.beforeJoinPoint(ifNode);
+ }
+
+ if(afterLabel != null) {
method.label(afterLabel);
}
return false;
}
- @Override
- public boolean enterIndexNode(final IndexNode indexNode) {
- load(indexNode);
- return false;
+ private void emitBranch(final Expression test, final Label label, final boolean jumpWhenTrue) {
+ new BranchOptimizer(this, method).execute(test, label, jumpWhenTrue);
}
private void enterStatement(final Statement statement) {
@@ -1649,6 +1938,10 @@
lastLineNumber = lineNumber;
}
+ int getLastLineNumber() {
+ return lastLineNumber;
+ }
+
/**
* Load a list of nodes as an array of a specific type
* The array will contain the visited nodes.
@@ -1672,7 +1965,6 @@
final Type elementType = arrayType.getElementType();
if (units != null) {
- lc.enterSplitNode();
final MethodEmitter savedMethod = method;
final FunctionNode currentFunction = lc.getCurrentFunction();
@@ -1683,23 +1975,30 @@
final String name = currentFunction.uniqueName(SPLIT_PREFIX.symbolName());
final String signature = methodDescriptor(type, ScriptFunction.class, Object.class, ScriptObject.class, type);
- final MethodEmitter me = unit.getClassEmitter().method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), name, signature);
- method = lc.pushMethodEmitter(me);
+ pushMethodEmitter(unit.getClassEmitter().method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), name, signature));
method.setFunctionNode(currentFunction);
method.begin();
+ defineCommonSplitMethodParameters();
+ defineSplitMethodParameter(3, arrayType);
+
fixScopeSlot(currentFunction);
- method.load(arrayType, SPLIT_ARRAY_ARG.slot());
-
+ lc.enterSplitNode();
+
+ final int arraySlot = SPLIT_ARRAY_ARG.slot();
for (int i = arrayUnit.getLo(); i < arrayUnit.getHi(); i++) {
+ method.load(arrayType, arraySlot);
storeElement(nodes, elementType, postsets[i]);
}
+ method.load(arrayType, arraySlot);
method._return();
+ lc.exitSplitNode();
method.end();
- method = lc.popMethodEmitter(me);
+ lc.releaseSlots();
+ popMethodEmitter();
assert method == savedMethod;
method.loadCompilerConstant(CALLEE);
@@ -1712,20 +2011,23 @@
unit = lc.popCompileUnit(unit);
}
- lc.exitSplitNode();
return method;
}
- for (final int postset : postsets) {
- storeElement(nodes, elementType, postset);
- }
-
+ if(postsets.length > 0) {
+ final int arraySlot = method.getUsedSlotsWithLiveTemporaries();
+ method.storeTemp(arrayType, arraySlot);
+ for (final int postset : postsets) {
+ method.load(arrayType, arraySlot);
+ storeElement(nodes, elementType, postset);
+ }
+ method.load(arrayType, arraySlot);
+ }
return method;
}
private void storeElement(final Expression[] nodes, final Type elementType, final int index) {
- method.dup();
method.load(index);
final Expression element = nodes[index];
@@ -1733,7 +2035,7 @@
if (element == null) {
method.loadEmpty(elementType);
} else {
- load(element, elementType);
+ loadExpressionAsType(element, elementType);
}
method.arraystore();
@@ -1746,7 +2048,7 @@
for (int i = 0; i < args.size(); i++) {
method.dup();
method.load(i);
- load(args.get(i), Type.OBJECT); //has to be upcast to object or we fail
+ loadExpression(args.get(i), TypeBounds.OBJECT); // variable arity methods always take objects
method.arraystore();
}
@@ -1807,13 +2109,13 @@
}
// literal values
- private MethodEmitter loadLiteral(final LiteralNode<?> node, final Type type) {
+ private void loadLiteral(final LiteralNode<?> node, final TypeBounds resultBounds) {
final Object value = node.getValue();
if (value == null) {
method.loadNull();
} else if (value instanceof Undefined) {
- method.loadUndefined(Type.OBJECT);
+ method.loadUndefined(resultBounds.within(Type.OBJECT));
} else if (value instanceof String) {
final String string = (String)value;
@@ -1827,21 +2129,32 @@
} else if (value instanceof Boolean) {
method.load((Boolean)value);
} else if (value instanceof Integer) {
- if(type.isEquivalentTo(Type.NUMBER)) {
+ if(!resultBounds.canBeNarrowerThan(Type.OBJECT)) {
+ method.load((Integer)value);
+ method.convert(Type.OBJECT);
+ } else if(!resultBounds.canBeNarrowerThan(Type.NUMBER)) {
method.load(((Integer)value).doubleValue());
- } else if(type.isEquivalentTo(Type.LONG)) {
+ } else if(!resultBounds.canBeNarrowerThan(Type.LONG)) {
method.load(((Integer)value).longValue());
} else {
method.load((Integer)value);
}
} else if (value instanceof Long) {
- if(type.isEquivalentTo(Type.NUMBER)) {
+ if(!resultBounds.canBeNarrowerThan(Type.OBJECT)) {
+ method.load((Long)value);
+ method.convert(Type.OBJECT);
+ } else if(!resultBounds.canBeNarrowerThan(Type.NUMBER)) {
method.load(((Long)value).doubleValue());
} else {
method.load((Long)value);
}
} else if (value instanceof Double) {
- method.load((Double)value);
+ if(!resultBounds.canBeNarrowerThan(Type.OBJECT)) {
+ method.load((Double)value);
+ method.convert(Type.OBJECT);
+ } else {
+ method.load((Double)value);
+ }
} else if (node instanceof ArrayLiteralNode) {
final ArrayLiteralNode arrayLiteral = (ArrayLiteralNode)node;
final ArrayType atype = arrayLiteral.getArrayType();
@@ -1850,8 +2163,6 @@
} else {
throw new UnsupportedOperationException("Unknown literal for " + node.getClass() + " " + value.getClass() + " " + value);
}
-
- return method;
}
private MethodEmitter loadRegexToken(final RegexToken value) {
@@ -1888,17 +2199,6 @@
return method;
}
- @Override
- public boolean enterLiteralNode(final LiteralNode<?> literalNode) {
- return enterLiteralNode(literalNode, literalNode.getType());
- }
-
- private boolean enterLiteralNode(final LiteralNode<?> literalNode, final Type type) {
- assert literalNode.getSymbol() != null : literalNode + " has no symbol";
- loadLiteral(literalNode, type).convert(type).store(literalNode.getSymbol());
- return false;
- }
-
/**
* Check if a property value contains a particular program point
* @param value value
@@ -1930,8 +2230,7 @@
}.get();
}
- @Override
- public boolean enterObjectNode(final ObjectNode objectNode) {
+ private void loadObjectNode(final ObjectNode objectNode) {
final List<PropertyNode> elements = objectNode.getElements();
final List<MapTuple<Expression>> tuples = new ArrayList<>();
@@ -1943,9 +2242,10 @@
final int ccp = env.getCurrentContinuationEntryPoint();
for (final PropertyNode propertyNode : elements) {
- final Expression value = propertyNode.getValue();
- final String key = propertyNode.getKeyName();
- final Symbol symbol = value == null ? null : propertyNode.getKey().getSymbol();
+ final Expression value = propertyNode.getValue();
+ final String key = propertyNode.getKeyName();
+ // Just use a pseudo-symbol. We just need something non null; use the name and zero flags.
+ final Symbol symbol = value == null ? null : new Symbol(key, 0);
if (value == null) {
gettersSetters.add(propertyNode);
@@ -1962,10 +2262,11 @@
//for literals, a value of null means object type, i.e. the value null or getter setter function
//(I think)
- tuples.add(new MapTuple<Expression>(key, symbol, value) {
+ final Class<?> valueType = (OBJECT_FIELDS_ONLY || value == null || value.getType().isBoolean()) ? Object.class : value.getType().getTypeClass();
+ tuples.add(new MapTuple<Expression>(key, symbol, Type.typeFor(valueType), value) {
@Override
public Class<?> getValueType() {
- return OBJECT_FIELDS_ONLY || value == null || value.getType().isBoolean() ? Object.class : value.getType().getTypeClass();
+ return type.getTypeClass();
}
});
}
@@ -1976,8 +2277,8 @@
} else {
oc = new FieldObjectCreator<Expression>(this, tuples) {
@Override
- protected void loadValue(final Expression node) {
- load(node);
+ protected void loadValue(final Expression node, final Type type) {
+ loadExpressionAsType(node, type);
}};
}
oc.makeObject(method);
@@ -1993,11 +2294,10 @@
method.dup();
if (protoNode != null) {
- load(protoNode);
+ loadExpressionAsObject(protoNode);
method.invoke(ScriptObject.SET_PROTO_CHECK);
} else {
- globalObjectPrototype();
- method.invoke(ScriptObject.SET_PROTO);
+ method.invoke(ScriptObject.SET_GLOBAL_OBJECT_PROTO);
}
for (final PropertyNode propertyNode : gettersSetters) {
@@ -2021,13 +2321,13 @@
method.invoke(ScriptObject.SET_USER_ACCESSORS);
}
-
- method.store(objectNode.getSymbol());
- return false;
}
@Override
public boolean enterReturnNode(final ReturnNode returnNode) {
+ if(!method.isReachable()) {
+ return false;
+ }
enterStatement(returnNode);
method.registerReturn();
@@ -2036,7 +2336,7 @@
final Expression expression = returnNode.getExpression();
if (expression != null) {
- load(expression);
+ loadExpressionUnbounded(expression);
} else {
method.loadUndefined(returnType);
}
@@ -2046,11 +2346,6 @@
return false;
}
- private static boolean isNullLiteral(final Node node) {
- return node instanceof LiteralNode<?> && ((LiteralNode<?>) node).isNull();
- }
-
-
private boolean undefinedCheck(final RuntimeNode runtimeNode, final List<Expression> args) {
final Request request = runtimeNode.getRequest();
@@ -2061,11 +2356,17 @@
final Expression lhs = args.get(0);
final Expression rhs = args.get(1);
- final Symbol lhsSymbol = lhs.getSymbol();
- final Symbol rhsSymbol = rhs.getSymbol();
-
- final Symbol undefinedSymbol = "undefined".equals(lhsSymbol.getName()) ? lhsSymbol : rhsSymbol;
- final Expression expr = undefinedSymbol == lhsSymbol ? rhs : lhs;
+ final Symbol lhsSymbol = lhs instanceof IdentNode ? ((IdentNode)lhs).getSymbol() : null;
+ final Symbol rhsSymbol = rhs instanceof IdentNode ? ((IdentNode)rhs).getSymbol() : null;
+ // One must be a "undefined" identifier, otherwise we can't get here
+ assert lhsSymbol != null || rhsSymbol != null;
+ final Symbol undefinedSymbol;
+ if(isUndefinedSymbol(lhsSymbol)) {
+ undefinedSymbol = lhsSymbol;
+ } else {
+ assert isUndefinedSymbol(rhsSymbol);
+ undefinedSymbol = rhsSymbol;
+ }
if (!undefinedSymbol.isScope()) {
return false; //disallow undefined as local var or parameter
@@ -2076,40 +2377,47 @@
return false;
}
- if (compiler.getCompilationEnvironment().isCompileRestOf()) {
+ final CompilationEnvironment env = compiler.getCompilationEnvironment();
+ // TODO: why?
+ if (env.isCompileRestOf()) {
return false;
}
//make sure that undefined has not been overridden or scoped as a local var
//between us and global
- final CompilationEnvironment env = compiler.getCompilationEnvironment();
if (!env.isGlobalSymbol(lc.getCurrentFunction(), "undefined")) {
return false;
}
- load(expr);
-
+ final boolean isUndefinedCheck = request == Request.IS_UNDEFINED;
+ final Expression expr = undefinedSymbol == lhsSymbol ? rhs : lhs;
if (expr.getType().isPrimitive()) {
- method.pop(); //throw away lhs, but it still needs to be evaluated for side effects, even if not in scope, as it can be optimistic
- method.load(request == Request.IS_NOT_UNDEFINED);
+ loadAndDiscard(expr); //throw away lhs, but it still needs to be evaluated for side effects, even if not in scope, as it can be optimistic
+ method.load(!isUndefinedCheck);
} else {
- final Label isUndefined = new Label("ud_check_true");
- final Label notUndefined = new Label("ud_check_false");
- final Label end = new Label("end");
+ final Label checkTrue = new Label("ud_check_true");
+ final Label end = new Label("end");
+ loadExpressionAsObject(expr);
method.loadUndefined(Type.OBJECT);
- method.if_acmpeq(isUndefined);
- method.label(notUndefined);
- method.load(request == Request.IS_NOT_UNDEFINED);
+ method.if_acmpeq(checkTrue);
+ method.load(!isUndefinedCheck);
method._goto(end);
- method.label(isUndefined);
- method.load(request == Request.IS_UNDEFINED);
+ method.label(checkTrue);
+ method.load(isUndefinedCheck);
method.label(end);
}
- method.store(runtimeNode.getSymbol());
return true;
}
+ private static boolean isUndefinedSymbol(final Symbol symbol) {
+ return symbol != null && "undefined".equals(symbol.getName());
+ }
+
+ private static boolean isNullLiteral(final Node node) {
+ return node instanceof LiteralNode<?> && ((LiteralNode<?>) node).isNull();
+ }
+
private boolean nullCheck(final RuntimeNode runtimeNode, final List<Expression> args) {
final Request request = runtimeNode.getRequest();
@@ -2142,7 +2450,7 @@
final Label falseLabel = new Label("falseLabel");
final Label endLabel = new Label("end");
- load(lhs); //lhs
+ loadExpressionUnbounded(lhs); //lhs
final Label popLabel;
if (!Request.isStrict(request)) {
method.dup(); //lhs lhs
@@ -2187,143 +2495,31 @@
assert runtimeNode.getType().isBoolean();
method.convert(runtimeNode.getType());
- method.store(runtimeNode.getSymbol());
-
- return true;
- }
-
- private boolean specializationCheck(final RuntimeNode.Request request, final RuntimeNode node, final List<Expression> args) {
- if (!request.canSpecialize()) {
- return false;
- }
-
- assert args.size() == 2 : node;
- final Type returnType = node.getType();
-
- new OptimisticOperation() {
- private Request finalRequest = request;
-
- @Override
- void loadStack() {
- load(args.get(0));
- load(args.get(1));
-
- //if the request is a comparison, i.e. one that can be reversed
- //it keeps its semantic, but make sure that the object comes in
- //last
- final Request reverse = Request.reverse(request);
- if (method.peekType().isObject() && reverse != null) { //rhs is object
- if (!method.peekType(1).isObject()) { //lhs is not object
- method.swap(); //prefer object as lhs
- finalRequest = reverse;
- }
- }
- }
- @Override
- void consumeStack() {
- method.dynamicRuntimeCall(
- new SpecializedRuntimeNode(
- finalRequest,
- new Type[] {
- method.peekType(1),
- method.peekType()
- },
- returnType).getInitialName(),
- returnType,
- finalRequest);
-
- }
- }.emit(node);
-
- method.convert(node.getType());
- method.store(node.getSymbol());
return true;
}
- private static boolean isReducible(final Request request) {
- return Request.isComparison(request) || request == Request.ADD;
- }
-
- @Override
- public boolean enterRuntimeNode(final RuntimeNode runtimeNode) {
- /*
- * First check if this should be something other than a runtime node
- * AccessSpecializer might have changed the type
- *
- * TODO - remove this - Access Specializer will always know after Attr/Lower
- */
+ private void loadRuntimeNode(final RuntimeNode runtimeNode) {
final List<Expression> args = new ArrayList<>(runtimeNode.getArgs());
- if (runtimeNode.isPrimitive() && !runtimeNode.isFinal() && isReducible(runtimeNode.getRequest())) {
- final Expression lhs = args.get(0);
-
- final Type type = runtimeNode.getType();
- final Symbol symbol = runtimeNode.getSymbol();
-
- switch (runtimeNode.getRequest()) {
- case EQ:
- case EQ_STRICT:
- return enterCmp(lhs, args.get(1), Condition.EQ, type, symbol);
- case NE:
- case NE_STRICT:
- return enterCmp(lhs, args.get(1), Condition.NE, type, symbol);
- case LE:
- return enterCmp(lhs, args.get(1), Condition.LE, type, symbol);
- case LT:
- return enterCmp(lhs, args.get(1), Condition.LT, type, symbol);
- case GE:
- return enterCmp(lhs, args.get(1), Condition.GE, type, symbol);
- case GT:
- return enterCmp(lhs, args.get(1), Condition.GT, type, symbol);
- case ADD:
- final Expression rhs = args.get(1);
- final Type widest = Type.widest(lhs.getType(), rhs.getType());
- new OptimisticOperation() {
- @Override
- void loadStack() {
- load(lhs, widest);
- load(rhs, widest);
- }
-
- @Override
- void consumeStack() {
- method.add(runtimeNode.getProgramPoint());
- }
- }.emit(runtimeNode);
- method.convert(type);
- method.store(symbol);
- return false;
- default:
- // it's ok to send this one on with only primitive arguments, maybe INSTANCEOF(true, true) or similar
- // assert false : runtimeNode + " has all primitive arguments. This is an inconsistent state";
- break;
- }
- }
-
if (nullCheck(runtimeNode, args)) {
- return false;
- }
-
- if (undefinedCheck(runtimeNode, args)) {
- return false;
- }
-
+ return;
+ } else if(undefinedCheck(runtimeNode, args)) {
+ return;
+ }
+ // Revert a false undefined check to a strict equality check
final RuntimeNode newRuntimeNode;
- if (Request.isUndefinedCheck(runtimeNode.getRequest())) {
- newRuntimeNode = runtimeNode.setRequest(runtimeNode.getRequest() == Request.IS_UNDEFINED ? Request.EQ_STRICT : Request.NE_STRICT);
+ final Request request = runtimeNode.getRequest();
+ if (Request.isUndefinedCheck(request)) {
+ newRuntimeNode = runtimeNode.setRequest(request == Request.IS_UNDEFINED ? Request.EQ_STRICT : Request.NE_STRICT);
} else {
newRuntimeNode = runtimeNode;
}
- if (!newRuntimeNode.isFinal() && specializationCheck(newRuntimeNode.getRequest(), newRuntimeNode, args)) {
- return false;
- }
-
- new OptimisticOperation() {
+ new OptimisticOperation(newRuntimeNode, TypeBounds.UNBOUNDED) {
@Override
void loadStack() {
- for (final Expression arg : newRuntimeNode.getArgs()) {
- load(arg, Type.OBJECT);
+ for (final Expression arg : args) {
+ loadExpression(arg, TypeBounds.OBJECT);
}
}
@Override
@@ -2335,24 +2531,27 @@
false,
false,
newRuntimeNode.getType(),
- newRuntimeNode.getArgs().size()).toString());
- }
- }.emit(newRuntimeNode);
+ args.size()).toString());
+ }
+ }.emit();
method.convert(newRuntimeNode.getType());
- method.store(newRuntimeNode.getSymbol());
-
- return false;
}
@Override
public boolean enterSplitNode(final SplitNode splitNode) {
+ if(!method.isReachable()) {
+ return false;
+ }
+
final CompileUnit splitCompileUnit = splitNode.getCompileUnit();
final FunctionNode fn = lc.getCurrentFunction();
final String className = splitCompileUnit.getUnitClassName();
final String name = splitNode.getName();
+ final Type returnType = fn.getReturnType();
+
final Class<?> rtype = fn.getReturnType().getTypeClass();
final boolean needsArguments = fn.needsArguments();
final Class<?>[] ptypes = needsArguments ?
@@ -2374,7 +2573,7 @@
rtype,
ptypes);
- method = lc.pushMethodEmitter(splitEmitter);
+ pushMethodEmitter(splitEmitter);
method.setFunctionNode(fn);
assert fn.needsCallee() : "split function should require callee";
@@ -2385,22 +2584,49 @@
caller.loadCompilerConstant(ARGUMENTS);
}
caller.invoke(splitCall);
- caller.storeCompilerConstant(RETURN);
+ caller.storeCompilerConstant(RETURN, returnType);
method.begin();
+
+ defineCommonSplitMethodParameters();
+ if(needsArguments) {
+ defineSplitMethodParameter(3, ARGUMENTS);
+ }
+
// Copy scope to its target slot as first thing because the original slot could be used by return symbol.
fixScopeSlot(fn);
- method.loadUndefined(fn.getReturnType());
- method.storeCompilerConstant(RETURN);
-
+ final int returnSlot = fn.compilerConstant(RETURN).getSlot(returnType);
+ method.defineBlockLocalVariable(returnSlot, returnSlot + returnType.getSlots());
+ method.loadUndefined(returnType);
+ method.storeCompilerConstant(RETURN, returnType);
+
+ lc.enterSplitNode();
return true;
}
+ private void defineCommonSplitMethodParameters() {
+ defineSplitMethodParameter(0, CALLEE);
+ defineSplitMethodParameter(1, THIS);
+ defineSplitMethodParameter(2, SCOPE);
+ }
+
+ private void defineSplitMethodParameter(final int slot, final CompilerConstants cc) {
+ defineSplitMethodParameter(slot, Type.typeFor(cc.type()));
+ }
+
+ private void defineSplitMethodParameter(final int slot, final Type type) {
+ method.defineBlockLocalVariable(slot, slot + type.getSlots());
+ method.onLocalStore(type, slot);
+ }
+
private void fixScopeSlot(final FunctionNode functionNode) {
// TODO hack to move the scope to the expected slot (needed because split methods reuse the same slots as the root method)
- if (functionNode.compilerConstant(SCOPE).getSlot() != SCOPE.slot()) {
- method.load(SCOPE_TYPE, SCOPE.slot());
+ final int actualScopeSlot = functionNode.compilerConstant(SCOPE).getSlot(SCOPE_TYPE);
+ final int defaultScopeSlot = SCOPE.slot();
+ if (actualScopeSlot != defaultScopeSlot) {
+ method.defineBlockLocalVariable(actualScopeSlot, actualScopeSlot + 1);
+ method.load(SCOPE_TYPE, defaultScopeSlot);
method.storeCompilerConstant(SCOPE);
}
}
@@ -2408,18 +2634,26 @@
@Override
public Node leaveSplitNode(final SplitNode splitNode) {
assert method instanceof SplitMethodEmitter;
- final boolean hasReturn = method.hasReturn();
- final List<Label> targets = method.getExternalTargets();
+ lc.exitSplitNode();
+ final boolean hasReturn = method.hasReturn();
+ final SplitMethodEmitter splitMethod = ((SplitMethodEmitter)method);
+ final List<Label> targets = splitMethod.getExternalTargets();
+ final List<BreakableNode> targetNodes = splitMethod.getExternalTargetNodes();
+ final Type returnType = lc.getCurrentFunction().getReturnType();
try {
// Wrap up this method.
- method.loadCompilerConstant(RETURN);
- method._return(lc.getCurrentFunction().getReturnType());
+ if(method.isReachable()) {
+ method.loadCompilerConstant(RETURN, returnType);
+ method._return(returnType);
+ }
method.end();
+ lc.releaseSlots();
+
unit = lc.popCompileUnit(splitNode.getCompileUnit());
- method = lc.popMethodEmitter(method);
+ popMethodEmitter();
} catch (final Throwable t) {
Context.printStackTrace(t);
@@ -2450,8 +2684,8 @@
caller.ifne(breakLabel);
//has to be zero
caller.label(new Label("split_return"));
- caller.loadCompilerConstant(RETURN);
- caller._return(lc.getCurrentFunction().getReturnType());
+ caller.loadCompilerConstant(RETURN, returnType);
+ caller._return(returnType);
caller.label(breakLabel);
} else {
assert !targets.isEmpty();
@@ -2467,15 +2701,22 @@
for (int i = low; i <= targetCount; i++) {
caller.label(labels[i - low]);
if (i == 0) {
- caller.loadCompilerConstant(RETURN);
- caller._return(lc.getCurrentFunction().getReturnType());
+ caller.loadCompilerConstant(RETURN, returnType);
+ caller._return(returnType);
} else {
// Clear split state.
caller.loadCompilerConstant(SCOPE);
caller.checkcast(Scope.class);
caller.load(-1);
caller.invoke(Scope.SET_SPLIT_STATE);
- caller.splitAwareGoto(lc, targets.get(i - 1));
+ final BreakableNode targetNode = targetNodes.get(i - 1);
+ final Label label = targets.get(i - 1);
+ final JoinPredecessor jumpOrigin = splitNode.getJumpOrigin(label);
+ if(jumpOrigin != null) {
+ method.beforeJoinPoint(jumpOrigin);
+ }
+ popScopesUntil(targetNode);
+ caller.splitAwareGoto(lc, targets.get(i - 1), targetNode);
}
}
caller.label(breakLabel);
@@ -2491,31 +2732,40 @@
@Override
public boolean enterSwitchNode(final SwitchNode switchNode) {
+ if(!method.isReachable()) {
+ return false;
+ }
enterStatement(switchNode);
final Expression expression = switchNode.getExpression();
- final Symbol tag = switchNode.getTag();
- final boolean allInteger = tag.getSymbolType().isInteger();
final List<CaseNode> cases = switchNode.getCases();
- final CaseNode defaultCase = switchNode.getDefaultCase();
- final Label breakLabel = switchNode.getBreakLabel();
-
- Label defaultLabel = breakLabel;
- boolean hasDefault = false;
-
- if (defaultCase != null) {
- defaultLabel = defaultCase.getEntry();
- hasDefault = true;
- }
if (cases.isEmpty()) {
// still evaluate expression for side-effects.
- load(expression).pop();
- method.label(breakLabel);
+ loadAndDiscard(expression);
return false;
}
- if (allInteger) {
+ final CaseNode defaultCase = switchNode.getDefaultCase();
+ final Label breakLabel = switchNode.getBreakLabel();
+ final int liveLocalsOnBreak = method.getUsedSlotsWithLiveTemporaries();
+
+ final boolean hasDefault = defaultCase != null;
+ if(hasDefault && cases.size() == 1) {
+ // default case only
+ assert cases.get(0) == defaultCase;
+ loadAndDiscard(expression);
+ defaultCase.getBody().accept(this);
+ method.breakLabel(breakLabel, liveLocalsOnBreak);
+ return false;
+ }
+
+ // NOTE: it can still change in the tableswitch/lookupswitch case if there's no default case
+ // but we need to add a synthetic default case for local variable conversions
+ Label defaultLabel = hasDefault? defaultCase.getEntry() : breakLabel;
+ final boolean hasSkipConversion = LocalVariableConversion.hasLiveConversion(switchNode);
+
+ if (switchNode.isInteger()) {
// Tree for sorting values.
final TreeMap<Integer, Label> tree = new TreeMap<>();
@@ -2542,7 +2792,7 @@
// Discern low, high and range.
final int lo = values[0];
final int hi = values[size - 1];
- final int range = hi - lo + 1;
+ final long range = (long)hi - (long)lo + 1;
// Find an unused value for default.
int deflt = Integer.MIN_VALUE;
@@ -2555,7 +2805,7 @@
}
// Load switch expression.
- load(expression);
+ loadExpressionUnbounded(expression);
final Type type = expression.getType();
// If expression not int see if we can convert, if not use deflt to trigger default.
@@ -2565,9 +2815,14 @@
method.invoke(staticCallNoLookup(ScriptRuntime.class, "switchTagAsInt", int.class, exprClass.isPrimitive()? exprClass : Object.class, int.class));
}
- // If reasonable size and not too sparse (80%), use table otherwise use lookup.
- if (range > 0 && range < 4096 && range <= size * 5 / 4) {
- final Label[] table = new Label[range];
+ if(hasSkipConversion) {
+ assert defaultLabel == breakLabel;
+ defaultLabel = new Label("switch_skip");
+ }
+ // TABLESWITCH needs (range + 3) 32-bit values; LOOKUPSWITCH needs ((size * 2) + 2). Choose the one with
+ // smaller representation, favor TABLESWITCH when they're equal size.
+ if (range + 1 <= (size * 2) && range <= Integer.MAX_VALUE) {
+ final Label[] table = new Label[(int)range];
Arrays.fill(table, defaultLabel);
for (int i = 0; i < size; i++) {
final int value = values[i];
@@ -2583,43 +2838,77 @@
method.lookupswitch(defaultLabel, ints, labels);
}
+ // This is a synthetic "default case" used in absence of actual default case, created if we need to apply
+ // local variable conversions if neither case is taken.
+ if(hasSkipConversion) {
+ method.label(defaultLabel);
+ method.beforeJoinPoint(switchNode);
+ method._goto(breakLabel);
+ }
} else {
- load(expression, Type.OBJECT);
- method.store(tag);
+ final Symbol tagSymbol = switchNode.getTag();
+ // TODO: we could have non-object tag
+ final int tagSlot = tagSymbol.getSlot(Type.OBJECT);
+ loadExpressionAsObject(expression);
+ method.store(tagSymbol, Type.OBJECT);
for (final CaseNode caseNode : cases) {
final Expression test = caseNode.getTest();
if (test != null) {
- method.load(tag);
- load(test, Type.OBJECT);
+ method.load(Type.OBJECT, tagSlot);
+ loadExpressionAsObject(test);
method.invoke(ScriptRuntime.EQ_STRICT);
method.ifne(caseNode.getEntry());
}
}
-
- method._goto(hasDefault ? defaultLabel : breakLabel);
- }
+ if(hasDefault) {
+ method._goto(defaultLabel);
+ } else {
+ method.beforeJoinPoint(switchNode);
+ method._goto(breakLabel);
+ }
+ }
+
+ // First case is only reachable through jump
+ assert !method.isReachable();
for (final CaseNode caseNode : cases) {
+ final Label fallThroughLabel;
+ if(caseNode.getLocalVariableConversion() != null && method.isReachable()) {
+ fallThroughLabel = new Label("fallthrough");
+ method._goto(fallThroughLabel);
+ } else {
+ fallThroughLabel = null;
+ }
method.label(caseNode.getEntry());
+ method.beforeJoinPoint(caseNode);
+ if(fallThroughLabel != null) {
+ method.label(fallThroughLabel);
+ }
caseNode.getBody().accept(this);
}
- if (!switchNode.isTerminal()) {
- method.label(breakLabel);
- }
+ method.breakLabel(breakLabel, liveLocalsOnBreak);
return false;
}
@Override
public boolean enterThrowNode(final ThrowNode throwNode) {
+ if(!method.isReachable()) {
+ return false;
+ }
enterStatement(throwNode);
if (throwNode.isSyntheticRethrow()) {
+ method.beforeJoinPoint(throwNode);
+
//do not wrap whatever this is in an ecma exception, just rethrow it
- load(throwNode.getExpression());
+ final IdentNode exceptionExpr = (IdentNode)throwNode.getExpression();
+ final Symbol exceptionSymbol = exceptionExpr.getSymbol();
+ method.load(exceptionSymbol, EXCEPTION_TYPE);
+ method.checkcast(EXCEPTION_TYPE.getTypeClass());
method.athrow();
return false;
}
@@ -2635,13 +2924,14 @@
// this is that if expression is optimistic (or contains an optimistic subexpression), we'd potentially access
// the not-yet-<init>ialized object on the stack from the UnwarrantedOptimismException handler, and bytecode
// verifier forbids that.
- load(expression, Type.OBJECT);
+ loadExpressionAsObject(expression);
method.load(source.getName());
method.load(line);
method.load(column);
method.invoke(ECMAException.CREATE);
+ method.beforeJoinPoint(throwNode);
method.athrow();
return false;
@@ -2653,36 +2943,57 @@
@Override
public boolean enterTryNode(final TryNode tryNode) {
+ if(!method.isReachable()) {
+ return false;
+ }
enterStatement(tryNode);
final Block body = tryNode.getBody();
final List<Block> catchBlocks = tryNode.getCatchBlocks();
- final Symbol symbol = tryNode.getException();
+ final Symbol vmException = tryNode.getException();
final Label entry = new Label("try");
final Label recovery = new Label("catch");
- final Label exit = tryNode.getExit();
+ final Label exit = new Label("end_try");
final Label skip = new Label("skip");
+
+ method.canThrow(recovery);
+ // Effect any conversions that might be observed at the entry of the catch node before entering the try node.
+ // This is because even the first instruction in the try block must be presumed to be able to transfer control
+ // to the catch block. Note that this doesn't kill the original values; in this regard it works a lot like
+ // conversions of assignments within the try block.
+ method.beforeTry(tryNode, recovery);
method.label(entry);
-
- body.accept(this);
-
- if (!body.hasTerminalFlags()) {
- method._goto(skip);
+ catchLabels.push(recovery);
+ try {
+ body.accept(this);
+ } finally {
+ assert catchLabels.peek() == recovery;
+ catchLabels.pop();
+ }
+
+ method.label(exit);
+ final boolean bodyCanThrow = exit.isAfter(entry);
+ if(!bodyCanThrow) {
+ // The body can't throw an exception; don't even bother emitting the catch handlers, they're all dead code.
+ return false;
}
method._try(entry, exit, recovery, Throwable.class);
- method.label(exit);
-
+
+ if (method.isReachable()) {
+ method._goto(skip);
+ }
method._catch(recovery);
- method.store(symbol);
+ method.store(vmException, EXCEPTION_TYPE);
final int catchBlockCount = catchBlocks.size();
+ final Label afterCatch = new Label("after_catch");
for (int i = 0; i < catchBlockCount; i++) {
+ assert method.isReachable();
final Block catchBlock = catchBlocks.get(i);
- //TODO this is very ugly - try not to call enter/leave methods directly
- //better to use the implicit lexical context scoping given by the visitor's
- //accept method.
+ // Because of the peculiarities of the flow control, we need to use an explicit push/enterBlock/leaveBlock
+ // here.
lc.push(catchBlock);
enterBlock(catchBlock);
@@ -2694,13 +3005,14 @@
new Store<IdentNode>(exception) {
@Override
protected void storeNonDiscard() {
- //empty
+ // This expression is neither part of a discard, nor needs to be left on the stack after it was
+ // stored, so we override storeNonDiscard to be a no-op.
}
@Override
protected void evaluate() {
if (catchNode.isSyntheticRethrow()) {
- method.load(symbol);
+ method.load(vmException, EXCEPTION_TYPE);
return;
}
/*
@@ -2709,7 +3021,7 @@
* caught object itself to the script catch var.
*/
final Label notEcmaException = new Label("no_ecma_exception");
- method.load(symbol).dup()._instanceof(ECMAException.class).ifeq(notEcmaException);
+ method.load(vmException, EXCEPTION_TYPE).dup()._instanceof(ECMAException.class).ifeq(notEcmaException);
method.checkcast(ECMAException.class); //TODO is this necessary?
method.getField(ECMAException.THROWN);
method.label(notEcmaException);
@@ -2717,49 +3029,43 @@
}.store();
final boolean isConditionalCatch = exceptionCondition != null;
+ final Label nextCatch;
if (isConditionalCatch) {
- load(exceptionCondition, Type.BOOLEAN);
- // If catch body doesn't terminate the flow, then when we reach its break label, we could've come in
- // through either true or false branch, so we'll need a copy of the boolean evaluation on the stack to
- // know which path we took. On the other hand, if it does terminate the flow, then we won't have the
- // boolean on the top of the stack at the jump join point, so we must not push it on the stack.
- if(!catchBody.hasTerminalFlags()) {
- method.dup();
- }
- method.ifeq(catchBlock.getBreakLabel());
+ loadExpressionAsBoolean(exceptionCondition);
+ nextCatch = new Label("next_catch");
+ method.ifeq(nextCatch);
+ } else {
+ nextCatch = null;
}
catchBody.accept(this);
-
leaveBlock(catchBlock);
lc.pop(catchBlock);
-
- if(isConditionalCatch) {
- if(!catchBody.hasTerminalFlags()) {
- // If it was executed, skip. Note the dup() above that left us this value on stack. On the other
- // hand, if the catch body terminates the flow, we can reach here only if it was not executed, so
- // IFEQ is implied.
- method.ifne(skip);
- }
- if(i + 1 == catchBlockCount) {
- // No next catch block - rethrow if condition failed
- method.load(symbol).athrow();
- }
- } else {
- assert i + 1 == catchBlockCount;
- }
- }
-
+ if(method.isReachable()) {
+ method._goto(afterCatch);
+ }
+ if(nextCatch != null) {
+ method.label(nextCatch);
+ }
+ }
+
+ assert !method.isReachable();
+ // afterCatch could be the same as skip, except that we need to establish that the vmException is dead.
+ method.label(afterCatch);
+ if(method.isReachable()) {
+ method.markDeadLocalVariable(vmException);
+ }
method.label(skip);
// Finally body is always inlined elsewhere so it doesn't need to be emitted
-
return false;
}
@Override
public boolean enterVarNode(final VarNode varNode) {
-
+ if(!method.isReachable()) {
+ return false;
+ }
final Expression init = varNode.getInit();
if (init == null) {
@@ -2780,7 +3086,7 @@
}
if (needsScope) {
- load(init);
+ loadExpressionUnbounded(init);
final int flags = CALLSITE_SCOPE | getCallSiteFlags();
if (isFastScope(identSymbol)) {
storeFastScopeVar(identSymbol, flags);
@@ -2788,42 +3094,184 @@
method.dynamicSet(identNode.getName(), flags);
}
} else {
- load(init, identNode.getType());
- method.store(identSymbol);
+ final Type identType = identNode.getType();
+ if(identType == Type.UNDEFINED) {
+ // The symbol must not be slotted; the initializer is either itself undefined (explicit assignment of
+ // undefined to undefined), or the left hand side is a dead variable.
+ assert !identNode.getSymbol().isScope();
+ assert init.getType() == Type.UNDEFINED || identNode.getSymbol().slotCount() == 0;
+ loadAndDiscard(init);
+ return false;
+ }
+ loadExpressionAsType(init, identType);
+ storeIdentWithCatchConversion(identNode, identType);
}
return false;
}
+ private void storeIdentWithCatchConversion(final IdentNode identNode, final Type type) {
+ // Assignments happening in try/catch blocks need to ensure that they also store a possibly wider typed value
+ // that will be live at the exit from the try block
+ final LocalVariableConversion conversion = identNode.getLocalVariableConversion();
+ final Symbol symbol = identNode.getSymbol();
+ if(conversion != null && conversion.isLive()) {
+ assert symbol == conversion.getSymbol();
+ assert symbol.isBytecodeLocal();
+ // Only a single conversion from the target type to the join type is expected.
+ assert conversion.getNext() == null;
+ assert conversion.getFrom() == type;
+ // We must propagate potential type change to the catch block
+ final Label catchLabel = catchLabels.peek();
+ assert catchLabel != METHOD_BOUNDARY; // ident conversion only exists in try blocks
+ assert catchLabel.isReachable();
+ final Type joinType = conversion.getTo();
+ final Label.Stack catchStack = catchLabel.getStack();
+ final int joinSlot = symbol.getSlot(joinType);
+ // With nested try/catch blocks (incl. synthetic ones for finally), we can have a supposed conversion for
+ // the exception symbol in the nested catch, but it isn't live in the outer catch block, so prevent doing
+ // conversions for it. E.g. in "try { try { ... } catch(e) { e = 1; } } catch(e2) { ... }", we must not
+ // introduce an I->O conversion on "e = 1" assignment as "e" is not live in "catch(e2)".
+ if(catchStack.getUsedSlotsWithLiveTemporaries() > joinSlot) {
+ method.dup();
+ method.convert(joinType);
+ method.store(symbol, joinType);
+ catchLabel.getStack().onLocalStore(joinType, joinSlot, true);
+ method.canThrow(catchLabel);
+ // Store but keep the previous store live too.
+ method.store(symbol, type, false);
+ return;
+ }
+ }
+
+ method.store(symbol, type, true);
+ }
+
@Override
public boolean enterWhileNode(final WhileNode whileNode) {
- final Expression test = whileNode.getTest();
- final Block body = whileNode.getBody();
- final Label breakLabel = whileNode.getBreakLabel();
- final Label continueLabel = whileNode.getContinueLabel();
- final boolean isDoWhile = whileNode.isDoWhile();
- final Label loopLabel = new Label("loop");
-
- if (!isDoWhile) {
- method._goto(continueLabel);
- }
-
- method.label(loopLabel);
- body.accept(this);
- if (!whileNode.isTerminal()) {
- method.label(continueLabel);
+ if(!method.isReachable()) {
+ return false;
+ }
+ if(whileNode.isDoWhile()) {
+ enterDoWhile(whileNode);
+ } else {
enterStatement(whileNode);
- new BranchOptimizer(this, method).execute(test, loopLabel, true);
- method.label(breakLabel);
- }
-
+ enterForOrWhile(whileNode, null);
+ }
return false;
}
+ private void enterForOrWhile(final LoopNode loopNode, final JoinPredecessorExpression modify) {
+ // NOTE: the usual pattern for compiling test-first loops is "GOTO test; body; test; IFNE body". We use the less
+ // conventional "test; IFEQ break; body; GOTO test; break;". It has one extra unconditional GOTO in each repeat
+ // of the loop, but it's not a problem for modern JIT compilers. We do this because our local variable type
+ // tracking is unfortunately not really prepared for out-of-order execution, e.g. compiling the following
+ // contrived but legal JavaScript code snippet would fail because the test changes the type of "i" from object
+ // to double: var i = {valueOf: function() { return 1} }; while(--i >= 0) { ... }
+ // Instead of adding more complexity to the local variable type tracking, we instead choose to emit this
+ // different code shape.
+ final int liveLocalsOnBreak = method.getUsedSlotsWithLiveTemporaries();
+ final JoinPredecessorExpression test = loopNode.getTest();
+ if(Expression.isAlwaysFalse(test)) {
+ loadAndDiscard(test);
+ return;
+ }
+
+ method.beforeJoinPoint(loopNode);
+
+ final Label continueLabel = loopNode.getContinueLabel();
+ final Label repeatLabel = modify != null ? new Label("for_repeat") : continueLabel;
+ method.label(repeatLabel);
+ final int liveLocalsOnContinue = method.getUsedSlotsWithLiveTemporaries();
+
+ final Block body = loopNode.getBody();
+ final Label breakLabel = loopNode.getBreakLabel();
+ final boolean testHasLiveConversion = test != null && LocalVariableConversion.hasLiveConversion(test);
+ if(Expression.isAlwaysTrue(test)) {
+ if(test != null) {
+ loadAndDiscard(test);
+ if(testHasLiveConversion) {
+ method.beforeJoinPoint(test);
+ }
+ }
+ } else if(testHasLiveConversion) {
+ emitBranch(test.getExpression(), body.getEntryLabel(), true);
+ method.beforeJoinPoint(test);
+ method._goto(breakLabel);
+ } else {
+ emitBranch(test.getExpression(), breakLabel, false);
+ }
+
+ body.accept(this);
+ if(repeatLabel != continueLabel) {
+ emitContinueLabel(continueLabel, liveLocalsOnContinue);
+ }
+
+ if(method.isReachable()) {
+ if(modify != null) {
+ lineNumber(loopNode);
+ loadAndDiscard(modify);
+ method.beforeJoinPoint(modify);
+ }
+ method._goto(repeatLabel);
+ }
+
+ method.breakLabel(breakLabel, liveLocalsOnBreak);
+ }
+
+ private void emitContinueLabel(final Label continueLabel, final int liveLocals) {
+ final boolean reachable = method.isReachable();
+ method.breakLabel(continueLabel, liveLocals);
+ // If we reach here only through a continue statement (e.g. body does not exit normally) then the
+ // continueLabel can have extra non-temp symbols (e.g. exception from a try/catch contained in the body). We
+ // must make sure those are thrown away.
+ if(!reachable) {
+ method.undefineLocalVariables(lc.getUsedSlotCount(), false);
+ }
+ }
+
+ private void enterDoWhile(final WhileNode whileNode) {
+ final int liveLocalsOnContinueOrBreak = method.getUsedSlotsWithLiveTemporaries();
+ method.beforeJoinPoint(whileNode);
+
+ final Block body = whileNode.getBody();
+ body.accept(this);
+
+ emitContinueLabel(whileNode.getContinueLabel(), liveLocalsOnContinueOrBreak);
+ if(method.isReachable()) {
+ lineNumber(whileNode);
+ final JoinPredecessorExpression test = whileNode.getTest();
+ final Label bodyEntryLabel = body.getEntryLabel();
+ final boolean testHasLiveConversion = LocalVariableConversion.hasLiveConversion(test);
+ if(Expression.isAlwaysFalse(test)) {
+ loadAndDiscard(test);
+ if(testHasLiveConversion) {
+ method.beforeJoinPoint(test);
+ }
+ } else if(testHasLiveConversion) {
+ // If we have conversions after the test in do-while, they need to be effected on both branches.
+ final Label beforeExit = new Label("do_while_preexit");
+ emitBranch(test.getExpression(), beforeExit, false);
+ method.beforeJoinPoint(test);
+ method._goto(bodyEntryLabel);
+ method.label(beforeExit);
+ method.beforeJoinPoint(test);
+ } else {
+ emitBranch(test.getExpression(), bodyEntryLabel, true);
+ }
+ }
+ method.breakLabel(whileNode.getBreakLabel(), liveLocalsOnContinueOrBreak);
+ }
+
+
@Override
public boolean enterWithNode(final WithNode withNode) {
+ if(!method.isReachable()) {
+ return false;
+ }
+ enterStatement(withNode);
final Expression expression = withNode.getExpression();
- final Node body = withNode.getBody();
+ final Block body = withNode.getBody();
// It is possible to have a "pathological" case where the with block does not reference *any* identifiers. It's
// pointless, but legal. In that case, if nothing else in the method forced the assignment of a slot to the
@@ -2835,7 +3283,7 @@
method.loadCompilerConstant(SCOPE);
}
- load(expression, Type.OBJECT);
+ loadExpressionAsObject(expression);
final Label tryLabel;
if (hasScope) {
@@ -2860,52 +3308,60 @@
final Label catchLabel = new Label("with_catch");
final Label exitLabel = new Label("with_exit");
- if (!body.isTerminal()) {
+ method.label(endLabel);
+ // Somewhat conservatively presume that if the body is not empty, it can throw an exception. In any case,
+ // we must prevent trying to emit a try-catch for empty range, as it causes a verification error.
+ final boolean bodyCanThrow = endLabel.isAfter(tryLabel);
+ if(bodyCanThrow) {
+ method._try(tryLabel, endLabel, catchLabel);
+ }
+
+ boolean reachable = method.isReachable();
+ if(reachable) {
popScope();
- method._goto(exitLabel);
- }
-
- method._try(tryLabel, endLabel, catchLabel);
- method.label(endLabel);
-
- method._catch(catchLabel);
- popScope();
- method.athrow();
-
- method.label(exitLabel);
-
+ if(bodyCanThrow) {
+ method._goto(exitLabel);
+ }
+ }
+
+ if(bodyCanThrow) {
+ method._catch(catchLabel);
+ popScopeException();
+ method.athrow();
+ if(reachable) {
+ method.label(exitLabel);
+ }
+ }
}
return false;
}
- @Override
- public boolean enterADD(final UnaryNode unaryNode) {
- load(unaryNode.getExpression(), unaryNode.getType());
- assert unaryNode.getType().isNumeric();
- method.store(unaryNode.getSymbol());
- return false;
+ private void loadADD(final UnaryNode unaryNode, final TypeBounds resultBounds) {
+ loadExpression(unaryNode.getExpression(), resultBounds.booleanToInt().notWiderThan(Type.NUMBER));
+ if(method.peekType() == Type.BOOLEAN) {
+ // It's a no-op in bytecode, but we must make sure it is treated as an int for purposes of type signatures
+ method.convert(Type.INT);
+ }
}
- @Override
- public boolean enterBIT_NOT(final UnaryNode unaryNode) {
- load(unaryNode.getExpression(), Type.INT).load(-1).xor().store(unaryNode.getSymbol());
- return false;
+ private void loadBIT_NOT(final UnaryNode unaryNode) {
+ loadExpression(unaryNode.getExpression(), TypeBounds.INT).load(-1).xor();
}
- @Override
- public boolean enterDECINC(final UnaryNode unaryNode) {
- final Expression rhs = unaryNode.getExpression();
+ private void loadDECINC(final UnaryNode unaryNode) {
+ final Expression operand = unaryNode.getExpression();
final Type type = unaryNode.getType();
+ final TypeBounds typeBounds = new TypeBounds(type, Type.NUMBER);
final TokenType tokenType = unaryNode.tokenType();
final boolean isPostfix = tokenType == TokenType.DECPOSTFIX || tokenType == TokenType.INCPOSTFIX;
final boolean isIncrement = tokenType == TokenType.INCPREFIX || tokenType == TokenType.INCPOSTFIX;
assert !type.isObject();
- new SelfModifyingStore<UnaryNode>(unaryNode, rhs) {
+ new SelfModifyingStore<UnaryNode>(unaryNode, operand) {
private void loadRhs() {
- load(rhs, type, true);
+ loadExpression(operand, typeBounds, true);
}
@Override
@@ -2913,7 +3369,7 @@
if(isPostfix) {
loadRhs();
} else {
- new OptimisticOperation() {
+ new OptimisticOperation(unaryNode, typeBounds) {
@Override
void loadStack() {
loadRhs();
@@ -2921,9 +3377,9 @@
}
@Override
void consumeStack() {
- doDecInc();
+ doDecInc(getProgramPoint());
}
- }.emit(unaryNode, getOptimisticIgnoreCountForSelfModifyingExpression(rhs));
+ }.emit(getOptimisticIgnoreCountForSelfModifyingExpression(operand));
}
}
@@ -2931,16 +3387,16 @@
protected void storeNonDiscard() {
super.storeNonDiscard();
if (isPostfix) {
- new OptimisticOperation() {
+ new OptimisticOperation(unaryNode, typeBounds) {
@Override
void loadStack() {
loadMinusOne();
}
@Override
void consumeStack() {
- doDecInc();
+ doDecInc(getProgramPoint());
}
- }.emit(unaryNode, 1); // 1 for non-incremented result on the top of the stack pushed in evaluate()
+ }.emit(1); // 1 for non-incremented result on the top of the stack pushed in evaluate()
}
}
@@ -2954,482 +3410,431 @@
}
}
- private void doDecInc() {
- method.add(unaryNode.getProgramPoint());
+ private void doDecInc(final int programPoint) {
+ method.add(programPoint);
}
}.store();
-
- return false;
}
private static int getOptimisticIgnoreCountForSelfModifyingExpression(final Expression target) {
return target instanceof AccessNode ? 1 : target instanceof IndexNode ? 2 : 0;
}
- @Override
- public boolean enterDISCARD(final UnaryNode unaryNode) {
- final Expression rhs = unaryNode.getExpression();
-
- lc.pushDiscard(rhs);
- load(rhs);
-
- if (lc.getCurrentDiscard() == rhs) {
- assert !rhs.isAssignment();
+ private void loadAndDiscard(final Expression expr) {
+ // TODO: move checks for discarding to actual expression load code (e.g. as we do with void). That way we might
+ // be able to eliminate even more checks.
+ if(expr instanceof PrimitiveLiteralNode | isLocalVariable(expr)) {
+ assert lc.getCurrentDiscard() != expr;
+ // Don't bother evaluating expressions without side effects. Typical usage is "void 0" for reliably generating
+ // undefined.
+ return;
+ }
+
+ lc.pushDiscard(expr);
+ loadExpression(expr, TypeBounds.UNBOUNDED);
+ if (lc.getCurrentDiscard() == expr) {
+ assert !expr.isAssignment();
+ // NOTE: if we had a way to load with type void, we could avoid popping
method.pop();
lc.popDiscard();
}
-
- return false;
}
- @Override
- public boolean enterNEW(final UnaryNode unaryNode) {
+ private void loadNEW(final UnaryNode unaryNode) {
final CallNode callNode = (CallNode)unaryNode.getExpression();
final List<Expression> args = callNode.getArgs();
// Load function reference.
- load(callNode.getFunction(), Type.OBJECT); // must detect type error
+ loadExpressionAsObject(callNode.getFunction()); // must detect type error
method.dynamicNew(1 + loadArgs(args), getCallSiteFlags());
- method.store(unaryNode.getSymbol());
-
- return false;
}
- @Override
- public boolean enterNOT(final UnaryNode unaryNode) {
- final Expression rhs = unaryNode.getExpression();
-
- load(rhs, Type.BOOLEAN);
-
- final Label trueLabel = new Label("true");
- final Label afterLabel = new Label("after");
-
- method.ifne(trueLabel);
- method.load(true);
- method._goto(afterLabel);
- method.label(trueLabel);
- method.load(false);
- method.label(afterLabel);
- method.store(unaryNode.getSymbol());
-
- return false;
+ private void loadNOT(final UnaryNode unaryNode) {
+ final Expression expr = unaryNode.getExpression();
+ if(expr instanceof UnaryNode && expr.isTokenType(TokenType.NOT)) {
+ // !!x is idiomatic boolean cast in JavaScript
+ loadExpressionAsBoolean(((UnaryNode)expr).getExpression());
+ } else {
+ final Label trueLabel = new Label("true");
+ final Label afterLabel = new Label("after");
+
+ emitBranch(expr, trueLabel, true);
+ method.load(true);
+ method._goto(afterLabel);
+ method.label(trueLabel);
+ method.load(false);
+ method.label(afterLabel);
+ }
}
- @Override
- public boolean enterSUB(final UnaryNode unaryNode) {
+ private void loadSUB(final UnaryNode unaryNode, final TypeBounds resultBounds) {
assert unaryNode.getType().isNumeric();
- new OptimisticOperation() {
+ final TypeBounds numericBounds = resultBounds.booleanToInt();
+ new OptimisticOperation(unaryNode, numericBounds) {
@Override
void loadStack() {
- load(unaryNode.getExpression(), unaryNode.getType());
+ final Expression expr = unaryNode.getExpression();
+ loadExpression(expr, numericBounds.notWiderThan(Type.NUMBER));
}
@Override
void consumeStack() {
- method.neg(unaryNode.getProgramPoint());
- }
- }.emit(unaryNode);
- method.store(unaryNode.getSymbol());
-
- return false;
+ method.neg(getProgramPoint());
+ }
+ }.emit();
}
- @Override
- public boolean enterVOID(final UnaryNode unaryNode) {
- load(unaryNode.getExpression()).pop();
- method.loadUndefined(Type.OBJECT);
-
- return false;
+ public void loadVOID(final UnaryNode unaryNode, final TypeBounds resultBounds) {
+ loadAndDiscard(unaryNode.getExpression());
+ if(lc.getCurrentDiscard() == unaryNode) {
+ lc.popDiscard();
+ } else {
+ method.loadUndefined(resultBounds.widest);
+ }
}
- private void enterNumericAdd(final BinaryNode binaryNode, final Expression lhs, final Expression rhs, final Type type) {
- new OptimisticOperation() {
+ public void loadADD(final BinaryNode binaryNode, final TypeBounds resultBounds) {
+ new OptimisticOperation(binaryNode, resultBounds) {
@Override
void loadStack() {
- loadBinaryOperands(lhs, rhs, type);
- }
+ final TypeBounds operandBounds;
+ final boolean isOptimistic = isValid(getProgramPoint());
+ if(isOptimistic) {
+ operandBounds = new TypeBounds(binaryNode.getType(), Type.OBJECT);
+ } else {
+ // Non-optimistic, non-FP +. Allow it to overflow.
+ operandBounds = new TypeBounds(binaryNode.getWidestOperandType(), Type.OBJECT);
+ }
+ loadBinaryOperands(binaryNode.lhs(), binaryNode.rhs(), operandBounds, false);
+ }
+
@Override
void consumeStack() {
- method.add(binaryNode.getProgramPoint()); //if the symbol is optimistic, it always needs to be written, not on the stack?
- }
- }.emit(binaryNode);
- method.store(binaryNode.getSymbol());
+ method.add(getProgramPoint());
+ }
+ }.emit();
}
- @Override
- public boolean enterADD(final BinaryNode binaryNode) {
- final Expression lhs = binaryNode.lhs();
- final Expression rhs = binaryNode.rhs();
-
- final Type type = binaryNode.getType();
- if (type.isNumeric()) {
- enterNumericAdd(binaryNode, lhs, rhs, type);
- } else {
- loadBinaryOperands(binaryNode);
- method.add(INVALID_PROGRAM_POINT);
- method.store(binaryNode.getSymbol());
- }
-
- return false;
- }
-
- private boolean enterAND_OR(final BinaryNode binaryNode) {
- final Expression lhs = binaryNode.lhs();
- final Expression rhs = binaryNode.rhs();
+ private void loadAND_OR(final BinaryNode binaryNode, final TypeBounds resultBounds, final boolean isAnd) {
+ final Type narrowestOperandType = Type.widestReturnType(binaryNode.lhs().getType(), binaryNode.rhs().getType());
final Label skip = new Label("skip");
-
- load(lhs, Type.OBJECT).dup().convert(Type.BOOLEAN);
-
- if (binaryNode.tokenType() == TokenType.AND) {
- method.ifeq(skip);
+ if(narrowestOperandType == Type.BOOLEAN) {
+ // optimize all-boolean logical expressions
+ final Label onTrue = new Label("andor_true");
+ emitBranch(binaryNode, onTrue, true);
+ method.load(false);
+ method._goto(skip);
+ method.label(onTrue);
+ method.load(true);
+ method.label(skip);
+ return;
+ }
+
+ final TypeBounds outBounds = resultBounds.notNarrowerThan(narrowestOperandType);
+ final JoinPredecessorExpression lhs = (JoinPredecessorExpression)binaryNode.lhs();
+ final boolean lhsConvert = LocalVariableConversion.hasLiveConversion(lhs);
+ final Label evalRhs = lhsConvert ? new Label("eval_rhs") : null;
+
+ loadExpression(lhs, outBounds).dup().convert(Type.BOOLEAN);
+ if (isAnd) {
+ if(lhsConvert) {
+ method.ifne(evalRhs);
+ } else {
+ method.ifeq(skip);
+ }
+ } else if(lhsConvert) {
+ method.ifeq(evalRhs);
} else {
method.ifne(skip);
}
+ if(lhsConvert) {
+ method.beforeJoinPoint(lhs);
+ method._goto(skip);
+ method.label(evalRhs);
+ }
+
method.pop();
- load(rhs, Type.OBJECT);
+ final JoinPredecessorExpression rhs = (JoinPredecessorExpression)binaryNode.rhs();
+ loadExpression(rhs, outBounds);
+ method.beforeJoinPoint(rhs);
method.label(skip);
- method.store(binaryNode.getSymbol());
-
- return false;
}
- @Override
- public boolean enterAND(final BinaryNode binaryNode) {
- return enterAND_OR(binaryNode);
+ private static boolean isLocalVariable(final Expression lhs) {
+ return lhs instanceof IdentNode && isLocalVariable((IdentNode)lhs);
}
- @Override
- public boolean enterASSIGN(final BinaryNode binaryNode) {
+ private static boolean isLocalVariable(final IdentNode lhs) {
+ return lhs.getSymbol().isBytecodeLocal();
+ }
+
+ // NOTE: does not use resultBounds as the assignment is driven by the type of the RHS
+ private void loadASSIGN(final BinaryNode binaryNode) {
final Expression lhs = binaryNode.lhs();
final Expression rhs = binaryNode.rhs();
- final Type lhsType = lhs.getType();
final Type rhsType = rhs.getType();
-
- if (!lhsType.isEquivalentTo(rhsType)) {
- //this is OK if scoped, only locals are wrong
+ // Detect dead assignments
+ if(lhs instanceof IdentNode) {
+ final Symbol symbol = ((IdentNode)lhs).getSymbol();
+ if(!symbol.isScope() && !symbol.hasSlotFor(rhsType) && lc.getCurrentDiscard() == binaryNode) {
+ loadAndDiscard(rhs);
+ lc.popDiscard();
+ method.markDeadLocalVariable(symbol);
+ return;
+ }
}
new Store<BinaryNode>(binaryNode, lhs) {
@Override
protected void evaluate() {
- if (lhs instanceof IdentNode && !lhs.getSymbol().isScope()) {
- load(rhs, lhsType);
- } else {
- load(rhs);
- }
+ // NOTE: we're loading with "at least as wide as" so optimistic operations on the right hand side
+ // remain optimistic, and then explicitly convert to the required type if needed.
+ loadExpressionAsType(rhs, rhsType);
}
}.store();
-
- return false;
}
/**
- * Helper class for assignment ops, e.g. *=, += and so on..
+ * Binary self-assignment that can be optimistic: +=, -=, *=, and /=.
*/
- private abstract class AssignOp extends SelfModifyingStore<BinaryNode> {
-
- /** The type of the resulting operation */
- private final Type opType;
+ private abstract class BinaryOptimisticSelfAssignment extends SelfModifyingStore<BinaryNode> {
/**
* Constructor
*
* @param node the assign op node
*/
- AssignOp(final BinaryNode node) {
- this(node.getType(), node);
- }
-
- /**
- * Constructor
- *
- * @param opType type of the computation - overriding the type of the node
- * @param node the assign op node
- */
- AssignOp(final Type opType, final BinaryNode node) {
+ BinaryOptimisticSelfAssignment(final BinaryNode node) {
super(node, node.lhs());
- this.opType = opType;
+ }
+
+ protected abstract void op(OptimisticOperation oo);
+
+ @Override
+ protected void evaluate() {
+ final Expression lhs = assignNode.lhs();
+ final Type widest = assignNode.isTokenType(TokenType.ASSIGN_ADD) ? Type.OBJECT : assignNode.getWidestOperationType();
+ final TypeBounds bounds = new TypeBounds(assignNode.getType(), widest);
+ new OptimisticOperation(assignNode, bounds) {
+ @Override
+ void loadStack() {
+ loadBinaryOperands(lhs, assignNode.rhs(), bounds, true);
+ }
+ @Override
+ void consumeStack() {
+ op(this);
+ }
+ }.emit(getOptimisticIgnoreCountForSelfModifyingExpression(lhs));
+ method.convert(assignNode.getType());
+ }
+ }
+
+ /**
+ * Non-optimistic binary self-assignment operation. Basically, everything except +=, -=, *=, and /=.
+ */
+ private abstract class BinarySelfAssignment extends SelfModifyingStore<BinaryNode> {
+ BinarySelfAssignment(final BinaryNode node) {
+ super(node, node.lhs());
}
protected abstract void op();
@Override
protected void evaluate() {
- final Expression lhs = assignNode.lhs();
- new OptimisticOperation() {
- @Override
- void loadStack() {
- loadBinaryOperands(lhs, assignNode.rhs(), opType, true);
- }
- @Override
- void consumeStack() {
- op();
- }
- }.emit(assignNode, getOptimisticIgnoreCountForSelfModifyingExpression(lhs));
- method.convert(assignNode.getType());
+ loadBinaryOperands(assignNode.lhs(), assignNode.rhs(), TypeBounds.UNBOUNDED.notWiderThan(assignNode.getWidestOperandType()), true);
+ op();
}
}
- @Override
- public boolean enterASSIGN_ADD(final BinaryNode binaryNode) {
- assert RuntimeNode.Request.ADD.canSpecialize();
- final Type lhsType = binaryNode.lhs().getType();
- final Type rhsType = binaryNode.rhs().getType();
- final boolean specialize = binaryNode.getType() == Type.OBJECT;
-
- new AssignOp(binaryNode) {
-
+ private void loadASSIGN_ADD(final BinaryNode binaryNode) {
+ new BinaryOptimisticSelfAssignment(binaryNode) {
@Override
- protected void op() {
- if (specialize) {
- method.dynamicRuntimeCall(
- new SpecializedRuntimeNode(
- Request.ADD,
- new Type[] {
- lhsType,
- rhsType,
- },
- Type.OBJECT).getInitialName(),
- Type.OBJECT,
- Request.ADD);
- } else {
- method.add(binaryNode.getProgramPoint());
- }
- }
-
- @Override
- protected void evaluate() {
- super.evaluate();
+ protected void op(final OptimisticOperation oo) {
+ assert !(binaryNode.getType().isObject() && oo.isOptimistic);
+ method.add(oo.getProgramPoint());
}
}.store();
-
- return false;
}
- @Override
- public boolean enterASSIGN_BIT_AND(final BinaryNode binaryNode) {
- new AssignOp(Type.INT, binaryNode) {
+ private void loadASSIGN_BIT_AND(final BinaryNode binaryNode) {
+ new BinarySelfAssignment(binaryNode) {
@Override
protected void op() {
method.and();
}
}.store();
-
- return false;
}
- @Override
- public boolean enterASSIGN_BIT_OR(final BinaryNode binaryNode) {
- new AssignOp(Type.INT, binaryNode) {
+ private void loadASSIGN_BIT_OR(final BinaryNode binaryNode) {
+ new BinarySelfAssignment(binaryNode) {
@Override
protected void op() {
method.or();
}
}.store();
-
- return false;
}
- @Override
- public boolean enterASSIGN_BIT_XOR(final BinaryNode binaryNode) {
- new AssignOp(Type.INT, binaryNode) {
+ private void loadASSIGN_BIT_XOR(final BinaryNode binaryNode) {
+ new BinarySelfAssignment(binaryNode) {
@Override
protected void op() {
method.xor();
}
}.store();
-
- return false;
}
- @Override
- public boolean enterASSIGN_DIV(final BinaryNode binaryNode) {
- new AssignOp(binaryNode) {
+ private void loadASSIGN_DIV(final BinaryNode binaryNode) {
+ new BinaryOptimisticSelfAssignment(binaryNode) {
@Override
- protected void op() {
- method.div(binaryNode.getProgramPoint());
+ protected void op(final OptimisticOperation oo) {
+ method.div(oo.getProgramPoint());
}
}.store();
-
- return false;
}
- @Override
- public boolean enterASSIGN_MOD(final BinaryNode binaryNode) {
- new AssignOp(binaryNode) {
+ private void loadASSIGN_MOD(final BinaryNode binaryNode) {
+ new BinaryOptimisticSelfAssignment(binaryNode) {
@Override
- protected void op() {
- method.rem();
+ protected void op(final OptimisticOperation oo) {
+ method.rem(oo.getProgramPoint());
}
}.store();
-
- return false;
}
- @Override
- public boolean enterASSIGN_MUL(final BinaryNode binaryNode) {
- new AssignOp(binaryNode) {
+ private void loadASSIGN_MUL(final BinaryNode binaryNode) {
+ new BinaryOptimisticSelfAssignment(binaryNode) {
@Override
- protected void op() {
- method.mul(binaryNode.getProgramPoint());
+ protected void op(final OptimisticOperation oo) {
+ method.mul(oo.getProgramPoint());
}
}.store();
-
- return false;
}
- @Override
- public boolean enterASSIGN_SAR(final BinaryNode binaryNode) {
- new AssignOp(Type.INT, binaryNode) {
+ private void loadASSIGN_SAR(final BinaryNode binaryNode) {
+ new BinarySelfAssignment(binaryNode) {
@Override
protected void op() {
method.sar();
}
}.store();
-
- return false;
}
- @Override
- public boolean enterASSIGN_SHL(final BinaryNode binaryNode) {
- new AssignOp(Type.INT, binaryNode) {
+ private void loadASSIGN_SHL(final BinaryNode binaryNode) {
+ new BinarySelfAssignment(binaryNode) {
@Override
protected void op() {
method.shl();
}
}.store();
-
- return false;
}
- @Override
- public boolean enterASSIGN_SHR(final BinaryNode binaryNode) {
- new AssignOp(Type.INT, binaryNode) {
+ private void loadASSIGN_SHR(final BinaryNode binaryNode) {
+ new BinarySelfAssignment(binaryNode) {
@Override
protected void op() {
- method.shr();
- method.convert(Type.LONG).load(JSType.MAX_UINT).and();
+ doSHR();
+ }
+
+ }.store();
+ }
+
+ private void doSHR() {
+ // TODO: make SHR optimistic
+ method.shr().convert(Type.LONG).load(JSType.MAX_UINT).and();
+ }
+
+ private void loadASSIGN_SUB(final BinaryNode binaryNode) {
+ new BinaryOptimisticSelfAssignment(binaryNode) {
+ @Override
+ protected void op(final OptimisticOperation oo) {
+ method.sub(oo.getProgramPoint());
}
}.store();
-
- return false;
- }
-
- @Override
- public boolean enterASSIGN_SUB(final BinaryNode binaryNode) {
- new AssignOp(binaryNode) {
- @Override
- protected void op() {
- method.sub(binaryNode.getProgramPoint());
- }
- }.store();
-
- return false;
}
/**
* Helper class for binary arithmetic ops
*/
private abstract class BinaryArith {
-
- protected abstract void op();
-
- protected void evaluate(final BinaryNode node) {
- new OptimisticOperation() {
+ protected abstract void op(int programPoint);
+
+ protected void evaluate(final BinaryNode node, final TypeBounds resultBounds) {
+ final TypeBounds numericBounds = resultBounds.booleanToInt().objectToNumber();
+ new OptimisticOperation(node, numericBounds) {
@Override
void loadStack() {
- loadBinaryOperands(node);
+ final TypeBounds operandBounds;
+ if(numericBounds.narrowest == Type.NUMBER) {
+ // Result should be double always. Propagate it into the operands so we don't have lots of I2D
+ // and L2D after operand evaluation.
+ assert numericBounds.widest == Type.NUMBER;
+ operandBounds = numericBounds;
+ } else {
+ final boolean isOptimistic = isValid(getProgramPoint());
+ if(isOptimistic) {
+ operandBounds = new TypeBounds(node.getType(), Type.NUMBER);
+ } else if(node.isTokenType(TokenType.DIV) || node.isTokenType(TokenType.MOD)) {
+ // Non-optimistic division must always take double arguments as its result must also be
+ // double.
+ operandBounds = TypeBounds.NUMBER;
+ } else {
+ // Non-optimistic, non-FP subtraction or multiplication. Allow them to overflow.
+ operandBounds = new TypeBounds(Type.narrowest(node.getWidestOperandType(),
+ numericBounds.widest), Type.NUMBER);
+ }
+ }
+ loadBinaryOperands(node.lhs(), node.rhs(), operandBounds, false);
}
+
@Override
void consumeStack() {
- op();
+ op(getProgramPoint());
}
- }.emit(node);
- method.store(node.getSymbol());
+ }.emit();
}
}
- @Override
- public boolean enterBIT_AND(final BinaryNode binaryNode) {
- new BinaryArith() {
- @Override
- protected void op() {
- method.and();
- }
- }.evaluate(binaryNode);
-
- return false;
+ private void loadBIT_AND(final BinaryNode binaryNode) {
+ loadBinaryOperands(binaryNode);
+ method.and();
+ }
+
+ private void loadBIT_OR(final BinaryNode binaryNode) {
+ loadBinaryOperands(binaryNode);
+ method.or();
}
- @Override
- public boolean enterBIT_OR(final BinaryNode binaryNode) {
- new BinaryArith() {
- @Override
- protected void op() {
- method.or();
- }
- }.evaluate(binaryNode);
-
- return false;
+ private void loadBIT_XOR(final BinaryNode binaryNode) {
+ loadBinaryOperands(binaryNode);
+ method.xor();
}
- @Override
- public boolean enterBIT_XOR(final BinaryNode binaryNode) {
+ private void loadCOMMARIGHT(final BinaryNode binaryNode, final TypeBounds resultBounds) {
+ loadAndDiscard(binaryNode.lhs());
+ loadExpression(binaryNode.rhs(), resultBounds);
+ }
+
+ private void loadCOMMALEFT(final BinaryNode binaryNode, final TypeBounds resultBounds) {
+ loadExpression(binaryNode.lhs(), resultBounds);
+ loadAndDiscard(binaryNode.rhs());
+ }
+
+ private void loadDIV(final BinaryNode binaryNode, final TypeBounds resultBounds) {
new BinaryArith() {
@Override
- protected void op() {
- method.xor();
- }
- }.evaluate(binaryNode);
-
- return false;
- }
-
- private boolean enterComma(final BinaryNode binaryNode) {
- final Expression lhs = binaryNode.lhs();
- final Expression rhs = binaryNode.rhs();
-
- assert lhs.isTokenType(TokenType.DISCARD);
- load(lhs);
- load(rhs);
- method.store(binaryNode.getSymbol());
-
- return false;
- }
-
- @Override
- public boolean enterCOMMARIGHT(final BinaryNode binaryNode) {
- return enterComma(binaryNode);
+ protected void op(final int programPoint) {
+ method.div(programPoint);
+ }
+ }.evaluate(binaryNode, resultBounds);
}
- @Override
- public boolean enterCOMMALEFT(final BinaryNode binaryNode) {
- return enterComma(binaryNode);
- }
-
- @Override
- public boolean enterDIV(final BinaryNode binaryNode) {
- new BinaryArith() {
- @Override
- protected void op() {
- method.div(binaryNode.getProgramPoint());
- }
- }.evaluate(binaryNode);
-
- return false;
- }
-
- private boolean enterCmp(final Expression lhs, final Expression rhs, final Condition cond, final Type type, final Symbol symbol) {
- final Type lhsType = lhs.getType();
- final Type rhsType = rhs.getType();
-
- final Type widest = Type.widest(lhsType, rhsType);
- assert widest.isNumeric() || widest.isBoolean() : widest;
-
- loadBinaryOperands(lhs, rhs, widest);
+ private void loadCmp(final BinaryNode binaryNode, final Condition cond) {
+ assert comparisonOperandsArePrimitive(binaryNode) : binaryNode;
+ loadBinaryOperands(binaryNode);
+
final Label trueLabel = new Label("trueLabel");
final Label afterLabel = new Label("skip");
@@ -3440,171 +3845,88 @@
method.label(trueLabel);
method.load(Boolean.TRUE);
method.label(afterLabel);
-
- method.convert(type);
- method.store(symbol);
-
- return false;
}
- private boolean enterCmp(final BinaryNode binaryNode, final Condition cond) {
- return enterCmp(binaryNode.lhs(), binaryNode.rhs(), cond, binaryNode.getType(), binaryNode.getSymbol());
- }
-
- @Override
- public boolean enterEQ(final BinaryNode binaryNode) {
- return enterCmp(binaryNode, Condition.EQ);
- }
-
- @Override
- public boolean enterEQ_STRICT(final BinaryNode binaryNode) {
- return enterCmp(binaryNode, Condition.EQ);
+ private static boolean comparisonOperandsArePrimitive(final BinaryNode binaryNode) {
+ final Type widest = Type.widest(binaryNode.lhs().getType(), binaryNode.rhs().getType());
+ return widest.isNumeric() || widest.isBoolean();
}
- @Override
- public boolean enterGE(final BinaryNode binaryNode) {
- return enterCmp(binaryNode, Condition.GE);
- }
-
- @Override
- public boolean enterGT(final BinaryNode binaryNode) {
- return enterCmp(binaryNode, Condition.GT);
+ private void loadMOD(final BinaryNode binaryNode, final TypeBounds resultBounds) {
+ new BinaryArith() {
+ @Override
+ protected void op(final int programPoint) {
+ method.rem(programPoint);
+ }
+ }.evaluate(binaryNode, resultBounds);
}
- @Override
- public boolean enterLE(final BinaryNode binaryNode) {
- return enterCmp(binaryNode, Condition.LE);
- }
-
- @Override
- public boolean enterLT(final BinaryNode binaryNode) {
- return enterCmp(binaryNode, Condition.LT);
- }
-
- @Override
- public boolean enterMOD(final BinaryNode binaryNode) {
+ private void loadMUL(final BinaryNode binaryNode, final TypeBounds resultBounds) {
new BinaryArith() {
@Override
- protected void op() {
- method.rem();
- }
- }.evaluate(binaryNode);
-
- return false;
+ protected void op(final int programPoint) {
+ method.mul(programPoint);
+ }
+ }.evaluate(binaryNode, resultBounds);
+ }
+
+ private void loadSAR(final BinaryNode binaryNode) {
+ loadBinaryOperands(binaryNode);
+ method.sar();
}
- @Override
- public boolean enterMUL(final BinaryNode binaryNode) {
+ private void loadSHL(final BinaryNode binaryNode) {
+ loadBinaryOperands(binaryNode);
+ method.shl();
+ }
+
+ private void loadSHR(final BinaryNode binaryNode) {
+ loadBinaryOperands(binaryNode);
+ doSHR();
+ }
+
+ private void loadSUB(final BinaryNode binaryNode, final TypeBounds resultBounds) {
new BinaryArith() {
@Override
- protected void op() {
- method.mul(binaryNode.getProgramPoint());
- }
- }.evaluate(binaryNode);
-
- return false;
- }
-
- @Override
- public boolean enterNE(final BinaryNode binaryNode) {
- return enterCmp(binaryNode, Condition.NE);
- }
-
- @Override
- public boolean enterNE_STRICT(final BinaryNode binaryNode) {
- return enterCmp(binaryNode, Condition.NE);
- }
-
- @Override
- public boolean enterOR(final BinaryNode binaryNode) {
- return enterAND_OR(binaryNode);
+ protected void op(final int programPoint) {
+ method.sub(programPoint);
+ }
+ }.evaluate(binaryNode, resultBounds);
}
@Override
- public boolean enterSAR(final BinaryNode binaryNode) {
- new BinaryArith() {
- @Override
- protected void op() {
- method.sar();
- }
- }.evaluate(binaryNode);
-
- return false;
- }
-
- @Override
- public boolean enterSHL(final BinaryNode binaryNode) {
- new BinaryArith() {
- @Override
- protected void op() {
- method.shl();
- }
- }.evaluate(binaryNode);
-
- return false;
+ public boolean enterLabelNode(LabelNode labelNode) {
+ labeledBlockBreakLiveLocals.push(lc.getUsedSlotCount());
+ return true;
}
@Override
- public boolean enterSHR(final BinaryNode binaryNode) {
- new BinaryArith() {
- @Override
- protected void evaluate(final BinaryNode node) {
- loadBinaryOperands(node.lhs(), node.rhs(), Type.INT);
- op();
- method.store(node.getSymbol());
- }
- @Override
- protected void op() {
- method.shr();
- method.convert(Type.LONG).load(JSType.MAX_UINT).and();
- }
- }.evaluate(binaryNode);
-
- return false;
+ protected boolean enterDefault(Node node) {
+ throw new AssertionError("Code generator entered node of type " + node.getClass().getName());
}
- @Override
- public boolean enterSUB(final BinaryNode binaryNode) {
- new BinaryArith() {
- @Override
- protected void op() {
- method.sub(binaryNode.getProgramPoint());
- }
- }.evaluate(binaryNode);
-
- return false;
- }
-
- @Override
- public boolean enterTernaryNode(final TernaryNode ternaryNode) {
- final Expression test = ternaryNode.getTest();
- final Expression trueExpr = ternaryNode.getTrueExpression();
- final Expression falseExpr = ternaryNode.getFalseExpression();
-
- final Symbol symbol = ternaryNode.getSymbol();
- final Label falseLabel = new Label("ternary_false");
- final Label exitLabel = new Label("ternary_exit");
-
- Type widest = Type.widest(ternaryNode.getType(), Type.widest(trueExpr.getType(), falseExpr.getType()));
- if (trueExpr.getType().isArray() || falseExpr.getType().isArray()) { //loadArray creates a Java array type on the stack, calls global allocate, which creates a native array type
- widest = Type.OBJECT;
- }
-
- load(test, Type.BOOLEAN);
- // we still keep the conversion here as the AccessSpecializer can have separated the types, e.g. var y = x ? x=55 : 17
- // will left as (Object)x=55 : (Object)17 by Lower. Then the first term can be {I}x=55 of type int, which breaks the
- // symmetry for the temporary slot for this TernaryNode. This is evidence that we assign types and explicit conversions
- // too early, or Apply the AccessSpecializer too late. We are mostly probably looking for a separate type pass to
- // do this property. Then we never need any conversions in CodeGenerator
- method.ifeq(falseLabel);
- load(trueExpr, widest);
+ private void loadTernaryNode(final TernaryNode ternaryNode, final TypeBounds resultBounds) {
+ final Expression test = ternaryNode.getTest();
+ final JoinPredecessorExpression trueExpr = ternaryNode.getTrueExpression();
+ final JoinPredecessorExpression falseExpr = ternaryNode.getFalseExpression();
+
+ final Label falseLabel = new Label("ternary_false");
+ final Label exitLabel = new Label("ternary_exit");
+
+ Type outNarrowest = Type.narrowest(resultBounds.widest, Type.generic(Type.widestReturnType(trueExpr.getType(), falseExpr.getType())));
+ final TypeBounds outBounds = resultBounds.notNarrowerThan(outNarrowest);
+
+ emitBranch(test, falseLabel, false);
+
+ loadExpression(trueExpr.getExpression(), outBounds);
+ assert Type.generic(method.peekType()) == outBounds.narrowest;
+ method.beforeJoinPoint(trueExpr);
method._goto(exitLabel);
method.label(falseLabel);
- load(falseExpr, widest);
+ loadExpression(falseExpr.getExpression(), outBounds);
+ assert Type.generic(method.peekType()) == outBounds.narrowest;
+ method.beforeJoinPoint(falseExpr);
method.label(exitLabel);
- method.store(symbol);
-
- return false;
}
/**
@@ -3677,7 +3999,7 @@
private int depth;
/** If we have too many arguments, we need temporary storage, this is stored in 'quick' */
- private Symbol quick;
+ private IdentNode quick;
/**
* Constructor
@@ -3708,9 +4030,6 @@
}
private void prologue() {
- final Symbol targetSymbol = target.getSymbol();
- final Symbol scopeSymbol = lc.getCurrentFunction().compilerConstant(SCOPE);
-
/**
* This loads the parts of the target, e.g base and index. they are kept
* on the stack throughout the store and used at the end to execute it
@@ -3719,9 +4038,9 @@
target.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
@Override
public boolean enterIdentNode(final IdentNode node) {
- if (targetSymbol.isScope()) {
- method.load(scopeSymbol);
- depth++;
+ if (node.getSymbol().isScope()) {
+ method.loadCompilerConstant(SCOPE);
+ depth += Type.SCOPE.getSlots();
assert depth == 1;
}
return false;
@@ -3732,7 +4051,7 @@
final BaseNode baseNode = (BaseNode)target;
final Expression base = baseNode.getBase();
- load(base, Type.OBJECT);
+ loadExpressionAsObject(base);
depth += Type.OBJECT.getSlots();
assert depth == 1;
@@ -3754,9 +4073,9 @@
final Expression index = node.getIndex();
if (!index.getType().isNumeric()) {
// could be boolean here as well
- load(index, Type.OBJECT);
+ loadExpressionAsObject(index);
} else {
- load(index);
+ loadExpressionUnbounded(index);
}
depth += index.getType().getSlots();
@@ -3771,28 +4090,23 @@
});
}
- private Symbol quickSymbol(final Type type) {
- return quickSymbol(type, QUICK_PREFIX.symbolName());
- }
-
/**
- * Quick symbol generates an extra local variable, always using the same
- * slot, one that is available after the end of the frame.
+ * Generates an extra local variable, always using the same slot, one that is available after the end of the
+ * frame.
*
- * @param type the type of the symbol
- * @param prefix the prefix for the variable name for the symbol
+ * @param type the type of the variable
*
- * @return the quick symbol
+ * @return the quick variable
*/
- private Symbol quickSymbol(final Type type, final String prefix) {
- final String name = lc.getCurrentFunction().uniqueName(prefix);
- final Symbol symbol = new Symbol(name, IS_TEMP | IS_INTERNAL);
-
- symbol.setType(type);
-
- symbol.setSlot(lc.quickSlot(symbol));
-
- return symbol;
+ private IdentNode quickLocalVariable(final Type type) {
+ final String name = lc.getCurrentFunction().uniqueName(QUICK_PREFIX.symbolName());
+ final Symbol symbol = new Symbol(name, IS_INTERNAL | HAS_SLOT);
+ symbol.setHasSlotFor(type);
+ symbol.setFirstSlot(lc.quickSlot(type));
+
+ final IdentNode quickIdent = IdentNode.createInternalIdentifier(symbol).setType(type);
+
+ return quickIdent;
}
// store the result that "lives on" after the op, e.g. "i" in i++ postfix.
@@ -3803,16 +4117,12 @@
return;
}
- final Symbol symbol = assignNode.getSymbol();
- if (symbol.hasSlot()) {
- method.dup().store(symbol);
- return;
- }
-
if (method.dup(depth) == null) {
method.dup();
- this.quick = quickSymbol(method.peekType());
- method.store(quick);
+ final Type quickType = method.peekType();
+ this.quick = quickLocalVariable(quickType);
+ final Symbol quickSymbol = quick.getSymbol();
+ method.storeTemp(quickType, quickSymbol.getFirstSlot());
}
}
@@ -3843,8 +4153,9 @@
method.dynamicSet(node.getName(), flags);
}
} else {
- method.convert(node.getType());
- method.store(symbol);
+ final Type storeType = assignNode.getType();
+ method.convert(storeType);
+ storeIdentWithCatchConversion(node, storeType);
}
return false;
@@ -3852,7 +4163,7 @@
@Override
public boolean enterAccessNode(final AccessNode node) {
- method.dynamicSet(node.getProperty().getName(), getCallSiteFlags());
+ method.dynamicSet(node.getProperty(), getCallSiteFlags());
return false;
}
@@ -3885,6 +4196,7 @@
final int fnId = functionNode.getId();
final CompilationEnvironment env = compiler.getCompilationEnvironment();
+
final RecompilableScriptFunctionData data = env.getScriptFunctionData(fnId);
assert data != null : functionNode.getName() + " has no data";
@@ -3923,7 +4235,6 @@
} else {
method.loadNull();
}
-
method.invoke(constructorNoLookup(SCRIPTFUNCTION_IMPL_NAME, RecompilableScriptFunctionData.class, ScriptObject.class));
}
@@ -3932,10 +4243,6 @@
return method.invokestatic(GLOBAL_OBJECT, "instance", "()L" + GLOBAL_OBJECT + ';');
}
- private MethodEmitter globalObjectPrototype() {
- return method.invokestatic(GLOBAL_OBJECT, "objectPrototype", methodDescriptor(ScriptObject.class));
- }
-
private MethodEmitter globalAllocateArguments() {
return method.invokestatic(GLOBAL_OBJECT, "allocateArguments", methodDescriptor(ScriptObject.class, Object[].class, Object.class, int.class));
}
@@ -3971,26 +4278,30 @@
}
private abstract class OptimisticOperation {
- MethodEmitter emit(final Optimistic optimistic) {
- return emit(optimistic, 0);
- }
-
- MethodEmitter emit(final Optimistic optimistic, final Type desiredType) {
- return emit(optimistic, desiredType, 0);
- }
-
- MethodEmitter emit(final Optimistic optimistic, final Type desiredType, final int ignoredArgCount) {
- return emit(optimistic.isOptimistic() && !desiredType.isObject(), optimistic.getProgramPoint(), ignoredArgCount);
- }
-
- MethodEmitter emit(final Optimistic optimistic, final int ignoredArgCount) {
- return emit(optimistic.isOptimistic(), optimistic.getProgramPoint(), ignoredArgCount);
- }
-
- MethodEmitter emit(final boolean isOptimistic, final int programPoint, final int ignoredArgCount) {
+ private final boolean isOptimistic;
+ // expression and optimistic are the same reference
+ private final Expression expression;
+ private final Optimistic optimistic;
+ private final TypeBounds resultBounds;
+
+ OptimisticOperation(final Optimistic optimistic, final TypeBounds resultBounds) {
+ this.optimistic = optimistic;
+ this.expression = (Expression)optimistic;
+ this.resultBounds = resultBounds;
+ this.isOptimistic = isOptimistic(optimistic) && useOptimisticTypes() &&
+ // Operation is only effectively optimistic if its type, after being coerced into the result bounds
+ // is narrower than the upper bound.
+ resultBounds.within(Type.generic(((Expression)optimistic).getType())).narrowerThan(resultBounds.widest);
+ }
+
+ MethodEmitter emit() {
+ return emit(0);
+ }
+
+ MethodEmitter emit(final int ignoredArgCount) {
final CompilationEnvironment env = compiler.getCompilationEnvironment();
- final boolean reallyOptimistic = isOptimistic && useOptimisticTypes();
- final boolean optimisticOrContinuation = reallyOptimistic || env.isContinuationEntryPoint(programPoint);
+ final int programPoint = optimistic.getProgramPoint();
+ final boolean optimisticOrContinuation = isOptimistic || env.isContinuationEntryPoint(programPoint);
final boolean currentContinuationEntryPoint = env.isCurrentContinuationEntryPoint(programPoint);
final int stackSizeOnEntry = method.getStackSize() - ignoredArgCount;
@@ -4002,7 +4313,7 @@
// Now, load the stack
loadStack();
- // Now store the values on the stack ultimately into local variables . In vast majority of cases, this is
+ // Now store the values on the stack ultimately into local variables. In vast majority of cases, this is
// (aside from creating the local types map) a no-op, as the first opportunistic stack store will already
// store all variables. However, there can be operations in the loadStack() that invalidate some of the
// stack stores, e.g. in "x[i] = x[++i]", "++i" will invalidate the already stored value for "i". In such
@@ -4010,14 +4321,13 @@
// stored into a local variable, although at the cost of doing a store/load on the loaded arguments as well.
final int liveLocalsCount = storeStack(method.getStackSize() - stackSizeOnEntry, optimisticOrContinuation);
assert optimisticOrContinuation == (liveLocalsCount != -1);
- assert !optimisticOrContinuation || everyTypeIsKnown(method.getLocalVariableTypes(), liveLocalsCount);
final Label beginTry;
final Label catchLabel;
- final Label afterConsumeStack = reallyOptimistic || currentContinuationEntryPoint ? new Label("") : null;
- if(reallyOptimistic) {
- beginTry = new Label("");
- catchLabel = new Label("");
+ final Label afterConsumeStack = isOptimistic || currentContinuationEntryPoint ? new Label("after_consume_stack") : null;
+ if(isOptimistic) {
+ beginTry = new Label("try_optimistic");
+ catchLabel = new Label(afterConsumeStack.toString() + "_handler");
method.label(beginTry);
} else {
beginTry = catchLabel = null;
@@ -4025,32 +4335,37 @@
consumeStack();
- if(reallyOptimistic) {
+ if(isOptimistic) {
method._try(beginTry, afterConsumeStack, catchLabel, UnwarrantedOptimismException.class);
}
- if(reallyOptimistic || currentContinuationEntryPoint) {
+ if(isOptimistic || currentContinuationEntryPoint) {
method.label(afterConsumeStack);
final int[] localLoads = method.getLocalLoadsOnStack(0, stackSizeOnEntry);
assert everyStackValueIsLocalLoad(localLoads) : Arrays.toString(localLoads) + ", " + stackSizeOnEntry + ", " + ignoredArgCount;
final List<Type> localTypesList = method.getLocalVariableTypes();
- final int usedLocals = getUsedSlotsWithLiveTemporaries(localTypesList, localLoads);
- final Type[] localTypes = localTypesList.subList(0, usedLocals).toArray(new Type[usedLocals]);
- assert everyLocalLoadIsValid(localLoads, usedLocals) : Arrays.toString(localLoads) + " ~ " + Arrays.toString(localTypes);
-
- if(reallyOptimistic) {
+ final int usedLocals = method.getUsedSlotsWithLiveTemporaries();
+ final List<Type> localTypes = method.getWidestLiveLocals(localTypesList.subList(0, usedLocals));
+ assert everyLocalLoadIsValid(localLoads, usedLocals) : Arrays.toString(localLoads) + " ~ " + localTypes;
+
+ if(isOptimistic) {
addUnwarrantedOptimismHandlerLabel(localTypes, catchLabel);
}
if(currentContinuationEntryPoint) {
final ContinuationInfo ci = getContinuationInfo();
assert !ci.hasTargetLabel(); // No duplicate program points
ci.setTargetLabel(afterConsumeStack);
- ci.setLocalVariableTypes(localTypes);
+ ci.getHandlerLabel().markAsOptimisticContinuationHandlerFor(afterConsumeStack);
+ // Can't rely on targetLabel.stack.localVariableTypes.length, as it can be higher due to effectively
+ // dead local variables.
+ ci.lvarCount = localTypes.size();
ci.setStackStoreSpec(localLoads);
ci.setStackTypes(Arrays.copyOf(method.getTypesFromStack(method.getStackSize()), stackSizeOnEntry));
assert ci.getStackStoreSpec().length == ci.getStackTypes().length;
ci.setReturnValueType(method.peekType());
+ ci.lineNumber = getLastLineNumber();
+ ci.catchLabel = catchLabels.peek();
}
}
return method;
@@ -4070,7 +4385,7 @@
* a label for a catch block for the {@code UnwarantedOptimizationException}, suitable for capturing the
* currently live local variables, tailored to their types.
*/
- private final int storeStack(final int ignoreArgCount, final boolean optimisticOrContinuation) {
+ private int storeStack(final int ignoreArgCount, final boolean optimisticOrContinuation) {
if(!optimisticOrContinuation) {
return -1; // NOTE: correct value to return is lc.getUsedSlotCount(), but it wouldn't be used anyway
}
@@ -4078,7 +4393,7 @@
final int stackSize = method.getStackSize();
final Type[] stackTypes = method.getTypesFromStack(stackSize);
final int[] localLoadsOnStack = method.getLocalLoadsOnStack(0, stackSize);
- final int usedSlots = getUsedSlotsWithLiveTemporaries(method.getLocalVariableTypes(), localLoadsOnStack);
+ final int usedSlots = method.getUsedSlotsWithLiveTemporaries();
final int firstIgnored = stackSize - ignoreArgCount;
// Find the first value on the stack (from the bottom) that is not a load from a local variable.
@@ -4116,7 +4431,7 @@
if(i >= firstIgnored) {
ignoreSlotCount += slots;
}
- method.store(type, lastTempSlot);
+ method.storeTemp(type, lastTempSlot);
} else {
method.pop();
}
@@ -4158,7 +4473,7 @@
return lastTempSlot - ignoreSlotCount;
}
- private void addUnwarrantedOptimismHandlerLabel(final Type[] localTypes, final Label label) {
+ private void addUnwarrantedOptimismHandlerLabel(final List<Type> localTypes, final Label label) {
final String lvarTypesDescriptor = getLvarTypesDescriptor(localTypes);
final Map<String, Collection<Label>> unwarrantedOptimismHandlers = lc.getUnwarrantedOptimismHandlers();
Collection<Label> labels = unwarrantedOptimismHandlers.get(lvarTypesDescriptor);
@@ -4166,36 +4481,140 @@
labels = new LinkedList<>();
unwarrantedOptimismHandlers.put(lvarTypesDescriptor, labels);
}
+ method.markLabelAsOptimisticCatchHandler(label, localTypes.size());
labels.add(label);
}
- /**
- * Returns the number of used local variable slots, including all live stack-store temporaries.
- * @param localVariableTypes the current local variable types
- * @param localLoadsOnStack the current local variable loads on the stack
- * @return the number of used local variable slots, including all live stack-store temporaries.
- */
- private final int getUsedSlotsWithLiveTemporaries(final List<Type> localVariableTypes, final int[] localLoadsOnStack) {
- // There are at least as many as are declared by the current blocks.
- int usedSlots = lc.getUsedSlotCount();
- // Look at every load on the stack, and bump the number of used slots up by the temporaries seen there.
- for (final int slot : localLoadsOnStack) {
- if(slot != Label.Stack.NON_LOAD) {
- final int afterSlot = slot + localVariableTypes.get(slot).getSlots();
- if(afterSlot > usedSlots) {
- usedSlots = afterSlot;
- }
- }
- }
- return usedSlots;
- }
-
abstract void loadStack();
// Make sure that whatever indy call site you emit from this method uses {@code getCallSiteFlagsOptimistic(node)}
// or otherwise ensure optimistic flag is correctly set in the call site, otherwise it doesn't make much sense
// to use OptimisticExpression for emitting it.
abstract void consumeStack();
+
+ /**
+ * Emits the correct dynamic getter code. Normally just delegates to method emitter, except when the target
+ * expression is optimistic, and the desired type is narrower than the optimistic type. In that case, it'll emit a
+ * dynamic getter with its original optimistic type, and explicitly insert a narrowing conversion. This way we can
+ * preserve the optimism of the values even if they're subsequently immediately coerced into a narrower type. This
+ * is beneficial because in this case we can still presume that since the original getter was optimistic, the
+ * conversion has no side effects.
+ * @param name the name of the property being get
+ * @param flags call site flags
+ * @param isMethod whether we're preferrably retrieving a function
+ * @return the current method emitter
+ */
+ MethodEmitter dynamicGet(final String name, final int flags, final boolean isMethod) {
+ if(isOptimistic) {
+ return method.dynamicGet(getOptimisticCoercedType(), name, getOptimisticFlags(flags), isMethod);
+ }
+ return method.dynamicGet(resultBounds.within(expression.getType()), name, nonOptimisticFlags(flags), isMethod);
+ }
+
+ MethodEmitter dynamicGetIndex(final int flags, final boolean isMethod) {
+ if(isOptimistic) {
+ return method.dynamicGetIndex(getOptimisticCoercedType(), getOptimisticFlags(flags), isMethod);
+ }
+ return method.dynamicGetIndex(resultBounds.within(expression.getType()), nonOptimisticFlags(flags), isMethod);
+ }
+
+ MethodEmitter dynamicCall(final int argCount, final int flags) {
+ if (isOptimistic) {
+ return method.dynamicCall(getOptimisticCoercedType(), argCount, getOptimisticFlags(flags));
+ }
+ return method.dynamicCall(resultBounds.within(expression.getType()), argCount, nonOptimisticFlags(flags));
+ }
+
+ int getOptimisticFlags(final int flags) {
+ return flags | CALLSITE_OPTIMISTIC | (optimistic.getProgramPoint() << CALLSITE_PROGRAM_POINT_SHIFT); //encode program point in high bits
+ }
+
+ int getProgramPoint() {
+ return isOptimistic ? optimistic.getProgramPoint() : INVALID_PROGRAM_POINT;
+ }
+
+ void convertOptimisticReturnValue() {
+ if (isOptimistic) {
+ final Type optimisticType = getOptimisticCoercedType();
+ if(!optimisticType.isObject()) {
+ method.load(optimistic.getProgramPoint());
+ if(optimisticType.isInteger()) {
+ method.invoke(ENSURE_INT);
+ } else if(optimisticType.isLong()) {
+ method.invoke(ENSURE_LONG);
+ } else if(optimisticType.isNumber()) {
+ method.invoke(ENSURE_NUMBER);
+ } else {
+ throw new AssertionError(optimisticType);
+ }
+ }
+ }
+ }
+
+ void replaceCompileTimeProperty() {
+ final IdentNode identNode = (IdentNode)expression;
+ final String name = identNode.getSymbol().getName();
+ if (CompilerConstants.__FILE__.name().equals(name)) {
+ replaceCompileTimeProperty(getCurrentSource().getName());
+ } else if (CompilerConstants.__DIR__.name().equals(name)) {
+ replaceCompileTimeProperty(getCurrentSource().getBase());
+ } else if (CompilerConstants.__LINE__.name().equals(name)) {
+ replaceCompileTimeProperty(getCurrentSource().getLine(identNode.position()));
+ }
+ }
+
+ /**
+ * When an ident with name __FILE__, __DIR__, or __LINE__ is loaded, we'll try to look it up as any other
+ * identifier. However, if it gets all the way up to the Global object, it will send back a special value that
+ * represents a placeholder for these compile-time location properties. This method will generate code that loads
+ * the value of the compile-time location property and then invokes a method in Global that will replace the
+ * placeholder with the value. Effectively, if the symbol for these properties is defined anywhere in the lexical
+ * scope, they take precedence, but if they aren't, then they resolve to the compile-time location property.
+ * @param propertyValue the actual value of the property
+ */
+ private void replaceCompileTimeProperty(final Object propertyValue) {
+ assert method.peekType().isObject();
+ if(propertyValue instanceof String) {
+ method.load((String)propertyValue);
+ } else if(propertyValue instanceof Integer) {
+ method.load(((Integer)propertyValue).intValue());
+ method.convert(Type.OBJECT);
+ } else {
+ throw new AssertionError();
+ }
+ globalReplaceLocationPropertyPlaceholder();
+ convertOptimisticReturnValue();
+ }
+
+ /**
+ * Returns the type that should be used as the return type of the dynamic invocation that is emitted as the code
+ * for the current optimistic operation. If the type bounds is exact boolean or narrower than the expression's
+ * optimistic type, then the optimistic type is returned, otherwise the coercing type. Effectively, this method
+ * allows for moving the coercion into the optimistic type when it won't adversely affect the optimistic
+ * evaluation semantics, and for preserving the optimistic type and doing a separate coercion when it would
+ * affect it.
+ * @return
+ */
+ private Type getOptimisticCoercedType() {
+ final Type optimisticType = expression.getType();
+ assert resultBounds.widest.widerThan(optimisticType);
+ final Type narrowest = resultBounds.narrowest;
+
+ if(narrowest.isBoolean() || narrowest.narrowerThan(optimisticType)) {
+ assert !optimisticType.isObject();
+ return optimisticType;
+ }
+ assert !narrowest.isObject();
+ return narrowest;
+ }
+ }
+
+ private static boolean isOptimistic(final Optimistic optimistic) {
+ if(!optimistic.canBeOptimistic()) {
+ return false;
+ }
+ final Expression expr = (Expression)optimistic;
+ return expr.getType().narrowerThan(expr.getWidestOperationType());
}
private static boolean everyLocalLoadIsValid(final int[] loads, final int localCount) {
@@ -4207,18 +4626,6 @@
return true;
}
- private static boolean everyTypeIsKnown(final List<Type> types, final int liveLocalsCount) {
- assert types instanceof RandomAccess;
- for(int i = 0; i < liveLocalsCount;) {
- final Type t = types.get(i);
- if(t == Type.UNKNOWN) {
- return false;
- }
- i += t.getSlots();
- }
- return true;
- }
-
private static boolean everyStackValueIsLocalLoad(final int[] loads) {
for (final int load : loads) {
if(load == Label.Stack.NON_LOAD) {
@@ -4228,20 +4635,13 @@
return true;
}
- private static String getLvarTypesDescriptor(final Type[] localVarTypes) {
- final StringBuilder desc = new StringBuilder(localVarTypes.length);
- for(int i = 0; i < localVarTypes.length;) {
- i += appendType(desc, localVarTypes[i]);
- }
- // Trailing unknown types are unnecessary. (These don't actually occur though as long as we conservatively
- // force-initialize all potentially-top values.)
- for(int l = desc.length(); l-- > 0;) {
- if(desc.charAt(l) != 'U') {
- desc.setLength(l + 1);
- break;
- }
- }
- return desc.toString();
+ private String getLvarTypesDescriptor(final List<Type> localVarTypes) {
+ final int count = localVarTypes.size();
+ final StringBuilder desc = new StringBuilder(count);
+ for(int i = 0; i < count;) {
+ i += appendType(desc, localVarTypes.get(i));
+ }
+ return method.markSymbolBoundariesInLvarTypesDescriptor(desc.toString());
}
private static int appendType(final StringBuilder b, final Type t) {
@@ -4249,6 +4649,16 @@
return t.getSlots();
}
+ private static int countSymbolsInLvarTypeDescriptor(final String lvarTypeDescriptor) {
+ int count = 0;
+ for(int i = 0; i < lvarTypeDescriptor.length(); ++i) {
+ if(Character.isUpperCase(lvarTypeDescriptor.charAt(i))) {
+ ++count;
+ }
+ }
+ return count;
+
+ }
/**
* Generates all the required {@code UnwarrantedOptimismException} handlers for the current function. The employed
* strategy strives to maximize code reuse. Every handler constructs an array to hold the local variables, then
@@ -4270,6 +4680,9 @@
if(unwarrantedOptimismHandlers.isEmpty()) {
return false;
}
+
+ method.lineNumber(0);
+
final List<OptimismExceptionHandlerSpec> handlerSpecs = new ArrayList<>(unwarrantedOptimismHandlers.size() * 4/3);
for(final String spec: unwarrantedOptimismHandlers.keySet()) {
handlerSpecs.add(new OptimismExceptionHandlerSpec(spec, true));
@@ -4285,10 +4698,12 @@
final OptimismExceptionHandlerSpec spec = handlerSpecs.get(handlerIndex);
final String lvarSpec = spec.lvarSpec;
if(spec.catchTarget) {
+ assert !method.isReachable();
// Start a catch block and assign the labels for this lvarSpec with it.
method._catch(unwarrantedOptimismHandlers.get(lvarSpec));
- // This spec is a catch target, so emit array creation code
- method.load(spec.lvarSpec.length());
+ // This spec is a catch target, so emit array creation code. The length of the array is the number of
+ // symbols - the number of uppercase characters.
+ method.load(countSymbolsInLvarTypeDescriptor(lvarSpec));
method.newarray(Type.OBJECT_ARRAY);
}
if(spec.delegationTarget) {
@@ -4301,11 +4716,13 @@
int lvarIndex;
final int firstArrayIndex;
+ final int firstLvarIndex;
Label delegationLabel;
final String commonLvarSpec;
if(lastHandler) {
// Last handler block, doesn't delegate to anything.
lvarIndex = 0;
+ firstLvarIndex = 0;
firstArrayIndex = 0;
delegationLabel = null;
commonLvarSpec = null;
@@ -4319,6 +4736,8 @@
final int nextHandlerIndex = handlerIndex + 1;
final String nextLvarSpec = handlerSpecs.get(nextHandlerIndex).lvarSpec;
commonLvarSpec = commonPrefix(lvarSpec, nextLvarSpec);
+ // We don't chop symbols in half
+ assert Character.isUpperCase(commonLvarSpec.charAt(commonLvarSpec.length() - 1));
// Let's find if we already have a declaration for such handler, or we need to insert it.
{
@@ -4345,12 +4764,12 @@
}
}
- // Calculate the local variable index at the end of the common prefix
- firstArrayIndex = commonLvarSpec.length();
+ firstArrayIndex = countSymbolsInLvarTypeDescriptor(commonLvarSpec);
lvarIndex = 0;
- for(int j = 0; j < firstArrayIndex; ++j) {
+ for(int j = 0; j < commonLvarSpec.length(); ++j) {
lvarIndex += CodeGeneratorLexicalContext.getTypeForSlotDescriptor(commonLvarSpec.charAt(j)).getSlots();
}
+ firstLvarIndex = lvarIndex;
// Create a delegation label if not already present
delegationLabel = delegationLabels.get(commonLvarSpec);
@@ -4363,27 +4782,54 @@
// Load local variables handled by this handler on stack
int args = 0;
- for(int arrayIndex = firstArrayIndex; arrayIndex < lvarSpec.length(); ++arrayIndex) {
- final Type lvarType = CodeGeneratorLexicalContext.getTypeForSlotDescriptor(lvarSpec.charAt(arrayIndex));
+ boolean symbolHadValue = false;
+ for(int typeIndex = commonLvarSpec == null ? 0 : commonLvarSpec.length(); typeIndex < lvarSpec.length(); ++typeIndex) {
+ final char typeDesc = lvarSpec.charAt(typeIndex);
+ final Type lvarType = CodeGeneratorLexicalContext.getTypeForSlotDescriptor(typeDesc);
if (!lvarType.isUnknown()) {
method.load(lvarType, lvarIndex);
+ symbolHadValue = true;
args++;
+ } else if(typeDesc == 'U' && !symbolHadValue) {
+ // Symbol boundary with undefined last value. Check if all previous values for this symbol were also
+ // undefined; if so, emit one explicit Undefined. This serves to ensure that we're emiting exactly
+ // one value for every symbol that uses local slots. While we could in theory ignore symbols that
+ // are undefined (in other words, dead) at the point where this exception was thrown, unfortunately
+ // we can't do it in practice. The reason for this is that currently our liveness analysis is
+ // coarse (it can determine whether a symbol has not been read with a particular type anywhere in
+ // the function being compiled, but that's it), and a symbol being promoted to Object due to a
+ // deoptimization will suddenly show up as "live for Object type", and previously dead U->O
+ // conversions on loop entries will suddenly become alive in the deoptimized method which will then
+ // expect a value for that slot in its continuation handler. If we had precise liveness analysis, we
+ // could go back to excluding known dead symbols from the payload of the RewriteException.
+ if(method.peekType() == Type.UNDEFINED) {
+ method.dup();
+ } else {
+ method.loadUndefined(Type.OBJECT);
+ }
+ args++;
+ }
+ if(Character.isUpperCase(typeDesc)) {
+ // Reached symbol boundary; reset flag for the next symbol.
+ symbolHadValue = false;
}
lvarIndex += lvarType.getSlots();
}
- // Delegate actual storing into array to an array populator utility method. These are reused within a
- // compilation unit.
+ assert args > 0;
+ // Delegate actual storing into array to an array populator utility method.
//on the stack:
// object array to be populated
// start index
// a lot of types
method.dynamicArrayPopulatorCall(args + 1, firstArrayIndex);
-
if(delegationLabel != null) {
// We cascade to a prefix handler to fill out the rest of the local variables and throw the
// RewriteException.
assert !lastHandler;
assert commonLvarSpec != null;
+ // Must undefine the local variables that we have already processed for the sake of correct join on the
+ // delegate label
+ method.undefineLocalVariables(firstLvarIndex, true);
final OptimismExceptionHandlerSpec nextSpec = handlerSpecs.get(handlerIndex + 1);
// If the delegate immediately follows, and it's not a catch target (so it doesn't have array setup
// code) don't bother emitting a jump, as we'd just jump to the next instruction.
@@ -4401,11 +4847,6 @@
method.dup(2);
method.pop();
loadConstant(getByteCodeSymbolNames(fn));
- if (fn.compilerConstant(SCOPE).hasSlot()) {
- method.loadCompilerConstant(SCOPE);
- } else {
- method.loadNull();
- }
final CompilationEnvironment env = compiler.getCompilationEnvironment();
if (env.isCompileRestOf()) {
loadConstant(env.getContinuationEntryPoints());
@@ -4413,7 +4854,6 @@
} else {
method.invoke(INIT_REWRITE_EXCEPTION);
}
-
method.athrow();
}
}
@@ -4443,9 +4883,13 @@
private static String commonPrefix(final String s1, final String s2) {
final int l1 = s1.length();
final int l = Math.min(l1, s2.length());
+ int lms = -1; // last matching symbol
for(int i = 0; i < l; ++i) {
- if(s1.charAt(i) != s2.charAt(i)) {
- return s1.substring(0, i);
+ final char c1 = s1.charAt(i);
+ if(c1 != s2.charAt(i)) {
+ return s1.substring(0, lms + 1);
+ } else if(Character.isUpperCase(c1)) {
+ lms = i;
}
}
return l == l1 ? s1 : s2;
@@ -4485,8 +4929,7 @@
private static class ContinuationInfo {
private final Label handlerLabel;
private Label targetLabel; // Label for the target instruction.
- // Types the local variable slots have to have when this node completes
- private Type[] localVariableTypes;
+ int lvarCount;
// Indices of local variables that need to be loaded on the stack when this node completes
private int[] stackStoreSpec;
// Types of values loaded on the stack
@@ -4497,6 +4940,12 @@
private PropertyMap objectLiteralMap;
// Object literal stack depth for object literal - not necessarly top if property is a tree
private int objectLiteralStackDepth = -1;
+ // The line number at the continuation point
+ private int lineNumber;
+ // The active catch label, in case the continuation point is in a try/catch block
+ private Label catchLabel;
+ // The number of scopes that need to be popped before control is transferred to the catch label.
+ private int exceptionScopePops;
ContinuationInfo() {
this.handlerLabel = new Label("continuation_handler");
@@ -4518,14 +4967,6 @@
this.targetLabel = targetLabel;
}
- Type[] getLocalVariableTypes() {
- return localVariableTypes.clone();
- }
-
- void setLocalVariableTypes(final Type[] localVariableTypes) {
- this.localVariableTypes = localVariableTypes;
- }
-
int[] getStackStoreSpec() {
return stackStoreSpec.clone();
}
@@ -4568,7 +5009,7 @@
@Override
public String toString() {
- return "[localVariableTypes=" + Arrays.toString(localVariableTypes) + ", stackStoreSpec=" +
+ return "[localVariableTypes=" + targetLabel.getStack().getLocalVariableTypesCopy() + ", stackStoreSpec=" +
Arrays.toString(stackStoreSpec) + ", returnValueType=" + returnValueType + "]";
}
}
@@ -4589,39 +5030,76 @@
// Nashorn has a bug), then line number 0 will be an indication of where it came from (line numbers are Uint16).
method.lineNumber(0);
- final Type[] lvarTypes = ci.getLocalVariableTypes();
- final int lvarCount = lvarTypes.length;
+ final Label.Stack stack = ci.getTargetLabel().getStack();
+ final List<Type> lvarTypes = stack.getLocalVariableTypesCopy();
+ final BitSet symbolBoundary = stack.getSymbolBoundaryCopy();
+ final int lvarCount = ci.lvarCount;
final Type rewriteExceptionType = Type.typeFor(RewriteException.class);
+ // Store the RewriteException into an unused local variable slot.
method.load(rewriteExceptionType, 0);
- method.dup();
+ method.storeTemp(rewriteExceptionType, lvarCount);
// Get local variable array
+ method.load(rewriteExceptionType, 0);
method.invoke(RewriteException.GET_BYTECODE_SLOTS);
- // Store local variables
- for(int lvarIndex = 0, arrayIndex = 0; lvarIndex < lvarCount; ++arrayIndex) {
- final Type lvarType = lvarTypes[lvarIndex];
+ // Store local variables. Note that deoptimization might introduce new value types for existing local variables,
+ // so we must use both liveLocals and symbolBoundary, as in some cases (when the continuation is inside of a try
+ // block) we need to store the incoming value into multiple slots. The optimism exception handlers will have
+ // exactly one array element for every symbol that uses bytecode storage. If in the originating method the value
+ // was undefined, there will be an explicit Undefined value in the array.
+ int arrayIndex = 0;
+ for(int lvarIndex = 0; lvarIndex < lvarCount;) {
+ final Type lvarType = lvarTypes.get(lvarIndex);
+ if(!lvarType.isUnknown()) {
+ method.dup();
+ method.load(arrayIndex).arrayload();
+ final Class<?> typeClass = lvarType.getTypeClass();
+ // Deoptimization in array initializers can cause arrays to undergo component type widening
+ if(typeClass == long[].class) {
+ method.load(rewriteExceptionType, lvarCount);
+ method.invoke(RewriteException.TO_LONG_ARRAY);
+ } else if(typeClass == double[].class) {
+ method.load(rewriteExceptionType, lvarCount);
+ method.invoke(RewriteException.TO_DOUBLE_ARRAY);
+ } else if(typeClass == Object[].class) {
+ method.load(rewriteExceptionType, lvarCount);
+ method.invoke(RewriteException.TO_OBJECT_ARRAY);
+ } else {
+ if(!(typeClass.isPrimitive() || typeClass == Object.class)) {
+ // NOTE: this can only happen with dead stores. E.g. for the program "1; []; f();" in which the
+ // call to f() will deoptimize the call site, but it'll expect :return to have the type
+ // NativeArray. However, in the more optimal version, :return's only live type is int, therefore
+ // "{O}:return = []" is a dead store, and the variable will be sent into the continuation as
+ // Undefined, however NativeArray can't hold Undefined instance.
+ method.loadType(Type.getInternalName(typeClass));
+ method.invoke(RewriteException.INSTANCE_OR_NULL);
+ }
+ method.convert(lvarType);
+ }
+ method.storeHidden(lvarType, lvarIndex, false);
+ }
final int nextLvarIndex = lvarIndex + lvarType.getSlots();
- if(nextLvarIndex < lvarCount) {
- // keep local variable array on the stack unless this is the last lvar
- method.dup();
- }
- method.load(arrayIndex).arrayload();
- method.convert(lvarType);
- method.store(lvarType, lvarIndex);
+ if(symbolBoundary.get(nextLvarIndex - 1)) {
+ ++arrayIndex;
+ }
lvarIndex = nextLvarIndex;
}
+ if(assertsEnabled) {
+ method.load(arrayIndex);
+ method.invoke(RewriteException.ASSERT_ARRAY_LENGTH);
+ } else {
+ method.pop();
+ }
final int[] stackStoreSpec = ci.getStackStoreSpec();
final Type[] stackTypes = ci.getStackTypes();
final boolean isStackEmpty = stackStoreSpec.length == 0;
if(!isStackEmpty) {
- // Store the RewriteException into an unused local variable slot.
- method.store(rewriteExceptionType, lvarCount);
// Load arguments on the stack
final int objectLiteralStackDepth = ci.getObjectLiteralStackDepth();
for(int i = 0; i < stackStoreSpec.length; ++i) {
final int slot = stackStoreSpec[i];
- method.load(lvarTypes[slot], slot);
+ method.load(lvarTypes.get(slot), slot);
method.convert(stackTypes[i]);
// stack: s0=object literal being initialized
// change map of s0 so that the property we are initilizing when we failed
@@ -4634,18 +5112,60 @@
method.invoke(ScriptObject.SET_MAP);
}
}
-
- // Load RewriteException back; get rid of the stored reference.
- method.load(Type.OBJECT, lvarCount);
- method.loadNull();
- method.store(Type.OBJECT, lvarCount);
- }
+ }
+
+ // Load RewriteException back.
+ method.load(rewriteExceptionType, lvarCount);
+ // Get rid of the stored reference
+ method.loadNull();
+ method.storeHidden(Type.OBJECT, lvarCount);
+ // Mark it dead
+ method.markDeadSlots(lvarCount, Type.OBJECT.getSlots());
// Load return value on the stack
method.invoke(RewriteException.GET_RETURN_VALUE);
- method.convert(ci.getReturnValueType());
+
+ final Type returnValueType = ci.getReturnValueType();
+
+ // Set up an exception handler for primitive type conversion of return value if needed
+ boolean needsCatch = false;
+ final Label targetCatchLabel = ci.catchLabel;
+ Label _try = null;
+ if(returnValueType.isPrimitive()) {
+ // If the conversion throws an exception, we want to report the line number of the continuation point.
+ method.lineNumber(ci.lineNumber);
+
+ if(targetCatchLabel != METHOD_BOUNDARY) {
+ _try = new Label("");
+ method.label(_try);
+ needsCatch = true;
+ }
+ }
+
+ // Convert return value
+ method.convert(returnValueType);
+
+ final int scopePopCount = needsCatch ? ci.exceptionScopePops : 0;
+
+ // Declare a try/catch for the conversion. If no scopes need to be popped until the target catch block, just
+ // jump into it. Otherwise, we'll need to create a scope-popping catch block below.
+ final Label catchLabel = scopePopCount > 0 ? new Label("") : targetCatchLabel;
+ if(needsCatch) {
+ final Label _end_try = new Label("");
+ method.label(_end_try);
+ method._try(_try, _end_try, catchLabel);
+ }
// Jump to continuation point
method._goto(ci.getTargetLabel());
+
+ // Make a scope-popping exception delegate if needed
+ if(catchLabel != targetCatchLabel) {
+ method.lineNumber(0);
+ assert scopePopCount > 0;
+ method._catch(catchLabel);
+ popScopes(scopePopCount);
+ method.uncheckedGoto(targetCatchLabel);
+ }
}
}
--- a/nashorn/src/jdk/nashorn/internal/codegen/CodeGeneratorLexicalContext.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CodeGeneratorLexicalContext.java Tue May 13 11:30:40 2014 +0200
@@ -31,7 +31,6 @@
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.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 @@
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 @@
}
assert splitNodes.peek() == 0;
splitNodes.pop();
- } else if (node instanceof SplitNode) {
- exitSplitNode();
}
return popped;
}
@@ -208,60 +204,67 @@
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 @@
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());
}
-
}
--- a/nashorn/src/jdk/nashorn/internal/codegen/CompilationEnvironment.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CompilationEnvironment.java Tue May 13 11:30:40 2014 +0200
@@ -34,7 +34,6 @@
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.Optimistic;
import jdk.nashorn.internal.objects.NativeArray;
import jdk.nashorn.internal.runtime.Context;
+import jdk.nashorn.internal.runtime.FindProperty;
import jdk.nashorn.internal.runtime.JSType;
-import jdk.nashorn.internal.runtime.FindProperty;
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 @@
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 @@
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 @@
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 @@
}
// 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 @@
if(!(base instanceof ScriptObject)) {
return null;
}
- return evaluatePropertySafely((ScriptObject)base, accessNode.getProperty().getName());
+ return evaluatePropertySafely((ScriptObject)base, accessNode.getProperty());
}
return null;
}
--- a/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java Tue May 13 11:30:40 2014 +0200
@@ -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 @@
}
},
- /**
- * 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 @@
}
},
+ 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 @@
} 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()) {
--- a/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java Tue May 13 11:30:40 2014 +0200
@@ -58,7 +58,6 @@
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 @@
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 @@
return installer;
}
- TemporarySymbols getTemporarySymbols() {
- return temporarySymbols;
- }
-
void addClass(final String name, final byte[] code) {
bytecode.put(name, code);
}
--- a/nashorn/src/jdk/nashorn/internal/codegen/CompilerConstants.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CompilerConstants.java Tue May 13 11:30:40 2014 +0200
@@ -29,8 +29,9 @@
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 @@
*/
public enum CompilerConstants {
-
/** the __FILE__ variable */
__FILE__,
@@ -87,7 +87,7 @@
* 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 @@
/** 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 @@
}
}
+ private static Set<String> symbolNames;
+
/**
* Prefix used for internal methods generated in script clases.
*/
@@ -223,12 +225,17 @@
* @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;
}
/**
--- a/nashorn/src/jdk/nashorn/internal/codegen/FinalizeTypes.java Mon May 05 14:17:20 2014 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -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;
- }
-
-
-}
--- a/nashorn/src/jdk/nashorn/internal/codegen/FindScopeDepths.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/FindScopeDepths.java Tue May 13 11:30:40 2014 +0200
@@ -34,15 +34,14 @@
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 @@
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 @@
@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);
--- a/nashorn/src/jdk/nashorn/internal/codegen/Label.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Label.java Tue May 13 11:30:40 2014 +0200
@@ -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 @@
//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;
+ List<Type> localVariableTypes;
+ int firstTemp; // index of the first temporary local variable
+ // Bitmap marking last slot belonging to a single symbol.
+ BitSet symbolBoundary;
+
Stack() {
- }
-
- 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);
+ data = new Type[8];
+ localLoads = new int[8];
+ localVariableTypes = new ArrayList<>(8);
+ symbolBoundary = new BitSet();
}
boolean isEmpty() {
@@ -64,18 +70,6 @@
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 @@
}
/**
- * 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.
+ */
+ 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 mergeLocalLoads(final Stack other) {
- final int[] otherLoads = other.localLoads;
+ 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 @@
}
/**
- * 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;
}
}
}
+ /**
+ * 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);
+ }
+ }
+ }
+
+ /**
+ * 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() {
- final StringBuilder builder = new StringBuilder("[");
- for (int i = 0; i < sp; i++) {
- builder.append(data[i]);
- if (i < sp - 1) {
- builder.append(", ");
- }
- }
- return builder.append("]").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 @@
/** 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 @@
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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java Tue May 13 11:30:40 2014 +0200
@@ -0,0 +1,1440 @@
+/*
+ * 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.ir.Expression.isAlwaysFalse;
+import static jdk.nashorn.internal.ir.Expression.isAlwaysTrue;
+
+import java.util.ArrayDeque;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import jdk.nashorn.internal.codegen.types.Type;
+import jdk.nashorn.internal.ir.AccessNode;
+import jdk.nashorn.internal.ir.BaseNode;
+import jdk.nashorn.internal.ir.BinaryNode;
+import jdk.nashorn.internal.ir.Block;
+import jdk.nashorn.internal.ir.BreakNode;
+import jdk.nashorn.internal.ir.BreakableNode;
+import jdk.nashorn.internal.ir.CaseNode;
+import jdk.nashorn.internal.ir.CatchNode;
+import jdk.nashorn.internal.ir.ContinueNode;
+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.IfNode;
+import jdk.nashorn.internal.ir.IndexNode;
+import jdk.nashorn.internal.ir.JoinPredecessor;
+import jdk.nashorn.internal.ir.JoinPredecessorExpression;
+import jdk.nashorn.internal.ir.JumpStatement;
+import jdk.nashorn.internal.ir.LabelNode;
+import jdk.nashorn.internal.ir.LexicalContext;
+import jdk.nashorn.internal.ir.LexicalContextNode;
+import jdk.nashorn.internal.ir.LiteralNode;
+import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
+import jdk.nashorn.internal.ir.LocalVariableConversion;
+import jdk.nashorn.internal.ir.LoopNode;
+import jdk.nashorn.internal.ir.Node;
+import jdk.nashorn.internal.ir.PropertyNode;
+import jdk.nashorn.internal.ir.ReturnNode;
+import jdk.nashorn.internal.ir.RuntimeNode;
+import jdk.nashorn.internal.ir.RuntimeNode.Request;
+import jdk.nashorn.internal.ir.SplitNode;
+import jdk.nashorn.internal.ir.SwitchNode;
+import jdk.nashorn.internal.ir.Symbol;
+import jdk.nashorn.internal.ir.TernaryNode;
+import jdk.nashorn.internal.ir.ThrowNode;
+import jdk.nashorn.internal.ir.TryNode;
+import jdk.nashorn.internal.ir.UnaryNode;
+import jdk.nashorn.internal.ir.VarNode;
+import jdk.nashorn.internal.ir.WhileNode;
+import jdk.nashorn.internal.ir.visitor.NodeVisitor;
+import jdk.nashorn.internal.parser.Token;
+import jdk.nashorn.internal.parser.TokenType;
+
+/**
+ * Calculates types for local variables. For purposes of local variable type calculation, the only types used are
+ * Undefined, boolean, int, long, double, and Object. The calculation eagerly widens types of local variable to their
+ * widest at control flow join points.
+ * TODO: investigate a more sophisticated solution that uses use/def information to only widens the type of a local
+ * variable to its widest used type after the join point. That would eliminate some widenings of undefined variables to
+ * object, most notably those used only in loops. We need a full liveness analysis for that. Currently, we can establish
+ * per-type liveness, which eliminates most of unwanted dead widenings.
+ */
+final class LocalVariableTypesCalculator extends NodeVisitor<LexicalContext>{
+
+ private static class JumpOrigin {
+ final JoinPredecessor node;
+ final Map<Symbol, LvarType> types;
+
+ JumpOrigin(final JoinPredecessor node, final Map<Symbol, LvarType> types) {
+ this.node = node;
+ this.types = types;
+ }
+ }
+
+ private static class JumpTarget {
+ private List<JumpOrigin> origins = new LinkedList<>();
+ private Map<Symbol, LvarType> types = Collections.emptyMap();
+
+ void addOrigin(final JoinPredecessor originNode, final Map<Symbol, LvarType> originTypes) {
+ origins.add(new JumpOrigin(originNode, originTypes));
+ this.types = getUnionTypes(this.types, originTypes);
+ }
+ }
+ private enum LvarType {
+ UNDEFINED(Type.UNDEFINED),
+ BOOLEAN(Type.BOOLEAN),
+ INT(Type.INT),
+ LONG(Type.LONG),
+ DOUBLE(Type.NUMBER),
+ OBJECT(Type.OBJECT);
+
+ private final Type type;
+ private LvarType(final Type type) {
+ this.type = type;
+ }
+ }
+
+ private static final Map<Type, LvarType> TO_LVAR_TYPE = new IdentityHashMap<>();
+
+ static {
+ for(final LvarType lvarType: LvarType.values()) {
+ TO_LVAR_TYPE.put(lvarType.type, lvarType);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static IdentityHashMap<Symbol, LvarType> cloneMap(final Map<Symbol, LvarType> map) {
+ return (IdentityHashMap<Symbol, LvarType>)((IdentityHashMap<?,?>)map).clone();
+ }
+
+ private LocalVariableConversion createConversion(final Symbol symbol, final LvarType branchLvarType,
+ final Map<Symbol, LvarType> joinLvarTypes, final LocalVariableConversion next) {
+ LvarType targetType = joinLvarTypes.get(symbol);
+ assert targetType != null;
+ if(targetType == branchLvarType) {
+ return next;
+ }
+ // NOTE: we could naively just use symbolIsUsed(symbol, branchLvarType) here, but that'd be wrong. While
+ // technically a conversion will read the value of the symbol with that type, but it will also write it to a new
+ // type, and that type might be dead (we can't know yet). For this reason, we don't treat conversion reads as
+ // real uses until we know their target type is live. If we didn't do this, and just did a symbolIsUsed here,
+ // we'd introduce false live variables which could nevertheless turn into dead ones in a subsequent
+ // deoptimization, causing a shift in the list of live locals that'd cause erroneous restoration of
+ // continuations (since RewriteException's byteCodeSlots carries an array and not a name-value map).
+
+ symbolIsConverted(symbol, branchLvarType, targetType);
+ //symbolIsUsed(symbol, branchLvarType);
+ return new LocalVariableConversion(symbol, branchLvarType.type, targetType.type, next);
+ }
+
+ private static Map<Symbol, LvarType> getUnionTypes(final Map<Symbol, LvarType> types1, final Map<Symbol, LvarType> types2) {
+ if(types1 == types2 || types1.isEmpty()) {
+ return types2;
+ } else if(types2.isEmpty()) {
+ return types1;
+ }
+ final Set<Symbol> commonSymbols = new HashSet<>(types1.keySet());
+ commonSymbols.retainAll(types2.keySet());
+ // We have a chance of returning an unmodified set if both sets have the same keys and one is strictly wider
+ // than the other.
+ final int commonSize = commonSymbols.size();
+ final int types1Size = types1.size();
+ final int types2Size = types2.size();
+ if(commonSize == types1Size && commonSize == types2Size) {
+ boolean matches1 = true, matches2 = true;
+ Map<Symbol, LvarType> union = null;
+ for(final Symbol symbol: commonSymbols) {
+ final LvarType type1 = types1.get(symbol);
+ final LvarType type2 = types2.get(symbol);
+ final LvarType widest = widestLvarType(type1, type2);
+ if(widest != type1 && matches1) {
+ matches1 = false;
+ if(!matches2) {
+ union = cloneMap(types1);
+ }
+ }
+ if (widest != type2 && matches2) {
+ matches2 = false;
+ if(!matches1) {
+ union = cloneMap(types2);
+ }
+ }
+ if(!(matches1 || matches2)) {
+ union.put(symbol, widest);
+ }
+ }
+ return matches1 ? types1 : matches2 ? types2 : union;
+ }
+ // General case
+ final Map<Symbol, LvarType> union;
+ if(types1Size > types2Size) {
+ union = cloneMap(types1);
+ union.putAll(types2);
+ } else {
+ union = cloneMap(types2);
+ union.putAll(types1);
+ }
+ for(final Symbol symbol: commonSymbols) {
+ final LvarType type1 = types1.get(symbol);
+ final LvarType type2 = types2.get(symbol);
+ union.put(symbol, widestLvarType(type1, type2));
+ }
+ return union;
+ }
+
+ private static void symbolIsUsed(final Symbol symbol, final LvarType type) {
+ if(type != LvarType.UNDEFINED) {
+ symbol.setHasSlotFor(type.type);
+ }
+ }
+
+ private static class SymbolConversions {
+ private static byte I2L = 1 << 0;
+ private static byte I2D = 1 << 1;
+ private static byte I2O = 1 << 2;
+ private static byte L2D = 1 << 3;
+ private static byte L2O = 1 << 4;
+ private static byte D2O = 1 << 5;
+
+ private byte conversions;
+
+ void recordConversion(final LvarType from, final LvarType to) {
+ switch(from) {
+ case UNDEFINED:
+ return;
+ case INT:
+ case BOOLEAN:
+ switch(to) {
+ case LONG:
+ recordConversion(I2L);
+ return;
+ case DOUBLE:
+ recordConversion(I2D);
+ return;
+ case OBJECT:
+ recordConversion(I2O);
+ return;
+ default:
+ illegalConversion(from, to);
+ return;
+ }
+ case LONG:
+ switch(to) {
+ case DOUBLE:
+ recordConversion(L2D);
+ return;
+ case OBJECT:
+ recordConversion(L2O);
+ return;
+ default:
+ illegalConversion(from, to);
+ return;
+ }
+ case DOUBLE:
+ if(to == LvarType.OBJECT) {
+ recordConversion(D2O);
+ }
+ return;
+ default:
+ illegalConversion(from, to);
+ }
+ }
+
+ private static void illegalConversion(final LvarType from, final LvarType to) {
+ throw new AssertionError("Invalid conversion from " + from + " to " + to);
+ }
+
+ void recordConversion(final byte convFlag) {
+ conversions = (byte)(conversions | convFlag);
+ }
+
+ boolean hasConversion(final byte convFlag) {
+ return (conversions & convFlag) != 0;
+ }
+
+ void calculateTypeLiveness(final Symbol symbol) {
+ if(symbol.hasSlotFor(Type.OBJECT)) {
+ if(hasConversion(D2O)) {
+ symbol.setHasSlotFor(Type.NUMBER);
+ }
+ if(hasConversion(L2O)) {
+ symbol.setHasSlotFor(Type.LONG);
+ }
+ if(hasConversion(I2O)) {
+ symbol.setHasSlotFor(Type.INT);
+ }
+ }
+ if(symbol.hasSlotFor(Type.NUMBER)) {
+ if(hasConversion(L2D)) {
+ symbol.setHasSlotFor(Type.LONG);
+ }
+ if(hasConversion(I2D)) {
+ symbol.setHasSlotFor(Type.INT);
+ }
+ }
+ if(symbol.hasSlotFor(Type.LONG)) {
+ if(hasConversion(I2L)) {
+ symbol.setHasSlotFor(Type.INT);
+ }
+ }
+ }
+ }
+
+ private void symbolIsConverted(final Symbol symbol, final LvarType from, final LvarType to) {
+ SymbolConversions conversions = symbolConversions.get(symbol);
+ if(conversions == null) {
+ conversions = new SymbolConversions();
+ symbolConversions.put(symbol, conversions);
+ }
+ conversions.recordConversion(from, to);
+ }
+
+ private static LvarType toLvarType(final Type type) {
+ assert type != null;
+ final LvarType lvarType = TO_LVAR_TYPE.get(type);
+ if(lvarType != null) {
+ return lvarType;
+ }
+ assert type.isObject();
+ return LvarType.OBJECT;
+ }
+ private static LvarType widestLvarType(final LvarType t1, final LvarType t2) {
+ if(t1 == t2) {
+ return t1;
+ }
+ // Undefined or boolean to anything always widens to object.
+ if(t1.ordinal() < LvarType.INT.ordinal() || t2.ordinal() < LvarType.INT.ordinal()) {
+ return LvarType.OBJECT;
+ }
+ // NOTE: we allow "widening" of long to double even though it can lose precision. ECMAScript doesn't have an
+ // Int64 type anyway, so this loss of precision is actually more conformant to the specification...
+ return LvarType.values()[Math.max(t1.ordinal(), t2.ordinal())];
+ }
+ private final CompilationEnvironment env;
+ private final Map<Label, JumpTarget> jumpTargets = new IdentityHashMap<>();
+ // Local variable type mapping at the currently evaluated point. No map instance is ever modified; setLvarType() always
+ // allocates a new map. Immutability of maps allows for cheap snapshots by just keeping the reference to the current
+ // value.
+ private Map<Symbol, LvarType> localVariableTypes = new IdentityHashMap<>();
+
+ // Whether the current point in the AST is reachable code
+ private boolean reachable = true;
+ // Return type of the function
+ private Type returnType = Type.UNKNOWN;
+
+ // Topmost current split node (if any)
+ private SplitNode topSplit;
+ private boolean split;
+
+ private boolean alreadyEnteredTopLevelFunction;
+
+ // LvarType and conversion information gathered during the top-down pass; applied to nodes in the bottom-up pass.
+ private final Map<JoinPredecessor, LocalVariableConversion> localVariableConversions = new IdentityHashMap<>();
+
+ private final Map<IdentNode, LvarType> identifierLvarTypes = new IdentityHashMap<>();
+ private final Map<Symbol, SymbolConversions> symbolConversions = new IdentityHashMap<>();
+
+ private SymbolToType symbolToType = new SymbolToType();
+
+ // Stack of open labels for starts of catch blocks, one for every currently traversed try block; for inserting
+ // control flow edges to them. Note that we currently don't insert actual control flow edges, but instead edges that
+ // help us with type calculations. This means that some operations that can result in an exception being thrown
+ // aren't considered (function calls, side effecting property getters and setters etc.), while some operations that
+ // don't result in control flow transfers do originate an edge to the catch blocks (namely, assignments to local
+ // variables).
+ private final Deque<Label> catchLabels = new ArrayDeque<>();
+
+ LocalVariableTypesCalculator(final CompilationEnvironment env) {
+ super(new LexicalContext());
+ this.env = env;
+ }
+
+ private JumpTarget createJumpTarget(final Label label) {
+ assert !jumpTargets.containsKey(label);
+ final JumpTarget jumpTarget = new JumpTarget();
+ jumpTargets.put(label, jumpTarget);
+ return jumpTarget;
+ }
+
+ private void doesNotContinueSequentially() {
+ reachable = false;
+ localVariableTypes = Collections.emptyMap();
+ }
+
+
+ @Override
+ public boolean enterBinaryNode(final BinaryNode binaryNode) {
+ final Expression lhs = binaryNode.lhs();
+ final Expression rhs = binaryNode.rhs();
+ final boolean isAssignment = binaryNode.isAssignment();
+
+ final TokenType tokenType = Token.descType(binaryNode.getToken());
+ if(tokenType.isLeftAssociative()) {
+ assert !isAssignment;
+ final boolean isLogical = binaryNode.isLogical();
+ final Label joinLabel = isLogical ? new Label("") : null;
+ lhs.accept(this);
+ if(isLogical) {
+ jumpToLabel((JoinPredecessor)lhs, joinLabel);
+ }
+ rhs.accept(this);
+ if(isLogical) {
+ jumpToLabel((JoinPredecessor)rhs, joinLabel);
+ }
+ joinOnLabel(joinLabel);
+ } else {
+ rhs.accept(this);
+ if(isAssignment) {
+ if(lhs instanceof BaseNode) {
+ ((BaseNode)lhs).getBase().accept(this);
+ if(lhs instanceof IndexNode) {
+ ((IndexNode)lhs).getIndex().accept(this);
+ } else {
+ assert lhs instanceof AccessNode;
+ }
+ } else {
+ assert lhs instanceof IdentNode;
+ if(binaryNode.isSelfModifying()) {
+ ((IdentNode)lhs).accept(this);
+ }
+ }
+ } else {
+ lhs.accept(this);
+ }
+ }
+
+ if(isAssignment && lhs instanceof IdentNode) {
+ if(binaryNode.isSelfModifying()) {
+ onSelfAssignment((IdentNode)lhs, binaryNode);
+ } else {
+ onAssignment((IdentNode)lhs, rhs);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean enterBlock(final Block block) {
+ for(Symbol symbol: block.getSymbols()) {
+ if(symbol.isBytecodeLocal() && getLocalVariableTypeOrNull(symbol) == null) {
+ setType(symbol, LvarType.UNDEFINED);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean enterBreakNode(final BreakNode breakNode) {
+ if(!reachable) {
+ return false;
+ }
+
+ final BreakableNode target = lc.getBreakable(breakNode.getLabelName());
+ return splitAwareJumpToLabel(breakNode, target, target.getBreakLabel());
+ }
+
+ @Override
+ public boolean enterContinueNode(final ContinueNode continueNode) {
+ if(!reachable) {
+ return false;
+ }
+ final LoopNode target = lc.getContinueTo(continueNode.getLabelName());
+ return splitAwareJumpToLabel(continueNode, target, target.getContinueLabel());
+ }
+
+ private boolean splitAwareJumpToLabel(final JumpStatement jumpStatement, final BreakableNode target, final Label targetLabel) {
+ final JoinPredecessor jumpOrigin;
+ if(topSplit != null && lc.isExternalTarget(topSplit, target)) {
+ // If the jump target is outside the topmost split node, then we'll create a synthetic jump origin in the
+ // split node.
+ jumpOrigin = new JoinPredecessorExpression();
+ topSplit.addJump(jumpOrigin, targetLabel);
+ } else {
+ // Otherwise, the original jump statement is the jump origin
+ jumpOrigin = jumpStatement;
+ }
+
+ jumpToLabel(jumpOrigin, targetLabel, getBreakTargetTypes(target));
+ doesNotContinueSequentially();
+ return false;
+ }
+
+ @Override
+ protected boolean enterDefault(final Node node) {
+ return reachable;
+ }
+
+ private void enterDoWhileLoop(final WhileNode loopNode) {
+ final JoinPredecessorExpression test = loopNode.getTest();
+ final Block body = loopNode.getBody();
+ final Label continueLabel = loopNode.getContinueLabel();
+ final Label breakLabel = loopNode.getBreakLabel();
+ final Map<Symbol, LvarType> beforeLoopTypes = localVariableTypes;
+ final Label repeatLabel = new Label("");
+ for(;;) {
+ jumpToLabel(loopNode, repeatLabel, beforeLoopTypes);
+ final Map<Symbol, LvarType> beforeRepeatTypes = localVariableTypes;
+ body.accept(this);
+ if(reachable) {
+ jumpToLabel(body, continueLabel);
+ }
+ joinOnLabel(continueLabel);
+ if(!reachable) {
+ break;
+ }
+ test.accept(this);
+ jumpToLabel(test, breakLabel);
+ if(isAlwaysFalse(test)) {
+ break;
+ }
+ jumpToLabel(test, repeatLabel);
+ joinOnLabel(repeatLabel);
+ if(localVariableTypes.equals(beforeRepeatTypes)) {
+ break;
+ }
+ resetJoinPoint(continueLabel);
+ resetJoinPoint(breakLabel);
+ resetJoinPoint(repeatLabel);
+ }
+
+ if(isAlwaysTrue(test)) {
+ doesNotContinueSequentially();
+ }
+
+ leaveBreakable(loopNode);
+ }
+
+ @Override
+ public boolean enterForNode(final ForNode forNode) {
+ if(!reachable) {
+ return false;
+ }
+
+ final Expression init = forNode.getInit();
+ if(forNode.isForIn()) {
+ forNode.getModify().accept(this);
+ enterTestFirstLoop(forNode, null, init);
+ } else {
+ if(init != null) {
+ init.accept(this);
+ }
+ enterTestFirstLoop(forNode, forNode.getModify(), null);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean enterFunctionNode(final FunctionNode functionNode) {
+ if(alreadyEnteredTopLevelFunction) {
+ return false;
+ }
+ int pos = 0;
+ if(!functionNode.isVarArg()) {
+ for (final IdentNode param : functionNode.getParameters()) {
+ final Symbol symbol = param.getSymbol();
+ // Parameter is not necessarily bytecode local as it can be scoped due to nested context use, but it
+ // must have a slot if we aren't in a function with vararg signature.
+ assert symbol.hasSlot();
+ final Type callSiteParamType = env.getParamType(functionNode, pos);
+ final LvarType paramType = callSiteParamType == null ? LvarType.OBJECT : toLvarType(callSiteParamType);
+ setType(symbol, paramType);
+ // Make sure parameter slot for its incoming value is not marked dead. NOTE: this is a heuristic. Right
+ // now, CodeGenerator.expandParameters() relies on the fact that every parameter's final slot width will
+ // be at least the same as incoming width, therefore even if a parameter is never read, we'll still keep
+ // its slot.
+ symbolIsUsed(symbol);
+ setIdentifierLvarType(param, paramType);
+ pos++;
+ }
+ }
+ setCompilerConstantAsObject(functionNode, CompilerConstants.THIS);
+ if(functionNode.needsParentScope()) {
+ setCompilerConstantAsObject(functionNode, CompilerConstants.SCOPE);
+ }
+ if(functionNode.needsCallee()) {
+ setCompilerConstantAsObject(functionNode, CompilerConstants.CALLEE);
+ }
+ if(functionNode.needsArguments()) {
+ setCompilerConstantAsObject(functionNode, CompilerConstants.ARGUMENTS);
+ }
+
+ alreadyEnteredTopLevelFunction = true;
+ return true;
+ }
+
+ @Override
+ public boolean enterIdentNode(final IdentNode identNode) {
+ final Symbol symbol = identNode.getSymbol();
+ if(symbol.isBytecodeLocal()) {
+ symbolIsUsed(symbol);
+ setIdentifierLvarType(identNode, getLocalVariableType(symbol));
+ }
+ return false;
+ }
+
+ @Override
+ public boolean enterIfNode(final IfNode ifNode) {
+ if(!reachable) {
+ return false;
+ }
+
+ final Expression test = ifNode.getTest();
+ final Block pass = ifNode.getPass();
+ final Block fail = ifNode.getFail();
+
+ test.accept(this);
+
+ final Map<Symbol, LvarType> afterTestLvarTypes = localVariableTypes;
+ if(!isAlwaysFalse(test)) {
+ pass.accept(this);
+ }
+ final Map<Symbol, LvarType> passLvarTypes = localVariableTypes;
+ final boolean reachableFromPass = reachable;
+
+ reachable = true;
+ localVariableTypes = afterTestLvarTypes;
+ if(!isAlwaysTrue(test) && fail != null) {
+ fail.accept(this);
+ final boolean reachableFromFail = reachable;
+ reachable |= reachableFromPass;
+ if(!reachable) {
+ return false;
+ }
+
+ if(reachableFromFail) {
+ if(reachableFromPass) {
+ final Map<Symbol, LvarType> failLvarTypes = localVariableTypes;
+ localVariableTypes = getUnionTypes(passLvarTypes, failLvarTypes);
+ setConversion(pass, passLvarTypes, localVariableTypes);
+ setConversion(fail, failLvarTypes, localVariableTypes);
+ }
+ return false;
+ }
+ }
+
+ if(reachableFromPass) {
+ localVariableTypes = getUnionTypes(afterTestLvarTypes, passLvarTypes);
+ // IfNode itself is associated with conversions that might need to be performed after the test if there's no
+ // else branch. E.g.
+ // if(x = 1, cond) { x = 1.0 } must widen "x = 1" to a double.
+ setConversion(pass, passLvarTypes, localVariableTypes);
+ setConversion(ifNode, afterTestLvarTypes, localVariableTypes);
+ } else {
+ localVariableTypes = afterTestLvarTypes;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean enterPropertyNode(final PropertyNode propertyNode) {
+ // Avoid falsely adding property keys to the control flow graph
+ if(propertyNode.getValue() != null) {
+ propertyNode.getValue().accept(this);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean enterReturnNode(final ReturnNode returnNode) {
+ final Expression returnExpr = returnNode.getExpression();
+ final Type returnExprType;
+ if(returnExpr != null) {
+ returnExpr.accept(this);
+ returnExprType = getType(returnExpr);
+ } else {
+ returnExprType = Type.UNDEFINED;
+ }
+ returnType = Type.widestReturnType(returnType, returnExprType);
+ doesNotContinueSequentially();
+ return false;
+ }
+
+ @Override
+ public boolean enterSplitNode(final SplitNode splitNode) {
+ // Need to visit inside of split nodes. While it's true that they don't have local variables, we need to visit
+ // breaks, continues, and returns in them.
+ if(topSplit == null) {
+ topSplit = splitNode;
+ }
+ split = true;
+ setType(getCompilerConstantSymbol(lc.getCurrentFunction(), CompilerConstants.RETURN), LvarType.UNDEFINED);
+ return true;
+ }
+
+ @Override
+ public boolean enterSwitchNode(final SwitchNode switchNode) {
+ if(!reachable) {
+ return false;
+ }
+
+ final Expression expr = switchNode.getExpression();
+ expr.accept(this);
+
+ final List<CaseNode> cases = switchNode.getCases();
+ if(cases.isEmpty()) {
+ return false;
+ }
+
+ // Control flow is different for all-integer cases where we dispatch by switch table, and for all other cases
+ // where we do sequential comparison. Note that CaseNode objects act as join points.
+ final boolean isInteger = switchNode.isInteger();
+ final Label breakLabel = switchNode.getBreakLabel();
+ final boolean hasDefault = switchNode.getDefaultCase() != null;
+
+ boolean tagUsed = false;
+ for(final CaseNode caseNode: cases) {
+ final Expression test = caseNode.getTest();
+ if(!isInteger && test != null) {
+ test.accept(this);
+ if(!tagUsed) {
+ symbolIsUsed(switchNode.getTag(), LvarType.OBJECT);
+ tagUsed = true;
+ }
+ }
+ // CaseNode carries the conversions that need to be performed on its entry from the test.
+ // CodeGenerator ensures these are only emitted when arriving on the branch and not through a
+ // fallthrough.
+ jumpToLabel(caseNode, caseNode.getBody().getEntryLabel());
+ }
+ if(!hasDefault) {
+ // No default case means we can arrive at the break label without entering any cases. In that case
+ // SwitchNode will carry the conversions that need to be performed before it does that jump.
+ jumpToLabel(switchNode, breakLabel);
+ }
+
+ // All cases are arrived at through jumps
+ doesNotContinueSequentially();
+
+ Block previousBlock = null;
+ for(final CaseNode caseNode: cases) {
+ final Block body = caseNode.getBody();
+ final Label entryLabel = body.getEntryLabel();
+ if(previousBlock != null && reachable) {
+ jumpToLabel(previousBlock, entryLabel);
+ }
+ joinOnLabel(entryLabel);
+ assert reachable == true;
+ body.accept(this);
+ previousBlock = body;
+ }
+ if(previousBlock != null && reachable) {
+ jumpToLabel(previousBlock, breakLabel);
+ }
+ leaveBreakable(switchNode);
+ return false;
+ }
+
+ @Override
+ public boolean enterTernaryNode(final TernaryNode ternaryNode) {
+ final Expression test = ternaryNode.getTest();
+ final Expression trueExpr = ternaryNode.getTrueExpression();
+ final Expression falseExpr = ternaryNode.getFalseExpression();
+
+ test.accept(this);
+
+ final Map<Symbol, LvarType> testExitLvarTypes = localVariableTypes;
+ if(!isAlwaysFalse(test)) {
+ trueExpr.accept(this);
+ }
+ final Map<Symbol, LvarType> trueExitLvarTypes = localVariableTypes;
+ localVariableTypes = testExitLvarTypes;
+ if(!isAlwaysTrue(test)) {
+ falseExpr.accept(this);
+ }
+ final Map<Symbol, LvarType> falseExitLvarTypes = localVariableTypes;
+ localVariableTypes = getUnionTypes(trueExitLvarTypes, falseExitLvarTypes);
+ setConversion((JoinPredecessor)trueExpr, trueExitLvarTypes, localVariableTypes);
+ setConversion((JoinPredecessor)falseExpr, falseExitLvarTypes, localVariableTypes);
+ return false;
+ }
+
+ private void enterTestFirstLoop(final LoopNode loopNode, final JoinPredecessorExpression modify, final Expression iteratorValues) {
+ final JoinPredecessorExpression test = loopNode.getTest();
+ if(isAlwaysFalse(test)) {
+ test.accept(this);
+ return;
+ }
+
+ final Label continueLabel = loopNode.getContinueLabel();
+ final Label breakLabel = loopNode.getBreakLabel();
+
+ final Label repeatLabel = modify == null ? continueLabel : new Label("");
+ final Map<Symbol, LvarType> beforeLoopTypes = localVariableTypes;
+ for(;;) {
+ jumpToLabel(loopNode, repeatLabel, beforeLoopTypes);
+ final Map<Symbol, LvarType> beforeRepeatTypes = localVariableTypes;
+ if(test != null) {
+ test.accept(this);
+ }
+ if(!isAlwaysTrue(test)) {
+ jumpToLabel(test, breakLabel);
+ }
+ if(iteratorValues instanceof IdentNode) {
+ // Receives iterator values; they're currently all objects (JDK-8034954).
+ onAssignment((IdentNode)iteratorValues, LvarType.OBJECT);
+ }
+ final Block body = loopNode.getBody();
+ body.accept(this);
+ if(reachable) {
+ jumpToLabel(body, continueLabel);
+ }
+ joinOnLabel(continueLabel);
+ if(!reachable) {
+ break;
+ }
+ if(modify != null) {
+ modify.accept(this);
+ jumpToLabel(modify, repeatLabel);
+ joinOnLabel(repeatLabel);
+ }
+ if(localVariableTypes.equals(beforeRepeatTypes)) {
+ break;
+ }
+ // Reset the join points and repeat the analysis
+ resetJoinPoint(continueLabel);
+ resetJoinPoint(breakLabel);
+ resetJoinPoint(repeatLabel);
+ }
+
+ if(isAlwaysTrue(test) && iteratorValues == null) {
+ doesNotContinueSequentially();
+ }
+
+ leaveBreakable(loopNode);
+ }
+
+ @Override
+ public boolean enterThrowNode(final ThrowNode throwNode) {
+ throwNode.getExpression().accept(this);
+ jumpToCatchBlock(throwNode);
+ doesNotContinueSequentially();
+ return false;
+ }
+
+ @Override
+ public boolean enterTryNode(final TryNode tryNode) {
+ if(!reachable) {
+ return false;
+ }
+
+ // This is the label for the join point at the entry of the catch blocks.
+ final Label catchLabel = new Label("");
+ catchLabels.push(catchLabel);
+
+ // Presume that even the start of the try block can immediately go to the catch
+ jumpToLabel(tryNode, catchLabel);
+
+ final Block body = tryNode.getBody();
+ body.accept(this);
+ catchLabels.pop();
+
+ // Final exit label for the whole try/catch construct (after the try block and after all catches).
+ final Label endLabel = new Label("");
+
+ boolean canExit = false;
+ if(reachable) {
+ jumpToLabel(body, endLabel);
+ canExit = true;
+ }
+ doesNotContinueSequentially();
+
+ joinOnLabel(catchLabel);
+ for(final CatchNode catchNode: tryNode.getCatches()) {
+ final IdentNode exception = catchNode.getException();
+ onAssignment(exception, LvarType.OBJECT);
+ final Expression condition = catchNode.getExceptionCondition();
+ if(condition != null) {
+ condition.accept(this);
+ }
+ final Map<Symbol, LvarType> afterConditionTypes = localVariableTypes;
+ final Block catchBody = catchNode.getBody();
+ // TODO: currently, we consider that the catch blocks are always reachable from the try block as currently
+ // we lack enough analysis to prove that no statement before a break/continue/return in the try block can
+ // throw an exception.
+ reachable = true;
+ catchBody.accept(this);
+ final Symbol exceptionSymbol = exception.getSymbol();
+ if(reachable) {
+ localVariableTypes = cloneMap(localVariableTypes);
+ localVariableTypes.remove(exceptionSymbol);
+ jumpToLabel(catchBody, endLabel);
+ canExit = true;
+ }
+ localVariableTypes = cloneMap(afterConditionTypes);
+ localVariableTypes.remove(exceptionSymbol);
+ }
+ // NOTE: if we had one or more conditional catch blocks with no unconditional catch block following them, then
+ // there will be an unconditional rethrow, so the join point can never be reached from the last
+ // conditionExpression.
+ doesNotContinueSequentially();
+
+ if(canExit) {
+ joinOnLabel(endLabel);
+ }
+
+ return false;
+ }
+
+
+ @Override
+ public boolean enterUnaryNode(final UnaryNode unaryNode) {
+ final Expression expr = unaryNode.getExpression();
+ expr.accept(this);
+
+ if(unaryNode.isSelfModifying()) {
+ if(expr instanceof IdentNode) {
+ onSelfAssignment((IdentNode)expr, unaryNode);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean enterVarNode(final VarNode varNode) {
+ final Expression init = varNode.getInit();
+ if(init != null) {
+ init.accept(this);
+ onAssignment(varNode.getName(), init);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean enterWhileNode(final WhileNode whileNode) {
+ if(!reachable) {
+ return false;
+ }
+ if(whileNode.isDoWhile()) {
+ enterDoWhileLoop(whileNode);
+ } else {
+ enterTestFirstLoop(whileNode, null, null);
+ }
+ return false;
+ }
+
+ private Map<Symbol, LvarType> getBreakTargetTypes(final BreakableNode target) {
+ // Remove symbols defined in the the blocks that are being broken out of.
+ Map<Symbol, LvarType> types = localVariableTypes;
+ for(final Iterator<LexicalContextNode> it = lc.getAllNodes(); it.hasNext();) {
+ final LexicalContextNode node = it.next();
+ if(node instanceof Block) {
+ for(final Symbol symbol: ((Block)node).getSymbols()) {
+ if(localVariableTypes.containsKey(symbol)) {
+ if(types == localVariableTypes) {
+ types = cloneMap(localVariableTypes);
+ }
+ types.remove(symbol);
+ }
+ }
+ }
+ if(node == target) {
+ break;
+ }
+ }
+ return types;
+ }
+
+ private LvarType getLocalVariableType(final Symbol symbol) {
+ final LvarType type = getLocalVariableTypeOrNull(symbol);
+ assert type != null;
+ return type;
+ }
+
+ private LvarType getLocalVariableTypeOrNull(final Symbol symbol) {
+ return localVariableTypes.get(symbol);
+ }
+
+ private JumpTarget getOrCreateJumpTarget(final Label label) {
+ JumpTarget jumpTarget = jumpTargets.get(label);
+ if(jumpTarget == null) {
+ jumpTarget = createJumpTarget(label);
+ }
+ return jumpTarget;
+ }
+
+
+ /**
+ * If there's a join point associated with a label, insert the join point into the flow.
+ * @param label the label to insert a join point for.
+ */
+ private void joinOnLabel(final Label label) {
+ final JumpTarget jumpTarget = jumpTargets.remove(label);
+ if(jumpTarget == null) {
+ return;
+ }
+ assert !jumpTarget.origins.isEmpty();
+ reachable = true;
+ localVariableTypes = getUnionTypes(jumpTarget.types, localVariableTypes);
+ for(final JumpOrigin jumpOrigin: jumpTarget.origins) {
+ setConversion(jumpOrigin.node, jumpOrigin.types, localVariableTypes);
+ }
+ }
+
+ /**
+ * If we're in a try/catch block, add an edge from the specified node to the try node's pre-catch label.
+ */
+ private void jumpToCatchBlock(final JoinPredecessor jumpOrigin) {
+ final Label currentCatchLabel = catchLabels.peek();
+ if(currentCatchLabel != null) {
+ jumpToLabel(jumpOrigin, currentCatchLabel);
+ }
+ }
+
+ private void jumpToLabel(final JoinPredecessor jumpOrigin, final Label label) {
+ jumpToLabel(jumpOrigin, label, localVariableTypes);
+ }
+
+ private void jumpToLabel(final JoinPredecessor jumpOrigin, final Label label, final Map<Symbol, LvarType> types) {
+ getOrCreateJumpTarget(label).addOrigin(jumpOrigin, types);
+ }
+
+ @Override
+ public Node leaveBlock(final Block block) {
+ if(lc.isFunctionBody()) {
+ // We must calculate the return type here (and not in leaveFunctionNode) as it can affect the liveness of
+ // the :return symbol and thus affect conversion type liveness calculations for it.
+ calculateReturnType();
+ }
+
+ boolean cloned = false;
+ for(final Symbol symbol: block.getSymbols()) {
+ // Undefine the symbol outside the block
+ if(localVariableTypes.containsKey(symbol)) {
+ if(!cloned) {
+ localVariableTypes = cloneMap(localVariableTypes);
+ cloned = true;
+ }
+ localVariableTypes.remove(symbol);
+ }
+
+ if(symbol.hasSlot()) {
+ final SymbolConversions conversions = symbolConversions.get(symbol);
+ if(conversions != null) {
+ // Potentially make some currently dead types live if they're needed as a source of a type
+ // conversion at a join.
+ conversions.calculateTypeLiveness(symbol);
+ }
+ if(symbol.slotCount() == 0) {
+ // This is a local variable that is never read. It won't need a slot.
+ symbol.setNeedsSlot(false);
+ }
+ }
+ }
+
+ if(reachable) {
+ // TODO: this is totally backwards. Block should not be breakable, LabelNode should be breakable.
+ final LabelNode labelNode = lc.getCurrentBlockLabelNode();
+ if(labelNode != null) {
+ jumpToLabel(labelNode, block.getBreakLabel());
+ }
+ }
+ leaveBreakable(block);
+ return block;
+ }
+
+ private void calculateReturnType() {
+ // NOTE: if return type is unknown, then the function does not explicitly return a value. Such a function under
+ // ECMAScript rules returns Undefined, which has Type.OBJECT. We might consider an optimization in the future
+ // where we can return void functions.
+ if(returnType.isUnknown()) {
+ returnType = Type.OBJECT;
+ }
+
+ if(split) {
+ // If the function is split, the ":return" symbol is used and needs a slot. Note we can't mark the return
+ // symbol as used in enterSplitNode, as we don't know the final return type of the function earlier than
+ // here.
+ Symbol retSymbol = getCompilerConstantSymbol(lc.getCurrentFunction(), CompilerConstants.RETURN);
+ retSymbol.setHasSlotFor(returnType);
+ retSymbol.setNeedsSlot(true);
+ }
+ }
+ /**
+ * Leave a breakable node. If there's a join point associated with its break label (meaning there was at least one
+ * break statement to the end of the node), insert the join point into the flow.
+ * @param breakable the breakable node being left.
+ */
+ private void leaveBreakable(final BreakableNode breakable) {
+ joinOnLabel(breakable.getBreakLabel());
+ }
+
+ @Override
+ public Node leaveFunctionNode(final FunctionNode functionNode) {
+ // Sets the return type of the function and also performs the bottom-up pass of applying type and conversion
+ // information to nodes as well as doing the calculation on nested functions as required.
+ FunctionNode newFunction = functionNode;
+ final NodeVisitor<LexicalContext> applyChangesVisitor = new NodeVisitor<LexicalContext>(new LexicalContext()) {
+ private boolean inOuterFunction = true;
+ private Deque<JoinPredecessor> joinPredecessors = new ArrayDeque<>();
+
+ @Override
+ protected boolean enterDefault(Node node) {
+ if(!inOuterFunction) {
+ return false;
+ }
+ if(node instanceof JoinPredecessor) {
+ joinPredecessors.push((JoinPredecessor)node);
+ }
+ return inOuterFunction;
+ }
+
+ @Override
+ public boolean enterFunctionNode(final FunctionNode fn) {
+ if(env.isOnDemandCompilation()) {
+ // Only calculate nested function local variable types if we're doing eager compilation
+ return false;
+ }
+ inOuterFunction = false;
+ return true;
+ }
+
+ @SuppressWarnings("fallthrough")
+ @Override
+ public Node leaveBinaryNode(BinaryNode binaryNode) {
+ if(binaryNode.isComparison()) {
+ final Expression lhs = binaryNode.lhs();
+ final Expression rhs = binaryNode.rhs();
+
+ Type cmpWidest = Type.widest(lhs.getType(), rhs.getType());
+ boolean newRuntimeNode = false, finalized = false;
+ final TokenType tt = binaryNode.tokenType();
+ switch (tt) {
+ case EQ_STRICT:
+ case NE_STRICT:
+ // Specialize comparison with undefined
+ final Expression undefinedNode = createIsUndefined(binaryNode, lhs, rhs,
+ tt == TokenType.EQ_STRICT ? Request.IS_UNDEFINED : Request.IS_NOT_UNDEFINED);
+ if(undefinedNode != binaryNode) {
+ return undefinedNode;
+ }
+ // Specialize comparison of boolean with non-boolean
+ if (lhs.getType().isBoolean() != rhs.getType().isBoolean()) {
+ newRuntimeNode = true;
+ cmpWidest = Type.OBJECT;
+ finalized = true;
+ }
+ // fallthrough
+ default:
+ if (newRuntimeNode || cmpWidest.isObject()) {
+ return new RuntimeNode(binaryNode).setIsFinal(finalized);
+ }
+ }
+ } else if(binaryNode.isOptimisticUndecidedType()) {
+ // At this point, we can assign a static type to the optimistic binary ADD operator as now we know
+ // the types of its operands.
+ return binaryNode.setType(Type.widest(binaryNode.lhs().getType(), binaryNode.rhs().getType()));
+ }
+ return binaryNode;
+ }
+
+ @Override
+ protected Node leaveDefault(final Node node) {
+ if(node instanceof JoinPredecessor) {
+ final JoinPredecessor original = joinPredecessors.pop();
+ assert original.getClass() == node.getClass() : original.getClass().getName() + "!=" + node.getClass().getName();
+ return (Node)setLocalVariableConversion(original, (JoinPredecessor)node);
+ }
+ return node;
+ }
+
+ @Override
+ public Node leaveFunctionNode(FunctionNode nestedFunctionNode) {
+ inOuterFunction = true;
+ final FunctionNode newNestedFunction = (FunctionNode)nestedFunctionNode.accept(
+ new LocalVariableTypesCalculator(env));
+ lc.replace(nestedFunctionNode, newNestedFunction);
+ return newNestedFunction;
+ }
+
+ @Override
+ public Node leaveIdentNode(IdentNode identNode) {
+ final IdentNode original = (IdentNode)joinPredecessors.pop();
+ final Symbol symbol = identNode.getSymbol();
+ if(symbol == null) {
+ assert identNode.isPropertyName();
+ return identNode;
+ } else if(symbol.hasSlot()) {
+ assert !symbol.isScope() || symbol.isParam(); // Only params can be slotted and scoped.
+ assert original.getName().equals(identNode.getName());
+ final LvarType lvarType = identifierLvarTypes.remove(original);
+ if(lvarType != null) {
+ return setLocalVariableConversion(original, identNode.setType(lvarType.type));
+ }
+ // If there's no type, then the identifier must've been in unreachable code. In that case, it can't
+ // have assigned conversions either.
+ assert localVariableConversions.get(original) == null;
+ } else {
+ assert identIsDeadAndHasNoLiveConversions(original);
+ }
+ return identNode;
+ }
+
+ @Override
+ public Node leaveLiteralNode(LiteralNode<?> literalNode) {
+ if(literalNode instanceof ArrayLiteralNode) {
+ ((ArrayLiteralNode)literalNode).analyze();
+ }
+ return literalNode;
+ }
+
+ @Override
+ public Node leaveRuntimeNode(final RuntimeNode runtimeNode) {
+ final Request request = runtimeNode.getRequest();
+ final boolean isEqStrict = request == Request.EQ_STRICT;
+ if(isEqStrict || request == Request.NE_STRICT) {
+ return createIsUndefined(runtimeNode, runtimeNode.getArgs().get(0), runtimeNode.getArgs().get(1),
+ isEqStrict ? Request.IS_UNDEFINED : Request.IS_NOT_UNDEFINED);
+ }
+ return runtimeNode;
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T extends JoinPredecessor> T setLocalVariableConversion(final JoinPredecessor original, final T jp) {
+ // NOTE: can't use Map.remove() as our copy-on-write AST semantics means some nodes appear twice (in
+ // finally blocks), so we need to be able to access conversions for them multiple times.
+ return (T)jp.setLocalVariableConversion(lc, localVariableConversions.get(original));
+ }
+ };
+
+ newFunction = newFunction.setBody(lc, (Block)newFunction.getBody().accept(applyChangesVisitor));
+ newFunction = newFunction.setReturnType(lc, returnType);
+
+
+ newFunction = newFunction.setState(lc, CompilationState.LOCAL_VARIABLE_TYPES_CALCULATED);
+ newFunction = newFunction.setParameters(lc, newFunction.visitParameters(applyChangesVisitor));
+ return newFunction;
+ }
+
+ private static Expression createIsUndefined(final Expression parent, final Expression lhs, final Expression rhs, final Request request) {
+ if (isUndefinedIdent(lhs) || isUndefinedIdent(rhs)) {
+ return new RuntimeNode(parent, request, lhs, rhs);
+ }
+ return parent;
+ }
+
+ private static boolean isUndefinedIdent(final Expression expr) {
+ return expr instanceof IdentNode && "undefined".equals(((IdentNode)expr).getName());
+ }
+
+ private boolean identIsDeadAndHasNoLiveConversions(final IdentNode identNode) {
+ final LocalVariableConversion conv = localVariableConversions.get(identNode);
+ return conv == null || !conv.isLive();
+ }
+
+ private void onAssignment(final IdentNode identNode, final Expression rhs) {
+ onAssignment(identNode, toLvarType(getType(rhs)));
+ }
+
+ private void onAssignment(final IdentNode identNode, final LvarType type) {
+ final Symbol symbol = identNode.getSymbol();
+ assert symbol != null : identNode.getName();
+ if(!symbol.isBytecodeLocal()) {
+ return;
+ }
+ assert type != null;
+ final LvarType finalType;
+ if(type == LvarType.UNDEFINED && getLocalVariableType(symbol) != LvarType.UNDEFINED) {
+ // Explicit assignment of a known undefined local variable to a local variable that is not undefined will
+ // materialize that undefined in the assignment target. Note that assigning known undefined to known
+ // undefined will *not* initialize the variable, e.g. "var x; var y = x;" compiles to no-op.
+ finalType = LvarType.OBJECT;
+ symbol.setFlag(Symbol.HAS_OBJECT_VALUE);
+ } else {
+ finalType = type;
+ }
+ setType(symbol, finalType);
+ // Explicit assignment of an undefined value. Make sure the variable can store an object
+ // TODO: if we communicated the fact to codegen with a flag on the IdentNode that the value was already
+ // undefined before the assignment, we could just ignore it. In general, we could ignore an assignment if we
+ // know that the value assigned is the same as the current value of the variable, but we'd need constant
+ // propagation for that.
+ setIdentifierLvarType(identNode, finalType);
+ // For purposes of type calculation, we consider an assignment to a local variable to be followed by
+ // the catch nodes of the current (if any) try block. This will effectively enforce that narrower
+ // assignments to a local variable in a try block will also have to store a widened value as well. Code
+ // within the try block will be able to keep loading the narrower value, but after the try block only
+ // the widest value will remain live.
+ // Rationale for this is that if there's an use for that variable in any of the catch blocks, or
+ // following the catch blocks, they must use the widest type.
+ // Example:
+ /*
+ Originally:
+ ===========
+ var x;
+ try {
+ x = 1; <-- stores into int slot for x
+ f(x); <-- loads the int slot for x
+ x = 3.14 <-- stores into the double slot for x
+ f(x); <-- loads the double slot for x
+ x = 1; <-- stores into int slot for x
+ f(x); <-- loads the int slot for x
+ } finally {
+ f(x); <-- loads the double slot for x, but can be reached by a path where x is int, so we need
+ to go back and ensure that double values are also always stored along with int
+ values.
+ }
+
+ After correction:
+ =================
+
+ var x;
+ try {
+ x = 1; <-- stores into both int and double slots for x
+ f(x); <-- loads the int slot for x
+ x = 3.14 <-- stores into the double slot for x
+ f(x); <-- loads the double slot for x
+ x = 1; <-- stores into both int and double slots for x
+ f(x); <-- loads the int slot for x
+ } finally {
+ f(x); <-- loads the double slot for x
+ }
+ */
+ jumpToCatchBlock(identNode);
+ }
+
+ private void onSelfAssignment(final IdentNode identNode, final Expression assignment) {
+ final Symbol symbol = identNode.getSymbol();
+ assert symbol != null : identNode.getName();
+ if(!symbol.isBytecodeLocal()) {
+ return;
+ }
+ final LvarType type = toLvarType(getType(assignment));
+ // Self-assignment never produce either a boolean or undefined
+ assert type != null && type != LvarType.UNDEFINED && type != LvarType.BOOLEAN;
+ setType(symbol, type);
+ jumpToCatchBlock(identNode);
+ }
+
+ private void resetJoinPoint(final Label label) {
+ jumpTargets.remove(label);
+ }
+
+ private void setCompilerConstantAsObject(final FunctionNode functionNode, final CompilerConstants cc) {
+ final Symbol symbol = getCompilerConstantSymbol(functionNode, cc);
+ setType(symbol, LvarType.OBJECT);
+ // never mark compiler constants as dead
+ symbolIsUsed(symbol);
+ }
+
+ private static Symbol getCompilerConstantSymbol(final FunctionNode functionNode, final CompilerConstants cc) {
+ return functionNode.getBody().getExistingSymbol(cc.symbolName());
+ }
+
+ private void setConversion(final JoinPredecessor node, final Map<Symbol, LvarType> branchLvarTypes, final Map<Symbol, LvarType> joinLvarTypes) {
+ if(node == null) {
+ return;
+ }
+ if(branchLvarTypes.isEmpty() || joinLvarTypes.isEmpty()) {
+ localVariableConversions.remove(node);
+ }
+
+ LocalVariableConversion conversion = null;
+ if(node instanceof IdentNode) {
+ // conversions on variable assignment in try block are special cases, as they only apply to the variable
+ // being assigned and all other conversions should be ignored.
+ final Symbol symbol = ((IdentNode)node).getSymbol();
+ conversion = createConversion(symbol, branchLvarTypes.get(symbol), joinLvarTypes, null);
+ } else {
+ for(Map.Entry<Symbol, LvarType> entry: branchLvarTypes.entrySet()) {
+ final Symbol symbol = entry.getKey();
+ LvarType branchLvarType = entry.getValue();
+ conversion = createConversion(symbol, branchLvarType, joinLvarTypes, conversion);
+ }
+ }
+ if(conversion != null) {
+ localVariableConversions.put(node, conversion);
+ } else {
+ localVariableConversions.remove(node);
+ }
+ }
+
+ private void setIdentifierLvarType(final IdentNode identNode, final LvarType type) {
+ assert type != null;
+ identifierLvarTypes.put(identNode, type);
+ }
+
+ /**
+ * Marks a local variable as having a specific type from this point onward. Invoked by stores to local variables.
+ * @param symbol the symbol representing the variable
+ * @param type the type
+ */
+ private void setType(final Symbol symbol, final LvarType type) {
+ if(getLocalVariableTypeOrNull(symbol) == type) {
+ return;
+ }
+ assert symbol.hasSlot();
+ assert !symbol.isGlobal();
+ localVariableTypes = localVariableTypes.isEmpty() ? new IdentityHashMap<Symbol, LvarType>() : cloneMap(localVariableTypes);
+ localVariableTypes.put(symbol, type);
+ }
+
+ /**
+ * Set a flag in the symbol marking it as needing to be able to store a value of a particular type. Every symbol for
+ * a local variable will be assigned between 1 and 6 local variable slots for storing all types it is known to need
+ * to store.
+ * @param symbol the symbol
+ */
+ private void symbolIsUsed(final Symbol symbol) {
+ symbolIsUsed(symbol, getLocalVariableType(symbol));
+ }
+
+ private Type getType(final Expression expr) {
+ return expr.getType(getSymbolToType());
+ }
+
+ private Function<Symbol, Type> getSymbolToType() {
+ // BinaryNode uses identity of the function to cache type calculations. Therefore, we must use different
+ // function instances for different localVariableTypes instances.
+ if(symbolToType.isStale()) {
+ symbolToType = new SymbolToType();
+ }
+ return symbolToType;
+ }
+
+ private class SymbolToType implements Function<Symbol, Type> {
+ private final Object boundTypes = localVariableTypes;
+ @Override
+ public Type apply(final Symbol t) {
+ return getLocalVariableType(t).type;
+ }
+
+ boolean isStale() {
+ return boundTypes != localVariableTypes;
+ }
+ }
+}
--- a/nashorn/src/jdk/nashorn/internal/codegen/Lower.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Lower.java Tue May 13 11:30:40 2014 +0200
@@ -28,13 +28,13 @@
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.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.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.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;
@@ -157,15 +160,6 @@
}
@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
//whatever is after the last terminal. block append won't append past it
@@ -247,19 +241,15 @@
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);
}
@@ -278,6 +268,16 @@
}
@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 @@
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 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 @@
@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 Block finallyBody = tryNode.getFinallyBody();
if (finallyBody == null) {
- return addStatement(tryNode);
+ return addStatement(ensureUnconditionalCatch(tryNode));
}
/*
@@ -494,7 +513,7 @@
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 @@
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 @@
@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 @@
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 @@
@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 @@
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 @@
* @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 @@
*/
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 @@
}
return false;
}
-
}
--- a/nashorn/src/jdk/nashorn/internal/codegen/MapCreator.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/MapCreator.java Tue May 13 11:30:40 2014 +0200
@@ -139,10 +139,6 @@
flags |= Property.NOT_CONFIGURABLE;
}
- if (symbol.canBeUndefined()) {
- flags |= Property.CAN_BE_UNDEFINED;
- }
-
if (symbol.isFunctionDeclaration()) {
flags |= Property.IS_FUNCTION_DECLARATION;
}
--- a/nashorn/src/jdk/nashorn/internal/codegen/MapTuple.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/MapTuple.java Tue May 13 11:30:40 2014 +0200
@@ -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 @@
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;
}
--- a/nashorn/src/jdk/nashorn/internal/codegen/MethodEmitter.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/MethodEmitter.java Tue May 13 11:30:40 2014 +0200
@@ -43,11 +43,11 @@
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 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.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.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 @@
/** 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 @@
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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
*/
MethodEmitter shr() {
debug("shr");
- popType(Type.INT);
- pushType(popInteger().shr(method));
+ popInteger();
+ pushType(popBitwise().shr(method));
return this;
}
@@ -632,21 +649,21 @@
*/
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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
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 @@
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 @@
return this;
}
- private Symbol compilerConstant(final CompilerConstants cc) {
+ private Symbol getCompilerConstantSymbol(final CompilerConstants cc) {
return functionNode.getBody().getExistingSymbol(cc.symbolName());
}
@@ -985,22 +1035,38 @@
* @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 @@
/**
* 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 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.
*/
- void store(final Symbol symbol) {
+ 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);
+ }
+
+ /**
+ * 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);
}
- void ensureLocalVariableCount(final int slotCount) {
- while(localVariableTypes.size() < slotCount) {
- localVariableTypes.add(Type.UNKNOWN);
+ /**
+ * 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 @@
final Type receiver = popType(Type.OBJECT);
assert Throwable.class.isAssignableFrom(receiver.getTypeClass()) : receiver.getTypeClass();
method.visitInsn(ATHROW);
- stack = null;
+ doesNotContinueSequentially();
}
/**
@@ -1354,7 +1558,7 @@
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 @@
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 @@
convert(type);
}
popType(type)._return(method);
- stack = null;
+ doesNotContinueSequentially();
}
/**
@@ -1449,7 +1653,7 @@
debug("return [void]");
assert stack.isEmpty() : stack;
method.visitInsn(RETURN);
- stack = null;
+ doesNotContinueSequentially();
}
/**
@@ -1458,8 +1662,10 @@
* 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 @@
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 @@
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
+ }
+
+ /**
+ * 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.
+ *
+ * @param loopStart start label of a loop
+ */
+ void gotoLoopStart(final Label loopStart) {
+ debug("goto (loop)", loopStart);
+ jump(GOTO, loopStart, 0);
}
/**
- * 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 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.
*
- * @return true if stacks are equivalent, false otherwise
+ * @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 @@
*
* @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 @@
* @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 @@
*
* @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 @@
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 @@
return this;
}
-
private static String getProgramPoint(final int flags) {
if((flags & CALLSITE_OPTIMISTIC) == 0) {
return "";
@@ -2236,6 +2473,44 @@
}
}
+ 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 @@
pad--;
}
- if (stack != null && !stack.isEmpty()) {
+ if (isReachable() && !stack.isEmpty()) {
sb.append("{");
sb.append(stack.size());
sb.append(":");
@@ -2408,8 +2683,14 @@
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) {
--- a/nashorn/src/jdk/nashorn/internal/codegen/ObjectClassGenerator.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/ObjectClassGenerator.java Tue May 13 11:30:40 2014 +0200
@@ -38,12 +38,12 @@
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.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 @@
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 @@
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 @@
*
* @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 @@
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 @@
* @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 @@
* @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 @@
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 @@
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 @@
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:
--- a/nashorn/src/jdk/nashorn/internal/codegen/ObjectCreator.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/ObjectCreator.java Tue May 13 11:30:40 2014 +0200
@@ -128,11 +128,12 @@
* 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 {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/src/jdk/nashorn/internal/codegen/OptimisticTypesCalculator.java Tue May 13 11:30:40 2014 +0200
@@ -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;
+ }
+}
--- a/nashorn/src/jdk/nashorn/internal/codegen/ProgramPoints.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/ProgramPoints.java Tue May 13 11:30:40 2014 +0200
@@ -27,11 +27,9 @@
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 @@
*/
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 @@
}
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 @@
@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 @@
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 @@
@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);
}
}
--- a/nashorn/src/jdk/nashorn/internal/codegen/RangeAnalyzer.java Mon May 05 14:17:20 2014 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -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;
- }
-}
--- a/nashorn/src/jdk/nashorn/internal/codegen/SpillObjectCreator.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/SpillObjectCreator.java Tue May 13 11:30:40 2014 +0200
@@ -32,7 +32,7 @@
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 @@
}
@Override
- protected void loadValue(final Expression node) {
- codegen.load(node);
+ protected void loadValue(final Expression expr, final Type type) {
+ codegen.loadExpressionAsType(expr, type);
}
}
--- a/nashorn/src/jdk/nashorn/internal/codegen/SplitMethodEmitter.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/SplitMethodEmitter.java Tue May 13 11:30:40 2014 +0200
@@ -25,13 +25,13 @@
package jdk.nashorn.internal.codegen;
+import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
+
import java.util.ArrayList;
import java.util.List;
-
-import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
-
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 @@
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 @@
}
@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 @@
_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 @@
return this;
}
- @Override
final List<Label> getExternalTargets() {
return externalTargets;
}
+
+ final List<BreakableNode> getExternalTargetNodes() {
+ return externalTargetNodes;
+ }
}
--- a/nashorn/src/jdk/nashorn/internal/codegen/WeighNodes.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/WeighNodes.java Tue May 13 11:30:40 2014 +0200
@@ -307,11 +307,6 @@
}
@Override
- public Node leaveDISCARD(final UnaryNode unaryNode) {
- return unaryNodeWeight(unaryNode);
- }
-
- @Override
public Node leaveNEW(final UnaryNode unaryNode) {
weight += NEW_WEIGHT;
return unaryNode;
--- a/nashorn/src/jdk/nashorn/internal/codegen/types/BooleanType.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/types/BooleanType.java Tue May 13 11:30:40 2014 +0200
@@ -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.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 @@
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 @@
}
if (to.isNumber()) {
- convert(method, OBJECT);
- invokestatic(method, JSType.TO_NUMBER);
- } else if (to.isInteger()) {
- return to; // do nothing.
+ method.visitInsn(I2D);
} else if (to.isLong()) {
- convert(method, OBJECT);
- invokestatic(method, JSType.TO_UINT32);
- } else if (to.isLong()) {
- convert(method, OBJECT);
- invokestatic(method, JSType.TO_LONG);
+ method.visitInsn(I2L);
+ } else if (to.isInteger()) {
+ //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 @@
@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;
}
}
--- a/nashorn/src/jdk/nashorn/internal/codegen/types/BytecodeNumericOps.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/types/BytecodeNumericOps.java Tue May 13 11:30:40 2014 +0200
@@ -84,7 +84,7 @@
* @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.
--- a/nashorn/src/jdk/nashorn/internal/codegen/types/IntType.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/types/IntType.java Tue May 13 11:30:40 2014 +0200
@@ -28,6 +28,7 @@
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_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 @@
@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 @@
@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;
}
--- a/nashorn/src/jdk/nashorn/internal/codegen/types/LongType.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/types/LongType.java Tue May 13 11:30:40 2014 +0200
@@ -27,21 +27,24 @@
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 @@
@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;
}
--- a/nashorn/src/jdk/nashorn/internal/codegen/types/NumberType.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/types/NumberType.java Tue May 13 11:30:40 2014 +0200
@@ -164,7 +164,7 @@
}
@Override
- public Type rem(final MethodVisitor method) {
+ public Type rem(final MethodVisitor method, final int programPoint) {
method.visitInsn(DREM);
return NUMBER;
}
--- a/nashorn/src/jdk/nashorn/internal/codegen/types/Range.java Mon May 05 14:17:20 2014 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -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);
- }
-}
--- a/nashorn/src/jdk/nashorn/internal/codegen/types/Type.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/codegen/types/Type.java Tue May 13 11:30:40 2014 +0200
@@ -54,7 +54,6 @@
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;
@@ -498,6 +497,37 @@
}
/**
+ * 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
*
* @param type0 type one
@@ -752,17 +782,17 @@
/**
* 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
--- a/nashorn/src/jdk/nashorn/internal/ir/AccessNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/AccessNode.java Tue May 13 11:30:40 2014 +0200
@@ -34,8 +34,8 @@
*/
@Immutable
public final class AccessNode extends BaseNode {
- /** Property ident. */
- private final IdentNode property;
+ /** Property name. */
+ private final String property;
/**
* Constructor
@@ -45,13 +45,13 @@
* @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 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 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 @@
}
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 @@
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 @@
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 @@
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);
- }
-
}
--- a/nashorn/src/jdk/nashorn/internal/ir/BaseNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/BaseNode.java Tue May 13 11:30:40 2014 +0200
@@ -27,6 +27,7 @@
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 @@
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 @@
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 @@
* @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 @@
}
@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 @@
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.
--- a/nashorn/src/jdk/nashorn/internal/ir/BinaryNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/BinaryNode.java Tue May 13 11:30:40 2014 +0200
@@ -29,7 +29,9 @@
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 @@
*/
@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 @@
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 @@
TokenType.ASSIGN_MOD,
TokenType.ASSIGN_MUL,
TokenType.ASSIGN_SUB
- }));
+ })));
/**
* Constructor
@@ -77,23 +83,25 @@
*/
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:
@@ -111,6 +119,36 @@
}
/**
+ * 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,9 +156,48 @@
*/
@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:
case ASSIGN_SHL:
@@ -135,13 +212,36 @@
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 @@
}
}
+ 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 @@
@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;
@@ -246,6 +353,30 @@
}
@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 @@
}
if (isOptimistic()) {
- sb.append(Node.OPT_IDENTIFIER);
+ sb.append(Expression.OPT_IDENTIFIER);
}
sb.append(' ');
@@ -320,7 +451,7 @@
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 @@
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 @@
@Override
public boolean canBeOptimistic() {
- return getMostOptimisticType() != getMostPessimisticType();
+ return isTokenType(TokenType.ADD) || (getMostOptimisticType() != getMostPessimisticType());
}
@Override
@@ -350,22 +481,16 @@
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 @@
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);
}
}
--- a/nashorn/src/jdk/nashorn/internal/ir/Block.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/Block.java Tue May 13 11:30:40 2014 +0200
@@ -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.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,17 +57,15 @@
/** 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 @@
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 @@
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 @@
this.entryLabel = new Label(block.entryLabel);
this.breakLabel = new Label(block.breakLabel);
this.finish = finish;
+ this.conversion = conversion;
}
/**
@@ -131,8 +128,8 @@
}
/**
- * 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 @@
@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 @@
}
/**
- * 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 @@
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 @@
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
*
@@ -275,6 +272,17 @@
}
/**
+ * 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
*
* @param lc lexical context
@@ -289,7 +297,7 @@
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 @@
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 @@
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));
}
/**
--- a/nashorn/src/jdk/nashorn/internal/ir/BreakNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/BreakNode.java Tue May 13 11:30:40 2014 +0200
@@ -32,9 +32,7 @@
* 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 @@
* @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 @@
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";
}
}
--- a/nashorn/src/jdk/nashorn/internal/ir/BreakableNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/BreakableNode.java Tue May 13 11:30:40 2014 +0200
@@ -32,7 +32,7 @@
* 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
--- a/nashorn/src/jdk/nashorn/internal/ir/BreakableStatement.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/BreakableStatement.java Tue May 13 11:30:40 2014 +0200
@@ -36,6 +36,8 @@
/** break label. */
protected final Label breakLabel;
+ final LocalVariableConversion conversion;
+
/**
* Constructor
*
@@ -47,16 +49,19 @@
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 @@
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);
}
--- a/nashorn/src/jdk/nashorn/internal/ir/CallNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/CallNode.java Tue May 13 11:30:40 2014 +0200
@@ -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 @@
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 @@
* @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 @@
}
@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 @@
@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 @@
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 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);
- }
}
--- a/nashorn/src/jdk/nashorn/internal/ir/CaseNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/CaseNode.java Tue May 13 11:30:40 2014 +0200
@@ -34,7 +34,7 @@
* 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;
@@ -45,6 +45,11 @@
private final Label entry;
/**
+ * @see JoinPredecessor
+ */
+ private final LocalVariableConversion conversion;
+
+ /**
* Constructors
*
* @param token token
@@ -58,14 +63,16 @@
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 @@
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);
}
}
--- a/nashorn/src/jdk/nashorn/internal/ir/CatchNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/CatchNode.java Tue May 13 11:30:40 2014 +0200
@@ -42,10 +42,7 @@
/** 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 @@
* @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 @@
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 @@
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 @@
* @return true if a finally synthetic rethrow
*/
public boolean isSyntheticRethrow() {
- return (flags & IS_SYNTHETIC_RETHROW) == IS_SYNTHETIC_RETHROW;
+ return isSyntheticRethrow;
}
-
}
--- a/nashorn/src/jdk/nashorn/internal/ir/ContinueNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/ContinueNode.java Tue May 13 11:30:40 2014 +0200
@@ -32,26 +32,21 @@
* 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 @@
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";
}
}
--- a/nashorn/src/jdk/nashorn/internal/ir/Expression.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/Expression.java Tue May 13 11:30:40 2014 +0200
@@ -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 @@
*
*/
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 @@
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
- *
- * @return the symbol
- */
- public Symbol getSymbol() {
- return symbol;
}
/**
- * Assign a symbol to this node. See {@link Expression#getSymbol()} for explanation
- * of what a symbol is
+ * Returns the type of the expression.
*
- * @param lc lexical context
- * @param symbol the symbol
- * @return new node
+ * @return the type of the expression.
*/
- 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;
+ public final Type getType() {
+ return getType(UNKNOWN_LOCALS);
}
/**
- * 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
+ * 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 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 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();
+ }
}
--- a/nashorn/src/jdk/nashorn/internal/ir/ForNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/ForNode.java Tue May 13 11:30:40 2014 +0200
@@ -33,11 +33,12 @@
*/
@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 @@
* @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);
+ 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 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;
- }
-
- 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);
- 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 @@
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 @@
@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 @@
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 @@
* Get the modification expression for this ForNode
* @return the modification expression
*/
- public Expression getModify() {
+ public JoinPredecessorExpression getModify() {
return modify;
}
@@ -222,24 +224,19 @@
* @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 @@
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 @@
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));
+ }
}
--- a/nashorn/src/jdk/nashorn/internal/ir/FunctionNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/FunctionNode.java Tue May 13 11:30:40 2014 +0200
@@ -32,7 +32,7 @@
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 @@
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 @@
@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 @@
/** 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 @@
* 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 @@
this.sourceURL = sourceURL;
this.compileUnit = null;
this.body = null;
+ this.thisProperties = 0;
}
private FunctionNode(
@@ -297,7 +304,8 @@
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 @@
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 @@
this.declaredSymbols = functionNode.declaredSymbols;
this.kind = functionNode.kind;
this.firstToken = functionNode.firstToken;
- this.thisProperties = functionNode.thisProperties;
}
@Override
@@ -330,6 +338,15 @@
}
/**
+ * 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 @@
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));
}
/**
@@ -386,14 +403,6 @@
}
/**
- * 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 @@
}
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 @@
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 @@
}
/**
- * 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 @@
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 @@
}
/**
- * 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 @@
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 @@
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));
}
/**
@@ -813,6 +824,16 @@
}
/**
+ * 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
* @param lc lexical context
@@ -823,7 +844,7 @@
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 @@
* @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 @@
//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 @@
type,
compileUnit,
compilationState,
- body.setReturnType(type),
- parameters
+ body,
+ parameters,
+ thisProperties
));
}
@@ -927,7 +954,7 @@
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));
}
/**
--- a/nashorn/src/jdk/nashorn/internal/ir/IdentNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/IdentNode.java Tue May 13 11:30:40 2014 +0200
@@ -28,33 +28,40 @@
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 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 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 @@
@Override
public void toString(final StringBuilder sb) {
- Node.optimisticType(this, sb);
+ optimisticTypeToString(sb, symbol == null || !symbol.hasSlot());
sb.append(name);
}
@@ -140,6 +163,31 @@
}
/**
+ * 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 @@
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 @@
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 @@
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 @@
}
@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 @@
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 @@
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 @@
}
@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 @@
}
@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;
}
}
--- a/nashorn/src/jdk/nashorn/internal/ir/IfNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/IfNode.java Tue May 13 11:30:40 2014 +0200
@@ -32,7 +32,7 @@
* 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;
@@ -43,6 +43,12 @@
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
*
* @param lineNumber line number
@@ -57,13 +63,15 @@
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 @@
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 @@
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 @@
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;
}
}
--- a/nashorn/src/jdk/nashorn/internal/ir/IndexNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/IndexNode.java Tue May 13 11:30:40 2014 +0200
@@ -49,8 +49,8 @@
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 @@
sb.append('(');
}
- Node.optimisticType(this, sb);
+ optimisticTypeToString(sb);
base.toString(sb);
@@ -97,7 +97,7 @@
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 @@
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 @@
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 @@
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);
}
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/src/jdk/nashorn/internal/ir/JoinPredecessor.java Tue May 13 11:30:40 2014 +0200
@@ -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();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/src/jdk/nashorn/internal/ir/JoinPredecessorExpression.java Tue May 13 11:30:40 2014 +0200
@@ -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);
+ }
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/src/jdk/nashorn/internal/ir/JumpStatement.java Tue May 13 11:30:40 2014 +0200
@@ -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;
+ }
+}
--- a/nashorn/src/jdk/nashorn/internal/ir/LabelNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/LabelNode.java Tue May 13 11:30:40 2014 +0200
@@ -29,36 +29,42 @@
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 @@
@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 @@
@Override
public void toString(final StringBuilder sb) {
- label.toString(sb);
- sb.append(':');
+ sb.append(labelName).append(':');
}
/**
@@ -101,22 +104,27 @@
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));
}
-
}
--- a/nashorn/src/jdk/nashorn/internal/ir/LexicalContext.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/LexicalContext.java Tue May 13 11:30:40 2014 +0200
@@ -27,7 +27,6 @@
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;
@@ -203,6 +202,19 @@
}
/**
+ * 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 @@
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 @@
* @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 @@
* @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 @@
}
/**
- * 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 @@
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 @@
/**
* 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 @@
/**
* 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 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 @@
}
/**
- * 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
--- a/nashorn/src/jdk/nashorn/internal/ir/LexicalContextExpression.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/LexicalContextExpression.java Tue May 13 11:30:40 2014 +0200
@@ -45,15 +45,4 @@
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));
- }
}
--- a/nashorn/src/jdk/nashorn/internal/ir/LiteralNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/LiteralNode.java Tue May 13 11:30:40 2014 +0200
@@ -28,6 +28,7 @@
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 @@
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 @@
}
@Override
- public Type getType() {
+ public Type getType(final Function<Symbol, Type> localVariableTypes) {
return Type.typeFor(value.getClass());
}
@@ -226,7 +222,7 @@
* Get the literal node value
* @return the value
*/
- public T getValue() {
+ public final T getValue() {
return value;
}
@@ -279,6 +275,16 @@
public boolean isLocal() {
return true;
}
+
+ @Override
+ public boolean isAlwaysFalse() {
+ return !isTrue();
+ }
+
+ @Override
+ public boolean isAlwaysTrue() {
+ return isTrue();
+ }
}
@Immutable
@@ -298,7 +304,7 @@
}
@Override
- public Type getType() {
+ public Type getType(final Function<Symbol, Type> localVariableTypes) {
return Type.BOOLEAN;
}
@@ -361,7 +367,7 @@
}
@Override
- public Type getType() {
+ public Type getType(final Function<Symbol, Type> localVariableTypes) {
return type;
}
@@ -485,7 +491,7 @@
}
@Override
- public Type getType() {
+ public Type getType(final Function<Symbol, Type> localVariableTypes) {
return Type.OBJECT;
}
@@ -554,7 +560,7 @@
}
@Override
- public Type getType() {
+ public Type getType(final Function<Symbol, Type> localVariableTypes) {
return Type.OBJECT;
}
@@ -567,7 +573,7 @@
/**
* 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 @@
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 @@
}
@Override
- public Type getType() {
+ public Type getType(final Function<Symbol, Type> localVariableTypes) {
return Type.typeFor(NativeArray.class);
}
@@ -866,16 +876,21 @@
@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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/src/jdk/nashorn/internal/ir/LocalVariableConversion.java Tue May 13 11:30:40 2014 +0200
@@ -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();
+ }
+}
--- a/nashorn/src/jdk/nashorn/internal/ir/LoopNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/LoopNode.java Tue May 13 11:30:40 2014 +0200
@@ -37,7 +37,7 @@
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 @@
* @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 @@
* @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 @@
* 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 @@
* @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 @@
* @return new loop node if changed otherwise the same
*/
public abstract LoopNode setControlFlowEscapes(final LexicalContext lc, final boolean controlFlowEscapes);
-
}
--- a/nashorn/src/jdk/nashorn/internal/ir/Node.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/Node.java Tue May 13 11:30:40 2014 +0200
@@ -27,7 +27,6 @@
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;
@@ -82,15 +81,6 @@
}
/**
- * 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?
*
* @return true if atom
@@ -110,31 +100,6 @@
}
/**
- * 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
* @param lc lexical context
@@ -282,21 +247,6 @@
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 @@
}
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('}');
- }
- }
-
}
--- a/nashorn/src/jdk/nashorn/internal/ir/ObjectNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/ObjectNode.java Tue May 13 11:30:40 2014 +0200
@@ -27,6 +27,8 @@
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;
@@ -66,6 +68,11 @@
}
@Override
+ public Type getType(final Function<Symbol, Type> localVariableTypes) {
+ return Type.OBJECT;
+ }
+
+ @Override
public void toString(final StringBuilder sb) {
sb.append('{');
--- a/nashorn/src/jdk/nashorn/internal/ir/Optimistic.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/Optimistic.java Tue May 13 11:30:40 2014 +0200
@@ -70,13 +70,6 @@
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 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);
}
--- a/nashorn/src/jdk/nashorn/internal/ir/OptimisticLexicalContext.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/OptimisticLexicalContext.java Tue May 13 11:30:40 2014 +0200
@@ -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 @@
/** 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 @@
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 @@
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;
- }
}
--- a/nashorn/src/jdk/nashorn/internal/ir/RuntimeNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/RuntimeNode.java Tue May 13 11:30:40 2014 +0200
@@ -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 @@
* @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 @@
}
/**
- * 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 @@
* Return type for the ReferenceNode
*/
@Override
- public Type getType() {
+ public Type getType(Function<Symbol, Type> localVariableTypes) {
return request.getReturnType();
}
@@ -568,17 +573,7 @@
}
@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;
}
}
--- a/nashorn/src/jdk/nashorn/internal/ir/SplitNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/SplitNode.java Tue May 13 11:30:40 2014 +0200
@@ -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 @@
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 @@
* @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 @@
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 @@
@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 @@
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);
+ }
}
--- a/nashorn/src/jdk/nashorn/internal/ir/SwitchNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/SwitchNode.java Tue May 13 11:30:40 2014 +0200
@@ -66,11 +66,12 @@
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 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 @@
* 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 @@
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 @@
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 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;
+ }
}
-
--- a/nashorn/src/jdk/nashorn/internal/ir/Symbol.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/Symbol.java Tue May 13 11:30:40 2014 +0200
@@ -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 @@
/** 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 @@
/** 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 @@
*
* @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 @@
* @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) {
@@ -190,17 +166,6 @@
}
/**
- * 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 .
*
* @param stream Stream to print to.
@@ -211,14 +176,10 @@
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 @@
sb.append(" this");
}
- if (!canBeUndefined()) {
- sb.append(" def'd");
- }
-
if (isProgramLevel()) {
sb.append(" program");
}
@@ -281,7 +238,12 @@
* @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 @@
* @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 @@
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 @@
}
/**
- * 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 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 @@
* @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 @@
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.
*/
@@ -451,76 +399,11 @@
}
/**
- * 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 @@
* @return true if let
*/
public boolean isLet() {
- return (flags & IS_LET) == IS_LET;
+ return (flags & IS_LET) != 0;
}
/**
@@ -561,7 +444,6 @@
*/
public void setIsLet() {
if (!isLet()) {
- assert !isShared();
flags |= IS_LET;
}
}
@@ -571,7 +453,7 @@
* @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 Symbol setFieldIndex(final int fieldIndex) {
if (this.fieldIndex != fieldIndex) {
- assert !isShared();
this.fieldIndex = fieldIndex;
}
return this;
@@ -615,7 +496,6 @@
*/
public Symbol setFlags(final int flags) {
if (this.flags != flags) {
- assert !isShared() : this;
this.flags = flags;
}
return this;
@@ -628,7 +508,6 @@
*/
public Symbol setFlag(final int flag) {
if ((this.flags & flag) == 0) {
- assert !isShared() : this;
this.flags |= flag;
}
return this;
@@ -641,7 +520,6 @@
*/
public Symbol clearFlag(final int flag) {
if ((this.flags & flag) != 0) {
- assert !isShared() : this;
this.flags &= ~flag;
}
return this;
@@ -656,11 +534,73 @@
}
/**
- * 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 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() {
- return 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 @@
/**
* Set the bytecode slot for this symbol
- * @param slot valid bytecode slot, or -1 if not available
- * @return the symbol
- */
- public Symbol setSlot(final int slot) {
- if (slot != this.slot) {
- assert !isShared();
- if(shouldTrace()) {
- trace("SET SLOT " + slot);
- }
- 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
+ * @param firstSlot valid bytecode slot
* @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;
+ public Symbol setFirstSlot(final int firstSlot) {
+ assert firstSlot >= 0 && firstSlot <= 65535;
+ if (firstSlot != this.firstSlot) {
if(shouldTrace()) {
- trace("TYPE CHANGE: " + old + "=>" + type + " == " + type);
+ trace("SET SLOT " + firstSlot);
}
- 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;
}
--- a/nashorn/src/jdk/nashorn/internal/ir/TemporarySymbols.java Mon May 05 14:17:20 2014 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -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;
- }
- }
- }
-
-}
--- a/nashorn/src/jdk/nashorn/internal/ir/TernaryNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/TernaryNode.java Tue May 13 11:30:40 2014 +0200
@@ -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 @@
* @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 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 @@
&& 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 @@
* 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 @@
* 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 @@
* @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 @@
* @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;
}
--- a/nashorn/src/jdk/nashorn/internal/ir/ThrowNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/ThrowNode.java Tue May 13 11:30:40 2014 +0200
@@ -32,14 +32,13 @@
* 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 @@
* @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 @@
if (expression != null) {
expression.toString(sb);
}
+ if(conversion != null) {
+ conversion.toString(sb);
+ }
}
/**
@@ -106,7 +111,7 @@
if (this.expression == expression) {
return this;
}
- return new ThrowNode(this, expression, flags);
+ return new ThrowNode(this, expression, isSyntheticRethrow, conversion);
}
/**
@@ -116,7 +121,20 @@
* @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;
}
}
--- a/nashorn/src/jdk/nashorn/internal/ir/TryNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/TryNode.java Tue May 13 11:30:40 2014 +0200
@@ -28,8 +28,6 @@
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 @@
* 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 @@
/** 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 @@
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 @@
setBody(newBody).
setFinallyBody(newFinallyBody).
setCatchBlocks(Node.accept(visitor, Block.class, catchBlocks)).
- setException(exception).
setFinallyCatchAll(finallyCatchAll));
}
@@ -144,7 +141,7 @@
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 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 @@
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 Symbol getException() {
return exception;
}
-
/**
* Set the exception symbol for this try block
* @param exception a symbol for the compiler to store the exception in
@@ -219,14 +219,6 @@
}
/**
- * 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 @@
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;
}
}
--- a/nashorn/src/jdk/nashorn/internal/ir/UnaryNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/UnaryNode.java Tue May 13 11:30:40 2014 +0200
@@ -33,6 +33,7 @@
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 @@
private final int programPoint;
- private final boolean isOptimistic;
-
private final Type type;
@Ignore
@@ -88,16 +87,14 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
@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);
}
}
--- a/nashorn/src/jdk/nashorn/internal/ir/VarNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/VarNode.java Tue May 13 11:30:40 2014 +0200
@@ -123,8 +123,9 @@
@Override
public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
if (visitor.enterVarNode(this)) {
+ // 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 Expression newInit = init == null ? null : (Expression)init.accept(visitor);
final VarNode newThis;
if (name != newName || init != newInit) {
newThis = new VarNode(this, newName, newInit, flags);
--- a/nashorn/src/jdk/nashorn/internal/ir/WhileNode.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/WhileNode.java Tue May 13 11:30:40 2014 +0200
@@ -47,7 +47,7 @@
* @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 @@
* @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 @@
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 @@
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 @@
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));
}
/**
--- a/nashorn/src/jdk/nashorn/internal/ir/debug/ASTWriter.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/debug/ASTWriter.java Tue May 13 11:30:40 2014 +0200
@@ -36,6 +36,7 @@
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 @@
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;
}
--- a/nashorn/src/jdk/nashorn/internal/ir/debug/JSONWriter.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/debug/JSONWriter.java Tue May 13 11:30:40 2014 +0200
@@ -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.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;
@@ -100,6 +102,17 @@
}
@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();
location(node);
@@ -129,8 +142,7 @@
accessNode.getBase().accept(this);
comma();
- property("property");
- accessNode.getProperty().accept(this);
+ property("property", accessNode.getProperty());
comma();
property("computed", false);
@@ -150,16 +162,6 @@
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 @@
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 @@
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 @@
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 @@
type("LabeledStatement");
comma();
- property("label");
- labelNode.getLabel().accept(this);
+ property("label", labelNode.getLabelName());
comma();
property("body");
--- a/nashorn/src/jdk/nashorn/internal/ir/debug/NashornTextifier.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/debug/NashornTextifier.java Tue May 13 11:30:40 2014 +0200
@@ -39,7 +39,6 @@
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 @@
}
} 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));
}
}
--- a/nashorn/src/jdk/nashorn/internal/ir/debug/PrintVisitor.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/debug/PrintVisitor.java Tue May 13 11:30:40 2014 +0200
@@ -29,18 +29,25 @@
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;
@@ -140,6 +147,27 @@
}
@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(' ');
sb.append('{');
@@ -190,6 +218,7 @@
sb.append(EOLN);
indent();
sb.append('}');
+ printLocalVariableConversion(block);
return false;
}
@@ -211,6 +240,24 @@
}
@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() {
@Override
@@ -252,7 +299,12 @@
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 @@
indent += TABWIDTH;
labeledNode.toString(sb);
labeledNode.getBody().accept(this);
-
+ printLocalVariableConversion(labeledNode);
return false;
}
@@ -296,12 +348,19 @@
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 @@
@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 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 @@
@Override
public boolean enterWhileNode(final WhileNode whileNode) {
+ printLocalVariableConversion(whileNode);
if (whileNode.isDoWhile()) {
sb.append("do");
whileNode.getBody().accept(this);
--- a/nashorn/src/jdk/nashorn/internal/ir/visitor/NodeOperatorVisitor.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/visitor/NodeOperatorVisitor.java Tue May 13 11:30:40 2014 +0200
@@ -53,8 +53,6 @@
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 @@
return leaveBIT_NOT(unaryNode);
case DELETE:
return leaveDELETE(unaryNode);
- case DISCARD:
- return leaveDISCARD(unaryNode);
case NEW:
return leaveNEW(unaryNode);
case NOT:
@@ -359,26 +355,6 @@
}
/**
- * 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
*
* @param unaryNode the node
--- a/nashorn/src/jdk/nashorn/internal/ir/visitor/NodeVisitor.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/ir/visitor/NodeVisitor.java Tue May 13 11:30:40 2014 +0200
@@ -41,6 +41,7 @@
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;
@@ -689,6 +690,27 @@
}
/**
+ * 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
*
* @param varNode the node
--- a/nashorn/src/jdk/nashorn/internal/objects/Global.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/objects/Global.java Tue May 13 11:30:40 2014 +0200
@@ -45,7 +45,6 @@
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 @@
* 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 @@
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());
--- a/nashorn/src/jdk/nashorn/internal/objects/NativeString.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/objects/NativeString.java Tue May 13 11:30:40 2014 +0200
@@ -1147,7 +1147,7 @@
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 @@
@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 @@
*/
@SpecializedConstructor
public static Object constructor(final boolean newObj, final Object self) {
- return newObj ? newObj(self, "") : "";
+ return newObj ? newObj("") : "";
}
/**
@@ -1197,7 +1197,7 @@
@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 @@
@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 @@
@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;
}
/**
--- a/nashorn/src/jdk/nashorn/internal/objects/ScriptFunctionImpl.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/objects/ScriptFunctionImpl.java Tue May 13 11:30:40 2014 +0200
@@ -30,7 +30,6 @@
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 @@
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();
}
/**
--- a/nashorn/src/jdk/nashorn/internal/parser/Parser.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/parser/Parser.java Tue May 13 11:30:40 2014 +0200
@@ -61,7 +61,6 @@
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.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 @@
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 @@
}
// 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 @@
*/
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 @@
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 @@
next();
// Get the collection expression.
- forNode = forNode.setModify(lc, expression());
+ forNode = forNode.setModify(lc, joinPredecessorExpression());
break;
default:
@@ -1343,7 +1342,7 @@
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 @@
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 @@
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 @@
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 @@
//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 @@
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 @@
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 @@
endOfLine();
- appendStatement(new ThrowNode(throwLine, throwToken, finish, expression, 0));
+ appendStatement(new ThrowNode(throwLine, throwToken, finish, expression, false));
}
/**
@@ -1831,7 +1830,7 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
switch (type) {
case NEW:
- // Get new exppression.
+ // Get new expression.
lhs = newExpression();
break;
@@ -2505,7 +2504,7 @@
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 @@
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 @@
*/
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 @@
parameters.add(ident);
}
+ parameters.trimToSize();
return parameters;
}
@@ -3089,6 +3089,10 @@
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 @@
// 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();
--- a/nashorn/src/jdk/nashorn/internal/parser/TokenType.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/parser/TokenType.java Tue May 13 11:30:40 2014 +0200
@@ -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.SPECIAL;
import static jdk.nashorn.internal.parser.TokenKind.UNARY;
+import java.util.Locale;
+
/**
* Description of all the JavaScript tokens.
*/
@@ -182,7 +183,6 @@
ARRAY (LITERAL, null),
COMMALEFT (IR, null),
- DISCARD (IR, null),
DECPOSTFIX (IR, null),
INCPOSTFIX (IR, null);
--- a/nashorn/src/jdk/nashorn/internal/runtime/AccessorProperty.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/AccessorProperty.java Tue May 13 11:30:40 2014 +0200
@@ -42,7 +42,6 @@
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 @@
* 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) {
--- a/nashorn/src/jdk/nashorn/internal/runtime/CompiledFunction.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/CompiledFunction.java Tue May 13 11:30:40 2014 +0200
@@ -37,7 +37,6 @@
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 @@
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 @@
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 @@
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 @@
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 @@
* 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 @@
// 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 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 @@
}
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 @@
* @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 @@
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 {
--- a/nashorn/src/jdk/nashorn/internal/runtime/JSType.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/JSType.java Tue May 13 11:30:40 2014 +0200
@@ -145,13 +145,16 @@
/** 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 @@
/** 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);
@@ -1418,6 +1424,24 @@
}
/**
+ * 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.
*
@@ -1443,6 +1467,24 @@
}
/**
+ * 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
*
* Catches ArithmeticException and rethrows as UnwarrantedOptimismException
--- a/nashorn/src/jdk/nashorn/internal/runtime/OptimisticReturnFilters.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/OptimisticReturnFilters.java Tue May 13 11:30:40 2014 +0200
@@ -49,6 +49,7 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
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;
--- a/nashorn/src/jdk/nashorn/internal/runtime/Property.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/Property.java Tue May 13 11:30:40 2014 +0200
@@ -73,11 +73,8 @@
/** 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 @@
* 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. */
@@ -654,18 +651,6 @@
}
/**
- * 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.
*/
--- a/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Tue May 13 11:30:40 2014 +0200
@@ -36,7 +36,6 @@
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.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 @@
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 @@
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 @@
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 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;
}
--- a/nashorn/src/jdk/nashorn/internal/runtime/RewriteException.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/RewriteException.java Tue May 13 11:30:40 2014 +0200
@@ -34,8 +34,9 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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';
}
--- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptEnvironment.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptEnvironment.java Tue May 13 11:30:40 2014 +0200
@@ -27,14 +27,11 @@
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 @@
/** 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 @@
_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");
@@ -324,18 +307,6 @@
}
/**
- * 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
*/
--- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java Tue May 13 11:30:40 2014 +0200
@@ -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.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 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);
@@ -1262,6 +1262,14 @@
}
/**
+ * 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.
*/
--- a/nashorn/src/jdk/nashorn/internal/runtime/UnwarrantedOptimismException.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/UnwarrantedOptimismException.java Tue May 13 11:30:40 2014 +0200
@@ -39,7 +39,7 @@
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 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;
--- a/nashorn/src/jdk/nashorn/internal/runtime/linker/Bootstrap.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/linker/Bootstrap.java Tue May 13 11:30:40 2014 +0200
@@ -33,7 +33,6 @@
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 @@
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 @@
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;
--- a/nashorn/src/jdk/nashorn/internal/runtime/linker/JSObjectLinker.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/linker/JSObjectLinker.java Tue May 13 11:30:40 2014 +0200
@@ -30,7 +30,6 @@
import java.lang.invoke.MethodType;
import java.util.HashMap;
import java.util.Map;
-
import jdk.internal.dynalink.CallSiteDescriptor;
import jdk.internal.dynalink.linker.GuardedInvocation;
import jdk.internal.dynalink.linker.GuardedTypeConversion;
@@ -72,7 +71,7 @@
final GuardedInvocation inv;
if (self instanceof JSObject) {
- inv = lookup(desc, request);
+ inv = lookup(desc);
} else {
throw new AssertionError(); // Should never reach here.
}
@@ -96,7 +95,7 @@
}
- private static GuardedInvocation lookup(final CallSiteDescriptor desc, final LinkRequest request) {
+ private static GuardedInvocation lookup(final CallSiteDescriptor desc) {
final String operator = CallSiteDescriptorFactory.tokenizeOperators(desc).get(0);
final int c = desc.getNameTokenCount();
--- a/nashorn/src/jdk/nashorn/internal/runtime/linker/NashornPrimitiveLinker.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/linker/NashornPrimitiveLinker.java Tue May 13 11:30:40 2014 +0200
@@ -39,6 +39,7 @@
import jdk.internal.dynalink.support.TypeUtilities;
import jdk.nashorn.internal.objects.Global;
import jdk.nashorn.internal.runtime.ConsString;
+import jdk.nashorn.internal.runtime.ScriptRuntime;
/**
* Internal linker for String, Boolean, and Number objects, only ever used by Nashorn engine and not exposed to other
@@ -46,6 +47,9 @@
* primitive type conversions for these types when linking to Java methods.
*/
final class NashornPrimitiveLinker implements TypeBasedGuardingDynamicLinker, GuardingTypeConverterFactory, ConversionComparator {
+ private static final GuardedTypeConversion VOID_TO_OBJECT = new GuardedTypeConversion(
+ new GuardedInvocation(MethodHandles.constant(Object.class, ScriptRuntime.UNDEFINED)), true);
+
@Override
public boolean canLinkType(final Class<?> type) {
return canLinkTypeStatic(type);
@@ -77,6 +81,9 @@
public GuardedTypeConversion convertToType(final Class<?> sourceType, final Class<?> targetType) {
final MethodHandle mh = JavaArgumentConverters.getConverter(targetType);
if (mh == null) {
+ if(targetType == Object.class && sourceType == void.class) {
+ return VOID_TO_OBJECT;
+ }
return null;
}
--- a/nashorn/src/jdk/nashorn/internal/runtime/resources/Options.properties Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/internal/runtime/resources/Options.properties Tue May 13 11:30:40 2014 +0200
@@ -306,14 +306,6 @@
desc="Enable scripting features." \
}
-nashorn.option.specialize.calls = { \
- name="--specialize-calls", \
- is_undocumented=true, \
- type=String, \
- params="[=function_1,...,function_n]", \
- desc="EXPERIMENTAL: Specialize all or a set of method according to callsite parameter types" \
-}
-
nashorn.option.stdout = { \
name="--stdout", \
is_undocumented=true, \
--- a/nashorn/src/jdk/nashorn/tools/Shell.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/src/jdk/nashorn/tools/Shell.java Tue May 13 11:30:40 2014 +0200
@@ -37,7 +37,6 @@
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
-
import jdk.nashorn.api.scripting.NashornException;
import jdk.nashorn.internal.codegen.Compiler;
import jdk.nashorn.internal.codegen.CompilerConstants;
@@ -453,7 +452,7 @@
}
} finally {
if (globalChanged) {
- Context.setGlobal(global);
+ Context.setGlobal(oldGlobal);
}
}
--- a/nashorn/test/script/basic/JDK-8012083.js Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/test/script/basic/JDK-8012083.js Tue May 13 11:30:40 2014 +0200
@@ -22,7 +22,7 @@
*/
/**
- * JDK-8012093 - array literals can only be subject to constant evaluation under very special
+ * JDK-8012083 - array literals can only be subject to constant evaluation under very special
* circumstances.
*
* @test
--- a/nashorn/test/script/basic/JDK-8026137.js Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/test/script/basic/JDK-8026137.js Tue May 13 11:30:40 2014 +0200
@@ -30,7 +30,9 @@
*/
try {
- (function f() { Object.defineProperty({},"x",{get: function(){return {valueOf:function(){throw 0}}}}).x - Object.defineProperty({},"x",{get: function(){throw 1}}).x })()
+ (function f() {
+ Object.defineProperty({},"x",{get: function(){return {valueOf:function(){throw 0}}}}).x -
+ Object.defineProperty({},"x",{get: function(){throw 1}}).x })()
}
catch (e) {
print(e);
--- a/nashorn/test/script/basic/NASHORN-737.js.EXPECTED Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/test/script/basic/NASHORN-737.js.EXPECTED Tue May 13 11:30:40 2014 +0200
@@ -3,10 +3,7 @@
"body": [
{
"type": "LabeledStatement",
- "label": {
- "type": "Identifier",
- "name": "label"
- },
+ "label": "label",
"body": {
"type": "BlockStatement",
"body": [
@@ -21,10 +18,7 @@
"body": [
{
"type": "BreakStatement",
- "label": {
- "type": "Identifier",
- "name": "label"
- }
+ "label": "label"
}
]
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/boolean_arithmetic.js Tue May 13 11:30:40 2014 +0200
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * test arithmetic operations on boolean variables
+ *
+ * @test
+ * @run
+ */
+
+function printOp(text, value) {
+ print(text + value);
+}
+
+(function () {
+var t = true;
+var f = false;
+
+print("+")
+printOp("f, f: ", (f + f))
+printOp("f, t: ", (f + t))
+printOp("t, f: ", (t + f))
+printOp("t, t: ", (t + t))
+
+print("-")
+printOp("f, f: ", (f - f))
+printOp("f, t: ", (f - t))
+printOp("t, f: ", (t - f))
+printOp("t, t: ", (t - t))
+
+print("*")
+printOp("f, f: ", (f * f))
+printOp("f, t: ", (f * t))
+printOp("t, f: ", (t * f))
+printOp("t, t: ", (t * t))
+
+print("/")
+printOp("f, f: ", (f / f))
+printOp("f, t: ", (f / t))
+printOp("t, f: ", (t / f))
+printOp("t, t: ", (t / t))
+
+print("<<")
+printOp("f, f: ", (f << f))
+printOp("f, t: ", (f << t))
+printOp("t, f: ", (t << f))
+printOp("t, t: ", (t << t))
+
+print(">>")
+printOp("f, f: ", (f >> f))
+printOp("f, t: ", (f >> t))
+printOp("t, f: ", (t >> f))
+printOp("t, t: ", (t >> t))
+
+print(">>>")
+printOp("f, f: ", (f >>> f))
+printOp("f, t: ", (f >>> t))
+printOp("t, f: ", (t >>> f))
+printOp("t, t: ", (t >>> t))
+
+print("|")
+printOp("f, f: ", (f | f))
+printOp("f, t: ", (f | t))
+printOp("t, f: ", (t | f))
+printOp("t, t: ", (t | t))
+
+print("&")
+printOp("f, f: ", (f & f))
+printOp("f, t: ", (f & t))
+printOp("t, f: ", (t & f))
+printOp("t, t: ", (t & t))
+
+print("^")
+printOp("f, f: ", (f ^ f))
+printOp("f, t: ", (f ^ t))
+printOp("t, f: ", (t ^ f))
+printOp("t, t: ", (t ^ t))
+
+print("~")
+printOp("f: ", (~f))
+printOp("t: ", (~t))
+
+print("+")
+printOp("f: ", (+f))
+printOp("t: ", (+t))
+
+print("-")
+printOp("f: ", (-f))
+printOp("t: ", (-t))
+
+printOp("1/-f: ", (1/-f))
+
+})();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/boolean_arithmetic.js.EXPECTED Tue May 13 11:30:40 2014 +0200
@@ -0,0 +1,60 @@
++
+f, f: 0
+f, t: 1
+t, f: 1
+t, t: 2
+-
+f, f: 0
+f, t: -1
+t, f: 1
+t, t: 0
+*
+f, f: 0
+f, t: 0
+t, f: 0
+t, t: 1
+/
+f, f: NaN
+f, t: 0
+t, f: Infinity
+t, t: 1
+<<
+f, f: 0
+f, t: 0
+t, f: 1
+t, t: 2
+>>
+f, f: 0
+f, t: 0
+t, f: 1
+t, t: 0
+>>>
+f, f: 0
+f, t: 0
+t, f: 1
+t, t: 0
+|
+f, f: 0
+f, t: 1
+t, f: 1
+t, t: 1
+&
+f, f: 0
+f, t: 0
+t, f: 0
+t, t: 1
+^
+f, f: 0
+f, t: 1
+t, f: 1
+t, t: 0
+~
+f: -1
+t: -2
++
+f: 0
+t: 1
+-
+f: 0
+t: -1
+1/-f: -Infinity
--- a/nashorn/test/script/basic/optimistic_check_type.js.EXPECTED Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/test/script/basic/optimistic_check_type.js.EXPECTED Tue May 13 11:30:40 2014 +0200
@@ -27,4 +27,4 @@
outer local true addition local int: double
global int addition outer local true: double
outer local true multiplication by outer local int: double
-outer local int multiplication by global undefined: double
\ No newline at end of file
+outer local int multiplication by global undefined: double
--- a/nashorn/test/script/basic/optimistic_logical_check_type.js.EXPECTED Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/test/script/basic/optimistic_logical_check_type.js.EXPECTED Tue May 13 11:30:40 2014 +0200
@@ -25,4 +25,4 @@
true AND double logical not non-falsey local int : boolean
false AND non-falsey local int: boolean
non-falsey local int OR true: int
-false OR true: boolean
\ No newline at end of file
+false OR true: boolean
--- a/nashorn/test/script/basic/parser/breakStat.js.EXPECTED Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/test/script/basic/parser/breakStat.js.EXPECTED Tue May 13 11:30:40 2014 +0200
@@ -24,10 +24,7 @@
"body": [
{
"type": "LabeledStatement",
- "label": {
- "type": "Identifier",
- "name": "loop"
- },
+ "label": "loop",
"body": {
"type": "BlockStatement",
"body": [
@@ -42,10 +39,7 @@
"body": [
{
"type": "BreakStatement",
- "label": {
- "type": "Identifier",
- "name": "loop"
- }
+ "label": "loop"
}
]
}
@@ -60,10 +54,7 @@
"body": [
{
"type": "LabeledStatement",
- "label": {
- "type": "Identifier",
- "name": "loop"
- },
+ "label": "loop",
"body": {
"type": "BlockStatement",
"body": [
@@ -77,10 +68,7 @@
"body": [
{
"type": "BreakStatement",
- "label": {
- "type": "Identifier",
- "name": "loop"
- }
+ "label": "loop"
}
]
}
--- a/nashorn/test/script/basic/parser/continueStat.js.EXPECTED Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/test/script/basic/parser/continueStat.js.EXPECTED Tue May 13 11:30:40 2014 +0200
@@ -24,10 +24,7 @@
"body": [
{
"type": "LabeledStatement",
- "label": {
- "type": "Identifier",
- "name": "begin"
- },
+ "label": "begin",
"body": {
"type": "BlockStatement",
"body": [
@@ -42,10 +39,7 @@
"body": [
{
"type": "ContinueStatement",
- "label": {
- "type": "Identifier",
- "name": "begin"
- }
+ "label": "begin"
}
]
}
@@ -60,10 +54,7 @@
"body": [
{
"type": "LabeledStatement",
- "label": {
- "type": "Identifier",
- "name": "start"
- },
+ "label": "start",
"body": {
"type": "BlockStatement",
"body": [
@@ -77,10 +68,7 @@
"body": [
{
"type": "ContinueStatement",
- "label": {
- "type": "Identifier",
- "name": "start"
- }
+ "label": "start"
}
]
}
--- a/nashorn/test/script/basic/parser/labelledStat.js.EXPECTED Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/test/script/basic/parser/labelledStat.js.EXPECTED Tue May 13 11:30:40 2014 +0200
@@ -3,10 +3,7 @@
"body": [
{
"type": "LabeledStatement",
- "label": {
- "type": "Identifier",
- "name": "begin"
- },
+ "label": "begin",
"body": {
"type": "BlockStatement",
"body": [
@@ -20,10 +17,7 @@
"body": [
{
"type": "BreakStatement",
- "label": {
- "type": "Identifier",
- "name": "begin"
- }
+ "label": "begin"
}
]
}
@@ -38,10 +32,7 @@
"body": [
{
"type": "LabeledStatement",
- "label": {
- "type": "Identifier",
- "name": "begin"
- },
+ "label": "begin",
"body": {
"type": "BlockStatement",
"body": [
@@ -56,10 +47,7 @@
"body": [
{
"type": "BreakStatement",
- "label": {
- "type": "Identifier",
- "name": "begin"
- }
+ "label": "begin"
}
]
}
--- a/nashorn/test/script/basic/parser/lhsExpr.js.EXPECTED Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/test/script/basic/parser/lhsExpr.js.EXPECTED Tue May 13 11:30:40 2014 +0200
@@ -69,10 +69,7 @@
"type": "Identifier",
"name": "obj"
},
- "property": {
- "type": "Identifier",
- "name": "foo"
- },
+ "property": "foo",
"computed": false
}
}
@@ -91,16 +88,10 @@
"type": "Identifier",
"name": "obj"
},
- "property": {
- "type": "Identifier",
- "name": "foo"
- },
+ "property": "foo",
"computed": false
},
- "property": {
- "type": "Identifier",
- "name": "bar"
- },
+ "property": "bar",
"computed": false
}
}
@@ -176,10 +167,7 @@
"type": "Identifier",
"name": "obj"
},
- "property": {
- "type": "Identifier",
- "name": "Type"
- },
+ "property": "Type",
"computed": false
},
"arguments": []
@@ -200,10 +188,7 @@
"type": "Identifier",
"name": "obj"
},
- "property": {
- "type": "Identifier",
- "name": "Type"
- },
+ "property": "Type",
"computed": false
},
"arguments": []
@@ -224,10 +209,7 @@
"type": "Identifier",
"name": "obj"
},
- "property": {
- "type": "Identifier",
- "name": "Type"
- },
+ "property": "Type",
"computed": false
},
"arguments": [
@@ -273,10 +255,7 @@
"type": "Identifier",
"name": "obj"
},
- "property": {
- "type": "Identifier",
- "name": "foo"
- },
+ "property": "foo",
"computed": false
},
"arguments": []
@@ -322,10 +301,7 @@
"type": "Identifier",
"name": "obj"
},
- "property": {
- "type": "Identifier",
- "name": "foo"
- },
+ "property": "foo",
"computed": false
},
"arguments": [
--- a/nashorn/test/script/basic/run-octane.js Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/test/script/basic/run-octane.js Tue May 13 11:30:40 2014 +0200
@@ -172,7 +172,6 @@
max_score = Math.max(max_score, scores[x]);
}
mean_score /= iters;
-
} catch (e) {
print_always("*** Aborted and setting score to zero. Reason: " + e);
if (e instanceof java.lang.Throwable) {
@@ -286,6 +285,3 @@
load(path + 'base.js');
run_suite(tests_found, iters);
-
-
-
--- a/nashorn/test/script/basic/runsunspider.js Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/test/script/basic/runsunspider.js Tue May 13 11:30:40 2014 +0200
@@ -54,6 +54,7 @@
var start = new Date;
load(name);
+
var stop = new Date - start;
total_time += stop;
@@ -83,6 +84,7 @@
var dir = (typeof(__DIR__) == 'undefined') ? "test/script/basic/" : __DIR__;
var single;
var verbose_run = false;
+var runall = false;
var args = [];
if (typeof $ARGS !== 'undefined') {
@@ -100,6 +102,9 @@
} else if (args[i] === '--single') {
i++;
single = args[i];
+ } else if (args[i] === '--runall') {
+ i++;
+ runall = true;
}
}
@@ -110,45 +115,53 @@
try {
for (var n = 0; n < tests.length; n++) {
- path = dir + '../external/sunspider/tests/sunspider-1.0.2/' + tests[n].name
+ try {
+ path = dir + '../external/sunspider/tests/sunspider-1.0.2/' + tests[n].name
- initrandom();
+ initrandom();
- var dd = new Date;
+ var dd = new Date;
- runbench(path);
- if (typeof tests[n].actual !== 'undefined') {
- assertEq(tests[n].actual(), tests[n].expected());
- }
+ runbench(path);
+ if (typeof tests[n].actual !== 'undefined') {
+ assertEq(tests[n].actual(), tests[n].expected());
+ }
- if (typeof tests[n].rerun !== 'undefined' && tests[n].times > 0) {
- pprint("rerunning " + tests[n].name + " " + tests[n].times + " times...");
- var times = 0;
- var to = tests[n].times;
+ if (typeof tests[n].rerun !== 'undefined' && tests[n].times > 0) {
+ pprint("rerunning " + tests[n].name + " " + tests[n].times + " times...");
+ var times = 0;
+ var to = tests[n].times;
- var elemsPerPercent = to / 100;
- var po = 0|(to / 10);
+ var elemsPerPercent = to / 100;
+ var po = 0|(to / 10);
- times = 0;
- for (; times < to; times++) {
- initrandom();
- tests[n].rerun();
- if ((times % (po|0)) == 0) {
- pprint(times/to * 100 + "%");
- }
- }
- }
-
- var t = new Date - dd;
- pprint("time: " + t + " ms");
- if (typeof tests[n].actual !== 'undefined') {
- assertEq(tests[n].actual(), tests[n].expected());
- }
- res.push(t);
+ times = 0;
+ for (; times < to; times++) {
+ initrandom();
+ tests[n].rerun();
+ if ((times % (po|0)) == 0) {
+ pprint(times/to * 100 + "%");
+ }
+ }
+ }
- pprint("");
+ var t = new Date - dd;
+ pprint("time: " + t + " ms");
+ if (typeof tests[n].actual !== 'undefined') {
+ assertEq(tests[n].actual(), tests[n].expected());
+ }
+ res.push(t);
+
+ pprint("");
- changed = true;
+ changed = true;
+ } catch(e) {
+ if(runall) {
+ print("FAIL!");
+ } else {
+ throw e;
+ }
+ }
}
} catch (e) {
print("FAIL!");
@@ -178,7 +191,7 @@
if (tests[n].times > 0) {
str += " [";
str += tests[n].times + " reruns]";
- }
+ }
pprint(str);
}
@@ -201,11 +214,11 @@
{ name: 'regexp-dna.js',
actual: function() {
return dnaOutputString + dnaInput;
- },
+ },
expected: function() {
return expectedDNAOutputString + expectedDNAInput;
},
- },
+ },
{ name: 'string-base64.js',
actual: function() {
@@ -225,12 +238,12 @@
15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
-1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
- ];
- var str = "";
+ ];
+ var str = "";
for (var i = 0; i < 8192; i++)
str += String.fromCharCode((25 * Math.random()) + 97);
-
- for (var i = 8192; i <= 16384; i *= 2) {
+
+ for (var i = 8192; i <= 16384; i *= 2) {
var base64;
base64 = toBase64(str);
var encoded = base64ToString(base64);
@@ -249,14 +262,14 @@
},
times: rtimes,
rerun: function() {
- date = new Date("1/1/2007 1:11:11");
+ date = new Date("1/1/2007 1:11:11");
for (i = 0; i < 4000; ++i) {
var shortFormat = date.dateFormat("Y-m-d");
var longFormat = date.dateFormat("l, F d, Y g:i:s A");
date.setTime(date.getTime() + 84266956);
}
}
-
+
},
{ name: 'string-validate-input.js',
actual: function() {
@@ -268,7 +281,7 @@
times: rtimes,
rerun: function() {
doTest();
- },
+ },
},
{ name: '3d-morph.js',
actual: function() {
@@ -279,9 +292,9 @@
return true;
},
times: rtimes,
- rerun: function() {
+ rerun: function() {
a = Array()
- for (var i=0; i < nx*nz*3; ++i)
+ for (var i=0; i < nx*nz*3; ++i)
a[i] = 0
for (var i = 0; i < loops; ++i) {
morph(a, i/loops)
@@ -290,7 +303,7 @@
for (var i = 0; i < nx; i++)
testOutput += a[3*(i*nx+i)+1];
a = null;
-
+
}
},
{ name: 'crypto-aes.js',
@@ -304,7 +317,7 @@
rerun: function() {
cipherText = AESEncryptCtr(plainText, password, 256);
decryptedText = AESDecryptCtr(cipherText, password, 256);
-
+
}
},
{ name: 'crypto-md5.js',
@@ -319,7 +332,7 @@
md5Output = hex_md5(plainText);
}
},
-
+
{ name: 'crypto-sha1.js',
actual: function() {
return sha1Output;
@@ -329,7 +342,7 @@
},
times: rtimes,
rerun: function() {
- sha1Output = hex_sha1(plainText);
+ sha1Output = hex_sha1(plainText);
}
},
@@ -344,7 +357,7 @@
rerun: function() {
bitwiseAndValue = 4294967296;
for (var i = 0; i < 600000; i++) {
- bitwiseAndValue = bitwiseAndValue & i;
+ bitwiseAndValue = bitwiseAndValue & i;
}
result = bitwiseAndValue;
}
@@ -390,7 +403,7 @@
},
times: rtimes,
rerun: function() {
- sum = TimeFunc(fast3bitlookup);
+ sum = TimeFunc(fast3bitlookup);
}
},
@@ -403,14 +416,14 @@
},
times: rtimes,
rerun: function() {
- var ret = 0;
+ var ret = 0;
for (var n = 3; n <= 24; n *= 2) {
(function(){
var bodies = new NBodySystem( Array(
Sun(),Jupiter(),Saturn(),Uranus(),Neptune()
));
var max = n * 100;
-
+
ret += bodies.energy();
for (var i=0; i<max; i++){
bodies.advance(0.01);
@@ -431,25 +444,25 @@
times: rtimes,
rerun: function() {
ret = 0;
-
+
for (var n = 4; n <= 7; n += 1) {
var minDepth = 4;
var maxDepth = Math.max(minDepth + 2, n);
var stretchDepth = maxDepth + 1;
-
+
var check = bottomUpTree(0,stretchDepth).itemCheck();
-
+
var longLivedTree = bottomUpTree(0,maxDepth);
for (var depth=minDepth; depth<=maxDepth; depth+=2){
var iterations = 1 << (maxDepth - depth + minDepth);
-
+
check = 0;
for (var i=1; i<=iterations; i++){
check += bottomUpTree(i,depth).itemCheck();
check += bottomUpTree(-i,depth).itemCheck();
}
}
-
+
ret += longLivedTree.itemCheck();
}
}
@@ -465,7 +478,7 @@
times: rtimes,
rerun: function() {
n = 8;
- ret = fannkuch(n);
+ ret = fannkuch(n);
}
},
@@ -482,7 +495,7 @@
},
times: rtimes,
rerun: function() {
- total = 0;
+ total = 0;
for (var i = 6; i <= 48; i *= 2) {
total += spectralnorm(i);
}
@@ -498,7 +511,7 @@
},
times: rtimes,
rerun: function() {
- testOutput = arrayToCanvasCommands(raytraceScene());
+ testOutput = arrayToCanvasCommands(raytraceScene());
}
},
@@ -531,7 +544,7 @@
},
times: rtimes,
rerun: function() {
- result = 0;
+ result = 0;
for (var i = 3; i <= 5; i++) {
result += ack(3,i);
result += fib(17.0+i);
@@ -549,7 +562,7 @@
},
times: rtimes,
rerun: function() {
- date = new Date("1/1/2007 1:11:11");
+ date = new Date("1/1/2007 1:11:11");
for (i = 0; i < 500; ++i) {
var shortFormat = date.formatDate("Y-m-d");
var longFormat = date.formatDate("l, F d, Y g:i:s A");
@@ -569,7 +582,7 @@
return 295906;
},
times: rtimes,
- rerun: function() {
+ rerun: function() {
tagInfo = tagInfoJSON.parseJSON(function(a, b) { if (a == "popularity") { return Math.log(b) / log2; } else {return b; } });
tagcloud = makeTagCloud(tagInfo);
}
@@ -601,7 +614,7 @@
times: rtimes,
rerun: function() {
result = sieve();
- }
+ }
},
{ name: '3d-cube.js',
@@ -612,7 +625,7 @@
MQube = new Array(); // position information of qube
I = new Array(); // entity matrix
Origin = new Object();
- Testing = new Object();
+ Testing = new Object();
for ( var i = 20; i <= 160; i *= 2 ) {
Init(i);
}
@@ -629,7 +642,7 @@
fastaRandom(3*count*1000, IUB);
fastaRandom(5*count*1000, HomoSap);
}
- },
+ },
//TODO no easy way to sanity check result
{ name: 'string-unpack-code.js',
--- a/nashorn/test/script/trusted/JDK-8006529.js Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/test/script/trusted/JDK-8006529.js Tue May 13 11:30:40 2014 +0200
@@ -249,4 +249,5 @@
testFirstFn("function f(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, p32, p33, p34, p35, p36, p37, p38, p39, p40, p41, p42, p43, p44, p45, p46, p47, p48, p49, p50, p51, p52, p53, p54, p55, p56, p57, p58, p59, p60, p61, p62, p63, p64, p65, p66, p67, p68, p69, p70, p71, p72, p73, p74, p75, p76, p77, p78, p79, p80, p81, p82, p83, p84, p85, p86, p87, p88, p89, p90, p91, p92, p93, p94, p95, p96, p97, p98, p99, p100, p101, p102, p103, p104, p105, p106, p107, p108, p109, p110, p111, p112, p113, p114, p115, p116, p117, p118, p119, p120, p121, p122, p123, p124, p125, p126, p127, p128, p129, p130, p131, p132, p133, p134, p135, p136, p137, p138, p139, p140, p141, p142, p143, p144, p145, p146, p147, p148, p149, p150, p151, p152, p153, p154, p155, p156, p157, p158, p159, p160, p161, p162, p163, p164, p165, p166, p167, p168, p169, p170, p171, p172, p173, p174, p175, p176, p177, p178, p179, p180, p181, p182, p183, p184, p185, p186, p187, p188, p189, p190, p191, p192, p193, p194, p195, p196, p197, p198, p199, p200, p201, p202, p203, p204, p205, p206, p207, p208, p209, p210, p211, p212, p213, p214, p215, p216, p217, p218, p219, p220, p221, p222, p223, p224, p225, p226, p227, p228, p229, p230, p231, p232, p233, p234, p235, p236, p237, p238, p239, p240, p241, p242, p243, p244, p245, p246, p247, p248, p249, p250) { p250 = p249 }")
// Function with 251 named parameters is variable arguments
-testFirstFn("function f(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, p32, p33, p34, p35, p36, p37, p38, p39, p40, p41, p42, p43, p44, p45, p46, p47, p48, p49, p50, p51, p52, p53, p54, p55, p56, p57, p58, p59, p60, p61, p62, p63, p64, p65, p66, p67, p68, p69, p70, p71, p72, p73, p74, p75, p76, p77, p78, p79, p80, p81, p82, p83, p84, p85, p86, p87, p88, p89, p90, p91, p92, p93, p94, p95, p96, p97, p98, p99, p100, p101, p102, p103, p104, p105, p106, p107, p108, p109, p110, p111, p112, p113, p114, p115, p116, p117, p118, p119, p120, p121, p122, p123, p124, p125, p126, p127, p128, p129, p130, p131, p132, p133, p134, p135, p136, p137, p138, p139, p140, p141, p142, p143, p144, p145, p146, p147, p148, p149, p150, p151, p152, p153, p154, p155, p156, p157, p158, p159, p160, p161, p162, p163, p164, p165, p166, p167, p168, p169, p170, p171, p172, p173, p174, p175, p176, p177, p178, p179, p180, p181, p182, p183, p184, p185, p186, p187, p188, p189, p190, p191, p192, p193, p194, p195, p196, p197, p198, p199, p200, p201, p202, p203, p204, p205, p206, p207, p208, p209, p210, p211, p212, p213, p214, p215, p216, p217, p218, p219, p220, p221, p222, p223, p224, p225, p226, p227, p228, p229, p230, p231, p232, p233, p234, p235, p236, p237, p238, p239, p240, p241, p242, p243, p244, p245, p246, p247, p248, p249, p250, p251) { p250 = p251 }", 'isVarArg')
+// NOTE: hasScopeBlock should be optimized away. Implementation of JDK-8038942 should take care of it.
+testFirstFn("function f(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, p32, p33, p34, p35, p36, p37, p38, p39, p40, p41, p42, p43, p44, p45, p46, p47, p48, p49, p50, p51, p52, p53, p54, p55, p56, p57, p58, p59, p60, p61, p62, p63, p64, p65, p66, p67, p68, p69, p70, p71, p72, p73, p74, p75, p76, p77, p78, p79, p80, p81, p82, p83, p84, p85, p86, p87, p88, p89, p90, p91, p92, p93, p94, p95, p96, p97, p98, p99, p100, p101, p102, p103, p104, p105, p106, p107, p108, p109, p110, p111, p112, p113, p114, p115, p116, p117, p118, p119, p120, p121, p122, p123, p124, p125, p126, p127, p128, p129, p130, p131, p132, p133, p134, p135, p136, p137, p138, p139, p140, p141, p142, p143, p144, p145, p146, p147, p148, p149, p150, p151, p152, p153, p154, p155, p156, p157, p158, p159, p160, p161, p162, p163, p164, p165, p166, p167, p168, p169, p170, p171, p172, p173, p174, p175, p176, p177, p178, p179, p180, p181, p182, p183, p184, p185, p186, p187, p188, p189, p190, p191, p192, p193, p194, p195, p196, p197, p198, p199, p200, p201, p202, p203, p204, p205, p206, p207, p208, p209, p210, p211, p212, p213, p214, p215, p216, p217, p218, p219, p220, p221, p222, p223, p224, p225, p226, p227, p228, p229, p230, p231, p232, p233, p234, p235, p236, p237, p238, p239, p240, p241, p242, p243, p244, p245, p246, p247, p248, p249, p250, p251) { p250 = p251 }", 'isVarArg', 'hasScopeBlock')
--- a/nashorn/test/src/jdk/nashorn/api/scripting/ScriptEngineTest.java Mon May 05 14:17:20 2014 +0200
+++ b/nashorn/test/src/jdk/nashorn/api/scripting/ScriptEngineTest.java Tue May 13 11:30:40 2014 +0200
@@ -30,7 +30,6 @@
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
-import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.InvocationHandler;