# HG changeset patch # User attila # Date 1425566623 -3600 # Node ID fb47e4d25a9fd73dbac9d286bdb5e55b2a153aa5 # Parent a8523237b66cdbcfb1cebc28d27157b0a83530cf 8035712: Restore some of the RuntimeCallSite specializations Reviewed-by: hannesw, lagergren diff -r a8523237b66c -r fb47e4d25a9f nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/BranchOptimizer.java --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/BranchOptimizer.java Mon Mar 02 14:33:55 2015 +0100 +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/BranchOptimizer.java Thu Mar 05 15:43:43 2015 +0100 @@ -105,33 +105,33 @@ case EQ: case EQ_STRICT: - codegen.loadBinaryOperands(binaryNode); + codegen.loadComparisonOperands(binaryNode); method.conditionalJump(state ? EQ : NE, true, label); return; case NE: case NE_STRICT: - codegen.loadBinaryOperands(binaryNode); + codegen.loadComparisonOperands(binaryNode); method.conditionalJump(state ? NE : EQ, true, label); return; case GE: - codegen.loadBinaryOperands(binaryNode); + codegen.loadComparisonOperands(binaryNode); method.conditionalJump(state ? GE : LT, false, label); return; case GT: - codegen.loadBinaryOperands(binaryNode); + codegen.loadComparisonOperands(binaryNode); method.conditionalJump(state ? GT : LE, false, label); return; case LE: - codegen.loadBinaryOperands(binaryNode); + codegen.loadComparisonOperands(binaryNode); method.conditionalJump(state ? LE : GT, true, label); return; case LT: - codegen.loadBinaryOperands(binaryNode); + codegen.loadComparisonOperands(binaryNode); method.conditionalJump(state ? LT : GE, true, label); return; diff -r a8523237b66c -r fb47e4d25a9f nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java Mon Mar 02 14:33:55 2015 +0100 +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java Thu Mar 05 15:43:43 2015 +0100 @@ -202,6 +202,12 @@ private static final Call CREATE_FUNCTION_OBJECT_NO_SCOPE = CompilerConstants.staticCallNoLookup(ScriptFunctionImpl.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(); @@ -618,6 +624,104 @@ 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 dyn:getProp|getElem|getMethod:size(Object;)D + // instead of the more costly: + // invokedynamic dyn:getProp|getElem|getMethod: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 final Type undefinedToNumber(final Type type) { return type == Type.UNDEFINED ? Type.NUMBER : type; } @@ -628,6 +732,7 @@ 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); @@ -2756,25 +2861,18 @@ newRuntimeNode = runtimeNode; } - new OptimisticOperation(newRuntimeNode, TypeBounds.UNBOUNDED) { - @Override - void loadStack() { - for (final Expression arg : args) { - loadExpression(arg, TypeBounds.OBJECT); - } - } - @Override - void consumeStack() { - method.invokestatic( - CompilerConstants.className(ScriptRuntime.class), - newRuntimeNode.getRequest().toString(), - new FunctionSignature( - false, - false, - newRuntimeNode.getType(), - args.size()).toString()); - } - }.emit(); + 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()); } @@ -3989,8 +4087,7 @@ } private void loadCmp(final BinaryNode binaryNode, final Condition cond) { - assert comparisonOperandsArePrimitive(binaryNode) : binaryNode; - loadBinaryOperands(binaryNode); + loadComparisonOperands(binaryNode); final Label trueLabel = new Label("trueLabel"); final Label afterLabel = new Label("skip"); @@ -4004,11 +4101,6 @@ method.label(afterLabel); } - private static boolean comparisonOperandsArePrimitive(final BinaryNode binaryNode) { - final Type widest = Type.widest(binaryNode.lhs().getType(), binaryNode.rhs().getType()); - return widest.isNumeric() || widest.isBoolean(); - } - private void loadMOD(final BinaryNode binaryNode, final TypeBounds resultBounds) { new BinaryArith() { @Override diff -r a8523237b66c -r fb47e4d25a9f nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java Mon Mar 02 14:33:55 2015 +0100 +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java Thu Mar 05 15:43:43 2015 +0100 @@ -1359,8 +1359,6 @@ final Expression lhs = binaryNode.lhs(); final Expression rhs = binaryNode.rhs(); - Type cmpWidest = Type.widest(lhs.getType(), rhs.getType()); - boolean newRuntimeNode = false, finalized = false; final TokenType tt = binaryNode.tokenType(); switch (tt) { case EQ_STRICT: @@ -1373,14 +1371,12 @@ } // Specialize comparison of boolean with non-boolean if (lhs.getType().isBoolean() != rhs.getType().isBoolean()) { - newRuntimeNode = true; - cmpWidest = Type.OBJECT; - finalized = true; + return new RuntimeNode(binaryNode); } // fallthrough default: - if (newRuntimeNode || cmpWidest.isObject()) { - return new RuntimeNode(binaryNode).setIsFinal(finalized); + if (lhs.getType().isObject() && rhs.getType().isObject()) { + return new RuntimeNode(binaryNode); } } } else if(binaryNode.isOptimisticUndecidedType()) { diff -r a8523237b66c -r fb47e4d25a9f nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/MethodEmitter.java --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/MethodEmitter.java Mon Mar 02 14:33:55 2015 +0100 +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/MethodEmitter.java Thu Mar 05 15:43:43 2015 +0100 @@ -94,7 +94,6 @@ import jdk.nashorn.internal.ir.JoinPredecessor; import jdk.nashorn.internal.ir.LiteralNode; import jdk.nashorn.internal.ir.LocalVariableConversion; -import jdk.nashorn.internal.ir.RuntimeNode; import jdk.nashorn.internal.ir.Symbol; import jdk.nashorn.internal.ir.TryNode; import jdk.nashorn.internal.objects.NativeArray; @@ -174,9 +173,6 @@ /** Bootstrap for normal indy:s */ private static final Handle LINKERBOOTSTRAP = new Handle(H_INVOKESTATIC, Bootstrap.BOOTSTRAP.className(), Bootstrap.BOOTSTRAP.name(), Bootstrap.BOOTSTRAP.descriptor()); - /** Bootstrap for runtime node indy:s */ - private static final Handle RUNTIMEBOOTSTRAP = new Handle(H_INVOKESTATIC, RuntimeCallSite.BOOTSTRAP.className(), RuntimeCallSite.BOOTSTRAP.name(), RuntimeCallSite.BOOTSTRAP.descriptor()); - /** Bootstrap for array populators */ private static final Handle POPULATE_ARRAY_BOOTSTRAP = new Handle(H_INVOKESTATIC, RewriteException.BOOTSTRAP.className(), RewriteException.BOOTSTRAP.name(), RewriteException.BOOTSTRAP.descriptor()); @@ -2188,25 +2184,6 @@ } /** - * Generate a dynamic call for a runtime node - * - * @param name tag for the invoke dynamic for this runtime node - * @param returnType return type - * @param request RuntimeNode request - * - * @return the method emitter - */ - MethodEmitter dynamicRuntimeCall(final String name, final Type returnType, final RuntimeNode.Request request) { - debug("dynamic_runtime_call", name, "args=", request.getArity(), "returnType=", returnType); - final String signature = getDynamicSignature(returnType, request.getArity()); - debug(" signature", signature); - method.visitInvokeDynamicInsn(name, signature, RUNTIMEBOOTSTRAP); - pushType(returnType); - - return this; - } - - /** * Generate dynamic getter. Pop scope from stack. Push result * * @param valueType type of the value to set diff -r a8523237b66c -r fb47e4d25a9f nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/RuntimeCallSite.java --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/RuntimeCallSite.java Mon Mar 02 14:33:55 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,683 +0,0 @@ -/* - * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.nashorn.internal.codegen; - -import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup; -import static jdk.nashorn.internal.codegen.types.Type.BOOLEAN; -import static jdk.nashorn.internal.codegen.types.Type.INT; -import static jdk.nashorn.internal.lookup.Lookup.MH; - -import java.lang.invoke.CallSite; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.lang.invoke.MutableCallSite; -import java.util.HashMap; -import java.util.Map; -import jdk.nashorn.internal.codegen.CompilerConstants.Call; -import jdk.nashorn.internal.codegen.types.Type; -import jdk.nashorn.internal.ir.RuntimeNode; -import jdk.nashorn.internal.ir.RuntimeNode.Request; -import jdk.nashorn.internal.lookup.Lookup; -import jdk.nashorn.internal.runtime.ScriptRuntime; -import jdk.nashorn.internal.runtime.linker.Bootstrap; - -/** - * Optimistic call site that assumes its Object arguments to be of a boxed type. - * Gradually reverts to wider boxed types if the assumption for the RuntimeNode - * is proven wrong. Finally reverts to the generic ScriptRuntime method. - * - * This is used from the CodeGenerator when we have a runtime node, but 1 or more - * primitive arguments. This class generated appropriate specializations, for example - * {@code Object a === int b} is a good idea to specialize to {@code ((Integer)a).intValue() == b} - * surrounded by catch blocks that will try less narrow specializations - */ -public final class RuntimeCallSite extends MutableCallSite { - static final Call BOOTSTRAP = staticCallNoLookup(Bootstrap.class, "runtimeBootstrap", CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class); - - private static final MethodHandle NEXT = findOwnMH_V("next", MethodHandle.class, String.class); - - private final RuntimeNode.Request request; - - /** - * A specialized runtime node, i.e. on where we know at least one more specific type than object - */ - static final class SpecializedRuntimeNode { - private static final char REQUEST_SEPARATOR = ':'; - - private final RuntimeNode.Request request; - - private final Type[] parameterTypes; - - private final Type returnType; - - /** - * Constructor. - * - * @param request runtime node request to specialize - * @param parameterTypes parameter types of the call site - * @param returnType return type of the call site - */ - SpecializedRuntimeNode(final RuntimeNode.Request request, final Type[] parameterTypes, final Type returnType) { - this.request = request; - this.parameterTypes = parameterTypes; - this.returnType = returnType; - } - - /** - * The first type to try to use for this generated runtime node - * - * @return a type - */ - public Type firstTypeGuess() { - Type widest = Type.UNKNOWN; - for (final Type type : parameterTypes) { - if (type.isObject()) { - continue; - } - widest = Type.widest(type, widest); - } - widest = Type.widest(widest, firstTypeGuessForObject(request)); - - return widest; - } - - private static Type firstTypeGuessForObject(final Request request) { - switch (request) { - case ADD: - return INT; - default: - return BOOLEAN; - } - } - - Request getRequest() { - return request; - } - - Type[] getParameterTypes() { - return parameterTypes; - } - - Type getReturnType() { - return returnType; - } - - private static char descFor(final Type type) { - if (type.isObject()) { - return 'O'; - } - return type.getDescriptor().charAt(0); - } - - @Override - public boolean equals(final Object other) { - if (other instanceof SpecializedRuntimeNode) { - final SpecializedRuntimeNode otherNode = (SpecializedRuntimeNode)other; - - if (!otherNode.getReturnType().equals(getReturnType())) { - return false; - } - - if (getParameterTypes().length != otherNode.getParameterTypes().length) { - return false; - } - - for (int i = 0; i < getParameterTypes().length; i++) { - if (!Type.areEquivalent(getParameterTypes()[i], otherNode.getParameterTypes()[i])) { - return false; - } - } - - return otherNode.getRequest().equals(getRequest()); - } - - return false; - } - - @Override - public int hashCode() { - int hashCode = getRequest().toString().hashCode(); - hashCode ^= getReturnType().hashCode(); - for (final Type type : getParameterTypes()) { - hashCode ^= type.hashCode(); - } - return hashCode; - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder(); - sb.append(getRequest().toString()); - sb.append(REQUEST_SEPARATOR); - sb.append(descFor(getReturnType())); - - for (final Type type : getParameterTypes()) { - sb.append(descFor(type)); - } - - return sb.toString(); - } - - String getName(final Type extraType) { - return toString() + "_" + descFor(extraType); - } - - String getInitialName() { - return getName(firstTypeGuess()); - } - } - - - /** - * Constructor - * - * @param type method type for call site - * @param name name of runtime call - */ - public RuntimeCallSite(final MethodType type, final String name) { - super(type); - this.request = Request.valueOf(name.substring(0, name.indexOf(SpecializedRuntimeNode.REQUEST_SEPARATOR))); - setTarget(makeMethod(name)); - } - - private String nextName(final String requestName) { - if (requestName.equals(request.toString())) { - return null; - } - - final char[] c = requestName.toCharArray(); - final int last = c.length - 1; - - if (c[last - 1] != '_') { - return null; - } - - switch (c[last]) { - case 'Z': - c[last] = 'I'; - break; - case 'I': - c[last] = 'J'; - break; - case 'J': - c[last] = 'D'; - break; - case 'D': - default: - return request.toString(); - } - - return new String(c); - } - - private boolean isSpecialized(final String requestName) { - return nextName(requestName) != null; - } - - private MethodHandle makeMethod(final String requestName) { - MethodHandle mh; - - if (isSpecialized(requestName)) { - final Class boxedType; - final Class primitiveType; - - switch (requestName.charAt(requestName.length() - 1)) { - case 'Z': - boxedType = Boolean.class; - primitiveType = int.class; - break; - case 'I': - boxedType = Integer.class; - primitiveType = int.class; - break; - case 'J': - boxedType = Long.class; - primitiveType = long.class; - break; - case 'D': - boxedType = Number.class; - primitiveType = double.class; - break; - default: - throw new RuntimeException("should not reach here"); - } - - final boolean isStrictCmp = (request == Request.EQ_STRICT || request == Request.NE_STRICT); - - if (isStrictCmp && - (boxedType != Boolean.class && - (type().parameterType(0) == boolean.class || - type().parameterType(1) == boolean.class))) { - // number and boolean are never strictly equal, e.g. 0 !== false - mh = MH.dropArguments(MH.constant(boolean.class, request == Request.NE_STRICT), 0, type().parameterArray()); - } else { - mh = METHODS.get(request.nonStrictName() + primitiveType.getSimpleName()); - // unbox objects - - for (int i = 0; i < type().parameterCount(); i++) { - if (!type().parameterType(i).isPrimitive()) { - mh = MH.filterArguments(mh, i, UNBOX.get(boxedType)); - } - } - - mh = Lookup.filterReturnType(mh, type().returnType()); - mh = MH.explicitCastArguments(mh, type()); - } - - final MethodHandle fallback = MH.foldArguments(MethodHandles.exactInvoker(type()), MH.insertArguments(NEXT, 0, this, requestName)); - - MethodHandle guard; - if (type().parameterType(0).isPrimitive()) { - guard = MH.insertArguments( - MH.dropArguments(CHECKCAST, 1, type().parameterType(0)), 0, boxedType); - } else if (type().parameterType(1).isPrimitive()) { - guard = MH.insertArguments( - MH.dropArguments(CHECKCAST, 2, type().parameterType(1)), 0, boxedType); - } else { - assert !type().parameterType(0).isPrimitive() && !type().parameterType(1).isPrimitive(); - guard = MH.insertArguments(CHECKCAST2, 0, boxedType); - } - - if (request == Request.ADD && boxedType == Integer.class) { - // int add needs additional overflow check - MethodHandle addcheck = ADDCHECK; - for (int i = 0; i < type().parameterCount(); i++) { - if (!type().parameterType(i).isPrimitive()) { - addcheck = MH.filterArguments(addcheck, i, UNBOX.get(boxedType)); - } - } - addcheck = MH.explicitCastArguments(addcheck, type().changeReturnType(boolean.class)); - guard = MH.guardWithTest(upcastGuard(guard), addcheck, - MH.dropArguments(MH.constant(boolean.class, false), 0, type().parameterArray())); - } - - return MH.guardWithTest(upcastGuard(guard), mh, fallback); - } - - // generic fallback - return MH.explicitCastArguments(Lookup.filterReturnType(GENERIC_METHODS.get(request.name()), type().returnType()), type()); - } - - private MethodHandle upcastGuard(final MethodHandle guard) { - return MH.asType(guard, type().changeReturnType(boolean.class)); - } - - /** - * This is public just so that the generated specialization code can - * use it to get the next wider typed method - * - * Do not call directly - * - * @param name current name (with type) of runtime call at the call site - * @return next wider specialization method for this RuntimeCallSite - */ - public MethodHandle next(final String name) { - final MethodHandle next = makeMethod(nextName(name)); - setTarget(next); - return next; - } - - /** Method cache */ - private static final Map METHODS; - - /** Generic method cache */ - private static final Map GENERIC_METHODS; - - /** Unbox cache */ - private static final Map, MethodHandle> UNBOX; - - private static final MethodHandle CHECKCAST = findOwnMH_S("checkcast", boolean.class, Class.class, Object.class); - private static final MethodHandle CHECKCAST2 = findOwnMH_S("checkcast", boolean.class, Class.class, Object.class, Object.class); - private static final MethodHandle ADDCHECK = findOwnMH_S("ADDcheck", boolean.class, int.class, int.class); - - /** - * Build maps of correct boxing operations - */ - static { - UNBOX = new HashMap<>(); - UNBOX.put(Boolean.class, findOwnMH_S("unboxZ", int.class, Object.class)); - UNBOX.put(Integer.class, findOwnMH_S("unboxI", int.class, Object.class)); - UNBOX.put(Long.class, findOwnMH_S("unboxJ", long.class, Object.class)); - UNBOX.put(Number.class, findOwnMH_S("unboxD", double.class, Object.class)); - - METHODS = new HashMap<>(); - - for (final Request req : Request.values()) { - if (req.canSpecialize()) { - if (req.name().endsWith("_STRICT")) { - continue; - } - - final boolean isCmp = Request.isComparison(req); - - METHODS.put(req.name() + "int", findOwnMH_S(req.name(), (isCmp ? boolean.class : int.class), int.class, int.class)); - METHODS.put(req.name() + "long", findOwnMH_S(req.name(), (isCmp ? boolean.class : long.class), long.class, long.class)); - METHODS.put(req.name() + "double", findOwnMH_S(req.name(), (isCmp ? boolean.class : double.class), double.class, double.class)); - } - } - - GENERIC_METHODS = new HashMap<>(); - for (final Request req : Request.values()) { - if (req.canSpecialize()) { - GENERIC_METHODS.put(req.name(), MH.findStatic(MethodHandles.lookup(), ScriptRuntime.class, req.name(), - MH.type(req.getReturnType().getTypeClass(), Object.class, Object.class))); - } - } - } - - /** - * Specialized version of != operator for two int arguments. Do not call directly. - * @param a int - * @param b int - * @return a != b - */ - public static boolean NE(final int a, final int b) { - return a != b; - } - - /** - * Specialized version of != operator for two double arguments. Do not call directly. - * @param a double - * @param b double - * @return a != b - */ - public static boolean NE(final double a, final double b) { - return a != b; - } - - /** - * Specialized version of != operator for two long arguments. Do not call directly. - * @param a long - * @param b long - * @return a != b - */ - public static boolean NE(final long a, final long b) { - return a != b; - } - - /** - * Specialized version of == operator for two int arguments. Do not call directly. - * @param a int - * @param b int - * @return a == b - */ - public static boolean EQ(final int a, final int b) { - return a == b; - } - - /** - * Specialized version of == operator for two double arguments. Do not call directly. - * @param a double - * @param b double - * @return a == b - */ - public static boolean EQ(final double a, final double b) { - return a == b; - } - - /** - * Specialized version of == operator for two long arguments. Do not call directly. - * @param a long - * @param b long - * @return a == b - */ - public static boolean EQ(final long a, final long b) { - return a == b; - } - - /** - * Specialized version of {@literal <} operator for two int arguments. Do not call directly. - * @param a int - * @param b int - * @return a {@code <} b - */ - public static boolean LT(final int a, final int b) { - return a < b; - } - - /** - * Specialized version of {@literal <} operator for two double arguments. Do not call directly. - * @param a double - * @param b double - * @return a {@literal <} b - */ - public static boolean LT(final double a, final double b) { - return a < b; - } - - /** - * Specialized version of {@literal <} operator for two long arguments. Do not call directly. - * @param a long - * @param b long - * @return a {@literal <} b - */ - public static boolean LT(final long a, final long b) { - return a < b; - } - - /** - * Specialized version of {@literal <=} operator for two int arguments. Do not call directly. - * @param a int - * @param b int - * @return a {@literal <=} b - */ - public static boolean LE(final int a, final int b) { - return a <= b; - } - - /** - * Specialized version of {@literal <=} operator for two double arguments. Do not call directly. - * @param a double - * @param b double - * @return a {@literal <=} b - */ - public static boolean LE(final double a, final double b) { - return a <= b; - } - - /** - * Specialized version of {@literal <=} operator for two long arguments. Do not call directly. - * @param a long - * @param b long - * @return a {@literal <=} b - */ - public static boolean LE(final long a, final long b) { - return a <= b; - } - - /** - * Specialized version of {@literal >} operator for two int arguments. Do not call directly. - * @param a int - * @param b int - * @return a {@literal >} b - */ - public static boolean GT(final int a, final int b) { - return a > b; - } - - /** - * Specialized version of {@literal >} operator for two double arguments. Do not call directly. - * @param a double - * @param b double - * @return a {@literal >} b - */ - public static boolean GT(final double a, final double b) { - return a > b; - } - - /** - * Specialized version of {@literal >} operator for two long arguments. Do not call directly. - * @param a long - * @param b long - * @return a {@literal >} b - */ - public static boolean GT(final long a, final long b) { - return a > b; - } - - /** - * Specialized version of {@literal >=} operator for two int arguments. Do not call directly. - * @param a int - * @param b int - * @return a {@literal >=} b - */ - public static boolean GE(final int a, final int b) { - return a >= b; - } - - /** - * Specialized version of {@literal >=} operator for two double arguments. Do not call directly. - * @param a double - * @param b double - * @return a {@literal >=} b - */ - public static boolean GE(final double a, final double b) { - return a >= b; - } - - /** - * Specialized version of {@literal >=} operator for two long arguments. Do not call directly. - * @param a long - * @param b long - * @return a {@code >=} b - */ - public static boolean GE(final long a, final long b) { - return a >= b; - } - - /** - * Specialized version of + operator for two int arguments. Do not call directly. - * @param a int - * @param b int - * @return a + b - */ - public static int ADD(final int a, final int b) { - return a + b; - } - - /** - * Specialized version of + operator for two long arguments. Do not call directly. - * @param a long - * @param b long - * @return a + b - */ - public static long ADD(final long a, final long b) { - return a + b; - } - - /** - * Specialized version of + operator for two double arguments. Do not call directly. - * @param a double - * @param b double - * @return a + b - */ - public static double ADD(final double a, final double b) { - return a + b; - } - - /** - * Check that ints are addition compatible, i.e. their sum is equal to the sum - * of them cast to long. Otherwise the addition will overflow. Do not call directly. - * - * @param a int - * @param b int - * - * @return true if addition does not overflow - */ - public static boolean ADDcheck(final int a, final int b) { - return (a + b == (long)a + (long)b); - } - - /** - * Checkcast used for specialized ops. Do not call directly - * - * @param type to to check against - * @param obj object to check for type - * - * @return true if type check holds - */ - public static boolean checkcast(final Class type, final Object obj) { - return type.isInstance(obj); - } - - /** - * Checkcast used for specialized ops. Do not call directly - * - * @param type type to check against - * @param objA first object to check against type - * @param objB second object to check against type - * - * @return true if type check holds for both objects - */ - public static boolean checkcast(final Class type, final Object objA, final Object objB) { - return type.isInstance(objA) && type.isInstance(objB); - } - - /** - * Unbox a java.lang.Boolean. Do not call directly - * @param obj object to cast to int and unbox - * @return an int value for the boolean, 1 is true, 0 is false - */ - public static int unboxZ(final Object obj) { - return (boolean)obj ? 1 : 0; - } - - /** - * Unbox a java.lang.Integer. Do not call directly - * @param obj object to cast to int and unbox - * @return an int - */ - public static int unboxI(final Object obj) { - return (int)obj; - } - - /** - * Unbox a java.lang.Long. Do not call directly - * @param obj object to cast to long and unbox - * @return a long - */ - public static long unboxJ(final Object obj) { - return (long)obj; - } - - /** - * Unbox a java.lang.Number. Do not call directly - * @param obj object to cast to Number and unbox - * @return a double - */ - public static double unboxD(final Object obj) { - return ((Number)obj).doubleValue(); - } - - private static MethodHandle findOwnMH_S(final String name, final Class rtype, final Class... types) { - return MH.findStatic(MethodHandles.lookup(), RuntimeCallSite.class, name, MH.type(rtype, types)); - } - - private static MethodHandle findOwnMH_V(final String name, final Class rtype, final Class... types) { - return MH.findVirtual(MethodHandles.lookup(), RuntimeCallSite.class, name, MH.type(rtype, types)); - } -} diff -r a8523237b66c -r fb47e4d25a9f nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/BinaryNode.java --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/BinaryNode.java Mon Mar 02 14:33:55 2015 +0100 +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/BinaryNode.java Thu Mar 05 15:43:43 2015 +0100 @@ -98,7 +98,7 @@ } /** - * Returns true if the node is a comparison operation. + * Returns true if the node is a comparison operation (either equality, inequality, or relational). * @return true if the node is a comparison operation. */ public boolean isComparison() { @@ -118,6 +118,22 @@ } /** + * Returns true if the node is a relational operation (less than (or equals), greater than (or equals)). + * @return true if the node is a relational operation. + */ + public boolean isRelational() { + switch (tokenType()) { + case LT: + case GT: + case LE: + case GE: + return true; + default: + return false; + } + } + + /** * Returns true if the node is a logical operation. * @return true if the node is a logical operation. */ diff -r a8523237b66c -r fb47e4d25a9f nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/RuntimeNode.java --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/RuntimeNode.java Mon Mar 02 14:33:55 2015 +0100 +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/RuntimeNode.java Thu Mar 05 15:43:43 2015 +0100 @@ -25,8 +25,6 @@ package jdk.nashorn.internal.ir; -import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT; - import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -39,7 +37,7 @@ * IR representation for a runtime call. */ @Immutable -public class RuntimeNode extends Expression implements Optimistic { +public class RuntimeNode extends Expression { private static final long serialVersionUID = 1L; /** @@ -333,11 +331,6 @@ /** Call arguments. */ private final List args; - /** is final - i.e. may not be removed again, lower in the code pipeline */ - private final boolean isFinal; - - private final int programPoint; - /** * Constructor * @@ -351,17 +344,13 @@ this.request = request; this.args = args; - this.isFinal = false; - this.programPoint = INVALID_PROGRAM_POINT; } - private RuntimeNode(final RuntimeNode runtimeNode, final Request request, final boolean isFinal, final List args, final int programPoint) { + private RuntimeNode(final RuntimeNode runtimeNode, final Request request, final List args) { super(runtimeNode); this.request = request; this.args = args; - this.isFinal = isFinal; - this.programPoint = programPoint; } /** @@ -399,8 +388,6 @@ this.request = request; this.args = args; - this.isFinal = false; - this.programPoint = parent instanceof Optimistic ? ((Optimistic)parent).getProgramPoint() : INVALID_PROGRAM_POINT; } /** @@ -428,32 +415,11 @@ * @return new runtime node or same if same request */ public RuntimeNode setRequest(final Request request) { - if (this.request == request) { - return this; - } - return new RuntimeNode(this, request, isFinal, args, programPoint); - } - - - /** - * Is this node final - i.e. it can never be replaced with other nodes again - * @return true if final - */ - public boolean isFinal() { - return isFinal; - } - - /** - * Flag this node as final - i.e it may never be replaced with other nodes again - * @param isFinal is the node final, i.e. can not be removed and replaced by a less generic one later in codegen - * @return same runtime node if already final, otherwise a new one - */ - public RuntimeNode setIsFinal(final boolean isFinal) { - if (this.isFinal == isFinal) { + if (this.request == request) { return this; } - return new RuntimeNode(this, request, isFinal, args, programPoint); - } + return new RuntimeNode(this, request, args); + } /** * Return type for the ReferenceNode @@ -510,7 +476,7 @@ if (this.args == args) { return this; } - return new RuntimeNode(this, request, isFinal, args, programPoint); + return new RuntimeNode(this, request, args); } /** @@ -536,39 +502,4 @@ } return true; } - -//TODO these are blank for now: - - @Override - public int getProgramPoint() { - return programPoint; - } - - @Override - public RuntimeNode setProgramPoint(final int programPoint) { - if(this.programPoint == programPoint) { - return this; - } - return new RuntimeNode(this, request, isFinal, args, programPoint); - } - - @Override - public boolean canBeOptimistic() { - return false; - } - - @Override - public Type getMostOptimisticType() { - return getType(); - } - - @Override - public Type getMostPessimisticType() { - return getType(); - } - - @Override - public RuntimeNode setType(final Type type) { - return this; - } } diff -r a8523237b66c -r fb47e4d25a9f nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/JSType.java --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/JSType.java Mon Mar 02 14:33:55 2015 +0100 +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/JSType.java Thu Mar 05 15:43:43 2015 +0100 @@ -761,6 +761,36 @@ return toNumberGeneric(obj); } + /** + * Converts an object for a comparison with a number. Almost identical to {@link #toNumber(Object)} but + * converts {@code null} to {@code NaN} instead of zero, so it won't compare equal to zero. + * + * @param obj an object + * + * @return a number + */ + public static double toNumberForEq(final Object obj) { + return obj == null ? Double.NaN : toNumber(obj); + } + + /** + * Converts an object for strict comparison with a number. Returns {@code NaN} for any object that is not + * a {@link Number}, so only boxed numerics can compare strictly equal to numbers. + * + * @param obj an object + * + * @return a number + */ + public static double toNumberForStrictEq(final Object obj) { + if (obj instanceof Double) { + return (Double)obj; + } + if (obj instanceof Number) { + return ((Number)obj).doubleValue(); + } + return Double.NaN; + } + /** * JavaScript compliant conversion of Boolean to number diff -r a8523237b66c -r fb47e4d25a9f nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptRuntime.java --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptRuntime.java Mon Mar 02 14:33:55 2015 +0100 +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptRuntime.java Thu Mar 05 15:43:43 2015 +0100 @@ -536,8 +536,6 @@ /** * ECMA 11.6.1 - The addition operator (+) - generic implementation - * Compiler specializes using {@link jdk.nashorn.internal.codegen.RuntimeCallSite} - * if any type information is available for any of the operands * * @param x first term * @param y second term @@ -953,8 +951,15 @@ * @return true if x is less than y */ public static boolean LT(final Object x, final Object y) { - final Object value = lessThan(x, y, true); - return value == UNDEFINED ? false : (Boolean)value; + final Object px = JSType.toPrimitive(x, Number.class); + final Object py = JSType.toPrimitive(y, Number.class); + + return areBothString(px, py) ? px.toString().compareTo(py.toString()) < 0 : + JSType.toNumber(px) < JSType.toNumber(py); + } + + private static boolean areBothString(final Object x, final Object y) { + return isString(x) && isString(y); } /** @@ -966,8 +971,11 @@ * @return true if x is greater than y */ public static boolean GT(final Object x, final Object y) { - final Object value = lessThan(y, x, false); - return value == UNDEFINED ? false : (Boolean)value; + final Object px = JSType.toPrimitive(x, Number.class); + final Object py = JSType.toPrimitive(y, Number.class); + + return areBothString(px, py) ? px.toString().compareTo(py.toString()) > 0 : + JSType.toNumber(px) > JSType.toNumber(py); } /** @@ -979,8 +987,11 @@ * @return true if x is less than or equal to y */ public static boolean LE(final Object x, final Object y) { - final Object value = lessThan(y, x, false); - return !(Boolean.TRUE.equals(value) || value == UNDEFINED); + final Object px = JSType.toPrimitive(x, Number.class); + final Object py = JSType.toPrimitive(y, Number.class); + + return areBothString(px, py) ? px.toString().compareTo(py.toString()) <= 0 : + JSType.toNumber(px) <= JSType.toNumber(py); } /** @@ -992,48 +1003,11 @@ * @return true if x is greater than or equal to y */ public static boolean GE(final Object x, final Object y) { - final Object value = lessThan(x, y, true); - return !(Boolean.TRUE.equals(value) || value == UNDEFINED); - } - - /** ECMA 11.8.5 The Abstract Relational Comparison Algorithm */ - private static Object lessThan(final Object x, final Object y, final boolean leftFirst) { - Object px, py; - - //support e.g. x < y should throw exception correctly if x or y are not numeric - if (leftFirst) { - px = JSType.toPrimitive(x, Number.class); - py = JSType.toPrimitive(y, Number.class); - } else { - py = JSType.toPrimitive(y, Number.class); - px = JSType.toPrimitive(x, Number.class); - } + final Object px = JSType.toPrimitive(x, Number.class); + final Object py = JSType.toPrimitive(y, Number.class); - if (isString(px) && isString(py)) { - // May be String or ConsString - return px.toString().compareTo(py.toString()) < 0; - } - - final double nx = JSType.toNumber(px); - final double ny = JSType.toNumber(py); - - if (Double.isNaN(nx) || Double.isNaN(ny)) { - return UNDEFINED; - } - - if (nx == ny) { - return false; - } - - if (nx > 0 && ny > 0 && Double.isInfinite(nx) && Double.isInfinite(ny)) { - return false; - } - - if (nx < 0 && ny < 0 && Double.isInfinite(nx) && Double.isInfinite(ny)) { - return false; - } - - return nx < ny; + return areBothString(px, py) ? px.toString().compareTo(py.toString()) >= 0 : + JSType.toNumber(px) >= JSType.toNumber(py); } /** @@ -1046,9 +1020,7 @@ final Context context = Context.getContextTrusted(); final SwitchPoint sp = context.getBuiltinSwitchPoint(name); assert sp != null; - if (sp != null) { - context.getLogger(ApplySpecialization.class).info("Overwrote special name '" + name +"' - invalidating switchpoint"); - SwitchPoint.invalidateAll(new SwitchPoint[] { sp }); - } + context.getLogger(ApplySpecialization.class).info("Overwrote special name '" + name +"' - invalidating switchpoint"); + SwitchPoint.invalidateAll(new SwitchPoint[] { sp }); } } diff -r a8523237b66c -r fb47e4d25a9f nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/Bootstrap.java --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/Bootstrap.java Mon Mar 02 14:33:55 2015 +0100 +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/Bootstrap.java Thu Mar 05 15:43:43 2015 +0100 @@ -48,7 +48,6 @@ import jdk.nashorn.api.scripting.JSObject; import jdk.nashorn.internal.codegen.CompilerConstants.Call; import jdk.nashorn.internal.codegen.ObjectClassGenerator; -import jdk.nashorn.internal.codegen.RuntimeCallSite; import jdk.nashorn.internal.lookup.MethodHandleFactory; import jdk.nashorn.internal.lookup.MethodHandleFunctionality; import jdk.nashorn.internal.objects.ScriptFunctionImpl; @@ -210,19 +209,6 @@ } /** - * Bootstrapper for a specialized Runtime call - * - * @param lookup lookup - * @param initialName initial name for callsite - * @param type method type for call site - * - * @return callsite for a runtime node - */ - public static CallSite runtimeBootstrap(final MethodHandles.Lookup lookup, final String initialName, final MethodType type) { - return new RuntimeCallSite(type, initialName); - } - - /** * Boostrapper for math calls that may overflow * @param lookup lookup * @param name name of operation diff -r a8523237b66c -r fb47e4d25a9f nashorn/test/script/basic/JDK-8035712.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nashorn/test/script/basic/JDK-8035712.js Thu Mar 05 15:43:43 2015 +0100 @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2015 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * JDK-8035712: Restore some of the RuntimeCallSite specializations + * + * @test + * @run + */ + +if ((typeof Assert) == "undefined") { + Assert = { + assertTrue: function(x) { if(!x) { throw "expected true" } }, + assertFalse: function(x) { if(x) { throw "expected false" } }, + }; +} + +function nop() {} + +function EQ(x, y) { + // Exercise normal evaluation + Assert.assertTrue (x == y); + Assert.assertTrue (y == x); + Assert.assertFalse(x != y); + Assert.assertFalse(y != x); + // Exercise the branch optimizer + if (x == y) { nop(); } else { Assert.fail(); } + if (y == x) { nop(); } else { Assert.fail(); } + if (x != y) { Assert.fail(); } else { nop(); } + if (y != x) { Assert.fail(); } else { nop(); } +} + +function NE(x, y) { + // Exercise normal evaluation + Assert.assertTrue (x != y); + Assert.assertTrue (y != x); + Assert.assertFalse(x == y); + Assert.assertFalse(y == x); + // Exercise the branch optimizer + if (x != y) { nop(); } else { Assert.fail(); } + if (y != x) { nop(); } else { Assert.fail(); } + if (x == y) { Assert.fail(); } else { nop(); } + if (y == x) { Assert.fail(); } else { nop(); } +} + +function STRICT_EQ(x, y) { + // Exercise normal evaluation + Assert.assertTrue (x === y); + Assert.assertTrue (y === x); + Assert.assertFalse(x !== y); + Assert.assertFalse(y !== x); + // Exercise the branch optimizer + if (x === y) { nop(); } else { Assert.fail(); } + if (y === x) { nop(); } else { Assert.fail(); } + if (x !== y) { Assert.fail(); } else { nop(); } + if (y !== x) { Assert.fail(); } else { nop(); } +} + +function STRICT_NE(x, y) { + // Exercise normal evaluation + Assert.assertTrue (x !== y); + Assert.assertTrue (y !== x); + Assert.assertFalse(x === y); + Assert.assertFalse(y === x); + // Exercise the branch optimizer + if (x !== y) { nop(); } else { Assert.fail(); } + if (y !== x) { nop(); } else { Assert.fail(); } + if (x === y) { Assert.fail(); } else { nop(); } + if (y === x) { Assert.fail(); } else { nop(); } +} + +function cmpToAnyNumber(cmp, value) { + cmp(1, value); + cmp(4294967296, value); + cmp(1.2, value); + cmp(Infinity, value); + cmp(-Infinity, value); + cmp(1/Infinity, value); + cmp(0, value); + cmp(-0, value); + cmp(true, value); + cmp(false, value); +} + +function notEqualToAnyNumber(value) { + cmpToAnyNumber(NE, value); + cmpToAnyNumber(STRICT_NE, value); +} + +notEqualToAnyNumber(null); +notEqualToAnyNumber(void 0); +notEqualToAnyNumber("abc"); +notEqualToAnyNumber({}); +notEqualToAnyNumber(["xyz"]); + +function objectWithPrimitiveFunctionNotEqualToAnyNumber(fnName) { + var obj = { + count: 0 + }; + obj[fnName] = function() { this.count++; return "foo"; }; + notEqualToAnyNumber(obj); + // Every NE will invoke it 8 times; cmpToAnyNumber has 10 comparisons + // STRICT_NE doesn't invoke toString. + Assert.assertTrue(80 === obj.count); +} +objectWithPrimitiveFunctionNotEqualToAnyNumber("valueOf"); +objectWithPrimitiveFunctionNotEqualToAnyNumber("toString"); + +function objectEqualButNotStrictlyEqual(val, obj) { + EQ(val, obj); + STRICT_NE(val, obj); +} + +function numberEqualButNotStrictlyEqualToObject(num, obj) { + objectEqualButNotStrictlyEqual(num, obj); + objectEqualButNotStrictlyEqual(num, [obj]); + objectEqualButNotStrictlyEqual(num, [[obj]]); +} + +function numberEqualButNotStrictlyEqualToZeroObjects(num) { + numberEqualButNotStrictlyEqualToObject(num, [0]); + numberEqualButNotStrictlyEqualToObject(num, ""); + numberEqualButNotStrictlyEqualToObject(num, []); + numberEqualButNotStrictlyEqualToObject(num, "0"); +} + +numberEqualButNotStrictlyEqualToZeroObjects(0); +numberEqualButNotStrictlyEqualToZeroObjects(1/Infinity); +numberEqualButNotStrictlyEqualToZeroObjects(false); + +function numberEqualButNotStrictlyEqualToObjectEquivalent(num) { + var str = String(num); + objectEqualButNotStrictlyEqual(num, str); + objectEqualButNotStrictlyEqual(num, { valueOf: function() { return str }}); + objectEqualButNotStrictlyEqual(num, { toString: function() { return str }}); + objectEqualButNotStrictlyEqual(num, { valueOf: function() { return num }}); + objectEqualButNotStrictlyEqual(num, { toString: function() { return num }}); +} + +numberEqualButNotStrictlyEqualToObjectEquivalent(1); +numberEqualButNotStrictlyEqualToObjectEquivalent(4294967296); +numberEqualButNotStrictlyEqualToObjectEquivalent(1.2); +numberEqualButNotStrictlyEqualToObjectEquivalent(Infinity); +numberEqualButNotStrictlyEqualToObjectEquivalent(-Infinity); +numberEqualButNotStrictlyEqualToObjectEquivalent(1/Infinity); +numberEqualButNotStrictlyEqualToObjectEquivalent(0); +numberEqualButNotStrictlyEqualToObjectEquivalent(-0); + +STRICT_EQ(1, new java.lang.Integer(1)); +STRICT_EQ(1, new java.lang.Double(1)); +STRICT_EQ(1.2, new java.lang.Double(1.2)); + +function LE(x, y) { + // Exercise normal evaluation + Assert.assertTrue(x <= y); + Assert.assertTrue(y >= x); + Assert.assertFalse(x > y); + Assert.assertFalse(x < y); + // Exercise the branch optimizer + if (x <= y) { nop(); } else { Assert.fail(); } + if (y >= x) { nop(); } else { Assert.fail(); } + if (x > y) { Assert.fail(); } else { nop(); } + if (y < x) { Assert.fail(); } else { nop(); } +} + +function mutuallyLessThanOrEqual(x, y) { + LE(x, y); + LE(y, x); +} + +mutuallyLessThanOrEqual(0, null); +mutuallyLessThanOrEqual(false, null); +mutuallyLessThanOrEqual(1/Infinity, null); + +function mutuallyLessThanEqualToObjectWithValue(num, val) { + mutuallyLessThanOrEqual(num, { valueOf: function() { return val } }); + mutuallyLessThanOrEqual(num, { toString: function() { return val } }); +} + +mutuallyLessThanEqualToObjectWithValue(false, 0); +mutuallyLessThanEqualToObjectWithValue(false, ""); + +mutuallyLessThanEqualToObjectWithValue(true, 1); +mutuallyLessThanEqualToObjectWithValue(true, "1"); + +function lessThanEqualToObjectEquivalent(num) { + var str = String(num); + mutuallyLessThanOrEqual(num, str); + mutuallyLessThanEqualToObjectWithValue(num, num); + mutuallyLessThanEqualToObjectWithValue(num, str); +} + +lessThanEqualToObjectEquivalent(1); +lessThanEqualToObjectEquivalent(4294967296); +lessThanEqualToObjectEquivalent(1.2); +lessThanEqualToObjectEquivalent(Infinity); +lessThanEqualToObjectEquivalent(-Infinity); +lessThanEqualToObjectEquivalent(1/Infinity); +lessThanEqualToObjectEquivalent(0); +lessThanEqualToObjectEquivalent(-0); + +function INCOMPARABLE(x, y) { + // Exercise normal evaluation + Assert.assertFalse(x < y); + Assert.assertFalse(x > y); + Assert.assertFalse(x <= y); + Assert.assertFalse(x >= y); + Assert.assertFalse(y < x); + Assert.assertFalse(y > x); + Assert.assertFalse(y <= x); + Assert.assertFalse(y >= x); + // Exercise the branch optimizer + if (x < y) { Assert.fail(); } else { nop(); } + if (x > y) { Assert.fail(); } else { nop(); } + if (x <= y) { Assert.fail(); } else { nop(); } + if (x >= y) { Assert.fail(); } else { nop(); } + if (y < x) { Assert.fail(); } else { nop(); } + if (y > x) { Assert.fail(); } else { nop(); } + if (y <= x) { Assert.fail(); } else { nop(); } + if (y >= x) { Assert.fail(); } else { nop(); } +} + +function isIncomparable(value) { + cmpToAnyNumber(INCOMPARABLE, value); +} + +isIncomparable(void 0); +isIncomparable({ valueOf: function() { return NaN }}); +isIncomparable({ toString: function() { return NaN }}); + +// Force ScriptRuntime.LT(Object, Object) etc. comparisons +function cmpObj(fn, x, y) { + fn({valueOf: function() { return x }}, {valueOf: function() { return y }}); +} + +function LT(x, y) { + Assert.assertTrue(x < y); + Assert.assertTrue(y > x); + Assert.assertFalse(x >= y); + Assert.assertFalse(y <= x); +} + +cmpObj(LT, 1, 2); +cmpObj(LT, 1, "2"); +cmpObj(LT, "1", 2); +cmpObj(LT, "a", "b"); +cmpObj(LT, -Infinity, 0); +cmpObj(LT, 0, Infinity); +cmpObj(LT, -Infinity, Infinity); +cmpObj(INCOMPARABLE, 1, NaN); +cmpObj(INCOMPARABLE, NaN, NaN); +cmpObj(INCOMPARABLE, "boo", NaN); +cmpObj(INCOMPARABLE, 1, "boo"); // boo number value will be NaN + +// Test that a comparison call site can deoptimize from (int, int) to (object, object) +(function(){ + var x = [1, 2, "a"]; + var y = [2, "3", "b"]; + for(var i = 0; i < 3; ++i) { + Assert.assertTrue(x[i] < y[i]); + } +})();