/* * Copyright (c) 2010, 2016, 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.ClassEmitter.Flag.PRIVATE; import static jdk.nashorn.internal.codegen.ClassEmitter.Flag.STATIC; import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS; import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE; import static jdk.nashorn.internal.codegen.CompilerConstants.CREATE_PROGRAM_FUNCTION; import static jdk.nashorn.internal.codegen.CompilerConstants.GET_MAP; import static jdk.nashorn.internal.codegen.CompilerConstants.GET_STRING; import static jdk.nashorn.internal.codegen.CompilerConstants.QUICK_PREFIX; import static jdk.nashorn.internal.codegen.CompilerConstants.REGEX_PREFIX; import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE; import static jdk.nashorn.internal.codegen.CompilerConstants.SPLIT_PREFIX; import static jdk.nashorn.internal.codegen.CompilerConstants.THIS; import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS; import static jdk.nashorn.internal.codegen.CompilerConstants.interfaceCallNoLookup; import static jdk.nashorn.internal.codegen.CompilerConstants.methodDescriptor; import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup; import static jdk.nashorn.internal.codegen.CompilerConstants.typeDescriptor; import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup; import static jdk.nashorn.internal.ir.Symbol.HAS_SLOT; import static jdk.nashorn.internal.ir.Symbol.IS_INTERNAL; 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; import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_DECLARE; import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_FAST_SCOPE; import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_OPTIMISTIC; import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_PROGRAM_POINT_SHIFT; import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_SCOPE; import java.io.PrintWriter; 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; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.function.Supplier; import jdk.nashorn.internal.AssertsEnabled; import jdk.nashorn.internal.IntDeque; import jdk.nashorn.internal.codegen.ClassEmitter.Flag; import jdk.nashorn.internal.codegen.CompilerConstants.Call; import jdk.nashorn.internal.codegen.types.ArrayType; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.AccessNode; import jdk.nashorn.internal.ir.BaseNode; import jdk.nashorn.internal.ir.BinaryNode; import jdk.nashorn.internal.ir.Block; import jdk.nashorn.internal.ir.BlockStatement; import jdk.nashorn.internal.ir.BreakNode; import jdk.nashorn.internal.ir.CallNode; import jdk.nashorn.internal.ir.CaseNode; import jdk.nashorn.internal.ir.CatchNode; import jdk.nashorn.internal.ir.ContinueNode; import jdk.nashorn.internal.ir.EmptyNode; import jdk.nashorn.internal.ir.Expression; import jdk.nashorn.internal.ir.ExpressionStatement; import jdk.nashorn.internal.ir.ForNode; import jdk.nashorn.internal.ir.FunctionNode; import jdk.nashorn.internal.ir.GetSplitState; import jdk.nashorn.internal.ir.IdentNode; import jdk.nashorn.internal.ir.IfNode; import jdk.nashorn.internal.ir.IndexNode; import jdk.nashorn.internal.ir.JoinPredecessorExpression; import jdk.nashorn.internal.ir.JumpStatement; import jdk.nashorn.internal.ir.JumpToInlinedFinally; 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.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; import jdk.nashorn.internal.ir.Optimistic; import jdk.nashorn.internal.ir.PropertyNode; import jdk.nashorn.internal.ir.ReturnNode; import jdk.nashorn.internal.ir.RuntimeNode; import jdk.nashorn.internal.ir.RuntimeNode.Request; import jdk.nashorn.internal.ir.SetSplitState; import jdk.nashorn.internal.ir.SplitReturn; import jdk.nashorn.internal.ir.Splittable; import jdk.nashorn.internal.ir.Statement; import jdk.nashorn.internal.ir.SwitchNode; import jdk.nashorn.internal.ir.Symbol; import jdk.nashorn.internal.ir.TernaryNode; import jdk.nashorn.internal.ir.ThrowNode; import jdk.nashorn.internal.ir.TryNode; import jdk.nashorn.internal.ir.UnaryNode; import jdk.nashorn.internal.ir.VarNode; import jdk.nashorn.internal.ir.WhileNode; import jdk.nashorn.internal.ir.WithNode; import jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor; import jdk.nashorn.internal.ir.visitor.SimpleNodeVisitor; import jdk.nashorn.internal.objects.Global; import jdk.nashorn.internal.parser.Lexer.RegexToken; import jdk.nashorn.internal.parser.TokenType; import jdk.nashorn.internal.runtime.Context; import jdk.nashorn.internal.runtime.Debug; import jdk.nashorn.internal.runtime.ECMAException; import jdk.nashorn.internal.runtime.JSType; import jdk.nashorn.internal.runtime.OptimisticReturnFilters; import jdk.nashorn.internal.runtime.PropertyMap; import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData; import jdk.nashorn.internal.runtime.RewriteException; import jdk.nashorn.internal.runtime.Scope; import jdk.nashorn.internal.runtime.ScriptEnvironment; import jdk.nashorn.internal.runtime.ScriptFunction; import jdk.nashorn.internal.runtime.ScriptObject; import jdk.nashorn.internal.runtime.ScriptRuntime; import jdk.nashorn.internal.runtime.Source; import jdk.nashorn.internal.runtime.Undefined; import jdk.nashorn.internal.runtime.UnwarrantedOptimismException; import jdk.nashorn.internal.runtime.arrays.ArrayData; import jdk.nashorn.internal.runtime.linker.LinkerCallSite; import jdk.nashorn.internal.runtime.logging.DebugLogger; import jdk.nashorn.internal.runtime.logging.Loggable; import jdk.nashorn.internal.runtime.logging.Logger; import jdk.nashorn.internal.runtime.options.Options; /** * This is the lowest tier of the code generator. It takes lowered ASTs emitted * from Lower and emits Java byte code. The byte code emission logic is broken * out into MethodEmitter. MethodEmitter works internally with a type stack, and * keeps track of the contents of the byte code stack. This way we avoid a large * number of special cases on the form * <pre> * if (type == INT) { * visitInsn(ILOAD, slot); * } else if (type == DOUBLE) { * visitInsn(DOUBLE, slot); * } * </pre> * This quickly became apparent when the code generator was generalized to work * with all types, and not just numbers or objects. * <p> * The CodeGenerator visits nodes only once and emits bytecode for them. */ @Logger(name="codegen") final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContext> implements Loggable { private static final Type SCOPE_TYPE = Type.typeFor(ScriptObject.class); private static final String GLOBAL_OBJECT = Type.getInternalName(Global.class); private static final Call CREATE_REWRITE_EXCEPTION = CompilerConstants.staticCallNoLookup(RewriteException.class, "create", RewriteException.class, UnwarrantedOptimismException.class, Object[].class, String[].class); private static final Call CREATE_REWRITE_EXCEPTION_REST_OF = CompilerConstants.staticCallNoLookup(RewriteException.class, "create", RewriteException.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); private static final Call ENSURE_NUMBER = CompilerConstants.staticCallNoLookup(OptimisticReturnFilters.class, "ensureNumber", double.class, Object.class, int.class); private static final Call CREATE_FUNCTION_OBJECT = CompilerConstants.staticCallNoLookup(ScriptFunction.class, "create", ScriptFunction.class, Object[].class, int.class, ScriptObject.class); private static final Call CREATE_FUNCTION_OBJECT_NO_SCOPE = CompilerConstants.staticCallNoLookup(ScriptFunction.class, "create", ScriptFunction.class, Object[].class, int.class); private static final Call TO_NUMBER_FOR_EQ = CompilerConstants.staticCallNoLookup(JSType.class, "toNumberForEq", double.class, Object.class); private static final Call TO_NUMBER_FOR_STRICT_EQ = CompilerConstants.staticCallNoLookup(JSType.class, "toNumberForStrictEq", double.class, Object.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()); private static final Integer INT_ZERO = 0; /** Constant data & installation. The only reason the compiler keeps this is because it is assigned * by reflection in class installation */ private final Compiler compiler; /** Is the current code submitted by 'eval' call? */ private final boolean evalCode; /** Call site flags given to the code generator to be used for all generated call sites */ private final int callSiteFlags; /** How many regexp fields have been emitted */ private int regexFieldCount; /** Line number for last statement. If we encounter a new line number, line number bytecode information * needs to be generated */ private int lastLineNumber = -1; /** When should we stop caching regexp expressions in fields to limit bytecode size? */ private static final int MAX_REGEX_FIELDS = 2 * 1024; /** Current method emitter */ private MethodEmitter method; /** Current compile unit */ private CompileUnit unit; private final DebugLogger log; /** From what size should we use spill instead of fields for JavaScript objects? */ static final int OBJECT_SPILL_THRESHOLD = Options.getIntProperty("nashorn.spill.threshold", 256); private final Set<String> emittedMethods = new HashSet<>(); // Function Id -> ContinuationInfo. Used by compilation of rest-of function only. private ContinuationInfo continuationInfo; private final Deque<Label> scopeEntryLabels = new ArrayDeque<>(); 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(); //is this a rest of compilation private final int[] continuationEntryPoints; // Scope object creators needed for for-of and for-in loops private final Deque<FieldObjectCreator<?>> scopeObjectCreators = new ArrayDeque<>(); /** * Constructor. * * @param compiler */ CodeGenerator(final Compiler compiler, final int[] continuationEntryPoints) { super(new CodeGeneratorLexicalContext()); this.compiler = compiler; this.evalCode = compiler.getSource().isEvalCode(); this.continuationEntryPoints = continuationEntryPoints; this.callSiteFlags = compiler.getScriptEnvironment()._callsite_flags; this.log = initLogger(compiler.getContext()); } @Override public DebugLogger getLogger() { return log; } @Override public DebugLogger initLogger(final Context context) { return context.getLogger(this.getClass()); } /** * Gets the call site flags, adding the strict flag if the current function * being generated is in strict mode * * @return the correct flags for a call site in the current function */ int getCallSiteFlags() { return lc.getCurrentFunction().getCallSiteFlags() | callSiteFlags; } /** * Gets the flags for a scope call site. * @param symbol a scope symbol * @return the correct flags for the scope call site */ private int getScopeCallSiteFlags(final Symbol symbol) { assert symbol.isScope(); final int flags = getCallSiteFlags() | CALLSITE_SCOPE; if (isEvalCode() && symbol.isGlobal()) { return flags; // Don't set fast-scope flag on non-declared globals in eval code - see JDK-8077955. } return isFastScope(symbol) ? flags | CALLSITE_FAST_SCOPE : flags; } /** * Are we generating code for 'eval' code? * @return true if currently compiled code is 'eval' code. */ boolean isEvalCode() { return evalCode; } /** * Are we using dual primitive/object field representation? * @return true if using dual field representation, false for object-only fields */ boolean useDualFields() { return compiler.getContext().useDualFields(); } /** * Load an identity node * * @param identNode an identity node to load * @return the method generator used */ private MethodEmitter loadIdent(final IdentNode identNode, final TypeBounds resultBounds) { checkTemporalDeadZone(identNode); final Symbol symbol = identNode.getSymbol(); if (!symbol.isScope()) { final Type type = identNode.getType(); if(type == Type.UNDEFINED) { return method.loadUndefined(resultBounds.widest); } assert symbol.hasSlot() || symbol.isParam(); return method.load(identNode); } assert identNode.getSymbol().isScope() : identNode + " is not in scope!"; final int flags = getScopeCallSiteFlags(symbol); 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 && !identNode.isOptimistic()) { // As shared scope vars are only used with non-optimistic identifiers, we switch from using TypeBounds to // just a single definitive type, resultBounds.widest. new OptimisticOperation(identNode, TypeBounds.OBJECT) { @Override void loadStack() { method.loadCompilerConstant(SCOPE); } @Override void consumeStack() { loadSharedScopeVar(resultBounds.widest, symbol, flags); } }.emit(); } else { new LoadFastScopeVar(identNode, resultBounds, flags).emit(); } } else { //slow scope load, we have no proto depth new LoadScopeVar(identNode, resultBounds, flags).emit(); } return method; } // Any access to LET and CONST variables before their declaration must throw ReferenceError. // This is called the temporal dead zone (TDZ). See https://gist.github.com/rwaldron/f0807a758aa03bcdd58a private void checkTemporalDeadZone(final IdentNode identNode) { if (identNode.isDead()) { method.load(identNode.getSymbol().getName()).invoke(ScriptRuntime.THROW_REFERENCE_ERROR); } } // Runtime check for assignment to ES6 const private void checkAssignTarget(final Expression expression) { if (expression instanceof IdentNode && ((IdentNode)expression).getSymbol().isConst()) { method.load(((IdentNode)expression).getSymbol().getName()).invoke(ScriptRuntime.THROW_CONST_TYPE_ERROR); } } private boolean isRestOf() { return continuationEntryPoints != null; } private boolean isCurrentContinuationEntryPoint(final int programPoint) { return isRestOf() && getCurrentContinuationEntryPoint() == programPoint; } private int[] getContinuationEntryPoints() { return isRestOf() ? continuationEntryPoints : null; } private int getCurrentContinuationEntryPoint() { return isRestOf() ? continuationEntryPoints[0] : INVALID_PROGRAM_POINT; } private boolean isContinuationEntryPoint(final int programPoint) { if (isRestOf()) { assert continuationEntryPoints != null; for (final int cep : continuationEntryPoints) { if (cep == programPoint) { return true; } } } return false; } /** * Check if this symbol can be accessed directly with a putfield or getfield or dynamic load * * @param symbol symbol to check for fast scope * @return true if fast scope */ private boolean isFastScope(final Symbol symbol) { if (!symbol.isScope()) { return false; } if (!lc.inDynamicScope()) { // If there's no with or eval in context, and the symbol is marked as scoped, it is fast scoped. Such a // symbol must either be global, or its defining block must need scope. assert symbol.isGlobal() || lc.getDefiningBlock(symbol).needsScope() : symbol.getName(); return true; } if (symbol.isGlobal()) { // Shortcut: if there's a with or eval in context, globals can't be fast scoped return false; } // Otherwise, check if there's a dynamic scope between use of the symbol and its definition final String name = symbol.getName(); boolean previousWasBlock = false; for (final Iterator<LexicalContextNode> it = lc.getAllNodes(); it.hasNext();) { final LexicalContextNode node = it.next(); if (node instanceof Block) { // If this block defines the symbol, then we can fast scope the symbol. final Block block = (Block)node; if (block.getExistingSymbol(name) == symbol) { assert block.needsScope(); return true; } previousWasBlock = true; } else { if (node instanceof WithNode && previousWasBlock || node instanceof FunctionNode && ((FunctionNode)node).needsDynamicScope()) { // If we hit a scope that can have symbols introduced into it at run time before finding the defining // block, the symbol can't be fast scoped. A WithNode only counts if we've immediately seen a block // before - its block. Otherwise, we are currently processing the WithNode's expression, and that's // obviously not subjected to introducing new symbols. return false; } previousWasBlock = false; } } // Should've found the symbol defined in a block throw new AssertionError(); } private MethodEmitter loadSharedScopeVar(final Type valueType, final Symbol symbol, final int flags) { assert isFastScope(symbol); method.load(getScopeProtoDepth(lc.getCurrentBlock(), symbol)); return lc.getScopeGet(unit, symbol, valueType, flags).generateInvoke(method); } 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() { //empty } @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(), false); replaceCompileTimeProperty(); } else { dynamicGet(identNode.getSymbol().getName(), flags, identNode.isFunction(), false); } } } private class LoadFastScopeVar extends LoadScopeVar { LoadFastScopeVar(final IdentNode identNode, final TypeBounds resultBounds, final int flags) { super(identNode, resultBounds, flags); } @Override void getProto() { loadFastScopeProto(identNode.getSymbol(), false); } } private MethodEmitter storeFastScopeVar(final Symbol symbol, final int flags) { loadFastScopeProto(symbol, true); method.dynamicSet(symbol.getName(), flags, false); return method; } private int getScopeProtoDepth(final Block startingBlock, final Symbol symbol) { //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 externalDepth = compiler.getScriptFunctionData(fn.getId()).getExternalSymbolDepth(symbol.getName()); //count the number of scopes from this place to the start of the function final int internalDepth = FindScopeDepths.findInternalDepth(lc, fn, startingBlock, symbol); final int scopesToStart = FindScopeDepths.findScopesToStart(lc, fn, startingBlock); int depth = 0; if (internalDepth == -1) { depth = scopesToStart + externalDepth; } else { assert internalDepth <= scopesToStart; depth = internalDepth; } return depth; } private void loadFastScopeProto(final Symbol symbol, final boolean swap) { final int depth = getScopeProtoDepth(lc.getCurrentBlock(), symbol); assert depth != -1 : "Couldn't find scope depth for symbol " + symbol.getName() + " in " + lc.getCurrentFunction(); if (depth > 0) { if (swap) { method.swap(); } if (depth > 1) { method.load(depth); method.invoke(ScriptObject.GET_PROTO_DEPTH); } else { method.invoke(ScriptObject.GET_PROTO); } if (swap) { method.swap(); } } } /** * Generate code that loads this node to the stack, not constraining its type * * @param expr node to load * * @return the method emitter used */ 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 // with possible side effects from calling an object's toString or valueOf methods. private static boolean noToPrimitiveConversion(final Type source, final Type target) { // Object to boolean conversion does not cause ToPrimitive call return source.isJSPrimitive() || !target.isJSPrimitive() || target.isBoolean(); } MethodEmitter loadBinaryOperands(final BinaryNode binaryNode) { return loadBinaryOperands(binaryNode.lhs(), binaryNode.rhs(), TypeBounds.UNBOUNDED.notWiderThan(binaryNode.getWidestOperandType()), false, false); } private MethodEmitter loadBinaryOperands(final Expression lhs, final Expression rhs, final TypeBounds explicitOperandBounds, final boolean baseAlreadyOnStack, final boolean forceConversionSeparation) { // 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 // can combine a LOAD with a CONVERT operation (e.g. use a dynamic getter with the conversion target type as its // return value). What we do here is reorder LOAD RIGHT and CONVERT LEFT when possible; it is possible only when // we can prove that executing CONVERT LEFT can't have a side effect that changes the value of LOAD RIGHT. // Basically, if we know that either LEFT already is a primitive value, or does not have to be converted to // 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. // 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 lhsType = undefinedToNumber(lhs.getType()); final Type rhsType = undefinedToNumber(rhs.getType()); final Type narrowestOperandType = Type.narrowest(Type.widest(lhsType, rhsType), explicitOperandBounds.widest); final TypeBounds operandBounds = explicitOperandBounds.notNarrowerThan(narrowestOperandType); if (noToPrimitiveConversion(lhsType, explicitOperandBounds.widest) || rhs.isLocal()) { // Can reorder. We might still need to separate conversion, but at least we can do it with reordering if (forceConversionSeparation) { // Can reorder, but can't move conversion into the operand as the operation depends on operands // exact types for its overflow guarantees. E.g. with {L}{%I}expr1 {L}* {L}{%I}expr2 we are not allowed // to merge {L}{%I} into {%L}, as that can cause subsequent overflows; test for JDK-8058610 contains // concrete cases where this could happen. final TypeBounds safeConvertBounds = TypeBounds.UNBOUNDED.notNarrowerThan(narrowestOperandType); loadExpression(lhs, safeConvertBounds, baseAlreadyOnStack); method.convert(operandBounds.within(method.peekType())); loadExpression(rhs, safeConvertBounds, false); method.convert(operandBounds.within(method.peekType())); } else { // Can reorder and move conversion into the operand. Combine load and convert into single operations. loadExpression(lhs, operandBounds, baseAlreadyOnStack); loadExpression(rhs, operandBounds, false); } } else { // Can't reorder. Load and convert separately. final TypeBounds safeConvertBounds = TypeBounds.UNBOUNDED.notNarrowerThan(narrowestOperandType); loadExpression(lhs, safeConvertBounds, baseAlreadyOnStack); final Type lhsLoadedType = method.peekType(); loadExpression(rhs, safeConvertBounds, false); final Type convertedLhsType = operandBounds.within(method.peekType()); if (convertedLhsType != lhsLoadedType) { // Do it conditionally, so that if conversion is a no-op we don't introduce a SWAP, SWAP. method.swap().convert(convertedLhsType).swap(); } method.convert(operandBounds.within(method.peekType())); } assert Type.generic(method.peekType()) == operandBounds.narrowest; assert Type.generic(method.peekType(1)) == operandBounds.narrowest; return method; } /** * Similar to {@link #loadBinaryOperands(BinaryNode)} but used specifically for loading operands of * relational and equality comparison operators where at least one argument is non-object. (When both * arguments are objects, we use {@link ScriptRuntime#EQ(Object, Object)}, {@link ScriptRuntime#LT(Object, Object)} * etc. methods instead. Additionally, {@code ScriptRuntime} methods are used for strict (in)equality comparison * of a boolean to anything that isn't a boolean.) This method handles the special case where one argument * is an object and another is a primitive. Naively, these could also be delegated to {@code ScriptRuntime} methods * by boxing the primitive. However, in all such cases the comparison is performed on numeric values, so it is * possible to strength-reduce the operation by taking the number value of the object argument instead and * comparing that to the primitive value ("primitive" will always be int, long, double, or boolean, and booleans * compare as ints in these cases, so they're essentially numbers too). This method will emit code for loading * arguments for such strength-reduced comparison. When both arguments are primitives, it just delegates to * {@link #loadBinaryOperands(BinaryNode)}. * * @param cmp the comparison operation for which the operands need to be loaded on stack. * @return the current method emitter. */ MethodEmitter loadComparisonOperands(final BinaryNode cmp) { final Expression lhs = cmp.lhs(); final Expression rhs = cmp.rhs(); final Type lhsType = lhs.getType(); final Type rhsType = rhs.getType(); // Only used when not both are object, for that we have ScriptRuntime.LT etc. assert !(lhsType.isObject() && rhsType.isObject()); if (lhsType.isObject() || rhsType.isObject()) { // We can reorder CONVERT LEFT and LOAD RIGHT only if either the left is a primitive, or the right // is a local. This is more strict than loadBinaryNode reorder criteria, as it can allow JS primitive // types too (notably: String is a JS primitive, but not a JVM primitive). We disallow String otherwise // we would prematurely convert it to number when comparing to an optimistic expression, e.g. in // "Hello" === String("Hello") the RHS starts out as an optimistic-int function call. If we allowed // reordering, we'd end up with ToNumber("Hello") === {I%}String("Hello") that is obviously incorrect. final boolean canReorder = lhsType.isPrimitive() || rhs.isLocal(); // If reordering is allowed, and we're using a relational operator (that is, <, <=, >, >=) and not an // (in)equality operator, then we encourage combining of LOAD and CONVERT into a single operation. // This is because relational operators' semantics prescribes vanilla ToNumber() conversion, while // (in)equality operators need the specialized JSType.toNumberFor[Strict]Equals. E.g. in the code snippet // "i < obj.size" (where i is primitive and obj.size is statically an object), ".size" will thus be allowed // to compile as: // invokedynamic GET_PROPERTY:size(Object;)D // instead of the more costly: // invokedynamic GET_PROPERTY:size(Object;)Object // invokestatic JSType.toNumber(Object)D // Note also that even if this is allowed, we're only using it on operands that are non-optimistic, as // otherwise the logic for determining effective optimistic-ness would turn an optimistic double return // into a freely coercible one, which would be wrong. final boolean canCombineLoadAndConvert = canReorder && cmp.isRelational(); // LOAD LEFT loadExpression(lhs, canCombineLoadAndConvert && !lhs.isOptimistic() ? TypeBounds.NUMBER : TypeBounds.UNBOUNDED); final Type lhsLoadedType = method.peekType(); final TokenType tt = cmp.tokenType(); if (canReorder) { // Can reorder CONVERT LEFT and LOAD RIGHT emitObjectToNumberComparisonConversion(method, tt); loadExpression(rhs, canCombineLoadAndConvert && !rhs.isOptimistic() ? TypeBounds.NUMBER : TypeBounds.UNBOUNDED); } else { // Can't reorder CONVERT LEFT and LOAD RIGHT loadExpression(rhs, TypeBounds.UNBOUNDED); if (lhsLoadedType != Type.NUMBER) { method.swap(); emitObjectToNumberComparisonConversion(method, tt); method.swap(); } } // CONVERT RIGHT emitObjectToNumberComparisonConversion(method, tt); return method; } // For primitive operands, just don't do anything special. return loadBinaryOperands(cmp); } private static void emitObjectToNumberComparisonConversion(final MethodEmitter method, final TokenType tt) { switch(tt) { case EQ: case NE: if (method.peekType().isObject()) { TO_NUMBER_FOR_EQ.invoke(method); return; } break; case EQ_STRICT: case NE_STRICT: if (method.peekType().isObject()) { TO_NUMBER_FOR_STRICT_EQ.invoke(method); return; } break; default: break; } method.convert(Type.NUMBER); } private static Type undefinedToNumber(final Type type) { return type == Type.UNDEFINED ? Type.NUMBER : type; } 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(CodeGenerator.booleanToInt(narrowest), CodeGenerator.booleanToInt(widest)); } TypeBounds objectToNumber() { return maybeNew(CodeGenerator.objectToNumber(narrowest), CodeGenerator.objectToNumber(widest)); } 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 static Type booleanToInt(final Type t) { return t == Type.BOOLEAN ? Type.INT : t; } private static Type objectToNumber(final Type t) { return t.isObject() ? Type.NUMBER : t; } 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" * or IndexNode e.g. "x[y]". Both AccessNodes and IndexNodes are * BaseNodes and the logic for loading the base object is reused */ final CodeGenerator codegen = this; final boolean isCurrentDiscard = codegen.lc.isCurrentDiscard(expr); expr.accept(new NodeOperatorVisitor<LexicalContext>(new LexicalContext()) { @Override public boolean enterIdentNode(final IdentNode identNode) { loadIdent(identNode, resultBounds); return false; } @Override public boolean enterAccessNode(final AccessNode accessNode) { new OptimisticOperation(accessNode, resultBounds) { @Override void loadStack() { if (!baseAlreadyOnStack) { loadExpressionAsObject(accessNode.getBase()); } assert method.peekType().isObject(); } @Override void consumeStack() { final int flags = getCallSiteFlags(); dynamicGet(accessNode.getProperty(), flags, accessNode.isFunction(), accessNode.isIndex()); } }.emit(baseAlreadyOnStack ? 1 : 0); return false; } @Override public boolean enterIndexNode(final IndexNode indexNode) { new OptimisticOperation(indexNode, resultBounds) { @Override void loadStack() { if (!baseAlreadyOnStack) { loadExpressionAsObject(indexNode.getBase()); loadExpressionUnbounded(indexNode.getIndex()); } } @Override void consumeStack() { final int flags = getCallSiteFlags(); dynamicGetIndex(flags, indexNode.isFunction()); } }.emit(baseAlreadyOnStack ? 2 : 0); return false; } @Override public boolean enterFunctionNode(final FunctionNode functionNode) { // function nodes will always leave a constructed function object on stack, no need to load the symbol // separately as in enterDefault() lc.pop(functionNode); functionNode.accept(codegen); // NOTE: functionNode.accept() will produce a different FunctionNode that we discard. This incidentally // doesn't cause problems as we're never touching FunctionNode again after it's visited here - codegen // 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); return false; } @Override public boolean enterASSIGN(final BinaryNode binaryNode) { checkAssignTarget(binaryNode.lhs()); loadASSIGN(binaryNode); return false; } @Override public boolean enterASSIGN_ADD(final BinaryNode binaryNode) { checkAssignTarget(binaryNode.lhs()); loadASSIGN_ADD(binaryNode); return false; } @Override public boolean enterASSIGN_BIT_AND(final BinaryNode binaryNode) { checkAssignTarget(binaryNode.lhs()); loadASSIGN_BIT_AND(binaryNode); return false; } @Override public boolean enterASSIGN_BIT_OR(final BinaryNode binaryNode) { checkAssignTarget(binaryNode.lhs()); loadASSIGN_BIT_OR(binaryNode); return false; } @Override public boolean enterASSIGN_BIT_XOR(final BinaryNode binaryNode) { checkAssignTarget(binaryNode.lhs()); loadASSIGN_BIT_XOR(binaryNode); return false; } @Override public boolean enterASSIGN_DIV(final BinaryNode binaryNode) { checkAssignTarget(binaryNode.lhs()); loadASSIGN_DIV(binaryNode); return false; } @Override public boolean enterASSIGN_MOD(final BinaryNode binaryNode) { checkAssignTarget(binaryNode.lhs()); loadASSIGN_MOD(binaryNode); return false; } @Override public boolean enterASSIGN_MUL(final BinaryNode binaryNode) { checkAssignTarget(binaryNode.lhs()); loadASSIGN_MUL(binaryNode); return false; } @Override public boolean enterASSIGN_SAR(final BinaryNode binaryNode) { checkAssignTarget(binaryNode.lhs()); loadASSIGN_SAR(binaryNode); return false; } @Override public boolean enterASSIGN_SHL(final BinaryNode binaryNode) { checkAssignTarget(binaryNode.lhs()); loadASSIGN_SHL(binaryNode); return false; } @Override public boolean enterASSIGN_SHR(final BinaryNode binaryNode) { checkAssignTarget(binaryNode.lhs()); loadASSIGN_SHR(binaryNode); return false; } @Override public boolean enterASSIGN_SUB(final BinaryNode binaryNode) { checkAssignTarget(binaryNode.lhs()); loadASSIGN_SUB(binaryNode); return false; } @Override public boolean enterCallNode(final CallNode callNode) { return loadCallNode(callNode, resultBounds); } @Override public boolean enterLiteralNode(final LiteralNode<?> literalNode) { 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(final 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(final UnaryNode unaryNode) { loadNOT(unaryNode); return false; } @Override public boolean enterADD(final UnaryNode unaryNode) { loadADD(unaryNode, resultBounds); return false; } @Override public boolean enterBIT_NOT(final 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(final 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) { checkAssignTarget(unaryNode.getExpression()); loadDECINC(unaryNode); return false; } @Override public boolean enterJoinPredecessorExpression(final JoinPredecessorExpression joinExpr) { loadMaybeDiscard(joinExpr, joinExpr.getExpression(), resultBounds); return false; } @Override public boolean enterGetSplitState(final GetSplitState getSplitState) { method.loadScope(); method.invoke(Scope.GET_SPLIT_STATE); return false; } @Override public boolean enterDefault(final Node otherNode) { // Must have handled all expressions that can legally be encountered. throw new AssertionError(otherNode.getClass().getName()); } }); if(!isCurrentDiscard) { coerceStackTop(resultBounds); } return method; } private MethodEmitter coerceStackTop(final TypeBounds typeBounds) { return method.convert(typeBounds.within(method.peekType())); } /** * Closes any still open entries for this block's local variables in the bytecode local variable table. * * @param block block containing symbols. */ private void closeBlockVariables(final Block block) { for (final Symbol symbol : block.getSymbols()) { if (symbol.isBytecodeLocal()) { method.closeLocalVariable(symbol, block.getBreakLabel()); } } } @Override public boolean enterBlock(final Block block) { final Label entryLabel = block.getEntryLabel(); if (entryLabel.isBreakTarget()) { // Entry label is a break target only for an inlined finally block. assert !method.isReachable(); method.breakLabel(entryLabel, lc.getUsedSlotCount()); } else { method.label(entryLabel); } if(!method.isReachable()) { return false; } if(lc.isFunctionBody() && emittedMethods.contains(lc.getCurrentFunction().getName())) { return false; } initLocals(block); assert lc.getUsedSlotCount() == method.getFirstTemp(); return true; } boolean useOptimisticTypes() { return !lc.inSplitNode() && compiler.useOptimisticTypes(); } @Override public Node leaveBlock(final Block block) { popBlockScope(block); method.beforeJoinPoint(block); closeBlockVariables(block); lc.releaseSlots(); assert !method.isReachable() || (lc.isFunctionBody() ? 0 : lc.getUsedSlotCount()) == method.getFirstTemp() : "reachable="+method.isReachable() + " isFunctionBody=" + lc.isFunctionBody() + " usedSlotCount=" + lc.getUsedSlotCount() + " firstTemp=" + method.getFirstTemp(); return block; } private void popBlockScope(final Block block) { final Label breakLabel = block.getBreakLabel(); if (block.providesScopeCreator()) { scopeObjectCreators.pop(); } if(!block.needsScope() || lc.isFunctionBody()) { emitBlockBreakLabel(breakLabel); return; } final Label beginTryLabel = scopeEntryLabels.pop(); final Label recoveryLabel = new Label("block_popscope_catch"); 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 { 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)); } private void popScopes(final int count) { if(count == 0) { return; } assert count > 0; // together with count == 0 check, asserts nonnegative count if (!method.hasScope()) { // We can sometimes invoke this method even if the method has no slot for the scope object. Typical example: // for(;;) { with({}) { break; } }. WithNode normally creates a scope, but if it uses no identifiers and // nothing else forces creation of a scope in the method, we just won't have the :scope local variable. return; } method.loadCompilerConstant(SCOPE); if (count > 1) { method.load(count); method.invoke(ScriptObject.GET_PROTO_DEPTH); } else { method.invoke(ScriptObject.GET_PROTO); } method.storeCompilerConstant(SCOPE); } @Override public boolean enterBreakNode(final BreakNode breakNode) { return enterJumpStatement(breakNode); } @Override public boolean enterJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) { return enterJumpStatement(jumpToInlinedFinally); } private boolean enterJumpStatement(final JumpStatement jump) { if(!method.isReachable()) { return false; } enterStatement(jump); method.beforeJoinPoint(jump); popScopesUntil(jump.getPopScopeLimit(lc)); final Label targetLabel = jump.getTargetLabel(lc); targetLabel.markAsBreakTarget(); method._goto(targetLabel); return false; } private int loadArgs(final List<Expression> args) { final int argCount = args.size(); // arg have already been converted to objects here. if (argCount > LinkerCallSite.ARGLIMIT) { loadArgsArray(args); return 1; } for (final Expression arg : args) { assert arg != null; loadExpressionUnbounded(arg); } return argCount; } private boolean loadCallNode(final CallNode callNode, final TypeBounds resultBounds) { lineNumber(callNode.getLineNumber()); final List<Expression> args = callNode.getArgs(); final Expression function = callNode.getFunction(); final Block currentBlock = lc.getCurrentBlock(); final CodeGeneratorLexicalContext codegenLexicalContext = lc; function.accept(new SimpleNodeVisitor() { private MethodEmitter sharedScopeCall(final IdentNode identNode, final int flags) { final Symbol symbol = identNode.getSymbol(); final boolean isFastScope = isFastScope(symbol); new OptimisticOperation(callNode, resultBounds) { @Override void loadStack() { method.loadCompilerConstant(SCOPE); if (isFastScope) { method.load(getScopeProtoDepth(currentBlock, symbol)); } else { method.load(-1); // Bypass fast-scope code in shared callsite } loadArgs(args); } @Override void consumeStack() { final Type[] paramTypes = method.getTypesFromStack(args.size()); // 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, flags); scopeCall.generateInvoke(method); } }.emit(); return method; } private void scopeCall(final IdentNode ident, final int flags) { new OptimisticOperation(callNode, resultBounds) { int argsCount; @Override void loadStack() { 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(2 + argsCount, flags, ident.getName()); } }.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(callNode, resultBounds) { int argsCount; @Override void loadStack() { /* * We want to load 'eval' to check if it is indeed global builtin eval. * If this eval call is inside a 'with' statement, GET_METHOD_PROPERTY * would be generated if ident is a "isFunction". But, that would result in a * bound function from WithObject. We don't want that as bound function as that * won't be detected as builtin eval. So, we make ident as "not a function" which * results in GET_PROPERTY being generated and so WithObject * would return unbounded eval function. * * Example: * * var global = this; * function func() { * with({ eval: global.eval) { eval("var x = 10;") } * } */ loadExpressionAsObject(ident.setIsNotFunction()); // Type.OBJECT as foo() makes no sense if foo == 3 globalIsEval(); method.ifeq(is_not_eval); // Load up self (scope). method.loadCompilerConstant(SCOPE); final List<Expression> evalArgs = callNode.getEvalArgs().getArgs(); // load evaluated code loadExpressionAsObject(evalArgs.get(0)); // load second and subsequent args for side-effect final int numArgs = evalArgs.size(); for (int i = 1; i < numArgs; i++) { loadAndDiscard(evalArgs.get(i)); } method._goto(invoke_direct_eval); method.label(is_not_eval); // load this time but with GET_METHOD_PROPERTY loadExpressionAsObject(ident); // Type.OBJECT as foo() makes no sense if foo == 3 // This is some scope 'eval' or global eval replaced by user // but not the built-in ECMAScript 'eval' function call method.loadNull(); argsCount = loadArgs(callNode.getArgs()); } @Override void consumeStack() { // Ordinary call dynamicCall(2 + argsCount, flags, "eval"); method._goto(eval_done); method.label(invoke_direct_eval); // Special/extra 'eval' arguments. These can be loaded late (in consumeStack) as we know none of // them can ever be optimistic. method.loadCompilerConstant(THIS); method.load(callNode.getEvalArgs().getLocation()); method.load(CodeGenerator.this.lc.getCurrentFunction().isStrict()); // direct call to Global.directEval globalDirectEval(); convertOptimisticReturnValue(); coerceStackTop(resultBounds); } }.emit(); method.label(eval_done); } @Override public boolean enterIdentNode(final IdentNode node) { final Symbol symbol = node.getSymbol(); if (symbol.isScope()) { final int flags = getScopeCallSiteFlags(symbol); final int useCount = symbol.getUseCount(); // Threshold for generating shared scope callsite is lower for fast scope symbols because we know // we can dial in the correct scope. However, we also need to enable it for non-fast scopes to // support huge scripts like mandreel.js. if (callNode.isEval()) { evalCall(node, flags); } else if (useCount <= SharedScopeCall.FAST_SCOPE_CALL_THRESHOLD || !isFastScope(symbol) && useCount <= SharedScopeCall.SLOW_SCOPE_CALL_THRESHOLD || CodeGenerator.this.lc.inDynamicScope() || callNode.isOptimistic()) { scopeCall(node, flags); } else { sharedScopeCall(node, flags); } assert method.peekType().equals(resultBounds.within(callNode.getType())) : method.peekType() + " != " + resultBounds + "(" + callNode.getType() + ")"; } else { enterDefault(node); } return false; } @Override public boolean enterAccessNode(final AccessNode node) { //check if this is an apply to call node. only real applies, that haven't been //shadowed from their way to the global scope counts //call nodes have program points. final int flags = getCallSiteFlags() | (callNode.isApplyToCall() ? CALLSITE_APPLY_TO_CALL : 0); new OptimisticOperation(callNode, resultBounds) { int argCount; @Override void loadStack() { 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(); method.dynamicGet(node.getType(), node.getProperty(), flags, true, node.isIndex()); method.swap(); argCount = loadArgs(args); } @Override void consumeStack() { dynamicCall(2 + argCount, flags, node.toString(false)); } }.emit(); return false; } @Override public boolean enterFunctionNode(final FunctionNode origCallee) { new OptimisticOperation(callNode, resultBounds) { FunctionNode callee; int argsCount; @Override void loadStack() { callee = (FunctionNode)origCallee.accept(CodeGenerator.this); if (callee.isStrict()) { // "this" is undefined method.loadUndefined(Type.OBJECT); } else { // get global from scope (which is the self) globalInstance(); } argsCount = loadArgs(args); } @Override void consumeStack() { dynamicCall(2 + argsCount, getCallSiteFlags(), null); } }.emit(); return false; } @Override public boolean enterIndexNode(final IndexNode node) { new OptimisticOperation(callNode, resultBounds) { int argsCount; @Override void loadStack() { loadExpressionAsObject(node.getBase()); method.dup(); final Type indexType = node.getIndex().getType(); if (indexType.isObject() || indexType.isBoolean()) { loadExpressionAsObject(node.getIndex()); //TODO boolean } else { 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. assert !node.isOptimistic(); method.dynamicGetIndex(node.getType(), getCallSiteFlags(), true); method.swap(); argsCount = loadArgs(args); } @Override void consumeStack() { dynamicCall(2 + argsCount, getCallSiteFlags(), node.toString(false)); } }.emit(); return false; } @Override protected boolean enterDefault(final Node node) { new OptimisticOperation(callNode, resultBounds) { int argsCount; @Override void loadStack() { // Load up function. 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 = getCallSiteFlags() | CALLSITE_SCOPE; dynamicCall(2 + argsCount, flags, node.toString(false)); } }.emit(); return false; } }); return false; } /** * 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); } @Override public boolean enterContinueNode(final ContinueNode continueNode) { return enterJumpStatement(continueNode); } @Override public boolean enterEmptyNode(final EmptyNode emptyNode) { // Don't even record the line number, it's irrelevant as there's no code. return false; } @Override public boolean enterExpressionStatement(final ExpressionStatement expressionStatement) { if(!method.isReachable()) { return false; } enterStatement(expressionStatement); loadAndDiscard(expressionStatement.getExpression()); assert method.getStackSize() == 0 : "stack not empty in " + expressionStatement; return false; } @Override public boolean enterBlockStatement(final BlockStatement blockStatement) { if(!method.isReachable()) { return false; } enterStatement(blockStatement); blockStatement.getBlock().accept(this); return false; } @Override public boolean enterForNode(final ForNode forNode) { if(!method.isReachable()) { return false; } enterStatement(forNode); if (forNode.isForInOrOf()) { enterForIn(forNode); } else { final Expression init = forNode.getInit(); if (init != null) { loadAndDiscard(init); } enterForOrWhile(forNode, forNode.getModify()); } return false; } private void enterForIn(final ForNode forNode) { loadExpression(forNode.getModify(), TypeBounds.OBJECT); if (forNode.isForEach()) { method.invoke(ScriptRuntime.TO_VALUE_ITERATOR); } else if (forNode.isForIn()) { method.invoke(ScriptRuntime.TO_PROPERTY_ITERATOR); } else if (forNode.isForOf()) { method.invoke(ScriptRuntime.TO_ES6_ITERATOR); } else { throw new IllegalArgumentException("Unexpected for node"); } 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.ifeq(breakLabel); } new Store<Expression>(forNode.getInit()) { @Override protected void storeNonDiscard() { // 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() { new OptimisticOperation((Optimistic)forNode.getInit(), TypeBounds.UNBOUNDED) { @Override void loadStack() { method.load(ITERATOR_TYPE, iterSlot); } @Override void consumeStack() { method.invoke(interfaceCallNoLookup(ITERATOR_CLASS, "next", Object.class)); convertOptimisticReturnValue(); } }.emit(); } }.store(); body.accept(this); if (forNode.needsScopeCreator() && lc.getCurrentBlock().providesScopeCreator()) { // for-in loops with lexical declaration need a new scope for each iteration. final FieldObjectCreator<?> creator = scopeObjectCreators.peek(); assert creator != null; creator.createForInIterationScope(method); method.storeCompilerConstant(SCOPE); } if(method.isReachable()) { method._goto(continueLabel); } method.label(breakLabel); } /** * Initialize the slots in a frame to undefined. * * @param block block with local vars. */ private void initLocals(final Block 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); method.invoke(ScriptFunction.GET_SCOPE); } else { assert function.hasScopeBlock(); method.loadNull(); } method.storeCompilerConstant(SCOPE); } if (function.needsArguments()) { initArguments(function); } } /* * Determine if block needs scope, if not, just do initSymbols for this block. */ if (block.needsScope()) { /* * Determine if function is varargs and consequently variables have to * be in the scope. */ final boolean varsInScope = function.allVarsInScope(); // TODO for LET we can do better: if *block* does not contain any eval/with, we don't need its vars in scope. 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()) { continue; } if (symbol.isVar()) { assert !varsInScope || symbol.isScope(); 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(); //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.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 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(); 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() { if (!useDualFields() || value == null || paramType == null || paramType.isBoolean()) { return Object.class; } return paramType.getTypeClass(); } }); } } /* * Create a new object based on the symbols and values, generate * bootstrap code for object */ final FieldObjectCreator<Symbol> creator = new FieldObjectCreator<Symbol>(this, tuples, true, hasArguments) { @Override protected void loadValue(final Symbol value, final Type type) { method.load(value, type); } }; creator.makeObject(method); if (block.providesScopeCreator()) { scopeObjectCreators.push(creator); } // program function: merge scope into global if (isFunctionBody && function.isProgram()) { method.invoke(ScriptRuntime.MERGE_SCOPE); } method.storeCompilerConstant(SCOPE); 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("scope_entry"); scopeEntryLabels.push(scopeEntryLabel); method.label(scopeEntryLabel); } } 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; for (final IdentNode param : function.getParameters()) { param.getSymbol().setFieldIndex(nextParam++); } } // Debugging: print symbols? @see --print-symbols flag printSymbols(block, function, (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(final 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(final 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(final 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()) { method.loadCompilerConstant(CALLEE); } else { // If function is strict mode, "arguments.callee" is not populated, so we don't necessarily need the // caller. assert function.isStrict(); method.loadNull(); } method.load(function.getParameters().size()); globalAllocateArguments(); method.storeCompilerConstant(ARGUMENTS); } private boolean skipFunction(final FunctionNode functionNode) { final ScriptEnvironment env = compiler.getScriptEnvironment(); final boolean lazy = env._lazy_compilation; final boolean onDemand = compiler.isOnDemandCompilation(); // If this is on-demand or lazy compilation, don't compile a nested (not topmost) function. if((onDemand || lazy) && lc.getOutermostFunction() != functionNode) { return true; } // If lazy compiling with optimistic types, don't compile the program eagerly either. It will soon be // invalidated anyway. In presence of a class cache, this further means that an obsoleted program version // lingers around. Also, currently loading previously persisted optimistic types information only works if // we're on-demand compiling a function, so with this strategy the :program method can also have the warmup // benefit of using previously persisted types. // // NOTE that this means the first compiled class will effectively just have a :createProgramFunction method, and // the RecompilableScriptFunctionData (RSFD) object in its constants array. It won't even have the :program // method. This is by design. It does mean that we're wasting one compiler execution (and we could minimize this // by just running it up to scope depth calculation, which creates the RSFDs and then this limited codegen). // We could emit an initial separate compile unit with the initial version of :program in it to better utilize // the compilation pipeline, but that would need more invasive changes, as currently the assumption that // :program is emitted into the first compilation unit of the function lives in many places. return !onDemand && lazy && env._optimistic_types && functionNode.isProgram(); } @Override public boolean enterFunctionNode(final FunctionNode functionNode) { if (skipFunction(functionNode)) { // In case we are not generating code for the function, we must create or retrieve the function object and // load it on the stack here. newFunctionObject(functionNode, false); return false; } final String fnName = functionNode.getName(); // NOTE: we only emit the method for a function with the given name once. We can have multiple functions with // the same name as a result of inlining finally blocks. However, in the future -- with type specialization, // notably -- we might need to check for both name *and* signature. Of course, even that might not be // sufficient; the function might have a code dependency on the type of the variables in its enclosing scopes, // and the type of such a variable can be different in catch and finally blocks. So, in the future we will have // to decide to either generate a unique method for each inlined copy of the function, maybe figure out its // exact type closure and deduplicate based on that, or just decide that functions in finally blocks aren't // worth it, and generate one method with most generic type closure. if (!emittedMethods.contains(fnName)) { log.info("=== BEGIN ", fnName); assert functionNode.getCompileUnit() != null : "no compile unit for " + fnName + " " + Debug.id(functionNode); unit = lc.pushCompileUnit(functionNode.getCompileUnit()); assert lc.hasCompileUnits(); final ClassEmitter classEmitter = unit.getClassEmitter(); pushMethodEmitter(isRestOf() ? classEmitter.restOfMethod(functionNode) : classEmitter.method(functionNode)); method.setPreventUndefinedLoad(); if(useOptimisticTypes()) { lc.pushUnwarrantedOptimismHandlers(); } // new method - reset last line number lastLineNumber = -1; method.begin(); if (isRestOf()) { assert continuationInfo == null; continuationInfo = new ContinuationInfo(); method.gotoLoopStart(continuationInfo.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 { final boolean markOptimistic; if (emittedMethods.add(functionNode.getName())) { markOptimistic = generateUnwarrantedOptimismExceptionHandlers(functionNode); generateContinuationHandler(); method.end(); // wrap up this method unit = lc.popCompileUnit(functionNode.getCompileUnit()); popMethodEmitter(); log.info("=== END ", functionNode.getName()); } else { markOptimistic = false; } FunctionNode newFunctionNode = functionNode; if (markOptimistic) { newFunctionNode = newFunctionNode.setFlag(lc, FunctionNode.IS_DEOPTIMIZABLE); } newFunctionObject(newFunctionNode, true); return newFunctionNode; } catch (final Throwable t) { Context.printStackTrace(t); final VerifyError e = new VerifyError("Code generation bug in \"" + functionNode.getName() + "\": likely stack misaligned: " + t + " " + functionNode.getSource().getName()); e.initCause(t); throw e; } } @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(); if (Expression.isAlwaysTrue(test)) { loadAndDiscard(test); pass.accept(this); return false; } else if (Expression.isAlwaysFalse(test)) { loadAndDiscard(test); if (fail != null) { fail.accept(this); } return false; } final boolean hasFailConversion = LocalVariableConversion.hasLiveConversion(ifNode); final Label failLabel = new Label("if_fail"); final Label afterLabel = (fail == null && !hasFailConversion) ? null : new Label("if_done"); emitBranch(test, failLabel, false); pass.accept(this); if(method.isReachable() && afterLabel != null) { method._goto(afterLabel); //don't fallthru to fail block } method.label(failLabel); if (fail != null) { fail.accept(this); } else if(hasFailConversion) { method.beforeJoinPoint(ifNode); } if(afterLabel != null && afterLabel.isReachable()) { method.label(afterLabel); } 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) { lineNumber(statement); } private void lineNumber(final Statement statement) { lineNumber(statement.getLineNumber()); } private void lineNumber(final int lineNumber) { if (lineNumber != lastLineNumber && lineNumber != Node.NO_LINE_NUMBER) { method.lineNumber(lineNumber); 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. * * @param arrayLiteralNode the array of contents * @param arrayType the type of the array, e.g. ARRAY_NUMBER or ARRAY_OBJECT */ private void loadArray(final ArrayLiteralNode arrayLiteralNode, final ArrayType arrayType) { assert arrayType == Type.INT_ARRAY || arrayType == Type.NUMBER_ARRAY || arrayType == Type.OBJECT_ARRAY; final Expression[] nodes = arrayLiteralNode.getValue(); final Object presets = arrayLiteralNode.getPresets(); final int[] postsets = arrayLiteralNode.getPostsets(); final List<Splittable.SplitRange> ranges = arrayLiteralNode.getSplitRanges(); loadConstant(presets); final Type elementType = arrayType.getElementType(); if (ranges != null) { loadSplitLiteral(new SplitLiteralCreator() { @Override public void populateRange(final MethodEmitter method, final Type type, final int slot, final int start, final int end) { for (int i = start; i < end; i++) { method.load(type, slot); storeElement(nodes, elementType, postsets[i]); } method.load(type, slot); } }, ranges, arrayType); return; } 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); } } private void storeElement(final Expression[] nodes, final Type elementType, final int index) { method.load(index); final Expression element = nodes[index]; if (element == null) { method.loadEmpty(elementType); } else { loadExpressionAsType(element, elementType); } method.arraystore(); } private MethodEmitter loadArgsArray(final List<Expression> args) { final Object[] array = new Object[args.size()]; loadConstant(array); for (int i = 0; i < args.size(); i++) { method.dup(); method.load(i); loadExpression(args.get(i), TypeBounds.OBJECT); // variable arity methods always take objects method.arraystore(); } return method; } /** * Load a constant from the constant array. This is only public to be callable from the objects * subpackage. Do not call directly. * * @param string string to load */ void loadConstant(final String string) { final String unitClassName = unit.getUnitClassName(); final ClassEmitter classEmitter = unit.getClassEmitter(); final int index = compiler.getConstantData().add(string); method.load(index); method.invokestatic(unitClassName, GET_STRING.symbolName(), methodDescriptor(String.class, int.class)); classEmitter.needGetConstantMethod(String.class); } /** * Load a constant from the constant array. This is only public to be callable from the objects * subpackage. Do not call directly. * * @param object object to load */ void loadConstant(final Object object) { loadConstant(object, unit, method); } private void loadConstant(final Object object, final CompileUnit compileUnit, final MethodEmitter methodEmitter) { final String unitClassName = compileUnit.getUnitClassName(); final ClassEmitter classEmitter = compileUnit.getClassEmitter(); final int index = compiler.getConstantData().add(object); final Class<?> cls = object.getClass(); if (cls == PropertyMap.class) { methodEmitter.load(index); methodEmitter.invokestatic(unitClassName, GET_MAP.symbolName(), methodDescriptor(PropertyMap.class, int.class)); classEmitter.needGetConstantMethod(PropertyMap.class); } else if (cls.isArray()) { methodEmitter.load(index); final String methodName = ClassEmitter.getArrayMethodName(cls); methodEmitter.invokestatic(unitClassName, methodName, methodDescriptor(cls, int.class)); classEmitter.needGetConstantMethod(cls); } else { methodEmitter.loadConstants().load(index).arrayload(); if (object instanceof ArrayData) { methodEmitter.checkcast(ArrayData.class); methodEmitter.invoke(virtualCallNoLookup(ArrayData.class, "copy", ArrayData.class)); } else if (cls != Object.class) { methodEmitter.checkcast(cls); } } } private void loadConstantsAndIndex(final Object object, final MethodEmitter methodEmitter) { methodEmitter.loadConstants().load(compiler.getConstantData().add(object)); } // literal values 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(resultBounds.within(Type.OBJECT)); } else if (value instanceof String) { final String string = (String)value; if (string.length() > MethodEmitter.LARGE_STRING_THRESHOLD / 3) { // 3 == max bytes per encoded char loadConstant(string); } else { method.load(string); } } else if (value instanceof RegexToken) { loadRegex((RegexToken)value); } else if (value instanceof Boolean) { method.load((Boolean)value); } else if (value instanceof Integer) { 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 { method.load((Integer)value); } } else if (value instanceof Double) { 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(); loadArray(arrayLiteral, atype); globalAllocateArray(atype); } else { throw new UnsupportedOperationException("Unknown literal for " + node.getClass() + " " + value.getClass() + " " + value); } } private MethodEmitter loadRegexToken(final RegexToken value) { method.load(value.getExpression()); method.load(value.getOptions()); return globalNewRegExp(); } private MethodEmitter loadRegex(final RegexToken regexToken) { if (regexFieldCount > MAX_REGEX_FIELDS) { return loadRegexToken(regexToken); } // emit field final String regexName = lc.getCurrentFunction().uniqueName(REGEX_PREFIX.symbolName()); final ClassEmitter classEmitter = unit.getClassEmitter(); classEmitter.field(EnumSet.of(PRIVATE, STATIC), regexName, Object.class); regexFieldCount++; // get field, if null create new regex, finally clone regex object method.getStatic(unit.getUnitClassName(), regexName, typeDescriptor(Object.class)); method.dup(); final Label cachedLabel = new Label("cached"); method.ifnonnull(cachedLabel); method.pop(); loadRegexToken(regexToken); method.dup(); method.putStatic(unit.getUnitClassName(), regexName, typeDescriptor(Object.class)); method.label(cachedLabel); globalRegExpCopy(); return method; } /** * Check if a property value contains a particular program point * @param value value * @param pp program point * @return true if it's there. */ private static boolean propertyValueContains(final Expression value, final int pp) { return new Supplier<Boolean>() { boolean contains; @Override public Boolean get() { value.accept(new SimpleNodeVisitor() { @Override public boolean enterFunctionNode(final FunctionNode functionNode) { return false; } @Override public boolean enterDefault(final Node node) { if (contains) { return false; } if (node instanceof Optimistic && ((Optimistic)node).getProgramPoint() == pp) { contains = true; return false; } return true; } }); return contains; } }.get(); } private void loadObjectNode(final ObjectNode objectNode) { final List<PropertyNode> elements = objectNode.getElements(); final List<MapTuple<Expression>> tuples = new ArrayList<>(); // List below will contain getter/setter properties and properties with computed keys (ES6) final List<PropertyNode> specialProperties = new ArrayList<>(); final int ccp = getCurrentContinuationEntryPoint(); final List<Splittable.SplitRange> ranges = objectNode.getSplitRanges(); Expression protoNode = null; boolean restOfProperty = false; for (final PropertyNode propertyNode : elements) { final Expression value = propertyNode.getValue(); final String key = propertyNode.getKeyName(); final boolean isComputedOrAccessor = propertyNode.isComputed() || value == null; // Just use a pseudo-symbol. We just need something non null; use the name and zero flags. final Symbol symbol = isComputedOrAccessor ? null : new Symbol(key, 0); if (isComputedOrAccessor) { // Properties with computed names or getter/setters need special handling. specialProperties.add(propertyNode); } else if (propertyNode.getKey() instanceof IdentNode && key.equals(ScriptObject.PROTO_PROPERTY_NAME)) { // ES6 draft compliant __proto__ inside object literal // Identifier key and name is __proto__ protoNode = value; continue; } restOfProperty |= value != null && isValid(ccp) && propertyValueContains(value, ccp); //for literals, a value of null means object type, i.e. the value null or getter setter function //(I think) final Class<?> valueType = (!useDualFields() || isComputedOrAccessor || value.getType().isBoolean()) ? Object.class : value.getType().getTypeClass(); tuples.add(new MapTuple<Expression>(key, symbol, Type.typeFor(valueType), value) { @Override public Class<?> getValueType() { return type.getTypeClass(); } }); } final ObjectCreator<?> oc; if (elements.size() > OBJECT_SPILL_THRESHOLD) { oc = new SpillObjectCreator(this, tuples); } else { oc = new FieldObjectCreator<Expression>(this, tuples) { @Override protected void loadValue(final Expression node, final Type type) { // Use generic type in order to avoid conversion between object types loadExpressionAsType(node, Type.generic(type)); }}; } if (ranges != null) { oc.createObject(method); loadSplitLiteral(oc, ranges, Type.typeFor(oc.getAllocatorClass())); } else { oc.makeObject(method); } //if this is a rest of method and our continuation point was found as one of the values //in the properties above, we need to reset the map to oc.getMap() in the continuation //handler if (restOfProperty) { final ContinuationInfo ci = getContinuationInfo(); ci.setObjectLiteralMap(method.getStackSize(), oc.getMap()); } method.dup(); if (protoNode != null) { loadExpressionAsObject(protoNode); // take care of { __proto__: 34 } or some such! method.convert(Type.OBJECT); method.invoke(ScriptObject.SET_PROTO_FROM_LITERAL); } else { method.invoke(ScriptObject.SET_GLOBAL_OBJECT_PROTO); } for (final PropertyNode propertyNode : specialProperties) { method.dup(); if (propertyNode.isComputed()) { assert propertyNode.getKeyName() == null; loadExpressionAsObject(propertyNode.getKey()); } else { method.loadKey(propertyNode.getKey()); } if (propertyNode.getValue() != null) { loadExpressionAsObject(propertyNode.getValue()); method.load(0); method.invoke(ScriptObject.GENERIC_SET); } else { final FunctionNode getter = propertyNode.getGetter(); final FunctionNode setter = propertyNode.getSetter(); assert getter != null || setter != null; if (getter == null) { method.loadNull(); } else { getter.accept(this); } if (setter == null) { method.loadNull(); } else { setter.accept(this); } method.invoke(ScriptObject.SET_USER_ACCESSORS); } } } @Override public boolean enterReturnNode(final ReturnNode returnNode) { if(!method.isReachable()) { return false; } enterStatement(returnNode); final Type returnType = lc.getCurrentFunction().getReturnType(); final Expression expression = returnNode.getExpression(); if (expression != null) { loadExpressionUnbounded(expression); } else { method.loadUndefined(returnType); } method._return(returnType); return false; } private boolean undefinedCheck(final RuntimeNode runtimeNode, final List<Expression> args) { final Request request = runtimeNode.getRequest(); if (!Request.isUndefinedCheck(request)) { return false; } final Expression lhs = args.get(0); final Expression rhs = args.get(1); 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; } assert undefinedSymbol != null; //remove warning if (!undefinedSymbol.isScope()) { return false; //disallow undefined as local var or parameter } if (lhsSymbol == undefinedSymbol && lhs.getType().isPrimitive()) { //we load the undefined first. never mind, because this will deoptimize anyway return false; } if(isDeoptimizedExpression(lhs)) { // This is actually related to "lhs.getType().isPrimitive()" above: any expression being deoptimized in // the current chain of rest-of compilations used to have a type narrower than Object (so it was primitive). // We must not perform undefined check specialization for them, as then we'd violate the basic rule of // "Thou shalt not alter the stack shape between a deoptimized method and any of its (transitive) rest-ofs." return false; } //make sure that undefined has not been overridden or scoped as a local var //between us and global if (!compiler.isGlobalSymbol(lc.getCurrentFunction(), "undefined")) { return false; } final boolean isUndefinedCheck = request == Request.IS_UNDEFINED; final Expression expr = undefinedSymbol == lhsSymbol ? rhs : lhs; if (expr.getType().isPrimitive()) { 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 checkTrue = new Label("ud_check_true"); final Label end = new Label("end"); loadExpressionAsObject(expr); method.loadUndefined(Type.OBJECT); method.if_acmpeq(checkTrue); method.load(!isUndefinedCheck); method._goto(end); method.label(checkTrue); method.load(isUndefinedCheck); method.label(end); } 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(); if (!Request.isEQ(request) && !Request.isNE(request)) { return false; } assert args.size() == 2 : "EQ or NE or TYPEOF need two args"; Expression lhs = args.get(0); Expression rhs = args.get(1); if (isNullLiteral(lhs)) { final Expression tmp = lhs; lhs = rhs; rhs = tmp; } if (!isNullLiteral(rhs)) { return false; } if (!lhs.getType().isObject()) { return false; } if(isDeoptimizedExpression(lhs)) { // This is actually related to "!lhs.getType().isObject()" above: any expression being deoptimized in // the current chain of rest-of compilations used to have a type narrower than Object. We must not // perform null check specialization for them, as then we'd no longer be loading aconst_null on stack // and thus violate the basic rule of "Thou shalt not alter the stack shape between a deoptimized // method and any of its (transitive) rest-ofs." // NOTE also that if we had a representation for well-known constants (e.g. null, 0, 1, -1, etc.) in // Label$Stack.localLoads then this wouldn't be an issue, as we would never (somewhat ridiculously) // allocate a temporary local to hold the result of aconst_null before attempting an optimistic // operation. return false; } // this is a null literal check, so if there is implicit coercion // involved like {D}x=null, we will fail - this is very rare final Label trueLabel = new Label("trueLabel"); final Label falseLabel = new Label("falseLabel"); final Label endLabel = new Label("end"); loadExpressionUnbounded(lhs); //lhs final Label popLabel; if (!Request.isStrict(request)) { method.dup(); //lhs lhs popLabel = new Label("pop"); } else { popLabel = null; } if (Request.isEQ(request)) { method.ifnull(!Request.isStrict(request) ? popLabel : trueLabel); if (!Request.isStrict(request)) { method.loadUndefined(Type.OBJECT); method.if_acmpeq(trueLabel); } method.label(falseLabel); method.load(false); method._goto(endLabel); if (!Request.isStrict(request)) { method.label(popLabel); method.pop(); } method.label(trueLabel); method.load(true); method.label(endLabel); } else if (Request.isNE(request)) { method.ifnull(!Request.isStrict(request) ? popLabel : falseLabel); if (!Request.isStrict(request)) { method.loadUndefined(Type.OBJECT); method.if_acmpeq(falseLabel); } method.label(trueLabel); method.load(true); method._goto(endLabel); if (!Request.isStrict(request)) { method.label(popLabel); method.pop(); } method.label(falseLabel); method.load(false); method.label(endLabel); } assert runtimeNode.getType().isBoolean(); method.convert(runtimeNode.getType()); return true; } /** * Was this expression or any of its subexpressions deoptimized in the current recompilation chain of rest-of methods? * @param rootExpr the expression being tested * @return true if the expression or any of its subexpressions was deoptimized in the current recompilation chain. */ private boolean isDeoptimizedExpression(final Expression rootExpr) { if(!isRestOf()) { return false; } return new Supplier<Boolean>() { boolean contains; @Override public Boolean get() { rootExpr.accept(new SimpleNodeVisitor() { @Override public boolean enterFunctionNode(final FunctionNode functionNode) { return false; } @Override public boolean enterDefault(final Node node) { if(!contains && node instanceof Optimistic) { final int pp = ((Optimistic)node).getProgramPoint(); contains = isValid(pp) && isContinuationEntryPoint(pp); } return !contains; } }); return contains; } }.get(); } private void loadRuntimeNode(final RuntimeNode runtimeNode) { final List<Expression> args = new ArrayList<>(runtimeNode.getArgs()); if (nullCheck(runtimeNode, args)) { return; } else if(undefinedCheck(runtimeNode, args)) { return; } // Revert a false undefined check to a strict equality check final RuntimeNode newRuntimeNode; 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; } for (final Expression arg : args) { loadExpression(arg, TypeBounds.OBJECT); } method.invokestatic( CompilerConstants.className(ScriptRuntime.class), newRuntimeNode.getRequest().toString(), new FunctionSignature( false, false, newRuntimeNode.getType(), args.size()).toString()); method.convert(newRuntimeNode.getType()); } 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 loadSplitLiteral(final SplitLiteralCreator creator, final List<Splittable.SplitRange> ranges, final Type literalType) { assert ranges != null; // final Type literalType = Type.typeFor(literalClass); final MethodEmitter savedMethod = method; final FunctionNode currentFunction = lc.getCurrentFunction(); for (final Splittable.SplitRange splitRange : ranges) { unit = lc.pushCompileUnit(splitRange.getCompileUnit()); assert unit != null; final String className = unit.getUnitClassName(); final String name = currentFunction.uniqueName(SPLIT_PREFIX.symbolName()); final Class<?> clazz = literalType.getTypeClass(); final String signature = methodDescriptor(clazz, ScriptFunction.class, Object.class, ScriptObject.class, clazz); pushMethodEmitter(unit.getClassEmitter().method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), name, signature)); method.setFunctionNode(currentFunction); method.begin(); defineCommonSplitMethodParameters(); defineSplitMethodParameter(CompilerConstants.SPLIT_ARRAY_ARG.slot(), literalType); // NOTE: when this is no longer needed, SplitIntoFunctions will no longer have to add IS_SPLIT // to synthetic functions, and FunctionNode.needsCallee() will no longer need to test for isSplit(). final int literalSlot = fixScopeSlot(currentFunction, 3); lc.enterSplitNode(); creator.populateRange(method, literalType, literalSlot, splitRange.getLow(), splitRange.getHigh()); method._return(); lc.exitSplitNode(); method.end(); lc.releaseSlots(); popMethodEmitter(); assert method == savedMethod; method.loadCompilerConstant(CALLEE).swap(); method.loadCompilerConstant(THIS).swap(); method.loadCompilerConstant(SCOPE).swap(); method.invokestatic(className, name, signature); unit = lc.popCompileUnit(unit); } } private int fixScopeSlot(final FunctionNode functionNode, final int extraSlot) { // TODO hack to move the scope to the expected slot (needed because split methods reuse the same slots as the root method) final int actualScopeSlot = functionNode.compilerConstant(SCOPE).getSlot(SCOPE_TYPE); final int defaultScopeSlot = SCOPE.slot(); int newExtraSlot = extraSlot; if (actualScopeSlot != defaultScopeSlot) { if (actualScopeSlot == extraSlot) { newExtraSlot = extraSlot + 1; method.defineBlockLocalVariable(newExtraSlot, newExtraSlot + 1); method.load(Type.OBJECT, extraSlot); method.storeHidden(Type.OBJECT, newExtraSlot); } else { method.defineBlockLocalVariable(actualScopeSlot, actualScopeSlot + 1); } method.load(SCOPE_TYPE, defaultScopeSlot); method.storeCompilerConstant(SCOPE); } return newExtraSlot; } @Override public boolean enterSplitReturn(final SplitReturn splitReturn) { if (method.isReachable()) { method.loadUndefined(lc.getCurrentFunction().getReturnType())._return(); } return false; } @Override public boolean enterSetSplitState(final SetSplitState setSplitState) { if (method.isReachable()) { method.setSplitState(setSplitState.getState()); } return false; } @Override public boolean enterSwitchNode(final SwitchNode switchNode) { if(!method.isReachable()) { return false; } enterStatement(switchNode); final Expression expression = switchNode.getExpression(); final List<CaseNode> cases = switchNode.getCases(); if (cases.isEmpty()) { // still evaluate expression for side-effects. loadAndDiscard(expression); return false; } final CaseNode defaultCase = switchNode.getDefaultCase(); final Label breakLabel = switchNode.getBreakLabel(); final int liveLocalsOnBreak = method.getUsedSlotsWithLiveTemporaries(); if (defaultCase != null && 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 = defaultCase != null ? defaultCase.getEntry() : breakLabel; final boolean hasSkipConversion = LocalVariableConversion.hasLiveConversion(switchNode); if (switchNode.isUniqueInteger()) { // Tree for sorting values. final TreeMap<Integer, Label> tree = new TreeMap<>(); // Build up sorted tree. for (final CaseNode caseNode : cases) { final Node test = caseNode.getTest(); if (test != null) { final Integer value = (Integer)((LiteralNode<?>)test).getValue(); final Label entry = caseNode.getEntry(); // Take first duplicate. if (!tree.containsKey(value)) { tree.put(value, entry); } } } // Copy values and labels to arrays. final int size = tree.size(); final Integer[] values = tree.keySet().toArray(new Integer[0]); final Label[] labels = tree.values().toArray(new Label[0]); // Discern low, high and range. final int lo = values[0]; final int hi = values[size - 1]; final long range = (long)hi - (long)lo + 1; // Find an unused value for default. int deflt = Integer.MIN_VALUE; for (final int value : values) { if (deflt == value) { deflt++; } else if (deflt < value) { break; } } // Load switch expression. loadExpressionUnbounded(expression); final Type type = expression.getType(); // If expression not int see if we can convert, if not use deflt to trigger default. if (!type.isInteger()) { method.load(deflt); final Class<?> exprClass = type.getTypeClass(); method.invoke(staticCallNoLookup(ScriptRuntime.class, "switchTagAsInt", int.class, exprClass.isPrimitive()? exprClass : Object.class, int.class)); } 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]; table[value - lo] = labels[i]; } method.tableswitch(lo, hi, defaultLabel, table); } else { final int[] ints = new int[size]; for (int i = 0; i < size; i++) { ints[i] = values[i]; } 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 { 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(Type.OBJECT, tagSlot); loadExpressionAsObject(test); method.invoke(ScriptRuntime.EQ_STRICT); method.ifne(caseNode.getEntry()); } } if (defaultCase != null) { 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); } 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 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; } final Source source = getCurrentSource(); final Expression expression = throwNode.getExpression(); final int position = throwNode.position(); final int line = throwNode.getLineNumber(); final int column = source.getColumn(position); // NOTE: we first evaluate the expression, and only after it was evaluated do we create the new ECMAException // object and then somewhat cumbersomely move it beneath the evaluated expression on the stack. The reason for // 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. loadExpressionAsObject(expression); method.load(source.getName()); method.load(line); method.load(column); method.invoke(ECMAException.CREATE); method.beforeJoinPoint(throwNode); method.athrow(); return false; } private Source getCurrentSource() { return lc.getCurrentFunction().getSource(); } @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 vmException = tryNode.getException(); final Label entry = new Label("try"); final Label recovery = new Label("catch"); 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); 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); if (method.isReachable()) { method._goto(skip); } for (final Block inlinedFinally : tryNode.getInlinedFinallies()) { TryNode.getLabelledInlinedFinallyBlock(inlinedFinally).accept(this); // All inlined finallies end with a jump or a return assert !method.isReachable(); } method._catch(recovery); 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); // Because of the peculiarities of the flow control, we need to use an explicit push/enterBlock/leaveBlock // here. lc.push(catchBlock); enterBlock(catchBlock); final CatchNode catchNode = (CatchNode)catchBlocks.get(i).getStatements().get(0); final IdentNode exception = catchNode.getExceptionIdentifier(); final Expression exceptionCondition = catchNode.getExceptionCondition(); final Block catchBody = catchNode.getBody(); new Store<IdentNode>(exception) { @Override protected void storeNonDiscard() { // 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(vmException, EXCEPTION_TYPE); return; } /* * If caught object is an instance of ECMAException, then * bind obj.thrown to the script catch var. Or else bind the * caught object itself to the script catch var. */ final Label notEcmaException = new Label("no_ecma_exception"); 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); } }.store(); final boolean isConditionalCatch = exceptionCondition != null; final Label nextCatch; if (isConditionalCatch) { loadExpressionAsBoolean(exceptionCondition); nextCatch = new Label("next_catch"); nextCatch.markAsBreakTarget(); method.ifeq(nextCatch); } else { nextCatch = null; } catchBody.accept(this); leaveBlock(catchBlock); lc.pop(catchBlock); if(nextCatch != null) { if(method.isReachable()) { method._goto(afterCatch); } method.breakLabel(nextCatch, lc.getUsedSlotCount()); } } // 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 assert tryNode.getFinallyBody() == null; return false; } @Override public boolean enterVarNode(final VarNode varNode) { if(!method.isReachable()) { return false; } final Expression init = varNode.getInit(); final IdentNode identNode = varNode.getName(); final Symbol identSymbol = identNode.getSymbol(); assert identSymbol != null : "variable node " + varNode + " requires a name with a symbol"; final boolean needsScope = identSymbol.isScope(); if (init == null) { // Block-scoped variables need a DECLARE flag to signal end of temporal dead zone (TDZ). // However, don't do this for CONST which always has an initializer except in the special case of // for-in/of loops, in which it is initialized in the loop header and should be left untouched here. if (needsScope && varNode.isLet()) { method.loadCompilerConstant(SCOPE); method.loadUndefined(Type.OBJECT); final int flags = getScopeCallSiteFlags(identSymbol) | CALLSITE_DECLARE; assert isFastScope(identSymbol); storeFastScopeVar(identSymbol, flags); } return false; } enterStatement(varNode); assert method != null; if (needsScope) { method.loadCompilerConstant(SCOPE); loadExpressionUnbounded(init); // block scoped variables need a DECLARE flag to signal end of temporal dead zone (TDZ) final int flags = getScopeCallSiteFlags(identSymbol) | (varNode.isBlockScoped() ? CALLSITE_DECLARE : 0); if (isFastScope(identSymbol)) { storeFastScopeVar(identSymbol, flags); } else { method.dynamicSet(identNode.getName(), flags, false); } } else { final Type identType = identNode.getType(); if(identType == Type.UNDEFINED) { // The initializer is either itself undefined (explicit assignment of undefined to undefined), // or the left hand side is a dead variable. 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) { if(!method.isReachable()) { return false; } if(whileNode.isDoWhile()) { enterDoWhile(whileNode); } else { enterStatement(whileNode); 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 (test != null) { 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 (loopNode.hasPerIterationScope() && lc.getCurrentBlock().needsScope()) { // ES6 for loops with LET init need a new scope for each iteration. We just create a shallow copy here. method.loadCompilerConstant(SCOPE); method.invoke(virtualCallNoLookup(ScriptObject.class, "copy", ScriptObject.class)); method.storeCompilerConstant(SCOPE); } 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 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 // scope object, its' possible that it won't have a slot assigned. In this case we'll only evaluate expression // for its side effect and visit the body, and not bother opening and closing a WithObject. final boolean hasScope = method.hasScope(); if (hasScope) { method.loadCompilerConstant(SCOPE); } loadExpressionAsObject(expression); final Label tryLabel; if (hasScope) { // Construct a WithObject if we have a scope method.invoke(ScriptRuntime.OPEN_WITH); method.storeCompilerConstant(SCOPE); tryLabel = new Label("with_try"); method.label(tryLabel); } else { // We just loaded the expression for its side effect and to check // for null or undefined value. globalCheckObjectCoercible(); tryLabel = null; } // Always process body body.accept(this); if (hasScope) { // Ensure we always close the WithObject final Label endLabel = new Label("with_end"); final Label catchLabel = new Label("with_catch"); final Label exitLabel = new Label("with_exit"); 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); } final boolean reachable = method.isReachable(); if(reachable) { popScope(); if(bodyCanThrow) { method._goto(exitLabel); } } if(bodyCanThrow) { method._catch(catchLabel); popScopeException(); method.athrow(); if(reachable) { method.label(exitLabel); } } } 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); } } private void loadBIT_NOT(final UnaryNode unaryNode) { loadExpression(unaryNode.getExpression(), TypeBounds.INT).load(-1).xor(); } 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, operand) { private void loadRhs() { loadExpression(operand, typeBounds, true); } @Override protected void evaluate() { if(isPostfix) { loadRhs(); } else { new OptimisticOperation(unaryNode, typeBounds) { @Override void loadStack() { loadRhs(); loadMinusOne(); } @Override void consumeStack() { doDecInc(getProgramPoint()); } }.emit(getOptimisticIgnoreCountForSelfModifyingExpression(operand)); } } @Override protected void storeNonDiscard() { super.storeNonDiscard(); if (isPostfix) { new OptimisticOperation(unaryNode, typeBounds) { @Override void loadStack() { loadMinusOne(); } @Override void consumeStack() { doDecInc(getProgramPoint()); } }.emit(1); // 1 for non-incremented result on the top of the stack pushed in evaluate() } } private void loadMinusOne() { if (type.isInteger()) { method.load(isIncrement ? 1 : -1); } else { method.load(isIncrement ? 1.0 : -1.0); } } private void doDecInc(final int programPoint) { method.add(programPoint); } }.store(); } private static int getOptimisticIgnoreCountForSelfModifyingExpression(final Expression target) { return target instanceof AccessNode ? 1 : target instanceof IndexNode ? 2 : 0; } 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.isCurrentDiscard(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.popDiscardIfCurrent(expr)) { assert !expr.isAssignment(); // NOTE: if we had a way to load with type void, we could avoid popping method.pop(); } } /** * Loads the expression with the specified type bounds, but if the parent expression is the current discard, * then instead loads and discards the expression. * @param parent the parent expression that's tested for being the current discard * @param expr the expression that's either normally loaded or discard-loaded * @param resultBounds result bounds for when loading the expression normally */ private void loadMaybeDiscard(final Expression parent, final Expression expr, final TypeBounds resultBounds) { loadMaybeDiscard(lc.popDiscardIfCurrent(parent), expr, resultBounds); } /** * Loads the expression with the specified type bounds, or loads and discards the expression, depending on the * value of the discard flag. Useful as a helper for expressions with control flow where you often can't combine * testing for being the current discard and loading the subexpressions. * @param discard if true, the expression is loaded and discarded * @param expr the expression that's either normally loaded or discard-loaded * @param resultBounds result bounds for when loading the expression normally */ private void loadMaybeDiscard(final boolean discard, final Expression expr, final TypeBounds resultBounds) { if (discard) { loadAndDiscard(expr); } else { loadExpression(expr, resultBounds); } } private void loadNEW(final UnaryNode unaryNode) { final CallNode callNode = (CallNode)unaryNode.getExpression(); final List<Expression> args = callNode.getArgs(); final Expression func = callNode.getFunction(); // Load function reference. loadExpressionAsObject(func); // must detect type error method.dynamicNew(1 + loadArgs(args), getCallSiteFlags(), func.toString(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); } } private void loadSUB(final UnaryNode unaryNode, final TypeBounds resultBounds) { final Type type = unaryNode.getType(); assert type.isNumeric(); final TypeBounds numericBounds = resultBounds.booleanToInt(); new OptimisticOperation(unaryNode, numericBounds) { @Override void loadStack() { final Expression expr = unaryNode.getExpression(); loadExpression(expr, numericBounds.notWiderThan(Type.NUMBER)); } @Override void consumeStack() { // Must do an explicit conversion to the operation's type when it's double so that we correctly handle // negation of an int 0 to a double -0. With this, we get the correct negation of a local variable after // it deoptimized, e.g. "iload_2; i2d; dneg". Without this, we get "iload_2; ineg; i2d". if(type.isNumber()) { method.convert(type); } method.neg(getProgramPoint()); } }.emit(); } public void loadVOID(final UnaryNode unaryNode, final TypeBounds resultBounds) { loadAndDiscard(unaryNode.getExpression()); if (!lc.popDiscardIfCurrent(unaryNode)) { method.loadUndefined(resultBounds.widest); } } public void loadADD(final BinaryNode binaryNode, final TypeBounds resultBounds) { new OptimisticOperation(binaryNode, resultBounds) { @Override void loadStack() { final TypeBounds operandBounds; final boolean isOptimistic = isValid(getProgramPoint()); boolean forceConversionSeparation = false; if(isOptimistic) { operandBounds = new TypeBounds(binaryNode.getType(), Type.OBJECT); } else { // Non-optimistic, non-FP +. Allow it to overflow. final Type widestOperationType = binaryNode.getWidestOperationType(); operandBounds = new TypeBounds(Type.narrowest(binaryNode.getWidestOperandType(), resultBounds.widest), widestOperationType); forceConversionSeparation = widestOperationType.narrowerThan(resultBounds.widest); } loadBinaryOperands(binaryNode.lhs(), binaryNode.rhs(), operandBounds, false, forceConversionSeparation); } @Override void consumeStack() { method.add(getProgramPoint()); } }.emit(); } 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 boolean isCurrentDiscard = lc.popDiscardIfCurrent(binaryNode); final Label skip = new Label("skip"); if(narrowestOperandType == Type.BOOLEAN) { // optimize all-boolean logical expressions final Label onTrue = new Label("andor_true"); emitBranch(binaryNode, onTrue, true); if (isCurrentDiscard) { method.label(onTrue); } else { 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); if (!isCurrentDiscard) { method.dup(); } method.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); } if (!isCurrentDiscard) { method.pop(); } final JoinPredecessorExpression rhs = (JoinPredecessorExpression)binaryNode.rhs(); loadMaybeDiscard(isCurrentDiscard, rhs, outBounds); method.beforeJoinPoint(rhs); method.label(skip); } private static boolean isLocalVariable(final Expression lhs) { return lhs instanceof IdentNode && isLocalVariable((IdentNode)lhs); } 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 rhsType = rhs.getType(); // Detect dead assignments if(lhs instanceof IdentNode) { final Symbol symbol = ((IdentNode)lhs).getSymbol(); if(!symbol.isScope() && !symbol.hasSlotFor(rhsType) && lc.popDiscardIfCurrent(binaryNode)) { loadAndDiscard(rhs); method.markDeadLocalVariable(symbol); return; } } new Store<BinaryNode>(binaryNode, lhs) { @Override protected void evaluate() { // 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(); } /** * Binary self-assignment that can be optimistic: +=, -=, *=, and /=. */ private abstract class BinaryOptimisticSelfAssignment extends SelfModifyingStore<BinaryNode> { /** * Constructor * * @param node the assign op node */ BinaryOptimisticSelfAssignment(final BinaryNode node) { super(node, node.lhs()); } protected abstract void op(OptimisticOperation oo); @Override protected void evaluate() { final Expression lhs = assignNode.lhs(); final Expression rhs = assignNode.rhs(); final Type widestOperationType = assignNode.getWidestOperationType(); final TypeBounds bounds = new TypeBounds(assignNode.getType(), widestOperationType); new OptimisticOperation(assignNode, bounds) { @Override void loadStack() { final boolean forceConversionSeparation; if (isValid(getProgramPoint()) || widestOperationType == Type.NUMBER) { forceConversionSeparation = false; } else { final Type operandType = Type.widest(booleanToInt(objectToNumber(lhs.getType())), booleanToInt(objectToNumber(rhs.getType()))); forceConversionSeparation = operandType.narrowerThan(widestOperationType); } loadBinaryOperands(lhs, rhs, bounds, true, forceConversionSeparation); } @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() { loadBinaryOperands(assignNode.lhs(), assignNode.rhs(), TypeBounds.UNBOUNDED.notWiderThan(assignNode.getWidestOperandType()), true, false); op(); } } private void loadASSIGN_ADD(final BinaryNode binaryNode) { new BinaryOptimisticSelfAssignment(binaryNode) { @Override protected void op(final OptimisticOperation oo) { assert !(binaryNode.getType().isObject() && oo.isOptimistic); method.add(oo.getProgramPoint()); } }.store(); } private void loadASSIGN_BIT_AND(final BinaryNode binaryNode) { new BinarySelfAssignment(binaryNode) { @Override protected void op() { method.and(); } }.store(); } private void loadASSIGN_BIT_OR(final BinaryNode binaryNode) { new BinarySelfAssignment(binaryNode) { @Override protected void op() { method.or(); } }.store(); } private void loadASSIGN_BIT_XOR(final BinaryNode binaryNode) { new BinarySelfAssignment(binaryNode) { @Override protected void op() { method.xor(); } }.store(); } private void loadASSIGN_DIV(final BinaryNode binaryNode) { new BinaryOptimisticSelfAssignment(binaryNode) { @Override protected void op(final OptimisticOperation oo) { method.div(oo.getProgramPoint()); } }.store(); } private void loadASSIGN_MOD(final BinaryNode binaryNode) { new BinaryOptimisticSelfAssignment(binaryNode) { @Override protected void op(final OptimisticOperation oo) { method.rem(oo.getProgramPoint()); } }.store(); } private void loadASSIGN_MUL(final BinaryNode binaryNode) { new BinaryOptimisticSelfAssignment(binaryNode) { @Override protected void op(final OptimisticOperation oo) { method.mul(oo.getProgramPoint()); } }.store(); } private void loadASSIGN_SAR(final BinaryNode binaryNode) { new BinarySelfAssignment(binaryNode) { @Override protected void op() { method.sar(); } }.store(); } private void loadASSIGN_SHL(final BinaryNode binaryNode) { new BinarySelfAssignment(binaryNode) { @Override protected void op() { method.shl(); } }.store(); } private void loadASSIGN_SHR(final BinaryNode binaryNode) { new SelfModifyingStore<BinaryNode>(binaryNode, binaryNode.lhs()) { @Override protected void evaluate() { new OptimisticOperation(assignNode, new TypeBounds(Type.INT, Type.NUMBER)) { @Override void loadStack() { assert assignNode.getWidestOperandType() == Type.INT; if (isRhsZero(binaryNode)) { loadExpression(binaryNode.lhs(), TypeBounds.INT, true); } else { loadBinaryOperands(binaryNode.lhs(), binaryNode.rhs(), TypeBounds.INT, true, false); method.shr(); } } @Override void consumeStack() { if (isOptimistic(binaryNode)) { toUint32Optimistic(binaryNode.getProgramPoint()); } else { toUint32Double(); } } }.emit(getOptimisticIgnoreCountForSelfModifyingExpression(binaryNode.lhs())); method.convert(assignNode.getType()); } }.store(); } private void doSHR(final BinaryNode binaryNode) { new OptimisticOperation(binaryNode, new TypeBounds(Type.INT, Type.NUMBER)) { @Override void loadStack() { if (isRhsZero(binaryNode)) { loadExpressionAsType(binaryNode.lhs(), Type.INT); } else { loadBinaryOperands(binaryNode); method.shr(); } } @Override void consumeStack() { if (isOptimistic(binaryNode)) { toUint32Optimistic(binaryNode.getProgramPoint()); } else { toUint32Double(); } } }.emit(); } private void toUint32Optimistic(final int programPoint) { method.load(programPoint); JSType.TO_UINT32_OPTIMISTIC.invoke(method); } private void toUint32Double() { JSType.TO_UINT32_DOUBLE.invoke(method); } private void loadASSIGN_SUB(final BinaryNode binaryNode) { new BinaryOptimisticSelfAssignment(binaryNode) { @Override protected void op(final OptimisticOperation oo) { method.sub(oo.getProgramPoint()); } }.store(); } /** * Helper class for binary arithmetic ops */ private abstract class BinaryArith { 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() { final TypeBounds operandBounds; boolean forceConversionSeparation = false; 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 || node.isTokenType(TokenType.DIV) || node.isTokenType(TokenType.MOD)) { operandBounds = new TypeBounds(node.getType(), Type.NUMBER); } else { // Non-optimistic, non-FP subtraction or multiplication. Allow them to overflow. operandBounds = new TypeBounds(Type.narrowest(node.getWidestOperandType(), numericBounds.widest), Type.NUMBER); forceConversionSeparation = true; } } loadBinaryOperands(node.lhs(), node.rhs(), operandBounds, false, forceConversionSeparation); } @Override void consumeStack() { op(getProgramPoint()); } }.emit(); } } private void loadBIT_AND(final BinaryNode binaryNode) { loadBinaryOperands(binaryNode); method.and(); } private void loadBIT_OR(final BinaryNode binaryNode) { // Optimize x|0 to (int)x if (isRhsZero(binaryNode)) { loadExpressionAsType(binaryNode.lhs(), Type.INT); } else { loadBinaryOperands(binaryNode); method.or(); } } private static boolean isRhsZero(final BinaryNode binaryNode) { final Expression rhs = binaryNode.rhs(); return rhs instanceof LiteralNode && INT_ZERO.equals(((LiteralNode<?>)rhs).getValue()); } private void loadBIT_XOR(final BinaryNode binaryNode) { loadBinaryOperands(binaryNode); method.xor(); } private void loadCOMMARIGHT(final BinaryNode binaryNode, final TypeBounds resultBounds) { loadAndDiscard(binaryNode.lhs()); loadMaybeDiscard(binaryNode, binaryNode.rhs(), resultBounds); } private void loadCOMMALEFT(final BinaryNode binaryNode, final TypeBounds resultBounds) { loadMaybeDiscard(binaryNode, binaryNode.lhs(), resultBounds); loadAndDiscard(binaryNode.rhs()); } private void loadDIV(final BinaryNode binaryNode, final TypeBounds resultBounds) { new BinaryArith() { @Override protected void op(final int programPoint) { method.div(programPoint); } }.evaluate(binaryNode, resultBounds); } private void loadCmp(final BinaryNode binaryNode, final Condition cond) { loadComparisonOperands(binaryNode); final Label trueLabel = new Label("trueLabel"); final Label afterLabel = new Label("skip"); method.conditionalJump(cond, trueLabel); method.load(Boolean.FALSE); method._goto(afterLabel); method.label(trueLabel); method.load(Boolean.TRUE); method.label(afterLabel); } private void loadMOD(final BinaryNode binaryNode, final TypeBounds resultBounds) { new BinaryArith() { @Override protected void op(final int programPoint) { method.rem(programPoint); } }.evaluate(binaryNode, resultBounds); } private void loadMUL(final BinaryNode binaryNode, final TypeBounds resultBounds) { new BinaryArith() { @Override protected void op(final int programPoint) { method.mul(programPoint); } }.evaluate(binaryNode, resultBounds); } private void loadSAR(final BinaryNode binaryNode) { loadBinaryOperands(binaryNode); method.sar(); } private void loadSHL(final BinaryNode binaryNode) { loadBinaryOperands(binaryNode); method.shl(); } private void loadSHR(final BinaryNode binaryNode) { doSHR(binaryNode); } private void loadSUB(final BinaryNode binaryNode, final TypeBounds resultBounds) { new BinaryArith() { @Override protected void op(final int programPoint) { method.sub(programPoint); } }.evaluate(binaryNode, resultBounds); } @Override public boolean enterLabelNode(final LabelNode labelNode) { labeledBlockBreakLiveLocals.push(lc.getUsedSlotCount()); return true; } @Override protected boolean enterDefault(final Node node) { throw new AssertionError("Code generator entered node of type " + node.getClass().getName()); } 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"); final Type outNarrowest = Type.narrowest(resultBounds.widest, Type.generic(Type.widestReturnType(trueExpr.getType(), falseExpr.getType()))); final TypeBounds outBounds = resultBounds.notNarrowerThan(outNarrowest); emitBranch(test, falseLabel, false); final boolean isCurrentDiscard = lc.popDiscardIfCurrent(ternaryNode); loadMaybeDiscard(isCurrentDiscard, trueExpr.getExpression(), outBounds); assert isCurrentDiscard || Type.generic(method.peekType()) == outBounds.narrowest; method.beforeJoinPoint(trueExpr); method._goto(exitLabel); method.label(falseLabel); loadMaybeDiscard(isCurrentDiscard, falseExpr.getExpression(), outBounds); assert isCurrentDiscard || Type.generic(method.peekType()) == outBounds.narrowest; method.beforeJoinPoint(falseExpr); method.label(exitLabel); } /** * Generate all shared scope calls generated during codegen. */ void generateScopeCalls() { for (final SharedScopeCall scopeAccess : lc.getScopeCalls()) { scopeAccess.generateScopeCall(); } } /** * Debug code used to print symbols * * @param block the block we are in * @param function the function we are in * @param ident identifier for block or function where applicable */ private void printSymbols(final Block block, final FunctionNode function, final String ident) { if (compiler.getScriptEnvironment()._print_symbols || function.getDebugFlag(FunctionNode.DEBUG_PRINT_SYMBOLS)) { final PrintWriter out = compiler.getScriptEnvironment().getErr(); out.println("[BLOCK in '" + ident + "']"); if (!block.printSymbols(out)) { out.println("<no symbols>"); } out.println(); } } /** * The difference between a store and a self modifying store is that * the latter may load part of the target on the stack, e.g. the base * of an AccessNode or the base and index of an IndexNode. These are used * both as target and as an extra source. Previously it was problematic * for self modifying stores if the target/lhs didn't belong to one * of three trivial categories: IdentNode, AcessNodes, IndexNodes. In that * case it was evaluated and tagged as "resolved", which meant at the second * time the lhs of this store was read (e.g. in a = a (second) + b for a += b, * it would be evaluated to a nop in the scope and cause stack underflow * * see NASHORN-703 * * @param <T> */ private abstract class SelfModifyingStore<T extends Expression> extends Store<T> { protected SelfModifyingStore(final T assignNode, final Expression target) { super(assignNode, target); } @Override protected boolean isSelfModifying() { return true; } } /** * Helper class to generate stores */ private abstract class Store<T extends Expression> { /** An assignment node, e.g. x += y */ protected final T assignNode; /** The target node to store to, e.g. x */ private final Expression target; /** How deep on the stack do the arguments go if this generates an indy call */ private int depth; /** If we have too many arguments, we need temporary storage, this is stored in 'quick' */ private IdentNode quick; /** * Constructor * * @param assignNode the node representing the whole assignment * @param target the target node of the assignment (destination) */ protected Store(final T assignNode, final Expression target) { this.assignNode = assignNode; this.target = target; } /** * Constructor * * @param assignNode the node representing the whole assignment */ protected Store(final T assignNode) { this(assignNode, assignNode); } /** * Is this a self modifying store operation, e.g. *= or ++ * @return true if self modifying store */ protected boolean isSelfModifying() { return false; } private void prologue() { /* * 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 */ target.accept(new SimpleNodeVisitor() { @Override public boolean enterIdentNode(final IdentNode node) { if (node.getSymbol().isScope()) { method.loadCompilerConstant(SCOPE); depth += Type.SCOPE.getSlots(); assert depth == 1; } return false; } private void enterBaseNode() { assert target instanceof BaseNode : "error - base node " + target + " must be instanceof BaseNode"; final BaseNode baseNode = (BaseNode)target; final Expression base = baseNode.getBase(); loadExpressionAsObject(base); depth += Type.OBJECT.getSlots(); assert depth == 1; if (isSelfModifying()) { method.dup(); } } @Override public boolean enterAccessNode(final AccessNode node) { enterBaseNode(); return false; } @Override public boolean enterIndexNode(final IndexNode node) { enterBaseNode(); final Expression index = node.getIndex(); if (!index.getType().isNumeric()) { // could be boolean here as well loadExpressionAsObject(index); } else { loadExpressionUnbounded(index); } depth += index.getType().getSlots(); if (isSelfModifying()) { //convert "base base index" to "base index base index" method.dup(1); } return false; } }); } /** * 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 variable * * @return the quick variable */ 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. protected void storeNonDiscard() { if (lc.popDiscardIfCurrent(assignNode)) { assert assignNode.isAssignment(); return; } if (method.dup(depth) == null) { method.dup(); final Type quickType = method.peekType(); this.quick = quickLocalVariable(quickType); final Symbol quickSymbol = quick.getSymbol(); method.storeTemp(quickType, quickSymbol.getFirstSlot()); } } private void epilogue() { /** * Take the original target args from the stack and use them * together with the value to be stored to emit the store code * * The case that targetSymbol is in scope (!hasSlot) and we actually * need to do a conversion on non-equivalent types exists, but is * very rare. See for example test/script/basic/access-specializer.js */ target.accept(new SimpleNodeVisitor() { @Override protected boolean enterDefault(final Node node) { throw new AssertionError("Unexpected node " + node + " in store epilogue"); } @Override public boolean enterIdentNode(final IdentNode node) { final Symbol symbol = node.getSymbol(); assert symbol != null; if (symbol.isScope()) { final int flags = getScopeCallSiteFlags(symbol) | (node.isDeclaredHere() ? CALLSITE_DECLARE : 0); if (isFastScope(symbol)) { storeFastScopeVar(symbol, flags); } else { method.dynamicSet(node.getName(), flags, false); } } else { final Type storeType = assignNode.getType(); assert storeType != Type.LONG; if (symbol.hasSlotFor(storeType)) { // Only emit a convert for a store known to be live; converts for dead stores can // give us an unnecessary ClassCastException. method.convert(storeType); } storeIdentWithCatchConversion(node, storeType); } return false; } @Override public boolean enterAccessNode(final AccessNode node) { method.dynamicSet(node.getProperty(), getCallSiteFlags(), node.isIndex()); return false; } @Override public boolean enterIndexNode(final IndexNode node) { method.dynamicSetIndex(getCallSiteFlags()); return false; } }); // whatever is on the stack now is the final answer } protected abstract void evaluate(); void store() { if (target instanceof IdentNode) { checkTemporalDeadZone((IdentNode)target); } prologue(); evaluate(); // leaves an operation of whatever the operationType was on the stack storeNonDiscard(); epilogue(); if (quick != null) { method.load(quick); } } } private void newFunctionObject(final FunctionNode functionNode, final boolean addInitializer) { assert lc.peek() == functionNode; final RecompilableScriptFunctionData data = compiler.getScriptFunctionData(functionNode.getId()); if (functionNode.isProgram() && !compiler.isOnDemandCompilation()) { final MethodEmitter createFunction = functionNode.getCompileUnit().getClassEmitter().method( EnumSet.of(Flag.PUBLIC, Flag.STATIC), CREATE_PROGRAM_FUNCTION.symbolName(), ScriptFunction.class, ScriptObject.class); createFunction.begin(); loadConstantsAndIndex(data, createFunction); createFunction.load(SCOPE_TYPE, 0); createFunction.invoke(CREATE_FUNCTION_OBJECT); createFunction._return(); createFunction.end(); } if (addInitializer && !compiler.isOnDemandCompilation()) { functionNode.getCompileUnit().addFunctionInitializer(data, functionNode); } // We don't emit a ScriptFunction on stack for the outermost compiled function (as there's no code being // generated in its outer context that'd need it as a callee). if (lc.getOutermostFunction() == functionNode) { return; } loadConstantsAndIndex(data, method); if (functionNode.needsParentScope()) { method.loadCompilerConstant(SCOPE); method.invoke(CREATE_FUNCTION_OBJECT); } else { method.invoke(CREATE_FUNCTION_OBJECT_NO_SCOPE); } } // calls on Global class. private MethodEmitter globalInstance() { return method.invokestatic(GLOBAL_OBJECT, "instance", "()L" + GLOBAL_OBJECT + ';'); } private MethodEmitter globalAllocateArguments() { return method.invokestatic(GLOBAL_OBJECT, "allocateArguments", methodDescriptor(ScriptObject.class, Object[].class, Object.class, int.class)); } private MethodEmitter globalNewRegExp() { return method.invokestatic(GLOBAL_OBJECT, "newRegExp", methodDescriptor(Object.class, String.class, String.class)); } private MethodEmitter globalRegExpCopy() { return method.invokestatic(GLOBAL_OBJECT, "regExpCopy", methodDescriptor(Object.class, Object.class)); } private MethodEmitter globalAllocateArray(final ArrayType type) { //make sure the native array is treated as an array type return method.invokestatic(GLOBAL_OBJECT, "allocate", "(" + type.getDescriptor() + ")Ljdk/nashorn/internal/objects/NativeArray;"); } private MethodEmitter globalIsEval() { return method.invokestatic(GLOBAL_OBJECT, "isEval", methodDescriptor(boolean.class, Object.class)); } private MethodEmitter globalReplaceLocationPropertyPlaceholder() { return method.invokestatic(GLOBAL_OBJECT, "replaceLocationPropertyPlaceholder", methodDescriptor(Object.class, Object.class, Object.class)); } private MethodEmitter globalCheckObjectCoercible() { return method.invokestatic(GLOBAL_OBJECT, "checkObjectCoercible", methodDescriptor(void.class, Object.class)); } private MethodEmitter globalDirectEval() { return method.invokestatic(GLOBAL_OBJECT, "directEval", methodDescriptor(Object.class, Object.class, Object.class, Object.class, Object.class, boolean.class)); } private abstract class OptimisticOperation { 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 int programPoint = optimistic.getProgramPoint(); final boolean optimisticOrContinuation = isOptimistic || isContinuationEntryPoint(programPoint); final boolean currentContinuationEntryPoint = isCurrentContinuationEntryPoint(programPoint); final int stackSizeOnEntry = method.getStackSize() - ignoredArgCount; // First store the values on the stack opportunistically into local variables. Doing it before loadStack() // allows us to not have to pop/load any arguments that are pushed onto it by loadStack() in the second // storeStack(). storeStack(ignoredArgCount, optimisticOrContinuation); // Now, load the stack loadStack(); // 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 // unfortunate cases this second storeStack() will restore the invariant that everything on the stack is // 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); final Label beginTry; final Label catchLabel; final Label afterConsumeStack = isOptimistic || currentContinuationEntryPoint ? new Label("after_consume_stack") : null; if(isOptimistic) { beginTry = new Label("try_optimistic"); final String catchLabelName = (afterConsumeStack == null ? "" : afterConsumeStack.toString()) + "_handler"; catchLabel = new Label(catchLabelName); method.label(beginTry); } else { beginTry = catchLabel = null; } consumeStack(); if(isOptimistic) { method._try(beginTry, afterConsumeStack, catchLabel, UnwarrantedOptimismException.class); } 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 = 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 != null : "no continuation info found for " + lc.getCurrentFunction(); assert !ci.hasTargetLabel(); // No duplicate program points ci.setTargetLabel(afterConsumeStack); 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; } /** * Stores the current contents of the stack into local variables so they are not lost before invoking something that * can result in an {@code UnwarantedOptimizationException}. * @param ignoreArgCount the number of topmost arguments on stack to ignore when deciding on the shape of the catch * block. Those are used in the situations when we could not place the call to {@code storeStack} early enough * (before emitting code for pushing the arguments that the optimistic call will pop). This is admittedly a * deficiency in the design of the code generator when it deals with self-assignments and we should probably look * into fixing it. * @return types of the significant local variables after the stack was stored (types for local variables used * for temporary storage of ignored arguments are not returned). * @param optimisticOrContinuation if false, this method should not execute * a label for a catch block for the {@code UnwarantedOptimizationException}, suitable for capturing the * currently live local variables, tailored to their types. */ 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 } final int stackSize = method.getStackSize(); final Type[] stackTypes = method.getTypesFromStack(stackSize); final int[] localLoadsOnStack = method.getLocalLoadsOnStack(0, stackSize); 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. int firstNonLoad = 0; while(firstNonLoad < firstIgnored && localLoadsOnStack[firstNonLoad] != Label.Stack.NON_LOAD) { firstNonLoad++; } // Only do the store/load if first non-load is not an ignored argument. Otherwise, do nothing and return // the number of used slots as the number of live local variables. if(firstNonLoad >= firstIgnored) { return usedSlots; } // Find the number of new temporary local variables that we need; it's the number of values on the stack that // are not direct loads of existing local variables. int tempSlotsNeeded = 0; for(int i = firstNonLoad; i < stackSize; ++i) { if(localLoadsOnStack[i] == Label.Stack.NON_LOAD) { tempSlotsNeeded += stackTypes[i].getSlots(); } } // Ensure all values on the stack that weren't directly loaded from a local variable are stored in a local // variable. We're starting from highest local variable index, so that in case ignoreArgCount > 0 the ignored // ones end up at the end of the local variable table. int lastTempSlot = usedSlots + tempSlotsNeeded; int ignoreSlotCount = 0; for(int i = stackSize; i -- > firstNonLoad;) { final int loadSlot = localLoadsOnStack[i]; if(loadSlot == Label.Stack.NON_LOAD) { final Type type = stackTypes[i]; final int slots = type.getSlots(); lastTempSlot -= slots; if(i >= firstIgnored) { ignoreSlotCount += slots; } method.storeTemp(type, lastTempSlot); } else { method.pop(); } } assert lastTempSlot == usedSlots; // used all temporary locals final List<Type> localTypesList = method.getLocalVariableTypes(); // Load values back on stack. for(int i = firstNonLoad; i < stackSize; ++i) { final int loadSlot = localLoadsOnStack[i]; final Type stackType = stackTypes[i]; final boolean isLoad = loadSlot != Label.Stack.NON_LOAD; final int lvarSlot = isLoad ? loadSlot : lastTempSlot; final Type lvarType = localTypesList.get(lvarSlot); method.load(lvarType, lvarSlot); if(isLoad) { // Conversion operators (I2L etc.) preserve "load"-ness of the value despite the fact that, in the // strict sense they are creating a derived value from the loaded value. This special behavior of // on-stack conversion operators is necessary to accommodate for differences in local variable types // after deoptimization; having a conversion operator throw away "load"-ness would create different // local variable table shapes between optimism-failed code and its deoptimized rest-of method). // After we load the value back, we need to redo the conversion to the stack type if stack type is // different. // NOTE: this would only strictly be necessary for widening conversions (I2L, L2D, I2D), and not for // narrowing ones (L2I, D2L, D2I) as only widening conversions are the ones that can get eliminated // in a deoptimized method, as their original input argument got widened. Maybe experiment with // throwing away "load"-ness for narrowing conversions in MethodEmitter.convert()? method.convert(stackType); } else { // temporary stores never needs a convert, as their type is always the same as the stack type. assert lvarType == stackType; lastTempSlot += lvarType.getSlots(); } } // used all temporaries assert lastTempSlot == usedSlots + tempSlotsNeeded; return lastTempSlot - ignoreSlotCount; } 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); if(labels == null) { labels = new LinkedList<>(); unwarrantedOptimismHandlers.put(lvarTypesDescriptor, labels); } method.markLabelAsOptimisticCatchHandler(label, localTypes.size()); labels.add(label); } 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 preferably retrieving a function * @return the current method emitter */ MethodEmitter dynamicGet(final String name, final int flags, final boolean isMethod, final boolean isIndex) { if(isOptimistic) { return method.dynamicGet(getOptimisticCoercedType(), name, getOptimisticFlags(flags), isMethod, isIndex); } return method.dynamicGet(resultBounds.within(expression.getType()), name, nonOptimisticFlags(flags), isMethod, isIndex); } 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, final String msg) { if (isOptimistic) { return method.dynamicCall(getOptimisticCoercedType(), argCount, getOptimisticFlags(flags), msg); } return method.dynamicCall(resultBounds.within(expression.getType()), argCount, nonOptimisticFlags(flags), msg); } 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.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 || propertyValue == null) { method.load((String)propertyValue); } else if(propertyValue instanceof Integer) { method.load(((Integer)propertyValue)); 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) { for (final int load : loads) { if(load < 0 || load >= localCount) { return false; } } return true; } private static boolean everyStackValueIsLocalLoad(final int[] loads) { for (final int load : loads) { if(load == Label.Stack.NON_LOAD) { return false; } } return true; } 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) { b.append(t.getBytecodeStackType()); 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 * fills in some trailing part of the local variables (those for which it has a unique suffix in the descriptor), * then jumps to a handler for a prefix that's shared with other handlers. A handler that fills up locals up to * position 0 will not jump to a prefix handler (as it has no prefix), but instead end with constructing and * throwing a {@code RewriteException}. Since we lexicographically sort the entries, we only need to check every * entry to its immediately preceding one for longest matching prefix. * @return true if there is at least one exception handler */ private boolean generateUnwarrantedOptimismExceptionHandlers(final FunctionNode fn) { if(!useOptimisticTypes()) { return false; } // Take the mapping of lvarSpecs -> labels, and turn them into a descending lexicographically sorted list of // handler specifications. final Map<String, Collection<Label>> unwarrantedOptimismHandlers = lc.popUnwarrantedOptimismHandlers(); 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)); } Collections.sort(handlerSpecs, Collections.reverseOrder()); // Map of local variable specifications to labels for populating the array for that local variable spec. final Map<String, Label> delegationLabels = new HashMap<>(); // Do everything in a single pass over the handlerSpecs list. Note that the list can actually grow as we're // passing through it as we might add new prefix handlers into it, so can't hoist size() outside of the loop. for(int handlerIndex = 0; handlerIndex < handlerSpecs.size(); ++handlerIndex) { 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. 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) { // If another handler can delegate to this handler as its prefix, then put a jump target here for the // shared code (after the array creation code, which is never shared). method.label(delegationLabels.get(lvarSpec)); // label must exist } final boolean lastHandler = handlerIndex == handlerSpecs.size() - 1; 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; } else { // Not yet the last handler block, will definitely delegate to another handler; let's figure out which // one. It can be an already declared handler further down the list, or it might need to declare a new // prefix handler. // Since we're lexicographically ordered, the common prefix handler is defined by the common prefix of // this handler and the next handler on the list. 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. { boolean addNewHandler = true; int commonHandlerIndex = nextHandlerIndex; for(; commonHandlerIndex < handlerSpecs.size(); ++commonHandlerIndex) { final OptimismExceptionHandlerSpec forwardHandlerSpec = handlerSpecs.get(commonHandlerIndex); final String forwardLvarSpec = forwardHandlerSpec.lvarSpec; if(forwardLvarSpec.equals(commonLvarSpec)) { // We already have a handler for the common prefix. addNewHandler = false; // Make sure we mark it as a delegation target. forwardHandlerSpec.delegationTarget = true; break; } else if(!forwardLvarSpec.startsWith(commonLvarSpec)) { break; } } if(addNewHandler) { // We need to insert a common prefix handler. Note handlers created with catchTarget == false // will automatically have delegationTarget == true (because that's the only reason for their // existence). handlerSpecs.add(commonHandlerIndex, new OptimismExceptionHandlerSpec(commonLvarSpec, false)); } } firstArrayIndex = countSymbolsInLvarTypeDescriptor(commonLvarSpec); lvarIndex = 0; 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); if(delegationLabel == null) { // uo_pa == "unwarranted optimism, populate array" delegationLabel = new Label("uo_pa_" + commonLvarSpec); delegationLabels.put(commonLvarSpec, delegationLabel); } } // Load local variables handled by this handler on stack int args = 0; 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(); } 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. if(!nextSpec.lvarSpec.equals(commonLvarSpec) || nextSpec.catchTarget) { method._goto(delegationLabel); } } else { assert lastHandler; // Nothing to delegate to, so this handler must create and throw the RewriteException. // At this point we have the UnwarrantedOptimismException and the Object[] with local variables on // stack. We need to create a RewriteException, push two references to it below the constructor // arguments, invoke the constructor, and throw the exception. loadConstant(getByteCodeSymbolNames(fn)); if (isRestOf()) { loadConstant(getContinuationEntryPoints()); method.invoke(CREATE_REWRITE_EXCEPTION_REST_OF); } else { method.invoke(CREATE_REWRITE_EXCEPTION); } method.athrow(); } } return true; } private static String[] getByteCodeSymbolNames(final FunctionNode fn) { // Only names of local variables on the function level are captured. This information is used to reduce // deoptimizations, so as much as we can capture will help. We rely on the fact that function wide variables are // all live all the time, so the array passed to rewrite exception contains one element for every slotted symbol // here. final List<String> names = new ArrayList<>(); for (final Symbol symbol: fn.getBody().getSymbols()) { if (symbol.hasSlot()) { if (symbol.isScope()) { // slot + scope can only be true for parameters assert symbol.isParam(); names.add(null); } else { names.add(symbol.getName()); } } } return names.toArray(new String[0]); } 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) { 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; } private static class OptimismExceptionHandlerSpec implements Comparable<OptimismExceptionHandlerSpec> { private final String lvarSpec; private final boolean catchTarget; private boolean delegationTarget; OptimismExceptionHandlerSpec(final String lvarSpec, final boolean catchTarget) { this.lvarSpec = lvarSpec; this.catchTarget = catchTarget; if(!catchTarget) { delegationTarget = true; } } @Override public int compareTo(final OptimismExceptionHandlerSpec o) { return lvarSpec.compareTo(o.lvarSpec); } @Override public String toString() { final StringBuilder b = new StringBuilder(64).append("[HandlerSpec ").append(lvarSpec); if(catchTarget) { b.append(", catchTarget"); } if(delegationTarget) { b.append(", delegationTarget"); } return b.append("]").toString(); } } private static class ContinuationInfo { private final Label handlerLabel; private Label targetLabel; // Label for the target instruction. 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 private Type[] stackTypes; // If non-null, this node should perform the requisite type conversion private Type returnValueType; // If we are in the middle of an object literal initialization, we need to update the property maps private Map<Integer, PropertyMap> objectLiteralMaps; // 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"); } Label getHandlerLabel() { return handlerLabel; } boolean hasTargetLabel() { return targetLabel != null; } Label getTargetLabel() { return targetLabel; } void setTargetLabel(final Label targetLabel) { this.targetLabel = targetLabel; } int[] getStackStoreSpec() { return stackStoreSpec.clone(); } void setStackStoreSpec(final int[] stackStoreSpec) { this.stackStoreSpec = stackStoreSpec; } Type[] getStackTypes() { return stackTypes.clone(); } void setStackTypes(final Type[] stackTypes) { this.stackTypes = stackTypes; } Type getReturnValueType() { return returnValueType; } void setReturnValueType(final Type returnValueType) { this.returnValueType = returnValueType; } void setObjectLiteralMap(final int objectLiteralStackDepth, final PropertyMap objectLiteralMap) { if (objectLiteralMaps == null) { objectLiteralMaps = new HashMap<>(); } objectLiteralMaps.put(objectLiteralStackDepth, objectLiteralMap); } PropertyMap getObjectLiteralMap(final int stackDepth) { return objectLiteralMaps == null ? null : objectLiteralMaps.get(stackDepth); } @Override public String toString() { return "[localVariableTypes=" + targetLabel.getStack().getLocalVariableTypesCopy() + ", stackStoreSpec=" + Arrays.toString(stackStoreSpec) + ", returnValueType=" + returnValueType + "]"; } } private ContinuationInfo getContinuationInfo() { return continuationInfo; } private void generateContinuationHandler() { if (!isRestOf()) { return; } final ContinuationInfo ci = getContinuationInfo(); method.label(ci.getHandlerLabel()); // There should never be an exception thrown from the continuation handler, but in case there is (meaning, // 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 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.storeTemp(rewriteExceptionType, lvarCount); // Get local variable array method.load(rewriteExceptionType, 0); method.invoke(RewriteException.GET_BYTECODE_SLOTS); // 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(symbolBoundary.get(nextLvarIndex - 1)) { ++arrayIndex; } lvarIndex = nextLvarIndex; } if (AssertsEnabled.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; int replacedObjectLiteralMaps = 0; if(!isStackEmpty) { // Load arguments on the stack for(int i = 0; i < stackStoreSpec.length; ++i) { final int slot = stackStoreSpec[i]; 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 initializing when we failed // is now ci.returnValueType final PropertyMap map = ci.getObjectLiteralMap(i); if (map != null) { method.dup(); assert ScriptObject.class.isAssignableFrom(method.peekType().getTypeClass()) : method.peekType().getTypeClass() + " is not a script object"; loadConstant(map); method.invoke(ScriptObject.SET_MAP); replacedObjectLiteralMaps++; } } } // Must have emitted the code for replacing all object literal maps assert ci.objectLiteralMaps == null || ci.objectLiteralMaps.size() == replacedObjectLiteralMaps; // 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); 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); } } /** * Interface implemented by object creators that support splitting over multiple methods. */ interface SplitLiteralCreator { /** * Generate code to populate a range of the literal object. A reference to the object * should be left on the stack when the method terminates. * * @param method the method emitter * @param type the type of the literal object * @param slot the local slot containing the literal object * @param start the start index (inclusive) * @param end the end index (exclusive) */ void populateRange(MethodEmitter method, Type type, int slot, int start, int end); } }