Mercurial > people > rkennke > jdk9-shenandoah-final > nashorn
changeset 850:c61d579dd5a8
8042118: Separate types from symbols
Reviewed-by: hannesw, lagergren
line wrap: on
line diff
--- a/src/jdk/internal/dynalink/support/TypeUtilities.java Mon May 05 14:17:20 2014 +0200 +++ b/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/src/jdk/internal/dynalink/support/messages.properties Mon May 05 14:17:20 2014 +0200 +++ b/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/src/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java Mon May 05 14:17:20 2014 +0200 +++ b/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/src/jdk/nashorn/internal/codegen/ApplySpecialization.java Mon May 05 14:17:20 2014 +0200 +++ b/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/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/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/src/jdk/nashorn/internal/codegen/BranchOptimizer.java Mon May 05 14:17:20 2014 +0200 +++ b/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/src/jdk/nashorn/internal/codegen/ClassEmitter.java Mon May 05 14:17:20 2014 +0200 +++ b/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/src/jdk/nashorn/internal/codegen/CodeGenerator.java Mon May 05 14:17:20 2014 +0200 +++ b/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/src/jdk/nashorn/internal/codegen/CodeGeneratorLexicalContext.java Mon May 05 14:17:20 2014 +0200 +++ b/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/src/jdk/nashorn/internal/codegen/CompilationEnvironment.java Mon May 05 14:17:20 2014 +0200 +++ b/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/src/jdk/nashorn/internal/codegen/CompilationPhase.java Mon May 05 14:17:20 2014 +0200 +++ b/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()) { +